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
.exeand.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:
AppImageand.deb
+ - Signing
- n/a +
- Tested on
- x86_64 Ubuntu 26.04 with no
adband no clipboard tools, using the 0.3.1 AppImage under Xvfb with the bundled Python andadb. The arm64 builds and the.debpackages weren't run; the arm64 package was only checked to contain an ARM Python.
+
Features
+| Feature | macOS | Windows | Linux |
|---|---|---|---|
| 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 |
+ Tested | +Tested |
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). |
+ Tested | +Not built OpenSSH limitation |
+ Tested |
| Headset view | |||
Capture headset view The left eye or both eyes as the lenses show them, saved as PNG |
+ Tested | Tested | Tested |
Capture desktop panel gamescope's flat layer |
+ Tested | Tested | Tested |
Live view 720p H.264 at about 30 fps, decoded with WebCodecs |
+ Tested | Tested | Tested |
Headset screenshots Browse the screenshots you took with Steam's shortcut, and save them to ~/Pictures/SteamFrame |
+ Tested | +Partial 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 |
+ Tested | Tested | Tested |
System status Storage, memory, temperature, Wi-Fi, uptime, running services |
+ Tested | Tested | Tested |
Volume and mute |
+ Tested | +Partial Read only |
+ Partial Read only |
| Games | |||
Owned games with Frame ratings Verified, Playable, Unsupported or Unknown |
+ Tested | Tested | Tested |
Install a game on the Frame Uses the headset's Steam client, with live progress |
+ Tested | Built | Built |
Store search, Buy, Store on Frame |
+ Tested | Built | Built |
Library shelf and Play button |
+ Built | Built | Built |
| Android apps | |||
Installed Android apps list |
+ Tested | Tested | Tested |
F-Droid catalogue search About 4,500 apps with Frame ratings, bundled with the app |
+ Tested | Tested | Tested |
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 |
+ Built | +Partial Launch and stop |
Report an APK Reports are saved on your computer; the shared database is maintainer-only |
+ Automated | Automated | Automated |
Android display settings Resolution, UI scale, text size |
+ Tested Density and text size set, then reset |
+ Built | +Partial 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. |
+ Tested | Tested | Tested |
Drop an APK to install it |
+ Tested | Built | Built |
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 |
+ Built | Built | Built |
| One-click tools | |||
SSH or SFTP in a terminal macOS: Terminal. Windows: cmd. Linux: GNOME Terminal, Konsole, xterm and others. |
+ Built | Built | Built |
Steam Link |
+ Built | Built | Built |
Remote desktop macOS: Windows App. Windows: Remote Desktop. Linux: Remmina or FreeRDP. |
+ Built | Built | Built |
Sleep, restart, shut down Opens a terminal because SteamOS asks for the sudo password |
+ Built | Built | Built |
| 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 |
+ Automated | Automated | Automated |
Mac or PC wording The UI says Finder or File Explorer, and Mac or PC, to match your system |
+ Tested | Tested | Tested |
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.exehangs 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. +
+
@@ -103,6 +103,9 @@ already ships (sideloading a game copies Valve's own devkit scripts to
| **Linux** (x64) | [AppImage](https://github.com/saphid/steam-frame/releases/latest/download/Frame-Control-linux-x86_64.AppImage) · [.deb](https://github.com/saphid/steam-frame/releases/latest/download/Frame-Control-linux-amd64.deb) | `ssh` (most desktops have it) |
| **Linux** (arm64) | [AppImage](https://github.com/saphid/steam-frame/releases/latest/download/Frame-Control-linux-arm64.AppImage) · [.deb](https://github.com/saphid/steam-frame/releases/latest/download/Frame-Control-linux-arm64.deb) | `ssh`, and `adb` for Android apps (`sudo apt install adb`) |
+**iPhone and iPad:** the same features from your phone, with nothing to install on
+a computer. Build it from [`ios/`](ios) in Xcode; see [docs/iphone.md](docs/iphone.md).
+
The app brings its own Python and `adb`; SSH is built into macOS and Windows.
From 0.4 it updates itself: when a new version is published, a banner offers
**Update and restart**. It sends anonymous usage statistics, which you can turn
@@ -205,6 +208,9 @@ 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 |
+| [Testing](docs/testing.md) | Unit tests, end-to-end tests against a fake Frame in Docker, and the headset smoke test |
| [Open questions](docs/open-questions.md) | What's still unchecked |
+
+In the iOS Simulator (iOS 26.5) against a real Frame, 2026-09-27: the app connected
+with its key, copied the bundle over SFTP, started the server on the Frame and
+showed all four tabs with live data. In the app's web view, Capture returned a
+headset still and Live played H.264 video at 31 fps (WebCodecs works in
+WKWebView). Through the app's tunnel: status, games, Steam library, Android apps,
+screenshots, a file upload (checked on the Frame), a background install job, and
+the power password check (a wrong password is refused). The server on the Frame
+exits within seconds of the app closing.
+
+Against Valve's own Steam Frame OS (SteamOS 0.3.0 build 20260922.5152327, the
+`rootfs-A` partition of the Frame recovery image, run with its own sshd; see
+[tests/frame-container](../tests/frame-container)), and a Holo Core stand-in:
+pairing with the password (key added with the right
+permissions, host key pinned, password stored nowhere), the power password
+check (a wrong or missing password refused; the right one reaches `systemctl`),
+a changed host key refused with "Pair with the Frame again", and a wrong
+pairing password reported the same way.
+
+Also verified in the Simulator against the Frame (2026-09-27): the setup screen
+found the Frame by itself over Bonjour (`frame · 192.168.1.237`); a paired app
+waiting for a sleeping Frame connected 4 s after it answered; an upload from the
+app's web view landed in `~/Downloads`; the share sheet offers Save Image
+(needs `NSPhotoLibraryAddUsageDescription`, now declared); an install link opens
+the confirm dialog and downloads nothing until Install; Steam Link without the
+app installed opens its App Store page.
+
+Things iOS asks the first time: **Local Network** (tap Allow, or the app can't
+see the Frame), and **Paste** when you send the iPhone's clipboard (tap Allow
+Paste, or set Settings → Apps → Frame Control → Paste from Other Apps → Allow).
+Sending text to the Frame's clipboard needs the desktop panel open in the
+headset, as on the desktop app.
+
+Not yet exercised: Android display changes through podman (no Android app was
+running), a real sleep/restart/shut down on the Frame, and a physical iPhone.
+
+Debug builds have Simulator test hooks (`FRAME_TEST_HOST`, `FRAME_TEST_PAGE`,
+`FRAME_TEST_JS`, and the tunnel URL in the app's Caches folder); release builds
+don't.
diff --git a/docs/open-questions.md b/docs/open-questions.md
index 67f4645..13b4c45 100644
--- a/docs/open-questions.md
+++ b/docs/open-questions.md
@@ -33,14 +33,19 @@ build 20260922.6101926, kernel 6.18, aarch64):
- **10.** `install-apps.sh remmina --vnc-host