Hybrid brightness: native DDC/CI (dxva2) with per-monitor WMI fallback

- BrightnessController enumerates monitors, opens each via dxva2 and reads/writes
  brightness directly (sub-ms); falls back per monitor to screen_brightness_control
  (WMI) for panels without DDC/CI (laptop internal screens)
- native dim sets each monitor to its minimum; restore sets saved value
- tagged v1-wmi-only before this change as safe rollback point
This commit is contained in:
Hermes Agent committed 2026-09-16 16:57:18 +02:00
1 parent dc2f754f86
commit d0f6fd8818
1 file changed
+171 -36
+171 -36
View File
@@ -45,34 +45,142 @@ def setup_logging():
# --------------------------------------------------------------------------
# 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]
# --------------------------------------------------------------------------
# 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 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 __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 restore_all_brightness(original):
if not original:
return
for i, b in enumerate(original):
def _open_physical(self, hmon):
dx = ctypes.windll.dxva2
arr = (BrightnessController.PHYSICAL_MONITOR * 1)()
if not dx.GetPhysicalMonitorsFromHMONITOR(int(hmon), 1, ctypes.byref(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:
sbc.set_brightness(int(b), display=i)
dx = ctypes.windll.dxva2
return bool(dx.SetMonitorBrightness(int(handle), wt.DWORD(int(value))))
except Exception as e:
LOG.warning("restore brightness monitor %d failed: %r", i, 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, ctypes.byref(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
# --------------------------------------------------------------------------
@@ -222,6 +330,31 @@ if sys.platform == "win32":
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
@@ -238,6 +371,16 @@ if sys.platform == "win32":
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:
@@ -357,15 +500,9 @@ if sys.platform == "win32":
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 _dim_and_save(bc):
bc.save()
bc.dim()
def main():
@@ -373,7 +510,8 @@ def main():
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)
bc = BrightnessController()
dim_thread = threading.Thread(target=_dim_and_save, args=(bc,), daemon=True)
dim_thread.start()
try:
if sys.platform == "win32":
@@ -384,10 +522,7 @@ def main():
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)
bc.restore()
LOG.info("exiting")