utils/gatelock/gatelock/_hooks.py
Krzysztof kuhy Rudnicki 6154dc09bf Pin pre-commit's ruff to 0.16.7 and satisfy the CPY001 it brings
The three Python packages pinned ruff twice at different versions:
requirements.txt at 0.16.7, .pre-commit-config.yaml at v0.15.2. Only the
older one ever ran, so `select = ["ALL"]` was enforced two minor versions
behind. Measured before aligning: v0.15.2 reports 0 findings, every 0.16.x
reports CPY001 on each file -- 70 crdt-sync, 23 freedays, 71 gatelock, and
no other rule.

Resolved by adding the notice rather than ignoring the rule, so the gate
keeps its teeth.

Two gatelock modules sat at exactly the 250-line cap, so a one-line header
tipped them to 251. Both are split along a real seam rather than shaved:

  _widget_fills.py    colour arithmetic over LockConfig, no tkinter import
  _surfaces_focus.py  selects among existing surfaces, never mutates them

The focus split keeps _surfaces.py's documented containment (it stays the
only module that creates, moves or destroys windows), and the static guard
in test_recovery_invariants.py globs _surfaces*.py, so the new sibling is
covered with no list to extend. widgets.py re-exports the fills helpers,
which keeps gatelock.widgets the single public surface its tests import.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kMvSMtsVMB3d2uoZ56pbp
2026-09-12 18:11:24 +02:00

53 lines
2.0 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki
"""The callback interface an app embedding :class:`~gatelock.LockWindow` fills.
Split out of :mod:`gatelock._window` so the pure *interface* -- what an app
must implement -- can be read without the window mechanics that call it. This
module deliberately imports nothing from the runtime half, so an app can type
against the protocol without pulling in Tk window machinery.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Protocol
if TYPE_CHECKING:
import tkinter as tk
from gatelock._surfaces import SurfaceInfo
class LockWindowHooks(Protocol):
"""Callbacks :class:`LockWindow` invokes; the embedding app supplies all."""
def build_surface(self, parent: tk.Misc, surface: SurfaceInfo) -> None:
"""Build this app's widgets inside ``parent``, for one output.
Called once per live output, and again whenever an output comes back.
Tk variables should stay mastered on the root so every surface shows
the same state, and every surface's submit path should call the same
handler -- solving the lock on any monitor dismisses all of them.
"""
def teardown_surface(self, surface: SurfaceInfo) -> None:
"""Forget per-surface state; that output went dark and is closing."""
def on_focus_ready(self, surface: SurfaceInfo | None) -> None:
"""Called once the lock is mapped and (if applicable) grabbed.
Args:
surface: The surface that took initial focus, or None when no
output is live and there is therefore nothing to focus.
"""
def on_callback_error(self) -> None:
"""Called when a Tk callback raised (see :class:`~gatelock.GateRoot`)."""
def on_close(self) -> None:
"""Called once, from :meth:`LockWindow.close`, before VT is restored.
Runs on every exit path -- normal dismiss, SIGTERM, SIGINT -- not just
a clean close, so app-specific teardown (restoring hardware state,
etc.) can't be skipped by killing the process.
"""