diff --git a/src/action.rs b/src/action.rs index 12f584a..ab15365 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,7 +1,37 @@ -//! The different actions that can be done. +//! The different actions that can be executed via any given key. use crate::key_code::KeyCode; +/// The different types of actions we support for key sequences/macros +#[non_exhaustive] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum SequenceEvent { + /// No operation action: just do nothing (a placeholder). + NoOp, + /// A keypress/keydown + Press(KeyCode), + /// Key release/keyup + Release(KeyCode), + /// A shortcut for `Press(KeyCode), Release(KeyCode)` + Tap(KeyCode), + /// For sequences that need to wait a bit before continuing + Delay { + /// How long (in ticks) this Delay will last + duration: u32, // NOTE: This isn't a u16 because that's only max ~65 seconds (assuming 1000 ticks/sec) + }, + /// A marker that indicates there's more of the macro than would fit + /// in the 'sequenced' ArrayDeque + Continue { + /// The current chunk + index: usize, + /// The full list of Sequence Events (that aren't Continue()) + events: &'static [SequenceEvent], + }, + /// Cancels the running sequence and can be used to mark the end of a sequence + /// instead of using a number of Release() events + Complete, +} + /// Behavior configuration of HoldTap. #[non_exhaustive] #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -89,6 +119,13 @@ where /// update, set this to 0. tap_hold_interval: u16, }, + /// A sequence of SequenceEvents + Sequence { + /// An array of SequenceEvents that will be triggered (in order) + events: &'static [SequenceEvent], + }, + /// Cancels any running sequences + CancelSequences, /// Custom action. /// /// Define a user defined action. This enum can be anything you diff --git a/src/layout.rs b/src/layout.rs index f2c490a..8ae5987 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -45,7 +45,7 @@ /// ``` pub use keyberon_macros::*; -use crate::action::{Action, HoldTapConfig}; +use crate::action::{Action, HoldTapConfig, SequenceEvent}; use crate::key_code::KeyCode; use arraydeque::ArrayDeque; use heapless::Vec; @@ -72,6 +72,7 @@ where states: Vec, 64>, waiting: Option>, stacked: Stack, + active_sequences: ArrayDeque<[SequenceState; 4], arraydeque::behavior::Wrapping>, } /// An event on the key matrix. @@ -167,6 +168,7 @@ enum State { NormalKey { keycode: KeyCode, coord: (u8, u8) }, LayerModifier { value: usize, coord: (u8, u8) }, Custom { value: &'static T, coord: (u8, u8) }, + FakeKey { keycode: KeyCode }, // Fake key event for sequences } impl Copy for State {} impl Clone for State { @@ -178,6 +180,7 @@ impl State { fn keycode(&self) -> Option { match self { NormalKey { keycode, .. } => Some(*keycode), + FakeKey { keycode } => Some(*keycode), _ => None, } } @@ -194,6 +197,12 @@ impl State { _ => Some(*self), } } + fn seq_release(&self, kc: KeyCode) -> Option { + match *self { + FakeKey { keycode, .. } if keycode == kc => None, + _ => Some(*self), + } + } fn get_layer(&self) -> Option { match self { LayerModifier { value, .. } => Some(*value), @@ -258,6 +267,14 @@ impl WaitingState { } } +#[derive(Debug, Copy, Clone)] +struct SequenceState { + cur_event: Option, + delay: u32, // Keeps track of SequenceEvent::Delay time remaining + tapped: Option, // Keycode of a key that should be released at the next tick + remaining_events: &'static [SequenceEvent], +} + #[derive(Debug)] struct Stacked { event: Event, @@ -283,6 +300,7 @@ impl Layout { states: Vec::new(), waiting: None, stacked: ArrayDeque::new(), + active_sequences: ArrayDeque::new(), } } /// Iterates on the key codes of the current state. @@ -318,6 +336,7 @@ impl Layout { pub fn tick(&mut self) -> CustomEvent { self.states = self.states.iter().filter_map(State::tick).collect(); self.stacked.iter_mut().for_each(Stacked::tick); + self.process_sequences(); match &mut self.waiting { Some(w) => match w.tick(&self.stacked) { WaitingAction::Hold => self.waiting_into_hold(), @@ -330,6 +349,84 @@ impl Layout { }, } } + /// Takes care of draining and populating the `active_sequences` ArrayDeque, + /// giving us sequences (aka macros) of nearly limitless length! + fn process_sequences(&mut self) { + // Iterate over all active sequence events + for _ in 0..self.active_sequences.len() { + if let Some(mut seq) = self.active_sequences.pop_front() { + // If we've encountered a SequenceEvent::Delay we must count + // that down completely before doing anything else... + if seq.delay > 0 { + seq.delay = seq.delay.saturating_sub(1); + } else if let Some(keycode) = seq.tapped { + // Clear out the Press() matching this Tap()'s keycode + self.states = self + .states + .iter() + .filter_map(|s| s.seq_release(keycode)) + .collect(); + seq.tapped = None; + } else { + // Pull the next SequenceEvent + match seq.remaining_events { + [e, tail @ ..] => { + seq.cur_event = Some(*e); + seq.remaining_events = tail; + } + [] => (), + } + // Process it (SequenceEvent) + match seq.cur_event { + Some(SequenceEvent::Complete) => { + for fake_key in self.states.clone().iter() { + match *fake_key { + FakeKey { keycode } => { + self.states = self + .states + .iter() + .filter_map(|s| s.seq_release(keycode)) + .collect(); + } + _ => {} + } + } + seq.remaining_events = &[]; + } + Some(SequenceEvent::Press(keycode)) => { + // Start tracking this fake key Press() event + let _ = self.states.push(FakeKey { keycode }); + } + Some(SequenceEvent::Tap(keycode)) => { + // Same as Press() except we track it for one tick via seq.tapped: + let _ = self.states.push(FakeKey { keycode }); + seq.tapped = Some(keycode); + } + Some(SequenceEvent::Release(keycode)) => { + // Clear out the Press() matching this Release's keycode + self.states = self + .states + .iter() + .filter_map(|s| s.seq_release(keycode)) + .collect() + } + Some(SequenceEvent::Delay { duration }) => { + // Setup a delay that will be decremented once per tick until 0 + if duration > 0 { + // -1 to start since this tick counts + seq.delay = duration - 1; + } + } + _ => {} // We'll never get here + } + } + if seq.remaining_events.len() > 0 { + // Put it back + self.active_sequences.push_back(seq); + } + } + } + } fn unstack(&mut self, stacked: Stacked) -> CustomEvent { use Event::*; match stacked.event { @@ -416,6 +513,30 @@ impl Layout { } return custom; } + Sequence { events } => { + self.active_sequences.push_back(SequenceState { + cur_event: None, + delay: 0, + tapped: None, + remaining_events: events, + }); + } + CancelSequences => { + // Clear any and all running sequences then clean up any leftover FakeKey events + self.active_sequences.clear(); + for fake_key in self.states.clone().iter() { + match *fake_key { + FakeKey { keycode } => { + self.states = self + .states + .iter() + .filter_map(|s| s.seq_release(keycode)) + .collect(); + } + _ => {} + } + } + } &Layer(value) => { let _ = self.states.push(LayerModifier { value, coord }); } @@ -458,6 +579,7 @@ mod test { use super::{Event::*, Layers, Layout, *}; use crate::action::Action::*; use crate::action::HoldTapConfig; + use crate::action::SequenceEvent; use crate::action::{k, l, m}; use crate::key_code::KeyCode; use crate::key_code::KeyCode::*; @@ -707,4 +829,207 @@ mod test { assert_eq!(CustomEvent::Release(&42), layout.tick()); assert_keys(&[], layout.keycodes()); } + + #[test] + fn sequences() { + static LAYERS: Layers = &[&[&[ + Sequence { + // Simple Ctrl-C sequence/macro + events: &[ + SequenceEvent::Press(LCtrl), + SequenceEvent::Press(C), + SequenceEvent::Release(C), + SequenceEvent::Release(LCtrl), + ], + }, + Sequence { + // So we can test that Complete works + events: &[ + SequenceEvent::Press(LCtrl), + SequenceEvent::Press(C), + SequenceEvent::Complete, + ], + }, + Sequence { + // YO with a delay in the middle + events: &[ + SequenceEvent::Press(Y), + SequenceEvent::Release(Y), + // "How many licks does it take to get to the center?" + SequenceEvent::Delay { duration: 3 }, // Let's find out + SequenceEvent::Press(O), + SequenceEvent::Release(O), + ], + }, + Sequence { + // A long sequence to test the chunking capability + events: &[ + 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), + ], + }, + ]]]; + let mut layout = Layout::new(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()); + // 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()); + // 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()); + // // 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()); + assert_keys(&[], layout.keycodes()); + } }