mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 08:00:32 +02:00
Add Steam screenshots, Tailscale remote access, VR-video fixes
- Screenshots: list the Frame's Steam screenshots, open them in the viewer, and save new ones to ~/Pictures/SteamFrame. Ids are validated before any shell, and copies land atomically. - Tailscale: scripts/tailscale-on-frame.sh installs a userspace tailscaled as a lingering systemd --user service with no sudo, SHA-256 checked, safe to re-run, with --uninstall. docs/tailscale.md covers setup and warns that in userspace mode every Frame port, including loopback-only DevTools and ADB, is reachable from the tailnet. - push-vr-video.sh: filenames starting with "-" are safe, symlinks are followed, and a real Videos\VR directory triggers a warning. - Tests cover the screenshot routes (19 total). Docs keep placeholder addresses for the headset and tailnet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
99653151c4
commit
eabf4cd1f9
10 files changed
+427
-54
No files matched your search
@@ -35,7 +35,7 @@ Lepton (Android 11, podman container "lepton-dev") ← its own panel, app 305600
|
||||
| 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](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 |
|
||||
| Present: `rsync`, `flatpak`, `python3`, `git`, `qdbus6`, `xrdp`, `xprop`, `xwininfo`, `xterm`, `konsole`, `dolphin`, `gamescopectl`. Missing: `wl-copy`, `xclip`, `xsel`, `kdeconnect-cli`, `tailscale` (installable in `~`, see below), `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` |
|
||||
@@ -43,8 +43,10 @@ Lepton (Android 11, podman container "lepton-dev") ← its own panel, app 305600
|
||||
| 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](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](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](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 `XR_RUNTIME_JSON`, `xrGetInstanceProcAddr` or loader strings), even though `chrome://flags` lists `#webxr-runtime` → OpenXR. **Why (verified against source 2026-09-25):** M154 is the first release that compiles OpenXR on Linux (`enable_openxr` includes `is_linux`, `checkout_openxr` is true in Flathub's tarball, and Flathub's GN args don't turn it off). But `content/services/isolated_xr_device/xr_runtime_provider.cc` only creates an OpenXR device under `ENABLE_OPENXR && IS_WIN`, on 154, 155 and `main`. Nothing on Linux calls the OpenXR code, so the linker drops it. The missing pieces are two unmerged Gerrit CLs (bug 506004811): [8132979](https://chromium-review.googlesource.com/c/chromium/src/+/8132979) wires the provider on Linux (with `kOpenXR` still off by default, so it needs `--enable-features=OpenXR`), and [8441736](https://chromium-review.googlesource.com/c/chromium/src/+/8441736) runs the XR service in a sandbox that allows SteamVR's sockets. The Frame does have an aarch64 runtime: `~/.config/openxr/1/active_runtime.json` → SteamVR `bin/linuxarm64/vrclient.so`. To watch in 3D, use a native player, or a Chromium built with those two CLs. Started with `--remote-debugging-port=9222`, Chromium answers DevTools on loopback. **Verified 2026-09-25**, BUILD_ID 20260922.6101926. | Web video, [panels.md](panels.md) |
|
||||
| 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 `XR_RUNTIME_JSON`, `xrGetInstanceProcAddr` or loader strings), even though `chrome://flags` lists `#webxr-runtime` → OpenXR. **Why (verified against source 2026-09-25):** M154 is the first release that compiles OpenXR on Linux (`enable_openxr` includes `is_linux`, `checkout_openxr` is true in Flathub's tarball, and Flathub's GN args don't turn it off). But `content/services/isolated_xr_device/xr_runtime_provider.cc` only creates an OpenXR device under `ENABLE_OPENXR && IS_WIN`, on 154, 155 and `main`. Nothing on Linux calls the OpenXR code, so the linker drops it. The missing pieces are two unmerged Gerrit CLs (bug 506004811): [8132979](https://chromium-review.googlesource.com/c/chromium/src/+/8132979) wires the provider on Linux (with `kOpenXR` still off by default, so it needs `--enable-features=OpenXR`), and [8441736](https://chromium-review.googlesource.com/c/chromium/src/+/8441736) runs the XR service in a sandbox that allows SteamVR's sockets. The Frame does have an aarch64 runtime: `~/.config/openxr/1/active_runtime.json` → SteamVR `bin/linuxarm64/vrclient.so`. To watch in 3D, use a native player, or a Chromium built with those two CLs ([webxr-chromium.md](webxr-chromium.md)). Started with `--remote-debugging-port=9222`, Chromium answers DevTools on loopback. **Verified 2026-09-25**, BUILD_ID 20260922.6101926. | Web video, [panels.md](panels.md) |
|
||||
| **DeoVR (Steam app 837380, Windows/Unity) runs immersively** under Proton ARM64 + FEX: Unity's OpenVR XR plugin finds `OpenVR Headset(Steam Frame)` and the `frame_controller`, the GPU shows as Turnip Adreno 750, and AVPro Video decodes through `MF-MediaEngine-Hardware`. It played 7680×3840 and 8192×4096 H.265 VR180 SBS streams in dome/fisheye mode (`FirstFrameReady`). Unity's own `VideoPlayer` (used for grid thumbnails) fails with `0xc00d36bb`, so thumbnail previews stay blank. The first launch takes about 45 s (`ComputeShaders: InitAsync`). Log: `compatdata/837380/pfx/drive_c/users/steamuser/AppData/LocalLow/Deo VR/Deo VR/Player.log`. **Verified 2026-09-25**, BUILD_ID 20260922.6101926. | [vr-video.md](vr-video.md) |
|
||||
| **Wolvic (VR browser APK) runs in Lepton against SteamVR's OpenXR**, with limits. The stock Lynx build aborts (`Runtime doesn't support selected swapChain color format`: it wants `GL_RGBA8`), and the stock Quest build fails with `XR_ERROR_API_VERSION_UNSUPPORTED`. Patching `DeviceDelegateOpenXR::GetSwapChainCreateInfo` in the Lynx build's `libnative-lib.so` to `GL_SRGB8_ALPHA8` (0x8C43) and re-signing fixes start-up. The Gecko engine then segfaults in `libxul`. The Chromium-engine build (Lynx v1.3-chromium) browses fine as an immersive app. Its page reports `isSessionSupported("immersive-vr") == true`, and `requestSession` succeeds, running about 36 rAF/s, but the headset shows **black** for WebXR content, or Wolvic's loading spinner that never clears, until the session is ended. Video decodes on the software `OMX.google.h264.decoder`. Tapping the URL bar's selection menu crashes it (no clipboard service). Open URLs with `am start -a VIEW -n com.igalia.wolvic/.VRBrowserActivity -d <url>` over the instance's ADB. DevTools is at `localabstract:content_shell_devtools_remote`. **Verified 2026-09-25**, BUILD_ID 20260922.6101926. | Web VR video, [apks.md](apks.md) |
|
||||
| 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` |
|
||||
| Power actions need `sudo`, which asks for the Developer Mode password over SSH. | Frame Control's power buttons |
|
||||
|
||||
## Debug recipes
|
||||
@@ -70,5 +72,6 @@ ssh frame 'cat /opt/steamvr/resources/webinterface/dashboard/localization/dashbo
|
||||
- Files and clipboard: [file-transfer.md](file-transfer.md)
|
||||
- Android apps: [apks.md](apks.md)
|
||||
- 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)
|
||||
- What's still unverified: [open-questions.md](open-questions.md)
|
||||
@@ -25,7 +25,7 @@ build 20260922.6101926, kernel 6.18, aarch64):
|
||||
(`vrserver`, `vrcompositor`) and `xrdp` are running.
|
||||
- **9.** `rsync`, `flatpak`, `python3`, `git`, `qdbus6` and `xrdp` are present.
|
||||
`wl-copy`, `xclip`, `xsel`, `kdeconnect-cli`, `tailscale`, `krfb` and `wayvnc`
|
||||
are **not**. `paste-to-frame.sh` now uses Klipper over D-Bus and round-trips
|
||||
are **not** (Tailscale can be added in `~`; see [tailscale.md](tailscale.md)). `paste-to-frame.sh` now uses Klipper over D-Bus and round-trips
|
||||
text correctly.
|
||||
- Flathub is already configured as a **system** remote; Chromium is the only
|
||||
installed Flatpak. `/` is 10 GB (42% used); `/home` is 929 GB.
|
||||
@@ -40,7 +40,7 @@ build 20260922.6101926, kernel 6.18, aarch64):
|
||||
gets its own SteamVR overlay (`valve.steam.desktopgame.<id>`). Three were
|
||||
created side by side with `panel-on-frame.sh`. See [panels.md](panels.md).
|
||||
|
||||
Still open: 4, 6, 7, 11 (in-headset connect), 12–21.
|
||||
Still open: 4, 6, 7, 11 (in-headset connect), 12–15, 16 (off-LAN and after a reboot), 17–21.
|
||||
|
||||
## Check on the headset (in order)
|
||||
|
||||
@@ -82,8 +82,10 @@ Still open: 4, 6, 7, 11 (in-headset connect), 12–21.
|
||||
to type locally.
|
||||
15. **ADB**: does `adb shell` over USB-C from a Mac (not just a Windows PC)
|
||||
reach the Linux side? Does USB power from the Mac cope?
|
||||
16. **Tailscale**: can it be installed persistently (Flatpak? a
|
||||
userspace `tailscaled` in `~`?) for access off the home LAN?
|
||||
16. ~~**Tailscale**~~: answered 2026-09-25. A userspace `tailscaled` in `~`
|
||||
runs as a lingering user service with no sudo; see [tailscale.md](tailscale.md).
|
||||
Still open: reaching the Frame from outside the home network, and the service
|
||||
starting after a reboot.
|
||||
17. **Floating panels in the headset** (see [panels.md](panels.md)): do the
|
||||
panels from `panel-on-frame.sh` show up, take input, and offer **Float in
|
||||
World** / **Move** / **Size**? Do floating positions survive closing and
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# Tailscale: use the Frame from anywhere
|
||||
|
||||
With Tailscale on the Frame, the `frame` SSH alias works off the home LAN, and
|
||||
so does everything built on it: Frame Control, the scripts and the Mac app.
|
||||
When the Mac and the Frame are on the same network, Tailscale connects them
|
||||
directly, so there's no relay in the way (`tailscale ping frame` → `via
|
||||
192.168.1.50:41641`, 20 ms).
|
||||
|
||||
```sh
|
||||
scripts/tailscale-on-frame.sh # install or update, then approve the login URL
|
||||
scripts/connect.sh frame.<tailnet>.ts.net # point the alias at Tailscale (the script prints this)
|
||||
scripts/tailscale-on-frame.sh --uninstall
|
||||
```
|
||||
|
||||
## How it's installed
|
||||
|
||||
There's no Tailscale Flatpak, and the rootfs is read-only. So the script
|
||||
installs Tailscale's static arm64 build in the `steamos` user's home and runs
|
||||
`tailscaled --tun=userspace-networking` as a systemd **user** service. It
|
||||
doesn't need sudo, and SteamOS updates don't touch it.
|
||||
|
||||
| Path | What |
|
||||
|---|---|
|
||||
| `~/.local/share/tailscale/<version>/` | `tailscale`, `tailscaled` (SHA-256 checked against pkgs.tailscale.com) |
|
||||
| `~/.local/share/tailscale/current` | Symlink to the active version |
|
||||
| `~/.local/share/tailscale/state/` | Node key and state |
|
||||
| `~/.local/bin/tailscale` | CLI wrapper that points at the daemon's socket (`$XDG_RUNTIME_DIR/tailscale/tailscaled.sock`) |
|
||||
| `~/.config/systemd/user/tailscaled.service` | The service |
|
||||
|
||||
Lingering (`loginctl enable-linger`) is on, so the service starts at boot
|
||||
without anyone logging in. polkit allowed that without sudo. Re-running the
|
||||
script is safe. It restarts `tailscaled` only if the version or unit changed,
|
||||
and then does so detached after 3 s, because the SSH session may itself run
|
||||
over Tailscale.
|
||||
|
||||
To update, run the script again; it installs the latest stable version. To
|
||||
manage the node, use `ssh frame '~/.local/bin/tailscale status'` (or `set`,
|
||||
`down`, `up`).
|
||||
|
||||
**Verified 2026-09-25 (SteamOS 0.3.0, build 20260922.6101926, Tailscale
|
||||
1.102.4):**
|
||||
|
||||
- First install and login approval. This ran an earlier revision of the script,
|
||||
which restarted the daemon unconditionally. The node is `frame`,
|
||||
with a 100.x.y.z tailnet address.
|
||||
- SSH works over Tailscale: the Frame serves the same ED25519 host key as it
|
||||
does on `frame.local`.
|
||||
- Frame Control's status and Get games work through the alias.
|
||||
- The current script: a re-run with nothing changed doesn't restart anything,
|
||||
and a re-run with a changed unit restarts `tailscaled` 3 s after the SSH
|
||||
session ends and then reads `Running`. The timer needs
|
||||
`AccuracySec=100ms`; the default of 1 min made it fire up to a minute late.
|
||||
The first-install guard was checked on its own.
|
||||
|
||||
**Not verified:**
|
||||
|
||||
- A clean first install and login with the current script end to end. It would
|
||||
mean removing the node from the tailnet.
|
||||
- Reaching the Frame from outside the home network. Only the direct LAN path
|
||||
was tested.
|
||||
- The service coming up after a reboot. That's **inferred** from linger plus
|
||||
`WantedBy=default.target`; the Frame hasn't been rebooted since.
|
||||
|
||||
## Exposure: every port is on the tailnet
|
||||
|
||||
In userspace mode, `tailscaled` passes inbound tailnet connections to the
|
||||
Frame's **loopback**. Any device on the tailnet can therefore reach **every**
|
||||
listening port, including ones meant to be local-only. Checked from the Mac on
|
||||
2026-09-25:
|
||||
|
||||
| Port | Service | Normally |
|
||||
|---|---|---|
|
||||
| 22 | sshd | LAN |
|
||||
| 8080 | Steam client DevTools (full control of the Steam client and account session) | loopback only |
|
||||
| 27062 | SteamVR `vrserver` | loopback only |
|
||||
| 5555 | Lepton ADB (unauthenticated shell into Android) | LAN |
|
||||
| 3389 | xrdp | LAN |
|
||||
|
||||
The user accepted this on 2026-09-25, since the tailnet only holds their own
|
||||
devices. Other options:
|
||||
|
||||
- `tailscale set --shields-up` blocks **all** inbound connections. That
|
||||
includes SSH and Tailscale SSH (`--ssh`), both checked.
|
||||
- A tailnet policy that tags the Frame (`tag:frame`) and allows only
|
||||
`tag:frame:22` keeps the other ports private. This is an admin-console
|
||||
change.
|
||||
- Kernel-mode Tailscale (a root install, e.g. systemd-sysext) wouldn't expose
|
||||
loopback-only ports, but it needs sudo and may not survive SteamOS updates.
|
||||
|
||||
If the Mac's Tailscale is off, the alias won't resolve. Use
|
||||
`scripts/connect.sh frame.local` to go back to the LAN name.
|
||||
Reference in new issue
Block a user