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
This commit is contained in:
Hermes Agent committed 2026-09-16 16:02:34 +02:00
1 parent 1e86958277
commit 87bdbd7226
3 files changed
+108 -30

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@ a = Analysis(
['black_screen_dim.py'],
pathex=[],
binaries=[],
datas=[],
datas=[('icon.ico', '.')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
+4
View File
@@ -25,3 +25,7 @@ pyinstaller "Black Screen Dim.spec"
> Управление яркостью работает только на Windows. На других ОС приложение
> просто покажет чёрный экран без изменения яркости.
> При нескольких мониторах чёрный экран накрывает **весь виртуальный рабочий стол**
> (все мониторы сразу, включая те, что слева/сверху от основного), а яркость
> гасится и восстанавливается **на каждом** мониторе по отдельности.
+103 -29
View File
@@ -1,24 +1,41 @@
import pygame
import sys
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 get_current_brightness():
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:
current = sbc.get_brightness()
vals = sbc.get_brightness()
except Exception:
return None
if isinstance(current, list):
if not current:
return None
return current[0]
return current
if isinstance(vals, list):
return list(vals) if vals else None
return [vals]
def set_brightness_safe(value):
def set_all_brightness(value):
"""Set brightness on ALL monitors at once."""
try:
sbc.set_brightness(int(value))
return True
@@ -26,35 +43,95 @@ def set_brightness_safe(value):
return False
def main():
# --- яркость: читаем текущую и ставим минимум ---
original_brightness = get_current_brightness()
if original_brightness is not None:
set_brightness_safe(0)
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()
info = pygame.display.Info()
width, height = info.current_w, info.current_h
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
os.environ["SDL_VIDEO_WINDOW_POS"] = "0,0"
screen = pygame.display.set_mode((width, height), pygame.NOFRAME)
screen = pygame.display.set_mode((w, h), pygame.NOFRAME)
pygame.display.set_caption("Black Screen Dim")
try:
icon = pygame.image.load("icon.ico")
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=(width // 2, height // 2))
show_hint_until = time.time() + 1.0 # ~1 секунда
text_rect = text_surf.get_rect(center=(w // 2, h // 2))
show_hint_until = time.time() + 1.0 # ~1 second
running = True
while running:
@@ -70,12 +147,9 @@ def main():
pygame.display.flip()
# --- выходим: пытаемся вернуть яркость и курсор, без вылета ---
# --- exit: restore every monitor's brightness and the cursor ---
if original_brightness is not None:
try:
set_brightness_safe(original_brightness)
except Exception:
pass
restore_all_brightness(original_brightness)
try:
pygame.mouse.set_visible(True)