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

commit8c99a9686c7aaf9658f918d95739bad470ac64b7
parent3d675796a1
authorLucas Galante <[email protected]>
date2026-08-21 08:13
fix: overview-click exit flight landed above-left of the clicked window

Clicking a window in overview fires the exit flight (Focus + ramped
SetCamera centered on the window at zoom 1), but op_end's tap-restore
then re-ran focus_follow_pan with the camera still at overview zoom.
Its footprint math divided box_geom (virtual units) by zoom, inflating
the window ~1/zoom x, so the visibility gate tripped and set exponential
pan targets computed at overview zoom. The tick applies those targets on
top of the active ramp and they survive it, dragging the settled camera
to pan* + (w - vw)(1/zoom - 1)/2 — above and to the left of the window.

Three changes:
- op_end's tap-restore pan skips ops that started in overview: the
  release already launched a flight centered on this window.
- focus_follow_pan bails while a camera ramp is in flight — any target
  computed from a mid-flight camera is stale by construction.
- focus_follow_pan no longer divides box_geom by zoom: box_geom is
  virtual units (screen px are box_geom * zoom), so the footprint was
  wrong at any zoom != 1 and mistargeted centers and nudges.

Shadow-verified: overview click on a far window settles at zoom 1
exactly centered; focus-follow still pans to off-screen windows in
normal mode, and centers exactly at 2.14x zoom.

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

 src/server/seat.rs | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

diff --git a/src/server/seat.rs b/src/server/seat.rs
index 3ea9ea8..8bf057d 100644
--- a/src/server/seat.rs
+++ b/src/server/seat.rs
@@ -826,6 +826,13 @@ impl Seat {
         if self.suppress_focus_pan {
             return;
         }
+        // While a camera ramp owns the camera (an overview enter/exit
+        // flight), the current camera is a mid-flight sample — any pan
+        // target computed from it is stale by construction. Never retarget
+        // out from under the ramp.
+        if (*self.server).wm.camera_ramp_anim.is_some() {
+            return;
+        }
         if window.is_null()
             || !matches!(
                 (*window).tiling_mode,
@@ -886,10 +893,11 @@ impl Seat {
 
             let wm = &mut (*self.server).wm;
             let cam = wm.camera();
-            // fw/fh are screen px; the window's virtual
-            // footprint is that over zoom.
-            let vw_w = fw / cam.zoom;
-            let vw_h = fh / cam.zoom;
+            // box_geom is already virtual units (its screen footprint is
+            // box_geom * zoom) — dividing by zoom here inflated the window
+            // whenever zoom != 1 and mistargeted the pan.
+            let vw_w = fw;
+            let vw_h = fh;
             let visible = crate::policy::camera::visible_fraction(
                 (*window).virtual_x,
                 (*window).virtual_y,
@@ -1471,10 +1479,13 @@ impl Seat {
                 // sliver at the screen edge, it is easy to land on the
                 // border band instead, focus the window, and see nothing
                 // happen. Restore the pan for taps; real drags (any actual
-                // motion) keep the camera still.
+                // motion) keep the camera still. An overview tap is
+                // excluded: its release already launched the exit flight
+                // centered on this window, and a second pan computed from
+                // the still-overview camera drags that flight off target.
                 let dx = (op.x - op.start_x).abs();
                 let dy = (op.y - op.start_y).abs();
-                if dx < 4 && dy < 4 {
+                if dx < 4 && dy < 4 && !op.started_in_overview {
                     self.focus_follow_pan(win);
                 }
             }