mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-11 15:54:11 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
gatelock pre-commit / pre-commit (push) Waiting to run
gatelock tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
A keyring prompt (gcr-prompter) that grabbed input before the leetcode lock armed left the lock visible but unclickable: every click went to the prompt hidden under it. Each failed grab_set_global also froze Tk ~1 s, so the lock barely repainted. - Raise known credential prompts above the lock after 2 s of a blocked grab; games and other grabbers stay covered. - Probe the grab on a side Xlib connection first so a blocked grab never stalls the Tk loop (worst gap 1.05 s -> 0.05 s). - Log the Tk grab error and stop blaming our own claim. - 1.5 s startup grace in wait_for_turn, and a weaker armed lock steps back (windowless, re-arms later) when a stronger one is live, so only one lock is ever on screen. - Real-X checks: verify_prompt_yield, verify_grab_responsive, verify_all_lockers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWZbpxQZj41WpbQPuxB
102 lines
3.1 KiB
Python
Executable File
102 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2026 Krzysztof Rudnicki
|
|
# pylint: disable=missing-function-docstring
|
|
"""One stand-in locker for ``verify_all_lockers.py``: the real startup sequence.
|
|
|
|
Mirrors what leetcode_guard / screen_locker / diet_guard / book_guard /
|
|
focus_guard do: ``Arbiter`` -> ``publish`` -> ``wait_for_turn`` (windowless) ->
|
|
``acquire_holder`` -> ``LockWindow`` -> ``setup`` -> ``grab_input`` -> mainloop,
|
|
with the real app name and rank. Its only UI is one button; clicking it ends
|
|
the lock, like a solve does. Everything is reported on stdout, one line each:
|
|
``ARMED``, ``BTN x y`` (button centre, root coordinates), ``GRAB 0|1``,
|
|
``CLICKED``.
|
|
|
|
Usage: ``locker_child.py <app> <rank> <queue_poll_s>``
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import tkinter as tk
|
|
|
|
from gatelock import (
|
|
Arbiter,
|
|
GateRoot,
|
|
LockConfig,
|
|
LockWindow,
|
|
wait_for_turn,
|
|
)
|
|
|
|
REPORT_MS = 250
|
|
|
|
|
|
class _Child:
|
|
"""The hooks object, as each real locker is."""
|
|
|
|
def __init__(self, app: str, rank: int, poll: float) -> None:
|
|
self.app = app
|
|
self.button: tk.Button | None = None
|
|
config = LockConfig(mode="hard", app_name=app, rank=rank)
|
|
arbiter = Arbiter(
|
|
app,
|
|
rank,
|
|
grab=config.resolved_grab(),
|
|
disable_vt=False,
|
|
)
|
|
arbiter.publish()
|
|
wait_for_turn(arbiter, poll=poll)
|
|
arbiter.acquire_holder()
|
|
self.root = GateRoot(className=app)
|
|
self.lock = LockWindow(self.root, config, self, arbiter=arbiter)
|
|
self.lock.setup()
|
|
self.say("ARMED")
|
|
self.lock.grab_input()
|
|
self.root.after(REPORT_MS, self.report)
|
|
|
|
def say(self, text: str) -> None:
|
|
sys.stdout.write(f"{text}\n")
|
|
sys.stdout.flush()
|
|
|
|
def report(self) -> None:
|
|
self.say(f"GRAB {int(self.lock.recovery.holds_grab())}")
|
|
button = self.button
|
|
if button is not None and button.winfo_ismapped():
|
|
x = button.winfo_rootx() + button.winfo_width() // 2
|
|
y = button.winfo_rooty() + button.winfo_height() // 2
|
|
self.say(f"BTN {x} {y}")
|
|
self.root.after(REPORT_MS, self.report)
|
|
|
|
def clicked(self) -> None:
|
|
self.say("CLICKED")
|
|
self.lock.close()
|
|
|
|
# -- LockWindowHooks ---------------------------------------------------
|
|
def build_surface(self, parent: tk.Misc, surface: object) -> None:
|
|
del surface
|
|
self.button = tk.Button(parent, text=self.app, command=self.clicked)
|
|
self.button.place(relx=0.5, rely=0.5, width=240, height=90, anchor="center")
|
|
|
|
def teardown_surface(self, surface: object) -> None:
|
|
del surface
|
|
self.button = None
|
|
|
|
def on_focus_ready(self, surface: object) -> None:
|
|
del surface
|
|
|
|
def on_callback_error(self) -> None:
|
|
self.say("ERROR callback")
|
|
|
|
def on_close(self) -> None:
|
|
return
|
|
|
|
|
|
def main(argv: list[str]) -> int:
|
|
"""Arm one locker and run its mainloop."""
|
|
child = _Child(argv[0], int(argv[1]), float(argv[2]))
|
|
child.lock.run()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|