From 51b4e7931899800ef8e6c5e4f5feee2c73061bed Mon Sep 17 00:00:00 2001 From: Nikita Koptelov Date: Wed, 30 Sep 2026 18:02:24 +0200 Subject: [PATCH] Start Frametop's SteamVR clients only once SteamVR is up (#6) The pointer and gaze services need steamvr.service to be running (Requisite=), and ft-pointer, ft-screens, and ft-gaze connect as a background app before switching to overlay, so they never start a vrserver of their own. One started from the dev container never finds the headset, which left a reboot stuck in a loop. Co-Authored-By: Claude Opus 5.5 --- docs/design.md | 2 ++ gaze/frametop-gaze.service | 1 + gaze/ft-gaze.cpp | 6 +++++- pointer/helper/frametop-pointer.service | 1 + pointer/helper/ft-pointer.cpp | 6 +++++- screens/vr.cpp | 6 +++++- 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/design.md b/docs/design.md index 51fee34..b21aec1 100644 --- a/docs/design.md +++ b/docs/design.md @@ -64,6 +64,8 @@ SteamVR's dashboard and every overlay it hosts are driven by the vrcompositor `l Driver poses are in SteamVR's raw tracking space, and client programs work in the standing universe, which on the Frame is about 1.6 m above raw. Mixing them up put the laser's origin 1.6 m above your head. The helper converts using the headset's pose in both spaces every frame. +Frametop's SteamVR clients (ft-pointer, ft-screens, ft-gaze) connect as a background app first and switch to an overlay app only once that works. `VR_Init` as an overlay app starts vrserver itself when none is running, and one started that way from the dev container never finds the headset. At a boot where the gamescope session timed out, systemd dropped `steamvr.service`'s start job, the pointer service (ordered only `After=` it) started anyway, and its vrserver made every SteamVR launch fail with `HmdNotFound`. SteamOS's health check then kept resetting the Steam client and tried to fall back to the previous OS slot. The units also say `Requisite=steamvr.service`, so they don't start at all when SteamVR's start fails. + The driver starts disconnected, because holding the right-hand role while SteamVR starts leaves the Steam UI stuck on its loading icon. It connects when the mouse is used and claims the right hand. SteamVR keeps a hand role reserved for a disconnected device that still asks for it, so the driver switches its role hint between right hand (connected) and opt-out (not connected). ### The cursor diff --git a/gaze/frametop-gaze.service b/gaze/frametop-gaze.service index a19dc24..9b0c096 100644 --- a/gaze/frametop-gaze.service +++ b/gaze/frametop-gaze.service @@ -5,6 +5,7 @@ Documentation=file://@REPO@/gaze/README.md # Needs SteamVR's IPC (ft-gaze is an overlay client); it starts and stops with SteamVR. After=steamvr.service frametop-pointer.service PartOf=steamvr.service +Requisite=steamvr.service [Service] # Host Python; it runs ft-gaze in the dev container (distrobox enter), which quits when diff --git a/gaze/ft-gaze.cpp b/gaze/ft-gaze.cpp index e215fd2..894ad46 100644 --- a/gaze/ft-gaze.cpp +++ b/gaze/ft-gaze.cpp @@ -351,7 +351,11 @@ int main(int argc, char **argv) { stdinClosed = true; }).detach(); vr::EVRInitError err = vr::VRInitError_None; - vr::VR_Init(&err, vr::VRApplication_Overlay); + vr::VR_Init(&err, vr::VRApplication_Background); + if (err == vr::VRInitError_None) { + vr::VR_Shutdown(); + vr::VR_Init(&err, vr::VRApplication_Overlay); + } if (err != vr::VRInitError_None) { std::fprintf(stderr, "ft-gaze: SteamVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err)); return 1; diff --git a/pointer/helper/frametop-pointer.service b/pointer/helper/frametop-pointer.service index 3f180ea..5ffbbed 100644 --- a/pointer/helper/frametop-pointer.service +++ b/pointer/helper/frametop-pointer.service @@ -5,6 +5,7 @@ Documentation=file://@REPO@/README.md # Needs SteamVR's IPC; it starts and stops with SteamVR. After=steamvr.service frametop-input-relay.service PartOf=steamvr.service +Requisite=steamvr.service [Service] # Runs in the dev container (built there against its libraries). The helper process diff --git a/pointer/helper/ft-pointer.cpp b/pointer/helper/ft-pointer.cpp index 765eb68..bf16618 100644 --- a/pointer/helper/ft-pointer.cpp +++ b/pointer/helper/ft-pointer.cpp @@ -461,7 +461,11 @@ int main() { vr::EVRInitError err = vr::VRInitError_None; while (true) { - vr::VR_Init(&err, vr::VRApplication_Overlay); + vr::VR_Init(&err, vr::VRApplication_Background); + if (err == vr::VRInitError_None) { + vr::VR_Shutdown(); + vr::VR_Init(&err, vr::VRApplication_Overlay); + } if (err == vr::VRInitError_None) break; std::fprintf(stderr, "waiting for SteamVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err)); std::this_thread::sleep_for(std::chrono::seconds(2)); diff --git a/screens/vr.cpp b/screens/vr.cpp index 9fbb63c..0c4209c 100644 --- a/screens/vr.cpp +++ b/screens/vr.cpp @@ -1034,7 +1034,11 @@ extern "C" { bool ft_vr_init(void) { vr::EVRInitError err = vr::VRInitError_None; - vr::VR_Init(&err, vr::VRApplication_Overlay); + vr::VR_Init(&err, vr::VRApplication_Background); + if (err == vr::VRInitError_None) { + vr::VR_Shutdown(); + vr::VR_Init(&err, vr::VRApplication_Overlay); + } if (err != vr::VRInitError_None) { std::fprintf(stderr, "openvr: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err)); return false;