diff --git a/Black Screen Dim.spec b/Black Screen Dim.spec index 3cc3add..91ab276 100644 --- a/Black Screen Dim.spec +++ b/Black Screen Dim.spec @@ -5,7 +5,7 @@ a = Analysis( ['black_screen_dim.py'], pathex=[], binaries=[], - datas=[], + datas=[('icon.ico', '.')], hiddenimports=[], hookspath=[], hooksconfig={}, diff --git a/README.md b/README.md index 12b9ca0..73533b2 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,7 @@ pyinstaller "Black Screen Dim.spec" > Управление яркостью работает только на Windows. На других ОС приложение > просто покажет чёрный экран без изменения яркости. + +> При нескольких мониторах чёрный экран накрывает **весь виртуальный рабочий стол** +> (все мониторы сразу, включая те, что слева/сверху от основного), а яркость +> гасится и восстанавливается **на каждом** мониторе по отдельности. diff --git a/black_screen_dim.py b/black_screen_dim.py index 3d53bef..3e25e2d 100644 --- a/black_screen_dim.py +++ b/black_screen_dim.py @@ -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)