Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat(config): window_manager { swipe_threshold } sets when a swipe fires
The 50-unit travel at which a directional swipe bind fires was a
constant; it is now `swipe_threshold` under `window_manager` in
config.kdl, applied on reload. The lean scales with it, since the peek
is a fraction of the threshold reached.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
CLAUDE.md | 5 +++--
src/server/config.rs | 12 +++++++++++-
src/server/cursor.rs | 38 ++++++++++++++++++++------------------
src/server/window_manager.rs | 5 +++++
4 files changed, 39 insertions(+), 21 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index a8b98ba..6a6d1ae 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -508,8 +508,9 @@ same.
**Swipe binds peek before they fire.** A three-finger swipe bound to a
directional focus or pan (`focus_left (gesture)"swipe3_left"` in input.kdl)
-fires once the accumulated travel passes `cursor::SWIPE_TRIGGER_DISTANCE`
-(50 libinput units). Short of that the camera *leans* toward the bind the
+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 —
`window_manager { swipe_peek }` screen px at the threshold (default 60, 0
disables; `WindowManager::swipe_peek_px` — not under `input`, whose
diff --git a/src/server/config.rs b/src/server/config.rs
index d531e64..cbd4a8b 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -451,6 +451,9 @@ pub struct WindowManagerConfig {
/// under `input`, because input.kdl's `input {}` block replaces
/// config.kdl's wholesale. See "Swipe binds peek" in CLAUDE.md.
pub swipe_peek: Option<f64>,
+ /// Accumulated travel (libinput units, roughly mm) at which a swipe
+ /// bind fires (default 50). KDL: `swipe_threshold (f64)50.0`.
+ pub swipe_threshold: Option<f64>,
/// Reverse the drag direction, on top of the natural-scroll correction
/// the drag already makes. KDL: `touchpad_view_invert (bool)true`.
pub touchpad_view_invert: Option<bool>,
@@ -2443,9 +2446,10 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
let touchpad_view_swipe = get_child_arg_string_opt(node, "touchpad_view_swipe");
let touchpad_view_sensitivity = get_child_arg_f64_opt(node, "touchpad_view_sensitivity");
let swipe_peek = get_child_arg_f64_opt(node, "swipe_peek");
+ let swipe_threshold = get_child_arg_f64_opt(node, "swipe_threshold");
let touchpad_view_invert = get_child_arg_bool_opt(node, "touchpad_view_invert");
let touchpad_hscroll_shift_apps = get_child_args_string_vec_opt(node, "touchpad_hscroll_shift_apps");
- window_manager = Some(WindowManagerConfig { close_window, toggle_fullscreen, toggle_overview, window_switcher, window_switcher_prev, center_on_spawn, on_app_exit, corner_shape, rounded_apps, bevel_apps, xwayland_hidpi, xwayland_hidpi_except, touchpad_view_apps, touchpad_view_swipe, touchpad_view_sensitivity, swipe_peek, touchpad_view_invert, touchpad_hscroll_shift_apps });
+ window_manager = Some(WindowManagerConfig { close_window, toggle_fullscreen, toggle_overview, window_switcher, window_switcher_prev, center_on_spawn, on_app_exit, corner_shape, rounded_apps, bevel_apps, xwayland_hidpi, xwayland_hidpi_except, touchpad_view_apps, touchpad_view_swipe, touchpad_view_sensitivity, swipe_peek, swipe_threshold, touchpad_view_invert, touchpad_hscroll_shift_apps });
}
Ok(Config {
@@ -2523,6 +2527,10 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
.and_then(|w| w.swipe_peek)
.filter(|v| v.is_finite() && *v >= 0.0)
.unwrap_or(60.0);
+ state.swipe_threshold = tv
+ .and_then(|w| w.swipe_threshold)
+ .filter(|v| v.is_finite() && *v > 0.0)
+ .unwrap_or(50.0);
state.touchpad_view_invert = tv.and_then(|w| w.touchpad_view_invert).unwrap_or(false);
state.touchpad_hscroll_shift_apps = tv.and_then(|w| w.touchpad_hscroll_shift_apps.clone()).unwrap_or_default();
}
@@ -3054,6 +3062,7 @@ style {
toggle_fullscreen (keybind)"super+f"
toggle_overview ("menu:swipe_up,swipe_down,swipe_left,swipe_right,pinch_in,pinch_out")"swipe_up"
swipe_peek (f64)40.0
+ swipe_threshold (f64)80.0
}
"#;
let config = parse_kdl_config(content).unwrap();
@@ -3063,6 +3072,7 @@ style {
assert_eq!(wm.toggle_fullscreen, Some("super+f".to_string()));
assert_eq!(wm.toggle_overview, Some("swipe_up".to_string()));
assert_eq!(wm.swipe_peek, Some(40.0));
+ assert_eq!(wm.swipe_threshold, Some(80.0));
// Absent means "unset", which the apply step reads as the centring default.
assert_eq!(wm.center_on_spawn, None);
}
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index d653703..ffe5fe3 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -3530,13 +3530,14 @@ unsafe extern "C" fn handle_touch_frame(listener: *mut ffi::wl_listener, _data:
ffi::wlr_seat_touch_notify_frame(seat.wlr_seat);
}
-/// Accumulated swipe travel (libinput units) at which a directional swipe
-/// bind fires. Until then the camera *peeks*: it pans toward the swipe
-/// direction in proportion to the travel, up to `window_manager {
-/// swipe_peek }` screen px (`WindowManager::swipe_peek_px`, default 60) at the
-/// threshold, and eases back if the fingers lift short of it — so a
-/// hesitant three-finger swipe shows where it would go without going.
-const SWIPE_TRIGGER_DISTANCE: f64 = 50.0;
+/// A directional swipe bind fires once the accumulated travel (libinput
+/// units) passes `window_manager { swipe_threshold }`
+/// (`WindowManager::swipe_threshold`, default 50). Until then the camera
+/// *peeks*: it pans toward the swipe direction in proportion to the
+/// travel, up to `window_manager { swipe_peek }` screen px
+/// (`WindowManager::swipe_peek_px`, default 60) at the threshold, and
+/// eases back if the fingers lift short of it — so a hesitant
+/// three-finger swipe shows where it would go without going.
/// Does firing `action` carry the view in the swipe's direction? Only
/// such binds peek the camera beforehand: an overview toggle or a spawn
@@ -3547,13 +3548,13 @@ fn action_navigates(action: crate::config::Action) -> bool {
}
/// The peek the accumulated travel `d` along one axis calls for, in
-/// virtual units: proportional and clamped at the threshold, `peek_px`
-/// on screen there, and only toward a direction that has a navigating
-/// bind (`neg` / `pos`) — a swipe with nothing bound its way leaves the
+/// virtual units: proportional and clamped at `threshold`, `peek_px` on
+/// screen there, and only toward a direction that has a navigating bind
+/// (`neg` / `pos`) — a swipe with nothing bound its way leaves the
/// desktop still.
-fn swipe_peek_for(d: f64, neg: bool, pos: bool, peek_px: f64, zoom: f64) -> f64 {
+fn swipe_peek_for(d: f64, neg: bool, pos: bool, threshold: f64, peek_px: f64, zoom: f64) -> f64 {
if (d < 0.0 && neg) || (d > 0.0 && pos) {
- (d / SWIPE_TRIGGER_DISTANCE).clamp(-1.0, 1.0) * peek_px / zoom.max(1e-6)
+ (d / threshold).clamp(-1.0, 1.0) * peek_px / zoom.max(1e-6)
} else {
0.0
}
@@ -3627,6 +3628,7 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
0
};
+ let threshold = (*seat.server).wm.swipe_threshold;
let mut matched_action = crate::config::Action::None;
let mut matched_command = None;
// Which directions this finger count + chord could still fire a
@@ -3636,10 +3638,10 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
for gb in &(*seat.server).wm.gesture_binds {
if gb.gesture_type == "swipe" && gb.fingers == (*event).fingers && gb.mods == modifiers {
let (matched, slot) = match gb.direction.as_str() {
- "left" => (cursor.gesture_dx < -SWIPE_TRIGGER_DISTANCE, Some(0)),
- "right" => (cursor.gesture_dx > SWIPE_TRIGGER_DISTANCE, Some(1)),
- "up" => (cursor.gesture_dy < -SWIPE_TRIGGER_DISTANCE, Some(2)),
- "down" => (cursor.gesture_dy > SWIPE_TRIGGER_DISTANCE, Some(3)),
+ "left" => (cursor.gesture_dx < -threshold, Some(0)),
+ "right" => (cursor.gesture_dx > threshold, Some(1)),
+ "up" => (cursor.gesture_dy < -threshold, Some(2)),
+ "down" => (cursor.gesture_dy > threshold, Some(3)),
_ => (false, None),
};
if matched {
@@ -3744,8 +3746,8 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
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], peek_px, wm.desk_zoom),
- swipe_peek_for(cursor.gesture_dy, navigates[2], navigates[3], peek_px, wm.desk_zoom),
+ 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 delta = [want[0] - cursor.swipe_peek[0], want[1] - cursor.swipe_peek[1]];
if delta != [0.0, 0.0] {
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 205fe3f..3ced192 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -214,6 +214,9 @@ pub struct WindowManager {
/// directional swipe bind at its threshold, screen px (default 60;
/// 0 disables). See `cursor::swipe_peek_for`.
pub swipe_peek_px: f64,
+ /// `window_manager { swipe_threshold }`: accumulated swipe travel
+ /// (libinput units) at which a swipe bind fires (default 50).
+ pub swipe_threshold: f64,
/// See `WindowManagerConfig::touchpad_hscroll_shift_apps`.
pub touchpad_hscroll_shift_apps: Vec<String>,
/// Live override-redirect X11 surfaces (menus, tooltips, combo lists),
@@ -525,6 +528,7 @@ impl WindowManager {
self.touchpad_view_swipe_tumble = false;
self.touchpad_view_sensitivity = 1.0;
self.swipe_peek_px = 60.0;
+ self.swipe_threshold = 50.0;
self.touchpad_view_invert = false;
self.touchpad_hscroll_shift_apps = Vec::new();
self.display = std::collections::HashMap::new();
@@ -606,6 +610,7 @@ impl WindowManager {
self.touchpad_view_swipe_tumble = false;
self.touchpad_view_sensitivity = 1.0;
self.swipe_peek_px = 60.0;
+ self.swipe_threshold = 50.0;
self.touchpad_view_invert = false;
self.touchpad_hscroll_shift_apps = Vec::new();
self.display = std::collections::HashMap::new();