Skip to content

Commit 3e3d40d

Browse files
committed
🐛 fix: fix(viewer): wayland resize stability + CSD edge resize handles
Problem: KWin bypasses Qt's setMaximumSize for frameless windows during cross-origin navigation (YouTube sign-in). Window stretches to ~3300px when compositor receives null textures from QtWebEngine during page load. Root cause: Wayland compositors reset frameless window geometry when QtWebEngine produces null textures during cross-origin navigation. Fix: lock window size with setFixedSize() on loadStarted, unlock on loadFinished. Prevents compositor geometry changes during vulnerable transition period. Additional fixes: - Add _ResizeHandle widget class → transparent edge overlays for CSD resize from left/right/bottom borders + bottom corners - Replace broken mouseMoveEvent/mousePressEvent approach (child widgets consume events) with independent handle widgets per edge - Each handle owns its cursor + calls startSystemResize() on drag - Handles hidden when maximized/fullscreen, repositioned on resize - JS reflow dispatch on loadFinished → fix YouTube sidebar layout after size lock release - Clamp restored geometry to 90% of screen in _load_geometry() - Enforce screen-size max via _enforce_screen_limits() - Lift max-size limit before fullscreen, restore after exit
1 parent 0346056 commit 3e3d40d

1 file changed

Lines changed: 66 additions & 2 deletions

File tree

biglinux-webapps/usr/bin/big-webapps-viewer

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
CSD headerbar replicating GTK4/Adwaita style. Fullscreen auto-hide overlay.
44
"""
55

6-
APP_VERSION = "3.5.0"
6+
APP_VERSION = "3.5.1"
77

88
import argparse
99
import json
@@ -68,6 +68,7 @@ from PySide6.QtWidgets import (
6868
DATA_BASE = Path.home() / ".local" / "share" / "biglinux-webapps"
6969
CONFIG_BASE = Path.home() / ".config" / "biglinux-webapps"
7070
HOVER_ZONE = 60
71+
RESIZE_MARGIN = 8 # px from edge to trigger border resize
7172

7273

7374
def _is_dark_theme() -> bool:
@@ -319,6 +320,25 @@ class HeaderBar(QWidget):
319320
w.showMaximized()
320321

321322

323+
class _ResizeHandle(QWidget):
324+
"""Transparent edge widget that initiates system resize on drag."""
325+
326+
def __init__(self, edges: Qt.Edges, cursor_shape, parent=None):
327+
super().__init__(parent)
328+
self.edges = edges
329+
self.setCursor(cursor_shape)
330+
self.setMouseTracking(True)
331+
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, False)
332+
self.setStyleSheet("background: transparent;")
333+
self.raise_()
334+
335+
def mousePressEvent(self, event) -> None:
336+
if event.button() == Qt.LeftButton:
337+
wh = self.window().windowHandle()
338+
if wh:
339+
wh.startSystemResize(self.edges)
340+
341+
322342
class NavOverlay(QWidget):
323343
"""Fullscreen-only auto-hide nav: ← → ⟳ ⊞"""
324344

@@ -546,6 +566,9 @@ class WebAppWindow(QMainWindow):
546566
self._grip = QSizeGrip(self)
547567
self._grip.setFixedSize(16, 16)
548568

569+
# edge resize handles (left/right/bottom/corners)
570+
self._setup_resize_handles()
571+
549572
def _setup_shortcuts(self) -> None:
550573
for key, slot in (
551574
(QKeySequence("F5"), lambda: self.webview.reload()),
@@ -674,11 +697,15 @@ class WebAppWindow(QMainWindow):
674697

675698
def _on_load_finished(self, ok: bool) -> None:
676699
"""Unlock window size + transfer pending file."""
677-
# restore resizability
700+
# restore resizability + trigger page reflow after size lock
678701
if hasattr(self, "_pre_nav_size"):
679702
self.setMinimumSize(0, 0)
680703
self._enforce_screen_limits()
681704
del self._pre_nav_size
705+
# force page layout recalculation after unlock
706+
self.webview.page().runJavaScript(
707+
"window.dispatchEvent(new Event('resize'));"
708+
)
682709
if ok and self._pending_file and self._pending_file.is_file():
683710
self._page._pending_file = self._pending_file
684711
self._pending_file = None
@@ -745,12 +772,49 @@ class WebAppWindow(QMainWindow):
745772

746773
# --- geometry ---
747774

775+
def _setup_resize_handles(self) -> None:
776+
"""Create transparent edge widgets for resize (left/right/bottom/corners)."""
777+
m = RESIZE_MARGIN
778+
self._resize_handles = []
779+
780+
specs = [
781+
# (edges, cursor, x, y, w, h) — geometry set in resizeEvent
782+
(Qt.LeftEdge, Qt.SizeHorCursor),
783+
(Qt.RightEdge, Qt.SizeHorCursor),
784+
(Qt.BottomEdge, Qt.SizeVerCursor),
785+
(Qt.LeftEdge | Qt.BottomEdge, Qt.SizeBDiagCursor),
786+
(Qt.RightEdge | Qt.BottomEdge, Qt.SizeFDiagCursor),
787+
]
788+
for edges, cursor_shape in specs:
789+
h = _ResizeHandle(edges, cursor_shape, self)
790+
self._resize_handles.append(h)
791+
792+
def _position_resize_handles(self) -> None:
793+
m = RESIZE_MARGIN
794+
w, h = self.width(), self.height()
795+
hdr_h = self.header.height() if self.header.isVisible() else 0
796+
for handle in self._resize_handles:
797+
e = handle.edges
798+
if e == Qt.LeftEdge:
799+
handle.setGeometry(0, hdr_h, m, h - hdr_h - m)
800+
elif e == Qt.RightEdge:
801+
handle.setGeometry(w - m, hdr_h, m, h - hdr_h - m)
802+
elif e == Qt.BottomEdge:
803+
handle.setGeometry(m, h - m, w - 2 * m, m)
804+
elif e == (Qt.LeftEdge | Qt.BottomEdge):
805+
handle.setGeometry(0, h - m, m, m)
806+
elif e == (Qt.RightEdge | Qt.BottomEdge):
807+
handle.setGeometry(w - m, h - m, m, m)
808+
vis = not (self.isMaximized() or self.isFullScreen())
809+
handle.setVisible(vis)
810+
748811
def resizeEvent(self, event) -> None:
749812
super().resizeEvent(event)
750813
self._grip.move(
751814
self.width() - self._grip.width(),
752815
self.height() - self._grip.height(),
753816
)
817+
self._position_resize_handles()
754818
self._apply_mask()
755819

756820
def _apply_mask(self) -> None:

0 commit comments

Comments
 (0)