From cfd7b3740d304054de92dfec8d8dac9c87bd22a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8Jannick?= <¨jannick.richter@web.de¨> Date: Thu, 5 Feb 2026 22:24:56 +0100 Subject: [PATCH] feat: per-monitor pause when maximized/fullscreen --- src/player/video_player.py | 35 +++++++++++++++----- src/utils.py | 68 +++++++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/src/player/video_player.py b/src/player/video_player.py index e15406b..564342a 100644 --- a/src/player/video_player.py +++ b/src/player/video_player.py @@ -313,6 +313,7 @@ def __init__(self, *args, **kwargs): # Handler should be created after everything initialized self.active_handler, self.window_handler = None, None self.is_any_maximized, self.is_any_fullscreen = False, False + self.monitor_states = {} # Per-monitor maximized/fullscreen state self.is_paused_by_user = False def new_window(self, gdk_monitor): @@ -337,14 +338,25 @@ def _on_active_changed(self, active): self.pause_playback() def _on_window_state_changed(self, state): - self.is_any_maximized, self.is_any_fullscreen = state["is_any_maximized"], state["is_any_fullscreen"] - logger.info(f"is_any_maximized: {self.is_any_maximized}, is_any_fullscreen: {self.is_any_fullscreen}") + self.is_any_maximized = state["is_any_maximized"] + self.is_any_fullscreen = state["is_any_fullscreen"] + self.monitor_states = state.get("monitor_states", {}) + logger.info(f"Window state changed: {state}") if self.config[CONFIG_KEY_PAUSE_WHEN_MAXIMIZED]: - if self._should_playback_start(): - self.start_playback() - else: - self.pause_playback() + # Per-monitor pause/play logic + for monitor, window in self.windows.items(): + monitor_name = monitor.get_model() + mon_state = self.monitor_states.get(monitor_name, {}) + + if mon_state.get("maximized") or mon_state.get("fullscreen"): + window.pause_fade(fade_duration_sec=self.config[CONFIG_KEY_FADE_DURATION_SEC], + fade_interval=self.config[CONFIG_KEY_FADE_INTERVAL]) + else: + if not self.is_paused_by_user: + window.play_fade(target=self.volume if monitor.is_primary() else 0, + fade_duration_sec=self.config[CONFIG_KEY_FADE_DURATION_SEC], + fade_interval=self.config[CONFIG_KEY_FADE_INTERVAL]) elif self.config[CONFIG_KEY_MUTE_WHEN_MAXIMIZED]: for monitor, window in self.windows.items(): if not monitor.is_primary(): @@ -356,11 +368,16 @@ def _on_window_state_changed(self, state): window.volume_fade(target=self.volume, fade_duration_sec=self.config[CONFIG_KEY_FADE_DURATION_SEC], fade_interval=self.config[CONFIG_KEY_FADE_INTERVAL]) - def _should_playback_start(self): - if self.config[CONFIG_KEY_PAUSE_WHEN_MAXIMIZED] and (self.is_any_maximized or self.is_any_fullscreen): - return False + def _should_playback_start(self, monitor_name=None): + """Check if playback should start. If monitor_name given, check that specific monitor.""" if self.is_paused_by_user: return False + if self.config[CONFIG_KEY_PAUSE_WHEN_MAXIMIZED]: + if monitor_name and hasattr(self, 'monitor_states'): + mon_state = self.monitor_states.get(monitor_name, {}) + return not (mon_state.get("maximized") or mon_state.get("fullscreen")) + # Fallback to global check + return not (self.is_any_maximized or self.is_any_fullscreen) return True @property diff --git a/src/utils.py b/src/utils.py index 5fce19c..3d00b6d 100644 --- a/src/utils.py +++ b/src/utils.py @@ -5,7 +5,7 @@ import gi gi.require_version("Wnck", "3.0") -from gi.repository import Gio, GLib, Wnck # , Gdk +from gi.repository import Gio, GLib, Wnck, Gdk import pydbus @@ -287,6 +287,9 @@ def __init__(self, on_window_state_changed: callable): self.signal_handlers = [] self.window_signal_handlers = {} + # Cache monitor geometries for window-to-monitor matching + self.monitor_geometries = self._get_monitor_geometries() + # Connect screen signals and store handler IDs handler_id = self.screen.connect("window-opened", self.window_opened, None) self.signal_handlers.append((self.screen, handler_id)) @@ -305,6 +308,30 @@ def __init__(self, on_window_state_changed: callable): # Initial check self.eval() + def _get_monitor_geometries(self): + """Get all monitor geometries as dict {name: (x, y, width, height)}""" + display = Gdk.Display.get_default() + geometries = {} + for i in range(display.get_n_monitors()): + monitor = display.get_monitor(i) + rect = monitor.get_geometry() + geometries[monitor.get_model()] = (rect.x, rect.y, rect.width, rect.height) + return geometries + + def _get_window_monitor(self, window): + """Determine which monitor a window is on based on its center point""" + geo = window.get_geometry() + if geo is None: + return None + + center_x = geo[0] + geo[2] // 2 + center_y = geo[1] + geo[3] // 2 + + for name, (mx, my, mw, mh) in self.monitor_geometries.items(): + if mx <= center_x < mx + mw and my <= center_y < my + mh: + return name + return None + def _connect_window(self, window): """Connect to a window and store the handler ID""" if window not in self.window_signal_handlers: @@ -315,31 +342,42 @@ def window_opened(self, screen, window, _): self._connect_window(window) def eval(self, *args): - # TODO: #28 (Wallpaper stops animating on other monitor when app maximized on other) is_changed = False - is_any_maximized, is_any_fullscreen = False, False + monitor_states = {name: {"maximized": False, "fullscreen": False} + for name in self.monitor_geometries} + for window in self.screen.get_windows(): base_state = not Wnck.Window.is_minimized(window) and \ Wnck.Window.is_on_workspace( window, self.screen.get_active_workspace()) - window_name, is_maximized, is_fullscreen = window.get_name(), \ - Wnck.Window.is_maximized(window) and base_state, \ - Wnck.Window.is_fullscreen(window) and base_state - if is_maximized is True: - is_any_maximized = True - if is_fullscreen is True: - is_any_fullscreen = True - - cur_state = {"is_any_maximized": is_any_maximized, - "is_any_fullscreen": is_any_fullscreen} + + is_maximized = Wnck.Window.is_maximized(window) and base_state + is_fullscreen = Wnck.Window.is_fullscreen(window) and base_state + + if is_maximized or is_fullscreen: + # Find which monitor this window is on + monitor_name = self._get_window_monitor(window) + if monitor_name and monitor_name in monitor_states: + if is_maximized: + monitor_states[monitor_name]["maximized"] = True + if is_fullscreen: + monitor_states[monitor_name]["fullscreen"] = True + + is_any_maximized = any(s["maximized"] for s in monitor_states.values()) + is_any_fullscreen = any(s["fullscreen"] for s in monitor_states.values()) + + cur_state = { + "is_any_maximized": is_any_maximized, + "is_any_fullscreen": is_any_fullscreen, + "monitor_states": monitor_states + } if self.prev_state is None or self.prev_state != cur_state: is_changed = True self.prev_state = cur_state if is_changed: - self.on_window_state_changed( - {"is_any_maximized": is_any_maximized, "is_any_fullscreen": is_any_fullscreen}) + self.on_window_state_changed(cur_state) logger.debug(f"[WindowHandler] {cur_state}") def cleanup(self):