Privacy screen: resume video on un-hide, popout uses in-place overlay
Two related improvements to the Ctrl+P privacy screen flow.
1. Resume video on un-hide
Pre-fix: Ctrl+P paused any playing video in the embedded preview and
the popout, but the second Ctrl+P only hid the privacy overlay — the
videos stayed paused. The user had to manually click Play to resume.
Fix: in _toggle_privacy's privacy-off branch, mirror the privacy-on
pause logic with resume() calls on the embedded preview's video player
and the popout's video. Unconditional resume — if the user manually
paused before Ctrl+P, the auto-resume on un-hide is a tiny annoyance,
but the common case (privacy hides → user comes back → video should
be playing again) wins.
2. Popout privacy uses an in-place overlay instead of hide()
Pre-fix attempt: privacy-on called self._fullscreen_window.hide() and
privacy-off called .show(). On Wayland (Hyprland) the hide→show round
trip drops the window's position because the compositor unmaps the
window on hide and remaps it at the default tile position on show.
A first attempt at restoring the position via a deferred
hyprctl_resize_and_move dispatch in privacy_show didn't take — by
the time the dispatch landed, the window had already been re-tiled
and the move was gated by `if not win.get("floating"): return`.
Cleaner fix: don't hide the popout window at all. FullscreenPreview
gains its own _privacy_overlay (a black QWidget child of central,
parallel to the existing toolbar / controls bar children) that
privacy_hide raises over the media stack. The popout window stays
mapped, position is preserved automatically because nothing moves,
and the overlay covers the content visually.
privacy_hide / privacy_show methods live in FullscreenPreview, not
in main_window — popout-internal state belongs to the popout module.
_toggle_privacy in main_window just calls them. This also makes
adding more popout-side privacy state later (e.g. fullscreen save)
a one-method change inside the popout class.
Also added a _popout_was_visible flag in BooruApp._toggle_privacy so
privacy-off only restores the popout if it was actually visible at
privacy-on time. Without the gate, privacy-off would inappropriately
re-show a popout the user had closed before triggering privacy.
Verified manually:
- popout open + drag to non-default pos + Ctrl+P + Ctrl+P → popout
still at the dragged position, content visible again
- popout open + video playing + Ctrl+P + Ctrl+P → video resumes
- popout closed + Ctrl+P + Ctrl+P → popout stays closed
- embedded preview video + Ctrl+P + Ctrl+P → resumes
- Ctrl+P with no video on screen → no errors
This commit is contained in:
parent
92c1824720
commit
553e31075d
@ -2929,6 +2929,11 @@ class BooruApp(QMainWindow):
|
|||||||
self._privacy_overlay = QWidget(self)
|
self._privacy_overlay = QWidget(self)
|
||||||
self._privacy_overlay.setStyleSheet("background: black;")
|
self._privacy_overlay.setStyleSheet("background: black;")
|
||||||
self._privacy_overlay.hide()
|
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
|
self._privacy_on = not self._privacy_on
|
||||||
if self._privacy_on:
|
if self._privacy_on:
|
||||||
@ -2939,15 +2944,26 @@ class BooruApp(QMainWindow):
|
|||||||
# Pause preview video
|
# Pause preview video
|
||||||
if self._preview._stack.currentIndex() == 1:
|
if self._preview._stack.currentIndex() == 1:
|
||||||
self._preview._video_player.pause()
|
self._preview._video_player.pause()
|
||||||
# Hide and pause popout
|
# Delegate popout hide-and-pause to FullscreenPreview so it
|
||||||
if self._fullscreen_window and self._fullscreen_window.isVisible():
|
# can capture its own geometry for restore.
|
||||||
if self._fullscreen_window._stack.currentIndex() == 1:
|
self._popout_was_visible = bool(
|
||||||
self._fullscreen_window._video.pause()
|
self._fullscreen_window and self._fullscreen_window.isVisible()
|
||||||
self._fullscreen_window.hide()
|
)
|
||||||
|
if self._popout_was_visible:
|
||||||
|
self._fullscreen_window.privacy_hide()
|
||||||
else:
|
else:
|
||||||
self._privacy_overlay.hide()
|
self._privacy_overlay.hide()
|
||||||
if self._fullscreen_window:
|
# Resume embedded preview video — unconditional resume, the
|
||||||
self._fullscreen_window.show()
|
# 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:
|
def resizeEvent(self, event) -> None:
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
|
|||||||
@ -172,6 +172,17 @@ class FullscreenPreview(QMainWindow):
|
|||||||
self._video._controls_bar.raise_()
|
self._video._controls_bar.raise_()
|
||||||
self._toolbar.raise_()
|
self._toolbar.raise_()
|
||||||
|
|
||||||
|
# Privacy overlay — black QWidget child of central, raised over
|
||||||
|
# the media stack on privacy_hide. Lives inside the popout
|
||||||
|
# itself instead of forcing main_window to hide() the popout
|
||||||
|
# window — Wayland's hide→show round-trip drops position because
|
||||||
|
# the compositor unmaps and remaps, and Hyprland may re-tile the
|
||||||
|
# remap depending on window rules. Keeping the popout mapped
|
||||||
|
# with an in-place overlay sidesteps both issues.
|
||||||
|
self._privacy_overlay = QWidget(central)
|
||||||
|
self._privacy_overlay.setStyleSheet("background: black;")
|
||||||
|
self._privacy_overlay.hide()
|
||||||
|
|
||||||
# Auto-hide timer for overlay UI
|
# Auto-hide timer for overlay UI
|
||||||
self._ui_visible = True
|
self._ui_visible = True
|
||||||
self._hide_timer = QTimer(self)
|
self._hide_timer = QTimer(self)
|
||||||
@ -904,6 +915,30 @@ class FullscreenPreview(QMainWindow):
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def privacy_hide(self) -> None:
|
||||||
|
"""Cover the popout's content with a black overlay for privacy.
|
||||||
|
|
||||||
|
The popout window itself is NOT hidden — Wayland's hide→show
|
||||||
|
round-trip drops position because the compositor unmaps and
|
||||||
|
remaps the window, and Hyprland may re-tile the remapped window
|
||||||
|
depending on its rules. Instead we raise an in-place black
|
||||||
|
QWidget overlay over the central widget. The window stays
|
||||||
|
mapped, position is preserved automatically, video is paused.
|
||||||
|
"""
|
||||||
|
if self._stack.currentIndex() == 1:
|
||||||
|
self._video.pause()
|
||||||
|
central = self.centralWidget()
|
||||||
|
if central is not None:
|
||||||
|
self._privacy_overlay.setGeometry(0, 0, central.width(), central.height())
|
||||||
|
self._privacy_overlay.raise_()
|
||||||
|
self._privacy_overlay.show()
|
||||||
|
|
||||||
|
def privacy_show(self) -> None:
|
||||||
|
"""Lift the black overlay and resume video. Counterpart to privacy_hide."""
|
||||||
|
self._privacy_overlay.hide()
|
||||||
|
if self._stack.currentIndex() == 1:
|
||||||
|
self._video.resume()
|
||||||
|
|
||||||
def _enter_fullscreen(self) -> None:
|
def _enter_fullscreen(self) -> None:
|
||||||
"""Enter fullscreen — capture windowed geometry first so F11 back can restore it.
|
"""Enter fullscreen — capture windowed geometry first so F11 back can restore it.
|
||||||
|
|
||||||
@ -996,6 +1031,9 @@ class FullscreenPreview(QMainWindow):
|
|||||||
self._toolbar.setGeometry(0, 0, w, tb_h)
|
self._toolbar.setGeometry(0, 0, w, tb_h)
|
||||||
ctrl_h = self._video._controls_bar.sizeHint().height()
|
ctrl_h = self._video._controls_bar.sizeHint().height()
|
||||||
self._video._controls_bar.setGeometry(0, h - ctrl_h, w, ctrl_h)
|
self._video._controls_bar.setGeometry(0, h - ctrl_h, w, ctrl_h)
|
||||||
|
# Privacy overlay covers the entire central widget when active.
|
||||||
|
if self._privacy_overlay.isVisible():
|
||||||
|
self._privacy_overlay.setGeometry(0, 0, w, h)
|
||||||
# Capture corner-resize into the persistent viewport so the
|
# Capture corner-resize into the persistent viewport so the
|
||||||
# long_side the user chose survives subsequent navigations.
|
# long_side the user chose survives subsequent navigations.
|
||||||
#
|
#
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user