mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 01:00:18 +02:00
Package the Frame Control web UI as an installable Electron Mac app and bring in the tooling built alongside it. - app/: Electron wrapper that starts ui/server.py on a free loopback port, hardened window (sandbox, no navigation, runAsNode fuse off), login-shell PATH so Homebrew tools work from Finder, first-run offer to run connect.sh, ad-hoc signed DMG/zip via electron-builder. - ui/: headset view (OpenVR screenshots), device status, library, Steam "Get games" (owned games, install, store search), Android apps as persistent Lepton instances with a rated F-Droid catalogue and a private compatibility database, Android display controls over ADB, file and clipboard transfer, Flatpaks, remote and power actions. - apk-catalog/, compat-db/, frame/: catalogue build pipeline, Lakebed capsule for compatibility reports, Frame-side launchers. - tests/ and CI: server guard and validation tests plus Steam helper tests, run on Python 3.9 with script and app syntax checks. - Docs: README leads with the Mac app; new Android, panels, Steam games and field-notes docs; security notes on LAN-exposed ADB ports. Screenshot values for the headset's IP and Wi-Fi name are placeholders. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7.4 KiB
7.4 KiB
How the Frame is put together (field notes)
What we learnt by poking at a real Frame over SSH. Unless a line says
otherwise, it was verified 2026-09-25 on SteamOS 0.3.0 (VARIANT_ID=vr,
build 20260922.6101926, kernel 6.18, aarch64). Topic docs go deeper. This page
is the map.
The layer cake
SteamVR (vrserver, vrcompositor, vrdashboard) ← renders the room + panels
└─ gamescope --backend openvr ← one SteamVR overlay per app id
├─ Xwayland :0 (Steam UI, games, tagged apps) ← STEAM_GAME property = app id
├─ Xwayland :1 (STEAM_GAME_DISPLAY_0)
├─ Wayland socket gamescope-0
└─ steamos-nested-desktop ← "the Linux desktop" panel
└─ dbus-run-session startplasma-wayland
└─ kwin_wayland 1280×800, Wayland wayland-0, Xwayland :2
└─ plasmashell, Konsole, Dolphin, Flatpaks you open there
Lepton (Android 11, podman container "lepton-dev") ← its own panel, app 3056000
Facts worth knowing
| Fact | Where it matters |
|---|---|
The desktop is a nested Plasma session: runtime dir /run/user/1000/nested_plasma, its own D-Bus bus, WAYLAND_DISPLAY=wayland-0, DISPLAY=:2. A plain ssh frame app can't find it. Copy the env from plasmashell's /proc/<pid>/environ. |
run-on-frame.sh, paste-to-frame.sh |
The desktop size is hard-coded to 1280×800 in /usr/bin/steamos-nested-desktop (read-only rootfs). |
panels.md |
gamescope runs with --virtual-connector-strategy PerAppId. Each app id becomes a SteamVR overlay valve.steam.desktopgame.<id>, which is a panel you can float. Setting STEAM_GAME on an X11 window on :0 makes a new panel. |
panel-on-frame.sh, panels.md |
Handy gamescope root properties on :0: GAMESCOPE_FOCUSABLE_APPS, GAMESCOPE_FOCUSABLE_WINDOWS (triples: window, app id, pid), GAMESCOPE_FOCUSED_APP. Read them with DISPLAY=:0 xprop -root. |
Debugging panels |
gamescopectl screenshot <file> (with WAYLAND_DISPLAY=gamescope-0) captures gamescope's flat layer. |
Frame Control's capture |
The headset view (both eyes, fully composited: room, panels, dashboard, controllers) comes from OpenVR IVRScreenshots::RequestScreenshot(VRScreenshotType_Stereo). It's callable from python3 with ctypes against /opt/steamvr/bin/linuxarm64/libopenvr_api.so as an overlay app. The compositor appends .png, writing a 1920×1080 side-by-side image (960×1080 per eye) plus a left-eye preview, in about 0.3s. In standby the frame is blank. vrcmd --screenshot and vrcmd --compositorcmd screenshot_request wrote nothing, even with steamvr/rawCapturePath set. |
ui/frame_vrshot.py |
Battery: /sys/class/power_supply/max1720x_bat_7-36 gives µV/µA (current is positive while charging), time_to_full_now/time_to_empty_now in seconds, and temp in tenths of °C. The charger shows up as tcpm-source-psy-… (type=USB, usb_type=C PD [PD_PPS]), for example 12 V × 1.67 A. |
Frame Control's battery card |
vrcmd --stats reports activity_level (3 = standby). |
Telling whether the headset is being worn |
The SteamVR dashboard has docking: Float in World, Move, Size, Curvature, controller docking, Theater, Multitasking View. Inferred from /opt/steamvr/resources/webinterface/dashboard/ and not yet driven by hand. |
panels.md |
SteamVR settings live in ~/.config/openvr/config/steamvr.vrsettings, not under ~/.local/share/Steam/config/. dashboard.lastAccessedExternalOverlayKey names the last panel you used. |
Settings tweaks |
The Steam client's journal (journalctl --user) carries SteamVR system UI lines such as [Overlays] Created: … and vroverlay_uid<appid>. It's the quickest way to see panels come and go. |
Debugging |
Present: rsync, flatpak, python3, git, qdbus6, xrdp, xprop, xwininfo, xterm, konsole, dolphin, gamescopectl. Missing: wl-copy, xclip, xsel, kdeconnect-cli, tailscale, krfb, wayvnc. |
Script design |
Flathub is a system remote. --user installs over SSH work and show up in the desktop menu. |
install-apps.sh |
/ is 10 GB and read-only. /home is 929 GB. |
Where to put things |
Clipboard: Klipper over the nested D-Bus bus (qdbus6 org.kde.klipper …). |
paste-to-frame.sh |
Lepton listens for ADB on the Frame's loopback 5555, so tunnel it over SSH. It's Android 11 (API 30), 64-bit ARM only, with no clipboard service: Compose < 1.11, SDL/Kivy and Godot 4.3 apps crash on launch. |
apks.md, apk-catalog/ |
Lepton Development deletes every ADB-installed app when it exits (clear_baked_app_data "non steamlaunch container" in …/common/Lepton/lepton) unless LEPTON_NO_CLEANUP is set. |
apks.md |
Any APK can run as its own Lepton instance: run …/common/Lepton/lepton waitforexitandrun -- app.apk with SteamAppId set and STEAM_COMPAT_DATA_PATH under ~/.local/share/Steam. Data persists and each gets its own container and panel. frame/android/lepton-app.sh, ui/frame_android.py. |
apks.md |
The Steam client runs with -cef-enable-debugging, so its UI answers Chrome DevTools on loopback 127.0.0.1:8080. The SharedJSContext page has appStore (owned apps), downloadsStore and SteamClient.*. steam steam://install/<appid> over SSH installs an owned game; when the options dialog shows (state 7), SteamClient.Installs.ContinueInstall() accepts it. Verified 2026-09-25 with Balatro and Broforce. The Frame rating is steam_hw_compat_category_packed >> 8 & 3. |
steam-games.md, ui/frame_steam.py |
Chromium Flatpak 154 has no immersive WebXR: navigator.xr exists, but isSessionSupported("immersive-vr") returns false. Web VR180 players (DL8/DeoVR embeds) still play video inline as a flat, pannable view, and their VR button opens a tab on immersiveweb.dev. Forcing it doesn't help. --enable-features=OpenXR,WebXR --force-webxr-runtime=openxr, with /opt/steamvr and XR_RUNTIME_JSON exposed to the Flatpak, still returns false. The aarch64 Linux binary has no OpenXR code at all (no xrCreateInstance, no loader), even though chrome://flags lists #webxr-runtime → OpenXR. To watch in 3D, use a native player. Started with --remote-debugging-port=9222, Chromium answers DevTools on loopback. Verified 2026-09-25, BUILD_ID 20260922.6101926. |
Web video, panels.md |
Power actions need sudo, which asks for the Developer Mode password over SSH. |
Frame Control's power buttons |
Debug recipes
# Which panels (app ids) exist right now?
ssh frame 'DISPLAY=:0 xprop -root GAMESCOPE_FOCUSABLE_APPS GAMESCOPE_FOCUSED_APP'
# Watch panels being created
ssh frame 'journalctl --user -f | grep --line-buffered "\[Overlays\]"'
# gamescope's full flags (in case Valve changes them)
ssh frame 'tr "\0" " " < /proc/$(pgrep -x gamescope | head -n 1)/cmdline'
# Everything the SteamVR dashboard can say (find hidden features)
ssh frame 'cat /opt/steamvr/resources/webinterface/dashboard/localization/dashboard_english.json'
Where the rest lives
- Access and SSH: ssh.md
- Seeing the Frame from the Mac, and the Mac from the Frame: streaming.md
- Files and clipboard: file-transfer.md
- Android apps: apks.md
- Installing and buying Steam games: steam-games.md
- Floating windows in space: panels.md
- What's still unverified: open-questions.md