diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 2946b8c..86c799a 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -58,8 +58,8 @@ pub struct Config { #[derive(knuffel::Decode, Debug, Default, PartialEq)] pub struct Input { - #[knuffel(child, default)] - pub keyboard: Keyboard, + #[knuffel(children(name = "keyboard"), default)] + pub keyboards: Vec, #[knuffel(child, default)] pub touchpad: Touchpad, #[knuffel(child, default)] @@ -80,8 +80,28 @@ pub struct Input { pub workspace_auto_back_and_forth: bool, } -#[derive(knuffel::Decode, Debug, PartialEq, Eq)] +impl Input { + pub fn fallback_keyboard(&self) -> Keyboard { + self.keyboards + .iter() + .find(|keyboard| keyboard.name.is_none()) + .cloned() + .unwrap_or_default() + } + + pub fn keyboard_named>(&self, name: K) -> Keyboard { + self.keyboards + .iter() + .find(|keyboard| keyboard.name.as_deref() == Some(name.as_ref())) + .cloned() + .unwrap_or_else(|| self.fallback_keyboard()) + } +} + +#[derive(knuffel::Decode, Debug, PartialEq, Eq, Clone)] pub struct Keyboard { + #[knuffel(argument, unwrap(argument))] + pub name: Option, #[knuffel(child, default)] pub xkb: Xkb, // The defaults were chosen to match wlroots and sway. @@ -96,6 +116,7 @@ pub struct Keyboard { impl Default for Keyboard { fn default() -> Self { Self { + name: None, xkb: Default::default(), repeat_delay: 600, repeat_rate: 25, @@ -142,7 +163,7 @@ pub enum CenterFocusedColumn { OnOverflow, } -#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)] +#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq, Clone)] pub enum TrackLayout { /// The layout change is global. #[default] @@ -2489,6 +2510,16 @@ mod tests { } } + keyboard "test_keyboard" { + repeat-delay 500 + repeat-rate 30 + track-layout "window" + xkb { + layout "us,ru" + options "grp:win_space_toggle" + } + } + touchpad { tap dwt @@ -2651,16 +2682,30 @@ mod tests { "##, Config { input: Input { - keyboard: Keyboard { - xkb: Xkb { - layout: "us,ru".to_owned(), - options: Some("grp:win_space_toggle".to_owned()), + keyboards: vec![ + Keyboard { + xkb: Xkb { + layout: "us,ru".to_owned(), + options: Some("grp:win_space_toggle".to_owned()), + ..Default::default() + }, + repeat_delay: 600, + repeat_rate: 25, + track_layout: TrackLayout::Window, ..Default::default() }, - repeat_delay: 600, - repeat_rate: 25, - track_layout: TrackLayout::Window, - }, + Keyboard { + xkb: Xkb { + layout: "us,ru".to_owned(), + options: Some("grp:win_space_toggle".to_owned()), + ..Default::default() + }, + repeat_delay: 500, + repeat_rate: 30, + track_layout: TrackLayout::Window, + name: Some("test_keyboard".to_owned()), + }, + ], touchpad: Touchpad { off: false, tap: true, @@ -3067,7 +3112,7 @@ mod tests { #[test] fn default_repeat_params() { let config = Config::parse("config.kdl", "").unwrap(); - assert_eq!(config.input.keyboard.repeat_delay, 600); - assert_eq!(config.input.keyboard.repeat_rate, 25); + assert_eq!(config.input.fallback_keyboard().repeat_delay, 600); + assert_eq!(config.input.fallback_keyboard().repeat_rate, 25); } } diff --git a/src/input/mod.rs b/src/input/mod.rs index e74ff10..437a39c 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -287,7 +287,12 @@ impl State { Some(pos + target_geo.loc.to_f64()) } - fn on_keyboard(&mut self, event: I::KeyboardKeyEvent) { + fn on_keyboard(&mut self, event: I::KeyboardKeyEvent) + where + I::Device: 'static, + { + self.apply_keyboard_config::(&event); + let comp_mod = self.backend.mod_key(); let serial = SERIAL_COUNTER.next_serial(); @@ -383,6 +388,57 @@ impl State { self.niri.bind_repeat_timer = Some(token); } + fn apply_keyboard_config(&mut self, event: &I::KeyboardKeyEvent) + where + I::Device: 'static, + { + let device = event.device(); + + let keyboard_config = + if let Some(input_device) = (&device as &dyn Any).downcast_ref::() { + if let Some(cached) = self.niri.keyboard_device_cache.get(input_device).cloned() { + cached + } else { + let keyboard_config = self + .niri + .config + .borrow() + .input + .keyboard_named(device.name()); + + self.niri + .keyboard_device_cache + .insert(input_device.clone(), keyboard_config.clone()); + + keyboard_config + } + } else { + self.niri.config.borrow().input.fallback_keyboard() + }; + + if keyboard_config != self.niri.current_keyboard { + let keyboard = self.niri.seat.get_keyboard().unwrap(); + + if keyboard_config.xkb != self.niri.current_keyboard.xkb { + if let Err(err) = keyboard.set_xkb_config(self, keyboard_config.xkb.to_xkb_config()) + { + warn!("error updating xkb config: {err:?}"); + } + } + + if keyboard_config.repeat_rate != self.niri.current_keyboard.repeat_rate + || keyboard_config.repeat_delay != self.niri.current_keyboard.repeat_delay + { + keyboard.change_repeat_info( + keyboard_config.repeat_rate.into(), + keyboard_config.repeat_delay.into(), + ); + } + + self.niri.current_keyboard = keyboard_config; + } + } + pub fn handle_bind(&mut self, bind: Bind) { let Some(cooldown) = bind.cooldown else { self.do_action(bind.action, bind.allow_when_locked); diff --git a/src/niri.rs b/src/niri.rs index 124c355..9503568 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -190,7 +190,9 @@ pub struct Niri { // When false, we're idling with monitors powered off. pub monitors_active: bool, + pub current_keyboard: niri_config::Keyboard, pub devices: HashSet, + pub keyboard_device_cache: HashMap, pub tablets: HashMap, pub touch: HashSet, @@ -854,7 +856,7 @@ impl State { } } - if self.niri.config.borrow().input.keyboard.track_layout == TrackLayout::Window { + if self.niri.current_keyboard.track_layout == TrackLayout::Window { let current_layout = keyboard.with_xkb_state(self, |context| context.active_layout()); @@ -954,19 +956,23 @@ impl State { self.niri.cursor_texture_cache.clear(); } + let default_keyboard = config.input.fallback_keyboard(); + + let current_keyboard = self.niri.current_keyboard.clone(); + // We need &mut self to reload the xkb config, so just store it here. - if config.input.keyboard.xkb != old_config.input.keyboard.xkb { - reload_xkb = Some(config.input.keyboard.xkb.clone()); + if default_keyboard.xkb != current_keyboard.xkb { + reload_xkb = Some(default_keyboard.xkb.clone()); } // Reload the repeat info. - if config.input.keyboard.repeat_rate != old_config.input.keyboard.repeat_rate - || config.input.keyboard.repeat_delay != old_config.input.keyboard.repeat_delay + if default_keyboard.repeat_rate != current_keyboard.repeat_rate + || default_keyboard.repeat_delay != current_keyboard.repeat_delay { let keyboard = self.niri.seat.get_keyboard().unwrap(); keyboard.change_repeat_info( - config.input.keyboard.repeat_rate.into(), - config.input.keyboard.repeat_delay.into(), + default_keyboard.repeat_rate.into(), + default_keyboard.repeat_delay.into(), ); } @@ -1532,9 +1538,9 @@ impl Niri { let mut seat: Seat = seat_state.new_wl_seat(&display_handle, backend.seat_name()); seat.add_keyboard( - config_.input.keyboard.xkb.to_xkb_config(), - config_.input.keyboard.repeat_delay.into(), - config_.input.keyboard.repeat_rate.into(), + config_.input.fallback_keyboard().xkb.to_xkb_config(), + config_.input.fallback_keyboard().repeat_delay.into(), + config_.input.fallback_keyboard().repeat_rate.into(), ) .unwrap(); seat.add_pointer(); @@ -1650,7 +1656,9 @@ impl Niri { root_surface: HashMap::new(), monitors_active: true, + current_keyboard: Default::default(), devices: HashSet::new(), + keyboard_device_cache: HashMap::new(), tablets: HashMap::new(), touch: HashSet::new(),