From 057408e22a2ec71dfbf2bbd7a6483d4fdb8ed80f Mon Sep 17 00:00:00 2001 From: Riskable Date: Fri, 16 Oct 2020 14:58:00 -0400 Subject: [PATCH 1/8] Added support for Sequence Actions (aka macros) whereby multiple key events are executed in succession. --- src/action.rs | 31 ++++- src/layout.rs | 328 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 354 insertions(+), 5 deletions(-) diff --git a/src/action.rs b/src/action.rs index 97f94f6..b70701a 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,9 +1,31 @@ -//! The different actions that can be done. +//! The different actions that can be executed via any given key. use crate::key_code::KeyCode; use crate::layout::{StackedIter, WaitingAction}; use core::fmt::Debug; +/// 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(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 + 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) + }, + /// 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(Clone, Copy)] @@ -198,6 +220,13 @@ where DefaultLayer(usize), /// Perform different actions on key hold/tap (see [`HoldTapAction`]). HoldTap(&'static HoldTapAction), + /// 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 7ac54f0..8f9f853 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -46,7 +46,7 @@ /// ``` pub use keyberon_macros::*; -use crate::action::{Action, HoldTapAction, HoldTapConfig}; +use crate::action::{Action, HoldTapAction, HoldTapConfig, SequenceEvent}; use crate::key_code::KeyCode; use arraydeque::ArrayDeque; use heapless::Vec; @@ -73,6 +73,9 @@ pub type Layers< /// Events can be retrieved by iterating over this struct and calling [Stacked::event]. type Stack = ArrayDeque; +// The maximum number of simultaneously-executing Squences: +const MAX_SEQUENCES: usize = 4; + /// The layout manager. It takes `Event`s and `tick`s as input, and /// generate keyboard reports. pub struct Layout< @@ -91,6 +94,7 @@ pub struct Layout< waiting: Option>, stacked: Stack, tap_hold_tracker: TapHoldTracker, + active_sequences: ArrayDeque, MAX_SEQUENCES, arraydeque::behavior::Wrapping>, } /// An event on the key matrix. @@ -182,6 +186,7 @@ enum State { NormalKey { keycode: K, coord: (u8, u8) }, LayerModifier { value: usize, coord: (u8, u8) }, Custom { value: &'static T, coord: (u8, u8) }, + FakeKey { keycode: K }, // Fake key event for sequences } impl Copy for State {} impl Clone for State { @@ -189,11 +194,11 @@ impl Clone for State { *self } } - -impl State { +impl State { fn keycode(&self) -> Option { match self { NormalKey { keycode, .. } => Some(*keycode), + FakeKey { keycode } => Some(*keycode), _ => None, } } @@ -210,6 +215,12 @@ impl State { _ => Some(*self), } } + fn seq_release(&self, kc: K) -> Option { + match *self { + FakeKey { keycode, .. } if keycode == kc => None, + _ => Some(*self), + } + } fn get_layer(&self) -> Option { match self { LayerModifier { value, .. } => Some(*value), @@ -301,6 +312,14 @@ impl<'a> Iterator for StackedIter<'a> { } } +#[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], +} + /// An event, waiting in a stack to be processed. #[derive(Debug)] pub struct Stacked { @@ -335,7 +354,7 @@ impl TapHoldTracker { } } -impl +impl Layout { /// Creates a new `Layout` object. @@ -347,6 +366,7 @@ impl match w.tick(&self.stacked) { Some(WaitingAction::Hold) => self.waiting_into_hold(), @@ -403,6 +424,81 @@ impl 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() { + if let FakeKey { keycode } = *fake_key { + 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.is_empty() { + // Put it back + self.active_sequences.push_back(seq); + } + } + } + } fn unstack(&mut self, stacked: Stacked) -> CustomEvent { use Event::*; match stacked.event { @@ -503,6 +599,27 @@ impl { + 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() { + if let FakeKey { keycode } = *fake_key { + self.states = self + .states + .iter() + .filter_map(|s| s.seq_release(keycode)) + .collect(); + } + } + } &Layer(value) => { self.tap_hold_tracker.coord = coord; let _ = self.states.push(LayerModifier { value, coord }); @@ -544,6 +661,7 @@ mod test { use super::{Event::*, 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::*; @@ -1225,4 +1343,206 @@ mod test { assert_keys(&[Enter], layout.keycodes()); } } + + #[test] + fn sequences() { + static LAYERS: Layers<4, 1, 1> = [[[ + 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()); + } } From 81d71932ae7ffbc8907b1a1c9cde190bd92714ae Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Sun, 23 Apr 2023 23:28:32 +0200 Subject: [PATCH 2/8] action: Action::Sequence has only one member and make it only use 2 pointers --- src/action.rs | 6 +++--- src/layout.rs | 38 +++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/action.rs b/src/action.rs index b70701a..21527d1 100644 --- a/src/action.rs +++ b/src/action.rs @@ -221,10 +221,10 @@ where /// Perform different actions on key hold/tap (see [`HoldTapAction`]). HoldTap(&'static HoldTapAction), /// A sequence of SequenceEvents - Sequence { + Sequence( /// An array of SequenceEvents that will be triggered (in order) - events: &'static [SequenceEvent], - }, + &'static &'static [SequenceEvent], + ), /// Cancels any running sequences CancelSequences, /// Custom action. diff --git a/src/layout.rs b/src/layout.rs index 8f9f853..011cdef 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -599,7 +599,7 @@ impl { + Sequence(events) => { self.active_sequences.push_back(SequenceState { cur_event: None, delay: 0, @@ -1347,37 +1347,40 @@ mod test { #[test] fn sequences() { static LAYERS: Layers<4, 1, 1> = [[[ - Sequence { + Sequence( // Simple Ctrl-C sequence/macro - events: &[ + &[ SequenceEvent::Press(LCtrl), SequenceEvent::Press(C), SequenceEvent::Release(C), SequenceEvent::Release(LCtrl), - ], - }, - Sequence { + ] + .as_slice(), + ), + Sequence( // So we can test that Complete works - events: &[ + &[ SequenceEvent::Press(LCtrl), SequenceEvent::Press(C), SequenceEvent::Complete, - ], - }, - Sequence { + ] + .as_slice(), + ), + 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 { + ] + .as_slice(), + ), + 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), @@ -1418,8 +1421,9 @@ mod test { SequenceEvent::Press(Kb1), SequenceEvent::Release(Kb1), SequenceEvent::Release(LShift), - ], - }, + ] + .as_slice(), + ), ]]]; let mut layout = Layout::new(&LAYERS); // Test a basic sequence From b69ca49176f39eae58cbee61ad369f55ea01659b Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Tue, 27 Jun 2023 23:31:23 +0200 Subject: [PATCH 3/8] Sequences: CancelSequences is unneeded --- src/action.rs | 2 -- src/layout.rs | 13 ------------- 2 files changed, 15 deletions(-) diff --git a/src/action.rs b/src/action.rs index 21527d1..c9e0d24 100644 --- a/src/action.rs +++ b/src/action.rs @@ -225,8 +225,6 @@ where /// An array of SequenceEvents that will be triggered (in order) &'static &'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 011cdef..6485560 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -607,19 +607,6 @@ impl { - // 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() { - if let FakeKey { keycode } = *fake_key { - self.states = self - .states - .iter() - .filter_map(|s| s.seq_release(keycode)) - .collect(); - } - } - } &Layer(value) => { self.tap_hold_tracker.coord = coord; let _ = self.states.push(LayerModifier { value, coord }); From f628222523cf9f7d916ade378bb85537a1f910f2 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Tue, 27 Jun 2023 23:58:37 +0200 Subject: [PATCH 4/8] Sequences: fix seqences ending with Tap + test --- src/layout.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index 6485560..820b37b 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -492,7 +492,7 @@ impl {} // We'll never get here } } - if !seq.remaining_events.is_empty() { + if !seq.remaining_events.is_empty() || seq.tapped.is_some() { // Put it back self.active_sequences.push_back(seq); } @@ -1333,7 +1333,7 @@ mod test { #[test] fn sequences() { - static LAYERS: Layers<4, 1, 1> = [[[ + static LAYERS: Layers<5, 1, 1> = [[[ Sequence( // Simple Ctrl-C sequence/macro &[ @@ -1411,6 +1411,14 @@ mod test { ] .as_slice(), ), + Sequence( + &[ + SequenceEvent::Tap(Q), + SequenceEvent::Tap(W), + SequenceEvent::Tap(E), + ] + .as_slice(), + ), ]]]; let mut layout = Layout::new(&LAYERS); // Test a basic sequence @@ -1427,6 +1435,10 @@ mod test { 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)); @@ -1437,6 +1449,10 @@ mod test { 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()); @@ -1453,6 +1469,10 @@ mod test { 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()); @@ -1535,5 +1555,33 @@ mod test { 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()); } } From 3fb35f6aff7aa9cbb180a2110dc796b1136944b7 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Sun, 2 Jul 2023 17:48:32 +0200 Subject: [PATCH 5/8] layout: add tests with 2 sequences intertwined (cherry picked from commit 30343989a9f3f40c4cefd5175661e6c5a1c8477b) --- src/layout.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/layout.rs b/src/layout.rs index 820b37b..2c1ee60 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1333,7 +1333,7 @@ mod test { #[test] fn sequences() { - static LAYERS: Layers<5, 1, 1> = [[[ + static LAYERS: Layers<6, 1, 1> = [[[ Sequence( // Simple Ctrl-C sequence/macro &[ @@ -1419,6 +1419,14 @@ mod test { ] .as_slice(), ), + Sequence( + &[ + SequenceEvent::Tap(X), + SequenceEvent::Tap(Y), + SequenceEvent::Tap(Z), + ] + .as_slice(), + ), ]]]; let mut layout = Layout::new(&LAYERS); // Test a basic sequence @@ -1583,5 +1591,32 @@ mod test { 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()); } } From 69164064f684a5a5ecab472ec38df1ec75e742df Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Mon, 3 Jul 2023 13:58:30 +0200 Subject: [PATCH 6/8] layout: document SequenceState and State::FakeKey --- src/layout.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index 2c1ee60..cc7387d 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -183,10 +183,22 @@ impl CustomEvent { #[derive(Debug, Eq, PartialEq)] enum State { - NormalKey { keycode: K, coord: (u8, u8) }, - LayerModifier { value: usize, coord: (u8, u8) }, - Custom { value: &'static T, coord: (u8, u8) }, - FakeKey { keycode: K }, // Fake key event for sequences + NormalKey { + keycode: K, + coord: (u8, u8), + }, + LayerModifier { + value: usize, + coord: (u8, u8), + }, + Custom { + value: &'static T, + coord: (u8, u8), + }, + /// Fake key event for sequences + FakeKey { + keycode: K, + }, } impl Copy for State {} impl Clone for State { @@ -215,6 +227,7 @@ impl State { _ => Some(*self), } } + /// Return whether the state is a [`State::FakeKey`] with keycode `kc`. fn seq_release(&self, kc: K) -> Option { match *self { FakeKey { keycode, .. } if keycode == kc => None, @@ -314,10 +327,15 @@ impl<'a> Iterator for StackedIter<'a> { #[derive(Debug, Copy, Clone)] struct SequenceState { + /// Current event being processed 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 to process remaining_events: &'static [SequenceEvent], + /// Keeps track of SequenceEvent::Delay time remaining + delay: u32, + /// Keycode of a key that should be released at the next tick + tapped: Option, + } /// An event, waiting in a stack to be processed. From 8deb7e6cb0093ba3301d12414a64ddebccd24411 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Tue, 4 Jul 2023 00:08:22 +0200 Subject: [PATCH 7/8] layout: impl Default for SequenceState --- src/layout.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index cc7387d..8274b6a 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -335,7 +335,17 @@ struct SequenceState { delay: u32, /// Keycode of a key that should be released at the next tick tapped: Option, +} +impl Default for SequenceState { + fn default() -> Self { + Self { + cur_event: None, + remaining_events: &[], + delay: 0, + tapped: None, + } + } } /// An event, waiting in a stack to be processed. @@ -619,10 +629,8 @@ impl { self.active_sequences.push_back(SequenceState { - cur_event: None, - delay: 0, - tapped: None, remaining_events: events, + ..Default::default() }); } &Layer(value) => { From 5f94202520e24851fc1af86ec4f59062ab8e84d3 Mon Sep 17 00:00:00 2001 From: Boris Faure Date: Sun, 9 Jul 2023 15:42:50 +0200 Subject: [PATCH 8/8] sequences: be able to filter/restore some key presses --- src/action.rs | 6 +- src/layout.rs | 269 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 272 insertions(+), 3 deletions(-) diff --git a/src/action.rs b/src/action.rs index c9e0d24..1995b8b 100644 --- a/src/action.rs +++ b/src/action.rs @@ -7,7 +7,7 @@ use core::fmt::Debug; /// The different types of actions we support for key sequences/macros #[non_exhaustive] #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum SequenceEvent { +pub enum SequenceEvent { /// No operation action: just do nothing (a placeholder). NoOp, /// A keypress/keydown @@ -24,6 +24,10 @@ pub enum 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, + /// If those keys are pressed, release them + Filter(&'static &'static [K]), + /// Restore keys if they were previously Filter-ed + Restore, } /// Behavior configuration of HoldTap. diff --git a/src/layout.rs b/src/layout.rs index 8274b6a..8345624 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -49,6 +49,7 @@ pub use keyberon_macros::*; use crate::action::{Action, HoldTapAction, HoldTapConfig, SequenceEvent}; use crate::key_code::KeyCode; use arraydeque::ArrayDeque; +use core::convert::TryFrom; use heapless::Vec; use State::*; @@ -326,7 +327,38 @@ impl<'a> Iterator for StackedIter<'a> { } #[derive(Debug, Copy, Clone)] -struct SequenceState { +/// Enum to save a state that represents a key pressed +enum SavedKeyCodeState { + /// Key pressed + NormalKey { keycode: K, coord: (u8, u8) }, + /// Fake key event for sequences + FakeKey { keycode: K }, +} + +impl From> for State { + /// Convert a [`SavedKeyCodeState`] into a [`State`] + fn from(saved: SavedKeyCodeState) -> Self { + match saved { + SavedKeyCodeState::NormalKey { keycode, coord } => Self::NormalKey { keycode, coord }, + SavedKeyCodeState::FakeKey { keycode } => Self::FakeKey { keycode }, + } + } +} + +impl TryFrom> for SavedKeyCodeState { + type Error = &'static str; + /// Try to convert a [`State`] into a [`SavedKeyCodeState`] + fn try_from(state: State) -> Result { + match state { + NormalKey { keycode, coord } => Ok(Self::NormalKey { keycode, coord }), + FakeKey { keycode } => Ok(Self::FakeKey { keycode }), + _ => Err("Unsupported State conversion to SavedKeyCodeState"), + } + } +} + +#[derive(Debug, Copy, Clone)] +struct SequenceState { /// Current event being processed cur_event: Option>, /// Remaining events to process @@ -335,6 +367,8 @@ struct SequenceState { delay: u32, /// Keycode of a key that should be released at the next tick tapped: Option, + /// Keys filtered that can be restored later + to_restore: [Option>; 64], } impl Default for SequenceState { @@ -344,6 +378,23 @@ impl Default for SequenceState { remaining_events: &[], delay: 0, tapped: None, + to_restore: [None; 64], + } + } +} +impl SequenceState { + fn add_to_restore(&mut self, s: State) { + for e in self.to_restore.iter_mut() { + if e.is_none() { + match s { + NormalKey { .. } | FakeKey { .. } => { + let saved = SavedKeyCodeState::::try_from(s); + *e = Some(saved.unwrap()); + } + _ => {} + } + return; + } } } } @@ -517,7 +568,36 @@ impl {} // We'll never get here + Some(SequenceEvent::Filter(keys)) => { + self.states = self + .states + .iter() + .filter_map(|s| match s.keycode() { + Some(k) => { + if keys.contains(&k) { + seq.add_to_restore(*s); + None + } else { + Some(*s) + } + } + _ => Some(*s), + }) + .collect() + } + Some(SequenceEvent::Restore) => seq + .to_restore + .iter() + .filter_map(|s| { + if let Some(saved) = s { + let _ = self.states.push((*saved).into()); + } + None + }) + .collect(), + _ => { + panic!("invalid sequence"); + } } } if !seq.remaining_events.is_empty() || seq.tapped.is_some() { @@ -1645,4 +1725,189 @@ mod test { // finished assert_keys(&[], layout.keycodes()); } + + #[test] + fn sequences_unshift() { + static LAYERS: Layers<8, 1, 1> = [[[ + k(LShift), + k(RShift), + k(A), + k(B), + k(C), + Sequence( + &[ + SequenceEvent::Press(A), + SequenceEvent::Release(A), + SequenceEvent::Press(RShift), + SequenceEvent::Press(B), + SequenceEvent::Release(B), + SequenceEvent::Release(RShift), + SequenceEvent::Press(C), + SequenceEvent::Release(C), + ] + .as_slice(), + ), + Sequence( + &[ + SequenceEvent::Tap(A), + SequenceEvent::Press(RShift), + SequenceEvent::Tap(B), + SequenceEvent::Release(RShift), + SequenceEvent::Tap(C), + ] + .as_slice(), + ), + Sequence( + &[ + SequenceEvent::Tap(A), + SequenceEvent::Filter(&[LShift, RShift].as_slice()), + SequenceEvent::Tap(B), + SequenceEvent::Restore, + SequenceEvent::Tap(C), + ] + .as_slice(), + ), + /* TODO: sequence with explicit Shift */ + ]]]; + let mut layout = Layout::new(&LAYERS); + + // Test a sequence that contains Shift + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 2)); // A + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[A], layout.keycodes()); + layout.event(Release(0, 2)); // A + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 1)); // RShift + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[RShift], layout.keycodes()); + layout.event(Press(0, 3)); // B + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[B, RShift], layout.keycodes()); + layout.event(Release(0, 3)); // B + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[RShift], layout.keycodes()); + layout.event(Release(0, 1)); // RShift + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + layout.event(Press(0, 4)); // C + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[C], layout.keycodes()); + layout.event(Release(0, 4)); // C + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + + // Test a sequence that contains Shift + layout.event(Press(0, 5)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence detected & added + assert_keys(&[], layout.keycodes()); + layout.event(Release(0, 5)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // To Process Press(A) + assert_keys(&[A], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(A) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Press(RShift) + assert_keys(&[RShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Press(B) + assert_keys(&[RShift, B], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(B) + assert_keys(&[RShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(RShift) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(C) + assert_keys(&[C], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(C) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence is + // finished + assert_keys(&[], layout.keycodes()); + + // Test a sequence that contains Shift with Tap + layout.event(Press(0, 6)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence detected & added + assert_keys(&[], layout.keycodes()); + layout.event(Release(0, 6)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // To Process Tap(A) + assert_keys(&[A], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(A) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Press(RShift) + assert_keys(&[RShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(B) + assert_keys(&[RShift, B], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(B) + assert_keys(&[RShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(RShift) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(C) + assert_keys(&[C], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(C) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence is + // finished + assert_keys(&[], layout.keycodes()); + + // Test a sequence with Unshift/Restore while Shift has not been + // pressed + layout.event(Press(0, 7)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence detected & added + assert_keys(&[], layout.keycodes()); + layout.event(Release(0, 7)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // To Process Tap(A) + assert_keys(&[A], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(A) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Unshift + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(B) + assert_keys(&[B], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(B) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // RestoreShift + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(C) + assert_keys(&[C], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(C) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence is + // finished + assert_keys(&[], layout.keycodes()); + + // Test a sequence with Unshift/Restore while RShift has been pressed + + layout.event(Press(0, 1)); // RShift + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[RShift], layout.keycodes()); + + layout.event(Press(0, 7)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence detected & added + assert_keys(&[RShift], layout.keycodes()); + layout.event(Release(0, 7)); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // To Process Tap(A) + assert_keys(&[RShift, A], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(A) + assert_keys(&[RShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Unshift + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(B) + assert_keys(&[B], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(B) + assert_keys(&[], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // RestoreShift + assert_keys(&[RShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Tap(C) + assert_keys(&[RShift, C], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Release(C) + assert_keys(&[RShift], layout.keycodes()); + assert_eq!(CustomEvent::NoEvent, layout.tick()); // Sequence is + // finished + assert_keys(&[RShift], layout.keycodes()); + layout.event(Release(0, 1)); // RShift + assert_eq!(CustomEvent::NoEvent, layout.tick()); + assert_keys(&[], layout.keycodes()); + } }