From 3c2aa5820d85c33ae3b01827f1018be88e0b77c7 Mon Sep 17 00:00:00 2001 From: pax Date: Tue, 14 Apr 2026 19:01:34 -0500 Subject: [PATCH] popout: remember tiled state across open/close Popout was always reopening as floating even when it had been tiled at close. closeEvent already persisted geometry + fullscreen, but nothing captured the Hyprland floating/tiled bit, so the windowrule's `float = yes` rule always won on reopen. Now closeEvent records `_saved_tiled` from hyprctl, popout_controller persists it as `slideshow_tiled`, and FullscreenPreview's restore path calls the new `hyprland.settiled` helper shortly after show() to push the window back into the layout. Saved geometry is ignored for tiled reopens since the tile extent is the layout's concern. behavior change: popout reopens tiled if it was tiled at close. --- booru_viewer/gui/popout/hyprland.py | 26 ++++++++++++++++++++++++++ booru_viewer/gui/popout/window.py | 16 +++++++++++++++- booru_viewer/gui/popout_controller.py | 6 ++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/booru_viewer/gui/popout/hyprland.py b/booru_viewer/gui/popout/hyprland.py index 9638172..8faee0a 100644 --- a/booru_viewer/gui/popout/hyprland.py +++ b/booru_viewer/gui/popout/hyprland.py @@ -211,9 +211,35 @@ def get_monitor_available_rect(monitor_id: int | None = None) -> tuple[int, int, return None +def settiled(window_title: str) -> None: + """Ask Hyprland to un-float the popout, restoring it to tiled layout. + + Used on reopen when the popout was tiled at close — the windowrule + opens it floating, so we dispatch `settiled` to push it back into + the layout. + + Gated by BOORU_VIEWER_NO_HYPR_RULES so ricers with their own rules + keep control. + """ + if not _on_hyprland(): + return + if not hypr_rules_enabled(): + return + win = get_window(window_title) + if not win: + return + addr = win.get("address") + if not addr: + return + if not win.get("floating"): + return + _dispatch_batch([f"dispatch settiled address:{addr}"]) + + __all__ = [ "get_window", "get_monitor_available_rect", "resize", "resize_and_move", + "settiled", ] diff --git a/booru_viewer/gui/popout/window.py b/booru_viewer/gui/popout/window.py index 0ede060..a315ea8 100644 --- a/booru_viewer/gui/popout/window.py +++ b/booru_viewer/gui/popout/window.py @@ -350,7 +350,16 @@ class FullscreenPreview(QMainWindow): # F11 → fullscreen → F11 has a sensible target. self._windowed_geometry = None # Restore saved state or start fullscreen - if FullscreenPreview._saved_geometry and not FullscreenPreview._saved_fullscreen: + if FullscreenPreview._saved_tiled and not FullscreenPreview._saved_fullscreen: + # Was tiled at last close — let Hyprland's layout place it, + # then dispatch `settiled` to override the windowrule's float. + # Saved geometry is meaningless for a tiled window, so skip + # setGeometry entirely. + self.show() + QTimer.singleShot( + 50, lambda: hyprland.settiled(self.windowTitle()) + ) + elif FullscreenPreview._saved_geometry and not FullscreenPreview._saved_fullscreen: self.setGeometry(FullscreenPreview._saved_geometry) self._pending_position_restore = ( FullscreenPreview._saved_geometry.x(), @@ -628,6 +637,7 @@ class FullscreenPreview(QMainWindow): _saved_geometry = None # remembers window size/position across opens _saved_fullscreen = False + _saved_tiled = False # True if Hyprland had it tiled at last close _current_tags: dict[str, list[str]] = {} _current_tag_list: list[str] = [] @@ -1754,9 +1764,13 @@ class FullscreenPreview(QMainWindow): # Geometry is adapter-side concern, not state machine concern, # so the state machine doesn't see it. FullscreenPreview._saved_fullscreen = self.isFullScreen() + FullscreenPreview._saved_tiled = False if not self.isFullScreen(): # On Hyprland, Qt doesn't know the real position — ask the WM win = hyprland.get_window(self.windowTitle()) + if win and win.get("floating") is False: + # Tiled: reopen will re-tile instead of restoring geometry. + FullscreenPreview._saved_tiled = True if win and win.get("at") and win.get("size"): from PySide6.QtCore import QRect x, y = win["at"] diff --git a/booru_viewer/gui/popout_controller.py b/booru_viewer/gui/popout_controller.py index 8ebd2ad..4bc0290 100644 --- a/booru_viewer/gui/popout_controller.py +++ b/booru_viewer/gui/popout_controller.py @@ -76,17 +76,21 @@ class PopoutController: from .popout.window import FullscreenPreview saved_geo = self._app._db.get_setting("slideshow_geometry") saved_fs = self._app._db.get_setting_bool("slideshow_fullscreen") + saved_tiled = self._app._db.get_setting_bool("slideshow_tiled") if saved_geo: parts = saved_geo.split(",") if len(parts) == 4: from PySide6.QtCore import QRect FullscreenPreview._saved_geometry = QRect(*[int(p) for p in parts]) FullscreenPreview._saved_fullscreen = saved_fs + FullscreenPreview._saved_tiled = saved_tiled else: FullscreenPreview._saved_geometry = None FullscreenPreview._saved_fullscreen = True + FullscreenPreview._saved_tiled = False else: FullscreenPreview._saved_fullscreen = True + FullscreenPreview._saved_tiled = saved_tiled cols = self._app._grid._flow.columns show_actions = self._app._stack.currentIndex() != 2 monitor = self._app._db.get_setting("slideshow_monitor") @@ -135,7 +139,9 @@ class PopoutController: from .popout.window import FullscreenPreview fs = FullscreenPreview._saved_fullscreen geo = FullscreenPreview._saved_geometry + tiled = FullscreenPreview._saved_tiled self._app._db.set_setting("slideshow_fullscreen", "1" if fs else "0") + self._app._db.set_setting("slideshow_tiled", "1" if tiled else "0") if geo: self._app._db.set_setting("slideshow_geometry", f"{geo.x()},{geo.y()},{geo.width()},{geo.height()}") self._app._preview.show()