Block all keyboard (except ESC) and all mouse input via WH_MOUSE_LL; tagged idle nudge passes through own hook; hint updated

This commit is contained in:
Hermes Agent committed 2026-09-18 13:02:58 +02:00
1 parent d16a2db9ae
commit d3286ef3b8
1 file changed
+40 -7
+40 -7
View File
@@ -245,6 +245,8 @@ if sys.platform == "win32":
WM_KEYDOWN_LL = 0x0100 WM_KEYDOWN_LL = 0x0100
VK_ESCAPE = 0x1B VK_ESCAPE = 0x1B
WH_KEYBOARD_LL = 13 WH_KEYBOARD_LL = 13
WH_MOUSE_LL = 14
MOUSE_NUDGE_TAG = 0x42424242
DT_CENTER = 0x00000001 DT_CENTER = 0x00000001
DT_VCENTER = 0x00000004 DT_VCENTER = 0x00000004
DT_SINGLELINE = 0x00000020 DT_SINGLELINE = 0x00000020
@@ -302,6 +304,15 @@ if sys.platform == "win32":
("dwExtraInfo", ULONG_PTR), ("dwExtraInfo", ULONG_PTR),
] ]
class MSLLHOOKSTRUCT(ctypes.Structure):
_fields_ = [
("pt", wt.POINT),
("mouseData", wt.DWORD),
("flags", wt.DWORD),
("time", wt.DWORD),
("dwExtraInfo", ULONG_PTR),
]
# --- prototypes (64-bit safe) --- # --- prototypes (64-bit safe) ---
user32.EnumDisplayMonitors.argtypes = [wt.HDC, ctypes.POINTER(wt.RECT), MONITORENUMPROC, wt.LPARAM] user32.EnumDisplayMonitors.argtypes = [wt.HDC, ctypes.POINTER(wt.RECT), MONITORENUMPROC, wt.LPARAM]
user32.EnumDisplayMonitors.restype = wt.BOOL user32.EnumDisplayMonitors.restype = wt.BOOL
@@ -401,7 +412,7 @@ if sys.platform == "win32":
_hwnds = [] _hwnds = []
_hint_active = True _hint_active = True
_hook_id = None _hook_id = None
_hint_text = "ESC - exit" _hint_text = "ESC - exit | mouse/keyboard locked"
def enum_monitor_rects(): def enum_monitor_rects():
rects = [] rects = []
@@ -456,15 +467,28 @@ if sys.platform == "win32":
return user32.DefWindowProcW(hwnd, msg, wparam, lparam) return user32.DefWindowProcW(hwnd, msg, wparam, lparam)
def _low_level_keyboard_proc(nCode, wParam, lParam): def _low_level_keyboard_proc(nCode, wParam, lParam):
if nCode >= 0 and wParam == WM_KEYDOWN_LL: if nCode >= 0:
kb = ctypes.cast(lParam, ctypes.POINTER(KBDLLHOOKSTRUCT)).contents kb = ctypes.cast(lParam, ctypes.POINTER(KBDLLHOOKSTRUCT)).contents
if kb.vkCode == VK_ESCAPE: if kb.vkCode == VK_ESCAPE and wParam == WM_KEYDOWN_LL:
LOG.info("ESC pressed -> exit") LOG.info("ESC pressed -> exit")
user32.PostQuitMessage(0) user32.PostQuitMessage(0)
return 1 # block ALL keyboard input while the overlay is up
return user32.CallNextHookEx(_hook_id, nCode, wParam, lParam) return user32.CallNextHookEx(_hook_id, nCode, wParam, lParam)
_mouse_hook_id = None
def _low_level_mouse_proc(nCode, wParam, lParam):
if nCode >= 0:
ms = ctypes.cast(lParam, ctypes.POINTER(MSLLHOOKSTRUCT)).contents
# allow only our own idle-keepalive nudge (tagged dwExtraInfo);
# block every real mouse event (moves, clicks, wheel)
if int(ms.dwExtraInfo) == MOUSE_NUDGE_TAG:
return user32.CallNextHookEx(_mouse_hook_id, nCode, wParam, lParam)
return 1
return user32.CallNextHookEx(_mouse_hook_id, nCode, wParam, lParam)
def run_overlay(): def run_overlay():
global _hwnds, _hook_id global _hwnds, _hook_id, _mouse_hook_id
# Per-monitor V2 DPI awareness: device-coord rects map 1:1 to each # Per-monitor V2 DPI awareness: device-coord rects map 1:1 to each
# monitor regardless of mixed DPI / where the primary sits. # monitor regardless of mixed DPI / where the primary sits.
if shcore is not None: if shcore is not None:
@@ -522,11 +546,16 @@ if sys.platform == "win32":
for _ in range(12): for _ in range(12):
if user32.ShowCursor(False) < 0: if user32.ShowCursor(False) < 0:
break break
# global ESC hook # global ESC hook (also blocks all other keyboard input)
kb_proc = HOOKPROC(_low_level_keyboard_proc) kb_proc = HOOKPROC(_low_level_keyboard_proc)
_hook_id = user32.SetWindowsHookExW(WH_KEYBOARD_LL, kb_proc, hinst, 0) _hook_id = user32.SetWindowsHookExW(WH_KEYBOARD_LL, kb_proc, hinst, 0)
if not _hook_id: if not _hook_id:
LOG.warning("SetWindowsHookExW failed: %d", kernel32.GetLastError()) LOG.warning("SetWindowsHookExW failed: %d", kernel32.GetLastError())
# global mouse hook: block all real mouse input
mouse_proc = HOOKPROC(_low_level_mouse_proc)
_mouse_hook_id = user32.SetWindowsHookExW(WH_MOUSE_LL, mouse_proc, hinst, 0)
if not _mouse_hook_id:
LOG.warning("mouse hook failed: %d", kernel32.GetLastError())
# keep display awake while running (prevents monitor standby, which would # keep display awake while running (prevents monitor standby, which would
# make ESC's first press get swallowed by the wake and a ~30s DDC hang) # make ESC's first press get swallowed by the wake and a ~30s DDC hang)
@@ -542,8 +571,9 @@ if sys.platform == "win32":
def _activity(): def _activity():
while not _activity_stop.wait(30): while not _activity_stop.wait(30):
try: try:
user32.mouse_event(0x0001, 1, 0, 0, None) tag = ctypes.c_void_p(MOUSE_NUDGE_TAG)
user32.mouse_event(0x0001, 0xFFFFFFFF, 0, 0, None) user32.mouse_event(0x0001, 1, 0, 0, tag)
user32.mouse_event(0x0001, 0xFFFFFFFF, 0, 0, tag)
except Exception: except Exception:
pass pass
@@ -557,6 +587,9 @@ if sys.platform == "win32":
if _hook_id: if _hook_id:
user32.UnhookWindowsHookEx(_hook_id) user32.UnhookWindowsHookEx(_hook_id)
if _mouse_hook_id:
user32.UnhookWindowsHookEx(_mouse_hook_id)
_mouse_hook_id = None
_activity_stop.set() _activity_stop.set()
at.join(timeout=1) at.join(timeout=1)
kernel32.SetThreadExecutionState(ES_CONTINUOUS) # allow display sleep again kernel32.SetThreadExecutionState(ES_CONTINUOUS) # allow display sleep again