From d3286ef3b850570c2bf71f8bb5e62ac1b8ac5c0a Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Fri, 18 Sep 2026 13:02:58 +0200 Subject: [PATCH] Block all keyboard (except ESC) and all mouse input via WH_MOUSE_LL; tagged idle nudge passes through own hook; hint updated --- black_screen_dim.py | 47 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/black_screen_dim.py b/black_screen_dim.py index 3d04745..0298f19 100644 --- a/black_screen_dim.py +++ b/black_screen_dim.py @@ -245,6 +245,8 @@ if sys.platform == "win32": WM_KEYDOWN_LL = 0x0100 VK_ESCAPE = 0x1B WH_KEYBOARD_LL = 13 + WH_MOUSE_LL = 14 + MOUSE_NUDGE_TAG = 0x42424242 DT_CENTER = 0x00000001 DT_VCENTER = 0x00000004 DT_SINGLELINE = 0x00000020 @@ -302,6 +304,15 @@ if sys.platform == "win32": ("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) --- user32.EnumDisplayMonitors.argtypes = [wt.HDC, ctypes.POINTER(wt.RECT), MONITORENUMPROC, wt.LPARAM] user32.EnumDisplayMonitors.restype = wt.BOOL @@ -401,7 +412,7 @@ if sys.platform == "win32": _hwnds = [] _hint_active = True _hook_id = None - _hint_text = "ESC - exit" + _hint_text = "ESC - exit | mouse/keyboard locked" def enum_monitor_rects(): rects = [] @@ -456,15 +467,28 @@ if sys.platform == "win32": return user32.DefWindowProcW(hwnd, msg, 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 - if kb.vkCode == VK_ESCAPE: + if kb.vkCode == VK_ESCAPE and wParam == WM_KEYDOWN_LL: LOG.info("ESC pressed -> exit") user32.PostQuitMessage(0) + return 1 # block ALL keyboard input while the overlay is up 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(): - global _hwnds, _hook_id + global _hwnds, _hook_id, _mouse_hook_id # Per-monitor V2 DPI awareness: device-coord rects map 1:1 to each # monitor regardless of mixed DPI / where the primary sits. if shcore is not None: @@ -522,11 +546,16 @@ if sys.platform == "win32": for _ in range(12): if user32.ShowCursor(False) < 0: break - # global ESC hook + # global ESC hook (also blocks all other keyboard input) kb_proc = HOOKPROC(_low_level_keyboard_proc) _hook_id = user32.SetWindowsHookExW(WH_KEYBOARD_LL, kb_proc, hinst, 0) if not _hook_id: 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 # 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(): while not _activity_stop.wait(30): try: - user32.mouse_event(0x0001, 1, 0, 0, None) - user32.mouse_event(0x0001, 0xFFFFFFFF, 0, 0, None) + tag = ctypes.c_void_p(MOUSE_NUDGE_TAG) + user32.mouse_event(0x0001, 1, 0, 0, tag) + user32.mouse_event(0x0001, 0xFFFFFFFF, 0, 0, tag) except Exception: pass @@ -557,6 +587,9 @@ if sys.platform == "win32": if _hook_id: user32.UnhookWindowsHookEx(_hook_id) + if _mouse_hook_id: + user32.UnhookWindowsHookEx(_mouse_hook_id) + _mouse_hook_id = None _activity_stop.set() at.join(timeout=1) kernel32.SetThreadExecutionState(ES_CONTINUOUS) # allow display sleep again