utils/gatelock
Krzysztof kuhy Rudnicki 7327c287f4
Some checks are pending
gatelock pre-commit / pre-commit (push) Waiting to run
gatelock tests / test (3.10) (push) Waiting to run
gatelock tests / test (3.11) (push) Waiting to run
gatelock tests / test (3.12) (push) Waiting to run
gatelock: extract the shared escape-hatch core
Every locker here already had a bounded way out -- screen-locker's sick
mode, diet-guard's log-a-meal -- except the wake alarm, which had only the
challenge itself. Rather than copy sick mode a third time, the mechanism
moves into gatelock so all three consumers share one implementation and one
set of budget rules.

`_escape.py` owns the parts that are policy-free: rolling 7/30/90-day
budgets, the justification draft and its validation, the escalating lockout
that doubles with each recent use, HMAC-signed history entries, and the
"last ten, played back to you" summary. App-specific policy (screen-locker's
commitment penalty and workout debt) stays in the consumer.

Entries that fail HMAC verification are KEPT rather than dropped. Dropping
them looks safer but is not: `verify_entry_hmac` also returns False when the
key is merely unreadable, so "discard what you cannot verify" would turn a
chmod on the key file into an unlimited escape hatch. They stay in the
budget count and are reported through a `tampered` counter instead.

278 tests, 100% branch coverage, no lint suppressions.
2026-07-25 15:35:20 +02:00
..
.github/workflows Initial gatelock: shared lock-window + HMAC log-integrity backend 2026-06-19 17:39:44 +02:00
gatelock gatelock: extract the shared escape-hatch core 2026-07-25 15:35:20 +02:00
.gitignore Initial gatelock: shared lock-window + HMAC log-integrity backend 2026-06-19 17:39:44 +02:00
.pre-commit-config.yaml Initial gatelock: shared lock-window + HMAC log-integrity backend 2026-06-19 17:39:44 +02:00
LICENSE Initial gatelock: shared lock-window + HMAC log-integrity backend 2026-06-19 17:39:44 +02:00
pyproject.toml gatelock v0.2.0: per-output locking, monotonic recovery, N-app arbitration 2026-07-25 14:30:03 +02:00
README.md gatelock: warm-tint the unified-design-system palette, add on-fill token 2026-07-21 21:11:07 +02:00
requirements.txt Initial gatelock: shared lock-window + HMAC log-integrity backend 2026-06-19 17:39:44 +02:00

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, wake_alarm, and 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

pip install "gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.1.1#subdirectory=gatelock"

Usage

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). Each axis (overrideredirect, grab, disable_vt, grab_retry_ms) can be set explicitly to reproduce a consumer's exact prior behavior where it diverges from the preset.

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.

Development

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
pytest
pre-commit install && pre-commit install --hook-type pre-push