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
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));

Reply via email to