From 10bf18fd50c687bce2ab462818de6a245ef87fc4 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:25:01 +1000 Subject: [PATCH] Document the Frame's recovery images and what we learnt about the device - docs/recovery-and-images.md: where Valve's Frame images are (not linked from the SteamOS download page), file names, sizes and our checksums, the GPT layout with exact start sectors, what's in rootfs-A (btrfs, SteamOS 0.3.0 build 20260922.5152327, users, sudo and sshd config), extracting it, running it without the headset, and Valve/Collabora's Holo Core aarch64 preview. - how-the-frame-works.md: correct the recovery image file names; add verified facts on the SSH server, tools on the image (no adb), Lepton instances as podman containers, going off the network when asleep, and the battery reading at full charge. - ssh.md: pairing from an iPhone and why devkit RSA pairing doesn't fit it. - open-questions.md, README.md and the steam-frame skill point to the new pages. Co-Authored-By: Claude Opus 5.5 (1M context) --- .claude/skills/steam-frame/SKILL.md | 3 + README.md | 2 + docs/how-the-frame-works.md | 10 +- docs/open-questions.md | 22 ++++ docs/recovery-and-images.md | 109 ++++++++++++++++ docs/ssh.md | 12 ++ docs/support-matrix.html | 190 ++++++++++++++++++++++++++++ 7 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 docs/recovery-and-images.md create mode 100644 docs/support-matrix.html diff --git a/.claude/skills/steam-frame/SKILL.md b/.claude/skills/steam-frame/SKILL.md index ed74648..0437825 100644 --- a/.claude/skills/steam-frame/SKILL.md +++ b/.claude/skills/steam-frame/SKILL.md @@ -27,6 +27,9 @@ desktop or panels. | Flatpaks | `docs/streaming.md` | `scripts/install-apps.sh` | | Launch an app inside the desktop panel | the script's header comment | `scripts/run-on-frame.sh` | | Mac GUI over all of this | `README.md` → Frame Control | `scripts/frame-ui.sh` | +| iPhone/iPad app (server runs on the Frame, `FRAME_LOCAL=1`) | `docs/iphone.md` | `ios/`, `ui/local-bin/ssh` | +| Recovery images, factory reset, boot loops | `docs/recovery-and-images.md`, `docs/how-the-frame-works.md` | `~/Downloads/steam-frame-recovery/` | +| Test without the headset (the Frame OS image's own sshd) | `tests/frame-container/README.md` | `tests/frame-container/frame-image.sh` | | What's still unverified | `docs/open-questions.md` | — | Each script's usage is in its header comment. Read the header rather than diff --git a/README.md b/README.md index 089606c..93cabeb 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,8 @@ Frame's software fits together, all checked against a real headset and labelled | [Install links for websites](docs/web-install.md) | `frame-control://install` links and manifests, the rules, a button to paste | | [Steam games](docs/steam-games.md) · [VR video](docs/vr-video.md) · [WebXR in Chromium](docs/webxr-chromium.md) | Installing and buying, watching VR180/360, the Chromium build | | [SSH](docs/ssh.md) · [Streaming](docs/streaming.md) · [Files](docs/file-transfer.md) · [Panels](docs/panels.md) · [Tailscale](docs/tailscale.md) | Topic notes | +| [Frame Control for iPhone](docs/iphone.md) | The iPhone and iPad app, how it runs the server on the Frame, pairing | +| [Recovery and OS images](docs/recovery-and-images.md) | Where to download the Frame's OS, what's inside, testing without the headset | | [Open questions](docs/open-questions.md) | What's still unchecked |
diff --git a/docs/how-the-frame-works.md b/docs/how-the-frame-works.md index 315a889..74f81f1 100644 --- a/docs/how-the-frame-works.md +++ b/docs/how-the-frame-works.md @@ -50,7 +50,13 @@ Lepton (Android 11, podman container "lepton-dev") ← its own panel, app 305600 | Tailscale runs without root as a userspace `tailscaled` user service (static arm64 build in `~/.local/share/tailscale`, lingering on). In userspace mode, inbound tailnet connections reach the Frame's **loopback**, so every port, including DevTools on 8080, is reachable from the tailnet. **Verified 2026-09-25.** | [tailscale.md](tailscale.md), `scripts/tailscale-on-frame.sh` | | **T3 Code desktop runs natively.** The stock release `T3-Code-0.0.42-arm64.AppImage` in `~/Applications/T3CodeDesktop/` starts with no extra setup: glibc 2.39, `libfuse.so.2`, GTK 3, NSS and libsecret are on the image. `panel-on-frame.sh --name t3code-desktop -- '~/Applications/T3CodeDesktop/T3-Code.AppImage'` gives it its own panel (`valve.steam.desktopgame.2000281357`, `--ozone-platform=x11`). Its bundled server listens on `127.0.0.1:3773` and shows up in onboarding as the `frame` computer, with `passwordStore: gnome-libsecret`. The image has no agent CLI and no `node`. Agents run through the LAN CLIProxyAPI (`llm-proxy.lan:8317`, which resolves on the Frame). Claude Code 2.1.283 comes from `claude.ai/install.sh`, and Codex 0.157.1 from the `codex-aarch64-unknown-linux-musl` release tarball, both into `~/.local/bin`. `with-cliproxy` and a mode-600 `~/.config/cliproxyapi/secrets.env` are copied from the Mac. The wrappers `claude-cliproxy` and `codex-cliproxy` (a `-c model_provider=cliproxy`, `wire_api="responses"`, `env_key="CLIPROXY_API_KEY"`) are set as `providers.claudeAgent.binaryPath` and `providers.codex.binaryPath` in `~/.t3/userdata/settings.json`, and T3 picked that up without a restart. Through the wrappers, `claude auth status` reports `loggedIn: true` (`oauth_token`), and both CLIs answered a prompt with `kimi-k3`. `gamescopectl screenshot` captured another layer (the Lepton T3 app) rather than this panel. `DISPLAY=:0 xwd -id ` piped to `ffmpeg` captures the window itself (1920×1080). **Verified 2026-09-26**, BUILD_ID 20260922.6101926. | Running T3 Code as a host on the Frame | | Power actions need `sudo`, which asks for the Developer Mode password over SSH. | Frame Control's power buttons | -| **Boot / recovery menu.** Hold Power ~10 s until the LED goes off, then power on while holding the **AUX button on top of the Power button** (not the volume keys) until a text menu appears. Entries: `Current` (SteamOS-A/B + build), `Previous` (the other A/B slot), `Boot from USB`, `Repair Steam Installation`, `Erase User Data` (factory reset), `ADB mode`, `Battery Ship Mode`. It auto-boots `Current` after a ~15 s countdown. **Volume Up/Down (left side) move, AUX (right side) selects.** For a boot loop, Valve says pick `Previous` (keeps user data); then `Repair Steam Installation`; `Erase User Data` wipes `~` (SSH keys, Tailscale, Flatpaks, T3 setup). Last resort is a full re-image, two ways: (1) USB: write `steamframe-repair-latest.img.bz2` to an 8 GB+ USB-C stick (Balena Etcher on the Mac), pick `Boot from USB`, then use "Wipe Device & Install SteamOS" / "Repair SteamOS" (keeps games and personal content) from the recovery desktop; (2) cable/EDL: `steamframe-repair-qdl-latest.tar.gz`, run `flash.sh` (Linux) or `flash.cmd` (Windows), then with the Frame off for 10 s hold Power + Vol Up + Vol Down for 10 s and plug it in; it reflashes and reboots. Both images: `https://steamdeck-images.steamos.cloud/recovery/` (build 20260922.5153644, 0.3.0, ~4 GB each, no published checksums); local copies in `~/Downloads/steam-frame-recovery/`. Source: Valve's [SteamOS Recovery FAQ](https://help.steampowered.com/en/faqs/view/1B71-EDF2-EB6D-2BB3) and [Installation and Repair FAQ](https://help.steampowered.com/en/faqs/view/65B4-2AA3-5F37-4227), plus a menu photo in [EloiStree/HelloSteamFrame#9](https://github.com/EloiStree/HelloSteamFrame/issues/9). **Inferred** (Valve docs, 2026-09-26); not yet tried on our Frame. | Recovering from a boot loop | +| **SSH server:** OpenSSH 9.7p1. It offers `publickey,password` (keyboard-interactive is off, PAM on) and also asks `userdbctl ssh-authorized-keys` for keys. OpenSSH ≥ 8.8 rejects SHA-1 `ssh-rsa` signatures by default, so a client whose RSA support is SHA-1 only (the Swift library Citadel, for one) can't log in with the RSA key that devkit pairing installs; use ed25519 (**inferred** from OpenSSH defaults). **Verified 2026-09-27**, BUILD_ID 20260922.6101926. | [iphone.md](iphone.md), `ui/frame_connect.py` | +| **Tools on the image:** Python 3.12.3, `ffmpeg`, `openssl`, `curl`, `rsync`, `zip`/`unzip`, `flatpak`, `wpctl`, `podman`. **No `adb`.** `steamos` is uid 1000, in `wheel`, and sudoers has `%wheel ALL=(ALL) ALL`, so `sudo -S` takes the Developer Mode password on stdin. **Verified 2026-09-27.** | Running Frame Control's server on the Frame (`FRAME_LOCAL=1`, [iphone.md](iphone.md)) | +| **Each Lepton instance is a podman container** named `lepton-steamlaunch-`, labelled with its ADB port (`podman ps --format '{{.Names}} {{.Labels.adb_port}}'`). `podman exec /system/bin/sh -c '…'` runs Android's shell inside it with no adb at all (used for `pidof` and `logcat` by the app tester). Running `wm size`/`wm density` that way is untested. **Verified 2026-09-27.** | `ui/frame_android.py`, the iPhone app's display settings | +| **Asleep means off the network.** In standby the Frame stops answering on its LAN address, `frame.local` and Tailscale alike (`Host is down`, `No route to host`, timeouts), and ping fails. It was unreachable for about 2.5 hours until woken. Nothing over SSH can wake it. **Verified 2026-09-27.** | Frame Control's offline banner and retries | +| **Battery at full on a charger** can read `Discharging` at about 0 W (for example 99 %, 0.0 W, USB-C PD 18 W). Treat under 0.5 W on a charger as "not charging", not "draining". **Verified 2026-09-27.** | Frame Control's battery card | +| **The OS image is downloadable.** Valve's recovery images for the Frame are at `https://steamdeck-images.steamos.cloud/recovery/`. The root filesystem inside is btrfs, and it runs as an SSH test target on ARM64 Linux without the headset (`tests/frame-container/frame-image.sh`). **Verified 2026-09-27.** | [recovery-and-images.md](recovery-and-images.md) | +| **Boot / recovery menu.** Hold Power ~10 s until the LED goes off, then power on while holding the **AUX button on top of the Power button** (not the volume keys) until a text menu appears. Entries: `Current` (SteamOS-A/B + build), `Previous` (the other A/B slot), `Boot from USB`, `Repair Steam Installation`, `Erase User Data` (factory reset), `ADB mode`, `Battery Ship Mode`. It auto-boots `Current` after a ~15 s countdown. **Volume Up/Down (left side) move, AUX (right side) selects.** For a boot loop, Valve says pick `Previous` (keeps user data); then `Repair Steam Installation`; `Erase User Data` wipes `~` (SSH keys, Tailscale, Flatpaks, T3 setup). Last resort is a full re-image, two ways: (1) USB: write `steamframe-oobe-repair-.img.bz2` to an 8 GB+ USB-C stick (Balena Etcher on the Mac), pick `Boot from USB`, then use "Wipe Device & Install SteamOS" / "Repair SteamOS" (keeps games and personal content) from the recovery desktop; (2) cable/EDL: `steamframe-oobe-repair-qdl-.tar.gz`, run `flash.sh` (Linux) or `flash.cmd` (Windows), then with the Frame off for 10 s hold Power + Vol Up + Vol Down for 10 s and plug it in; it reflashes and reboots. Both images: `https://steamdeck-images.steamos.cloud/recovery/` (build 20260922.5153644, 0.3.0, 3.8 GiB each, no published checksums); local copies in `~/Downloads/steam-frame-recovery/`. File names, checksums and what's inside: [recovery-and-images.md](recovery-and-images.md). Source: Valve's [SteamOS Recovery FAQ](https://help.steampowered.com/en/faqs/view/1B71-EDF2-EB6D-2BB3) and [Installation and Repair FAQ](https://help.steampowered.com/en/faqs/view/65B4-2AA3-5F37-4227), plus a menu photo in [EloiStree/HelloSteamFrame#9](https://github.com/EloiStree/HelloSteamFrame/issues/9). **Inferred** (Valve docs, 2026-09-26); not yet tried on our Frame. | Recovering from a boot loop | | **Boot loop cause: the SteamVR health check.** `steamvr.service` runs `/usr/share/deckard/steamvr-health-check`, which appends `frog:glasses:` to `$XDG_RUNTIME_DIR/steamvr-short-session-tracker` on every failed or <10 s SteamVR run. At 3 it runs `steam-health-check --repair-now`, which **deletes all of `~/.local/share/Steam` (games, login, Developer Mode) and `~/.steam`**, keeping only `registry.vdf`. At 4 it also tries `steamos-bootconf set-mode reboot-other` (fails as the user: `bootenv: Permission denied`). SteamVR normally fails 1–2 times per boot while it waits for the Steam client (`SteamAPI_InitEx failed … Steam is probably not running`, then `fatal stalled cross-thread pipe`). Once Steam has been wiped, it has to re-download a ~210 MB client on every boot, so SteamVR keeps failing, Steam keeps getting wiped and the Frame reboots, in a loop. Also, the Steam updater can deadlock at `Installing update...` (main process blocked writing to the `-child-update-ui` process, which is stuck in `drm_syncobj_array_wait_timeout`). Killing only the `-child-update-ui` process lets the install finish (`package/*.installed` appears). **Fix without sudo:** over USB-C ADB (`adb -s frame shell` works as `steamos` while the Frame is looping; SSH is refused once Developer Mode is lost), truncate both `/run/user/1000/steam{,vr}-short-session-tracker` files and `chmod 444` them (the health check then logs `Permission denied` and does nothing; this is tmpfs, so it resets on reboot). Unstick the updater if needed, let Steam finish installing, then hold Power 10 s and start the Frame normally. `systemctl reboot` over ADB needs interactive auth. After the fix, sign in to Steam and turn Developer Mode back on. **Verified 2026-09-26**, BUILD_ID 20260922.6101926, slot B (clean boot: 0 SteamVR failures, SSH and Tailscale back). | Diagnosing a boot loop | ## Debug recipes @@ -78,4 +84,6 @@ ssh frame 'cat /opt/steamvr/resources/webinterface/dashboard/localization/dashbo - Installing and buying Steam games: [steam-games.md](steam-games.md) - Remote access from anywhere: [tailscale.md](tailscale.md) - Floating windows in space: [panels.md](panels.md) +- Recovery images, what's in them, testing without the headset: [recovery-and-images.md](recovery-and-images.md) +- Frame Control on iPhone (the server running on the Frame itself): [iphone.md](iphone.md) - What's still unverified: [open-questions.md](open-questions.md) diff --git a/docs/open-questions.md b/docs/open-questions.md index 67f4645..44583b2 100644 --- a/docs/open-questions.md +++ b/docs/open-questions.md @@ -103,6 +103,28 @@ Still open: 4, 6, 7, 11 (in-headset connect), 12–15, 16 (off-LAN and after a r the colour-coded test clips play in 3D (red left eye, cyan right) for both H.264 and H.265? Does the DLNA browser find a server on the Mac? +## Verified 2026-09-27 + +- **Recovery images exist** for the Frame at + `https://steamdeck-images.steamos.cloud/recovery/`; the root filesystem inside + is btrfs and runs, as a userland, on ARM64 Linux. See + [recovery-and-images.md](recovery-and-images.md). +- **Frame Control's server runs on the Frame itself** (the iPhone app does + this), including headset capture, 31 fps live video and file uploads. See + [iphone.md](iphone.md). +- **Password pairing and `sudo -S`** work against the recovery image's own + sshd and sudo (not yet against the headset, whose password we don't hold). + +## Still open (2026-09-27) + +- Does `podman exec /system/bin/sh -c 'wm size'` change an + instance's display the way `adb shell wm size` does? +- Can the recovery image, or its kernel, boot in a VM at all? +- Does a real sleep, restart or shut down from the iPhone app work (via + `sudo -S systemctl`)? +- The Mac EDL flashing script in `~/Downloads/steam-frame-recovery/` hasn't + been run against a Frame. + ## Unconfirmed claims made in these docs - `/home` and `/etc` persist across Frame OS updates. This is inferred from diff --git a/docs/recovery-and-images.md b/docs/recovery-and-images.md new file mode 100644 index 0000000..ad170d6 --- /dev/null +++ b/docs/recovery-and-images.md @@ -0,0 +1,109 @@ +# Recovery images and OS images for the Frame + +Where to get the Steam Frame's operating system, what's inside it, and how to +run it for testing without the headset. For recovering a Frame that won't boot, +see the boot menu and boot-loop entries in +[how-the-frame-works.md](how-the-frame-works.md#facts-worth-knowing). + +## Downloads + +Valve's SteamOS download page (`store.steampowered.com/steamos/download`) +redirects to the [Installation and Repair FAQ](https://help.steampowered.com/en/faqs/view/65B4-2AA3-5F37-4227), +which offers the Steam Deck image. The **Steam Frame images are on the same +server** but aren't linked from that page: +**https://steamdeck-images.steamos.cloud/recovery/** (a plain directory +listing, checked 2026-09-27). + +| File | Size | Use | +|---|---|---| +| `steamframe-oobe-repair-20260922.5153644-0.3.0.img.bz2` (or `.img.zip`) | 3.8 GiB | Write to an 8 GB+ USB-C stick, then **Boot from USB** in the Frame's boot menu | +| `steamframe-oobe-repair-qdl-20260922.5153644-0.3.0.tar.gz` (or `.zip`) | 3.8 GiB | Flash over a USB-C cable in Qualcomm EDL mode with `flash.sh` (Linux) or `flash.cmd` (Windows), which use [qdl](https://github.com/linux-msm/qdl). **Wipes everything** | + +All four are dated 2026-09-22. Everything else there is for the Steam Deck +(`steamdeck-…`, x86-64), which won't run on the Frame. Valve publishes **no +checksums**. These are the SHA-256s of our downloads (2026-09-26), which passed +`bzip2 -t` and `tar -t`: + +``` +3a4a077f1b1f40688ab3279affcb56776bd97c54db1573e7c65fc52a97106676 steamframe-oobe-repair-20260922.5153644-0.3.0.img.bz2 +d3323bfa8efe9ece1954948421cdf5f705e8942eb50c960e2916d935d1b850ab steamframe-oobe-repair-qdl-20260922.5153644-0.3.0.tar.gz +``` + +Our copies, with a Mac EDL flashing script built on qdl (untested), are in +`~/Downloads/steam-frame-recovery/` on the Mac. + +## What's inside the USB image + +A GPT disk with 512-byte sectors and one A slot (a Frame has A and B slots; +the installer makes the rest). **Verified 2026-09-27** from +`steamframe-oobe-repair-20260922.5153644-0.3.0.img.bz2`: + +| # | Name | Start sector | Size | Type GUID | +|---|---|---|---|---| +| 1 | `esp` | 34 | 256 MiB | `c12a7328-f81f-11d2-ba4b-00a0c93ec93b` (EFI system) | +| 2 | `efi-A` | 524322 | 64 MiB | `ebd0a0a2-b9e5-4433-87c0-68b6b72699c7` | +| 3 | `rootfs-A` | 655394 | 5120 MiB | `4f68bce3-e8cd-4db1-96e7-fbcaf984b709` | +| 4 | `var-A` | 11141154 | 256 MiB | `4d21b016-b534-45c2-a9fb-5c16e091fd2d` | +| 5 | `home` | 11665442 | 100 MiB | `933ac7e1-2eb4-4f13-b844-0e14e2aef915` | + +The partitions start at sector 34, not on MiB boundaries, so compute offsets +from the table (sector × 512), not from rounded sizes. `rootfs-A` is **btrfs** +(label `rootfs-A`, 9.2 GB of files), mounted read-only on the Frame. +Its `/etc/os-release` says `NAME="SteamOS"`, `ID=steamos`, `ID_LIKE=arch`, +`VERSION_CODENAME=holo`; the running system reports version 0.3.0, variant +`vr`, build **20260922.5152327**, which is a different number from the +`5153644` in the file name. Our headset reports build 20260922.6101926. + +Inside, it matches a real Frame: + +- User `steamos` (uid 1000) is in `wheel` (gid 998), and sudoers has + `%wheel ALL=(ALL) ALL`, so sudo asks for the Developer Mode password. +- `sshd_config` includes `sshd_config.d/*.conf`, uses `.ssh/authorized_keys` + plus `AuthorizedKeysCommand /usr/bin/userdbctl ssh-authorized-keys %u`, + and sets `KbdInteractiveAuthentication no` and `UsePAM yes`. So sshd offers + `publickey,password`, the same as the headset. +- `/usr/bin` has `sshd`, `sudo`, `python3` and `podman`. + +Get just the root filesystem without unpacking the whole 5.8 GB image (the +partition's start and size, in sectors, come from the table above): + +```sh +bzcat steamframe-oobe-repair-*.img.bz2 | tail -c +$((655394 * 512 + 1)) | head -c $((10485760 * 512)) > rootfs-A.img +``` + +A Mac can't mount btrfs; a Linux machine or VM can (`mount -o ro -t btrfs`). + +## Running it without the headset + +The image can't boot in a generic virtual machine: its kernel and bootloader +are built for the Frame's Qualcomm Snapdragon 8 Gen 3 (**inferred**; not +attempted). Its **userland** runs fine on any ARM64 Linux, which covers +anything that talks to the Frame over SSH. + +[`tests/frame-container/frame-image.sh`](../tests/frame-container/frame-image.sh) +extracts `rootfs-A`, mounts it read-only with a throwaway writable layer, and +starts the image's own `sshd` on port 2223 (user `steamos`, a test password; +`systemctl` only records requests). On a Mac, run it in Colima's ARM64 VM (see +[tests/frame-container/README.md](../tests/frame-container/README.md)). +**Verified 2026-09-27:** the iPhone app paired with it by password (the image's +sshd logged `Accepted password`, then `Accepted publickey … ED25519`), ran +Frame Control's server on the image's Python, and the image's sudo rejected a +wrong power password and passed the right one to `systemctl`. Without the +Frame's hardware there's no SteamVR, Steam client, battery or Lepton, so those +parts stay untested this way. + +## Holo Core aarch64 (Valve and Collabora) + +The ARM64 port of Arch Linux that the Frame's SteamOS is built on, published as +a preview in July 2026 ([Collabora's announcement](https://www.collabora.com/news-and-blog/news-and-events/building-an-arch-linux-aarch64-port-for-holo-core.html)). +It's a base system and build environment, not the Frame's OS: + +- Source: `https://gitlab.steamos.cloud/holo/holo-core-aarch64-preview` +- Packages: `https://holo-packages.steamos.cloud/holo-core-aarch64-preview/mash-20251118` +- Container: `registry.gitlab.steamos.cloud/holo/holo-core-aarch64-preview/base-devel:latest` + (1.7 GB; `/etc/os-release` says "Holo core Aarch64 port (preview)"; `pacman` + installs OpenSSH 10.2, Python 3.13 and sudo from its repositories. Checked 2026-09-27.) + +[`tests/frame-container/Dockerfile`](../tests/frame-container/Dockerfile) builds a +lighter Frame stand-in on it (a `steamos` user with a password and sudo, sshd +with keys and passwords), handy when you don't have the 4 GB image. diff --git a/docs/ssh.md b/docs/ssh.md index 93aac3b..7833bb5 100644 --- a/docs/ssh.md +++ b/docs/ssh.md @@ -102,6 +102,18 @@ started. `curl http://:32000/properties.json` shows whether the servic `~/.ssh/authorized_keys` lives under `/home`, which SteamOS keeps across OS updates (inferred from Deck; the Frame uses the same A/B image scheme). +## From an iPhone or iPad + +The iPhone app ([iphone.md](iphone.md)) makes its own ed25519 key and adds it +with the Developer Mode password, once, over a password login; the Frame's sshd +offers `publickey,password` (OpenSSH 9.7p1, keyboard-interactive off). It can't +use the devkit pairing above: that installs an RSA key, and the Swift SSH +library signs RSA only with SHA-1, which OpenSSH 8.8 and later refuse by default. +The app pins the Frame's host key on first use and asks you to pair again if it +changes. **Verified 2026-09-27** against the Frame's recovery image +([recovery-and-images.md](recovery-and-images.md)); on the headset, the add-the-key-yourself +route was used. + ## Keeping `sshd` enabled across updates - **Frame**: SSH is tied to the Developer Mode toggle, so it should survive diff --git a/docs/support-matrix.html b/docs/support-matrix.html new file mode 100644 index 0000000..8051ce1 --- /dev/null +++ b/docs/support-matrix.html @@ -0,0 +1,190 @@ + + + + + +Frame Control 0.3.1: features by OS + + + +
+

Frame Control 0.3.1: features by OS

+

Which features are built for each OS, and which were tested against a real Steam Frame + (SteamOS 0.3.0, build 20260922.6101926). Status as of 26 September 2026, for + PR #2 (0.3.1). Every build now bundles its own + Python 3.12, adb and CA certificates, so nothing else needs installing (only ssh on Linux, + plus the system adb on arm64 Linux).

+ +

Builds and test machines

+
+

macOS

+
Built
Apple Silicon (arm64): .dmg, .zip. No Intel build.
+
Signing
Ad-hoc signed, not notarised
+
Tested on
Apple Silicon Mac, macOS 26, using a local 0.3.1 build with the bundled Python and adb. All 15 calls it made to the Frame at startup returned OK.
+
+

Windows

+
Built
x64: NSIS installer .exe and .zip
+
Signing
Unsigned. SmartScreen shows a warning.
+
Tested on
Windows 11 x64 VM. Real-Frame results below are from 0.3.0. The 0.3.1 installer from CI installs cleanly (31 s) and reinstalls over itself (38 s). The server starts on the bundled Python, and HTTPS to Steam and F-Droid works. The Frame went offline before its 0.3.1 run on the headset.
+
+

Linux

+
Built
x86_64 and arm64: AppImage and .deb
+
Signing
n/a
+
Tested on
x86_64 Ubuntu 26.04 with no adb and no clipboard tools, using the 0.3.1 AppImage under Xvfb with the bundled Python and adb. The arm64 builds and the .deb packages weren't run; the arm64 package was only checked to contain an ARM Python.
+
+
+ +

Features

+
+ Tested: worked against the real Frame on that OS + Partial: only part of the feature was tested (see note) + Automated: covered by CI tests on that OS, not tried on a real Frame + Built: in the build, not tested + Not built +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeaturemacOSWindowsLinux
Connection
Set Up Connection
Finds the Frame, writes the frame SSH alias, copies your key using the Frame's password. macOS runs connect.sh in Terminal; Windows and Linux run frame_connect.py.
Partial
Existing alias used, script not re-run
TestedTested
Shared SSH connection
A single SSH connection is reused, so each request takes about 0.3 s. Windows OpenSSH can't do this, so there each request opens its own connection (about 0.5 to 1 s).
TestedNot built
OpenSSH limitation
Tested
Headset view
Capture headset view
The left eye or both eyes as the lenses show them, saved as PNG
TestedTestedTested
Capture desktop panel
gamescope's flat layer
TestedTestedTested
Live view
720p H.264 at about 30 fps, decoded with WebCodecs
TestedTestedTested
Headset screenshots
Browse the screenshots you took with Steam's shortcut, and save them to ~/Pictures/SteamFrame
TestedPartial
Listed (5 found); saving not tried
Partial
Listed with thumbnails; saving not tried
Status
Battery and charging
Percentage, watts, time to full or empty, charger type, temperature
TestedTestedTested
System status
Storage, memory, temperature, Wi-Fi, uptime, running services
TestedTestedTested
Volume and mute
TestedPartial
Read only
Partial
Read only
Games
Owned games with Frame ratings
Verified, Playable, Unsupported or Unknown
TestedTestedTested
Install a game on the Frame
Uses the headset's Steam client, with live progress
TestedBuiltBuilt
Store search, Buy, Store on Frame
TestedBuiltBuilt
Library shelf and Play button
BuiltBuiltBuilt
Android apps
Installed Android apps list
TestedTestedTested
F-Droid catalogue search
About 4,500 apps with Frame ratings, bundled with the app
TestedTestedTested
Install, launch, stop, test, remove an app
Each app runs as its own Lepton instance, using the bundled adb. APK files are read by a built-in parser (no aapt2) that matched aapt2 on 9 F-Droid APKs.
Tested
Diary: read, install, launch, test, remove
BuiltPartial
Launch and stop
Report an APK
Reports are saved on your computer; the shared database is maintainer-only
AutomatedAutomatedAutomated
Android display settings
Resolution, UI scale, text size
Tested
Density and text size set, then reset
BuiltPartial
Read over the bundled adb
Transfer
Send files to ~/Downloads
Test files had non-English characters in their names (é, ✓). macOS and Linux copy with rsync; Windows uses scp.
TestedTestedTested
Drop an APK to install it
TestedBuiltBuilt
Send text or clipboard to the Frame
Needs the headset desktop open. The app reads your clipboard through Electron, so no extra tools are needed.
Tested
Reading the clipboard retested in 0.3.1
Partial
Reached the Frame; desktop was closed
Partial
Clipboard read with no xclip; Frame desktop was closed
Flatpak install and remove
BuiltBuiltBuilt
One-click tools
SSH or SFTP in a terminal
macOS: Terminal. Windows: cmd. Linux: GNOME Terminal, Konsole, xterm and others.
BuiltBuiltBuilt
Steam Link
BuiltBuiltBuilt
Remote desktop
macOS: Windows App. Windows: Remote Desktop. Linux: Remmina or FreeRDP.
BuiltBuiltBuilt
Sleep, restart, shut down
Opens a terminal because SteamOS asks for the sudo password
BuiltBuiltBuilt
App
Local server test suite
Runs in GitHub Actions on every push (Python 3.12 on macOS and Windows, Python 3.13 on Ubuntu), including the APK reader tests
AutomatedAutomatedAutomated
Mac or PC wording
The UI says Finder or File Explorer, and Mac or PC, to match your system
TestedTestedTested
+ +

Notes

+
    +
  • Tested means the app, running on that OS, got a successful response from the real Frame: for example, a PNG from a capture, 868 owned games, or the Android apps listed.
  • +
  • The Windows VM tests ran in its desktop session. ssh.exe hangs when it's started from a remote SSH session, but a normal desktop user won't hit that.
  • +
  • The macOS test from 25 September also covered the capture shown when the headset is in standby, input validation, and using the clipboard with the headset desktop open.
  • +
  • Everything marked Built runs a command that works on its own. It just hasn't been tried end to end from the app on that OS yet.
  • +
+ +
+ +