Skip to content

Commit

Permalink
Merge pull request #125 from HaoboGu/feat/hrm
Browse files Browse the repository at this point in the history
Refactor tap/hold processing, add better HRMs support out-of-the-box
  • Loading branch information
HaoboGu authored Dec 11, 2024
2 parents 10f3398 + faf9348 commit 63b4e5c
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 35 deletions.
209 changes: 209 additions & 0 deletions docs/src/images/tap_hold.drawio

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions docs/src/keyboard_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,25 @@ adjust = 3
```
In this example, when both layers 1 (`upper`) and 2 (`lower`) are active, layer 3 (`adjust`) will also be enabled.

#### Tap Hold

In the `tap_hold` sub-table, you can configure the following parameters:

- `enable_hrm`: Enables or disables HRM (Home Row Mod) mode. When enabled, the `prior_idle_time` setting becomes functional. Defaults to `false`.
- `prior_idle_time`: If the previous non-modifier key is released within this period before pressing the current tap-hold key, the tap action for the tap-hold behavior will be triggered. This parameter is effective only when enable_hrm is set to `true`. Defaults to 120ms.
- `hold_timeout`: Defines the duration a tap-hold key must be pressed to determine hold behavior. If tap-hold key is released within this time, the key is recognized as a "tap". Holding it beyond this duration triggers the "hold" action. Defaults to 250ms.
- `post_wait_time`: Adds an additional delay after releasing a tap-hold key to check if any keys pressed during the `hold_timeout` are released. This helps accommodate fast typing scenarios where some keys may not be fully released during a hold. Defaults to 50ms

The following are the typical configurations:

```toml
[behavior]
# Enable HRM
tap_hold = { enable_hrm = true, prior_idle_time = "120ms", hold_timeout = "250ms", post_wait_time = "50ms"}
# Disable HRM, you can safely ignore any fields if you don't want to change them
tap_hold = { enable_hrm = false, hold_timeout = "200ms" }
```

#### One Shot

In the `one_shot` sub-table you can define how long OSM or OSL will wait before releasing the modifier/layer with the `timeout` option, default is one second.
Expand Down
3 changes: 3 additions & 0 deletions examples/use_config/nrf52840_ble_split/keyboard.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ keymap = [
[ble]
enabled = true

[behavior]
tap_hold = { enable_hrm = true, prior_idle_time = "120ms", hold_timeout = "250ms", post_wait_time = "50ms"}

[split]
connection = "ble"

Expand Down
38 changes: 35 additions & 3 deletions rmk-macro/src/behavior.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
//! Initialize behavior config boilerplate of RMK
//!
use quote::quote;

use crate::config::{OneShotConfig, TriLayerConfig};
use crate::config::{OneShotConfig, TapHoldConfig, TriLayerConfig};
use crate::keyboard_config::KeyboardConfig;
use quote::quote;

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

fn expand_tap_hold(tap_hold: &Option<TapHoldConfig>) -> proc_macro2::TokenStream {
let default = quote! {::rmk::config::TapHoldConfig::default()};
match tap_hold {
Some(tap_hold) => {
let enable_hrm = tap_hold.enable_hrm.unwrap_or_default();
let prior_idle_time = match &tap_hold.prior_idle_time {
Some(t) => t.0,
None => 120,
};
let post_wait_time = match &tap_hold.post_wait_time {
Some(t) => t.0,
None => 50,
};
let hold_timeout = match &tap_hold.hold_timeout {
Some(t) => t.0,
None => 250,
};

quote! {
::rmk::config::TapHoldConfig {
enable_hrm: #enable_hrm,
prior_idle_time: ::embassy_time::Duration::from_millis(#prior_idle_time),
post_wait_time: ::embassy_time::Duration::from_millis(#post_wait_time),
hold_timeout: ::embassy_time::Duration::from_millis(#hold_timeout),
}
}
}
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 tap_hold = expand_tap_hold(&keyboard_config.behavior.tap_hold);
let one_shot = expand_one_shot(&keyboard_config.behavior.one_shot);

quote! {
let behavior_config = ::rmk::config::BehaviorConfig {
tri_layer: #tri_layer,
tap_hold: #tap_hold,
one_shot: #one_shot,
};
}
Expand Down
10 changes: 10 additions & 0 deletions rmk-macro/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,19 @@ pub struct LayoutConfig {
#[derive(Clone, Debug, Default, Deserialize)]
pub struct BehaviorConfig {
pub tri_layer: Option<TriLayerConfig>,
pub tap_hold: Option<TapHoldConfig>,
pub one_shot: Option<OneShotConfig>,
}

/// Configurations for tap hold
#[derive(Clone, Debug, Deserialize)]
pub struct TapHoldConfig {
pub enable_hrm: Option<bool>,
pub prior_idle_time: Option<DurationMillis>,
pub post_wait_time: Option<DurationMillis>,
pub hold_timeout: Option<DurationMillis>,
}

/// Configurations for tri layer
#[derive(Clone, Debug, Deserialize)]
pub struct TriLayerConfig {
Expand Down
7 changes: 5 additions & 2 deletions rmk-macro/src/keyboard_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub(crate) struct KeyboardConfig {
pub(crate) board: BoardConfig,
// Layout config
pub(crate) layout: LayoutConfig,
// Begavior Config
// Behavior Config
pub(crate) behavior: BehaviorConfig,
// Light config
pub(crate) light: LightConfig,
Expand Down Expand Up @@ -339,7 +339,9 @@ impl KeyboardConfig {
split: Option<SplitConfig>,
) -> Result<BoardConfig, TokenStream2> {
match (matrix, split) {
(None, Some(s)) => Ok(BoardConfig::Split(s)),
(None, Some(s)) => {
Ok(BoardConfig::Split(s))
},
(Some(m), None) => {
match m.matrix_type {
MatrixType::normal => {
Expand Down Expand Up @@ -438,6 +440,7 @@ impl KeyboardConfig {
None => default.tri_layer,
};

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

Ok(behavior)
Expand Down
3 changes: 2 additions & 1 deletion rmk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Support `direct_pin` type matrix for split configurations
- Support home row mod(HRM) mode with improved tap hold processing
- BREAKING: Support `direct_pin` type matrix for split configurations, move pin config to [split.central/peripheral.matrix]
- Add `clear_storage` option

### Changed
Expand Down
20 changes: 20 additions & 0 deletions rmk/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,29 @@ impl<'a, O: OutputPin> Default for RmkConfig<'a, O> {
#[derive(Default)]
pub struct BehaviorConfig {
pub tri_layer: Option<[u8; 3]>,
pub tap_hold: TapHoldConfig,
pub one_shot: OneShotConfig,
}

/// Configurations for tap hold behavior
pub struct TapHoldConfig {
pub enable_hrm: bool,
pub prior_idle_time: Duration,
pub post_wait_time: Duration,
pub hold_timeout: Duration,
}

impl Default for TapHoldConfig {
fn default() -> Self {
Self {
enable_hrm: false,
prior_idle_time: Duration::from_millis(120),
post_wait_time: Duration::from_millis(50),
hold_timeout: Duration::from_millis(250),
}
}
}

/// Config for one shot behavior
pub struct OneShotConfig {
pub timeout: Duration,
Expand Down
Loading

0 comments on commit 63b4e5c

Please sign in to comment.