From d3aebdbec4ae2c1ac4199cdd4e95a8d218362b25 Mon Sep 17 00:00:00 2001 From: Salman Farooq Date: Sun, 30 Jun 2024 22:37:44 +0500 Subject: Implement key repeat for compositor binds --- niri-config/src/lib.rs | 16 ++++++++++++++ src/input/mod.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/niri.rs | 2 ++ 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 39d78522..aaf81bc0 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -886,6 +886,7 @@ pub struct Binds(pub Vec); pub struct Bind { pub key: Key, pub action: Action, + pub repeat: bool, pub cooldown: Option, pub allow_when_locked: bool, } @@ -2217,11 +2218,15 @@ where .parse::() .map_err(|e| DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind")))?; + let mut repeat = true; let mut cooldown = None; let mut allow_when_locked = false; let mut allow_when_locked_node = None; for (name, val) in &node.properties { match &***name { + "repeat" => { + repeat = knuffel::traits::DecodeScalar::decode(val, ctx)?; + } "cooldown-ms" => { cooldown = Some(Duration::from_millis( knuffel::traits::DecodeScalar::decode(val, ctx)?, @@ -2249,6 +2254,7 @@ where let dummy = Self { key, action: Action::Spawn(vec![]), + repeat: true, cooldown: None, allow_when_locked: false, }; @@ -2276,6 +2282,7 @@ where Ok(Self { key, action, + repeat, cooldown, allow_when_locked, }) @@ -2844,6 +2851,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR, }, action: Action::Spawn(vec!["alacritty".to_owned()]), + repeat: true, cooldown: None, allow_when_locked: true, }, @@ -2853,6 +2861,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR, }, action: Action::CloseWindow, + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2862,6 +2871,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT, }, action: Action::FocusMonitorLeft, + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2871,6 +2881,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL, }, action: Action::MoveWindowToMonitorRight, + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2880,6 +2891,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR, }, action: Action::ConsumeWindowIntoColumn, + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2889,6 +2901,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR, }, action: Action::FocusWorkspace(WorkspaceReference::Index(1)), + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2900,6 +2913,7 @@ mod tests { action: Action::FocusWorkspace(WorkspaceReference::Name( "workspace-1".to_string(), )), + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2909,6 +2923,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT, }, action: Action::Quit(true), + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2918,6 +2933,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR, }, action: Action::FocusWorkspaceDown, + repeat: true, cooldown: Some(Duration::from_millis(150)), allow_when_locked: false, }, diff --git a/src/input/mod.rs b/src/input/mod.rs index 2a1073f9..7fa363c0 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -294,6 +294,19 @@ impl State { let time = Event::time_msec(&event); let pressed = event.state() == KeyState::Pressed; + // Stop bind key repeat on any release. This won't work 100% correctly in cases like: + // 1. Press Mod + // 2. Press Left (repeat starts) + // 3. Press PgDown (new repeat starts) + // 4. Release Left (PgDown repeat stops) + // But it's good enough for now. + // FIXME: handle this properly. + if !pressed { + if let Some(token) = self.niri.bind_repeat_timer.take() { + self.niri.event_loop.remove(token); + } + } + let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input( self, event.key_code(), @@ -330,12 +343,44 @@ impl State { return; }; - // Filter actions when the key is released or the session is locked. if !pressed { return; } - self.handle_bind(bind); + self.handle_bind(bind.clone()); + + // Start the key repeat timer if necessary. + if !bind.repeat { + return; + } + + // Stop the previous key repeat if any. + if let Some(token) = self.niri.bind_repeat_timer.take() { + self.niri.event_loop.remove(token); + } + + let config = self.niri.config.borrow(); + let config = &config.input.keyboard; + + let repeat_rate = config.repeat_rate; + if repeat_rate == 0 { + return; + } + let repeat_duration = Duration::from_secs_f64(1. / f64::from(repeat_rate)); + + let repeat_timer = + Timer::from_duration(Duration::from_millis(u64::from(config.repeat_delay))); + + let token = self + .niri + .event_loop + .insert_source(repeat_timer, move |_, _, state| { + state.handle_bind(bind.clone()); + TimeoutAction::ToDuration(repeat_duration) + }) + .unwrap(); + + self.niri.bind_repeat_timer = Some(token); } pub fn handle_bind(&mut self, bind: Bind) { @@ -2132,6 +2177,7 @@ fn should_intercept_key( modifiers: Modifiers::empty(), }, action, + repeat: true, cooldown: None, allow_when_locked: false, }); @@ -2181,6 +2227,7 @@ fn find_bind( modifiers: Modifiers::empty(), }, action, + repeat: true, cooldown: None, allow_when_locked: false, }); @@ -2512,6 +2559,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR | Modifiers::CTRL, }, action: Action::CloseWindow, + repeat: true, cooldown: None, allow_when_locked: false, }]); @@ -2645,6 +2693,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR, }, action: Action::CloseWindow, + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2654,6 +2703,7 @@ mod tests { modifiers: Modifiers::SUPER, }, action: Action::FocusColumnLeft, + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2663,6 +2713,7 @@ mod tests { modifiers: Modifiers::empty(), }, action: Action::FocusWindowDown, + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2672,6 +2723,7 @@ mod tests { modifiers: Modifiers::COMPOSITOR | Modifiers::SUPER, }, action: Action::FocusWindowUp, + repeat: true, cooldown: None, allow_when_locked: false, }, @@ -2681,6 +2733,7 @@ mod tests { modifiers: Modifiers::SUPER | Modifiers::ALT, }, action: Action::FocusColumnRight, + repeat: true, cooldown: None, allow_when_locked: false, }, diff --git a/src/niri.rs b/src/niri.rs index 54211bce..177f5765 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -234,6 +234,7 @@ pub struct Niri { /// Scancodes of the keys to suppress. pub suppressed_keys: HashSet, pub bind_cooldown_timers: HashMap, + pub bind_repeat_timer: Option, pub keyboard_focus: KeyboardFocus, pub idle_inhibiting_surfaces: HashSet, pub is_fdo_idle_inhibited: Arc, @@ -1657,6 +1658,7 @@ impl Niri { popup_grab: None, suppressed_keys: HashSet::new(), bind_cooldown_timers: HashMap::new(), + bind_repeat_timer: Option::default(), presentation_state, security_context_state, gamma_control_manager_state, -- cgit