aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/wiki/Configuration:-Input.md4
-rw-r--r--niri-config/src/lib.rs282
-rw-r--r--src/input/mod.rs35
3 files changed, 300 insertions, 21 deletions
diff --git a/docs/wiki/Configuration:-Input.md b/docs/wiki/Configuration:-Input.md
index c9ca0ba2..a03dd67b 100644
--- a/docs/wiki/Configuration:-Input.md
+++ b/docs/wiki/Configuration:-Input.md
@@ -37,6 +37,7 @@ input {
// accel-speed 0.2
// accel-profile "flat"
// scroll-factor 1.0
+ // scroll-factor vertical=1.0 horizontal=-2.0
// scroll-method "two-finger"
// scroll-button 273
// scroll-button-lock
@@ -53,6 +54,7 @@ input {
// accel-speed 0.2
// accel-profile "flat"
// scroll-factor 1.0
+ // scroll-factor vertical=1.0 horizontal=-2.0
// scroll-method "no-scroll"
// scroll-button 273
// scroll-button-lock
@@ -252,6 +254,8 @@ Settings specific to `touchpad` and `mouse`:
- `scroll-factor`: <sup>Since: 0.1.10</sup> scales the scrolling speed by this value.
+ <sup>Since: next release</sup> You can also override horizontal and vertical scroll factor separately like so: `scroll-factor horizontal=2.0 vertical=-1.0`
+
Settings specific to `tablet`s:
- `calibration-matrix`: <sup>Since: 25.02</sup> set to six floating point numbers to change the calibration matrix. See the [`LIBINPUT_CALIBRATION_MATRIX` documentation](https://wayland.freedesktop.org/libinput/doc/latest/device-configuration-via-udev.html) for examples.
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index 16ec6dca..dbd6b5ea 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -191,6 +191,25 @@ pub enum TrackLayout {
Window,
}
+#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
+pub struct ScrollFactor {
+ #[knuffel(argument)]
+ pub base: Option<FloatOrInt<0, 100>>,
+ #[knuffel(property)]
+ pub horizontal: Option<FloatOrInt<-100, 100>>,
+ #[knuffel(property)]
+ pub vertical: Option<FloatOrInt<-100, 100>>,
+}
+
+impl ScrollFactor {
+ pub fn h_v_factors(&self) -> (f64, f64) {
+ let base_value = self.base.map(|f| f.0).unwrap_or(1.0);
+ let h = self.horizontal.map(|f| f.0).unwrap_or(base_value);
+ let v = self.vertical.map(|f| f.0).unwrap_or(base_value);
+ (h, v)
+ }
+}
+
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Touchpad {
#[knuffel(child)]
@@ -227,8 +246,8 @@ pub struct Touchpad {
pub disabled_on_external_mouse: bool,
#[knuffel(child)]
pub middle_emulation: bool,
- #[knuffel(child, unwrap(argument))]
- pub scroll_factor: Option<FloatOrInt<0, 100>>,
+ #[knuffel(child)]
+ pub scroll_factor: Option<ScrollFactor>,
}
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
@@ -251,8 +270,8 @@ pub struct Mouse {
pub left_handed: bool,
#[knuffel(child)]
pub middle_emulation: bool,
- #[knuffel(child, unwrap(argument))]
- pub scroll_factor: Option<FloatOrInt<0, 100>>,
+ #[knuffel(child)]
+ pub scroll_factor: Option<ScrollFactor>,
}
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
@@ -4055,6 +4074,237 @@ mod tests {
}
#[test]
+ fn parse_scroll_factor_combined() {
+ // Test combined scroll-factor syntax
+ let parsed = do_parse(
+ r#"
+ input {
+ mouse {
+ scroll-factor 2.0
+ }
+ touchpad {
+ scroll-factor 1.5
+ }
+ }
+ "#,
+ );
+
+ assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
+ Some(
+ ScrollFactor {
+ base: Some(
+ FloatOrInt(
+ 2.0,
+ ),
+ ),
+ horizontal: None,
+ vertical: None,
+ },
+ )
+ "#);
+ assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
+ Some(
+ ScrollFactor {
+ base: Some(
+ FloatOrInt(
+ 1.5,
+ ),
+ ),
+ horizontal: None,
+ vertical: None,
+ },
+ )
+ "#);
+ }
+
+ #[test]
+ fn parse_scroll_factor_split() {
+ // Test split horizontal/vertical syntax
+ let parsed = do_parse(
+ r#"
+ input {
+ mouse {
+ scroll-factor horizontal=2.0 vertical=-1.0
+ }
+ touchpad {
+ scroll-factor horizontal=-1.5 vertical=0.5
+ }
+ }
+ "#,
+ );
+
+ assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
+ Some(
+ ScrollFactor {
+ base: None,
+ horizontal: Some(
+ FloatOrInt(
+ 2.0,
+ ),
+ ),
+ vertical: Some(
+ FloatOrInt(
+ -1.0,
+ ),
+ ),
+ },
+ )
+ "#);
+ assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
+ Some(
+ ScrollFactor {
+ base: None,
+ horizontal: Some(
+ FloatOrInt(
+ -1.5,
+ ),
+ ),
+ vertical: Some(
+ FloatOrInt(
+ 0.5,
+ ),
+ ),
+ },
+ )
+ "#);
+ }
+
+ #[test]
+ fn parse_scroll_factor_partial() {
+ // Test partial specification (only one axis)
+ let parsed = do_parse(
+ r#"
+ input {
+ mouse {
+ scroll-factor horizontal=2.0
+ }
+ touchpad {
+ scroll-factor vertical=-1.5
+ }
+ }
+ "#,
+ );
+
+ assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
+ Some(
+ ScrollFactor {
+ base: None,
+ horizontal: Some(
+ FloatOrInt(
+ 2.0,
+ ),
+ ),
+ vertical: None,
+ },
+ )
+ "#);
+ assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
+ Some(
+ ScrollFactor {
+ base: None,
+ horizontal: None,
+ vertical: Some(
+ FloatOrInt(
+ -1.5,
+ ),
+ ),
+ },
+ )
+ "#);
+ }
+
+ #[test]
+ fn parse_scroll_factor_mixed() {
+ // Test mixed base + override syntax
+ let parsed = do_parse(
+ r#"
+ input {
+ mouse {
+ scroll-factor 2 vertical=-1
+ }
+ touchpad {
+ scroll-factor 1.5 horizontal=3
+ }
+ }
+ "#,
+ );
+
+ assert_debug_snapshot!(parsed.input.mouse.scroll_factor, @r#"
+ Some(
+ ScrollFactor {
+ base: Some(
+ FloatOrInt(
+ 2.0,
+ ),
+ ),
+ horizontal: None,
+ vertical: Some(
+ FloatOrInt(
+ -1.0,
+ ),
+ ),
+ },
+ )
+ "#);
+ assert_debug_snapshot!(parsed.input.touchpad.scroll_factor, @r#"
+ Some(
+ ScrollFactor {
+ base: Some(
+ FloatOrInt(
+ 1.5,
+ ),
+ ),
+ horizontal: Some(
+ FloatOrInt(
+ 3.0,
+ ),
+ ),
+ vertical: None,
+ },
+ )
+ "#);
+ }
+
+ #[test]
+ fn scroll_factor_h_v_factors() {
+ let sf = ScrollFactor {
+ base: Some(FloatOrInt(2.0)),
+ horizontal: None,
+ vertical: None,
+ };
+ assert_debug_snapshot!(sf.h_v_factors(), @r#"
+ (
+ 2.0,
+ 2.0,
+ )
+ "#);
+
+ let sf = ScrollFactor {
+ base: None,
+ horizontal: Some(FloatOrInt(3.0)),
+ vertical: Some(FloatOrInt(-1.0)),
+ };
+ assert_debug_snapshot!(sf.h_v_factors(), @r#"
+ (
+ 3.0,
+ -1.0,
+ )
+ "#);
+
+ let sf = ScrollFactor {
+ base: Some(FloatOrInt(2.0)),
+ horizontal: Some(FloatOrInt(1.0)),
+ vertical: None,
+ };
+ assert_debug_snapshot!(sf.h_v_factors(), @r"
+ (
+ 1.0,
+ 2.0,
+ )
+ ");
+ }
+
+ #[test]
fn parse() {
let parsed = do_parse(
r##"
@@ -4370,9 +4620,15 @@ mod tests {
disabled_on_external_mouse: true,
middle_emulation: false,
scroll_factor: Some(
- FloatOrInt(
- 0.9,
- ),
+ ScrollFactor {
+ base: Some(
+ FloatOrInt(
+ 0.9,
+ ),
+ ),
+ horizontal: None,
+ vertical: None,
+ },
),
},
mouse: Mouse {
@@ -4394,9 +4650,15 @@ mod tests {
left_handed: false,
middle_emulation: true,
scroll_factor: Some(
- FloatOrInt(
- 0.2,
- ),
+ ScrollFactor {
+ base: Some(
+ FloatOrInt(
+ 0.2,
+ ),
+ ),
+ horizontal: None,
+ vertical: None,
+ },
),
},
trackpoint: Trackpoint {
diff --git a/src/input/mod.rs b/src/input/mod.rs
index c2667dfc..cc07aed3 100644
--- a/src/input/mod.rs
+++ b/src/input/mod.rs
@@ -3086,31 +3086,44 @@ impl State {
self.update_pointer_contents();
- let scroll_factor = match source {
- AxisSource::Wheel => self.niri.config.borrow().input.mouse.scroll_factor,
- AxisSource::Finger => self.niri.config.borrow().input.touchpad.scroll_factor,
- _ => None,
+ let device_scroll_factor = {
+ let config = self.niri.config.borrow();
+ match source {
+ AxisSource::Wheel => config.input.mouse.scroll_factor,
+ AxisSource::Finger => config.input.touchpad.scroll_factor,
+ _ => None,
+ }
};
- let scroll_factor = scroll_factor.map(|x| x.0).unwrap_or(1.);
+ // Get window-specific scroll factor
let window_scroll_factor = pointer
.current_focus()
.map(|focused| self.niri.find_root_shell_surface(&focused))
.and_then(|root| self.niri.layout.find_window_and_output(&root).unzip().0)
- .and_then(|window| window.rules().scroll_factor);
- let scroll_factor = scroll_factor * window_scroll_factor.unwrap_or(1.);
+ .and_then(|window| window.rules().scroll_factor)
+ .unwrap_or(1.);
+
+ // Determine final scroll factors based on configuration
+ let (horizontal_factor, vertical_factor) = device_scroll_factor
+ .map(|x| x.h_v_factors())
+ .unwrap_or((1.0, 1.0));
+ let (horizontal_factor, vertical_factor) = (
+ horizontal_factor * window_scroll_factor,
+ vertical_factor * window_scroll_factor,
+ );
let horizontal_amount = horizontal_amount.unwrap_or_else(|| {
// Winit backend, discrete scrolling.
horizontal_amount_v120.unwrap_or(0.0) / 120. * 15.
- }) * scroll_factor;
+ }) * horizontal_factor;
+
let vertical_amount = vertical_amount.unwrap_or_else(|| {
// Winit backend, discrete scrolling.
vertical_amount_v120.unwrap_or(0.0) / 120. * 15.
- }) * scroll_factor;
+ }) * vertical_factor;
- let horizontal_amount_v120 = horizontal_amount_v120.map(|x| x * scroll_factor);
- let vertical_amount_v120 = vertical_amount_v120.map(|x| x * scroll_factor);
+ let horizontal_amount_v120 = horizontal_amount_v120.map(|x| x * horizontal_factor);
+ let vertical_amount_v120 = vertical_amount_v120.map(|x| x * vertical_factor);
let mut frame = AxisFrame::new(event.time_msec()).source(source);
if horizontal_amount != 0.0 {