# gatelock Shared fullscreen lock-window + HMAC log-integrity backend, extracted from three personal automation tools that each independently implemented "lock the screen with a blocking overlay until a condition is met": [diet_guard](https://github.com/kuhyx/testsAndMisc/tree/main/python_pkg/diet_guard), [wake_alarm](https://github.com/kuhyx/testsAndMisc/tree/main/python_pkg/wake_alarm), and [screen-locker](https://github.com/kuhyx/screen-locker). ## Why All three reimplemented the same tkinter fullscreen mechanics (overrideredirect, input grab, VT-switch disable) at different levels of maturity, and the HMAC-signed-state module had already been hand-copied between two of them because they live in separate repos. `gatelock` is the one place that logic now lives. ## Install ```bash pip install "gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.1.1#subdirectory=gatelock" ``` ## Usage ```python import tkinter as tk from gatelock import GateRoot, LockConfig, LockWindow class MyGate: def __init__(self, *, demo_mode: bool) -> None: self.root = GateRoot() self.root.on_callback_error = self._handle_callback_error config = LockConfig(mode="soft" if demo_mode else "hard") self._lock = LockWindow(self.root, config, hooks=self) self._lock.setup() # ... build your widgets ... self._lock.grab_input() def on_focus_ready(self) -> None: self.my_entry.focus_force() def on_callback_error(self) -> None: self.close() def on_close(self) -> None: ... # release any hardware/state held while locked def close(self) -> None: self._lock.close() def run(self) -> None: self._lock.run() ``` `LockConfig`'s `mode` preset bundles the common combination ("soft" = topmost only, typeable, WM-escapable; "hard" = overrideredirect + global grab + VT-disable). `overrideredirect` and the `grab=GrabPolicy(kind=..., disable_vt=..., retry_ms=...)` axes can be set explicitly to reproduce a consumer's exact prior behavior where it diverges from the preset. Colours, type scale and spacing are the `palette`, `typography` and `spacing` records (`gatelock.LockPalette` etc.), overridden as a unit. `gatelock.log_integrity` ports the HMAC-signed state module used by all three projects; `DEFAULT_HMAC_KEY_FILE` (`/etc/workout-locker/hmac.key`) is unchanged so existing signed history keeps verifying. `gatelock.morning_session.morning_skip` is the one reader of wake-alarm's signed `morning_session.json` (the wake-early carrot: no lock until the signed `exempt_until`, 11:00 for a morning completed in time). screen-locker, leetcode-guard and book-guard all call it; the policy itself stays in wake-alarm. Path and retry budget are arguments so each consumer's tests keep redirecting their own constant. `gatelock.night_lockdown.evening_lockdown_today` answers "has tonight's evening night-lockdown already happened?" from night-lockdown's `/var/lib/night-lockdown/locked_at_epoch` (local date today, local time >= 12:00; the 04:01 morning re-lock does not count). Earner gates (screen-locker's workout lock, leetcode-guard, book-guard) opt in and do not re-arm for the rest of such a day; diet-guard does not. Missing or garbage means "no lockdown" (garbage warns), so the gate locks as before. `gatelock.held_grabs(claims_dir=None) -> tuple[Claim, ...]` answers "is a locker's X grab ACTUALLY held right now?": live claims (flock-proved) whose `grab_held` is true and whose declared grab is not `none`. `grab_held` turns true only after `grab_set`/`grab_set_global` succeeded, follows each recovery tick, and is cleared/restored around a deliberate release via `LockWindow.report_grab_held(held=...)` (or `Arbiter.set_grab_held`) -- leetcode-guard's study mode. It is rewritten in place under the owner's flock. A claim from a pre-0.11 writer has no key and reads as `grab != "none"`. Root has no `XDG_RUNTIME_DIR`, so a root reader passes the user's dir (`/run/user/1000/gatelock/claims`). ## Development ```bash python -m venv .venv && source .venv/bin/activate pip install -r requirements.txt pytest pre-commit install && pre-commit install --hook-type pre-push ```