Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat(config): window_manager { swipe_peek } sets the swipe lean
The lean a directional swipe bind gives the desktop before it fires was
a constant, 60 screen px at the threshold. It is now `swipe_peek` under
`window_manager` in config.kdl (0 turns it off), applied on reload.
It lives under `window_manager`, not `input` beside the scroll settings,
because input.kdl's `input {}` block replaces config.kdl's wholesale, so
a key there is dead for anyone with pointer settings in input.kdl.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
CLAUDE.md | 4 +++-
src/server/config.rs | 15 ++++++++++++++-
src/server/cursor.rs | 24 +++++++++++++-----------
src/server/window_manager.rs | 6 ++++++
4 files changed, 36 insertions(+), 13 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 48280d3..a8b98ba 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -511,7 +511,9 @@ 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
swipe is heading for, 1:1 with the fingers and proportional to the travel —
-`SWIPE_PEEK_PX` (60 screen px) at the threshold, clamped there — and eases
+`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
back to where it started if the fingers lift first (`handle_swipe_end`), so
a hesitant swipe shows where it would go without going. Only binds whose
action `cursor::action_navigates` (focus/pan left/right/up/down) peek, and
diff --git a/src/server/config.rs b/src/server/config.rs
index d5cc2fb..d531e64 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -445,6 +445,12 @@ pub struct WindowManagerConfig {
pub touchpad_view_swipe: Option<String>,
/// Finger-to-pointer distance factor for the emulated drag (default 1).
pub touchpad_view_sensitivity: Option<f64>,
+ /// How far the desktop leans toward a directional swipe bind by the
+ /// time the swipe reaches its threshold, screen px (default 60; 0
+ /// turns the lean off). KDL: `swipe_peek (f64)60.0`. Lives here, not
+ /// 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>,
/// 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>,
@@ -2436,9 +2442,10 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
let touchpad_view_apps = get_child_args_string_vec_opt(node, "touchpad_view_apps");
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 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, 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, touchpad_view_invert, touchpad_hscroll_shift_apps });
}
Ok(Config {
@@ -2512,6 +2519,10 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
.and_then(|w| w.touchpad_view_swipe.as_deref())
.map_or(false, |s| s.eq_ignore_ascii_case("tumble"));
state.touchpad_view_sensitivity = tv.and_then(|w| w.touchpad_view_sensitivity).unwrap_or(1.0);
+ state.swipe_peek_px = tv
+ .and_then(|w| w.swipe_peek)
+ .filter(|v| v.is_finite() && *v >= 0.0)
+ .unwrap_or(60.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();
}
@@ -3042,6 +3053,7 @@ style {
close_window (keybind)"super+q"
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
}
"#;
let config = parse_kdl_config(content).unwrap();
@@ -3050,6 +3062,7 @@ style {
assert_eq!(wm.close_window, Some("super+q".to_string()));
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));
// 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 ce4c2e0..d653703 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -3532,11 +3532,11 @@ unsafe extern "C" fn handle_touch_frame(listener: *mut ffi::wl_listener, _data:
/// 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 `SWIPE_PEEK_PX` on screen
-/// 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.
+/// 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;
-const SWIPE_PEEK_PX: f64 = 60.0;
/// 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,12 +3547,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, 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, zoom: f64) -> f64 {
+/// 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
+/// desktop still.
+fn swipe_peek_for(d: f64, neg: bool, pos: bool, peek_px: f64, zoom: f64) -> f64 {
if (d < 0.0 && neg) || (d > 0.0 && pos) {
- (d / SWIPE_TRIGGER_DISTANCE).clamp(-1.0, 1.0) * SWIPE_PEEK_PX / zoom.max(1e-6)
+ (d / SWIPE_TRIGGER_DISTANCE).clamp(-1.0, 1.0) * peek_px / zoom.max(1e-6)
} else {
0.0
}
@@ -3741,9 +3742,10 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
// reversal leans back through zero.
{
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], wm.desk_zoom),
- swipe_peek_for(cursor.gesture_dy, navigates[2], navigates[3], wm.desk_zoom),
+ 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),
];
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 3996233..205fe3f 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -210,6 +210,10 @@ pub struct WindowManager {
pub touchpad_view_swipe_tumble: bool,
pub touchpad_view_sensitivity: f64,
pub touchpad_view_invert: bool,
+ /// `window_manager { swipe_peek }`: the desktop's lean toward a
+ /// directional swipe bind at its threshold, screen px (default 60;
+ /// 0 disables). See `cursor::swipe_peek_for`.
+ pub swipe_peek_px: f64,
/// See `WindowManagerConfig::touchpad_hscroll_shift_apps`.
pub touchpad_hscroll_shift_apps: Vec<String>,
/// Live override-redirect X11 surfaces (menus, tooltips, combo lists),
@@ -520,6 +524,7 @@ impl WindowManager {
self.touchpad_view_apps = Vec::new();
self.touchpad_view_swipe_tumble = false;
self.touchpad_view_sensitivity = 1.0;
+ self.swipe_peek_px = 60.0;
self.touchpad_view_invert = false;
self.touchpad_hscroll_shift_apps = Vec::new();
self.display = std::collections::HashMap::new();
@@ -600,6 +605,7 @@ impl WindowManager {
self.touchpad_view_apps = Vec::new();
self.touchpad_view_swipe_tumble = false;
self.touchpad_view_sensitivity = 1.0;
+ self.swipe_peek_px = 60.0;
self.touchpad_view_invert = false;
self.touchpad_hscroll_shift_apps = Vec::new();
self.display = std::collections::HashMap::new();