Bug#1146122: kwin-wayland: Windows become permanently invisible/unmovable at fractional scale

James Robertson <[email protected]>
Newsgroups gmane.linux.debian.devel.qt-kde
Message-ID <178805665202.30664.12763134941282642717.reportbug__5986.70994383923$1788057329$gmane$org@millionaire>
Package: kwin-wayland
Version: 4:6.3.6-1
Severity: important

Dear Maintainer,

On Wayland sessions with a fractional display scale factor (125%, 150%, ...),
KWin 6.3.6 can get into a state where every geometry change applied to a
window - including plain same-size moves - is silently deferred behind an
xdg_surface configure that the client can never satisfy, making the window
permanently invisible and unmovable.

Root cause (src/xdgshellwindow.cpp, XdgSurfaceWindow::moveResizeInternal):
whether a geometry change can be applied immediately is decided by comparing
the requested client size with the committed client size, truncated via
QSizeF::toSize(). The two values are derived differently, and at a fractional
scale they can disagree permanently. Example at 125%: logical width 1041 is
1301.25 device pixels; KWin's side becomes 1040.8 -> 1040 while the client's
committed size rounds to 1041. 1040 != 1041, forever. Every placement of that
window then takes the configure path - even a move to a different position at
the same size. Clients that suspend rendering while fully off-screen
(Chromium/Electron-based applications, Firefox) never acknowledge the
configure, so the window is wedged: it cannot be moved on-screen because KWin
is waiting for the client, and the client will not draw because it is
off-screen.

Impact: any application window can end up invisible with no user-visible
recovery short of killing the application. Scrolling/tiling KWin scripts
(e.g. Karousel) move windows off-screen as part of normal operation and
trigger this within minutes of ordinary use; user reports of the symptom:
https://github.com/peterfajdiga/karousel/issues/166

Reproduction (Plasma Wayland session, any output at 125% scale):
1. Open a Chromium-family browser (Wayland-native, e.g. chromium
   --ozone-platform=wayland) and note its window is e.g. 1041 px wide
   (any width whose product with the scale factor is non-integer).
2. Move the window fully outside all outputs via the KWin scripting console
   (assigning frameGeometry), let it idle a few seconds.
3. Attempt to move it back on-screen the same way, same size.
   The assignment is never applied; reading frameGeometry back shows the old
   position indefinitely. At 100% scale the same steps behave normally.

Fixed upstream in Plasma 6.7.0 - incidentally, as part of scale-override
preparation work, which is presumably why it was never identified as a bugfix
or backported to 6.4-6.6:
https://invent.kde.org/plasma/kwin/-/commit/16affa41f2877799edaf073ddb440ef1e7c38d9f
(the relevant part: both sides of the comparison now go through
SurfaceInterface::snappedSize(), rounding both to the surface's pixel grid
before comparing).

The attached patch is a minimal equivalent of that comparison change for
6.3.6, applying on top of the existing
upstream_e8541cf2_wayland-Fix-resizing-with-fractional-increments.patch
(which reworked the surrounding lines but kept the truncated comparison).
DEP-3 headers included.

Tested: built in a trixie chroot and deployed on a trixie system running
Plasma Wayland at 125% scale where the deadlock was reproducible at will;
with the patch it cannot be reproduced at all, and previously-affected
setups (tiling script + Chromium/Electron apps) behave normally. I am
currently running the patched package, so reportbug may show my version as
4:6.3.6-1+snapfix1; the bug applies to stock 4:6.3.6-1.

Given the small, targeted nature of the fix and that the failure mode is
windows becoming permanently invisible, this seems like a candidate for a
trixie point release; happy to provide any further testing.

-- System Information:
Debian Release: 13.6
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'stable-security'), (500, 'stable')
Architecture: amd64 (x86_64)

Kernel: Linux 7.1.3+deb13-amd64 (SMP w/8 CPU threads; PREEMPT)
Kernel taint flags: TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE
Locale: LANG=en_AU.UTF-8, LC_CTYPE=en_AU.UTF-8 (charmap=UTF-8), LANGUAGE=en_AU:en
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages kwin-wayland depends on:
ii  libc6                                  2.41-12+deb13u3
ii  libcanberra0                           0.30-18
ii  libcap2-bin                            1:2.75-10+deb13u1+b1
ii  libepoxy0                              1.5.10-2
ii  libkdecorations3-6                     4:6.3.4-1
ii  libkf6configcore6                      6.13.0-2
ii  libkf6configgui6                       6.13.0-2
ii  libkf6configwidgets6                   6.13.0-1
ii  libkf6coreaddons6                      6.13.0-1
ii  libkf6crash6                           6.13.0-1
ii  libkf6dbusaddons6                      6.13.0-1
ii  libkf6globalaccel6                     6.13.0-1
ii  libkf6i18n6                            6.13.0-1
ii  libkf6idletime6                        6.13.0-1
ii  libkf6notifications6                   6.13.0-1
ii  libkf6service-bin                      6.13.0-1
ii  libkf6service6                         6.13.0-1
ii  libkf6svg6                             6.13.0-1
ii  libkf6windowsystem6                    6.13.0-2
ii  libkglobalacceld0                      6.3.6-1
hi  libkwin6                               4:6.3.6-1+snapfix1
ii  libqaccessibilityclient-qt6-0          0.6.0-3
ii  libqt6core6t64 [qt6-base-private-abi]  6.8.2+dfsg-9+deb13u2
ii  libqt6dbus6                            6.8.2+dfsg-9+deb13u2
ii  libqt6gui6                             6.8.2+dfsg-9+deb13u2
ii  libqt6qml6                             6.8.2+dfsg-7
ii  libqt6quick6                           6.8.2+dfsg-7
ii  libqt6widgets6                         6.8.2+dfsg-9+deb13u2
ii  libstdc++6                             14.2.0-19
ii  libsystemd0                            257.13-1~deb13u1
ii  libxcb-composite0                      1.17.0-2+b1
ii  libxcb-randr0                          1.17.0-2+b1
ii  libxcb-xfixes0                         1.17.0-2+b1
ii  libxcb1                                1.17.0-2+b1
ii  xwayland                               2:24.1.6-1

