From a79ccc523be0f70fbbc4f7a8c8ded613c11ce44b Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Mon, 19 Oct 2026 12:04:48 +0200 Subject: [PATCH] Workout handoff clears gatelock's Claim.grab_held while the app has input Lending the X grab to the workout app stops recovery and releases it with the claim still live, so gatelock 0.11 held_grabs (leisure-watch C2) read "held" for the whole workout. release() now reports held=False before grab_release; reacquire()'s grab_input() sets it true once the grab lands. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N --- screen_locker/_workout_handoff.py | 4 ++++ screen_locker/tests/test_workout_handoff.py | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/screen_locker/_workout_handoff.py b/screen_locker/_workout_handoff.py index 78a5016..46f29c0 100644 --- a/screen_locker/_workout_handoff.py +++ b/screen_locker/_workout_handoff.py @@ -42,6 +42,10 @@ def lock_grab_handoff(lock: LockWindow) -> GrabHandoff: # Stop first: a watchdog tick between the release and the app's own # grab would take the screen straight back off it. lock._recovery.stop() + # Claim.grab_held false BEFORE the release (fail closed), so + # leisure-watch never reads "held" while the workout app has input. + # reacquire() -> grab_input() sets it true once the grab lands. + lock.report_grab_held(held=False) with contextlib.suppress(tk.TclError): lock.root.grab_release() _logger.warning( diff --git a/screen_locker/tests/test_workout_handoff.py b/screen_locker/tests/test_workout_handoff.py index 9255289..47cd560 100644 --- a/screen_locker/tests/test_workout_handoff.py +++ b/screen_locker/tests/test_workout_handoff.py @@ -43,6 +43,10 @@ class _FakeLock: self.root = _Root() + def report_grab_held(self, *, held: bool) -> None: + """gatelock 0.11: what leisure-watch reads via ``held_grabs``.""" + self.events.append(f"held={held}") + def grab_input(self) -> None: self.events.append("grab_input") self._recovery.running = True @@ -59,14 +63,14 @@ class TestLockGrabHandoff: lock = _FakeLock() lock_grab_handoff(lock).release() assert lock._recovery.events == ["stop"] - assert lock.events == ["grab_release"] + assert lock.events == ["held=False", "grab_release"] assert not lock._recovery.running def test_reacquire_restores_the_grab_and_the_watchdog(self) -> None: handoff = lock_grab_handoff(lock := _FakeLock()) handoff.release() handoff.reacquire() - assert lock.events == ["grab_release", "grab_input"] + assert lock.events == ["held=False", "grab_release", "grab_input"] assert lock._recovery.running def test_never_closes_the_lock_window(self) -> None: @@ -81,7 +85,7 @@ class TestLockGrabHandoff: handoff = lock_grab_handoff(lock := _FakeLock(release_raises=True)) handoff.release() handoff.reacquire() - assert lock.events == ["grab_release", "grab_input"] + assert lock.events == ["held=False", "grab_release", "grab_input"] class TestStartWorkout: