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

commit7c09314c68bc0603f4351f5ab6fa0e4e4c609872
parent1ffd0e98cc
authorLucas Galante <[email protected]>
date2026-09-08 12:23
fix(xwayland): derive X11 surface pointer coordinates from geometry, not dest state

The residual hover flicker after 1ffd0e9: now and then the hovered item
and a control up-left of it flickered together — one pointer event
reaching Houdini at half its coordinates. wlr_scene_node_at maps the
point through the buffer's CURRENT dest size, and the commit hook only
closes the gap the surface commit opens; any other writer of dest sizes
(a transaction's frozen copy, a fullscreen tick, the per-frame pass
racing an event) reopens it for one event.

The scene hit-test now derives an X11 surface's point from what is
invariant instead: the node's layout origin and the scale the buffer is
meant to be shown at (output scale over overview zoom for a toplevel,
output scale for an override-redirect). Input no longer depends on the
dest state at all; rendering keeps the commit hook and per-frame pass.

Verified headless at output scale 2 with the GTK4 X11 motion logger: 86
motion samples with no step above 2 units and a 50-pixel drag arriving
as 50, and a click on an item of a half-scaled popover still delivered.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/server/scene.rs | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/src/server/scene.rs b/src/server/scene.rs
index 078145f..ab0e31d 100644
--- a/src/server/scene.rs
+++ b/src/server/scene.rs
@@ -199,6 +199,43 @@ impl Scene {
                 }
 
                 let surface = ffi::river_scene_node_get_surface(node);
+                // An X11 surface under xwayland_hidpi is a physical-pixel
+                // buffer drawn at 1/scale, and wlr_scene_node_at maps the
+                // point through the buffer's CURRENT dest size. That size
+                // is reset to natural on every commit and restored by the
+                // commit hook — but any other writer of dest sizes (a
+                // transaction's frozen copy, a fullscreen tick) opens the
+                // same gap, in which a pointer event reaches the client at
+                // half its coordinates: Houdini's hover jumping up-left for
+                // a frame. Derive the surface point from what is INVARIANT
+                // instead — the node's layout origin and the scale the
+                // buffer is meant to be shown at — so input never depends
+                // on the dest state.
+                let (mut sx, mut sy) = (sx, sy);
+                if !surface.is_null() {
+                    let ratio: Option<f64> = match scene_node_data.data {
+                        SceneNodeDataVal::Window(window)
+                            if !window.is_null()
+                                && matches!((*window).impl_type, crate::window::WindowImpl::Xwayland(_)) =>
+                        {
+                            let s = crate::xwayland_window::x11_scale((*window).server) as f64;
+                            let zoom = if (*window).scale > 0.0 { (*window).scale } else { 1.0 };
+                            (s != 1.0).then_some(s / zoom)
+                        }
+                        SceneNodeDataVal::OverrideRedirect(or) if !or.is_null() => {
+                            let s = crate::xwayland_window::x11_scale((*or).server) as f64;
+                            (s != 1.0).then_some(s)
+                        }
+                        _ => None,
+                    };
+                    if let Some(ratio) = ratio {
+                        let (mut nx, mut ny) = (0, 0);
+                        if ffi::wlr_scene_node_coords(node, &mut nx, &mut ny) {
+                            sx = (lx - nx as f64) * ratio;
+                            sy = (ly - ny as f64) * ratio;
+                        }
+                    }
+                }
                 result = Some(AtResult {
                     node,
                     surface,