diff --git a/README.md b/README.md index ce8d62f..6a9f27f 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ A huge thanks goes to the following projects: - For providing the logic for keyboard matrix and layouts - [jtroo's `keyberon` fork](https://github.com/jtroo/kanata/tree/main/keyberon) - For the implementation of extra layout actions, like one shot and tap dance keys +- [riskable and borisfaure](https://github.com/TeXitoi/keyberon/pull/122) + - For the implementation of sequences/macros in `keyberon` - [simmsb's corne firmware](https://github.com/simmsb/keyboard) - Very helpful reference for developing a keyboard firmware using [embassy-rs](https://github.com/embassy-rs/embassy) - [TeXitoi's keyseebee project](https://github.com/TeXitoi/keyseebee) diff --git a/docs/src/content/docs/index.md b/docs/src/content/docs/index.md index eb5cde7..ec51f17 100644 --- a/docs/src/content/docs/index.md +++ b/docs/src/content/docs/index.md @@ -89,6 +89,8 @@ A huge thanks goes to the following projects: - For powering the logic for keyboard matrix and layouts - [jtroo's `keyberon` fork](https://github.com/jtroo/kanata/tree/main/keyberon) - For the implementation of extra layout actions, like one shot and tap dance keys +- [riskable and borisfaure](https://github.com/TeXitoi/keyberon/pull/122) + - For the implementation of sequences/macros in `keyberon` - [simmsb's corne firmware](https://github.com/simmsb/keyboard) - Very helpful reference for developing a keyboard firmware using [embassy-rs](https://github.com/embassy-rs/embassy) - [TeXitoi's keyseebee project](https://github.com/TeXitoi/keyseebee) diff --git a/keyberon/src/action.rs b/keyberon/src/action.rs index 4694823..d30d790 100644 --- a/keyberon/src/action.rs +++ b/keyberon/src/action.rs @@ -231,6 +231,27 @@ where pub config: TapDanceConfig, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The different tyeps of actions we support for key sequences/macros +pub enum SequenceEvent +where + K: 'static, +{ + /// No operation action: just do nothing (a placeholder). + NoOp, + /// A keypress/keydown + Press(K), + /// Key release/keyup + Release(K), + /// A shortcut for `Press(K), Release(K)` + Tap(K), + /// For sequences that need to wait a bit before continuing. The number represents how long (in + /// ticks) this Delay will last for. + Delay(u16), + /// Cancels the running sequence and can be used to mark the end of a sequence. + Complete, +} + /// The different actions that can be done. #[non_exhaustive] #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -283,6 +304,8 @@ where /// - `timeout` ticks elapse since the last tap of the same tap-dance key /// - the number of taps is equal to the length of `actions`. TapDance(&'static TapDanceAction), + /// An array of SequenceEvents that will be triggered (in order) + Sequence(&'static &'static [SequenceEvent]), /// Custom action. /// /// Define a user defined action. This enum can be anything you diff --git a/keyberon/src/layout.rs b/keyberon/src/layout.rs index 3ace971..3cda5bf 100644 --- a/keyberon/src/layout.rs +++ b/keyberon/src/layout.rs @@ -47,8 +47,8 @@ pub use keyberon_macros::*; use crate::action::{ - Action, HoldTapAction, HoldTapConfig, OneShotAction, OneShotEndConfig, TapDanceAction, - TapDanceConfig, + Action, HoldTapAction, HoldTapConfig, OneShotAction, OneShotEndConfig, SequenceEvent, + TapDanceAction, TapDanceConfig, }; use crate::key_code::KeyCode; use arraydeque::ArrayDeque; @@ -94,6 +94,7 @@ pub struct Layout< waiting: Option>, oneshot: Option, tapdance: Option>, + active_sequences: ArrayDeque<[SequenceState; 4], arraydeque::behavior::Wrapping>, stacked: Stack, tap_hold_tracker: TapHoldTracker, } @@ -185,6 +186,7 @@ impl CustomEvent { #[derive(Debug, Eq, PartialEq)] enum State { NormalKey { keycode: K, coord: (u8, u8) }, + FakeKey { keycode: K }, MomentaryLayerModifier { value: usize, coord: (u8, u8) }, ToggleLayerModifier { value: usize }, Custom { value: T, coord: (u8, u8) }, @@ -196,10 +198,11 @@ impl Clone for State { } } -impl State { +impl State { fn keycode(&self) -> Option { match self { NormalKey { keycode, .. } => Some(*keycode), + FakeKey { keycode } => Some(*keycode), _ => None, } } @@ -216,6 +219,12 @@ impl State { _ => Some(*self), } } + fn sequence_release(&self, key: K) -> Option { + match *self { + FakeKey { keycode } if keycode == key => None, + _ => Some(*self), + } + } fn get_layer(&self) -> Option { match self { MomentaryLayerModifier { value, .. } => Some(*value), @@ -293,6 +302,16 @@ impl WaitingState { } } +struct SequenceState +where + K: 'static + Copy, +{ + events: &'static [SequenceEvent], + current_event: u8, + delay: u16, + tap_in_progress: bool, +} + struct TapDanceState where T: 'static, @@ -488,8 +507,13 @@ struct ActionContext { inside_tapdance: bool, } -impl - Layout +impl< + const C: usize, + const R: usize, + const L: usize, + T: 'static + Copy, + K: 'static + Copy + PartialEq, + > Layout { /// Creates a new `Layout` object. pub fn new(layers: &'static mut [[[Action; C]; R]; L]) -> Self { @@ -500,6 +524,7 @@ impl 0 { + sequence.delay -= 1; + self.active_sequences.push_back(sequence); + continue; + } + + let event = sequence.events.get(sequence.current_event as usize); + + match event { + Some(&SequenceEvent::NoOp) => { + sequence.current_event += 1; + } + Some(&SequenceEvent::Press(keycode)) => { + let _ = self.states.push(FakeKey { keycode }); + sequence.current_event += 1; + } + Some(&SequenceEvent::Release(keycode)) => { + self.states + .retain(|s| s.sequence_release(keycode).is_some()); + sequence.current_event += 1; + } + Some(&SequenceEvent::Tap(keycode)) if !sequence.tap_in_progress => { + sequence.tap_in_progress = true; + let _ = self.states.push(FakeKey { keycode }); + } + Some(&SequenceEvent::Tap(keycode)) => { + sequence.tap_in_progress = false; + self.states + .retain(|s| s.sequence_release(keycode).is_some()); + sequence.current_event += 1; + } + Some(&SequenceEvent::Delay(delay)) => { + sequence.delay = delay.saturating_sub(1); + sequence.current_event += 1; + } + Some(&SequenceEvent::Complete) => { + for state in self.states.clone().iter() { + if let FakeKey { keycode } = state { + self.states + .retain(|s| s.sequence_release(*keycode).is_some()); + } + } + // skip to the end of the sequence + sequence.current_event = sequence.events.len() as u8; + } + None => {} + } + + // if the sequence is not done (an event was processed, and there are still events + // to process), add it back to the list of active sequences + if event.is_some() && sequence.current_event < sequence.events.len() as u8 { + self.active_sequences.push_back(sequence); + } + } + } + } /// A time event. /// /// This method must be called regularly, typically every millisecond. @@ -597,6 +681,9 @@ impl { + self.active_sequences.push_back(SequenceState { + events, + current_event: 0, + delay: 0, + tap_in_progress: false, + }); + } KeyCode(keycode) => { self.tap_hold_tracker.coord = coord; let _ = self.states.push(NormalKey { coord, keycode }); @@ -2667,4 +2762,293 @@ mod test { assert_eq!(CustomEvent::NoEvent, layout.tick()); assert_keys(&[], layout.keycodes()); } + + #[test] + fn sequences() { + static mut LAYERS: Layers<6, 1, 1> = [[[ + Sequence( + // Simple Ctrl-C sequence/macro + &[ + SequenceEvent::Press(LCtrl), + SequenceEvent::Press(C), + SequenceEvent::Release(C), + SequenceEvent::Release(LCtrl), + ] + .as_slice(), + ), + Sequence( + // So we can test that Complete works + &[ + SequenceEvent::Press(LCtrl), + SequenceEvent::Press(C), + SequenceEvent::Complete, + ] + .as_slice(), + ), + Sequence( + // YO with a delay in the middle + &[ + SequenceEvent::Press(Y), + SequenceEvent::Release(Y), + // "How many licks does it take to get to the center?" + SequenceEvent::Delay(3), // Let's find out + SequenceEvent::Press(O), + SequenceEvent::Release(O), + ] + .as_slice(), + ), + Sequence( + // A long sequence to test the chunking capability + &[ + SequenceEvent::Press(LShift), // Important: Shift must remain held + SequenceEvent::Press(U), // ...or the message just isn't the same! + SequenceEvent::Release(U), + SequenceEvent::Press(N), + SequenceEvent::Release(N), + SequenceEvent::Press(L), + SequenceEvent::Release(L), + SequenceEvent::Press(I), + SequenceEvent::Release(I), + SequenceEvent::Press(M), + SequenceEvent::Release(M), + SequenceEvent::Press(I), + SequenceEvent::Release(I), + SequenceEvent::Press(T), + SequenceEvent::Release(T), + SequenceEvent::Press(E), + SequenceEvent::Release(E), + SequenceEvent::Press(D), + SequenceEvent::Release(D), + SequenceEvent::Press(Space), + SequenceEvent::Release(Space), + SequenceEvent::Press(P), + SequenceEvent::Release(P), + SequenceEvent::Press(O), + SequenceEvent::Release(O), + SequenceEvent::Press(W), + SequenceEvent::Release(W), + SequenceEvent::Press(E), + SequenceEvent::Release(E), + SequenceEvent::Press(R), + SequenceEvent::Release(R), + SequenceEvent::Press(Kb1), + SequenceEvent::Release(Kb1), + SequenceEvent::Press(Kb1), + SequenceEvent::Release(Kb1), + SequenceEvent::Press(Kb1), + SequenceEvent::Release(Kb1), + SequenceEvent::Press(Kb1), + SequenceEvent::Release(Kb1), + SequenceEvent::Release(LShift), + ] + .as_slice(), + ), + Sequence( + &[ + SequenceEvent::Tap(Q), + SequenceEvent::Tap(W), + SequenceEvent::Tap(E), + ] + .as_slice(), + ), + Sequence( + &[ + SequenceEvent::Tap(X), + SequenceEvent::Tap(Y), + SequenceEvent::Tap(Z), + ] + .as_slice(), + ), + ]]]; + let mut layout = Layout::new(unsafe { &mut LAYERS }); + // Test a basic sequence + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 0)); + // Sequences take an extra tick to kickoff since the first tick starts the sequence: + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence detected & added + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence starts + assert_keys(&[LCtrl], layout.keycodes()); // First item in the SequenceEvent + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LCtrl, C], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LCtrl], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Release(0, 0)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test the use of Complete() + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LCtrl], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LCtrl, C], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Release(0, 1)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test a sequence with a Delay() (aka The Mr Owl test; duration == 3) + layout.event(Press(0, 2)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[Y], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // First decrement (2) + assert_keys(&[], layout.keycodes()); // "Eh Ooone!" + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Second decrement (1) + assert_keys(&[], layout.keycodes()); // "Eh two!" + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Final decrement (0) + assert_keys(&[], layout.keycodes()); // "Eh three." + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Press() added for the next tick() + assert_eq!(CustomEvent::NoEvent, layout.tick()); // FakeKey Press() + assert_keys(&[O], layout.keycodes()); // CHOMP! + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Release(0, 2)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // // Test really long sequences (aka macros)... + layout.event(Press(0, 3)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, U], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, N], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, L], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, I], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, M], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, I], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, T], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, E], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, D], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, Space], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, P], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, O], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, W], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, E], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, R], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, Kb1], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, Kb1], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, Kb1], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift, Kb1], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[LShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + layout.event(Release(0, 3)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test a sequence with Tap Events + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 4)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence detected & added + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // To Process Tap(Q) + assert_keys(&[Q], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(Q) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // To Process Tap(W) + assert_keys(&[W], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(W) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // To Process Tap(E) + assert_keys(&[E], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(E) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence is + // finished + assert_keys(&[], layout.keycodes()); + layout.event(Release(0, 4)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test two sequences + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 5)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence detected & added + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 4)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(X) + assert_keys(&[X], layout.keycodes()); + layout.event(Release(0, 5)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(X), Tap(Q) + assert_keys(&[Q], layout.keycodes()); + layout.event(Release(0, 4)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(Y), Release(Q) + assert_keys(&[Y], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(Y), Press(W) + assert_keys(&[W], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Press(Z), Release(W) + assert_keys(&[Z], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(Z), Press(E) + assert_keys(&[E], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(E) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence is + // finished + assert_keys(&[], layout.keycodes()); + } }