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.
This commit is contained in:
pax 2026-04-14 19:01:34 -05:00
parent a2609199bd
commit 3c2aa5820d
3 changed files with 47 additions and 1 deletions

View File

@ -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",
]

View File

@ -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"]

View File

@ -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()