Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Sequences (aka macros) #30

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3a83060
Fixed description and updated the either dependency to 1.6
riskable Oct 15, 2020
6e5e068
Added support for Sequence Actions (aka macros) whereby multiple key …
riskable Oct 16, 2020
b56de4f
Added support for Sequence Actions (aka macros) whereby multiple key …
riskable Oct 16, 2020
7df7519
Modified per TeXitoi's instructions (as best I understood them) and a…
riskable Oct 19, 2020
d65daac
Merge branch 'master' of github.com:riskable/keyberon
riskable Oct 19, 2020
6067648
Removed some leftover commented-out cruft
riskable Oct 19, 2020
95650fd
Made some changes to make Clippy happy.
riskable Oct 19, 2020
255c4d3
Unit tests for sequences and improved tick start
riskable Oct 19, 2020
18fb401
Minor fixes to make Clippy happy
riskable Oct 19, 2020
37e6b31
Support for sequences of nearly limitless length!
riskable Oct 20, 2020
b7a77d3
Make Clippy happy
riskable Oct 20, 2020
999b9f2
Changed do_action() to make Clippy happy
riskable Oct 20, 2020
ab0bfe1
Fixed a bug where Delay() wasn't working properly
riskable Oct 20, 2020
0785151
Added the ability to cancel running sequences
riskable Oct 20, 2020
60b08b9
Fixed an edge case with CancelSequence
riskable Oct 20, 2020
0d89989
Added `SequenceEvent::Tap`
riskable Oct 23, 2020
0660d2b
CHANGELOG and more doc for custom actionmagnet:?xt=urn:btih:a6721ad2e…
TeXitoi Dec 11, 2020
f3ad3ff
Merge remote-tracking branch 'upstream/master'
riskable Dec 14, 2020
3fe71e5
Changed how sequences are processed (MUCH simpler).
riskable Dec 15, 2020
43ed3e5
Merge remote-tracking branch 'upstream/master'
riskable May 3, 2021
f93d8ec
Added SequenceEvent back to layout.rs (got removed with last merge of…
riskable May 3, 2021
f0e7d65
Merge remote-tracking branch 'upstream/master'
riskable Sep 22, 2021
a26a3b6
Merged upstream
riskable Dec 25, 2021
4604ec7
Added some Sequence test cases
riskable May 2, 2022
2585845
Merge remote-tracking branch 'upstream/master'
riskable May 2, 2022
c990d9d
Fixed Sequence tests to use the new syntax
riskable May 2, 2022
d93436d
Removed unused enum variant and modified to use a const generic va…
riskable May 2, 2022
8e2dfa9
Merged upstream
riskable Oct 4, 2022
883a2d1
Merge remote-tracking branch 'upstream/master' into sequences
riskable Oct 4, 2022
5e67102
Added SequenceEvent back to action.rs
riskable Oct 4, 2022
16be78a
Fixed duplicate HoldTapConfig lines
riskable Oct 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions src/action.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
//! 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 macros
#[non_exhaustive] // Definitely NOT exhaustive! Let's add more! Mouse events maybe? :)
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SequenceEvent {
/// A keypress/keydown
Press(KeyCode),
/// Key release/keyup
Release(KeyCode),
/// For sequences that need to wait a bit before continuing
Delay {
/// A delay (in ticks) to wait before executing the next SequenceEvent
since: u32,
/// Number of ticks to wait before removing the Delay
ticks: u32,
TeXitoi marked this conversation as resolved.
Show resolved Hide resolved
},
}

impl SequenceEvent {
/// Returns the keycode associated with the given Press/Release event
pub fn keycode(&self) -> Option<KeyCode> {
TeXitoi marked this conversation as resolved.
Show resolved Hide resolved
match *self {
SequenceEvent::Press(keycode) => Some(keycode),
SequenceEvent::Release(keycode) => Some(keycode),
_ => None,
}
}
}

/// The different actions that can be done.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
Expand Down Expand Up @@ -41,6 +69,11 @@ pub enum Action {
/// The tap action.
tap: &'static Action,
},
/// A sequence of SequenceEvents
Sequence {
/// An array of SequenceEvents that will be triggered (in order)
events: &'static [SequenceEvent],
},
}
impl Action {
/// Gets the layer number if the action is the `Layer` action.
Expand Down Expand Up @@ -78,8 +111,9 @@ pub const fn d(layer: usize) -> Action {
Action::DefaultLayer(layer)
}

/// A shortcut to create a `Action::KeyCode`, useful to create compact
/// A shortcut to create `Action::MultipleKeyCodes`, useful to create compact
/// layout.
pub const fn m(kcs: &'static [KeyCode]) -> Action {
Action::MultipleKeyCodes(kcs)
}

118 changes: 115 additions & 3 deletions src/layout.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Layout management.

use crate::action::Action;
use crate::action::{Action, SequenceEvent};
use crate::key_code::KeyCode;
use arraydeque::ArrayDeque;
use heapless::consts::U64;
Expand All @@ -23,6 +23,9 @@ pub struct Layout {
states: Vec<State, U64>,
waiting: Option<WaitingState>,
stacked: ArrayDeque<[Stacked; 16], arraydeque::behavior::Wrapping>,
sequenced: ArrayDeque<[SequenceEvent; 32], arraydeque::behavior::Wrapping>,
// Riskable NOTE: Wish we didn't have to preallocate sequenced like this.
// I want to be able to have my keyboard type long sentences/quotes!
}

/// An event on the key matrix.
Expand Down Expand Up @@ -67,15 +70,34 @@ impl Event {
}
}

