From 016a77af71c913ca7c44dc63e235c04209243871 Mon Sep 17 00:00:00 2001 From: Theo Lemay Date: Mon, 22 May 2023 15:27:25 -0700 Subject: [PATCH] feat: add slow release positions --- app/dts/bindings/zmk,combos.yaml | 4 ++++ app/src/combo.c | 33 ++++++++++++++++++++++++++++++-- docs/docs/config/combos.md | 1 + docs/docs/features/combos.md | 6 ++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/dts/bindings/zmk,combos.yaml b/app/dts/bindings/zmk,combos.yaml index d094b5c4..fa18070b 100644 --- a/app/dts/bindings/zmk,combos.yaml +++ b/app/dts/bindings/zmk,combos.yaml @@ -20,6 +20,10 @@ child-binding: default: 50 slow-release: type: boolean + slow-release-positions: + type: array + required: false + default: [] layers: type: array default: [-1] diff --git a/app/src/combo.c b/app/src/combo.c index 31ceec40..ee2a8cfd 100644 --- a/app/src/combo.c +++ b/app/src/combo.c @@ -29,9 +29,12 @@ struct combo_cfg { int32_t key_positions[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO]; int32_t key_position_len; int32_t timeout_ms; - // if slow release is set, the combo releases when the last key is released. - // otherwise, the combo releases when the first key is released. + // if slow release is set, the combo releases when the last key in slow_release_positions is + // released or all keys are released. otherwise, the combo releases when the first key is + // released. bool slow_release; + int32_t slow_release_positions[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO]; + int32_t slow_release_positions_len; int8_t layers[CONFIG_ZMK_COMBO_MAX_LAYERS_PER_COMBO]; int32_t layers_len; // the virtual key position is a key position outside the range used by the keyboard. @@ -47,6 +50,8 @@ struct active_combo { // The keys are removed from this array when they are released. // Once this array is empty, the behavior is released. const zmk_event_t *key_positions_pressed[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO]; + // keep track if the behavior has already been released (used for slow release) + bool behavior_released; }; struct combo_candidate { @@ -315,6 +320,7 @@ static struct active_combo *store_active_combo(struct combo_cfg *combo) { for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS; i++) { if (active_combos[i].combo == NULL) { active_combos[i].combo = combo; + active_combos[i].behavior_released = false; active_combo_count++; return &active_combos[i]; } @@ -374,6 +380,27 @@ static bool release_combo_key(int32_t position, int64_t timestamp, const zmk_eve } if (key_released) { + // slow release + if (!active_combo->combo->slow_release && all_keys_pressed) { + // if slow release is not enabled, release the behavior + process_combo_behavior(active_combo->combo, timestamp, false); + } else if (active_combo->combo->slow_release && !active_combo->behavior_released) { + // if slow release is enabled and the behavior has not yet been released + if (all_keys_released) { + // if all keys are released, release the behavior + process_combo_behavior(active_combo->combo, timestamp, false); + active_combo->behavior_released = true; + } else { + // if the key being released is a slow release key, release the behavior, + // otherwise ignore + for (int i = 0; i < active_combo->combo->slow_release_positions_len; i++) { + if (active_combo->combo->slow_release_positions[i] == position) { + process_combo_behavior(active_combo->combo, timestamp, false); + active_combo->behavior_released = true; + } + } + } + } // partial holds for (int i = 1; i < active_combo->combo->behaviors_len; i++) { @@ -539,6 +566,8 @@ ZMK_SUBSCRIPTION(combo, zmk_position_state_changed); .key_positions = DT_PROP(n, key_positions), \ .key_position_len = DT_PROP_LEN(n, key_positions), \ .slow_release = DT_PROP(n, slow_release), \ + .slow_release_positions = DT_PROP(n, slow_release_positions), \ + .slow_release_positions_len = DT_PROP_LEN(n, slow_release_positions), \ .layers = DT_PROP(n, layers), \ .layers_len = DT_PROP_LEN(n, layers), \ .virtual_key_position = ZMK_VIRTUAL_KEY_POSITION_COMBO(__COUNTER__), \ diff --git a/docs/docs/config/combos.md b/docs/docs/config/combos.md index b0b93b22..86809ab1 100644 --- a/docs/docs/config/combos.md +++ b/docs/docs/config/combos.md @@ -38,6 +38,7 @@ Each child node can have the following properties: | `key-positions` | array | A list of key position indices for the keys which should trigger the combo | | | `timeout-ms` | int | All the keys in `key-positions` must be pressed within this time in milliseconds to trigger the combo | 50 | | `slow-release` | bool | Releases the combo when all keys are released instead of when any key is released | false | +| `slow-release-positions` | array | A list of key position indices for the keys that must be held during `slow-release`. If any key in `slow-release-positions` is released, the combo is released. | | | `layers` | array | A list of layers on which the combo may be triggered. `-1` allows all layers. | `<-1>` | The `key-positions` array must not be longer than the `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` setting, which defaults to 4. If you want a combo that triggers when pressing 5 keys, then you must change the setting to 5. diff --git a/docs/docs/features/combos.md b/docs/docs/features/combos.md index f266be0f..57e460b5 100644 --- a/docs/docs/features/combos.md +++ b/docs/docs/features/combos.md @@ -50,6 +50,12 @@ Invoking a source-specific behavior such as one of the [reset behaviors](behavio See [combo configuration](/docs/config/combos) for advanced configuration options. +### Slow Release + +If you want the combo binding to be released when all positions are released, instead of when any position is released, enable `slow-release`. This is useful for combos that are used to toggle a layer, for example. + +However, you may want to continue to hold the combo when one position is held but not the other. For example, if the keys corresponding to the combo positions 0 and 1 are `&mo NAV` and `&kp A`, and the combo behavior is `&kp LEFT`, you may want to continue holding `LEFT` while you hold `A` and release `NAV`, but not if you hold `NAV` and release `A`. To solve this, you can specify `slow-release-positions` to select which keys must be held to maintain `slow-release`. In this example, you would specify `slow-release-positions = <1>`. In other words, the combo will be held as long _all_ keys in `slow-release-positions` are held, and released when _any_ key in `slow-release-positions` is released. + ### Partial Holds After pressing a combo, you may want to specify the behavior that is activated when the combo is partially released. For example, if the keys corresponding to the combo positions 0, 1, and 2 are `&tog NAV`, `&kp A`, and `&kp LSFT` and the combo behavior is `&kp LEFT`, you may want to activate `&mo NAV` when you release `A` or `LSFT` but continue to hold `NAV`, or activate `LSFT` when you release `NAV` or `A` but continue to hold `LSFT`.