mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 05:00:08 +02:00
Use the other eye when the tracker loses one, and add a headset fit check
SteamVR's combined gaze keeps going on one eye, but it holds the lost eye's yaw, so the gaze moves half as far sideways as the eyes do. ft-gaze now reads each eye's tracking uncertainty and raw measurement from eye-server.mmap. When the tracker loses an eye, ft-gazed takes the gaze from the other one, plus the offset that eye usually shows against both, learned while both are seen. On a recording, one eye alone came out a median 0.8 degrees from both eyes' gaze. Glances down at the keyboard, past every screen, aren't sent. The pointer stays put, and eyes lost there don't count as lost. The gaze probe gets a Headset fit mode. It shows per-eye tracking, openness and confidence, maps where each eye gets lost, gives hints, and has a guided check. The settings app opens it from the Gaze page and shows how often each eye is lost. The probe can also test each eye alone, and its side panel now collapses to a title bar so the dot isn't hidden behind it. Snapping to UI elements is deferred; the mouse drag is the correction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e60cb2f28a
commit
f01c84f9a3
10 files changed
+715
-68
No files matched your search
@@ -541,9 +541,12 @@ class Backend(QObject):
|
||||
n = status["samples"] - prev[1]["samples"]
|
||||
status["rate"] = n / (now - prev[0])
|
||||
status["one_eye_share"] = (status["one_eye"] - prev[1]["one_eye"]) / n if n else 0.0
|
||||
for key in ("lost_left", "lost_right"):
|
||||
if key in status and key in prev[1]:
|
||||
status[key + "_share"] = (status[key] - prev[1][key]) / n if n else 0.0
|
||||
elif self._gaze:
|
||||
status.setdefault("rate", self._gaze.get("rate"))
|
||||
status.setdefault("one_eye_share", self._gaze.get("one_eye_share"))
|
||||
for key in ("rate", "one_eye_share", "lost_left_share", "lost_right_share"):
|
||||
status.setdefault(key, self._gaze.get(key))
|
||||
if not prev or now - prev[0] > 0.5:
|
||||
self._gaze_prev = (now, status)
|
||||
self._gaze = status
|
||||
@@ -604,13 +607,21 @@ class Backend(QObject):
|
||||
@Slot()
|
||||
def openGazeProbe(self):
|
||||
"""Calibrate in ft-gazeprobe (a GTK app on the host, fullscreen on a Frametop screen)."""
|
||||
self._open_probe([], "Opening the gaze probe: calibrate there, then close it")
|
||||
|
||||
@Slot()
|
||||
def openHeadsetFit(self):
|
||||
"""The probe's Headset fit mode: how well the tracker sees each eye, as you adjust."""
|
||||
self._open_probe(["--mode", "fit"], "Opening the headset fit check in the gaze probe")
|
||||
|
||||
def _open_probe(self, args, done):
|
||||
runner = ["distrobox-host-exec"] if shutil.which("distrobox-host-exec") else []
|
||||
env = [f"{k}={os.environ[k]}" for k in ("WAYLAND_DISPLAY", "DISPLAY", "XAUTHORITY", "DBUS_SESSION_BUS_ADDRESS")
|
||||
if os.environ.get(k)]
|
||||
try:
|
||||
subprocess.Popen(runner + ["env"] + env + [GAZE_PROBE], stdin=subprocess.DEVNULL,
|
||||
subprocess.Popen(runner + ["env"] + env + [GAZE_PROBE] + args, stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
|
||||
self.message.emit("Opening the gaze probe: calibrate there, then close it", False)
|
||||
self.message.emit(done, False)
|
||||
except OSError as e:
|
||||
self.message.emit(f"Couldn't open the gaze probe: {e}", True)
|
||||
|
||||
|
||||
Reference in new issue
Block a user