diff --git a/docs/design.md b/docs/design.md index c57533e..b61f77b 100644 --- a/docs/design.md +++ b/docs/design.md @@ -169,7 +169,7 @@ The Frame controllers can be mapped like mouse buttons, but they aren't input de ## The desktop session -The session is modeled on SteamOS's `steamos-nested-desktop` and runs beside it. It has its own runtime directory, config (`~/.config/frametop`), and state, so it never disturbs the stock desktop's layout or panels. It runs on a private D-Bus from `dbus-run-session`, which has two consequences. KDE only launches apps in systemd scopes when systemd is on the session bus, so everything started in the desktop lands in its systemd unit, and stopping the unit would kill all of it; `session/keep-apps.sh` moves those programs out first. And tools that need the real user bus, like podman and `distrobox-host-exec`, have to be pointed at it explicitly. +The session is modeled on SteamOS's `steamos-nested-desktop` and runs beside it. It has its own runtime directory, config (`~/.config/frametop`), and state, so it never disturbs the stock desktop's layout or panels. It runs on a private D-Bus from `dbus-run-session`, which has two consequences. KDE only launches apps in systemd scopes when systemd is on the session bus, so everything started in the desktop lands in its systemd unit, and stopping the unit would kill all of it; `session/keep-apps.sh` moves those programs out first. And tools that need the real user bus, like podman and `distrobox-host-exec`, have to be pointed at it explicitly. Its own config folder also hides SteamVR's path registry (`~/.config/openvr/openvrpaths.vrpath`) from everything started in it: OpenVR programs there fail with `VRInitError_Init_PathRegistryNotFound`, and `vrpathreg adddriver` writes a new registry under `~/.config/frametop/openvr` that has no SteamVR in it and that SteamVR never reads. So Frametop's scripts run SteamVR's tools with `XDG_CONFIG_HOME=~/.config`. The VR launcher starts the session from the Steam client, and the client's environment came along: `LD_LIBRARY_PATH` pointing at Steam's own runtime, whose `libavcodec` has no H.264 decoder, so VLC in the desktop couldn't play most videos, plus the client's overlay and launch settings. The session script drops the client's variables before it starts anything. SteamOS's global Mesa settings (`/usr/share/deckard/mesavars.sh`) stay, and the gamescope session's Vulkan layer (`ENABLE_GAMESCOPE_WSI`) is only kept for the gamescope backend. diff --git a/install.sh b/install.sh index eddab5b..9c0775e 100755 --- a/install.sh +++ b/install.sh @@ -75,7 +75,7 @@ step "3/10 input relay (keeps Bluetooth mice working in SteamVR, device roles, b step "4/10 3D mouse: SteamVR driver" "$root/pointer/driver/build.sh" -"$root/pointer/driver/install.sh" install 2>&1 | grep -v xdg-open +"$root/pointer/driver/install.sh" install step "5/10 3D mouse: pointer helper service" "$root/pointer/helper/build.sh" diff --git a/layout/ft_layout.py b/layout/ft_layout.py index 774817e..60284f3 100755 --- a/layout/ft_layout.py +++ b/layout/ft_layout.py @@ -534,7 +534,8 @@ def capture_screens(): # ---------------------------------------------------------------- gamescope (dashboard panels) def vrcmd(*args, timeout=10): - env = dict(os.environ, LD_LIBRARY_PATH=os.path.dirname(VRCMD)) + # SteamVR's config folder: in the desktop, XDG_CONFIG_HOME is its own (docs/design.md). + env = dict(os.environ, LD_LIBRARY_PATH=os.path.dirname(VRCMD), XDG_CONFIG_HOME=os.path.expanduser("~/.config")) try: return subprocess.run([VRCMD, *args], capture_output=True, text=True, timeout=timeout, env=env).stdout except (OSError, subprocess.TimeoutExpired): diff --git a/pointer/driver/install.sh b/pointer/driver/install.sh index b781fd8..bbf4bc7 100755 --- a/pointer/driver/install.sh +++ b/pointer/driver/install.sh @@ -14,6 +14,10 @@ frame="$root/scripts/frame.sh" src=$FRAME_REPO/pointer/driver dest='$HOME/.local/share/frametop/ft_pointer' # expanded on the Frame, in the commands below reg='/opt/steamvr/bin/linuxarm64/vrpathreg' +# SteamVR's tools with SteamVR's config folder and libraries. A terminal in the Frametop desktop +# has XDG_CONFIG_HOME=~/.config/frametop (session/frametop-session.sh), where vrpathreg finds no +# registry, so it made one there that SteamVR never reads. +steamvr='env XDG_CONFIG_HOME=$HOME/.config LD_LIBRARY_PATH=/opt/steamvr/bin/linuxarm64' # expanded on the Frame case ${1:-install} in install) @@ -21,6 +25,11 @@ case ${1:-install} in # Replacing the files of a driver SteamVR has loaded leaves its input bindings in a bad # state (the 3D mouse no longer gets the laser) until SteamVR restarts, so an unchanged # driver is left alone. + # A registry an earlier install made in the desktop's config folder (see steamvr above) has + # no SteamVR in it, and hid SteamVR from OpenVR programs started in the desktop + # (VRInitError_Init_InstallationNotFound): it goes. + # vrpathreg runs 'xdg-open vrmonitor://driverinstalled' for SteamVR's desktop monitor, which + # the Frame doesn't have, so the desktop said "could not read file": a stand-in takes it. "$frame" --host "set -e; test -f $src/build/driver_ft_pointer.so rm -rf $dest.new; mkdir -p $dest.new/bin/linuxarm64 cp -r $src/ft_pointer/. $dest.new/ @@ -31,15 +40,22 @@ else rm -rf $dest; mv $dest.new $dest echo 'installed; restart SteamVR to load it' fi -LD_LIBRARY_PATH=/opt/steamvr/bin/linuxarm64 $reg adddriver $dest -LD_LIBRARY_PATH=/opt/steamvr/bin/linuxarm64 $reg show | grep -A3 -i 'external'" ;; +stray=\$HOME/.config/frametop/openvr/openvrpaths.vrpath +if grep -qs '\"jsonid\" : \"vrpathreg\"' \$stray && grep -qsE '\"runtime\"[[:space:]]*:[[:space:]]*null' \$stray; then + rm -f \$stray; rmdir \$HOME/.config/frametop/openvr 2>/dev/null || true + echo 'removed a SteamVR path registry an earlier install left in ~/.config/frametop/openvr' +fi +noopen=\$(mktemp -d); trap 'rm -rf \$noopen' EXIT +printf '#!/bin/sh\nexit 0\n' > \$noopen/xdg-open; chmod +x \$noopen/xdg-open +PATH=\$noopen:\$PATH $steamvr $reg adddriver $dest +$steamvr $reg show | grep -A3 -i 'external'" ;; uninstall) - "$frame" --host "LD_LIBRARY_PATH=/opt/steamvr/bin/linuxarm64 $reg removedriver $dest; rm -rf $dest; echo 'removed; restart SteamVR to unload it'" ;; + "$frame" --host "$steamvr $reg removedriver $dest; rm -rf $dest; echo 'removed; restart SteamVR to unload it'" ;; send) "$frame" --host "python3 -c 'import socket,sys; s=socket.socket(socket.AF_UNIX,socket.SOCK_DGRAM); s.sendto(sys.argv[1].encode(), \"\\0ft_pointer\")' $(printf %q "${2:?command}")" ;; - probe) "$frame" -C pointer/probe 'LD_LIBRARY_PATH=/opt/steamvr/bin/linuxarm64 ./build/vrprobe' ;; + probe) "$frame" -C pointer/probe "$steamvr ./build/vrprobe" ;; aimhere) - read -r yaw pitch < <("$frame" -C pointer/probe 'LD_LIBRARY_PATH=/opt/steamvr/bin/linuxarm64 ./build/vrprobe' | sed -n 's/^head yaw = \([-0-9.]*\) pitch = \([-0-9.]*\)$/\1 \2/p') + read -r yaw pitch < <("$frame" -C pointer/probe "$steamvr ./build/vrprobe" | sed -n 's/^head yaw = \([-0-9.]*\) pitch = \([-0-9.]*\)$/\1 \2/p') [ -n "${yaw:-}" ] || { echo "head pose not valid (headset off?)" >&2; exit 1; } "$0" send "aim $yaw $pitch" && echo "aimed at yaw $yaw pitch $pitch" ;; log) "$frame" --host "grep -iE 'ft_pointer' ~/.local/share/Steam/logs/vrserver.txt | tail -n ${2:-20}" ;; diff --git a/scripts/report.sh b/scripts/report.sh index ea031e1..f59fb7c 100755 --- a/scripts/report.sh +++ b/scripts/report.sh @@ -26,7 +26,8 @@ for u in frametop-input-relay frametop-pointer frametop-power; do done echo "desktop: $(pgrep -x ft-screens >/dev/null && echo running || echo 'not running'), plasmashell: $(pgrep -c plasmashell || true)" echo "paused for VR games: $(cat /run/user/$(id -u)/frametop-pause.json 2>/dev/null || echo 'no (never paused since boot)')" -LD_LIBRARY_PATH=/opt/steamvr/bin/linuxarm64 /opt/steamvr/bin/linuxarm64/vrpathreg show 2>/dev/null | sed -n '/xternal/,$p' +# SteamVR's config folder: a terminal in the desktop has the session's own (docs/design.md). +XDG_CONFIG_HOME=$HOME/.config LD_LIBRARY_PATH=/opt/steamvr/bin/linuxarm64 /opt/steamvr/bin/linuxarm64/vrpathreg show 2>/dev/null | sed -n '/xternal/,$p' section "What Frametop needs from SteamOS (scripts/update-check.py)" python3 "$repo/scripts/update-check.py" 2>&1 || true diff --git a/scripts/update-check.py b/scripts/update-check.py index 0b320b8..3e540e1 100755 --- a/scripts/update-check.py +++ b/scripts/update-check.py @@ -35,6 +35,9 @@ KNOWN_GOOD = os.path.join(HOME, ".local/state/frametop/known-good.json") STEAMVR_BIN = "/opt/steamvr/bin/linuxarm64" LAUNCHER = os.path.join(HOME, ".local/share/applications/deckard-nested-desktop.desktop") VRPATHS = os.path.join(HOME, ".config/openvr/openvrpaths.vrpath") +# The Frametop desktop has its own XDG_CONFIG_HOME (session/frametop-session.sh). An install from a +# terminal there, before pointer/driver/install.sh set SteamVR's, left vrpathreg's registry here. +STRAY_VRPATHS = os.path.join(HOME, ".config/frametop/openvr/openvrpaths.vrpath") BACKLIGHT = "/sys/class/backlight/ae94000.dsi.0/brightness" # as in power/ft-powerd.cpp EYE_MMAP = "/dev/shm/eye-server.mmap" VRSERVER = "http://127.0.0.1:27062" # its web socket: input/vrws.py @@ -227,6 +230,14 @@ def check_host(): else: report("FAIL", "pointer driver", "not registered with SteamVR; run pointer/driver/install.sh install, " "then restart SteamVR") + try: + with open(STRAY_VRPATHS) as f: + stray = json.load(f).get("runtime") is None + except (OSError, ValueError, AttributeError): + stray = False + if stray: + report("warn", "OpenVR path registry", f"{STRAY_VRPATHS} has no SteamVR in it, and hides SteamVR from " + "OpenVR programs started in the Frametop desktop; pointer/driver/install.sh install removes it") session = launcher_session() if session is None: @@ -474,6 +485,8 @@ def main(): # A terminal in the desktop has its session's runtime dir and bus; systemctl needs the real ones. os.environ["XDG_RUNTIME_DIR"] = f"/run/user/{UID}" os.environ["DBUS_SESSION_BUS_ADDRESS"] = f"unix:path=/run/user/{UID}/bus" + # And its own config folder, where SteamVR's path registry isn't: OpenVR and vrcmd need ours. + os.environ["XDG_CONFIG_HOME"] = os.path.join(HOME, ".config") versions = current_versions() check_versions(versions)