feat: add partial combo holds
This commit is contained in:
parent
864394b40a
commit
df26967cc5
8 changed files with 191 additions and 37 deletions
|
@ -346,6 +346,10 @@ config ZMK_COMBO_MAX_KEYS_PER_COMBO
|
||||||
int "Maximum number of keys per combo"
|
int "Maximum number of keys per combo"
|
||||||
default 4
|
default 4
|
||||||
|
|
||||||
|
config ZMK_COMBO_MAX_LAYERS_PER_COMBO
|
||||||
|
int "Maximum number of layers specified per combo"
|
||||||
|
default 4
|
||||||
|
|
||||||
#Combo options
|
#Combo options
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
|
|
|
@ -18,4 +18,5 @@
|
||||||
#include <behaviors/caps_word.dtsi>
|
#include <behaviors/caps_word.dtsi>
|
||||||
#include <behaviors/key_repeat.dtsi>
|
#include <behaviors/key_repeat.dtsi>
|
||||||
#include <behaviors/backlight.dtsi>
|
#include <behaviors/backlight.dtsi>
|
||||||
#include <behaviors/macros.dtsi>
|
#include <behaviors/macros.dtsi>
|
||||||
|
#include <behaviors/combos.dtsi>
|
15
app/dts/behaviors/combos.dtsi
Normal file
15
app/dts/behaviors/combos.dtsi
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/ {
|
||||||
|
behaviors {
|
||||||
|
partial_hold_position: combo_partial_hold_position {
|
||||||
|
compatible = "zmk,combo-partial-hold-position";
|
||||||
|
label = "PARTIAL_HOLD_POSITION";
|
||||||
|
#binding-cells = <1>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
8
app/dts/bindings/zmk,combo-partial-hold-position.yaml
Normal file
8
app/dts/bindings/zmk,combo-partial-hold-position.yaml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Copyright (c) 2022 The ZMK Contributors
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: Add a partial hold position to a combo
|
||||||
|
|
||||||
|
compatible: "zmk,combo-partial-hold-position"
|
||||||
|
|
||||||
|
include: one_param.yaml
|
105
app/src/combo.c
105
app/src/combo.c
|
@ -28,16 +28,17 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
struct combo_cfg {
|
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;
|
||||||
struct zmk_behavior_binding behavior;
|
|
||||||
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 is released.
|
||||||
// otherwise, the combo releases when the first key is released.
|
// otherwise, the combo releases when the first key is released.
|
||||||
bool slow_release;
|
bool slow_release;
|
||||||
|
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.
|
// the virtual key position is a key position outside the range used by the keyboard.
|
||||||
// it is necessary so hold-taps can uniquely identify a behavior.
|
// it is necessary so hold-taps can uniquely identify a behavior.
|
||||||
int32_t virtual_key_position;
|
int32_t virtual_key_position;
|
||||||
int32_t layers_len;
|
int32_t behaviors_len;
|
||||||
int8_t layers[];
|
struct zmk_behavior_binding behaviors[];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct active_combo {
|
struct active_combo {
|
||||||
|
@ -266,22 +267,32 @@ static int release_pressed_keys() {
|
||||||
return CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO;
|
return CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int press_combo_behavior(struct combo_cfg *combo, int32_t timestamp) {
|
static inline int process_behavior(int32_t position, int32_t timestamp,
|
||||||
|
struct zmk_behavior_binding *behavior, bool pressed) {
|
||||||
struct zmk_behavior_binding_event event = {
|
struct zmk_behavior_binding_event event = {
|
||||||
.position = combo->virtual_key_position,
|
.position = position,
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
return behavior_keymap_binding_pressed(&combo->behavior, event);
|
if (pressed) {
|
||||||
|
return behavior_keymap_binding_pressed(behavior, event);
|
||||||
|
} else {
|
||||||
|
return behavior_keymap_binding_released(behavior, event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int release_combo_behavior(struct combo_cfg *combo, int32_t timestamp) {
|
static inline int process_combo_behavior(struct combo_cfg *combo, int32_t timestamp, bool pressed) {
|
||||||
struct zmk_behavior_binding_event event = {
|
return process_behavior(combo->virtual_key_position, timestamp, &combo->behaviors[0], pressed);
|
||||||
.position = combo->virtual_key_position,
|
}
|
||||||
.timestamp = timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
return behavior_keymap_binding_released(&combo->behavior, event);
|
// press or release the behavior at the given position, used for partial holds
|
||||||
|
static inline int process_position(int32_t position, int32_t timestamp, const zmk_event_t *ev,
|
||||||
|
bool pressed) {
|
||||||
|
const struct zmk_position_state_changed *pos_ev;
|
||||||
|
if ((pos_ev = as_zmk_position_state_changed(ev)) != NULL) {
|
||||||
|
return zmk_keymap_position_state_changed(pos_ev->source, position, pressed, timestamp);
|
||||||
|
}
|
||||||
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void move_pressed_keys_to_active_combo(struct active_combo *active_combo) {
|
static void move_pressed_keys_to_active_combo(struct active_combo *active_combo) {
|
||||||
|
@ -322,8 +333,9 @@ static void activate_combo(struct combo_cfg *combo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
move_pressed_keys_to_active_combo(active_combo);
|
move_pressed_keys_to_active_combo(active_combo);
|
||||||
press_combo_behavior(
|
process_combo_behavior(
|
||||||
combo, as_zmk_position_state_changed(active_combo->key_positions_pressed[0])->timestamp);
|
combo, as_zmk_position_state_changed(active_combo->key_positions_pressed[0])->timestamp,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void deactivate_combo(int active_combo_index) {
|
static void deactivate_combo(int active_combo_index) {
|
||||||
|
@ -336,8 +348,12 @@ static void deactivate_combo(int active_combo_index) {
|
||||||
active_combos[active_combo_count] = (struct active_combo){0};
|
active_combos[active_combo_count] = (struct active_combo){0};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define ZM_IS_NODE_MATCH(a, b) (strcmp(a, b) == 0)
|
||||||
|
#define PARTIAL_HOLD_POSITION DT_PROP(DT_INST(0, zmk_combo_partial_hold_position), label)
|
||||||
|
#define IS_PARTIAL_HOLD_POSITION(dev) ZM_IS_NODE_MATCH(dev, PARTIAL_HOLD_POSITION)
|
||||||
|
|
||||||
/* returns true if a key was released. */
|
/* returns true if a key was released. */
|
||||||
static bool release_combo_key(int32_t position, int64_t timestamp) {
|
static bool release_combo_key(int32_t position, int64_t timestamp, const zmk_event_t *ev) {
|
||||||
for (int combo_idx = 0; combo_idx < active_combo_count; combo_idx++) {
|
for (int combo_idx = 0; combo_idx < active_combo_count; combo_idx++) {
|
||||||
struct active_combo *active_combo = &active_combos[combo_idx];
|
struct active_combo *active_combo = &active_combos[combo_idx];
|
||||||
|
|
||||||
|
@ -358,10 +374,51 @@ static bool release_combo_key(int32_t position, int64_t timestamp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key_released) {
|
if (key_released) {
|
||||||
if ((active_combo->combo->slow_release && all_keys_released) ||
|
|
||||||
(!active_combo->combo->slow_release && all_keys_pressed)) {
|
// partial holds
|
||||||
release_combo_behavior(active_combo->combo, timestamp);
|
for (int i = 1; i < active_combo->combo->behaviors_len; i++) {
|
||||||
|
// loop through every behavior except the first. If the behavior is a partial hold
|
||||||
|
// position, process it. If the next behavior is also a behavior, press that
|
||||||
|
// behavior instead of the partial hold position.
|
||||||
|
struct zmk_behavior_binding *binding = &active_combo->combo->behaviors[i];
|
||||||
|
// if the behavior is not a partial hold position, skip it
|
||||||
|
if (!IS_PARTIAL_HOLD_POSITION(binding->behavior_dev)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int32_t partial_hold_position = binding->param1;
|
||||||
|
// if the next behavior is a behavior (not a partial hold position)
|
||||||
|
bool has_explicit_behavior =
|
||||||
|
i == active_combo->combo->behaviors_len - 1
|
||||||
|
? false
|
||||||
|
: !IS_PARTIAL_HOLD_POSITION(
|
||||||
|
active_combo->combo->behaviors[i + 1].behavior_dev);
|
||||||
|
|
||||||
|
// if all keys are pressed, press all other partial hold keys that are still held
|
||||||
|
if (all_keys_pressed) {
|
||||||
|
// except the one that was released
|
||||||
|
if (partial_hold_position != position) {
|
||||||
|
// either press the position, or press the behavior
|
||||||
|
if (has_explicit_behavior) {
|
||||||
|
process_behavior(position, timestamp,
|
||||||
|
&active_combo->combo->behaviors[i + 1], true);
|
||||||
|
} else {
|
||||||
|
process_position(partial_hold_position, timestamp, ev, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// now release any partial hold keys that are released
|
||||||
|
if (partial_hold_position == position) {
|
||||||
|
// either release the position, or release the behavior
|
||||||
|
if (has_explicit_behavior) {
|
||||||
|
process_behavior(position, timestamp,
|
||||||
|
&active_combo->combo->behaviors[i + 1], false);
|
||||||
|
} else {
|
||||||
|
process_position(partial_hold_position, timestamp, ev, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (all_keys_released) {
|
if (all_keys_released) {
|
||||||
deactivate_combo(combo_idx);
|
deactivate_combo(combo_idx);
|
||||||
}
|
}
|
||||||
|
@ -432,7 +489,7 @@ static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_
|
||||||
|
|
||||||
static int position_state_up(const zmk_event_t *ev, struct zmk_position_state_changed *data) {
|
static int position_state_up(const zmk_event_t *ev, struct zmk_position_state_changed *data) {
|
||||||
int released_keys = cleanup();
|
int released_keys = cleanup();
|
||||||
if (release_combo_key(data->position, data->timestamp)) {
|
if (release_combo_key(data->position, data->timestamp, ev)) {
|
||||||
return ZMK_EV_EVENT_HANDLED;
|
return ZMK_EV_EVENT_HANDLED;
|
||||||
}
|
}
|
||||||
if (released_keys > 1) {
|
if (released_keys > 1) {
|
||||||
|
@ -471,16 +528,22 @@ static int position_state_changed_listener(const zmk_event_t *ev) {
|
||||||
ZMK_LISTENER(combo, position_state_changed_listener);
|
ZMK_LISTENER(combo, position_state_changed_listener);
|
||||||
ZMK_SUBSCRIPTION(combo, zmk_position_state_changed);
|
ZMK_SUBSCRIPTION(combo, zmk_position_state_changed);
|
||||||
|
|
||||||
|
#define BINDING_WITH_COMMA(idx, drv_inst) ZMK_KEYMAP_EXTRACT_BINDING(idx, drv_inst)
|
||||||
|
|
||||||
|
#define TRANSFORMED_BEHAVIORS(n) \
|
||||||
|
{ LISTIFY(DT_PROP_LEN(n, bindings), BINDING_WITH_COMMA, (, ), n) }
|
||||||
|
|
||||||
#define COMBO_INST(n) \
|
#define COMBO_INST(n) \
|
||||||
static struct combo_cfg combo_config_##n = { \
|
static struct combo_cfg combo_config_##n = { \
|
||||||
.timeout_ms = DT_PROP(n, timeout_ms), \
|
.timeout_ms = DT_PROP(n, timeout_ms), \
|
||||||
.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), \
|
||||||
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \
|
|
||||||
.virtual_key_position = ZMK_VIRTUAL_KEY_POSITION_COMBO(__COUNTER__), \
|
|
||||||
.slow_release = DT_PROP(n, slow_release), \
|
.slow_release = DT_PROP(n, slow_release), \
|
||||||
.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__), \
|
||||||
|
.behaviors = TRANSFORMED_BEHAVIORS(n), \
|
||||||
|
.behaviors_len = DT_PROP_LEN(n, bindings), \
|
||||||
};
|
};
|
||||||
|
|
||||||
#define INITIALIZE_COMBO(n) initialize_combo(&combo_config_##n);
|
#define INITIALIZE_COMBO(n) initialize_combo(&combo_config_##n);
|
||||||
|
|
|
@ -11,11 +11,12 @@ See [Configuration Overview](index.md) for instructions on how to change these s
|
||||||
|
|
||||||
Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)
|
Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig)
|
||||||
|
|
||||||
| Config | Type | Description | Default |
|
| Config | Type | Description | Default |
|
||||||
| ------------------------------------- | ---- | -------------------------------------------------------------- | ------- |
|
| --------------------------------------- | ---- | -------------------------------------------------------------- | ------- |
|
||||||
| `CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS` | int | Maximum number of combos that can be active at the same time | 4 |
|
| `CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS` | int | Maximum number of combos that can be active at the same time | 4 |
|
||||||
| `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` | int | Maximum number of active combos that use the same key position | 5 |
|
| `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` | int | Maximum number of active combos that use the same key position | 5 |
|
||||||
| `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` | int | Maximum number of keys to press to activate a combo | 4 |
|
| `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` | int | Maximum number of keys to press to activate a combo | 4 |
|
||||||
|
| `CONFIG_ZMK_COMBO_MAX_LAYERS_PER_COMBO` | int | Maximum number of layers specified per combo | 4 |
|
||||||
|
|
||||||
If `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` is 5, you can have 5 separate combos that use position `0`, 5 combos that use position `1`, and so on.
|
If `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` is 5, you can have 5 separate combos that use position `0`, 5 combos that use position `1`, and so on.
|
||||||
|
|
||||||
|
@ -31,12 +32,12 @@ The `zmk,combos` node itself has no properties. It should have one child node pe
|
||||||
|
|
||||||
Each child node can have the following properties:
|
Each child node can have the following properties:
|
||||||
|
|
||||||
| Property | Type | Description | Default |
|
| Property | Type | Description | Default |
|
||||||
| --------------- | ------------- | ----------------------------------------------------------------------------------------------------- | ------- |
|
| ------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
|
||||||
| `bindings` | phandle-array | A [behavior](../features/keymaps.md#behaviors) to run when the combo is triggered | |
|
| `bindings` | phandle-array | A [behavior](../features/keymaps.md#behaviors) to run when the combo is triggered, along with optional `partial-hold-position` specifiers with associated bindings | |
|
||||||
| `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 |
|
||||||
| `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.
|
||||||
|
|
|
@ -404,6 +404,7 @@ After creating the `.dtsi` from above, update `app/dts/behaviors.dtsi` to includ
|
||||||
#include <behaviors/key_repeat.dtsi>
|
#include <behaviors/key_repeat.dtsi>
|
||||||
#include <behaviors/backlight.dtsi>
|
#include <behaviors/backlight.dtsi>
|
||||||
#include <behaviors/macros.dtsi>
|
#include <behaviors/macros.dtsi>
|
||||||
|
#include <behaviors/combos.dtsi>
|
||||||
// highlight-next-line
|
// highlight-next-line
|
||||||
#include <behaviors/new_behavior_instance.dtsi>
|
#include <behaviors/new_behavior_instance.dtsi>
|
||||||
```
|
```
|
||||||
|
|
|
@ -6,7 +6,7 @@ title: Combos
|
||||||
|
|
||||||
Combo keys are a way to combine multiple keypresses to output a different key. For example, you can hit the Q and W keys on your keyboard to output escape.
|
Combo keys are a way to combine multiple keypresses to output a different key. For example, you can hit the Q and W keys on your keyboard to output escape.
|
||||||
|
|
||||||
### Configuration
|
## Configuration
|
||||||
|
|
||||||
Combos configured in your `.keymap` file, but are separate from the `keymap` node found there, since they are processed before the normal keymap. They are specified like this:
|
Combos configured in your `.keymap` file, but are separate from the `keymap` node found there, since they are processed before the normal keymap. They are specified like this:
|
||||||
|
|
||||||
|
@ -29,7 +29,8 @@ Combos configured in your `.keymap` file, but are separate from the `keymap` nod
|
||||||
- `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board.
|
- `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board.
|
||||||
- `layers = <0 1...>` will allow limiting a combo to specific layers. This is an _optional_ parameter, when omitted it defaults to global scope.
|
- `layers = <0 1...>` will allow limiting a combo to specific layers. This is an _optional_ parameter, when omitted it defaults to global scope.
|
||||||
- `bindings` is the behavior that is activated when the behavior is pressed.
|
- `bindings` is the behavior that is activated when the behavior is pressed.
|
||||||
- (advanced) you can specify `slow-release` if you want the combo binding to be released when all key-positions are released. The default is to release the combo as soon as any of the keys in the combo is released.
|
- (advanced) you can specify `slow-release` if you want the combo binding to be released when all key-positions are released. The default is to release the combo as soon as any of the keys in the combo is released. You can also specify `slow-release-positions` to select which key-positions _must_ be held to maintain the combo.
|
||||||
|
- (advanced) you can add `partial-hold-position` to the `bindings` array, optionally along with additional behaviors, to control what happens when a combo is partially released.
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ Key positions are numbered like the keys in your keymap, starting at 0. So, if t
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Advanced usage
|
## Advanced usage
|
||||||
|
|
||||||
- Partially overlapping combos like `0 1` and `0 2` are supported.
|
- Partially overlapping combos like `0 1` and `0 2` are supported.
|
||||||
- Fully overlapping combos like `0 1` and `0 1 2` are supported.
|
- Fully overlapping combos like `0 1` and `0 1 2` are supported.
|
||||||
|
@ -48,3 +49,63 @@ 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.
|
||||||
|
|
||||||
|
### 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`.
|
||||||
|
|
||||||
|
To do this, you can add `partial-hold-position` to the `bindings` array, optionally along with associated behaviors. When no explicit behavior is specified, it will default to the behavior belonging to the key position.
|
||||||
|
|
||||||
|
In this example, you would specify:
|
||||||
|
|
||||||
|
```
|
||||||
|
combo_nav {
|
||||||
|
timeout-ms = <50>;
|
||||||
|
key-positions = <0 1 2>;
|
||||||
|
bindings
|
||||||
|
= <&kp LEFT>
|
||||||
|
, <&partial-hold-position 0 &mo NAV>
|
||||||
|
, <&partial-hold-position 2> // defaults to &kp LSFT
|
||||||
|
;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Partial Hold with Slow Release Positions
|
||||||
|
|
||||||
|
Partial holds compliment `slow-release-positions`, by letting you control what happens when a combo is partially released. The best motivating example is a combo that is used to "accelerate" an existing thumb momentary layer with a key on that layer, allowing you to mash the keys together at the same time. The following layout is an example of this:
|
||||||
|
|
||||||
|
```
|
||||||
|
#define NAV 1
|
||||||
|
/ {
|
||||||
|
combos {
|
||||||
|
compatible = "zmk,combos";
|
||||||
|
combo_nav {
|
||||||
|
timeout-ms = <50>;
|
||||||
|
key-positions = <0 1>;
|
||||||
|
bindings = <&kp LEFT>, <&partial-hold-position 0>;
|
||||||
|
slow-release;
|
||||||
|
slow-release-positions = <1>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
keymap {
|
||||||
|
compatible = "zmk,keymap";
|
||||||
|
|
||||||
|
default_layer {
|
||||||
|
bindings = <
|
||||||
|
&mo NAV &kp A &kp B
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
nav_layer {
|
||||||
|
bindings = <
|
||||||
|
&mo NAV &kp LEFT &kp RIGHT
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, you can press `LEFT` by pressing `NAV`, pressing `LEFT`, then releasing `NAV`. However, this requires a slight pause to ensure `NAV` was pressed before `LEFT`, so that `A` isn't pressed instead. What if you wanted to be able to mash the keys together at the same time, and achieve a consistent result? This is what the combo does — it allows you to press `LEFT` up to 50ms before `NAV` and still activate `LEFT`.
|
||||||
|
|
||||||
|
However, the introduction of the combo means that you can no longer release `LEFT` while holding `NAV` then press `RIGHT`. This is because the combo will be released when `LEFT` is released, and `B` will be pressed instead. To solve this, you can use `partial-hold-position` to specify that `NAV` should be pressed when `LEFT` is released. Finally, you can use `slow-release-positions` to specify that the combo should be held as long as `LEFT` is held, allowing you to use key-repeat while holding `LEFT` but releasing `NAV`.
|
||||||
|
|
Loading…
Add table
Reference in a new issue