Files
Frametop--frametop/input/test/pause-buttons-test.py
T
DeeJanuzandClaude Opus 5.5 2ede132b62 Input relay tests: what standing down sends, in order
pause-buttons-test checked the pointer's driver_down set and filtered
the sent commands down to "btn" lines. On the code before the fix it
stopped at a missing attribute, before any behavior was checked, and it
couldn't see where "hide" went. The order matters: a release after
"hide" wakes a helper that wakes on any btn.

It now compares the exact commands stand_down sends (["btn trigger 0",
"hide"] for a held left button) and adds the cases that were missing:
the pointer already off with a button held (the idle timeout,
pointer_toggle), a mapped controller button and a key combination, and
gaze holds, which are the helper's to end. Its labels now say what each
case is: they called a plain mouse press a gaze drag. Against the relay
before the fix, 5 of its 11 checks fail; against the contributor's
commit, the 2 pointer-off ones.

keys-test gains a pause check through the relay's main(): in pointer
mode, a left button held into a pause comes up, then the pointer hides,
its release during the pause reaches no one, and clicks go to the helper
again after resume.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:53:45 -06:00

140 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Offline test of the relay's pointer standing down when Frametop pauses: a click still held
when the pause starts (a mouse button, a mapped controller button, or a key combination mapped
to left, right, middle or back) is released by stand_down, and "hide" follows the release, even
when the pointer was off already. Gaze holds (gazekey, gazedrag, precision) are the helper's
to end, so stand_down sends nothing for them. The pointer's socket is a recorder, so nothing
reaches the helper, SteamVR, or the running relay.
input/test/pause-buttons-test.py [RELAY]
"""
import importlib.util
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
relay_path = sys.argv[1] if len(sys.argv) > 1 else os.path.join(HERE, "..", "input-relay.py")
tag = f"ft_pause_buttons_test_{os.getpid()}"
src = open(relay_path).read().replace('"\\0frametop_relay"', f'"\\0{tag}_relay"')
# macOS has no SOCK_NONBLOCK; the pointer's socket is replaced below anyway.
src = src.replace("socket.SOCK_DGRAM | socket.SOCK_NONBLOCK", "socket.SOCK_DGRAM")
relay = importlib.util.module_from_spec(importlib.util.spec_from_loader("relay", loader=None))
relay.__file__ = os.path.abspath(relay_path)
sys.path.insert(0, os.path.dirname(os.path.abspath(relay_path)))
exec(compile(src, relay_path, "exec"), relay.__dict__)
failures = []
def check(label, got, want):
ok = got == want
print(("ok " if ok else "FAIL ") + label + ("" if ok else f": got {got!r}, want {want!r}"), flush=True)
if not ok:
failures.append(label)
class Recorder:
"""Stands in for the helper's socket: remembers every command, in order."""
def __init__(self):
self.sent = []
def sendto(self, data, addr):
self.sent.append(data.decode())
def pointer():
p = relay.Pointer(0.02, 3600.0)
p.sock = Recorder()
return p
def since(p, mark):
"""What the pointer sent the helper after mark (a length of p.sock.sent)."""
return p.sock.sent[mark:]
# ---------------------------------------------------------------- the fix
p = pointer()
p.action("left", 1, 10.0) # a mouse click (or drag) held: btn trigger 1
check("press reaches the helper", p.sock.sent, ["show", "recenter", "btn trigger 1"])
mark = len(p.sock.sent)
p.stand_down() # Frametop pauses while it's held
check("stand_down releases the held button, then hides", since(p, mark), ["btn trigger 0", "hide"])
# The release that pausing drops later is already covered: the button is no longer down,
# so a stray second stand_down sends nothing.
mark = len(p.sock.sent)
p.stand_down()
check("a second stand_down sends nothing more", since(p, mark), [])
# Two buttons down at once: a left drag tilted with the right button
p = pointer()
p.action("left", 1, 10.0)
p.action("right", 1, 10.5)
mark = len(p.sock.sent)
p.stand_down()
check("both held buttons are released, then it hides", since(p, mark), ["btn b 0", "btn trigger 0", "hide"])
# A mapped controller button and a key combination take the same path as the mouse.
p = pointer()
p.action("middle", 1, 10.0, "right") # a controller button mapped to middle
p.action("back", 1, 10.1, "keyboard") # a key combination mapped to back
mark = len(p.sock.sent)
p.stand_down()
check("controller and key combination clicks are released too", since(p, mark),
["btn joystick 0", "btn x 0", "hide"])
# ---------------------------------------------------------------- the pointer already off
# A release with no "hide" after it would wake a helper that wakes on any btn, connecting the
# virtual controller during the game.
p = pointer()
p.idle = 30.0
p.action("left", 1, 10.0)
for t in (10.3, 10.4, 41.0): # the claim pulse, then 30 s with no mouse input
p.tick(t)
check("held 30 s with no mouse input: the pointer goes off", (p.active, p.sock.sent[-1]), (False, "hide"))
mark = len(p.sock.sent)
p.stand_down()
check("idle with a button held: the release, then hide", since(p, mark), ["btn trigger 0", "hide"])
p = pointer()
p.action("left", 1, 10.0)
p.action("pointer_toggle", 1, 10.5)
mark = len(p.sock.sent)
p.stand_down()
check("pointer toggled off with a button held: the release, then hide", since(p, mark),
["btn trigger 0", "hide"])
# ---------------------------------------------------------------- the ordinary path
p = pointer()
p.action("left", 1, 10.0)
p.action("left", 0, 11.0) # released before the pause
mark = len(p.sock.sent)
p.stand_down()
check("a button released before the pause: stand_down only hides", since(p, mark), ["hide"])
p = pointer()
p.idle = 30.0
p.action("left", 1, 10.0)
p.action("left", 0, 11.0)
for t in (10.3, 10.4, 42.0): # off by itself
p.tick(t)
mark = len(p.sock.sent)
p.stand_down()
check("nothing held and the pointer off: stand_down sends nothing", since(p, mark), [])
# Gaze holds go to the helper as their own commands, and it ends them when the pointer hides.
p = pointer()
p.action("gaze_left", 1, 10.0, "keyboard") # Meta+J held
p.action("gaze_drag", 1, 10.1, "mouse")
mark = len(p.sock.sent)
p.stand_down()
check("gaze holds: stand_down sends only hide (the helper ends them)", since(p, mark), ["hide"])
print()
if failures:
print(f"{len(failures)} failed: {', '.join(failures)}")
sys.exit(1)
print("all ok")