Gaze: install our eye tracker, prefer it, and say why a calibration dot wasn't taken

A user on a fresh install got "Calibration failed: only 0 of 21 dots" with
no reason. The installer never installed our tracker, so gaze used
SteamVR's, and the only way SteamVR's tracker rejects a dot is losing an
eye for most of the look. The panel just showed a red ring.

- install.sh: step 9/10 installs our tracker (gaze/tracker/install.sh)
  after gaze mode, yes by default; it needs sudo, so --yes runs it only
  when sudo won't prompt. If it fails, gaze keeps SteamVR's tracker.
  Configs that still say GAZE_TRACKER=steam (the old template) are asked
  whether to switch.
- GAZE_TRACKER=auto, the new default: ours when it's installed (the
  frame grabber, its unit, and ft-eyes' Python), else SteamVR's.
  ft-gazed rechecks every second, so installing it switches over. Input
  Settings lists Own tracker first as recommended, and says how to
  install it when it's missing (checking the host's /etc through
  /run/host from the dev container).
- The calibration panel has a note line, orange over the instructions:
  why a dot wasn't taken (an eye lost, a blink, the eyes disagreeing for
  SteamVR's tracker, from steady_samples' new drop counts; ft-eyes' reply
  for ours), what a click is still waiting for after 1.5 s, and a failed
  calibration's most common reason, which the Gaze page shows too.
  steady_samples keeps the same samples as before (checked on 2037
  windows of recordings); a lost eye is named before a blink, since its
  openness reads 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DeeJanuzandClaude Opus 5.5 committed 2026-10-01 20:48:52 -06:00
1 parent a7f9a8dd80
commit 12ad93d24c
12 files changed
+252 -63

No files matched your search

+24 -4
View File
@@ -162,8 +162,12 @@ def key_bindings(rules):
counts as its own)."""
bound = rules.get("key_bindings")
return dict(bound) if isinstance(bound, dict) else dict(DEFAULT_KEY_BINDINGS)
# The gaze service's settings (gaze/ft-gazed): whose eye tracking, and the eye bias.
# The gaze service's settings (gaze/ft-gazed): whose eye tracking, and the eye bias. GAZE_TRACKER
# can also be auto (the default): ours when it's installed, else SteamVR's.
GAZE_TRACKERS = {"steam": "SteamVR's eye tracker", "own": "our own eye tracker"}
# Our tracker's frame grabber (gaze/tracker/install.sh), on the host: this runs in the dev
# container, which has the host's /etc under /run/host.
EYEGRAB = ("/etc/frametop/ft-eyegrab", "/run/host/etc/frametop/ft-eyegrab")
GAZE_EYES = {"auto": "auto", "left": "left eye", "right": "right eye"}
# Pointer settings: key, label, default, min, max, step, unit.
POINTER_SETTINGS = [
@@ -961,9 +965,25 @@ class Backend(QObject):
@Property(str, notify=gazeChanged)
def gazeTracker(self):
"""Whose eye tracking the gaze service uses: "steam" or "own" (GAZE_TRACKER)."""
v = read_conf().get("GAZE_TRACKER", "steam")
return v if v in GAZE_TRACKERS else "steam"
"""Whose eye tracking the gaze service uses: "steam" or "own" (GAZE_TRACKER; auto is
ours when it's installed)."""
v = read_conf().get("GAZE_TRACKER", "auto")
if v in GAZE_TRACKERS:
return v
return "own" if self.gazeOwnInstalled else "steam"
@Property(bool, notify=gazeChanged)
def gazeTrackerAuto(self):
"""GAZE_TRACKER is auto (or missing): ours once it's installed, without a choice here."""
return read_conf().get("GAZE_TRACKER", "auto") not in GAZE_TRACKERS
@Property(bool, notify=gazeChanged)
def gazeOwnInstalled(self):
"""Is our own tracker installed? The gaze service says (it also checks ft-eyes' Python in
its checkout); without it, whether the frame grabber is."""
if "own_installed" in self._gaze:
return bool(self._gaze["own_installed"])
return any(os.path.exists(p) for p in EYEGRAB)
@Property(str, notify=gazeChanged)
def gazeEye(self):