mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 03:00:18 +02:00
WebXR: point to the public chromium-webxr-steam-frame repo
The build script, launcher and Steam shortcut helper now live in saphid/chromium-webxr-steam-frame, so drop the duplicate copies here. The doc keeps the findings, verification and upstream status. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d7fc0189b5
commit
0cb289d571
4 files changed
+43
-366
No files matched your search
+43
-64
@@ -3,9 +3,11 @@
|
|||||||
Goal: open a web VR180 or 360 player (DeoVR and DL8 embeds, WebXR samples),
|
Goal: open a web VR180 or 360 player (DeoVR and DL8 embeds, WebXR samples),
|
||||||
press its VR button, and watch in 3D in the headset.
|
press its VR button, and watch in 3D in the headset.
|
||||||
|
|
||||||
A standalone, public version of this build (build script, the SO_PEERCRED
|
The build and installer now live in their own public repo,
|
||||||
patch, and a Frame-side installer that adds "Chromium XR" to the Steam library)
|
[saphid/chromium-webxr-steam-frame](https://github.com/saphid/chromium-webxr-steam-frame):
|
||||||
is at [saphid/chromium-webxr-steam-frame](https://github.com/saphid/chromium-webxr-steam-frame).
|
a build script for an x86-64 Linux host, the SO_PEERCRED patch, and a
|
||||||
|
Frame-side installer that adds "Chromium XR" to the Steam library. This page
|
||||||
|
keeps the findings and what was verified on this Frame.
|
||||||
|
|
||||||
## Why Flathub Chromium can't
|
## Why Flathub Chromium can't
|
||||||
|
|
||||||
@@ -45,76 +47,53 @@ gets both.
|
|||||||
SteamVR (`bin/linuxarm64/vrclient.so`, `VALVE_runtime_is_steamvr`). The
|
SteamVR (`bin/linuxarm64/vrclient.so`, `VALVE_runtime_is_steamvr`). The
|
||||||
Linux backend uses Vulkan (`XR_USE_GRAPHICS_API_VULKAN`).
|
Linux backend uses Vulkan (`XR_USE_GRAPHICS_API_VULKAN`).
|
||||||
|
|
||||||
## Building it
|
## Building and installing it
|
||||||
|
|
||||||
[`scripts/build-chromium-xr.sh`](../scripts/build-chromium-xr.sh)
|
Follow the [public repo's README](https://github.com/saphid/chromium-webxr-steam-frame#build).
|
||||||
cross-compiles arm64 Linux Chromium on an x64 Linux host. It doesn't need
|
In short: `build/build.sh` on an x64 Linux host (no sudo, about 90 GB of
|
||||||
sudo: the arm64 sysroot comes from Chromium's own script. It needs about
|
disk) produces `chromium-xr-arm64.tar.xz` (about 145 MB), and
|
||||||
90 GB of disk. It shallow-fetches the CL ref (patchset 44), runs
|
`frame/install.sh` on the Frame unpacks it to `~/chromium-xr`, installs the
|
||||||
`gclient sync --no-history`, installs the sysroot, applies one extra seccomp
|
`chromium-xr` launcher in `~/.local/bin`, and adds the Steam library shortcut
|
||||||
fix (below), builds `chrome` with `symbol_level=0` and proprietary codecs, and
|
through the Steam client's DevTools port, the same way as T3 Code
|
||||||
packs `chromium-xr-arm64.tar.xz` (about 145 MB, GPU libraries included).
|
([apks.md](apks.md)). Launching the shortcut gives Chromium its own panel,
|
||||||
Progress is logged to `~/chromium-xr/stage`. The build aborts if the disk
|
`valve.steam.desktopgame.<appid>`, like any other app.
|
||||||
holding `~/chromium-xr` drops below 12 GB free.
|
|
||||||
|
|
||||||
First run, 2026-09-25, on a 12-core, 31 GB x64 Linux box: 9 h 33 min for
|
First build, 2026-09-25, on a 12-thread, 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
|
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.
|
takes under a minute, plus about 4 minutes to repack.
|
||||||
|
|
||||||
**The extra fix.** The CL's XR seccomp policy refuses `getsockopt`. SteamVR's
|
To debug from the Mac, start it with DevTools on the Frame:
|
||||||
|
`ssh frame 'DISPLAY=:0 ~/.local/bin/chromium-xr --remote-debugging-port=9223 URL'`, or
|
||||||
|
launch it as a panel with
|
||||||
|
`scripts/panel-on-frame.sh -- '~/.local/bin/chromium-xr' --remote-debugging-port=9223 URL`
|
||||||
|
([panels.md](panels.md)). DevTools has no authentication. It listens on
|
||||||
|
loopback, but with the userspace Tailscale from [tailscale.md](tailscale.md)
|
||||||
|
running, loopback ports are reachable from your tailnet. Close the browser
|
||||||
|
when you're done. Chromium runs one browser per profile, so close the
|
||||||
|
Steam-launched one first or the flag is ignored.
|
||||||
|
|
||||||
|
**The SO_PEERCRED fix.** The XR seccomp policy refuses `getsockopt`. SteamVR's
|
||||||
client calls `getsockopt(SOL_SOCKET, SO_PEERCRED)` inside `xrCreateInstance`,
|
client calls `getsockopt(SOL_SOCKET, SO_PEERCRED)` inside `xrCreateInstance`,
|
||||||
so the XR process died with a seccomp crash (arm64 syscall 209). The script
|
so the XR process died with a seccomp crash (arm64 syscall 209). The patch
|
||||||
allows that one option. That's needed but not enough: `launch` still turns
|
allows that one option. It's needed but not enough: the launcher still turns
|
||||||
seccomp off (below), so the patch only matters once that's fixed too.
|
seccomp off (below), so the patch only matters once that's fixed too.
|
||||||
|
|
||||||
## Running it on the Frame
|
**Seccomp is off.** The launcher 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.
|
||||||
|
|
||||||
[`scripts/chromium-xr.sh`](../scripts/chromium-xr.sh):
|
**Upstream (2026-09-27).** CL 8441736 (the XR sandbox) has merged into
|
||||||
|
Chromium, still refusing `getsockopt`; CL 8132979 is still in review. Valve
|
||||||
```sh
|
and the CLs' author are working on Steam Frame support
|
||||||
BUILD_HOST=my-linux-box scripts/chromium-xr.sh install # your build host; scp, unpack to ~/chromium-xr
|
([utzcoz/chromium-webxr-linux#5](https://github.com/utzcoz/chromium-webxr-linux/issues/5)).
|
||||||
scripts/chromium-xr.sh launch [URL] # its own VR panel, --enable-features=OpenXR
|
Both sandbox problems above, with the patch, are reported in
|
||||||
scripts/chromium-xr.sh steam # adds "Chromium XR" to the Steam library
|
[utzcoz/chromium-webxr-linux#7](https://github.com/utzcoz/chromium-webxr-linux/issues/7).
|
||||||
scripts/chromium-xr.sh check # prints isSessionSupported('immersive-vr')
|
|
||||||
```
|
|
||||||
|
|
||||||
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`](../scripts/panel-on-frame.sh) ([panels.md](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*.
|
|
||||||
|
|
||||||
Both ways of starting it run
|
|
||||||
[`frame/chromium-xr/launch.sh`](../frame/chromium-xr/launch.sh), copied to
|
|
||||||
`~/Applications/ChromiumXR/launch.sh` on the Frame, which holds Chrome's flags.
|
|
||||||
It sits outside `~/chromium-xr` so `install` doesn't delete it.
|
|
||||||
|
|
||||||
**From the Steam library (verified 2026-09-26).** `steam` adds a non-Steam
|
|
||||||
shortcut called "Chromium XR" (with the Flathub Chromium icon, if that's
|
|
||||||
installed) through the Steam client's DevTools port, the same way as T3 Code
|
|
||||||
([apks.md](apks.md)), without restarting Steam. It saves the app id in
|
|
||||||
`~/Applications/ChromiumXR/shortcut-appid`, so rerunning it, even after you
|
|
||||||
rename the shortcut in the library, doesn't add a second one. On this Frame
|
|
||||||
the shortcut app id is 2240749789. Launching it from the library gives
|
|
||||||
Chromium its own panel, `valve.steam.desktopgame.2240749789`, like any other
|
|
||||||
app. A Steam launch doesn't open a DevTools port, so `check` needs `launch`.
|
|
||||||
Chromium runs one browser per profile: while the Steam-launched one is open,
|
|
||||||
`launch` opens its URL in that window, without DevTools, then prints
|
|
||||||
`failed: ... exited` because no new window appeared. Close it first.
|
|
||||||
|
|
||||||
**Seccomp is off.** The wrapper 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. DevTools on
|
|
||||||
port 9223 has no authentication. It listens on loopback, but with the
|
|
||||||
userspace Tailscale from [tailscale.md](tailscale.md) running, loopback ports
|
|
||||||
are reachable from your tailnet. Close the browser when you're done.
|
|
||||||
|
|
||||||
**Verified 2026-09-26** (Frame BUILD_ID 20260922.6101926, SteamVR 2.17.10,
|
**Verified 2026-09-26** (Frame BUILD_ID 20260922.6101926, SteamVR 2.17.10,
|
||||||
this build):
|
this build):
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Frame-side: start the WebXR Chromium build (~/chromium-xr). The Steam
|
|
||||||
# library shortcut "Chromium XR" runs this, and so does
|
|
||||||
# `scripts/chromium-xr.sh launch` (which adds a DevTools port). Extra
|
|
||||||
# arguments go to Chrome, so a URL opens that page.
|
|
||||||
#
|
|
||||||
# Lives in ~/Applications/ChromiumXR, outside ~/chromium-xr, so reinstalling
|
|
||||||
# the build doesn't delete it.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
CHROME="$HOME/chromium-xr/chrome"
|
|
||||||
[[ -x "$CHROME" ]] || { echo "launch.sh: no build at $CHROME (run chromium-xr.sh install)" >&2; exit 1; }
|
|
||||||
|
|
||||||
# Without --no-first-run and --password-store=basic, startup can stop at a
|
|
||||||
# first-run or keyring prompt.
|
|
||||||
# --disable-seccomp-filter-sandbox: under the XR seccomp policy, SteamVR's
|
|
||||||
# client reads /proc/self/status through the file broker, gets the broker's
|
|
||||||
# pid, and SteamVR binds the app to the wrong process, so xrCreateInstance
|
|
||||||
# fails. The namespace sandbox stays on, but seccomp is off for every
|
|
||||||
# process, so keep this profile for VR sites.
|
|
||||||
exec "$CHROME" \
|
|
||||||
--user-data-dir="$HOME/.config/chromium-xr" \
|
|
||||||
--enable-features=OpenXR \
|
|
||||||
--ozone-platform=x11 \
|
|
||||||
--no-first-run --no-default-browser-check --password-store=basic \
|
|
||||||
--disable-seccomp-filter-sandbox \
|
|
||||||
"$@"
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Linux-side (x64 host): cross-compile arm64 Chromium with the Linux OpenXR CLs
|
|
||||||
# (8441736 + 8132979, bug 506004811), plus a one-option seccomp fix, so WebXR
|
|
||||||
# immersive-vr works on the Frame.
|
|
||||||
# Needs ~90 GB free, no sudo. Takes hours; run it detached on the build host:
|
|
||||||
# scp scripts/build-chromium-xr.sh buildhost:chromium-xr/build.sh
|
|
||||||
# ssh buildhost 'cd ~/chromium-xr && tmux new -d -s chromium-xr "./build.sh > build.log 2>&1"'
|
|
||||||
# Progress: ~/chromium-xr/stage. Output: ~/chromium-xr/chromium-xr-arm64.tar.xz,
|
|
||||||
# which scripts/chromium-xr.sh install copies to the Frame.
|
|
||||||
# Re-running resumes: existing checkout and out/XR are reused.
|
|
||||||
set -euo pipefail
|
|
||||||
W=~/chromium-xr
|
|
||||||
cd "$W"
|
|
||||||
stage(){ echo "$(date -Is) $*" | tee -a "$W/stage"; }
|
|
||||||
# Returns non-zero below 12 GB free; set -e turns that into an exit at top level.
|
|
||||||
guard(){ avail=$(df --output=avail -BG "$W" | tail -n 1 | tr -dc 0-9); if [ "$avail" -lt 12 ]; then stage "ABORT: only ${avail}G free for $W"; return 3; fi; }
|
|
||||||
[ -d depot_tools ] || git clone -q https://chromium.googlesource.com/chromium/tools/depot_tools.git
|
|
||||||
export PATH="$W/depot_tools:$PATH" DEPOT_TOOLS_UPDATE=1 DEPOT_TOOLS_METRICS=0
|
|
||||||
CL_REF=refs/changes/79/8132979/44
|
|
||||||
if [ ! -f .gclient ]; then
|
|
||||||
cat > .gclient <<'G'
|
|
||||||
solutions = [{ "name": "src", "url": "https://chromium.googlesource.com/chromium/src.git",
|
|
||||||
"managed": False, "custom_deps": {}, "custom_vars": { "checkout_nacl": False } }]
|
|
||||||
target_os = ["linux"]
|
|
||||||
target_cpu = ["arm64"]
|
|
||||||
G
|
|
||||||
fi
|
|
||||||
# Keyed on a real commit, so an interrupted first fetch is retried on re-run.
|
|
||||||
if ! git -C src rev-parse -q --verify HEAD >/dev/null 2>&1; then
|
|
||||||
stage "clone src at $CL_REF"
|
|
||||||
mkdir -p src
|
|
||||||
[ -d src/.git ] || git -C src init -q
|
|
||||||
git -C src remote get-url origin >/dev/null 2>&1 || git -C src remote add origin https://chromium.googlesource.com/chromium/src.git
|
|
||||||
git -C src fetch -q --depth=1 origin "$CL_REF"
|
|
||||||
git -C src checkout -q FETCH_HEAD
|
|
||||||
fi
|
|
||||||
guard
|
|
||||||
stage "src at $(git -C src log -1 --format='%h %s')"
|
|
||||||
rev=$(git -C src rev-parse HEAD)
|
|
||||||
# Sync once per revision: once the patch below is applied, gclient sync
|
|
||||||
# refuses to run on the modified checkout, so re-runs must skip it.
|
|
||||||
if [ "$(cat "$W/synced" 2>/dev/null)" != "$rev" ]; then
|
|
||||||
stage "gclient sync"
|
|
||||||
gclient sync --nohooks --no-history -D --shallow --revision "src@$rev" -j 8
|
|
||||||
guard
|
|
||||||
stage "runhooks"
|
|
||||||
gclient runhooks
|
|
||||||
src/build/linux/sysroot_scripts/install-sysroot.py --arch=arm64
|
|
||||||
echo "$rev" > "$W/synced"
|
|
||||||
fi
|
|
||||||
guard
|
|
||||||
cd src
|
|
||||||
# CL 8441736's XR seccomp policy refuses getsockopt, and SteamVR's IPC client
|
|
||||||
# calls getsockopt(SO_PEERCRED) inside xrCreateInstance, which crashes the XR
|
|
||||||
# process (verified on the Frame 2026-09-26). Allow only that option.
|
|
||||||
IFS= read -r -d '' PEERCRED_PATCH <<'P' || true
|
|
||||||
diff --git a/sandbox/policy/linux/bpf_xr_policy_linux.cc b/sandbox/policy/linux/bpf_xr_policy_linux.cc
|
|
||||||
index 435e13d396..297453f582 100644
|
|
||||||
--- a/sandbox/policy/linux/bpf_xr_policy_linux.cc
|
|
||||||
+++ b/sandbox/policy/linux/bpf_xr_policy_linux.cc
|
|
||||||
@@ -11,6 +11,7 @@
|
|
||||||
#include "sandbox/linux/system_headers/linux_syscalls.h"
|
|
||||||
#include "sandbox/policy/linux/sandbox_linux.h"
|
|
||||||
|
|
||||||
+using sandbox::bpf_dsl::AllOf;
|
|
||||||
using sandbox::bpf_dsl::Allow;
|
|
||||||
using sandbox::bpf_dsl::Arg;
|
|
||||||
using sandbox::bpf_dsl::Error;
|
|
||||||
@@ -27,8 +28,8 @@ XrProcessPolicy::~XrProcessPolicy() = default;
|
|
||||||
ResultExpr XrProcessPolicy::EvaluateSyscall(int system_call_number) const {
|
|
||||||
switch (system_call_number) {
|
|
||||||
// The runtime reaches its compositor over an AF_UNIX socket and passes fds
|
|
||||||
- // with SCM_RIGHTS, neither of which the GPU policy allows. get/setsockopt
|
|
||||||
- // stay disallowed; add a narrow level/optname restriction if ever needed.
|
|
||||||
+ // with SCM_RIGHTS, neither of which the GPU policy allows. setsockopt
|
|
||||||
+ // stays disallowed; getsockopt is limited to SO_PEERCRED below.
|
|
||||||
#if defined(__NR_getpeername)
|
|
||||||
case __NR_getpeername:
|
|
||||||
#endif
|
|
||||||
@@ -49,6 +50,16 @@ ResultExpr XrProcessPolicy::EvaluateSyscall(int system_call_number) const {
|
|
||||||
case __NR_get_robust_list:
|
|
||||||
#endif
|
|
||||||
return Allow();
|
|
||||||
+#if defined(__NR_getsockopt)
|
|
||||||
+ case __NR_getsockopt: {
|
|
||||||
+ // SteamVR's IPC client checks who is on the other end of its socket
|
|
||||||
+ // with SO_PEERCRED. Nothing else is readable.
|
|
||||||
+ const Arg<int> level(1);
|
|
||||||
+ const Arg<int> optname(2);
|
|
||||||
+ return If(AllOf(level == SOL_SOCKET, optname == SO_PEERCRED), Allow())
|
|
||||||
+ .Else(Error(EPERM));
|
|
||||||
+ }
|
|
||||||
+#endif
|
|
||||||
#if defined(__NR_kill)
|
|
||||||
case __NR_kill: {
|
|
||||||
// SteamVR probes its sibling processes for liveness with kill(pid, 0).
|
|
||||||
P
|
|
||||||
if ! printf '%s\n' "$PEERCRED_PATCH" | git apply --reverse --check 2>/dev/null; then
|
|
||||||
printf '%s\n' "$PEERCRED_PATCH" | git apply
|
|
||||||
stage "applied SO_PEERCRED patch"
|
|
||||||
fi
|
|
||||||
mkdir -p out/XR
|
|
||||||
cat > out/XR/args.gn <<'A'
|
|
||||||
target_os = "linux"
|
|
||||||
target_cpu = "arm64"
|
|
||||||
is_debug = false
|
|
||||||
is_official_build = false
|
|
||||||
is_component_build = false
|
|
||||||
dcheck_always_on = false
|
|
||||||
symbol_level = 0
|
|
||||||
blink_symbol_level = 0
|
|
||||||
v8_symbol_level = 0
|
|
||||||
proprietary_codecs = true
|
|
||||||
ffmpeg_branding = "Chrome"
|
|
||||||
use_remoteexec = false
|
|
||||||
use_siso = true
|
|
||||||
treat_warnings_as_errors = false
|
|
||||||
A
|
|
||||||
stage "gn gen"
|
|
||||||
gn gen out/XR
|
|
||||||
gn args out/XR --list=enable_openxr --short | tee -a "$W/stage"
|
|
||||||
stage "build"
|
|
||||||
( while sleep 600; do guard || { pkill -u "$(id -u)" -f "siso|ninja"; exit 3; }; done ) &
|
|
||||||
GUARD=$!
|
|
||||||
trap 'kill $GUARD 2>/dev/null || true' EXIT
|
|
||||||
autoninja -C out/XR chrome chrome_sandbox chrome_crashpad_handler
|
|
||||||
stage "package"
|
|
||||||
cd out/XR
|
|
||||||
files=(chrome chrome_sandbox chrome_crashpad_handler *.pak *.bin icudtl.dat locales)
|
|
||||||
# GPU libraries aren't produced by every config; pack the ones that exist.
|
|
||||||
for f in libEGL.so libGLESv2.so libvk_swiftshader.so libvulkan.so.1 vk_swiftshader_icd.json; do
|
|
||||||
[ -e "$f" ] && files+=("$f")
|
|
||||||
done
|
|
||||||
tar -cJf "$W/chromium-xr-arm64.tar.xz" "${files[@]}"
|
|
||||||
stage "DONE $(ls -la $W/chromium-xr-arm64.tar.xz)"
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
#!/usr/bin/env zsh
|
|
||||||
# Mac-side: install and launch the WebXR-enabled Chromium build on the Frame.
|
|
||||||
#
|
|
||||||
# Flathub Chromium can't enter immersive WebXR on Linux: upstream only wires
|
|
||||||
# the OpenXR device on Windows (see docs/webxr-chromium.md). This deploys an
|
|
||||||
# arm64 build with the Linux OpenXR CLs, made on a Linux host by
|
|
||||||
# scripts/build-chromium-xr.sh, into ~/chromium-xr on the Frame (not a
|
|
||||||
# Flatpak, so SteamVR's sockets and the XR sandbox work unmodified).
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# scripts/chromium-xr.sh install [TARBALL] # default: scp from $BUILD_HOST
|
|
||||||
# scripts/chromium-xr.sh launch [URL] # opens as its own panel in the headset
|
|
||||||
# scripts/chromium-xr.sh steam # adds "Chromium XR" to the Steam library
|
|
||||||
# scripts/chromium-xr.sh check # isSessionSupported via DevTools
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
FRAME_ALIAS=${FRAME_ALIAS:-frame}
|
|
||||||
BUILD_HOST=${BUILD_HOST:-}
|
|
||||||
BUILD_TARBALL=${BUILD_TARBALL:-chromium-xr/chromium-xr-arm64.tar.xz}
|
|
||||||
DEVTOOLS_PORT=${DEVTOOLS_PORT:-9223}
|
|
||||||
STEAM_NAME=${STEAM_NAME:-Chromium XR}
|
|
||||||
here=${0:A:h}
|
|
||||||
wrapper='~/Applications/ChromiumXR/launch.sh'
|
|
||||||
|
|
||||||
# frame/chromium-xr/launch.sh holds Chrome's flags; both launch paths run it.
|
|
||||||
push_wrapper() {
|
|
||||||
# Write then rename, so a dropped connection can't leave a torn script.
|
|
||||||
ssh "$FRAME_ALIAS" 'mkdir -p ~/Applications/ChromiumXR && cd ~/Applications/ChromiumXR && cat > launch.sh.new && chmod +x launch.sh.new && mv launch.sh.new launch.sh' \
|
|
||||||
< "$here/../frame/chromium-xr/launch.sh"
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
install)
|
|
||||||
tarball=${2:-}
|
|
||||||
if [[ -z "$tarball" ]]; then
|
|
||||||
[[ -n "$BUILD_HOST" ]] || { print -u2 "Pass a tarball, or set BUILD_HOST to the build machine"; exit 2; }
|
|
||||||
tmp=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$tmp"' EXIT
|
|
||||||
tarball=$tmp/chromium-xr-arm64.tar.xz
|
|
||||||
scp -q "$BUILD_HOST:$BUILD_TARBALL" "$tarball"
|
|
||||||
fi
|
|
||||||
ssh "$FRAME_ALIAS" 'rm -rf ~/chromium-xr.new && mkdir -p ~/chromium-xr.new'
|
|
||||||
ssh "$FRAME_ALIAS" 'tar -xJf - -C ~/chromium-xr.new' < "$tarball"
|
|
||||||
# Check the new build runs before replacing the old one.
|
|
||||||
ssh "$FRAME_ALIAS" '~/chromium-xr.new/chrome --version && rm -rf ~/chromium-xr && mv ~/chromium-xr.new ~/chromium-xr'
|
|
||||||
;;
|
|
||||||
launch)
|
|
||||||
# Its own VR panel on gamescope's X display, so the Plasma desktop doesn't
|
|
||||||
# need to be open. DevTools is only on for this path (for `check`).
|
|
||||||
push_wrapper
|
|
||||||
exec "$here/panel-on-frame.sh" --name chromium-xr -- "$wrapper" \
|
|
||||||
--remote-debugging-port="$DEVTOOLS_PORT" \
|
|
||||||
"${2:-https://immersive-web.github.io/webxr-samples/}"
|
|
||||||
;;
|
|
||||||
steam)
|
|
||||||
# A non-Steam shortcut, added through the Steam client's DevTools port
|
|
||||||
# without restarting Steam (see docs/apks.md). Launching it from the
|
|
||||||
# library gives Chromium its own panel like any game. Safe to rerun: it
|
|
||||||
# refreshes the wrapper and only adds the shortcut if it's missing.
|
|
||||||
ssh "$FRAME_ALIAS" 'test -x ~/chromium-xr/chrome' ||
|
|
||||||
{ print -u2 "No build in ~/chromium-xr on the Frame: run 'chromium-xr.sh install' first"; exit 1; }
|
|
||||||
push_wrapper
|
|
||||||
# The app id is kept next to the wrapper, so renaming the shortcut in the
|
|
||||||
# library doesn't make a rerun add a second one.
|
|
||||||
shortcuts=$here/../frame/android/steam_shortcuts.py
|
|
||||||
home=$(ssh "$FRAME_ALIAS" 'printf %s "$HOME"')
|
|
||||||
saved=$(ssh "$FRAME_ALIAS" 'cat ~/Applications/ChromiumXR/shortcut-appid 2>/dev/null || true')
|
|
||||||
existing=$(ssh "$FRAME_ALIAS" python3 - list < "$shortcuts" |
|
|
||||||
python3 -c 'import json,sys
|
|
||||||
apps = json.load(sys.stdin)
|
|
||||||
ids = [a["appid"] for a in apps if str(a["appid"]) == sys.argv[2]] or [a["appid"] for a in apps if a["name"] == sys.argv[1]]
|
|
||||||
print(ids[0] if ids else "")' "$STEAM_NAME" "$saved")
|
|
||||||
if [[ -n "$existing" ]]; then
|
|
||||||
print -r -- "The shortcut is already in the Steam library (app id $existing)"
|
|
||||||
else
|
|
||||||
icon=''
|
|
||||||
for dir in /var/lib/flatpak '~/.local/share/flatpak'; do
|
|
||||||
candidate=$dir/exports/share/icons/hicolor/256x256/apps/org.chromium.Chromium.png
|
|
||||||
if ssh "$FRAME_ALIAS" "test -f $candidate"; then icon=${candidate/#\~/$home}; break; fi
|
|
||||||
done
|
|
||||||
existing=$(ssh "$FRAME_ALIAS" python3 - add ${(q)STEAM_NAME} ${(q)home}/Applications/ChromiumXR/launch.sh ${(q)home} ${(q)icon} < "$shortcuts")
|
|
||||||
[[ "$existing" == <-> ]] || { print -u2 -r -- "Steam didn't return a shortcut app id: $existing"; exit 1; }
|
|
||||||
print -r -- "Added $STEAM_NAME to the Steam library (shortcut app id $existing)"
|
|
||||||
fi
|
|
||||||
ssh "$FRAME_ALIAS" "printf '%s\n' $existing > ~/Applications/ChromiumXR/shortcut-appid"
|
|
||||||
;;
|
|
||||||
check)
|
|
||||||
# DevTools listens on the Frame's loopback only; evaluate there.
|
|
||||||
ssh "$FRAME_ALIAS" python3 - "$DEVTOOLS_PORT" <<'EOF'
|
|
||||||
import json, sys, urllib.request, base64, os, socket, struct
|
|
||||||
port = int(sys.argv[1])
|
|
||||||
tabs = json.load(urllib.request.urlopen(f"http://127.0.0.1:{port}/json", timeout=10))
|
|
||||||
page = next((t for t in tabs if t["type"] == "page"), None)
|
|
||||||
if page is None:
|
|
||||||
sys.exit("no open page: run 'chromium-xr.sh launch' first")
|
|
||||||
path = page["webSocketDebuggerUrl"].split(f":{port}", 1)[1]
|
|
||||||
s = socket.create_connection(("127.0.0.1", port), timeout=30)
|
|
||||||
key = base64.b64encode(os.urandom(16)).decode()
|
|
||||||
s.sendall(f"GET {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nUpgrade: websocket\r\n"
|
|
||||||
f"Connection: Upgrade\r\nSec-WebSocket-Key: {key}\r\n"
|
|
||||||
"Sec-WebSocket-Version: 13\r\n\r\n".encode())
|
|
||||||
s.recv(4096)
|
|
||||||
msg = json.dumps({"id": 1, "method": "Runtime.evaluate", "params": {
|
|
||||||
"expression": "navigator.xr ? navigator.xr.isSessionSupported('immersive-vr') : 'no navigator.xr'",
|
|
||||||
"awaitPromise": True}}).encode()
|
|
||||||
mask = os.urandom(4)
|
|
||||||
hdr = bytes([0x81]) + (bytes([0x80 | len(msg)]) if len(msg) < 126
|
|
||||||
else bytes([0x80 | 126]) + struct.pack(">H", len(msg)))
|
|
||||||
s.sendall(hdr + mask + bytes(b ^ mask[i % 4] for i, b in enumerate(msg)))
|
|
||||||
buf = b""
|
|
||||||
reply = None
|
|
||||||
while reply is None:
|
|
||||||
chunk = s.recv(65536)
|
|
||||||
if not chunk:
|
|
||||||
sys.exit("DevTools closed the connection")
|
|
||||||
buf += chunk
|
|
||||||
# Consume every complete frame already buffered before reading again.
|
|
||||||
while len(buf) >= 2:
|
|
||||||
n = buf[1] & 0x7F
|
|
||||||
off = 2
|
|
||||||
if n == 126:
|
|
||||||
if len(buf) < 4:
|
|
||||||
break
|
|
||||||
n, off = struct.unpack(">H", buf[2:4])[0], 4
|
|
||||||
elif n == 127:
|
|
||||||
if len(buf) < 10:
|
|
||||||
break
|
|
||||||
n, off = struct.unpack(">Q", buf[2:10])[0], 10
|
|
||||||
if len(buf) < off + n:
|
|
||||||
break
|
|
||||||
frame, buf = buf[off:off + n], buf[off + n:]
|
|
||||||
msg = json.loads(frame)
|
|
||||||
if msg.get("id") == 1:
|
|
||||||
reply = msg
|
|
||||||
break
|
|
||||||
print("immersive-vr supported:", reply["result"]["result"].get("value"))
|
|
||||||
EOF
|
|
||||||
;;
|
|
||||||
*) sed -n '2,14p' "$0"; exit 2 ;;
|
|
||||||
esac
|
|
||||||
Reference in new issue
Block a user