aboutsummaryrefslogtreecommitdiff
path: root/niri-config/src/lib.rs
diff options
context:
space:
mode:
authorBernardo Kuri <github.com@bkuri.com>2025-08-18 23:51:32 -0600
committerGitHub <noreply@github.com>2025-08-19 05:51:32 +0000
commit5ea9092a4954f8c5cd795f0ebec910666b367d5f (patch)
treec9a6aec2fe9a2f6f1692de3c66ec8ac82c890bbf /niri-config/src/lib.rs
parent43a2648e579fc81366fc81b15f834c9c9dff119b (diff)
downloadniri-5ea9092a4954f8c5cd795f0ebec910666b367d5f.tar.gz
niri-5ea9092a4954f8c5cd795f0ebec910666b367d5f.tar.bz2
niri-5ea9092a4954f8c5cd795f0ebec910666b367d5f.zip
Add per-axis scroll speed config for input devices (#2109)
* Add per-axis scroll speed config for input devices. Accepts negative values to inverse scroll direction. Properly complements/overrides global `scroll-direction` setting. Includes docs and tests. * Update per-axis scroll factor implementation after testing - Refined configuration structure in niri-config - Updated input handling to use per-axis scroll factors - Added comprehensive test snapshots - Updated documentation with per-axis examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Simplify per-axis scroll factor implementation per review feedback - Make documentation concise and clear - Remove unnecessary comments and test helper functions - Use inline snapshots for tests - Rename get_factors() to h_v_factors() for clarity - Remove unnecessary .clone() calls (ScrollFactor is Copy) - Reduce test count to essential cases only - Fix comment about window factor override behavior 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove unnecessary ScrollFactor::new() helper function The maintainer prefers minimal code, so removing this helper and constructing ScrollFactor directly in tests. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix scroll factor behavior - all settings now multiply with window factor Per maintainer feedback, both combined and per-axis scroll settings should multiply with the window-specific scroll factor, not override it. This ensures consistent behavior regardless of configuration method. Also removed the now-unused has_per_axis_override() method. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Final cleanup: remove redundant comments and unused snapshot files - Removed unused snapshot files (now using inline snapshots) - Removed redundant inline comments in tests - Simplified test descriptions to be more concise 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Convert scroll factor parsing tests to use assert_debug_snapshot Updates parse_scroll_factor_combined, parse_scroll_factor_split, and parse_scroll_factor_partial tests to use assert_debug_snapshot instead of manual assert_eq comparisons, as requested in PR review. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Convert to inline snapshots as requested - Convert all scroll factor parsing tests to use inline snapshots instead of external files - Remove external snapshot files to keep test directory clean - All tests still pass with inline snapshot assertions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix missed assert_eq in parse_scroll_factor_mixed test Converts the remaining assert_eq calls to assert_debug_snapshot with inline snapshots in the mixed syntax test function. Also fixes raw string delimiters from ### to #. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Convert scroll_factor_h_v_factors test to use assert_debug_snapshot Makes all scroll factor tests consistent by using snapshots instead of assert_eq for better maintainability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fixes --------- Co-authored-by: Bernardo Kuri <github@bkuri.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Diffstat (limited to 'niri-config/src/lib.rs')
-rw-r--r--niri-config/src/lib.rs282
1 files changed, 272 insertions, 10 deletions
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 {