Skip to content

Commit

Permalink
feat(combo): add combo implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
pcasotti committed Nov 28, 2024
1 parent a2ad626 commit b54444d
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 4 deletions.
82 changes: 78 additions & 4 deletions rmk/src/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
hid::{ConnectionType, HidWriterWrapper},
keyboard_macro::{MacroOperation, NUM_MACRO},
keycode::{KeyCode, ModifierCombination},
keymap::KeyMap,
keymap::{Combo, KeyMap, COMBO_MAX_LENGTH},
usb::descriptor::{CompositeReport, CompositeReportType, ViaReport},
KEYBOARD_STATE,
};
Expand All @@ -15,7 +15,7 @@ use embassy_sync::{
channel::{Channel, Receiver, Sender},
};
use embassy_time::{Instant, Timer};
use heapless::{FnvIndexMap, Vec};
use heapless::{Deque, FnvIndexMap, Vec};
use postcard::experimental::max_size::MaxSize;
use rmk_config::BehaviorConfig;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -159,6 +159,9 @@ pub(crate) struct Keyboard<'a, const ROW: usize, const COL: usize, const NUM_LAY
/// The current distance of mouse key moving
mouse_key_move_delta: i8,
mouse_wheel_move_delta: i8,

/// Buffer for pressed `KeyAction` and `KeyEvents` in combos
combo_actions_buffer: Deque<(KeyAction, KeyEvent), COMBO_MAX_LENGTH>,
}

impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
Expand Down Expand Up @@ -191,6 +194,7 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
last_mouse_tick: FnvIndexMap::new(),
mouse_key_move_delta: 8,
mouse_wheel_move_delta: 1,
combo_actions_buffer: Deque::new(),
}
}

Expand Down Expand Up @@ -270,11 +274,18 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
}

// Process key
let action = self
let key_action = self
.keymap
.borrow_mut()
.get_action_with_layer_cache(key_event);
match action {

if let Some(key_action) = self.process_combo(key_action, key_event).await {
self.process_key_action(key_action, key_event).await;
}
}

async fn process_key_action(&mut self, key_action: KeyAction, key_event: KeyEvent) {
match key_action {
KeyAction::No | KeyAction::Transparent => (),
KeyAction::Single(a) => self.process_key_action_normal(a, key_event).await,
KeyAction::WithModifier(a, m) => {
Expand Down Expand Up @@ -307,6 +318,69 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
}
}

async fn process_combo(
&mut self,
key_action: KeyAction,
key_event: KeyEvent,
) -> Option<KeyAction> {
for combo in self.keymap.borrow_mut().combos.iter_mut() {
if !key_event.pressed && combo.done() && combo.actions.contains(&key_action) {
combo.reset();
return Some(combo.output);
}
}

if self
.combo_actions_buffer
.push_back((key_action, key_event))
.is_err()
{
error!("Combo actions buffer overflowed! This is a bug and should not happen!");
}

let mut is_combo_action = false;
for combo in self.keymap.borrow_mut().combos.iter_mut() {
is_combo_action |= combo.update(key_action);
}

if is_combo_action && key_event.pressed {
let next_action = self
.keymap
.borrow_mut()
.combos
.iter()
.find_map(|combo| combo.done().then_some(combo.output));

if next_action.is_some() {
self.combo_actions_buffer.clear();
} else {
let timeout = embassy_time::Timer::after_millis(50);
match select(timeout, key_event_channel.receive()).await {
embassy_futures::select::Either::First(_) => self.dispatch_combos().await,
embassy_futures::select::Either::Second(event) => {
self.unprocessed_events.push(event).unwrap()
}
}
}
next_action
} else {
self.dispatch_combos().await;
None
}
}

async fn dispatch_combos(&mut self) {
while let Some((action, event)) = self.combo_actions_buffer.pop_front() {
self.process_key_action(action, event).await;
}
self.keymap
.borrow_mut()
.combos
.iter_mut()
.filter(|c| !c.done())
.for_each(Combo::reset);
}

async fn update_osm(&mut self, key_event: KeyEvent) {
match self.osm_state {
OneShotState::Initial(m) => self.osm_state = OneShotState::Held(m),
Expand Down
55 changes: 55 additions & 0 deletions rmk/src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,59 @@ use crate::{
};
use defmt::{error, warn};
use embedded_storage_async::nor_flash::NorFlash;
use heapless::Vec;
use num_enum::FromPrimitive;

pub(crate) const COMBO_MAX_NUM: usize = 8;
pub(crate) const COMBO_MAX_LENGTH: usize = 4;

pub(crate) struct Combo {
pub(crate) actions: Vec<KeyAction, COMBO_MAX_LENGTH>,
pub(crate) output: KeyAction,
state: u8,
}

impl Combo {
pub fn new(actions: Vec<KeyAction, COMBO_MAX_LENGTH>, output: KeyAction) -> Self {
Self {
actions,
output,
state: 0,
}
}

pub fn empty() -> Self {
Self::new(Vec::new(), KeyAction::No)
}

pub fn update(&mut self, key_action: KeyAction) -> bool {
let action_idx = self.actions.iter().position(|&a| a == key_action);
if let Some(i) = action_idx {
self.state |= 1 << i;
true
} else {
self.reset();
false
}
}

pub fn done(&self) -> bool {
self.started() && self.keys_pressed() == self.actions.len() as u32
}

pub fn started(&self) -> bool {
self.state != 0
}

pub fn keys_pressed(&self) -> u32 {
self.state.count_ones()
}

pub fn reset(&mut self) {
self.state = 0;
}
}

/// Keymap represents the stack of layers.
///
/// The conception of Keymap in rmk is borrowed from qmk: <https://docs.qmk.fm/#/keymap>.
Expand All @@ -27,6 +78,8 @@ pub(crate) struct KeyMap<'a, const ROW: usize, const COL: usize, const NUM_LAYER
layer_cache: [[u8; COL]; ROW],
/// Macro cache
pub(crate) macro_cache: [u8; MACRO_SPACE_SIZE],
/// Combos
pub(crate) combos: [Combo; COMBO_MAX_NUM],
}

impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
Expand All @@ -39,6 +92,7 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
default_layer: 0,
layer_cache: [[0; COL]; ROW],
macro_cache: [0; MACRO_SPACE_SIZE],
combos: [(); COMBO_MAX_NUM].map(|_| Combo::empty()),
}
}

Expand Down Expand Up @@ -80,6 +134,7 @@ impl<'a, const ROW: usize, const COL: usize, const NUM_LAYER: usize>
default_layer: 0,
layer_cache: [[0; COL]; ROW],
macro_cache,
combos: [(); COMBO_MAX_NUM].map(|_| Combo::empty()),
}
}

Expand Down

0 comments on commit b54444d

Please sign in to comment.