diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-08-27 10:06:41 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-08-27 10:43:57 +0300 |
| commit | ab52bb9521faa897727d5eff278fc4310390ff07 (patch) | |
| tree | 839dc03cab640fd396a628a63898d8fac3c34ec5 /niri-config/src/input.rs | |
| parent | da5e33775cbb7a4e64189360b4eb00eab6dbb1b3 (diff) | |
| download | niri-ab52bb9521faa897727d5eff278fc4310390ff07.tar.gz niri-ab52bb9521faa897727d5eff278fc4310390ff07.tar.bz2 niri-ab52bb9521faa897727d5eff278fc4310390ff07.zip | |
config: Extract input
Diffstat (limited to 'niri-config/src/input.rs')
| -rw-r--r-- | niri-config/src/input.rs | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/niri-config/src/input.rs b/niri-config/src/input.rs new file mode 100644 index 00000000..a5b546df --- /dev/null +++ b/niri-config/src/input.rs @@ -0,0 +1,487 @@ +use std::str::FromStr; + +use miette::miette; +use smithay::input::keyboard::XkbConfig; +use smithay::reexports::input; + +use crate::{FloatOrInt, Modifiers, Percent}; + +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Input { + #[knuffel(child, default)] + pub keyboard: Keyboard, + #[knuffel(child, default)] + pub touchpad: Touchpad, + #[knuffel(child, default)] + pub mouse: Mouse, + #[knuffel(child, default)] + pub trackpoint: Trackpoint, + #[knuffel(child, default)] + pub trackball: Trackball, + #[knuffel(child, default)] + pub tablet: Tablet, + #[knuffel(child, default)] + pub touch: Touch, + #[knuffel(child)] + pub disable_power_key_handling: bool, + #[knuffel(child)] + pub warp_mouse_to_focus: Option<WarpMouseToFocus>, + #[knuffel(child)] + pub focus_follows_mouse: Option<FocusFollowsMouse>, + #[knuffel(child)] + pub workspace_auto_back_and_forth: bool, + #[knuffel(child, unwrap(argument, str))] + pub mod_key: Option<ModKey>, + #[knuffel(child, unwrap(argument, str))] + pub mod_key_nested: Option<ModKey>, +} + +#[derive(knuffel::Decode, Debug, PartialEq, Eq)] +pub struct Keyboard { + #[knuffel(child, default)] + pub xkb: Xkb, + // The defaults were chosen to match wlroots and sway. + #[knuffel(child, unwrap(argument), default = Self::default().repeat_delay)] + pub repeat_delay: u16, + #[knuffel(child, unwrap(argument), default = Self::default().repeat_rate)] + pub repeat_rate: u8, + #[knuffel(child, unwrap(argument), default)] + pub track_layout: TrackLayout, + #[knuffel(child)] + pub numlock: bool, +} + +impl Default for Keyboard { + fn default() -> Self { + Self { + xkb: Default::default(), + repeat_delay: 600, + repeat_rate: 25, + track_layout: Default::default(), + numlock: Default::default(), + } + } +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq, Clone)] +pub struct Xkb { + #[knuffel(child, unwrap(argument), default)] + pub rules: String, + #[knuffel(child, unwrap(argument), default)] + pub model: String, + #[knuffel(child, unwrap(argument), default)] + pub layout: String, + #[knuffel(child, unwrap(argument), default)] + pub variant: String, + #[knuffel(child, unwrap(argument))] + pub options: Option<String>, + #[knuffel(child, unwrap(argument))] + pub file: Option<String>, +} + +impl Xkb { + pub fn to_xkb_config(&self) -> XkbConfig<'_> { + XkbConfig { + rules: &self.rules, + model: &self.model, + layout: &self.layout, + variant: &self.variant, + options: self.options.clone(), + } + } +} + +#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)] +pub enum TrackLayout { + /// The layout change is global. + #[default] + Global, + /// The layout change is window local. + 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)] + pub off: bool, + #[knuffel(child)] + pub tap: bool, + #[knuffel(child)] + pub dwt: bool, + #[knuffel(child)] + pub dwtp: bool, + #[knuffel(child, unwrap(argument))] + pub drag: Option<bool>, + #[knuffel(child)] + pub drag_lock: bool, + #[knuffel(child)] + pub natural_scroll: bool, + #[knuffel(child, unwrap(argument, str))] + pub click_method: Option<ClickMethod>, + #[knuffel(child, unwrap(argument), default)] + pub accel_speed: FloatOrInt<-1, 1>, + #[knuffel(child, unwrap(argument, str))] + pub accel_profile: Option<AccelProfile>, + #[knuffel(child, unwrap(argument, str))] + pub scroll_method: Option<ScrollMethod>, + #[knuffel(child, unwrap(argument))] + pub scroll_button: Option<u32>, + #[knuffel(child)] + pub scroll_button_lock: bool, + #[knuffel(child, unwrap(argument, str))] + pub tap_button_map: Option<TapButtonMap>, + #[knuffel(child)] + pub left_handed: bool, + #[knuffel(child)] + pub disabled_on_external_mouse: bool, + #[knuffel(child)] + pub middle_emulation: bool, + #[knuffel(child)] + pub scroll_factor: Option<ScrollFactor>, +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Mouse { + #[knuffel(child)] + pub off: bool, + #[knuffel(child)] + pub natural_scroll: bool, + #[knuffel(child, unwrap(argument), default)] + pub accel_speed: FloatOrInt<-1, 1>, + #[knuffel(child, unwrap(argument, str))] + pub accel_profile: Option<AccelProfile>, + #[knuffel(child, unwrap(argument, str))] + pub scroll_method: Option<ScrollMethod>, + #[knuffel(child, unwrap(argument))] + pub scroll_button: Option<u32>, + #[knuffel(child)] + pub scroll_button_lock: bool, + #[knuffel(child)] + pub left_handed: bool, + #[knuffel(child)] + pub middle_emulation: bool, + #[knuffel(child)] + pub scroll_factor: Option<ScrollFactor>, +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Trackpoint { + #[knuffel(child)] + pub off: bool, + #[knuffel(child)] + pub natural_scroll: bool, + #[knuffel(child, unwrap(argument), default)] + pub accel_speed: FloatOrInt<-1, 1>, + #[knuffel(child, unwrap(argument, str))] + pub accel_profile: Option<AccelProfile>, + #[knuffel(child, unwrap(argument, str))] + pub scroll_method: Option<ScrollMethod>, + #[knuffel(child, unwrap(argument))] + pub scroll_button: Option<u32>, + #[knuffel(child)] + pub scroll_button_lock: bool, + #[knuffel(child)] + pub left_handed: bool, + #[knuffel(child)] + pub middle_emulation: bool, +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Trackball { + #[knuffel(child)] + pub off: bool, + #[knuffel(child)] + pub natural_scroll: bool, + #[knuffel(child, unwrap(argument), default)] + pub accel_speed: FloatOrInt<-1, 1>, + #[knuffel(child, unwrap(argument, str))] + pub accel_profile: Option<AccelProfile>, + #[knuffel(child, unwrap(argument, str))] + pub scroll_method: Option<ScrollMethod>, + #[knuffel(child, unwrap(argument))] + pub scroll_button: Option<u32>, + #[knuffel(child)] + pub scroll_button_lock: bool, + #[knuffel(child)] + pub left_handed: bool, + #[knuffel(child)] + pub middle_emulation: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ClickMethod { + Clickfinger, + ButtonAreas, +} + +impl From<ClickMethod> for input::ClickMethod { + fn from(value: ClickMethod) -> Self { + match value { + ClickMethod::Clickfinger => Self::Clickfinger, + ClickMethod::ButtonAreas => Self::ButtonAreas, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AccelProfile { + Adaptive, + Flat, +} + +impl From<AccelProfile> for input::AccelProfile { + fn from(value: AccelProfile) -> Self { + match value { + AccelProfile::Adaptive => Self::Adaptive, + AccelProfile::Flat => Self::Flat, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScrollMethod { + NoScroll, + TwoFinger, + Edge, + OnButtonDown, +} + +impl From<ScrollMethod> for input::ScrollMethod { + fn from(value: ScrollMethod) -> Self { + match value { + ScrollMethod::NoScroll => Self::NoScroll, + ScrollMethod::TwoFinger => Self::TwoFinger, + ScrollMethod::Edge => Self::Edge, + ScrollMethod::OnButtonDown => Self::OnButtonDown, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TapButtonMap { + LeftRightMiddle, + LeftMiddleRight, +} + +impl From<TapButtonMap> for input::TapButtonMap { + fn from(value: TapButtonMap) -> Self { + match value { + TapButtonMap::LeftRightMiddle => Self::LeftRightMiddle, + TapButtonMap::LeftMiddleRight => Self::LeftMiddleRight, + } + } +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Tablet { + #[knuffel(child)] + pub off: bool, + #[knuffel(child, unwrap(arguments))] + pub calibration_matrix: Option<Vec<f32>>, + #[knuffel(child, unwrap(argument))] + pub map_to_output: Option<String>, + #[knuffel(child)] + pub left_handed: bool, +} + +#[derive(knuffel::Decode, Debug, Default, PartialEq)] +pub struct Touch { + #[knuffel(child)] + pub off: bool, + #[knuffel(child, unwrap(argument))] + pub map_to_output: Option<String>, +} + +#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] +pub struct FocusFollowsMouse { + #[knuffel(property, str)] + pub max_scroll_amount: Option<Percent>, +} + +#[derive(knuffel::Decode, Debug, PartialEq, Eq, Clone, Copy)] +pub struct WarpMouseToFocus { + #[knuffel(property, str)] + pub mode: Option<WarpMouseToFocusMode>, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum WarpMouseToFocusMode { + CenterXy, + CenterXyAlways, +} + +impl FromStr for WarpMouseToFocusMode { + type Err = miette::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "center-xy" => Ok(Self::CenterXy), + "center-xy-always" => Ok(Self::CenterXyAlways), + _ => Err(miette!( + r#"invalid mode for warp-mouse-to-focus, can be "center-xy" or "center-xy-always" (or leave unset for separate centering)"# + )), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ModKey { + Ctrl, + Shift, + Alt, + Super, + IsoLevel3Shift, + IsoLevel5Shift, +} + +impl ModKey { + pub fn to_modifiers(&self) -> Modifiers { + match self { + ModKey::Ctrl => Modifiers::CTRL, + ModKey::Shift => Modifiers::SHIFT, + ModKey::Alt => Modifiers::ALT, + ModKey::Super => Modifiers::SUPER, + ModKey::IsoLevel3Shift => Modifiers::ISO_LEVEL3_SHIFT, + ModKey::IsoLevel5Shift => Modifiers::ISO_LEVEL5_SHIFT, + } + } +} + +impl FromStr for ModKey { + type Err = miette::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match &*s.to_ascii_lowercase() { + "ctrl" | "control" => Ok(Self::Ctrl), + "shift" => Ok(Self::Shift), + "alt" => Ok(Self::Alt), + "super" | "win" => Ok(Self::Super), + "iso_level3_shift" | "mod5" => Ok(Self::IsoLevel3Shift), + "iso_level5_shift" | "mod3" => Ok(Self::IsoLevel5Shift), + _ => Err(miette!("invalid Mod key: {s}")), + } + } +} + +impl FromStr for ClickMethod { + type Err = miette::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "clickfinger" => Ok(Self::Clickfinger), + "button-areas" => Ok(Self::ButtonAreas), + _ => Err(miette!( + r#"invalid click method, can be "button-areas" or "clickfinger""# + )), + } + } +} + +impl FromStr for AccelProfile { + type Err = miette::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "adaptive" => Ok(Self::Adaptive), + "flat" => Ok(Self::Flat), + _ => Err(miette!( + r#"invalid accel profile, can be "adaptive" or "flat""# + )), + } + } +} + +impl FromStr for ScrollMethod { + type Err = miette::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "no-scroll" => Ok(Self::NoScroll), + "two-finger" => Ok(Self::TwoFinger), + "edge" => Ok(Self::Edge), + "on-button-down" => Ok(Self::OnButtonDown), + _ => Err(miette!( + r#"invalid scroll method, can be "no-scroll", "two-finger", "edge", or "on-button-down""# + )), + } + } +} + +impl FromStr for TapButtonMap { + type Err = miette::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "left-right-middle" => Ok(Self::LeftRightMiddle), + "left-middle-right" => Ok(Self::LeftMiddleRight), + _ => Err(miette!( + r#"invalid tap button map, can be "left-right-middle" or "left-middle-right""# + )), + } + } +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use super::*; + + #[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, + ) + "); + } +} |
