From cb2445a90aac582fde9998cf23fc21921946659f Mon Sep 17 00:00:00 2001 From: pax Date: Fri, 10 Apr 2026 14:41:10 -0500 Subject: [PATCH] refactor: extract PrivacyController from main_window.py Move _toggle_privacy and its lazy state (_privacy_on, _privacy_overlay, _popout_was_visible) into gui/privacy.py. Rewire menu action, popout signal, resizeEvent, and keyPressEvent to use the controller. No behavior change. main_window.py: 3111 -> 3068 lines. --- booru_viewer/gui/main_window.py | 57 ++++------------------------ booru_viewer/gui/privacy.py | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 50 deletions(-) create mode 100644 booru_viewer/gui/privacy.py diff --git a/booru_viewer/gui/main_window.py b/booru_viewer/gui/main_window.py index 9bf17b8..00e94c6 100644 --- a/booru_viewer/gui/main_window.py +++ b/booru_viewer/gui/main_window.py @@ -59,6 +59,7 @@ from .log_handler import LogHandler from .async_signals import AsyncSignals from .info_panel import InfoPanel from .window_state import WindowStateController +from .privacy import PrivacyController log = logging.getLogger("booru") @@ -130,6 +131,7 @@ class BooruApp(QMainWindow): # (and from the splitter timer's flush on close). Uses the same # 300ms debounce pattern as the splitter saver. self._window_state = WindowStateController(self) + self._privacy = PrivacyController(self) self._main_window_save_timer = QTimer(self) self._main_window_save_timer.setSingleShot(True) self._main_window_save_timer.setInterval(300) @@ -556,7 +558,7 @@ class BooruApp(QMainWindow): privacy_action = QAction("&Privacy Screen", self) privacy_action.setShortcut(QKeySequence("Ctrl+P")) - privacy_action.triggered.connect(self._toggle_privacy) + privacy_action.triggered.connect(self._privacy.toggle) view_menu.addAction(privacy_action) def _load_sites(self) -> None: @@ -2053,7 +2055,7 @@ class BooruApp(QMainWindow): self._fullscreen_window.open_in_default.connect(self._open_preview_in_default) self._fullscreen_window.open_in_browser.connect(self._open_preview_in_browser) self._fullscreen_window.closed.connect(self._on_fullscreen_closed) - self._fullscreen_window.privacy_requested.connect(self._toggle_privacy) + self._fullscreen_window.privacy_requested.connect(self._privacy.toggle) # Set post tags for BL Tag menu post = self._preview._current_post if post: @@ -2847,54 +2849,9 @@ class BooruApp(QMainWindow): else: self.showFullScreen() - # -- Privacy screen -- - - def _toggle_privacy(self) -> None: - if not hasattr(self, '_privacy_on'): - self._privacy_on = False - self._privacy_overlay = QWidget(self) - self._privacy_overlay.setStyleSheet("background: black;") - self._privacy_overlay.hide() - # Tracks whether the popout was visible at privacy-on time - # so privacy-off only restores it if it was actually up - # before. Without the gate, privacy-off would re-show a - # popout that the user closed before triggering privacy. - self._popout_was_visible = False - - self._privacy_on = not self._privacy_on - if self._privacy_on: - self._privacy_overlay.setGeometry(self.rect()) - self._privacy_overlay.raise_() - self._privacy_overlay.show() - self.setWindowTitle("booru-viewer") - # Pause preview video - if self._preview._stack.currentIndex() == 1: - self._preview._video_player.pause() - # Delegate popout hide-and-pause to FullscreenPreview so it - # can capture its own geometry for restore. - self._popout_was_visible = bool( - self._fullscreen_window and self._fullscreen_window.isVisible() - ) - if self._popout_was_visible: - self._fullscreen_window.privacy_hide() - else: - self._privacy_overlay.hide() - # Resume embedded preview video — unconditional resume, the - # common case (privacy hides → user comes back → video should - # be playing again) wins over the manually-paused edge case. - if self._preview._stack.currentIndex() == 1: - self._preview._video_player.resume() - # Restore the popout via its own privacy_show method, which - # also re-dispatches the captured geometry to Hyprland (Qt - # show() alone doesn't preserve position on Wayland) and - # resumes its video. - if self._popout_was_visible and self._fullscreen_window: - self._fullscreen_window.privacy_show() - def resizeEvent(self, event) -> None: super().resizeEvent(event) - if hasattr(self, '_privacy_overlay') and self._privacy_on: - self._privacy_overlay.setGeometry(self.rect()) + self._privacy.resize_overlay() # Capture window state proactively so the saved value is always # fresh — closeEvent's hyprctl query can fail if the compositor has # already started unmapping. Debounced via the 300ms timer. @@ -2915,10 +2872,10 @@ class BooruApp(QMainWindow): key = event.key() # Privacy screen always works if key == Qt.Key.Key_P and event.modifiers() == Qt.KeyboardModifier.ControlModifier: - self._toggle_privacy() + self._privacy.toggle() return # If privacy is on, only allow toggling it off - if hasattr(self, '_privacy_on') and self._privacy_on: + if self._privacy.is_active: return if key == Qt.Key.Key_F and self._posts: idx = self._grid.selected_index diff --git a/booru_viewer/gui/privacy.py b/booru_viewer/gui/privacy.py new file mode 100644 index 0000000..0ae1a60 --- /dev/null +++ b/booru_viewer/gui/privacy.py @@ -0,0 +1,66 @@ +"""Privacy-screen overlay for the main window.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from PySide6.QtWidgets import QWidget + +if TYPE_CHECKING: + from .main_window import BooruApp + + +class PrivacyController: + """Owns the privacy overlay toggle and popout coordination.""" + + def __init__(self, app: BooruApp) -> None: + self._app = app + self._on = False + self._overlay: QWidget | None = None + self._popout_was_visible = False + + @property + def is_active(self) -> bool: + return self._on + + def resize_overlay(self) -> None: + """Re-fit the overlay to the main window's current rect.""" + if self._overlay is not None and self._on: + self._overlay.setGeometry(self._app.rect()) + + def toggle(self) -> None: + if self._overlay is None: + self._overlay = QWidget(self._app) + self._overlay.setStyleSheet("background: black;") + self._overlay.hide() + + self._on = not self._on + if self._on: + self._overlay.setGeometry(self._app.rect()) + self._overlay.raise_() + self._overlay.show() + self._app.setWindowTitle("booru-viewer") + # Pause preview video + if self._app._preview._stack.currentIndex() == 1: + self._app._preview._video_player.pause() + # Delegate popout hide-and-pause to FullscreenPreview so it + # can capture its own geometry for restore. + self._popout_was_visible = bool( + self._app._fullscreen_window + and self._app._fullscreen_window.isVisible() + ) + if self._popout_was_visible: + self._app._fullscreen_window.privacy_hide() + else: + self._overlay.hide() + # Resume embedded preview video — unconditional resume, the + # common case (privacy hides -> user comes back -> video should + # be playing again) wins over the manually-paused edge case. + if self._app._preview._stack.currentIndex() == 1: + self._app._preview._video_player.resume() + # Restore the popout via its own privacy_show method, which + # also re-dispatches the captured geometry to Hyprland (Qt + # show() alone doesn't preserve position on Wayland) and + # resumes its video. + if self._popout_was_visible and self._app._fullscreen_window: + self._app._fullscreen_window.privacy_show()