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

commita34bf250f9abe2f565e3b6b8c2053a6677d04c70
parentd8f9ea0f84
authorLucas Galante <[email protected]>
date2026-09-22 10:30
fix(input): the swipe lean follows the dominant axis only

A hand swiping left drifts a little up or down as well, and with
focus_up/focus_down bound that drift leaned the camera vertically too,
then eased it back when the bind fired — a wobble on top of the real
move, plainly visible at a 120 px peek. The lean now follows whichever
axis the swipe has travelled further along, and nothing on the other.

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

 CLAUDE.md            |  4 +++-
 src/server/cursor.rs | 16 +++++++++++-----
 2 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 701fb5b..eff0e61 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -525,7 +525,9 @@ directional focus or pan (`focus_left (gesture)"swipe3_left"` in input.kdl)
 fires once the accumulated travel passes `window_manager { swipe_threshold }`
 (libinput units, default 50; `WindowManager::swipe_threshold`). Short of
 that the camera *leans* toward the bind the
-swipe is heading for, 1:1 with the fingers and proportional to the travel —
+swipe is heading for, 1:1 with the fingers along the swipe's dominant axis
+only (a hand's sideways drift must not lean the camera vertically, or the
+fire eases a wobble back) and proportional to the travel —
 `window_manager { swipe_peek }` screen px at the threshold (default 60, 0
 disables; `WindowManager::swipe_peek_px` — not under `input`, whose
 config.kdl block input.kdl's replaces wholesale), clamped there — and eases
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 250c422..ef87647 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -3784,14 +3784,20 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
     // Short of the threshold: lean the camera toward the bind the swipe
     // is heading for, 1:1 with the fingers like a two-finger pan (queued
     // for the frame, no easing), recomputed from the total travel so a
-    // reversal leans back through zero.
+    // reversal leans back through zero. Along the DOMINANT axis only: a
+    // hand swiping left drifts a little up or down as well, and with
+    // up/down binds present that drift leaned the camera vertically too,
+    // then eased it back when the bind fired — a wobble on top of the
+    // real move.
     {
         let wm = &mut (*seat.server).wm;
         let peek_px = wm.swipe_peek_px;
-        let want = [
-            swipe_peek_for(cursor.gesture_dx, navigates[0], navigates[1], threshold, peek_px, wm.desk_zoom),
-            swipe_peek_for(cursor.gesture_dy, navigates[2], navigates[3], threshold, peek_px, wm.desk_zoom),
-        ];
+        let (dx, dy) = (cursor.gesture_dx, cursor.gesture_dy);
+        let want = if dx.abs() >= dy.abs() {
+            [swipe_peek_for(dx, navigates[0], navigates[1], threshold, peek_px, wm.desk_zoom), 0.0]
+        } else {
+            [0.0, swipe_peek_for(dy, navigates[2], navigates[3], threshold, peek_px, wm.desk_zoom)]
+        };
         let delta = [want[0] - cursor.swipe_peek[0], want[1] - cursor.swipe_peek[1]];
         if delta != [0.0, 0.0] {
             wm.stop_panning_animation();