Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat(input): scroll_method for the trackpoint and mouse classes
libinput defaults a pointing stick to scroll-on-button-down with the
middle button. It withholds every middle press until the release to see
whether the stick moves, then emits the press (with its original time)
and the release together, so a client sees a zero-length click and a
middle drag scrolls. Measured with the TPPS/2 Elan TrackPoint: left and
right clicks reached Houdini and a bare Qt window 100-200 ms apart, every
middle click arrived press+release in the same millisecond, and a
Wayland-native client got the same, even for a 2 s hold. Houdini's
middle-click node popup never showed, and nothing in Houdini, Xwayland or
the compositor's button path was at fault.
`trackpoint { scroll_method "none" }` (also button, two_finger, edge;
same key in `mouse`) sets libinput's scroll method for the class. A
touchpad keeps its two-finger default. Applied with the rest of the
per-device config, so it takes effect on the next compositor start or
device add.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/server/config.rs | 16 ++++++++++++++++
src/server/libinput_device.rs | 32 ++++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/src/server/config.rs b/src/server/config.rs
index 6f51155..d3d3cc9 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -446,6 +446,13 @@ pub struct TrackpointConfig {
pub accel_speed: Option<f64>,
pub accel_profile: Option<String>,
pub scroll_factor: Option<f64>,
+ /// libinput scroll method: `"none"`, `"button"` (scroll while the
+ /// middle button is held) or `"two_finger"` / `"edge"` for devices that
+ /// support them. libinput defaults a pointing stick to `"button"`, which
+ /// withholds every middle press until the release to see whether it was
+ /// a scroll: clients then get a press and release in the same instant,
+ /// so a middle *click* never registers and a middle *drag* scrolls.
+ pub scroll_method: Option<String>,
}
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
@@ -453,6 +460,8 @@ pub struct MouseConfig {
pub accel_speed: Option<f64>,
pub accel_profile: Option<String>,
pub scroll_factor: Option<f64>,
+ /// See `TrackpointConfig::scroll_method`.
+ pub scroll_method: Option<String>,
}
/// Pointer device configuration. Per-class blocks (`mouse` / `touchpad` /
@@ -1905,6 +1914,7 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
accel_speed,
accel_profile,
scroll_factor: get_child_arg_f64_opt(tp_node, "scroll_factor"),
+ scroll_method: get_child_arg_string_opt(tp_node, "scroll_method"),
});
}
}
@@ -1916,6 +1926,7 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
accel_speed: get_child_arg_f64_opt(m_node, "accel_speed"),
accel_profile: get_child_arg_string_opt(m_node, "accel_profile"),
scroll_factor: get_child_arg_f64_opt(m_node, "scroll_factor"),
+ scroll_method: get_child_arg_string_opt(m_node, "scroll_method"),
});
}
}
@@ -3028,6 +3039,7 @@ style {
mouse {
accel_speed (f64)0.5
scroll_factor (f64)2.0
+ scroll_method "button"
}
trackpad {
tap_to_click (bool)true
@@ -3039,6 +3051,7 @@ style {
accel_speed (f64)0.4
accel_profile "adaptive"
scroll_factor (f64)3.0
+ scroll_method ("menu:none,button,two_finger,edge")"none"
}
}
"#;
@@ -3053,6 +3066,7 @@ style {
let mouse = input.mouse.unwrap();
assert_eq!(mouse.accel_speed, Some(0.5));
assert_eq!(mouse.scroll_factor, Some(2.0));
+ assert_eq!(mouse.scroll_method, Some("button".to_string()));
// `trackpad` parses into the touchpad block (input.kdl spelling).
let tp = input.touchpad.unwrap();
assert_eq!(tp.tap_to_click, Some(true));
@@ -3063,6 +3077,8 @@ style {
assert_eq!(tpoint.accel_speed, Some(0.4));
assert_eq!(tpoint.accel_profile, Some("adaptive".to_string()));
assert_eq!(tpoint.scroll_factor, Some(3.0));
+ // The annotated spelling the settings UI writes parses the same.
+ assert_eq!(tpoint.scroll_method, Some("none".to_string()));
}
#[test]
diff --git a/src/server/libinput_device.rs b/src/server/libinput_device.rs
index 06d4e00..a22fccb 100644
--- a/src/server/libinput_device.rs
+++ b/src/server/libinput_device.rs
@@ -152,6 +152,38 @@ impl LibinputDevice {
}
}
}
+
+ // Scroll method. Only the trackpoint and mouse classes carry it: a
+ // touchpad's two-finger default is what everyone wants, and a
+ // pointing stick's default -- scroll on middle-button-down -- is
+ // what nobody clicking a middle button wants (see TrackpointConfig).
+ let class_method = if is_trackpoint {
+ config.trackpoint.as_ref().and_then(|t| t.scroll_method.as_ref())
+ } else if is_touchpad {
+ None
+ } else {
+ config.mouse.as_ref().and_then(|m| m.scroll_method.as_ref())
+ };
+ if let Some(m_str) = class_method {
+ let method = match m_str.as_str() {
+ "none" => Some(ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_NO_SCROLL),
+ "button" | "on_button_down" => Some(ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN),
+ "two_finger" | "2fg" => Some(ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_2FG),
+ "edge" => Some(ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_EDGE),
+ other => {
+ log::warn!("input: unknown scroll_method {:?} (none, button, two_finger, edge)", other);
+ None
+ }
+ };
+ if let Some(m) = method {
+ let supported = ffi::libinput_device_config_scroll_get_methods(handle);
+ if m == ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_NO_SCROLL || (supported & m) != 0 {
+ ffi::libinput_device_config_scroll_set_method(handle, m);
+ } else {
+ log::warn!("input: {} does not support scroll_method {:?}", name, m_str);
+ }
+ }
+ }
}
pub unsafe fn deinit(&mut self) {