diff --git a/display-settings/ft-display-settings b/display-settings/ft-display-settings index 038a16c..a0c3bd2 100755 --- a/display-settings/ft-display-settings +++ b/display-settings/ft-display-settings @@ -10,6 +10,7 @@ case $wl in /*) ;; *) wl="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/$wl" ;; esac session_bus=${DBUS_SESSION_BUS_ADDRESS:-} export XDG_RUNTIME_DIR=/run/user/$(id -u) export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus +"$here/../scripts/container-up.sh" exec "$HOME/.local/bin/distrobox" enter dev -- env WAYLAND_DISPLAY="$wl" DISPLAY="${DISPLAY:-}" \ XAUTHORITY="${XAUTHORITY:-}" DBUS_SESSION_BUS_ADDRESS="$session_bus" \ QT_QPA_PLATFORM="wayland;xcb" \ diff --git a/docs/design.md b/docs/design.md index 1d2f5d1..0a7dcd6 100644 --- a/docs/design.md +++ b/docs/design.md @@ -106,6 +106,8 @@ Keyboard: still open. SteamVR opens keyboards (vrserver held the Z3 Keyboard), b ## Findings log +- 2026-09-26: The headset's displays didn't sleep when it came off (SteamVR reports the HMD idle the moment it's removed, and turns the screens off 5 s later, `power.turnOffScreensTimeout`). A removal test that stopped one piece every 20 s found the pointer helper: standby 5 s after it stopped, nothing from hiding the screens or stopping the relay. Two causes in the helper: an awake pointer (connected controller, laser mode forced on) until 30 s without mouse input, and, even with the pointer released, its background `vrcmd --overlays` every second, a new SteamVR client connection each time (the per-second "External connection from /run/host/opt/steamvr/.../vrcmd" lines in vrcompositor.txt; `/run/host` is the container's view of the host). Now the helper releases the pointer when `GetTrackedDeviceActivityLevel(HMD)` is Idle, Standby, or Idle_Timeout, won't wake until it's worn again, and pauses the overlay list while the pointer is off. Separately: a podman container's monitor (conmon) stays in the cgroup of whatever started it, and `distrobox enter` from a systemd service starts the container on demand, so stopping frametop-pointer (which had started it at boot) killed the container, and with it the desktop's compositor. `scripts/container-up.sh` starts the container in a scope of its own before every `distrobox enter` (services, session, apps, build scripts). + - 2026-09-26: Screens over VR games. Visible screens with VROverlayFlags_MakeOverlaysInteractiveIfVisible keep SteamVR's laser mouse on, which takes the controllers from a VR game. `IVRApplications::GetCurrentSceneProcessId()` is 0 with no game (the Frame's home isn't a scene app) and the game's pid while one runs, so ft-screens clears the flag while it's nonzero (checked twice a second; the `controllers` setting, default outside_games). Tested in a VR game: the screens stay up over it, the controllers stay in the game, and the screens take the lasers again while the SteamVR dashboard is open. - 2026-09-26: Programs that stop their own helpers when their window closes still lose them on a desktop restart, even after keep-apps.sh moved them out of the unit (22 processes moved, all gone 7 s later when the compositor went): nothing outside the app can keep them. Run such work somewhere independent of the desktop, like a systemd user service. Controls: shown only while some controller ray passes within max(1.5 x button, 12% of the bar) of a control (5 points along the bar, each button, the tab), 0.4 s linger; a zone covering the lower quarter of the screen showed them whenever a screen was in use. diff --git a/input-settings/ft-input-settings b/input-settings/ft-input-settings index 1673507..b64b936 100755 --- a/input-settings/ft-input-settings +++ b/input-settings/ft-input-settings @@ -10,6 +10,7 @@ case $wl in /*) ;; *) wl="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/$wl" ;; esac session_bus=${DBUS_SESSION_BUS_ADDRESS:-} export XDG_RUNTIME_DIR=/run/user/$(id -u) export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus +"$here/../scripts/container-up.sh" exec "$HOME/.local/bin/distrobox" enter dev -- env WAYLAND_DISPLAY="$wl" DISPLAY="${DISPLAY:-}" \ XAUTHORITY="${XAUTHORITY:-}" DBUS_SESSION_BUS_ADDRESS="$session_bus" \ QT_QPA_PLATFORM="wayland;xcb" \ diff --git a/pointer/helper/frametop-pointer.service b/pointer/helper/frametop-pointer.service index 3fd42a7..3f180ea 100644 --- a/pointer/helper/frametop-pointer.service +++ b/pointer/helper/frametop-pointer.service @@ -9,6 +9,9 @@ PartOf=steamvr.service [Service] # Runs in the dev container (built there against its libraries). The helper process # lives in the container, so clean it up explicitly around distrobox enter. +# Start the container in a scope of its own first: started by this service (distrobox enter +# does that on demand), stopping the service would stop the container and all in it. +ExecStartPre=-@REPO@/scripts/container-up.sh ExecStartPre=-/usr/bin/pkill -x ft-pointer ExecStart=%h/.local/bin/distrobox enter dev -- @REPO@/pointer/helper/build/ft-pointer ExecStopPost=-/usr/bin/pkill -x ft-pointer diff --git a/pointer/helper/ft-pointer.cpp b/pointer/helper/ft-pointer.cpp index cd798de..81dd80a 100644 --- a/pointer/helper/ft-pointer.cpp +++ b/pointer/helper/ft-pointer.cpp @@ -32,7 +32,9 @@ // It's restored when a controller takes the pointer back. // // Headset off: when SteamVR says nobody is wearing the headset, the pointer is released and -// stays off until it's worn again, so the displays can sleep (see the main loop). +// stays off until it's worn again, so the displays can sleep (see the main loop). While the +// pointer is off the helper also stops listing overlays with vrcmd, whose connection every +// second kept SteamVR from going to standby. // // Last used wins: when a real controller moves (picked up), the pointer is released // at once (driver "hide", which also drops its hand role hint), so the controller gets @@ -194,16 +196,24 @@ void SendTo(int fd, const char *name, const std::string &msg) { // public call to enumerate other apps' overlays). Hidden ones are listed too: the // window controls under a floating panel only appear while something hovers the // panel, and the cursor has to find them the moment they do, not a second later. +// Paused while the pointer is off: each vrcmd run connects to SteamVR as a new app, and a new +// app every second kept SteamVR (and the headset's displays) from going to standby. class OverlayList { public: void Start() { thread_ = std::thread([this] { while (running_) { - Refresh(); - std::this_thread::sleep_for(std::chrono::seconds(1)); + if (!paused_) Refresh(); + // Wait a second, or less when the pointer wakes (refresh right away then). + for (int i = 0; i < 10 && running_; ++i) { + const bool wasPaused = paused_; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (wasPaused && !paused_) break; + } } }); } + void SetPaused(bool paused) { paused_ = paused; } void Stop() { running_ = false; if (thread_.joinable()) thread_.join(); @@ -239,6 +249,7 @@ private: } std::thread thread_; + std::atomic paused_{false}; std::atomic running_{true}; std::mutex lock_; std::vector keys_; @@ -686,6 +697,9 @@ int main() { claimHeld = false; } + // The overlay list is only needed while the pointer is awake (see OverlayList). + overlays.SetPaused(!active || headsetOff); + // Laser mode on while the pointer is awake. if (active != laserModeShown) { laserModeShown = active; diff --git a/scripts/container-up.sh b/scripts/container-up.sh new file mode 100755 index 0000000..8c3ca27 --- /dev/null +++ b/scripts/container-up.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Runs on the Frame host. Starts the dev container, if it isn't running, in a systemd scope +# of its own. Whoever starts a podman container owns its monitor (conmon): started from one +# of our services (distrobox enter starts it on demand), the container, and everything in it +# like the desktop's compositor, would be killed when that service stops. Call this before +# `distrobox enter`. +box=${FRAME_BOX:-dev} +export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)} +[ "$(podman container inspect -f '{{.State.Running}}' "$box" 2>/dev/null)" = true ] && exit 0 +podman container exists "$box" 2>/dev/null || exit 0 # not created yet: setup/dev-container.sh does that +exec systemd-run --user --scope --quiet --collect --description="$box container (started for Frametop)" \ + podman start "$box" >/dev/null diff --git a/scripts/frame.sh b/scripts/frame.sh index c177499..fb05982 100755 --- a/scripts/frame.sh +++ b/scripts/frame.sh @@ -30,6 +30,7 @@ if [ "$FRAME_LOCAL" = 1 ]; then if [ "$on_host" = 1 ]; then exec bash -c "$*" fi + "$REPO_ROOT/scripts/container-up.sh" # in a scope of its own, not this shell's session exec "$HOME/.local/bin/distrobox" enter "$FRAME_BOX" -- bash -lc "$*" fi @@ -39,5 +40,5 @@ if [ "$on_host" = 1 ]; then exec ssh "${tty[@]}" "$FRAME_HOST" "cd $(printf %q "$dir") && $*" else exec ssh "${tty[@]}" "$FRAME_HOST" \ - "cd $(printf %q "$dir") && ~/.local/bin/distrobox enter $(printf %q "$FRAME_BOX") -- bash -lc $(printf %q "$*")" + "$(printf %q "$FRAME_REPO")/scripts/container-up.sh; cd $(printf %q "$dir") && ~/.local/bin/distrobox enter $(printf %q "$FRAME_BOX") -- bash -lc $(printf %q "$*")" fi diff --git a/session/frametop-session.sh b/session/frametop-session.sh index 0b7b38c..16bd50f 100755 --- a/session/frametop-session.sh +++ b/session/frametop-session.sh @@ -69,6 +69,7 @@ if [ "${1:-}" != --inner ]; then socket=ft-screens-0 read -ra screen_args <<< "$("$here/../layout/ft-layout" screen-args)" export FT_SCREEN_COUNT=$(( ${#screen_args[@]} / 2 )) + "$here/../scripts/container-up.sh" # not owned by this desktop, or stopping it would stop the container "$HOME/.local/bin/distrobox" enter dev -- "$here/../screens/build/ft-screens" --socket "$socket" \ "${screen_args[@]}" > /tmp/frametop-screens.log 2>&1 < /dev/null & stop_screens() { pkill -x ft-screens 2>/dev/null || true; } diff --git a/setup/dev-container.sh b/setup/dev-container.sh index 8aada26..7ef88ce 100755 --- a/setup/dev-container.sh +++ b/setup/dev-container.sh @@ -34,6 +34,7 @@ if ! podman container exists dev; then echo "creating the dev container (Fedora 44 toolbox)" "$distrobox" create --yes --name dev --image registry.fedoraproject.org/fedora-toolbox:44 fi +"$root/scripts/container-up.sh" "$distrobox" enter dev -- bash -c ' set -euo pipefail echo "installing ${#@} packages (already-installed ones are skipped)"