From 23fab37e1653d299f28de07991fafef6260af578 Mon Sep 17 00:00:00 2001 From: Jan Tache Date: Mon, 1 Aug 2022 16:56:01 -0700 Subject: [PATCH] Implement one shot functionality --- src/action.rs | 41 +++++ src/layout.rs | 457 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 489 insertions(+), 9 deletions(-) diff --git a/src/action.rs b/src/action.rs index 911cc26..31d84c1 100644 --- a/src/action.rs +++ b/src/action.rs @@ -167,6 +167,36 @@ where pub tap_hold_interval: u16, } +/// Define one shot key behaviour. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct OneShot +where + T: 'static, +{ + /// Action to activate until timeout expires or exactly one non-one-shot key is activated. + pub action: &'static Action, + /// Timeout after which one shot will expire. Note: timeout will be overwritten if another + /// one shot key is pressed. + pub timeout: u16, + /// Configuration of one shot end behaviour. Note: this will be overwritten if another one shot + /// key is pressed. Consider keeping this consistent between all your one shot keys to prevent + /// surprising behaviour. + pub end_config: OneShotEndConfig, +} + +/// Determine the ending behaviour of the one shot key. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum OneShotEndConfig { + /// End one shot activation on first non-one-shot key press. + EndOnFirstPress, + /// End one shot activation on first non-one-shot key release. + EndOnFirstRelease, +} + +/// Defines the maximum number of one shot keys that can be combined. +pub const ONE_SHOT_MAX_ACTIVE: usize = 8; + /// The different actions that can be done. #[non_exhaustive] #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -202,6 +232,17 @@ where /// to drive any non keyboard related actions that you might /// manage with key events. Custom(T), + /// One shot key. Also known as "sticky key". See `struct OneShot` for configuration info. + /// Activates `action` until a single other key that is not also a one shot key is used. For + /// example, a one shot key can be used to activate shift for exactly one keypress or switch to + /// another layer for exactly one keypress. Holding a one shot key will be treated as a normal + /// held keypress. + /// + /// If you use one shot outside of its intended use cases (modifier key action or layer + /// action) then you will likely have undesired behaviour. E.g. one shot with the space + /// key will hold space until either another key is pressed or the timeout occurs, which will + /// probably send many undesired space characters to your active application. + OneShot(&'static OneShot), } impl Action { /// Gets the layer number if the action is the `Layer` action. diff --git a/src/layout.rs b/src/layout.rs index dd9d1c5..0f8460a 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -46,7 +46,7 @@ /// ``` pub use keyberon_macros::*; -use crate::action::{Action, HoldTapConfig, HoldTapAction}; +use crate::action::{Action, HoldTapAction, HoldTapConfig, OneShotEndConfig, ONE_SHOT_MAX_ACTIVE}; use crate::key_code::KeyCode; use arraydeque::ArrayDeque; use heapless::Vec; @@ -79,6 +79,7 @@ where states: Vec, 64>, waiting: Option>, stacked: Stack, + oneshot: OneShotState, tap_hold_tracker: TapHoldTracker, } @@ -278,6 +279,74 @@ impl WaitingState { } } +type OneShotKeys = [(u8, u8); ONE_SHOT_MAX_ACTIVE]; +type ReleasedOneShotKeys = Vec<(u8, u8), ONE_SHOT_MAX_ACTIVE>; + +/// Contains the state of one shot keys that are currently active. +struct OneShotState { + /// Coordinates of one shot keys that are active + keys: ArrayDeque, + /// Coordinates of one shot keys that have been released + released_keys: ArrayDeque, + /// Timeout (ms) after which all one shot keys expire + timeout: u16, + /// Contains the end config of the most recently pressed one shot key + end_config: OneShotEndConfig, + /// Marks if release of the one shot keys should be done on the next tick + release_on_next_tick: bool, +} + +impl OneShotState { + fn tick(&mut self) -> Option { + if self.keys.is_empty() { + return None; + } + self.timeout = self.timeout.saturating_sub(1); + if self.release_on_next_tick || self.timeout == 0 { + self.release_on_next_tick = false; + self.timeout = 0; + self.keys.clear(); + Some(self.released_keys.drain(..).collect()) + } else { + None + } + } + + fn handle_press(&mut self, (i, j): (u8, u8), is_oneshot_key: bool) { + if self.keys.is_empty() { + return; + } + match (is_oneshot_key, self.end_config) { + (true, _) => { + // a one shot key release if it's re-pressed + self.released_keys.retain(|coord| *coord != (i, j)); + } + (_, OneShotEndConfig::EndOnFirstPress) => { + self.release_on_next_tick = true; + } + _ => {} + } + } + + /// Returns true if the caller should handle the release normally and false otherwise. + /// The second value in the tuple represents an overflow of released one shot keys and should + /// be released is it is `Some`. + fn handle_release(&mut self, (i, j): (u8, u8)) -> (bool, Option<(u8, u8)>) { + if self.keys.is_empty() { + return (true, None); + } + if !self.keys.contains(&(i, j)) { + if self.end_config == OneShotEndConfig::EndOnFirstRelease { + self.release_on_next_tick = true; + } + return (true, None); + } else { + // delay release for one shot keys + (false, self.released_keys.push_back((i, j))) + } + } +} + /// An iterator over the currently stacked events. /// /// Events can be retrieved by iterating over this struct and calling [Stacked::event]. @@ -336,6 +405,13 @@ impl Layout Layout match w.tick(&self.stacked) { + + let mut custom = CustomEvent::NoEvent; + if let Some(released_keys) = self.oneshot.tick() { + for key in released_keys.iter() { + custom.update(self.unstack(Stacked { + event: Event::Release(key.0, key.1), + since: 0, + })); + } + } + + custom.update(match &mut self.waiting { + Some(w) => match w.tick(&mut self.stacked) { Some(WaitingAction::Hold) => self.waiting_into_hold(), Some(WaitingAction::Tap) => self.waiting_into_tap(), Some(WaitingAction::NoOp) => self.drop_waiting(), @@ -391,22 +478,38 @@ impl Layout self.unstack(s), None => CustomEvent::NoEvent, }, - } + }); + + custom } fn unstack(&mut self, stacked: Stacked) -> CustomEvent { use Event::*; match stacked.event { Release(i, j) => { let mut custom = CustomEvent::NoEvent; - self.states = self - .states - .iter() - .filter_map(|s| s.release((i, j), &mut custom)) - .collect(); + + let (do_release, overflow_key) = self.oneshot.handle_release((i, j)); + if do_release { + self.states = self + .states + .iter() + .filter_map(|s| s.release((i, j), &mut custom)) + .collect(); + } + if let Some((i2, j2)) = overflow_key { + self.states = self + .states + .iter() + .filter_map(|s| s.release((i2, j2), &mut custom)) + .collect(); + } + custom } Press(i, j) => { let action = self.press_as_action((i, j), self.current_layer()); + self.oneshot + .handle_press((i, j), matches!(action, Action::OneShot(..))); self.do_action(action, (i, j), stacked.since) } } @@ -475,6 +578,15 @@ impl Layout { + self.tap_hold_tracker.coord = coord; + self.do_action(oneshot.action, coord, delay); + self.oneshot.timeout = oneshot.timeout; + self.oneshot.end_config = oneshot.end_config; + if let Some(overflow) = self.oneshot.keys.push_back((coord.0, coord.1)) { + self.event(Event::Release(overflow.0, overflow.1)); + } + } &KeyCode(keycode) => { self.tap_hold_tracker.coord = coord; let _ = self.states.push(NormalKey { coord, keycode }); @@ -1215,4 +1327,331 @@ mod test { assert_keys(&[Enter], layout.keycodes()); } } + + #[test] + fn one_shot() { + static LAYERS: Layers<3, 1, 1> = [[[ + OneShot(&crate::action::OneShot { + timeout: 100, + action: &k(LShift), + end_config: OneShotEndConfig::EndOnFirstPress, + }), + k(A), + k(B), + ]]]; + let mut layout = Layout::new(&LAYERS); + + // Test: + // 1. press one-shot + // 2. release one-shot + // 3. press A within timeout + // 4. press B within timeout + // 5. release A, B + layout.event(Press(0, 0)); + for _ in 0..25 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Release(0, 0)); + for _ in 0..25 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Press(0, 1)); + layout.event(Press(0, 2)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[A, LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[A, B], layout.keycodes()); + layout.event(Release(0, 1)); + layout.event(Release(0, 2)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[B], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test: + // 1. press one-shot + // 2. release one-shot + // 3. press A after timeout + // 4. release A + layout.event(Press(0, 0)); + for _ in 0..25 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Release(0, 0)); + for _ in 0..75 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[A], layout.keycodes()); + layout.event(Release(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test: + // 1. press one-shot + // 2. press A + // 3. release A + // 4. release one-shot + layout.event(Press(0, 0)); + for _ in 0..25 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Press(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, A], layout.keycodes()); + layout.event(Release(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test: + // 1. press one-shot + // 2. press A after timeout + // 3. release A + // 4. release one-shot + layout.event(Press(0, 0)); + for _ in 0..200 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Press(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, A], layout.keycodes()); + layout.event(Release(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test: + // 1. press one-shot + // 2. release one-shot + // 3. press one-shot within timeout + // 4. release one-shot + layout.event(Press(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Press(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + for _ in 0..200 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + } + + #[test] + fn one_shot_end_on_release() { + static LAYERS: Layers<3, 1, 1> = [[[ + OneShot(&crate::action::OneShot { + timeout: 100, + action: &k(LShift), + end_config: OneShotEndConfig::EndOnFirstRelease, + }), + k(A), + k(B), + ]]]; + let mut layout = Layout::new(&LAYERS); + + // Test: + // 1. press one-shot + // 2. release one-shot + // 3. press A within timeout + // 4. press B within timeout + // 5. release A, B + layout.event(Press(0, 0)); + for _ in 0..25 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Release(0, 0)); + for _ in 0..25 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Press(0, 1)); + layout.event(Press(0, 2)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[A, LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[A, B, LShift], layout.keycodes()); + layout.event(Release(0, 1)); + layout.event(Release(0, 2)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[B, LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test: + // 1. press one-shot + // 2. release one-shot + // 3. press A after timeout + // 4. release A + layout.event(Press(0, 0)); + for _ in 0..25 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Release(0, 0)); + for _ in 0..75 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[A], layout.keycodes()); + layout.event(Release(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test: + // 1. press one-shot + // 2. press A + // 3. release A + // 4. release one-shot + layout.event(Press(0, 0)); + for _ in 0..25 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Press(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, A], layout.keycodes()); + layout.event(Release(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test: + // 1. press one-shot + // 2. press A after timeout + // 3. release A + // 4. release one-shot + layout.event(Press(0, 0)); + for _ in 0..200 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Press(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, A], layout.keycodes()); + layout.event(Release(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test: + // 1. press one-shot + // 2. release one-shot + // 3. press one-shot within timeout + // 4. release one-shot + layout.event(Press(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + layout.event(Press(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + for _ in 0..200 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + } + + #[test] + fn one_shot_multi() { + static LAYERS: Layers<4, 1, 2> = [ + [[ + OneShot(&crate::action::OneShot { + timeout: 100, + action: &k(LShift), + end_config: OneShotEndConfig::EndOnFirstPress, + }), + OneShot(&crate::action::OneShot { + timeout: 100, + action: &k(LCtrl), + end_config: OneShotEndConfig::EndOnFirstPress, + }), + OneShot(&crate::action::OneShot { + timeout: 100, + action: &Layer(1), + end_config: OneShotEndConfig::EndOnFirstPress, + }), + NoOp, + ]], + [[k(A), k(B), k(C), k(D)]], + ]; + let mut layout = Layout::new(&LAYERS); + + layout.event(Press(0, 0)); + layout.event(Release(0, 0)); + for _ in 0..90 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + } + layout.event(Press(0, 1)); + for _ in 0..90 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, LCtrl], layout.keycodes()); + } + assert_eq!(layout.current_layer(), 0); + layout.event(Press(0, 2)); + layout.event(Release(0, 2)); + for _ in 0..90 { + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, LCtrl], layout.keycodes()); + assert_eq!(layout.current_layer(), 1); + } + layout.event(Press(0, 3)); + layout.event(Release(0, 3)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, LCtrl, D], layout.keycodes()); + assert_eq!(layout.current_layer(), 1); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LCtrl], layout.keycodes()); + assert_eq!(layout.current_layer(), 0); + layout.event(Release(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + } }