Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix(resize): anchor the dragged left/top edge for Xwayland windows too
Dragging a window's top or left handle in overview grew the window the
wrong way when the window was an X11 client (Steam, in the report): the
origin held still and the size grew away from the grabbed edge, so the
bottom/right edge moved instead of the one under the pointer.
The compensation that keeps the opposite edge anchored — origin =
drag-start origin + (drag-start size - committed size) — lived only in the
xdg-toplevel commit handler. An Xwayland window's commits go through
handle_window_commit, which never touched the virtual position, and the
Xwayland configure path sends the X window its new size at box_geom's
unchanged origin.
The math moves to Window::anchor_resize_commit, fed by both commit paths:
the xdg handler passes the toplevel geometry as before, and
handle_window_commit passes an X surface's committed size (with the wine
frame taken off, as the render pass does) ahead of the render_finish that
places the tree. Disarming after the op stays where it was — the first
commit to arrive once the seat op has ended.
Verified headless at output scale 2 with an Xwayland zenity and a cce-ui
client: left and top drags now hold the right/bottom edge fixed for both.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/server/window.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++
src/server/xdg_toplevel.rs | 65 +++----------------------------------
2 files changed, 86 insertions(+), 60 deletions(-)
diff --git a/src/server/window.rs b/src/server/window.rs
index 7adb1a4..dccd48a 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -1916,6 +1916,68 @@ impl Window {
}
}
+ /// Is a pointer resize op on this window still in progress on any seat?
+ pub unsafe fn resize_op_active(&self) -> bool {
+ let seats_list = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr_seat = (*seats_list).next;
+ while curr_seat != seats_list {
+ let seat = crate::container_of!(curr_seat, crate::seat::Seat, link);
+ if let Some(ref op) = (*seat).op {
+ if op.window_ptr == self as *const Window as *mut Window {
+ if let crate::seat::PointerOpType::Resize { .. } = op.op_type {
+ return true;
+ }
+ }
+ }
+ curr_seat = (*curr_seat).next;
+ }
+ false
+ }
+
+ /// Interactive-resize anchoring for the commit path, shared by every
+ /// surface kind. While a LEFT/TOP edge is being dragged, the client's
+ /// committed size decides where the window's origin goes: the opposite
+ /// edge stays where the drag found it, so the dragged edge is the one
+ /// that appears to move. Without this the origin holds still and the
+ /// window grows away from the grabbed edge — which is what Xwayland
+ /// windows did until they were routed through here: only the
+ /// xdg-toplevel commit handler had the math.
+ ///
+ /// `committed_w`/`committed_h` are the size the client just committed,
+ /// in `box_geom` units (content size; for wine X11 windows the caller has
+ /// already taken the 32px frame off, as the render pass does). Updates
+ /// the virtual position and the requested/box screen origin and returns
+ /// that origin, or `None` when no resize is armed. The anchoring outlives
+ /// the seat op by one commit — the last configure is usually still in
+ /// flight at release — so the first commit after the op disarms it.
+ pub unsafe fn anchor_resize_commit(&mut self, committed_w: i32, committed_h: i32) -> Option<(i32, i32)> {
+ let edges = self.resize_edges?;
+ let resize_active = self.resize_op_active();
+
+ if edges.left {
+ self.virtual_x = self.resize_start_vx + (self.resize_start_w as f64 - committed_w as f64);
+ }
+ if edges.top {
+ self.virtual_y = self.resize_start_vy + (self.resize_start_h as f64 - committed_h as f64);
+ }
+
+ let zoom = (*self.server).wm.desk_zoom;
+ let pan_x = (*self.server).wm.desk_pan_x;
+ let pan_y = (*self.server).wm.desk_pan_y;
+ let (out_x, out_y, _, _) = self.first_enabled_output_box();
+ let final_x = out_x as i32 + ((self.virtual_x - pan_x) * zoom) as i32;
+ let final_y = out_y as i32 + ((self.virtual_y - pan_y) * zoom) as i32;
+ self.rendering_requested.x = final_x;
+ self.rendering_requested.y = final_y;
+ self.box_geom.x = final_x;
+ self.box_geom.y = final_y;
+
+ if !resize_active {
+ self.resize_edges = None;
+ }
+ Some((final_x, final_y))
+ }
+
pub unsafe fn set_decoration_hint(&mut self, hint: ffi::zcce_window_v1_decoration_hint) {
self.wm_scheduled.decoration_hint = hint;
if hint != self.wm_sent.decoration_hint {
@@ -5077,6 +5139,25 @@ unsafe extern "C" fn handle_window_commit(listener: *mut ffi::wl_listener, _data
let window = crate::container_of!(listener, Window, commit);
(*window).stream_dirty = true;
let was_status = (*window).is_status_bar();
+ // An X11 client committing under a left/top-edge drag: anchor on the
+ // 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.)
+ 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 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);
+ }
+ (*window).anchor_resize_commit(w, h);
+ }
+ }
+ }
(*window).render_finish();
if was_status {
(*(*window).server).wm.dirty_windowing();
diff --git a/src/server/xdg_toplevel.rs b/src/server/xdg_toplevel.rs
index 2bdc676..3afdbf6 100644
--- a/src/server/xdg_toplevel.rs
+++ b/src/server/xdg_toplevel.rs
@@ -884,63 +884,12 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
}
}
- if let Some(edges) = (*window).resize_edges {
- let geometry = (*toplevel).geometry;
- let mut resize_active = false;
+ // Left/top-edge anchoring on the committed geometry — the shared
+ // Window::anchor_resize_commit; Xwayland windows take the same path from
+ // handle_window_commit.
+ let geometry = (*toplevel).geometry;
+ if let Some((final_x, final_y)) = (*window).anchor_resize_commit(geometry.width, geometry.height) {
let server = (*window).server;
- let seats_list = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
- let mut curr_seat = (*seats_list).next;
- while curr_seat != seats_list {
- let seat = crate::container_of!(curr_seat, crate::seat::Seat, link);
- if let Some(ref op) = (*seat).op {
- if op.window_ptr == window {
- if let crate::seat::PointerOpType::Resize { .. } = op.op_type {
- resize_active = true;
- break;
- }
- }
- }
- curr_seat = (*curr_seat).next;
- }
-
- let mut new_vx = (*window).virtual_x;
- let mut new_vy = (*window).virtual_y;
- if edges.left {
- new_vx = (*window).resize_start_vx + ((*window).resize_start_w as f64 - geometry.width as f64);
- }
- if edges.top {
- new_vy = (*window).resize_start_vy + ((*window).resize_start_h as f64 - geometry.height as f64);
- }
- (*window).virtual_x = new_vx;
- (*window).virtual_y = new_vy;
-
- let scale = (*server).wm.desk_zoom;
- let pan_x = (*server).wm.desk_pan_x;
- let pan_y = (*server).wm.desk_pan_y;
-
- let mut out_x = 0;
- let mut out_y = 0;
- let outputs_list = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
- let mut curr_out = (*outputs_list).next;
- while curr_out != outputs_list {
- let output = crate::container_of!(curr_out, crate::output::Output, link);
- if (*output).sent.state == crate::output::OutputStateValue::Enabled {
- let wlr_box = (*output).sent.box_layout();
- out_x = wlr_box.x;
- out_y = wlr_box.y;
- break;
- }
- curr_out = (*curr_out).next;
- }
-
- let final_x = out_x + ((new_vx - pan_x) * scale) as i32;
- let final_y = out_y + ((new_vy - pan_y) * scale) as i32;
-
- (*window).rendering_requested.x = final_x;
- (*window).rendering_requested.y = final_y;
- (*window).box_geom.x = final_x;
- (*window).box_geom.y = final_y;
-
// Keep the displayed buffer and the compensating position atomic.
// Live buffer (no configure in flight): the commit is already on
// screen, so move the scene tree in the same commit — waiting for
@@ -959,10 +908,6 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
} else {
(*server).wm.dirty_rendering();
}
-
- if !resize_active {
- (*window).resize_edges = None;
- }
}
}