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

feat: Added a flag to key press behavior to allow it to be inverted #2554

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-key-press.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ description: Key press/release behavior
compatible: "zmk,behavior-key-press"

include: one_param.yaml

properties:
invert-if-active:
type: boolean
23 changes: 20 additions & 3 deletions app/src/behaviors/behavior_key_press.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <zmk/event_manager.h>
#include <zmk/events/keycode_state_changed.h>
#include <zmk/hid.h>
#include <zmk/behavior.h>

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
Expand All @@ -37,18 +38,30 @@ static const struct behavior_parameter_metadata metadata = {

#endif

struct behavior_key_press_config {
bool invert_if_active;
};

static int behavior_key_press_init(const struct device *dev) { return 0; };

static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
return raise_zmk_keycode_state_changed_from_encoded(binding->param1, true, event.timestamp);
bool pressed = zmk_hid_is_pressed(binding->param1);
const struct behavior_key_press_config *cfg =
zmk_behavior_get_binding(binding->behavior_dev)->config;
return raise_zmk_keycode_state_changed_from_encoded(
binding->param1, !(cfg->invert_if_active & pressed), event.timestamp);
}

static int on_keymap_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
LOG_DBG("position %d keycode 0x%02X", event.position, binding->param1);
return raise_zmk_keycode_state_changed_from_encoded(binding->param1, false, event.timestamp);
bool pressed = zmk_hid_is_pressed(binding->param1);
const struct behavior_key_press_config *cfg =
zmk_behavior_get_binding(binding->behavior_dev)->config;
return raise_zmk_keycode_state_changed_from_encoded(
binding->param1, cfg->invert_if_active & !pressed, event.timestamp);
}

static const struct behavior_driver_api behavior_key_press_driver_api = {
Expand All @@ -60,7 +73,11 @@ static const struct behavior_driver_api behavior_key_press_driver_api = {
};

#define KP_INST(n) \
BEHAVIOR_DT_INST_DEFINE(n, behavior_key_press_init, NULL, NULL, NULL, POST_KERNEL, \
static const struct behavior_key_press_config behavior_key_press_config_##n = { \
DT_INST_PROP_OR(n, invert_if_active, false), \
}; \
BEHAVIOR_DT_INST_DEFINE(n, behavior_key_press_init, NULL, NULL, \
&behavior_key_press_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_key_press_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KP_INST)
2 changes: 1 addition & 1 deletion app/tests/keypress/behavior_keymap.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

default_layer {
bindings = <
&kp B &none
&kp B &kp B
&none &none
>;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s/.*hid_listener_keycode_//p
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "../behavior_keymap.dtsi"

&kp {
invert-if-active;
};

&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
>;
};
47 changes: 47 additions & 0 deletions docs/docs/keymaps/behaviors/key-press.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,50 @@ Example:
```dts
&kp A
```

### Configuration

#### Inverting

By default, ZMK will always send a keycode press event to the host on key press, and a keycode release event on key release.
This is the case even when pressing multiple keys with the same keycode at once, which can cause some unusual interactions.
For example, [toggling a key](./key-toggle.md) on and then pressing and releasing a key with the same keycode will result in the key press cancelling the toggle.

The `invert-if-active` flag allows you to change this interaction. It makes the key press behavior invert its output events.
In other words, if the keycode is pressed on key press, then it releases the keycode. If the keycode is released on key release, then it presses the keycode.

You can set the flag by updating the existing behavior:

```dts
&kp {
invert-if-active;
};

/ {
keymap {
...
};
};
```

If you wish to use both versions in the same keymap, you can define a new key press behavior instead:

```
/ {
behaviors {
kpi: inverting_key_press {
compatible = "zmk,behavior-key-press";
#binding-cells = <1>;
display-name = "Inverting Key Press";
invert-if-active;
};
...
};

keymap {
...
};
};
```

You would then use the normal behavior with `&kp` and the inverting behavior with `&kpi`.