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 <noreply@anthropic.com>
(cherry picked from commit 51b4e79318)
This commit is contained in:
Nikita Koptelov authored and DeeJanuz committed 2026-09-30 10:02:46 -06:00
1 parent 369736f0ca
commit 4e5131d8ce
6 files changed
+16

No files matched your search

+2
View File
@@ -70,6 +70,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. 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 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 ### The cursor
+1
View File
@@ -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. # Needs SteamVR's IPC (ft-gaze is an overlay client); it starts and stops with SteamVR.
After=steamvr.service frametop-pointer.service After=steamvr.service frametop-pointer.service
PartOf=steamvr.service PartOf=steamvr.service
Requisite=steamvr.service
[Service] [Service]
# Host Python; it runs ft-gaze in the dev container (distrobox enter), which quits when # Host Python; it runs ft-gaze in the dev container (distrobox enter), which quits when
+4
View File
@@ -372,7 +372,11 @@ int main(int argc, char **argv) {
stdinClosed = true; stdinClosed = true;
}).detach(); }).detach();
vr::EVRInitError err = vr::VRInitError_None; vr::EVRInitError err = vr::VRInitError_None;
vr::VR_Init(&err, vr::VRApplication_Background);
if (err == vr::VRInitError_None) {
vr::VR_Shutdown();
vr::VR_Init(&err, vr::VRApplication_Overlay); vr::VR_Init(&err, vr::VRApplication_Overlay);
}
if (err != vr::VRInitError_None) { if (err != vr::VRInitError_None) {
std::fprintf(stderr, "ft-gaze: SteamVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err)); std::fprintf(stderr, "ft-gaze: SteamVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err));
return 1; return 1;
+1
View File
@@ -5,6 +5,7 @@ Documentation=file://@REPO@/README.md
# Needs SteamVR's IPC; it starts and stops with SteamVR. # Needs SteamVR's IPC; it starts and stops with SteamVR.
After=steamvr.service frametop-input-relay.service After=steamvr.service frametop-input-relay.service
PartOf=steamvr.service PartOf=steamvr.service
Requisite=steamvr.service
[Service] [Service]
# Runs in the dev container (built there against its libraries). The helper process # Runs in the dev container (built there against its libraries). The helper process
+4
View File
@@ -546,7 +546,11 @@ int main() {
vr::EVRInitError err = vr::VRInitError_None; vr::EVRInitError err = vr::VRInitError_None;
while (true) { while (true) {
vr::VR_Init(&err, vr::VRApplication_Background);
if (err == vr::VRInitError_None) {
vr::VR_Shutdown();
vr::VR_Init(&err, vr::VRApplication_Overlay); vr::VR_Init(&err, vr::VRApplication_Overlay);
}
if (err == vr::VRInitError_None) break; if (err == vr::VRInitError_None) break;
std::fprintf(stderr, "waiting for SteamVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err)); std::fprintf(stderr, "waiting for SteamVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err));
std::this_thread::sleep_for(std::chrono::seconds(2)); std::this_thread::sleep_for(std::chrono::seconds(2));
+4
View File
@@ -1148,7 +1148,11 @@ extern "C" {
bool ft_vr_init(void) { bool ft_vr_init(void) {
vr::EVRInitError err = vr::VRInitError_None; vr::EVRInitError err = vr::VRInitError_None;
vr::VR_Init(&err, vr::VRApplication_Background);
if (err == vr::VRInitError_None) {
vr::VR_Shutdown();
vr::VR_Init(&err, vr::VRApplication_Overlay); vr::VR_Init(&err, vr::VRApplication_Overlay);
}
if (err != vr::VRInitError_None) { if (err != vr::VRInitError_None) {
std::fprintf(stderr, "openvr: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err)); std::fprintf(stderr, "openvr: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(err));
return false; return false;