/// The various states used internally by Keyberon
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum State {
NormalKey { keycode: KeyCode, coord: (u8, u8) },
LayerModifier { value: usize, coord: (u8, u8) },
/// Normal keys
TeXitoi marked this conversation as resolved.
Show resolved Hide resolved
NormalKey {
/// Keycode
keycode: KeyCode,
/// Coordinates in the matrix
coord: (u8, u8),
},
/// Layer modifier keys
LayerModifier {
/// Value
value: usize,
/// Coordinates of this layer modifier key in the matrix
coord: (u8, u8),
},
/// Fake key event for sequences
FakeKey {
/// The key to everything!
keycode: KeyCode,
},
}
impl State {
fn keycode(&self) -> Option<KeyCode> {
match self {
NormalKey { keycode, .. } => Some(*keycode),
FakeKey { keycode } => Some(*keycode),
_ => None,
}
}
Expand All @@ -90,6 +112,12 @@ impl State {
_ => Some(*self),
}
}
fn seq_release(&self, kc: KeyCode) -> Option<Self> {
match *self {
FakeKey { keycode, .. } if keycode == kc => None,
_ => Some(*self),
}
}
fn get_layer(&self) -> Option<usize> {
match self {
LayerModifier { value, .. } => Some(*value),
Expand Down Expand Up @@ -143,6 +171,7 @@ impl Layout {
states: Vec::new(),
waiting: None,
stacked: ArrayDeque::new(),
sequenced: ArrayDeque::new(),
}
}
/// Iterates on the key codes of the current state.
Expand Down Expand Up @@ -185,6 +214,32 @@ impl Layout {
}
}
}
// Process sequences
if let Some(event) = self.sequenced.pop_front() {
match event {
SequenceEvent::Press(keycode) => {
// Start tracking this fake key Press() event
let _ = self.states.push(FakeKey { keycode });
}
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()
}
SequenceEvent::Delay { since, ticks } => {
if since < ticks {
// Increment and put it back
self.sequenced.push_front(SequenceEvent::Delay {
since: since.saturating_add(1),
ticks: 1, // Count this tick as the first
TeXitoi marked this conversation as resolved.
Show resolved Hide resolved
});
}
}
}
}
self.keycodes()
}
fn unstack(&mut self, stacked: Stacked) {
Expand Down Expand Up @@ -278,6 +333,23 @@ impl Layout {
self.do_action(action, coord, delay);
}
}
Sequence { events } => {
// Copy the contents of the sequence events into the sequenced ArrayDeque
for key_event in events {
match *key_event {
SequenceEvent::Press(keycode) => {
self.sequenced.push_back(SequenceEvent::Press(keycode));
}
SequenceEvent::Release(keycode) => {
self.sequenced.push_back(SequenceEvent::Release(keycode));
}
SequenceEvent::Delay { since, ticks } => {
self.sequenced
.push_back(SequenceEvent::Delay { since, ticks });
}
}
}
}
Layer(value) => {
let _ = self.states.push(LayerModifier { value, coord });
}
Expand Down Expand Up @@ -306,6 +378,7 @@ mod test {
extern crate std;
use super::{Event::*, Layers, Layout};
use crate::action::Action::*;
use crate::action::SequenceEvent;
use crate::action::{k, l, m};
use crate::key_code::KeyCode;
use crate::key_code::KeyCode::*;
Expand Down Expand Up @@ -369,4 +442,43 @@ mod test {
assert_keys(&[LShift], layout.tick());
assert_keys(&[], layout.tick());
}

#[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 {
// 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 { since: 0, ticks: 3 }, // This many!
SequenceEvent::Press(O),
SequenceEvent::Release(O),
],
},
]]];
let mut layout = Layout::new(LAYERS);
assert_keys(&[], layout.tick());
assert_keys(&[], layout.event(Press(0, 0)));
assert_keys(&[LCtrl], layout.tick());
assert_keys(&[LCtrl, C], layout.tick());
assert_keys(&[LCtrl], layout.tick());
assert_keys(&[], layout.tick());
assert_keys(&[], layout.event(Press(0, 1)));
assert_keys(&[Y], layout.tick());
assert_keys(&[], layout.tick()); // 1
assert_keys(&[], layout.tick()); // 2
assert_keys(&[], layout.tick()); // 3
assert_keys(&[O], layout.tick()); // CHOMP!
}
}