"""Black Screen Dim — гасит все мониторы чёрным безрамочным окном (Windows). Для КАЖДОГО подключённого монитора создаётся отдельное borderless topmost окно, плотно закрывающее монитор целиком (по его rcMonitor, в device-координатах). Яркость гасится и восстанавливается на ВСЕХ мониторах. ESC (глобально, через low-level keyboard hook) — выход. Подробный лог пишется в %TEMP%\\black_screen_dim.log. """ import ctypes import ctypes.wintypes as wt import logging import os import sys import tempfile import threading import screen_brightness_control as sbc _original_brightness = None LOG = logging.getLogger("black_screen_dim") # -------------------------------------------------------------------------- # Logging # -------------------------------------------------------------------------- def setup_logging(): log_path = os.path.join(tempfile.gettempdir(), "black_screen_dim.log") try: logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.FileHandler(log_path, encoding="utf-8"), logging.StreamHandler(sys.stderr), ], ) except Exception: logging.basicConfig(level=logging.INFO) LOG.info("log file: %s", log_path) return log_path # -------------------------------------------------------------------------- # Brightness — every monitor # -------------------------------------------------------------------------- def get_all_brightness(): try: vals = sbc.get_brightness() except Exception as e: LOG.warning("get_brightness failed: %r", e) return None if isinstance(vals, list): return list(vals) if vals else None return [vals] def set_all_brightness(value): try: sbc.set_brightness(int(value)) return True except Exception as e: LOG.warning("set_brightness(%s) failed: %r", value, e) return False def restore_all_brightness(original): if not original: return for i, b in enumerate(original): try: sbc.set_brightness(int(b), display=i) except Exception as e: LOG.warning("restore brightness monitor %d failed: %r", i, e) # -------------------------------------------------------------------------- # Win32 overlay — one borderless topmost window per monitor # -------------------------------------------------------------------------- if sys.platform == "win32": user32 = ctypes.windll.user32 gdi32 = ctypes.windll.gdi32 kernel32 = ctypes.windll.kernel32 try: shcore = ctypes.windll.shcore except Exception: shcore = None WS_POPUP = 0x80000000 WS_VISIBLE = 0x10000000 WS_EX_TOPMOST = 0x00000008 WS_EX_TOOLWINDOW = 0x00000080 SWP_SHOWWINDOW = 0x0040 HWND_TOPMOST = -1 WM_PAINT = 0x000F WM_ERASEBKGND = 0x0014 WM_DESTROY = 0x0002 WM_CLOSE = 0x0010 WM_TIMER = 0x0113 WM_KEYDOWN_LL = 0x0100 VK_ESCAPE = 0x1B WH_KEYBOARD_LL = 13 DT_CENTER = 0x00000001 DT_VCENTER = 0x00000004 DT_SINGLELINE = 0x00000020 TRANSPARENT = 1 DEFAULT_GUI_FONT = 17 LRESULT = ctypes.c_int64 HMONITOR = ctypes.c_void_p HHOOK = ctypes.c_void_p HCURSOR = ctypes.c_void_p ULONG_PTR = ctypes.c_void_p UINT_PTR = ctypes.c_void_p WNDPROC = ctypes.WINFUNCTYPE(LRESULT, wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM) MONITORENUMPROC = ctypes.WINFUNCTYPE( wt.BOOL, HMONITOR, wt.HDC, ctypes.POINTER(wt.RECT), wt.LPARAM ) HOOKPROC = ctypes.WINFUNCTYPE(LRESULT, ctypes.c_int, wt.WPARAM, wt.LPARAM) class WNDCLASSEXW(ctypes.Structure): _fields_ = [ ("cbSize", wt.UINT), ("style", wt.UINT), ("lpfnWndProc", WNDPROC), ("cbClsExtra", wt.INT), ("cbWndExtra", wt.INT), ("hInstance", wt.HINSTANCE), ("hIcon", wt.HICON), ("hCursor", HCURSOR), ("hbrBackground", wt.HBRUSH), ("lpszMenuName", wt.LPCWSTR), ("lpszClassName", wt.LPCWSTR), ("hIconSm", wt.HICON), ] class PAINTSTRUCT(ctypes.Structure): _fields_ = [ ("hdc", wt.HDC), ("fErase", wt.BOOL), ("rcPaint", wt.RECT), ("fRestore", wt.BOOL), ("fIncUpdate", wt.BOOL), ("rgbReserved", wt.BYTE * 32), ] class KBDLLHOOKSTRUCT(ctypes.Structure): _fields_ = [ ("vkCode", wt.DWORD), ("scanCode", 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 user32.RegisterClassExW.argtypes = [ctypes.POINTER(WNDCLASSEXW)] user32.RegisterClassExW.restype = wt.ATOM user32.CreateWindowExW.argtypes = [ wt.DWORD, wt.LPCWSTR, wt.LPCWSTR, wt.DWORD, wt.INT, wt.INT, wt.INT, wt.INT, wt.HWND, wt.HMENU, wt.HINSTANCE, ctypes.c_void_p, ] user32.CreateWindowExW.restype = wt.HWND user32.ShowWindow.argtypes = [wt.HWND, wt.INT] user32.ShowWindow.restype = wt.BOOL user32.UpdateWindow.argtypes = [wt.HWND] user32.UpdateWindow.restype = wt.BOOL user32.SetWindowPos.argtypes = [wt.HWND, wt.HWND, wt.INT, wt.INT, wt.INT, wt.INT, wt.UINT] user32.SetWindowPos.restype = wt.BOOL user32.SetTimer.argtypes = [wt.HWND, UINT_PTR, wt.UINT, ctypes.c_void_p] user32.SetTimer.restype = UINT_PTR user32.KillTimer.argtypes = [wt.HWND, UINT_PTR] user32.KillTimer.restype = wt.BOOL user32.GetClientRect.argtypes = [wt.HWND, ctypes.POINTER(wt.RECT)] user32.GetClientRect.restype = wt.BOOL user32.BeginPaint.argtypes = [wt.HWND, ctypes.POINTER(PAINTSTRUCT)] user32.BeginPaint.restype = wt.HDC user32.EndPaint.argtypes = [wt.HWND, ctypes.POINTER(PAINTSTRUCT)] user32.EndPaint.restype = wt.BOOL user32.InvalidateRect.argtypes = [wt.HWND, ctypes.POINTER(wt.RECT), wt.BOOL] user32.InvalidateRect.restype = wt.BOOL user32.PostQuitMessage.argtypes = [wt.INT] user32.PostQuitMessage.restype = None user32.GetMessageW.argtypes = [ctypes.POINTER(wt.MSG), wt.HWND, wt.UINT, wt.UINT] user32.GetMessageW.restype = wt.LONG user32.TranslateMessage.argtypes = [ctypes.POINTER(wt.MSG)] user32.TranslateMessage.restype = wt.BOOL user32.DispatchMessageW.argtypes = [ctypes.POINTER(wt.MSG)] user32.DispatchMessageW.restype = wt.LONG user32.SetWindowsHookExW.argtypes = [wt.INT, HOOKPROC, wt.HINSTANCE, wt.DWORD] user32.SetWindowsHookExW.restype = HHOOK user32.CallNextHookEx.argtypes = [HHOOK, ctypes.c_int, wt.WPARAM, wt.LPARAM] user32.CallNextHookEx.restype = LRESULT user32.UnhookWindowsHookEx.argtypes = [HHOOK] user32.UnhookWindowsHookEx.restype = wt.BOOL user32.DestroyWindow.argtypes = [wt.HWND] user32.DestroyWindow.restype = wt.BOOL user32.ShowCursor.argtypes = [wt.BOOL] user32.ShowCursor.restype = wt.INT user32.DefWindowProcW.argtypes = [wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM] user32.DefWindowProcW.restype = LRESULT user32.SetProcessDPIAware.argtypes = [] user32.SetProcessDPIAware.restype = wt.BOOL gdi32.CreateSolidBrush.argtypes = [wt.COLORREF] gdi32.DeleteObject.restype = wt.BOOL gdi32.SetTextColor.argtypes = [wt.HDC, wt.COLORREF] gdi32.SetTextColor.restype = wt.COLORREF gdi32.SetBkMode.argtypes = [wt.HDC, wt.INT] gdi32.SetBkMode.restype = wt.INT gdi32.GetStockObject.argtypes = [wt.INT] gdi32.GetStockObject.restype = wt.HGDIOBJ gdi32.SelectObject.argtypes = [wt.HDC, wt.HGDIOBJ] gdi32.SelectObject.restype = wt.HGDIOBJ user32.DrawTextW.argtypes = [wt.HDC, wt.LPCWSTR, wt.INT, ctypes.POINTER(wt.RECT), wt.UINT] user32.DrawTextW.restype = wt.INT kernel32.GetModuleHandleW.argtypes = [wt.LPCWSTR] kernel32.GetModuleHandleW.restype = wt.HINSTANCE kernel32.GetLastError.argtypes = [] kernel32.GetLastError.restype = wt.DWORD _hwnds = [] _hint_active = True _hook_id = None _hint_text = "ESC - exit" def enum_monitor_rects(): rects = [] def cb(hmon, hdc, lprect, lparam): r = lprect.contents rects.append((r.left, r.top, r.right, r.bottom)) return True user32.EnumDisplayMonitors(None, None, MONITORENUMPROC(cb), 0) return rects def _wndproc(hwnd, msg, wparam, lparam): global _hint_active if msg == WM_PAINT: # Background (black) is erased by DefWindowProc via the class brush. ps = PAINTSTRUCT() hdc = user32.BeginPaint(hwnd, ctypes.byref(ps)) if _hint_active: gdi32.SetTextColor(hdc, 0xFFFFFF) gdi32.SetBkMode(hdc, TRANSPARENT) font = gdi32.GetStockObject(DEFAULT_GUI_FONT) old = gdi32.SelectObject(hdc, font) buf = ctypes.create_unicode_buffer(_hint_text) user32.DrawTextW( hdc, buf, -1, ctypes.byref(ps.rcPaint), DT_CENTER | DT_VCENTER | DT_SINGLELINE, ) if old: gdi32.SelectObject(hdc, old) user32.EndPaint(hwnd, ctypes.byref(ps)) return 0 if msg == WM_TIMER: _hint_active = False user32.KillTimer(hwnd, 1) for h in _hwnds: user32.InvalidateRect(h, None, True) return 0 if msg in (WM_DESTROY, WM_CLOSE): user32.PostQuitMessage(0) return 0 return user32.DefWindowProcW(hwnd, msg, wparam, lparam) def _low_level_keyboard_proc(nCode, wParam, lParam): if nCode >= 0 and wParam == WM_KEYDOWN_LL: kb = ctypes.cast(lParam, ctypes.POINTER(KBDLLHOOKSTRUCT)).contents if kb.vkCode == VK_ESCAPE: LOG.info("ESC pressed -> exit") user32.PostQuitMessage(0) return user32.CallNextHookEx(_hook_id, nCode, wParam, lParam) def run_overlay(): global _hwnds, _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: try: shcore.SetProcessDpiAwareness(2) # PROCESS_PER_MONITOR_DPI_AWARE_V2 except Exception as e: LOG.warning("SetProcessDpiAwareness failed: %r", e) user32.SetProcessDPIAware() else: user32.SetProcessDPIAware() rects = enum_monitor_rects() LOG.info("monitors found: %s", rects) if not rects: LOG.warning("no monitors enumerated, falling back to primary") w = user32.GetSystemMetrics(0) # SM_CXSCREEN h = user32.GetSystemMetrics(1) # SM_CYSCREEN rects = [(0, 0, w, h)] wndproc = WNDPROC(_wndproc) cls_name = "BlackScreenDimCls" hinst = kernel32.GetModuleHandleW(None) wc = WNDCLASSEXW() wc.cbSize = ctypes.sizeof(WNDCLASSEXW) wc.lpfnWndProc = wndproc wc.hInstance = hinst wc.hbrBackground = gdi32.CreateSolidBrush(0x000000) wc.lpszClassName = cls_name if not user32.RegisterClassExW(ctypes.byref(wc)): LOG.error("RegisterClassExW failed: %d", kernel32.GetLastError()) return for (left, top, right, bottom) in rects: w = right - left h = bottom - top hwnd = user32.CreateWindowExW( WS_EX_TOPMOST | WS_EX_TOOLWINDOW, cls_name, "Black Screen Dim", WS_POPUP | WS_VISIBLE, left, top, w, h, 0, 0, hinst, 0, ) if not hwnd: LOG.error("CreateWindowExW failed for %s: %d", (left, top, right, bottom), kernel32.GetLastError()) continue user32.ShowWindow(hwnd, 5) # SW_SHOW user32.UpdateWindow(hwnd) user32.SetWindowPos(hwnd, HWND_TOPMOST, left, top, w, h, SWP_SHOWWINDOW) user32.SetTimer(hwnd, 1, 1000, None) _hwnds.append(hwnd) LOG.info("created %d overlay window(s)", len(_hwnds)) # hide cursor for _ in range(12): if user32.ShowCursor(False) < 0: break # global ESC hook 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()) msg = wt.MSG() while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) > 0: user32.TranslateMessage(ctypes.byref(msg)) user32.DispatchMessageW(ctypes.byref(msg)) if _hook_id: user32.UnhookWindowsHookEx(_hook_id) for h in _hwnds: user32.DestroyWindow(h) # restore cursor for _ in range(12): if user32.ShowCursor(True) >= 0: break def _dim_and_save(): """Save current brightness and dim all monitors in the background so the black overlay can appear instantly; the dimming itself is invisible.""" global _original_brightness orig = get_all_brightness() _original_brightness = orig LOG.info("saved brightness: %s", orig) if orig is not None: set_all_brightness(0) def main(): setup_logging() LOG.info("starting Black Screen Dim") # Show the black overlay FIRST so the screen goes black immediately. # Brightness saving/dimming runs in the background, invisible behind it. dim_thread = threading.Thread(target=_dim_and_save, daemon=True) dim_thread.start() try: if sys.platform == "win32": run_overlay() else: LOG.warning("non-Windows platform; nothing to display") except Exception as e: LOG.exception("overlay error: %r", e) finally: dim_thread.join(timeout=10) orig = _original_brightness if orig is not None: restore_all_brightness(orig) LOG.info("restored brightness: %s", orig) LOG.info("exiting") if __name__ == "__main__": main()