git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit609627ca9f1847fa4c5db61e0bc53059d3f72201
parentd4c6ff243f
authorLucas Galante <[email protected]>
date2026-09-11 14:44
fix(xwayland): anchor a left/top resize on the LOGICAL committed size

Dragging an X11 window's left or top handle at output scale 2 threw the
window across the desktop: on the first motion the origin jumped a whole
window-width left, then tracked the pointer at double speed while the
opposite edge — the one the anchoring exists to pin — slid along with it.
Reported against Houdini; it was every X11 client under xwayland_hidpi.

anchor_resize_commit works in box_geom units, which are logical, and
computes origin = start origin + (start size - committed size). But
handle_window_commit fed it the raw committed surface extent, and an X11
surface commits PHYSICAL pixels — twice the logical box under
xwayland_hidpi, as the scale_only_render_finish call ten lines below
already says. So the subtraction was W - 2W, off by a window-width with
twice the gain.

The anchoring (506ec54) predates HiDPI X11 (da95c6f) by three days; that
change routed every other xsurface read-back through from_x11 and missed
this one. Convert the same way, and take the wine frame off after the
conversion in logical WINE_MARGIN units rather than as 32 raw physical
px, the way render_finish reads the size back.

Verified headless at scale 2 with an X11 zenity, before and after: the
pre-fix binary moves a 250-wide window's origin 195 -> -35 -> 25 over a
40px left-edge drag, the fixed one 195 -> 235 with the right edge held at
445 throughout. Grow-leftward, top-edge and right-edge drags all hold
their opposite edge too.

Co-Authored-By: Claude Opus 5 <[email protected]>

 src/server/window.rs | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/src/server/window.rs b/src/server/window.rs
index e103d6f..7cfd96e 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -5322,16 +5322,27 @@ unsafe extern "C" fn handle_window_commit(listener: *mut ffi::wl_listener, _data
     // size it just committed, ahead of the render_finish below that places
     // the tree at `rendering_requested`. (xdg toplevels do the same in their
     // own commit handler, where the toplevel geometry is the authority.)
+    //
+    // The committed surface is PHYSICAL pixels — under `xwayland_hidpi` twice
+    // the logical box, as the scale pass below says — while
+    // `anchor_resize_commit` works in box_geom's logical units. Convert first,
+    // and take the wine frame off after, the way `render_finish` reads the
+    // xsurface size back. Feeding it the raw buffer width put the origin a
+    // whole window-width to the left and made it track the pointer at double
+    // speed, every X11 left/top drag at scale 2.
     if let WindowImpl::Xwayland(xwindow) = (*window).impl_type {
         if !xwindow.is_null() && !(*xwindow).xsurface.is_null() && (*window).resize_edges.is_some() {
             let surface = (*(*xwindow).xsurface).surface;
             if !surface.is_null() {
-                let mut w = ffi::river_wlr_surface_get_width(surface);
-                let mut h = ffi::river_wlr_surface_get_height(surface);
+                let s = crate::xwayland_window::x11_scale_for((*window).server, (*xwindow).xsurface);
+                let mut w = crate::xwayland_window::from_x11(
+                    ffi::river_wlr_surface_get_width(surface), s);
+                let mut h = crate::xwayland_window::from_x11(
+                    ffi::river_wlr_surface_get_height(surface), s);
                 let has_parent = !(*(*xwindow).xsurface).parent.is_null();
                 if (*window).is_wine() && !has_parent && !(*window).is_fullscreen() {
-                    w = (w - 32).max(0);
-                    h = (h - 32).max(0);
+                    w = (w - crate::xwayland_window::WINE_MARGIN * 2).max(0);
+                    h = (h - crate::xwayland_window::WINE_MARGIN * 2).max(0);
                 }
                 (*window).anchor_resize_commit(w, h);
             }