diff --git a/black_screen_dim.py b/black_screen_dim.py index 8bde989..68d3d60 100644 --- a/black_screen_dim.py +++ b/black_screen_dim.py @@ -80,16 +80,52 @@ class BrightnessController: def _write_native(self, handle, value): try: dx = ctypes.windll.dxva2 - return bool(dx.SetMonitorBrightness(int(handle), wt.DWORD(int(value)))) + res = {} + + def _t(): + try: + res["v"] = bool( + dx.SetMonitorBrightness(int(handle), wt.DWORD(int(value))) + ) + except Exception as e: + res["e"] = e + + th = threading.Thread(target=_t, daemon=True) + th.start() + th.join(timeout=2.0) + if th.is_alive(): + LOG.warning("native SetMonitorBrightness timed out (monitor in standby?)") + return False + if "e" in res: + LOG.warning("native write failed: %r", res["e"]) + return False + return res.get("v", False) except Exception as e: - LOG.warning("native write failed: %r", e) + LOG.warning("native write error: %r", e) return False def _close_native(self, handle): - dx = ctypes.windll.dxva2 - arr = (BrightnessController.PHYSICAL_MONITOR * 1)() - arr[0].hPhysicalMonitor = handle - dx.DestroyPhysicalMonitors(1, arr) + try: + dx = ctypes.windll.dxva2 + arr = (BrightnessController.PHYSICAL_MONITOR * 1)() + arr[0].hPhysicalMonitor = handle + res = {} + + def _t(): + try: + res["v"] = dx.DestroyPhysicalMonitors(1, arr) + except Exception as e: + res["e"] = e + + th = threading.Thread(target=_t, daemon=True) + th.start() + th.join(timeout=2.0) + if th.is_alive(): + return + if "e" in res: + LOG.warning("native close failed: %r", res["e"]) + except Exception as e: + LOG.warning("native close error: %r", e) def _build(self): self.targets = [] @@ -214,6 +250,9 @@ if sys.platform == "win32": DT_SINGLELINE = 0x00000020 TRANSPARENT = 1 DEFAULT_GUI_FONT = 17 + ES_CONTINUOUS = 0x80000000 + ES_SYSTEM_REQUIRED = 0x00000001 + ES_DISPLAY_REQUIRED = 0x00000002 LRESULT = ctypes.c_int64 HMONITOR = ctypes.c_void_p @@ -329,6 +368,8 @@ if sys.platform == "win32": kernel32.GetModuleHandleW.restype = wt.HINSTANCE kernel32.GetLastError.argtypes = [] kernel32.GetLastError.restype = wt.DWORD + kernel32.SetThreadExecutionState.argtypes = [wt.DWORD] + kernel32.SetThreadExecutionState.restype = wt.DWORD # --- native DDC/CI brightness (dxva2) --- class PHYSICAL_MONITOR(ctypes.Structure): @@ -485,6 +526,13 @@ if sys.platform == "win32": if not _hook_id: LOG.warning("SetWindowsHookExW failed: %d", kernel32.GetLastError()) + # keep display awake while running: prevents the monitor from entering + # standby during idle, which would make the DDC/CI brightness restore on + # exit block/hang (often ~30s) waiting for the monitor to wake. + kernel32.SetThreadExecutionState( + ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED + ) + msg = wt.MSG() while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) > 0: user32.TranslateMessage(ctypes.byref(msg)) @@ -492,6 +540,7 @@ if sys.platform == "win32": if _hook_id: user32.UnhookWindowsHookEx(_hook_id) + kernel32.SetThreadExecutionState(ES_CONTINUOUS) # allow display sleep again for h in _hwnds: user32.DestroyWindow(h) # restore cursor