feat: add slow release positions

This commit is contained in:
Theo Lemay 2023-05-22 15:27:25 -07:00
parent df26967cc5
commit 016a77af71
4 changed files with 42 additions and 2 deletions

View file

@ -20,6 +20,10 @@ child-binding:
default: 50 default: 50
slow-release: slow-release:
type: boolean type: boolean
slow-release-positions:
type: array
required: false
default: []
layers: layers:
type: array type: array
default: [-1] default: [-1]

View file

@ -29,9 +29,12 @@ struct combo_cfg {
int32_t key_positions[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO]; int32_t key_positions[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO];
int32_t key_position_len; int32_t key_position_len;
int32_t timeout_ms; int32_t timeout_ms;
// if slow release is set, the combo releases when the last key is released. // if slow release is set, the combo releases when the last key in slow_release_positions is
// otherwise, the combo releases when the first key is released. // released or all keys are released. otherwise, the combo releases when the first key is
// released.
bool slow_release; 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]; int8_t layers[CONFIG_ZMK_COMBO_MAX_LAYERS_PER_COMBO];
int32_t layers_len; int32_t layers_len;
// the virtual key position is a key position outside the range used by the keyboard. // 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. // The keys are removed from this array when they are released.
// Once this array is empty, the behavior is released. // Once this array is empty, the behavior is released.
const zmk_event_t *key_positions_pressed[CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO]; 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 { 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++) { for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS; i++) {
if (active_combos[i].combo == NULL) { if (active_combos[i].combo == NULL) {
active_combos[i].combo = combo; active_combos[i].combo = combo;
active_combos[i].behavior_released = false;
active_combo_count++; active_combo_count++;
return &active_combos[i]; 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) { 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 // partial holds
for (int i = 1; i < active_combo->combo->behaviors_len; i++) { 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_positions = DT_PROP(n, key_positions), \
.key_position_len = DT_PROP_LEN(n, key_positions), \ .key_position_len = DT_PROP_LEN(n, key_positions), \
.slow_release = DT_PROP(n, slow_release), \ .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 = DT_PROP(n, layers), \
.layers_len = DT_PROP_LEN(n, layers), \ .layers_len = DT_PROP_LEN(n, layers), \
.virtual_key_position = ZMK_VIRTUAL_KEY_POSITION_COMBO(__COUNTER__), \ .virtual_key_position = ZMK_VIRTUAL_KEY_POSITION_COMBO(__COUNTER__), \

View file

@ -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 | | | `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 | | `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` | 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>` | | `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. 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.

View file

@ -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. 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 ### 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`. 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`.