Compare commits
7
Commits
v1-wmi-only
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3286ef3b8 | ||
|
|
d16a2db9ae | ||
|
|
0725915317 | ||
|
|
d3437f924b | ||
|
|
e61ea172d3 | ||
|
|
bca41eadc4 | ||
|
|
d0f6fd8818 |
No files matched your search
@@ -20,7 +20,9 @@ pyinstaller "Black Screen Dim.spec"
|
|||||||
Готовый exe — в `dist\Black Screen Dim\`.
|
Готовый exe — в `dist\Black Screen Dim\`.
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
- `screen-brightness-control` — управление яркостью (только Windows)
|
- Яркость — **гибрид**: сначала быстрый нативный DDC/CI (`dxva2.dll`, суб-миллисекунды),
|
||||||
|
при отсутствии DDC/CI на мониторе (например, встроенный экран ноутбука) — откат на
|
||||||
|
`screen-brightness-control` (WMI) для этого монитора.
|
||||||
- чёрная заливка экрана — напрямую через Win32 API (ctypes, входит в стандартную библиотеку), pygame больше не нужен
|
- чёрная заливка экрана — напрямую через Win32 API (ctypes, входит в стандартную библиотеку), pygame больше не нужен
|
||||||
- лог пишется в `%TEMP%\black_screen_dim.log`
|
- лог пишется в `%TEMP%\black_screen_dim.log`
|
||||||
|
|
||||||
@@ -30,3 +32,11 @@ pyinstaller "Black Screen Dim.spec"
|
|||||||
> При нескольких мониторах чёрный экран накрывает **весь виртуальный рабочий стол**
|
> При нескольких мониторах чёрный экран накрывает **весь виртуальный рабочий стол**
|
||||||
> (все мониторы сразу, включая те, что слева/сверху от основного), а яркость
|
> (все мониторы сразу, включая те, что слева/сверху от основного), а яркость
|
||||||
> гасится и восстанавливается **на каждом** мониторе по отдельности.
|
> гасится и восстанавливается **на каждом** мониторе по отдельности.
|
||||||
|
|
||||||
|
## Откат на WMI-только версию
|
||||||
|
Если гибридная яркость работает некорректно, есть два варианта вернуться к
|
||||||
|
проверенной версии (только WMI):
|
||||||
|
- **Быстрый (на ноуте):** переименуй `black_screen_dim.py.bak` обратно в `black_screen_dim.py`
|
||||||
|
и пересобери exe.
|
||||||
|
- **Через git:** `git -C <repo> checkout v1-wmi-only -- black_screen_dim.py`
|
||||||
|
(тег `v1-wmi-only` указывает на последний стабильный коммит до гибрида).
|
||||||
+277
-41
@@ -45,34 +45,178 @@ def setup_logging():
|
|||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Brightness — every monitor
|
# Brightness — every monitor
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
def get_all_brightness():
|
# --------------------------------------------------------------------------
|
||||||
try:
|
# Brightness — hybrid: fast native DDC/CI (dxva2) with per-monitor WMI fallback
|
||||||
vals = sbc.get_brightness()
|
# --------------------------------------------------------------------------
|
||||||
except Exception as e:
|
class BrightnessController:
|
||||||
LOG.warning("get_brightness failed: %r", e)
|
"""Controls brightness on every monitor. Prefers the native DDC/CI API
|
||||||
return None
|
(dxva2.dll, sub-ms) and falls back per monitor to screen_brightness_control
|
||||||
if isinstance(vals, list):
|
(WMI) for panels that don't expose DDC/CI (e.g. laptop internal screens)."""
|
||||||
return list(vals) if vals else None
|
|
||||||
return [vals]
|
|
||||||
|
|
||||||
|
PHYSICAL_MONITOR = None # ctypes struct, set on Windows
|
||||||
|
|
||||||
def set_all_brightness(value):
|
def __init__(self):
|
||||||
try:
|
self.targets = [] # [("native", handle, (lo,cur,hi)), ("wmi", idx)]
|
||||||
sbc.set_brightness(int(value))
|
self.saved = None
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
LOG.warning("set_brightness(%s) failed: %r", value, e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _native():
|
||||||
|
return sys.platform == "win32" and hasattr(ctypes.windll, "dxva2")
|
||||||
|
|
||||||
def restore_all_brightness(original):
|
def _open_physical(self, hmon):
|
||||||
if not original:
|
dx = ctypes.windll.dxva2
|
||||||
return
|
arr = (BrightnessController.PHYSICAL_MONITOR * 1)()
|
||||||
for i, b in enumerate(original):
|
if not dx.GetPhysicalMonitorsFromHMONITOR(int(hmon), 1, arr):
|
||||||
|
return None
|
||||||
|
return arr[0].hPhysicalMonitor
|
||||||
|
|
||||||
|
def _read_native(self, handle):
|
||||||
|
dx = ctypes.windll.dxva2
|
||||||
|
lo = wt.DWORD(); cur = wt.DWORD(); hi = wt.DWORD()
|
||||||
|
if not dx.GetMonitorBrightness(int(handle), ctypes.byref(lo), ctypes.byref(cur), ctypes.byref(hi)):
|
||||||
|
return None
|
||||||
|
return (lo.value, cur.value, hi.value)
|
||||||
|
|
||||||
|
def _write_native(self, handle, value):
|
||||||
try:
|
try:
|
||||||
sbc.set_brightness(int(b), display=i)
|
dx = ctypes.windll.dxva2
|
||||||
|
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:
|
except Exception as e:
|
||||||
LOG.warning("restore brightness monitor %d failed: %r", i, e)
|
LOG.warning("native write error: %r", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _close_native(self, handle):
|
||||||
|
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 = []
|
||||||
|
if self._native():
|
||||||
|
for i, hmon in enumerate(enum_monitor_handles()):
|
||||||
|
handle = None
|
||||||
|
try:
|
||||||
|
handle = self._open_physical(hmon)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("native open failed: %r", e)
|
||||||
|
handle = None
|
||||||
|
if handle is not None:
|
||||||
|
try:
|
||||||
|
r = self._read_native(handle)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("native read failed: %r", e)
|
||||||
|
r = None
|
||||||
|
if r is not None:
|
||||||
|
self.targets.append(("native", handle, r))
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
self._close_native(handle)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.targets.append(("wmi", i))
|
||||||
|
if not self.targets:
|
||||||
|
LOG.warning("no monitors via native; brightness control disabled")
|
||||||
|
LOG.info("brightness targets: %s", [t[0] for t in self.targets])
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
self._build()
|
||||||
|
values = []
|
||||||
|
for t in self.targets:
|
||||||
|
if t[0] == "native":
|
||||||
|
_, handle, (lo, cur, hi) = t
|
||||||
|
values.append(cur)
|
||||||
|
else:
|
||||||
|
idx = t[1]
|
||||||
|
try:
|
||||||
|
v = sbc.get_brightness(display=idx, method="wmi")
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("wmi get display %d failed: %r", idx, e)
|
||||||
|
v = None
|
||||||
|
if isinstance(v, list):
|
||||||
|
v = v[0] if v else None
|
||||||
|
values.append(v)
|
||||||
|
self.saved = values
|
||||||
|
LOG.info("saved brightness: %s", values)
|
||||||
|
return values
|
||||||
|
|
||||||
|
def dim(self):
|
||||||
|
for t in self.targets:
|
||||||
|
if t[0] == "native":
|
||||||
|
_, handle, (lo, cur, hi) = t
|
||||||
|
ok = self._write_native(handle, lo)
|
||||||
|
LOG.info("native dim -> %s (%s)", lo, "ok" if ok else "FAIL")
|
||||||
|
else:
|
||||||
|
idx = t[1]
|
||||||
|
try:
|
||||||
|
sbc.set_brightness(0, display=idx, method="wmi")
|
||||||
|
LOG.info("wmi dim display %d", idx)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("wmi dim display %d failed: %r", idx, e)
|
||||||
|
|
||||||
|
def restore(self):
|
||||||
|
if self.saved is None:
|
||||||
|
self.close()
|
||||||
|
return
|
||||||
|
for t, val in zip(self.targets, self.saved):
|
||||||
|
if val is None:
|
||||||
|
continue
|
||||||
|
if t[0] == "native":
|
||||||
|
_, handle, _ = t
|
||||||
|
ok = self._write_native(handle, val)
|
||||||
|
LOG.info("native restore %s (%s)", val, "ok" if ok else "FAIL")
|
||||||
|
else:
|
||||||
|
idx = t[1]
|
||||||
|
try:
|
||||||
|
sbc.set_brightness(int(val), display=idx, method="wmi")
|
||||||
|
LOG.info("wmi restore display %d -> %s", idx, val)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("wmi restore display %d failed: %r", idx, e)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
for t in self.targets:
|
||||||
|
if t[0] == "native":
|
||||||
|
try:
|
||||||
|
self._close_native(t[1])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
@@ -101,11 +245,16 @@ 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
|
||||||
TRANSPARENT = 1
|
TRANSPARENT = 1
|
||||||
DEFAULT_GUI_FONT = 17
|
DEFAULT_GUI_FONT = 17
|
||||||
|
ES_CONTINUOUS = 0x80000000
|
||||||
|
ES_SYSTEM_REQUIRED = 0x00000001
|
||||||
|
ES_DISPLAY_REQUIRED = 0x00000002
|
||||||
|
|
||||||
LRESULT = ctypes.c_int64
|
LRESULT = ctypes.c_int64
|
||||||
HMONITOR = ctypes.c_void_p
|
HMONITOR = ctypes.c_void_p
|
||||||
@@ -155,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
|
||||||
@@ -201,6 +359,8 @@ if sys.platform == "win32":
|
|||||||
user32.DestroyWindow.restype = wt.BOOL
|
user32.DestroyWindow.restype = wt.BOOL
|
||||||
user32.ShowCursor.argtypes = [wt.BOOL]
|
user32.ShowCursor.argtypes = [wt.BOOL]
|
||||||
user32.ShowCursor.restype = wt.INT
|
user32.ShowCursor.restype = wt.INT
|
||||||
|
user32.mouse_event.argtypes = [wt.DWORD, wt.DWORD, wt.DWORD, wt.DWORD, ctypes.c_void_p]
|
||||||
|
user32.mouse_event.restype = None
|
||||||
user32.DefWindowProcW.argtypes = [wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM]
|
user32.DefWindowProcW.argtypes = [wt.HWND, wt.UINT, wt.WPARAM, wt.LPARAM]
|
||||||
user32.DefWindowProcW.restype = LRESULT
|
user32.DefWindowProcW.restype = LRESULT
|
||||||
user32.SetProcessDPIAware.argtypes = []
|
user32.SetProcessDPIAware.argtypes = []
|
||||||
@@ -221,11 +381,38 @@ if sys.platform == "win32":
|
|||||||
kernel32.GetModuleHandleW.restype = wt.HINSTANCE
|
kernel32.GetModuleHandleW.restype = wt.HINSTANCE
|
||||||
kernel32.GetLastError.argtypes = []
|
kernel32.GetLastError.argtypes = []
|
||||||
kernel32.GetLastError.restype = wt.DWORD
|
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):
|
||||||
|
_fields_ = [
|
||||||
|
("hPhysicalMonitor", wt.HANDLE),
|
||||||
|
("szPhysicalMonitorDescription", ctypes.c_wchar * 128),
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
dx = ctypes.windll.dxva2
|
||||||
|
dx.GetPhysicalMonitorsFromHMONITOR.argtypes = [
|
||||||
|
HMONITOR, wt.DWORD, ctypes.POINTER(PHYSICAL_MONITOR)]
|
||||||
|
dx.GetPhysicalMonitorsFromHMONITOR.restype = wt.BOOL
|
||||||
|
dx.GetMonitorBrightness.argtypes = [
|
||||||
|
wt.HANDLE, ctypes.POINTER(wt.DWORD),
|
||||||
|
ctypes.POINTER(wt.DWORD), ctypes.POINTER(wt.DWORD)]
|
||||||
|
dx.GetMonitorBrightness.restype = wt.BOOL
|
||||||
|
dx.SetMonitorBrightness.argtypes = [wt.HANDLE, wt.DWORD]
|
||||||
|
dx.SetMonitorBrightness.restype = wt.BOOL
|
||||||
|
dx.DestroyPhysicalMonitors.argtypes = [
|
||||||
|
wt.DWORD, ctypes.POINTER(PHYSICAL_MONITOR)]
|
||||||
|
dx.DestroyPhysicalMonitors.restype = wt.BOOL
|
||||||
|
BrightnessController.PHYSICAL_MONITOR = PHYSICAL_MONITOR
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("dxva2 init failed: %r", e)
|
||||||
|
|
||||||
_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 = []
|
||||||
@@ -238,6 +425,16 @@ if sys.platform == "win32":
|
|||||||
user32.EnumDisplayMonitors(None, None, MONITORENUMPROC(cb), 0)
|
user32.EnumDisplayMonitors(None, None, MONITORENUMPROC(cb), 0)
|
||||||
return rects
|
return rects
|
||||||
|
|
||||||
|
def enum_monitor_handles():
|
||||||
|
handles = []
|
||||||
|
|
||||||
|
def cb(hmon, hdc, lprect, lparam):
|
||||||
|
handles.append(hmon)
|
||||||
|
return True
|
||||||
|
|
||||||
|
user32.EnumDisplayMonitors(None, None, MONITORENUMPROC(cb), 0)
|
||||||
|
return handles
|
||||||
|
|
||||||
def _wndproc(hwnd, msg, wparam, lparam):
|
def _wndproc(hwnd, msg, wparam, lparam):
|
||||||
global _hint_active
|
global _hint_active
|
||||||
if msg == WM_PAINT:
|
if msg == WM_PAINT:
|
||||||
@@ -270,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:
|
||||||
@@ -336,11 +546,39 @@ 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
|
||||||
|
# make ESC's first press get swallowed by the wake and a ~30s DDC hang)
|
||||||
|
sees = kernel32.SetThreadExecutionState(
|
||||||
|
ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED
|
||||||
|
)
|
||||||
|
LOG.info("SetThreadExecutionState -> %s", sees)
|
||||||
|
|
||||||
|
# belt-and-suspenders: emit a tiny (invisible) mouse move every 30s so the
|
||||||
|
# OS and any monitor hardware idle timer never trip during long idle.
|
||||||
|
_activity_stop = threading.Event()
|
||||||
|
|
||||||
|
def _activity():
|
||||||
|
while not _activity_stop.wait(30):
|
||||||
|
try:
|
||||||
|
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
|
||||||
|
|
||||||
|
at = threading.Thread(target=_activity, daemon=True)
|
||||||
|
at.start()
|
||||||
|
|
||||||
msg = wt.MSG()
|
msg = wt.MSG()
|
||||||
while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) > 0:
|
while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) > 0:
|
||||||
@@ -349,6 +587,12 @@ 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()
|
||||||
|
at.join(timeout=1)
|
||||||
|
kernel32.SetThreadExecutionState(ES_CONTINUOUS) # allow display sleep again
|
||||||
for h in _hwnds:
|
for h in _hwnds:
|
||||||
user32.DestroyWindow(h)
|
user32.DestroyWindow(h)
|
||||||
# restore cursor
|
# restore cursor
|
||||||
@@ -357,15 +601,9 @@ if sys.platform == "win32":
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def _dim_and_save():
|
def _dim_and_save(bc):
|
||||||
"""Save current brightness and dim all monitors in the background so the
|
bc.save()
|
||||||
black overlay can appear instantly; the dimming itself is invisible."""
|
bc.dim()
|
||||||
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():
|
def main():
|
||||||
@@ -373,7 +611,8 @@ def main():
|
|||||||
LOG.info("starting Black Screen Dim")
|
LOG.info("starting Black Screen Dim")
|
||||||
# Show the black overlay FIRST so the screen goes black immediately.
|
# Show the black overlay FIRST so the screen goes black immediately.
|
||||||
# Brightness saving/dimming runs in the background, invisible behind it.
|
# Brightness saving/dimming runs in the background, invisible behind it.
|
||||||
dim_thread = threading.Thread(target=_dim_and_save, daemon=True)
|
bc = BrightnessController()
|
||||||
|
dim_thread = threading.Thread(target=_dim_and_save, args=(bc,), daemon=True)
|
||||||
dim_thread.start()
|
dim_thread.start()
|
||||||
try:
|
try:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
@@ -384,10 +623,7 @@ def main():
|
|||||||
LOG.exception("overlay error: %r", e)
|
LOG.exception("overlay error: %r", e)
|
||||||
finally:
|
finally:
|
||||||
dim_thread.join(timeout=10)
|
dim_thread.join(timeout=10)
|
||||||
orig = _original_brightness
|
bc.restore()
|
||||||
if orig is not None:
|
|
||||||
restore_all_brightness(orig)
|
|
||||||
LOG.info("restored brightness: %s", orig)
|
|
||||||
LOG.info("exiting")
|
LOG.info("exiting")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user