Versions of packages kwin-wayland recommends:
ii  xwaylandvideobridge  0.4.0-2+b1

kwin-wayland suggests no packages.

-- no debconf information
kwinsnappedsize.patch (text/plain, 3.6 KB)
Description: Compare client sizes snapped to the surface scale in moveResizeInternal
 At fractional display scale factors (e.g. 125%), many logical window sizes are
 not representable in device pixels. XdgSurfaceWindow::moveResizeInternal()
 decides whether a geometry change can be applied immediately or must wait for
 the client to acknowledge a configure by comparing the requested client size
 with the committed client size, truncated via QSizeF::toSize(). The two values
 are derived differently, and at a fractional scale they can disagree
 permanently (e.g. logical width 1041 at 125% = 1301.25 device pixels: KWin's
 side becomes 1040.8 -> 1040 while the client's committed size rounds to 1041).
 .
 From that point on, every placement of the affected window - including plain
 same-size moves - schedules a configure the client can never satisfy exactly.
 Clients that suspend rendering while fully off-screen (Chromium- and
 Electron-based applications, Firefox) then never acknowledge it, and the
 window becomes permanently invisible and unmovable until the application
 happens to redraw or is closed. Scrolling/tiling window managers (e.g. the
 Karousel KWin script) trigger this readily; see
 https://github.com/peterfajdiga/karousel/issues/166 for user reports.
 .
 Upstream fixed this in Plasma 6.7.0, incidentally, as part of the scale
 override preparation work: both sides of the comparison now go through
 SurfaceInterface::snappedSize(), which rounds both values to the surface's
 pixel grid before comparing. That commit is entangled with new scale-override
 infrastructure, so this patch is a minimal equivalent for 6.3.6: snap both
 sides to the same target scale that the requested frame size was already
 snapped with two lines above. It sits on top of Debian's
 upstream_e8541cf2_wayland-Fix-resizing-with-fractional-increments.patch,
 which reworked the surrounding lines but kept the truncated comparison.
 .
 Verified on Debian trixie (Plasma 6.3.6, Wayland, 125% scale): before the
 patch the deadlock is reproducible within a minute of normal use with a
 scrolling tiler; after it, it cannot be reproduced at all. At 100% scale the
 bug never occurs, with or without the patch.
Origin: backport, https://invent.kde.org/plasma/kwin/-/commit/16affa41f2877799edaf073ddb440ef1e7c38d9f
Applied-Upstream: 6.7.0
Author: James <jelloir>
Last-Update: 2026-08-29
--- a/src/xdgshellwindow.cpp
+++ b/src/xdgshellwindow.cpp
@@ -272,9 +272,14 @@ void XdgSurfaceWindow::moveResizeInternal(const QRectF &rect, MoveResizeMode mo
         // and current client sizes have to be rounded to integers
         const QSizeF requestedFrameSize = snapToPixels(rect.size(), nextTargetScale());
         const QSizeF requestedClientSize = nextFrameSizeToClientSize(requestedFrameSize);
-        const QSize roundedRequestedClientSize = requestedClientSize.toSize();
+        const qreal snapScale = nextTargetScale();
+        const auto snapSizeToScale = [snapScale](const QSizeF &size) {
+            return QSizeF(qreal(qRound(size.width() * snapScale)) / snapScale,
+                          qreal(qRound(size.height() * snapScale)) / snapScale);
+        };
+        const QSizeF roundedRequestedClientSize = snapSizeToScale(requestedClientSize);
 
-        const QSize roundedClientSize = clientSize().toSize();
+        const QSizeF roundedClientSize = snapSizeToScale(clientSize());
         if (roundedRequestedClientSize == roundedClientSize) {
             const QRectF snappedRect = QRectF(rect.topLeft(), nextClientSizeToFrameSize(snapToPixels(roundedClientSize, nextTargetScale())));
             updateGeometry(gravitateGeometry(snappedRect, rect, m_nextGravity));
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.