diff --git a/docs/how-the-frame-works.md b/docs/how-the-frame-works.md index c5084ab..818bf09 100644 --- a/docs/how-the-frame-works.md +++ b/docs/how-the-frame-works.md @@ -44,7 +44,7 @@ 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/` 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 ([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) | +| 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)). That build (156.0.8071.0, arm64) reports `immersive-vr` as supported and starts a session that SteamVR takes as its scene app (verified 2026-09-26, seccomp sandbox off). What it looks like in the headset is still untested. 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 ` 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` | diff --git a/docs/webxr-chromium.md b/docs/webxr-chromium.md index 1132a0c..ee43ef4 100644 --- a/docs/webxr-chromium.md +++ b/docs/webxr-chromium.md @@ -46,12 +46,21 @@ Linux backend uses Vulkan (`XR_USE_GRAPHICS_API_VULKAN`). [`scripts/build-chromium-xr.sh`](../scripts/build-chromium-xr.sh) cross-compiles arm64 Linux Chromium on an x64 Linux host. It doesn't need sudo: the arm64 sysroot comes from Chromium's own script. It needs about -90 GB of disk. It shallow-fetches the CL ref, runs `gclient sync --no-history`, -installs the sysroot, builds `chrome` with `symbol_level=0` and proprietary -codecs, and packs `chromium-xr-arm64.tar.xz`. Progress is logged to -`~/chromium-xr/stage`. The build aborts if `/` drops below 12 GB free. +90 GB of disk. It shallow-fetches the CL ref (patchset 44), runs +`gclient sync --no-history`, installs the sysroot, applies one extra seccomp +fix (below), builds `chrome` with `symbol_level=0` and proprietary codecs, and +packs `chromium-xr-arm64.tar.xz` (about 145 MB, GPU libraries included). +Progress is logged to `~/chromium-xr/stage`. The build aborts if `/` drops +below 12 GB free. -First run: a 12-core, 31 GB x64 Linux box, started 2026-09-25. +First run, 2026-09-25, on a 12-core, 31 GB x64 Linux box: 9 h 33 min for +94,835 steps, giving Chromium 156.0.8071.0. A rebuild after a one-file change +takes under a minute, plus about 4 minutes to repack. + +**The extra fix.** The CL's XR seccomp policy refuses `getsockopt`. SteamVR's +client calls `getsockopt(SOL_SOCKET, SO_PEERCRED)` inside `xrCreateInstance`, +so the XR process died with a seccomp crash (arm64 syscall 209). The script +allows that one option. ## Running it on the Frame @@ -59,24 +68,49 @@ First run: a 12-core, 31 GB x64 Linux box, started 2026-09-25. ```sh BUILD_HOST=my-linux-box scripts/chromium-xr.sh install # your build host; scp, unpack to ~/chromium-xr -scripts/chromium-xr.sh launch [URL] # headset desktop, --enable-features=OpenXR +scripts/chromium-xr.sh launch [URL] # its own VR panel, --enable-features=OpenXR scripts/chromium-xr.sh check # prints isSessionSupported('immersive-vr') ``` -It runs natively, not as a Flatpak, so the XR sandbox and SteamVR's IPC work -as the CL expects. It uses its own profile (`~/.config/chromium-xr`) and -DevTools on loopback port 9223, so it doesn't collide with the Flatpak's 9222. +It runs natively, not as a Flatpak. `launch` opens it as its own panel on +gamescope's X display, the same way as [`panel-on-frame.sh`](panels.md), so +the Plasma desktop doesn't need to be open. It uses its own profile +(`~/.config/chromium-xr`) and DevTools on loopback port 9223, so it doesn't +collide with the Flatpak's 9222. When a page enters VR, Chrome asks +**Allow VR?** in the browser panel; choose *Allow this time* or *Allow while +visiting the site*. -**Verified 2026-09-25:** +**Seccomp is off.** `launch` passes `--disable-seccomp-filter-sandbox`. With +the XR seccomp policy on, SteamVR's client reads `/proc/self/status` through +Chrome's file broker and gets the broker's pid. SteamVR then binds the app to +the wrong process ("Unable to init path manager: VRInitError_Init_Internal") +and `xrCreateInstance` fails. The broker can't answer `/proc/self` for another +process, so fixing this needs a change in Chromium's broker client or in the +CL. The namespace sandbox stays on, but seccomp is off for every process, so +use this profile for VR sites rather than everyday browsing. -- Vulkan is there: Turnip (Mesa) on Adreno 750, API 1.4.359. -- Unprivileged user namespaces work (`unshare -Ur true`), so Chromium's - namespace sandbox shouldn't need the setuid `chrome_sandbox`. +**Verified 2026-09-26** (Frame BUILD_ID 20260922.6101926, SteamVR 2.17.10, +this build): -**Unverified (inferred):** +- `isSessionSupported('immersive-vr')` is `true`. The WebXR samples page + shows "VR support detected". +- `requestSession('immersive-vr')` succeeds after the prompt. With a WebGL + layer, the first XR frame has a viewer pose with 2 views and a + 2880 × 1440 framebuffer (1440 × 1440 per eye). +- SteamVR moves the app from `VRApplication_OpenXRInstance` to + `VRApplication_OpenXRScene` and gives it scene focus. `xrEndFrame` submits + both projection views, and the compositor receives the 2880 × 1440 scene. +- The OpenXR runtime uses Vulkan (`XR_KHR_vulkan_enable2`). Chromium's own GPU + process uses ANGLE on GL, running on zink over Turnip Vulkan (Adreno 750); + Chromium's Vulkan backend is off. That doesn't stop the session. +- Unprivileged user namespaces work (`unshare -Ur true`), so the namespace + sandbox runs without the setuid `chrome_sandbox`. -- Chromium's GPU process may still fall back from Vulkan to GL on Turnip. -- An immersive session started from a window on the nested desktop may not - hand over cleanly to the SteamVR compositor. -- If the sandbox fails to start, `--no-sandbox` is the fallback for a first - test. +**Not verified yet:** nobody was wearing the headset during the test, so +SteamVR kept it in standby. The session stayed at +`XR_SESSION_STATE_SYNCHRONIZED` (the page saw `visibilityState: "hidden"`) +and only the first frame ran. Still open: + +- Whether the image shows up correctly in the headset, and at what frame rate. +- Whether VR180 or 360 video players (DeoVR, DL8 embeds) play in 3D. +- Controller and hand input in the session. diff --git a/ui/server.py b/ui/server.py index 5415c51..28a8f5f 100755 --- a/ui/server.py +++ b/ui/server.py @@ -1047,6 +1047,10 @@ def main(): except KeyboardInterrupt: pass finally: + # The app both closes stdin and sends SIGTERM on quit; a second signal + # mid-cleanup would abort it and leave the SSH master running. + if not frame_host.WINDOWS: + signal.signal(signal.SIGTERM, signal.SIG_IGN) # The master was started with -N, so it stays up until told to exit. if CONTROL: subprocess.run([*MUX, "-O", "exit", FRAME], capture_output=True, stdin=subprocess.DEVNULL)