mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 04:04:21 +02:00
Skip the Frame-side status probe test on Windows; new README and docs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f6e77cd98c
commit
07de29d58a
6 files changed
+395
-214
No files matched your search
@@ -0,0 +1,133 @@
|
||||
# Frame Control in detail
|
||||
|
||||
What each part of the app does, how it works, and what has been checked on a
|
||||
real Frame. For installing it, see the [README](../README.md#install).
|
||||
|
||||
As of 2026-09-25 no other desktop app manages the Frame end to end.
|
||||
[Stream Frame](https://streamframe.app/) (macOS 14+, free) records and screenshots
|
||||
the headset over SSH. [FrameDrop](https://framedropvr.com) sideloads but is
|
||||
Windows-only. Steam Link views the headset.
|
||||
|
||||
You can also run the same UI in a browser without the app, from a checkout on
|
||||
macOS or Linux:
|
||||
|
||||
```sh
|
||||
./scripts/frame-ui.sh # opens http://127.0.0.1:47810 in its own window
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Headset view**: what the lenses show, as SteamVR composites it (the room,
|
||||
floating panels, dashboard and controllers). Shows the left eye, like pointing
|
||||
a camera into one lens, or both eyes, as a single shot; saves as PNG. **Live**
|
||||
is 720p video at about 30 fps: `ffmpeg` on the Frame encodes SteamVR's
|
||||
headset-view device (`/dev/video99`) to H.264 over SSH, and the page decodes
|
||||
it with WebCodecs. Live video is one eye; Capture still gets both. The viewer
|
||||
fits the whole frame; zoom with − / + (or scroll, or double-click), drag to
|
||||
pan, `0` to fit, `F` for full screen. Capture uses OpenVR's `IVRScreenshots`
|
||||
API through Python `ctypes` (`ui/frame_vrshot.py`). Nothing extra is
|
||||
installed on the Frame (SteamOS ships `ffmpeg`). **Desktop panel** captures
|
||||
gamescope's flat layer instead.
|
||||
- **Screenshots** you take in the headset with Steam's shortcut: browse them and
|
||||
save them to `~/Pictures/SteamFrame`.
|
||||
- **Battery** with charging state: charge rate in watts, time to full or empty,
|
||||
charger type and wattage (for example USB-C PD 20 W), and battery temperature.
|
||||
- **Status**: storage, memory, temperature, Wi-Fi, uptime, and whether SteamVR,
|
||||
the desktop, Lepton and xrdp are running.
|
||||
- **Library** shelf with Steam cover art and a Play button (`steam://rungameid`).
|
||||
- **Get games**: every game you own with its Steam Frame rating (Verified,
|
||||
Playable, Unsupported, Unknown). Install on Frame downloads it to the headset
|
||||
with live progress. Search the Steam store with prices and Frame ratings; Buy
|
||||
opens the store page in your browser, or Store on Frame opens it in the
|
||||
headset. It drives the Frame's own Steam client through its DevTools port;
|
||||
see [steam-games.md](steam-games.md).
|
||||
- **Volume** and mute (`wpctl`).
|
||||
- **Android apps**: search about 4,500 F-Droid apps rated for the Frame, install
|
||||
one with a click as its own Lepton instance (it keeps its data and shows in the
|
||||
Steam library), then launch, stop, test or remove it. **Report an APK** records
|
||||
whether any APK worked (F-Droid or not: pick a file, type a package, or use an
|
||||
installed app). Your reports are saved on your computer and change the verdicts
|
||||
you see. They aren't uploaded anywhere: the shared database is maintainer-only
|
||||
for now (see [compat-db/README.md](../compat-db/README.md)). Needs `adb`, and
|
||||
`aapt2` for reading APK files.
|
||||
- **Android display**: pick a running Lepton instance (by the app in it) and set
|
||||
its resolution (Native 1920×1080, or Sharp 2560×1440 with density scaled to
|
||||
match), UI scale (Smaller / Default / Larger, or an exact dpi) and text size
|
||||
(0.85–1.3×) over ADB (`wm size`, `wm density`, `font_scale`). Reset puts all
|
||||
three back. Whether the settings survive the app relaunching is untested.
|
||||
- **Transfer**: drag and drop files to `~/Downloads`; `.apk` files install as
|
||||
their own Android app. Send typed text, or your computer's clipboard, to the
|
||||
Frame clipboard.
|
||||
- **Flatpaks**: install and remove them (quick picks: Moonlight, Firefox, VLC,
|
||||
Remmina).
|
||||
- **One-click tools**: SSH or SFTP in a terminal window, Steam Link, and remote
|
||||
desktop (Windows App on macOS, Remote Desktop on Windows, Remmina or FreeRDP on
|
||||
Linux). Sleep, restart and shut down open a terminal window because SteamOS
|
||||
asks for the sudo password over SSH.
|
||||
|
||||
## How it works
|
||||
|
||||
`app/` is an Electron shell. It starts `ui/server.py` on a free loopback port
|
||||
and shows it in its own window; the server stops when you quit the app. The
|
||||
app bundles `ui/`, `scripts/`, `frame/android/` and the rated catalogue from
|
||||
`apk-catalog/`, and on Windows an embedded Python too.
|
||||
|
||||
The server is Python stdlib only and listens on 127.0.0.1. It rejects requests
|
||||
with a non-local `Host` header, and any `/api/` request without a custom
|
||||
header, so other websites can't drive it or read captures. Everything reaches
|
||||
the headset through the `frame` SSH alias. On macOS and Linux it keeps one
|
||||
multiplexed SSH connection open, so status and each capture take about 0.3 s.
|
||||
Windows' OpenSSH can't share a connection, so there each request connects on
|
||||
its own and the app is a little slower. What differs between the three
|
||||
systems lives in `ui/frame_host.py`.
|
||||
|
||||
Headset captures are deleted from the Frame as soon as they're copied, because
|
||||
they show everything on screen, including anything private. The look follows
|
||||
the Steam client: its palette, Motiva Sans (loaded from Valve's CDN), portrait
|
||||
library capsules and green Play buttons.
|
||||
|
||||
**Verified on the Frame 2026-09-25 (macOS app):** status and charging details,
|
||||
both capture modes (headset view while in use, and a blank frame in standby,
|
||||
which the UI labels), clipboard, volume, file push, and input validation.
|
||||
**Not yet exercised from the UI:** Launch, Flatpak install/remove, APK drop,
|
||||
and the power buttons. Each of these calls a command that was verified
|
||||
separately.
|
||||
|
||||
## Per-platform notes
|
||||
|
||||
**macOS.** The app reads `PATH` from your login shell, so Homebrew's `rsync`,
|
||||
`adb` and Python work when you launch it from Finder. Set Up Connection runs
|
||||
`scripts/connect.sh` in Terminal. The log is at
|
||||
`~/Library/Logs/Frame Control/server.log`. The build is ad-hoc signed and not
|
||||
notarized: a downloaded copy is quarantined until you run
|
||||
`xattr -dr com.apple.quarantine "/Applications/Frame Control.app"`. The first
|
||||
time you use them, macOS asks to allow local network access (for SSH) and
|
||||
control of Terminal (for SSH and power actions).
|
||||
|
||||
**Windows.** Python is bundled; `ssh` is Windows' built-in OpenSSH client
|
||||
(Settings → System → Optional features, if it's been removed). Set Up
|
||||
Connection runs `ui/frame_connect.py` in a console window. Copies use `scp`
|
||||
because Windows has no `rsync`. The installer isn't code-signed, so SmartScreen
|
||||
warns on first run: choose **More info → Run anyway**. The log is at
|
||||
`%APPDATA%\Frame Control\logs\server.log`.
|
||||
|
||||
**Linux.** Needs `python3` (3.8 or later) and `ssh`, which most desktops
|
||||
have. The AppImage runs anywhere; the `.deb` pulls both in on Debian and
|
||||
Ubuntu. Set Up Connection runs `ui/frame_connect.py` in your terminal emulator
|
||||
(GNOME Terminal, Konsole, xterm and others). Sending the clipboard needs
|
||||
`wl-clipboard` (Wayland) or `xclip` (X11). The log is at
|
||||
`~/.config/Frame Control/logs/server.log`.
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
cd app
|
||||
npm install
|
||||
npm start # run from the checkout without packaging
|
||||
npm run dist # macOS: dist/*.dmg and .zip (Apple Silicon)
|
||||
npm run dist:win # Windows: installer and .zip (fetches the embedded Python first)
|
||||
npm run dist:linux # Linux: AppImage and .deb, x64 and arm64
|
||||
```
|
||||
|
||||
Pushing a `v*` tag builds all three in GitHub Actions and attaches them to the
|
||||
release (`.github/workflows/release.yml`).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
+103
@@ -0,0 +1,103 @@
|
||||
# Scripts and headset setup
|
||||
|
||||
The command-line side of this repo: how SSH gets set up with as little typing on
|
||||
the headset as possible, what to use for each job, and the helper scripts that
|
||||
Frame Control is built on. The scripts are zsh/bash and run on macOS; most also
|
||||
run on Linux. On Windows, use the app.
|
||||
|
||||
## Minimum typing on the headset
|
||||
|
||||
Valve's own developer docs say SSH, ADB, and RDP are all turned on through a
|
||||
**UI toggle**. You don't need a terminal, `passwd`, or `systemctl`. The only
|
||||
thing you type on the headset is a password you choose.
|
||||
|
||||
On the Frame:
|
||||
|
||||
1. **Steam Settings → System → Enable Developer Mode** (a toggle, no typing).
|
||||
2. Scroll down to the **Developer** section and click **Set User Password**.
|
||||
Type a password. **This is the only thing you type on the headset.** Pick
|
||||
something short, because you'll type it once more on the Mac and then
|
||||
never again.
|
||||
3. (Optional, no typing) Note the IP address from **Quick Settings** or
|
||||
**Steam Settings → Internet**, in case `frame.local` doesn't resolve.
|
||||
4. (Optional) Check **Steam Settings → System → Hostname**. Leaving it as
|
||||
`frame` means the scripts work without any extra setup.
|
||||
|
||||
On the Mac:
|
||||
|
||||
To use the scripts from a checkout instead of the app:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/saphid/steam-frame.git && cd steam-frame
|
||||
./scripts/connect.sh # or: ./scripts/connect.sh 192.168.1.50
|
||||
ssh frame # passwordless from now on
|
||||
```
|
||||
|
||||
`connect.sh` does four things:
|
||||
|
||||
- finds the headset (`frame.local`, then `frame`, or the IP/host you pass in)
|
||||
- creates a dedicated key (`~/.ssh/id_ed25519_frame`)
|
||||
- adds a `Host frame` block to `~/.ssh/config`
|
||||
- runs `ssh-copy-id`, which asks for the Developer Mode password once
|
||||
|
||||
Run `./scripts/connect.sh --harden` later if you want to turn off SSH password
|
||||
logins.
|
||||
|
||||
Sources: [Valve: Setting up your Steam Frame for development](https://partner.steamgames.com/doc/steamhardware/steamframe/setup),
|
||||
[Valve: Steam Frame Debugging](https://partner.steamgames.com/doc/steamhardware/steamframe/debugging)
|
||||
(both **confirmed on Steam Frame**, Valve official).
|
||||
|
||||
**Fallback, only if the Developer Mode toggle doesn't give you SSH.** From the
|
||||
Mac, run `./scripts/serve-bootstrap.sh`. It prints a one-liner of about 30
|
||||
characters, like `curl -fsS mac.local:8765|bash`, to type into Konsole on the
|
||||
Frame's Linux desktop. The script it serves installs your Mac's public key and
|
||||
enables `sshd`. See [docs/ssh.md](ssh.md#fallback-bootstrap-one-liner).
|
||||
|
||||
## Recommended options
|
||||
|
||||
| Goal | Recommended | Confidence |
|
||||
|---|---|---|
|
||||
| Shell on the Frame | `ssh frame` (user `steamos`) | Confirmed (Valve docs) |
|
||||
| **See/control the Frame from the Mac** | **Steam Link for macOS → connect to `frame`** (Valve names this). Alternatives: RDP to `xrdp` with Microsoft *Windows App* for the Linux desktop, or `adb`/`scrcpy` for the Android (Lepton) layer only | Steam Link and xrdp confirmed on Frame; the Mac RDP client is inferred |
|
||||
| **Show the Mac's desktop inside the Frame** | **macOS Screen Sharing (built-in VNC) → Remmina (Flatpak, aarch64) on the Frame's Linux desktop**, installed over SSH | Inferred: each piece is documented, but the combination hasn't been tested on a Frame |
|
||||
| File transfer | `scp` / `rsync` over the `frame` alias (`scripts/push.sh`) | **Verified** (rsync is on the image) |
|
||||
| Paste Mac clipboard into the headset | `scripts/paste-to-frame.sh` (`pbpaste` → `ssh` → Klipper over D-Bus), or the clipboard sync in an RDP session | **Verified** (script); RDP untested |
|
||||
|
||||
Details: [docs/ssh.md](ssh.md), [docs/streaming.md](streaming.md),
|
||||
[docs/file-transfer.md](file-transfer.md),
|
||||
[docs/open-questions.md](open-questions.md). For how the Frame's software
|
||||
fits together, see [docs/how-the-frame-works.md](how-the-frame-works.md).
|
||||
|
||||
## Windows anywhere in the room
|
||||
|
||||
The in-headset Linux desktop is a single 1280×800 panel, and its windows can't
|
||||
leave it. Each Steam app, though, gets its own SteamVR panel. That also works
|
||||
for any Linux app tagged with an app id of its own:
|
||||
|
||||
```sh
|
||||
./scripts/panel-on-frame.sh konsole
|
||||
./scripts/panel-on-frame.sh mac-screen # the Mac's screen, in its own panel
|
||||
```
|
||||
|
||||
Then use the SteamVR dashboard's **Float in World**, **Move** and **Size**
|
||||
controls to place each panel. See [docs/panels.md](panels.md).
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Runs on | Purpose |
|
||||
|---|---|---|
|
||||
| `scripts/tailscale-on-frame.sh` | Mac → Frame | Install Tailscale in `~` as a userspace user service so `frame` works from anywhere; `--uninstall` (**verified** on the LAN) |
|
||||
| `scripts/connect.sh` | Mac | Discover, set up key and `~/.ssh/config`, copy key, optional `--harden` (**verified**; `--harden` untested) |
|
||||
| `scripts/install-apps.sh` | Mac → Frame | Install Flatpaks (Remmina, Moonlight, …) on the Frame over SSH as `--user` (**verified** with Remmina) |
|
||||
| `scripts/paste-to-frame.sh` | Mac → Frame | Send the Mac clipboard (or stdin) to the Frame clipboard (**verified**) |
|
||||
| `scripts/install-apk.sh` | Mac → Frame | Install APKs, each as its own persistent Lepton instance with a Steam library shortcut (`--dev`: old ADB path into Lepton Development) (**verified**; see [docs/apks.md](apks.md)) |
|
||||
| `scripts/panel-on-frame.sh` | Mac → Frame | Start an app as its own floating VR panel, outside the desktop (**verified**: overlays created; in-headset placement not yet checked) |
|
||||
| `scripts/run-on-frame.sh` | Mac → Frame | Start an app on the headset desktop, e.g. `mac-screen` opens Remmina straight into the Mac (**verified**) |
|
||||
| `scripts/frame-ui.sh` | Mac | Start the Frame Control web UI (`ui/server.py`) and open it (**verified**) |
|
||||
| `scripts/apk-catalog.sh` | Mac | Refresh the rated F-Droid catalogue that Frame Control's Android section shows (**verified**) |
|
||||
| `scripts/compat-db-backup.sh` | Mac | Maintainer-only: back up the shared compatibility database locally and to Google Drive (**verified**) |
|
||||
| `scripts/push-vr-video.sh` | Mac → Frame | Upload VR180/360 videos to `~/Videos/VR`, linked into DeoVR's Proton prefix; `--launch` starts DeoVR (**verified**: upload and link; in-headset playback of local files not yet checked). See [docs/vr-video.md](vr-video.md) |
|
||||
| `scripts/push.sh` | Mac → Frame | `rsync` files to `~/Downloads` (or a given path) on the Frame (**verified**) |
|
||||
| `scripts/serve-bootstrap.sh` | Mac | Fallback: serve `bootstrap-on-frame.sh` with your public key embedded |
|
||||
| `scripts/bootstrap-on-frame.sh` | Frame | Fallback: install the key and enable `sshd` |
|
||||
|
||||
Reference in new issue
Block a user