531 lines
20 KiB
Python
531 lines
20 KiB
Python
"""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
|
|
# --------------------------------------------------------------------------
|
|
# --------------------------------------------------------------------------
|
|
# Brightness — hybrid: fast native DDC/CI (dxva2) with per-monitor WMI fallback
|
|
# --------------------------------------------------------------------------
|
|
class BrightnessController:
|
|
"""Controls brightness on every monitor. Prefers the native DDC/CI API
|
|
(dxva2.dll, sub-ms) and falls back per monitor to screen_brightness_control
|
|
(WMI) for panels that don't expose DDC/CI (e.g. laptop internal screens)."""
|
|
|
|
PHYSICAL_MONITOR = None # ctypes struct, set on Windows
|
|
|
|
def __init__(self):
|
|
self.targets = [] # [("native", handle, (lo,cur,hi)), ("wmi", idx)]
|
|
self.saved = None
|
|
|
|
@staticmethod
|
|
def _native():
|
|
return sys.platform == "win32" and hasattr(ctypes.windll, "dxva2")
|
|
|
|
def _open_physical(self, hmon):
|
|
dx = ctypes.windll.dxva2
|
|
arr = (BrightnessController.PHYSICAL_MONITOR * 1)()
|
|
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:
|
|
dx = ctypes.windll.dxva2
|
|
return bool(dx.SetMonitorBrightness(int(handle), wt.DWORD(int(value))))
|
|
except Exception as e:
|
|
LOG.warning("native write failed: %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)
|
|
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# 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
|
|
|
|
# --- 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 = []
|
|
_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 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):
|
|
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(bc):
|
|
bc.save()
|
|
bc.dim()
|
|
|
|
|
|
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.
|
|
bc = BrightnessController()
|
|
dim_thread = threading.Thread(target=_dim_and_save, args=(bc,), 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)
|
|
bc.restore()
|
|
LOG.info("exiting")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|