Files
black-screen/black_screen_dim.py
T
Hermes Agent 87bdbd7226 Multi-monitor support: black overlay covers all displays, brightness dim/restore per monitor
- get_virtual_screen_rect(): bounds of the whole virtual desktop (negative x/y OK)
- window pinned topmost across every monitor via SetWindowPos + SetForegroundWindow
- brightness saved/restored as a per-monitor list (was only first monitor)
- resource_path() so icon.ico is found in the frozen exe; spec now bundles it
2026-09-16 16:02:34 +02:00

165 lines
5.0 KiB
Python

import os
import sys
import time
import pygame
import screen_brightness_control as sbc
# Windows-only calls are guarded so the module still imports on other OSes.
try:
import ctypes
from ctypes import wintypes
except Exception:
ctypes = None
def resource_path(rel):
"""Absolute path to a bundled resource, works both from source and from a
PyInstaller-frozen exe (sys._MEIPASS)."""
base = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base, rel)
# ---------------------------------------------------------------------------
# Brightness — handle EVERY monitor, not just the first one
# ---------------------------------------------------------------------------
def get_all_brightness():
"""Return a list with one brightness value per monitor, or None on failure."""
try:
vals = sbc.get_brightness()
except Exception:
return None
if isinstance(vals, list):
return list(vals) if vals else None
return [vals]
def set_all_brightness(value):
"""Set brightness on ALL monitors at once."""
try:
sbc.set_brightness(int(value))
return True
except Exception:
return False
def restore_all_brightness(original):
"""Restore each monitor to its previously saved brightness."""
if not original:
return
for i, b in enumerate(original):
try:
sbc.set_brightness(int(b), display=i)
except Exception:
pass
# ---------------------------------------------------------------------------
# Window geometry — cover the whole virtual desktop (all monitors)
# ---------------------------------------------------------------------------
def get_virtual_screen_rect():
"""Bounding rectangle of all monitors combined (may start at negative x/y)."""
user32 = ctypes.windll.user32
try:
user32.SetProcessDPIAware()
except Exception:
pass
SM_XVIRTUALSCREEN = 76
SM_YVIRTUALSCREEN = 77
SM_CXVIRTUALSCREEN = 78
SM_CYVIRTUALSCREEN = 79
x = user32.GetSystemMetrics(SM_XVIRTUALSCREEN)
y = user32.GetSystemMetrics(SM_YVIRTUALSCREEN)
w = user32.GetSystemMetrics(SM_CXVIRTUALSCREEN)
h = user32.GetSystemMetrics(SM_CYVIRTUALSCREEN)
if w <= 0 or h <= 0: # fallback to primary monitor
w = user32.GetSystemMetrics(0) # SM_CXSCREEN
h = user32.GetSystemMetrics(1) # SM_CYSCREEN
x = y = 0
return x, y, w, h
def make_window_cover_all_monitors(hwnd, rect):
"""Move/resize the window to the virtual-screen rect and pin it topmost so it
sits above the taskbar and every monitor."""
user32 = ctypes.windll.user32
HWND_TOPMOST = -1
x, y, w, h = rect
user32.SetWindowPos(int(hwnd), HWND_TOPMOST, x, y, w, h, 0)
try:
user32.SetForegroundWindow(int(hwnd))
except Exception:
pass
def main():
# --- brightness: save every monitor, then dim them all to 0 ---
original_brightness = get_all_brightness()
if original_brightness is not None:
set_all_brightness(0)
# --- black screen over the whole virtual desktop ---
pygame.init()
if sys.platform == "win32" and ctypes is not None:
x, y, w, h = get_virtual_screen_rect()
else:
info = pygame.display.Info()
x, y, w, h = 0, 0, info.current_w, info.current_h
screen = pygame.display.set_mode((w, h), pygame.NOFRAME)
pygame.display.set_caption("Black Screen Dim")
try:
icon = pygame.image.load(resource_path("icon.ico"))
pygame.display.set_icon(icon)
except Exception:
pass
pygame.mouse.set_visible(False)
# Pin the borderless window across all monitors and bring it to front.
if sys.platform == "win32" and ctypes is not None:
try:
hwnd = pygame.display.get_wm_info().get("window")
if hwnd:
make_window_cover_all_monitors(hwnd, (x, y, w, h))
except Exception:
pass
font = pygame.font.SysFont(None, 48)
hint_text = "ESC - exit"
text_surf = font.render(hint_text, True, (255, 255, 255))
text_rect = text_surf.get_rect(center=(w // 2, h // 2))
show_hint_until = time.time() + 1.0 # ~1 second
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
screen.fill((0, 0, 0))
if time.time() < show_hint_until:
screen.blit(text_surf, text_rect)
pygame.display.flip()
# --- exit: restore every monitor's brightness and the cursor ---
if original_brightness is not None:
restore_all_brightness(original_brightness)
try:
pygame.mouse.set_visible(True)
except Exception:
pass
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()