Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix(resize): one rounded virtual-to-screen conversion for every writer of a window's origin
Interactive resizes twitched: the anchored edge stepped a pixel back and
forth on every commit, and the whole window hopped a pixel on grab and on
release. Three writers of a window's screen origin truncated the
virtual-to-screen conversion — the seat op's Move and Resize arms and the
resize-commit anchoring — while the arrange pass rounds (cce-window-manager
6b83b0f). Whenever the fractional part was .5 or more, a commit placed the
tree at the truncated position and the next arrange moved it to the
rounded one; at overview zoom, where a virtual pixel is a third of a screen
pixel, that was nearly every step.
Window::virtual_to_screen is the single rounded conversion now, and the
three sites go through it (the two seat arms also drop their copies of the
first-enabled-output scan).
Measured headless at output scale 2, sampling after every 1px pointer step
of a left-edge drag: the anchored right edge stays at 453 from rest through
the drag and after release; before, it sat at 452 while dragging, 453 at
rest, and the origin flipped 154/155 at the same virtual position.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/server/seat.rs | 36 ++----------------------------------
src/server/window.rs | 24 ++++++++++++++++++------
2 files changed, 20 insertions(+), 40 deletions(-)
diff --git a/src/server/seat.rs b/src/server/seat.rs
index c45b004..abc84e2 100644
--- a/src/server/seat.rs
+++ b/src/server/seat.rs
@@ -1376,23 +1376,7 @@ impl Seat {
);
}
- let mut out_x = 0;
- let mut out_y = 0;
- let outputs_list = &mut (*(*self.server).wm.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 + ((vx - pan_x) * scale) as i32;
- let final_y = out_y + ((vy - pan_y) * scale) as i32;
+ let (final_x, final_y) = (*win).virtual_to_screen(vx, vy);
(*win).rendering_requested.x = final_x;
(*win).rendering_requested.y = final_y;
(*win).box_geom.x = final_x;
@@ -1446,23 +1430,7 @@ impl Seat {
(*win).virtual_x = vx;
(*win).virtual_y = vy;
- let mut out_x = 0;
- let mut out_y = 0;
- let outputs_list = &mut (*(*self.server).wm.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 + ((vx - pan_x) * scale) as i32;
- let final_y = out_y + ((vy - pan_y) * scale) as i32;
+ let (final_x, final_y) = (*win).virtual_to_screen(vx, vy);
(*win).rendering_requested.x = final_x;
(*win).rendering_requested.y = final_y;
diff --git a/src/server/window.rs b/src/server/window.rs
index 0cd98ab..4605c3f 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -1164,6 +1164,23 @@ impl Window {
(0.0, 0.0, 1920.0, 1080.0)
}
+ /// Virtual position to layout (screen) position, ROUNDED — the same
+ /// conversion the arrange pass makes (`PlacementCtx::virtual_to_screen`).
+ /// Every writer of a window's screen origin has to agree on the
+ /// rounding: the seat op and the resize-commit anchoring truncated while
+ /// the arrange pass rounds, so whenever the fractional part was .5 or
+ /// more the window stepped a pixel back and forth between a commit and
+ /// the next arrange — a twitch on every resize step at overview zoom,
+ /// and a one-pixel hop on grab and release.
+ pub unsafe fn virtual_to_screen(&self, vx: f64, vy: f64) -> (i32, i32) {
+ let wm = &(*self.server).wm;
+ let (out_x, out_y, _, _) = self.first_enabled_output_box();
+ (
+ out_x as i32 + ((vx - wm.desk_pan_x) * wm.desk_zoom).round() as i32,
+ out_y as i32 + ((vy - wm.desk_pan_y) * wm.desk_zoom).round() as i32,
+ )
+ }
+
/// Best-known window size in VIRTUAL units at map time. `box_geom` is the
/// render pass's size and is only filled in once a frame has been drawn
/// (or by `try_restore` from the saved geometry), so a first-ever launch
@@ -1966,12 +1983,7 @@ impl Window {
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;
+ let (final_x, final_y) = self.virtual_to_screen(self.virtual_x, self.virtual_y);
self.rendering_requested.x = final_x;
self.rendering_requested.y = final_y;
self.box_geom.x = final_x;