Go to file
Krzysztof kuhy Rudnicki a8b34befb5 Lend the lock screen's grab to the workout app, without unlocking it
The locker cannot close() to let the app take over: close() also restores VT
switching, which would hand the user Ctrl+Alt+F2 and an exit from the very
obligation the lock exists to enforce. Only the grab moves; the Tk window
stays mapped underneath, so the screen is still covered if the app dies.

The watchdog has to be stopped for the duration, and measuring it is what
makes that non-negotiable. gatelock's recovery loop re-takes any grab it
finds missing every 1000ms -- by design, so that pulling a cable is not an
escape -- and holds_grab() is `grab_current() is not None`, which is False
once another X client holds it. Verified on a private Xvfb display: a naked
release is stolen back within ~1.5s, while stopping the loop first lets the
app keep the screen and the locker take it back cleanly afterwards.

The same harness confirms the ordering the handshake depends on: while the
locker holds the global grab a second X client cannot even finish connecting
(4s, no output at all), so there is no window in which the screen is
uncovered. It only gets through after the release.

Reaching into lock._recovery earns an SLF001 waiver rather than a public
pause on LockWindow: gatelock's _recovery.py carries a monotonicity proof
that no transition ever decreases the grab, and a public lend_grab() would
be exactly that transition. The waiver is documented where the other four
SLF001 waivers in this repo already live.

start_workout blocks the Tk loop for the whole workout, which is deliberate
-- pumping Tk underneath is what would let a tick steal the screen mid-set.
The docstring names the cost: a monitor hotplug is not handled until the app
exits and grab_input() re-covers every live output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfJC1Wu1SEgJ6X49k5Jqtg
2026-08-22 12:08:40 +02:00
.github/workflows Finish the Python side: scripts, conftest, and the last test modules 2026-08-15 20:57:29 +02:00
docs Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
prompts Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
screen_locker Lend the lock screen's grab to the workout app, without unlocking it 2026-08-22 12:08:40 +02:00
scripts Queue behind stronger lockers, single-instance guard, fix budget-crash in fit check 2026-08-21 13:04:54 +02:00
stronglift_replacement Run the workout app on Linux, and let it hold the screen lock 2026-08-22 11:18:48 +02:00
.gitignore Stop the relaxed-day popup from re-firing every 30 minutes after Skip 2026-08-18 21:55:59 +02:00
.mcp.json Add read-only MCP server exposing workout/lock status 2026-07-10 21:28:09 +02:00
.pre-commit-config.yaml Adopt crdt-sync v0.9.0's bundled sync_log signature 2026-08-21 14:28:14 +02:00
arm.sh Re-arm the locker, and make a disarmed enforcer impossible to miss 2026-08-17 16:47:25 +02:00
CLAUDE.md Adopt crdt-sync v0.9.0's bundled sync_log signature 2026-08-21 14:28:14 +02:00
DESIGN_AUDIT_TODO.md Split DESIGN_AUDIT_TODO.md into one file per audited surface 2026-08-15 21:27:19 +02:00
early-bird-workout-check.timer Re-arm the locker, and make a disarmed enforcer impossible to miss 2026-08-17 16:47:25 +02:00
install_autostart.sh chore: set up as standalone repo 2026-05-28 07:43:06 +02:00
install_systemd.sh Re-arm the locker, and make a disarmed enforcer impossible to miss 2026-08-17 16:47:25 +02:00
install.sh Make every lock screen keyboard-operable, and add the missing installer 2026-07-30 16:25:45 +02:00
pyproject.toml Lend the lock screen's grab to the workout app, without unlocking it 2026-08-22 12:08:40 +02:00
README.md Add read-only MCP server exposing workout/lock status 2026-07-10 21:28:09 +02:00
refactor_claude_todo.md Refresh the refactor todo: 41 -> 10 files over the cap 2026-08-15 21:46:16 +02:00
remove_autostart.sh chore: set up as standalone repo 2026-05-28 07:43:06 +02:00
remove_systemd.sh Let the PC see phone workouts again, and stop losing progression on reinstall 2026-08-10 14:39:02 +02:00
requirements.txt Adopt crdt-sync v0.9.0's bundled sync_log signature 2026-08-21 14:28:14 +02:00
run.sh chore: set up as standalone repo 2026-05-28 07:43:06 +02:00
todo_finish_dart_250_prompt.md Add todo_finish_dart_250_prompt.md and the Dart test splitter 2026-08-15 21:49:15 +02:00
todo_fix_pylint_10_prompt.md Add todo_fix_pylint_10_prompt.md: raise pylint to a 10.00 gate 2026-08-15 19:18:39 +02:00
workout-locker.service Drop WorkingDirectory/PYTHONPATH from workout-locker.service 2026-06-22 12:43:58 +02:00
workout-locker.timer Re-arm the locker, and make a disarmed enforcer impossible to miss 2026-08-17 16:47:25 +02:00
workout-sync.service Re-arm the locker, and make a disarmed enforcer impossible to miss 2026-08-17 16:47:25 +02:00
workout-sync.timer Let the PC see phone workouts again, and stop losing progression on reinstall 2026-08-10 14:39:02 +02:00

screen-locker

Tkinter/systemd screen locker with workout tracking, sick-day management, and wake-alarm integration. It enforces a workout cadence by locking the screen until a RunnerUp-verified (or explicitly justified) workout is logged.

MCP server (Claude Code integration)

screen-locker exposes a read-only MCP server (screen_locker._mcp) so Claude Code and its subagents can query workout compliance and the lock-decision state through typed tools — without shelling out to the screen-locker-status CLI or opening the Tk window.

  • Read-only tools: get_status (full status snapshot), get_summary (one-line i3blocks summary + ok/warn/lock state word), explain_lock (why the screen is / isn't locked right now), get_flags (the individual boolean lock-decision predicates for today).
  • There are no write/action tools, by design. Nothing here logs a workout or mutates state — workouts are logged only from RunnerUp-verified TCX data, never from a caller's claim. No tool exposes the sync token or any HMAC key.

The mcp SDK is an optional dependency (pip install -e '.[mcp]'), kept out of the CLI/systemd system-python path. One-time setup of the dedicated venv that Claude Code spawns:

./scripts/setup_mcp.sh

Registration lives in the checked-in .mcp.json (project scope). Restart Claude Code in this repo and approve the project MCP-server prompt for it to load. Verify with claude mcp list.