diff options
| -rw-r--r-- | niri-config/src/lib.rs | 16 | ||||
| -rw-r--r-- | src/input/mod.rs | 57 | ||||
| -rw-r--r-- | 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<Bind>); pub struct Bind { pub key: Key, pub action: Action, + pub repeat: bool, pub cooldown: Option<Duration>, pub allow_when_locked: bool, } @@ -2217,11 +2218,15 @@ where .parse::<Key>() .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<u32>, pub bind_cooldown_timers: HashMap<Key, RegistrationToken>, + pub bind_repeat_timer: Option<RegistrationToken>, pub keyboard_focus: KeyboardFocus, pub idle_inhibiting_surfaces: HashSet<WlSurface>, pub is_fdo_idle_inhibited: Arc<AtomicBool>, @@ -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, |
