Gaze mode is a mouse feature: drop its controller parts

Steam reads the Frame controllers itself, outside SteamVR's bindings, and every
press and release it sees takes SteamVR out of laser mode, so controller clicks
at the gaze can't be done cleanly (docs/gaze-controllers.md, from the gaze-first
branch's tests).

- Gaze precision and Gaze drag take a mouse button or a key combination only;
  the controller source, its aim steering, and POINTER_PRECISION_GAIN,
  POINTER_PRECISION_DEADZONE and POINTER_GAZE_DRAG_GAIN are gone.
- The gaze actions (including Gaze pointer on/off) can't be mapped to controller
  buttons: the relay ignores them there and doesn't ask the helper for those
  buttons, and Input Settings no longer offers them.
- Last used wins in gaze mode too: picking up a controller hands it the laser,
  as docs/design.md already said.
- The gaze dot shows all the time (POINTER_GAZE_DOT=always, the default;
  moving brings back the old behaviour), with a switch on the Gaze page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DeeJanuzandClaude Opus 5.5 committed 2026-09-30 21:11:24 -06:00
1 parent d5c2ec65d7
commit e16b7588e2
9 files changed
+114 -106

No files matched your search

+16 -7
View File
@@ -6,9 +6,9 @@ to the input relay over its control socket (@frametop_relay):
- Devices: every USB/Bluetooth mouse and keyboard, a live activity light to
identify them, and a role for each (3D pointer, pass through, ignore).
- Buttons: press a button or key on a pointer device, then pick an action.
- Controllers: the same for the Frame controllers' buttons. They're read by the pointer
helper through SteamVR input (@ft_pointer_helper: vrstatus, vrglobal), and a mapped
button is taken from games.
- Controllers: the same for the Frame controllers' buttons, minus the gaze actions (gaze
mode is a mouse feature). They're read by the pointer helper through SteamVR input
(@ft_pointer_helper: vrstatus, vrglobal), and a mapped button is taken from games.
- Pointer: speed, dot size, distance and the rest, applied live.
- Ignored panels: SteamVR overlays the pointer passes through (POINTER_IGNORE), by app or
one by one. The helper lists them (@ft_pointer_helper "overlays").
@@ -87,16 +87,14 @@ CONTROLLER_BUTTONS = {
"right/bumper": "Right bumper", "right/trigger": "Right trigger", "right/grip": "Right grip",
"right/thumbstick": "Right stick click",
}
CONTROLLER_ACTIONS = [a for a in ACTION_LABELS if a not in ("key", "none")]
# Not the gaze actions: gaze mode is a mouse feature (docs/gaze-controllers.md; the relay's GAZE_ACTIONS).
CONTROLLER_ACTIONS = [a for a in ACTION_LABELS if a not in ("key", "none", "gaze_toggle", "gaze_precision", "gaze_drag")]
# Gaze mode settings (pointer helper), like POINTER_SETTINGS.
GAZE_SETTINGS = [
("POINTER_GAZE_RETAKE", "Look away to hand back", 5, 1, 45, 0.5, "°"),
("POINTER_GAZE_NUDGE_MAX", "Largest nudge to learn", 8, 1, 30, 0.5, "°"),
("POINTER_GAZE_HOLD", "Hold still to drag", 0.5, 0.1, 2.0, 0.05, "s"),
("POINTER_GAZE_SHOW", "Dot shows after moving", 1.0, 0.0, 5.0, 0.1, "s"),
("POINTER_PRECISION_GAIN", "Precision steering", 0.5, 0.1, 2.0, 0.05, "×"),
("POINTER_PRECISION_DEADZONE", "Precision dead zone", 0.3, 0.0, 3.0, 0.1, "°"),
("POINTER_GAZE_DRAG_GAIN", "Drag steering", 1.0, 0.1, 2.0, 0.05, "×"),
]
# In gaze mode, what the mouse's left button does (POINTER_GAZE_MOUSE).
GAZE_MOUSE = {"precision": "Gaze precision: hold to steer with the mouse, release to click",
@@ -809,6 +807,17 @@ class Backend(QObject):
self.pointerChanged.emit()
self.message.emit(f"Mouse in gaze mode: {GAZE_MOUSE[mode].lower()}", False)
@Property(bool, notify=pointerChanged)
def gazeDotAlways(self):
return read_conf().get("POINTER_GAZE_DOT", "always") != "moving"
@Slot(bool)
def setGazeDotAlways(self, on):
write_conf_value("POINTER_GAZE_DOT", "always" if on else "moving")
self.reload_timer.start()
self.pointerChanged.emit()
self.message.emit("Gaze dot: " + ("always shown" if on else "shown only while the mouse moves it"), False)
@Property(str, notify=pointerChanged)
def pointerRole(self):
v = read_conf().get("POINTER_ROLE", "right")
+9 -3
View File
@@ -833,12 +833,18 @@ Kirigami.ApplicationWindow {
}
}
}
Controls.Switch {
Kirigami.FormData.label: "Gaze dot:"
text: "Always shown (off: only while the mouse moves it)"
checked: backend.gazeDotAlways
onToggled: backend.setGazeDotAlways(checked)
}
Controls.Label {
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
wrapMode: Text.WordWrap
text: "Controllers: map a button to Gaze precision (hold, point the controller to steer, release to "
+ "click) or Gaze drag (the same, pressed at once) on the Controllers page. Gaze pointer on/off "
+ "can go on a controller button, a mouse button, or a key combination below."
text: "Gaze works with the mouse. The Frame controllers don't take part, and moving one hands the "
+ "pointer back to the controllers. Gaze pointer on/off, Gaze precision, and Gaze drag can go on "
+ "a mouse button (Buttons page) or a key combination below."
opacity: 0.7
font: Kirigami.Theme.smallFont
}