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

Combos support #152

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
25 changes: 24 additions & 1 deletion rmk-macro/src/behavior.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

use quote::quote;

use crate::config::{OneShotConfig, TriLayerConfig};
use crate::config::{CombosConfig, OneShotConfig, TriLayerConfig};
use crate::keyboard_config::KeyboardConfig;
use crate::layout::parse_key;

fn expand_tri_layer(tri_layer: &Option<TriLayerConfig>) -> proc_macro2::TokenStream {
match tri_layer {
Expand Down Expand Up @@ -39,14 +40,36 @@ fn expand_one_shot(one_shot: &Option<OneShotConfig>) -> proc_macro2::TokenStream
}
}

fn expand_combos(combos: &Option<CombosConfig>) -> proc_macro2::TokenStream {
let default = quote! {::core::default::Default::default()};
match combos {
Some(combos) => {
let combos = combos.combos.iter().map(|combo| {
let actions = combo.actions.iter().map(|a| parse_key(a.to_owned()));
let output = parse_key(combo.output.to_owned());
let layer = match combo.layer {
Some(layer) => quote! { ::core::option::Option::Some(#layer) },
None => quote! { ::core::option::Option::None },
};
quote! { ::rmk::combo::Combo::new([#(#actions),*], #output, #layer) }
});

quote! { ::heapless::Vec::from_iter([#(#combos),*]) }
}
None => default,
}
}

pub(crate) fn expand_behavior_config(keyboard_config: &KeyboardConfig) -> proc_macro2::TokenStream {
let tri_layer = expand_tri_layer(&keyboard_config.behavior.tri_layer);
let one_shot = expand_one_shot(&keyboard_config.behavior.one_shot);
let combos = expand_combos(&keyboard_config.behavior.combo);

quote! {
let behavior_config = ::rmk::config::BehaviorConfig {
tri_layer: #tri_layer,
one_shot: #one_shot,
combos: #combos,
};
}
}
17 changes: 16 additions & 1 deletion rmk-macro/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ pub struct LayoutConfig {
pub struct BehaviorConfig {
pub tri_layer: Option<TriLayerConfig>,
pub one_shot: Option<OneShotConfig>,
pub combo: Option<CombosConfig>,
}

/// Configurations for tri layer
Expand All @@ -150,6 +151,20 @@ pub struct OneShotConfig {
pub timeout: Option<DurationMillis>,
}

/// Configurations for combos
#[derive(Clone, Debug, Deserialize)]
pub struct CombosConfig {
pub combos: Vec<ComboConfig>,
}

/// Configurations for combo
#[derive(Clone, Debug, Deserialize)]
pub struct ComboConfig {
pub actions: Vec<String>,
pub output: String,
pub layer: Option<u8>,
}

/// Configurations for split keyboards
#[derive(Clone, Debug, Default, Deserialize)]
pub struct SplitConfig {
Expand Down Expand Up @@ -200,7 +215,7 @@ fn parse_duration_millis<'de, D: de::Deserializer<'de>>(deserializer: D) -> Resu
let unit = &input[num.len()..];
let num: u64 = num.parse().map_err(|_| {
de::Error::custom(format!(
"Invalid number \"{num}\" in [one_shot.timeout]: number part must be a u64"
"Invalid number \"{num}\" in duration: number part must be a u64"
))
})?;

Expand Down
24 changes: 24 additions & 0 deletions rmk-macro/src/keyboard_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ macro_rules! rmk_compile_error {
};
}

// Max number of macros
pub const COMBO_MAX_NUM: usize = 8;
// Max size of macros
pub const COMBO_MAX_LENGTH: usize = 4;

/// Keyboard's basic info
#[allow(unused)]
#[derive(Clone, Debug, Deserialize)]
Expand Down Expand Up @@ -440,6 +445,25 @@ impl KeyboardConfig {

behavior.one_shot = behavior.one_shot.or(default.one_shot);

behavior.combo = behavior.combo.or(default.combo);
if let Some(combo) = &behavior.combo {
if combo.combos.len() > COMBO_MAX_NUM {
return rmk_compile_error!(format!("keyboard.toml: number of combos is greater than [behavior.combo.max_num]"));
}

for (i, c) in combo.combos.iter().enumerate() {
if c.actions.len() > COMBO_MAX_LENGTH {
return rmk_compile_error!(format!("keyboard.toml: number of keys in combo #{i} is greater than [behavior.combo.max_length]"));
}

if let Some(layer) = c.layer {
if layer >= layout.layers {
return rmk_compile_error!(format!("keyboard.toml: layer in combo #{i} is greater than [layout.layers]"));
}
}
}
}

Ok(behavior)
}
None => Ok(default),
Expand Down
2 changes: 1 addition & 1 deletion rmk-macro/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fn expand_row(row: Vec<String>) -> TokenStream2 {
}

/// Parse the key string at a single position
fn parse_key(key: String) -> TokenStream2 {
pub(crate) fn parse_key(key: String) -> TokenStream2 {
if key.len() < 5 {
return if key.len() > 0 && key.trim_start_matches("_").len() == 0 {
quote! { ::rmk::a!(No) }
Expand Down
15 changes: 9 additions & 6 deletions rmk/src/ble/esp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,19 @@ pub(crate) async fn initialize_esp_ble_keyboard_with_config_and_run<
)
.await;

let keymap = RefCell::new(KeyMap::new_from_storage(default_keymap, Some(&mut storage)).await);
let keymap = RefCell::new(
KeyMap::new_from_storage(
default_keymap,
Some(&mut storage),
keyboard_config.behavior_config,
)
.await,
);

let keyboard_report_sender = keyboard_report_channel.sender();
let keyboard_report_receiver = keyboard_report_channel.receiver();

let mut keyboard = Keyboard::new(
&keymap,
&keyboard_report_sender,
keyboard_config.behavior_config,
);
let mut keyboard = Keyboard::new(&keymap, &keyboard_report_sender);
// esp32c3 doesn't have USB device, so there is no usb here
// TODO: add usb service for other chips of esp32 which have USB device

Expand Down
15 changes: 9 additions & 6 deletions rmk/src/ble/nrf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,14 @@ pub(crate) async fn initialize_nrf_ble_keyboard_and_run<
// Flash and keymap configuration
let flash = Flash::take(sd);
let mut storage = Storage::new(flash, default_keymap, keyboard_config.storage_config).await;
let keymap = RefCell::new(KeyMap::new_from_storage(default_keymap, Some(&mut storage)).await);
let keymap = RefCell::new(
KeyMap::new_from_storage(
default_keymap,
Some(&mut storage),
keyboard_config.behavior_config,
)
.await,
);

let mut buf: [u8; 128] = [0; 128];

Expand Down Expand Up @@ -292,11 +299,7 @@ pub(crate) async fn initialize_nrf_ble_keyboard_and_run<
let keyboard_report_receiver = keyboard_report_channel.receiver();

// Keyboard services
let mut keyboard = Keyboard::new(
&keymap,
&keyboard_report_sender,
keyboard_config.behavior_config,
);
let mut keyboard = Keyboard::new(&keymap, &keyboard_report_sender);
#[cfg(not(feature = "_no_usb"))]
let mut usb_device = KeyboardUsbDevice::new(usb_driver, keyboard_config.usb_config);
let mut vial_service = VialService::new(&keymap, keyboard_config.vial_config);
Expand Down
86 changes: 86 additions & 0 deletions rmk/src/combo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use heapless::Vec;

use crate::{action::KeyAction, keyboard::KeyEvent};

// Max number of macros
pub(crate) const COMBO_MAX_NUM: usize = 8;
// Max size of macros
pub(crate) const COMBO_MAX_LENGTH: usize = 4;

#[derive(Clone)]
pub struct Combo {
pub(crate) actions: Vec<KeyAction, COMBO_MAX_LENGTH>,
pub(crate) output: KeyAction,
pub(crate) layer: Option<u8>,
state: u8,
}

impl Default for Combo {
fn default() -> Self {
Self::empty()
}
}

impl Combo {
pub fn new<I: IntoIterator<Item = KeyAction>>(
actions: I,
output: KeyAction,
layer: Option<u8>,
) -> Self {
Self {
actions: Vec::from_iter(actions),
output,
layer,
state: 0,
}
}

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

pub(crate) fn update(
&mut self,
key_action: KeyAction,
key_event: KeyEvent,
active_layer: u8,
) -> bool {
if !key_event.pressed || key_action == KeyAction::No {
return false;
}

if let Some(layer) = self.layer {
if layer != active_layer {
return false;
}
}

let action_idx = self.actions.iter().position(|&a| a == key_action);
if let Some(i) = action_idx {
self.state |= 1 << i;
} else {
self.reset();
}
action_idx.is_some()
}

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

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

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

pub(crate) fn reset(&mut self) {
self.state = 0;
}
}
4 changes: 4 additions & 0 deletions rmk/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod esp_config;
#[cfg(feature = "_nrf_ble")]
mod nrf_config;

use ::heapless::Vec;
#[cfg(feature = "_esp_ble")]
pub use esp_config::BleBatteryConfig;
#[cfg(feature = "_nrf_ble")]
Expand All @@ -11,6 +12,8 @@ pub use nrf_config::BleBatteryConfig;
use embassy_time::Duration;
use embedded_hal::digital::OutputPin;

use crate::combo::{Combo, COMBO_MAX_NUM};

/// Internal configurations for RMK keyboard.
pub struct RmkConfig<'a, O: OutputPin> {
pub mouse_config: MouseConfig,
Expand Down Expand Up @@ -45,6 +48,7 @@ impl<'a, O: OutputPin> Default for RmkConfig<'a, O> {
pub struct BehaviorConfig {
pub tri_layer: Option<[u8; 3]>,
pub one_shot: OneShotConfig,
pub combos: Vec<Combo, COMBO_MAX_NUM>,
}

/// Config for one shot behavior
Expand Down
Loading