Restore popout position via hyprctl on first fit (Wayland ignores Qt setGeometry for child windows)

This commit is contained in:
pax 2026-04-06 21:36:40 -05:00
parent 2f3161f974
commit 92b7a16ab2

View File

@ -173,9 +173,21 @@ class FullscreenPreview(QMainWindow):
self.setScreen(target_screen) self.setScreen(target_screen)
self.setGeometry(target_screen.geometry()) self.setGeometry(target_screen.geometry())
self._adjusting = False self._adjusting = False
# Position-restore handshake: setGeometry below seeds Qt with the saved
# size, but Hyprland ignores the position for child windows. The first
# _fit_to_content call after show() picks up _pending_position_restore
# and corrects the position via a hyprctl batch (no race with the
# resize). After that first fit, navigation center-pins from whatever
# position the user has dragged the window to.
self._first_fit_pending = True
self._pending_position_restore: tuple[int, int] | None = None
# Restore saved state or start fullscreen # Restore saved state or start fullscreen
if FullscreenPreview._saved_geometry and not FullscreenPreview._saved_fullscreen: if FullscreenPreview._saved_geometry and not FullscreenPreview._saved_fullscreen:
self.setGeometry(FullscreenPreview._saved_geometry) self.setGeometry(FullscreenPreview._saved_geometry)
self._pending_position_restore = (
FullscreenPreview._saved_geometry.x(),
FullscreenPreview._saved_geometry.y(),
)
self.show() self.show()
else: else:
self.showFullScreen() self.showFullScreen()
@ -249,7 +261,8 @@ class FullscreenPreview(QMainWindow):
"""Size window to fit content. Width preserved, height from aspect ratio, clamped to screen.""" """Size window to fit content. Width preserved, height from aspect ratio, clamped to screen."""
if self.isFullScreen() or content_w <= 0 or content_h <= 0: if self.isFullScreen() or content_w <= 0 or content_h <= 0:
return return
if self._is_hypr_floating() is False: floating = self._is_hypr_floating()
if floating is False:
self._hyprctl_resize(0, 0) # tiled: just set keep_aspect_ratio self._hyprctl_resize(0, 0) # tiled: just set keep_aspect_ratio
return return
aspect = content_w / content_h aspect = content_w / content_h
@ -262,7 +275,24 @@ class FullscreenPreview(QMainWindow):
h = max_h h = max_h
w = int(h * aspect) w = int(h * aspect)
self.resize(w, h) self.resize(w, h)
self._hyprctl_resize(w, h) # Decide target top-left:
# first fit after open with a saved position → restore it (one-shot)
# any subsequent fit → center-pin from current Hyprland position
target: tuple[int, int] | None = None
if self._first_fit_pending and self._pending_position_restore:
target = self._pending_position_restore
elif floating is True:
win = self._hyprctl_get_window()
if win and win.get("at") and win.get("size"):
cx = win["at"][0] + win["size"][0] // 2
cy = win["at"][1] + win["size"][1] // 2
target = (cx - w // 2, cy - h // 2)
if target is not None:
self._hyprctl_resize_and_move(w, h, target[0], target[1])
else:
self._hyprctl_resize(w, h)
self._first_fit_pending = False
self._pending_position_restore = None
def _show_overlay(self) -> None: def _show_overlay(self) -> None:
"""Show toolbar and video controls, restart auto-hide timer.""" """Show toolbar and video controls, restart auto-hide timer."""
@ -402,6 +432,29 @@ class FullscreenPreview(QMainWindow):
except FileNotFoundError: except FileNotFoundError:
pass pass
def _hyprctl_resize_and_move(self, w: int, h: int, x: int, y: int) -> None:
"""Atomically resize and move this window via a single hyprctl batch."""
import os, subprocess
if not os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
return
win = self._hyprctl_get_window()
if not win or not win.get("floating"):
return
addr = win.get("address")
if not addr:
return
try:
subprocess.Popen(
["hyprctl", "--batch",
f"dispatch setprop address:{addr} keep_aspect_ratio 0"
f" ; dispatch resizewindowpixel exact {w} {h},address:{addr}"
f" ; dispatch movewindowpixel exact {x} {y},address:{addr}"
f" ; dispatch setprop address:{addr} keep_aspect_ratio 1"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
except FileNotFoundError:
pass
def _exit_fullscreen(self) -> None: def _exit_fullscreen(self) -> None:
"""Leave fullscreen — sizes to content aspect ratio using current width.""" """Leave fullscreen — sizes to content aspect ratio using current width."""
content_w, content_h = 0, 0 content_w, content_h = 0, 0