Skip to content

Commit

Permalink
feat(behaviors): &key_repeat behavior + tests.
Browse files Browse the repository at this point in the history
* Add new `&key_repeat` behavior that captures and re-sends
  the most recently triggered keycode.

Closes: zmkfirmware#583
  • Loading branch information
petejohanson committed Nov 29, 2021
1 parent 54dabff commit 1af5648
Show file tree
Hide file tree
Showing 16 changed files with 278 additions and 0 deletions.
1 change: 1 addition & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c)
target_sources(app PRIVATE src/behaviors/behavior_sticky_key.c)
target_sources(app PRIVATE src/behaviors/behavior_caps_word.c)
target_sources(app PRIVATE src/behaviors/behavior_key_repeat.c)
target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c)
target_sources(app PRIVATE src/behaviors/behavior_outputs.c)
Expand Down
1 change: 1 addition & 0 deletions app/dts/behaviors.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
#include <behaviors/ext_power.dtsi>
#include <behaviors/outputs.dtsi>
#include <behaviors/caps_word.dtsi>
#include <behaviors/key_repeat.dtsi>
19 changes: 19 additions & 0 deletions app/dts/behaviors/key_repeat.dtsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <dt-bindings/zmk/keys.h>

/ {
behaviors {
/omit-if-no-ref/ key_repeat: behavior_key_repeat {
compatible = "zmk,behavior-key-repeat";
label = "KEY_REPEAT";
#binding-cells = <0>;
usage-pages = <HID_USAGE_KEY>;
};
};
};

13 changes: 13 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-key-repeat.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT

description: Key repeat behavior

compatible: "zmk,behavior-key-repeat"

include: zero_param.yaml

properties:
usage-pages:
type: array
required: true
124 changes: 124 additions & 0 deletions app/src/behaviors/behavior_key_repeat.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#define DT_DRV_COMPAT zmk_behavior_key_repeat

#include <device.h>
#include <drivers/behavior.h>
#include <logging/log.h>
#include <zmk/behavior.h>

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

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

struct behavior_key_repeat_config {
uint8_t index;
uint8_t usage_pages_count;
uint16_t usage_pages[];
};

struct behavior_key_repeat_data {
struct zmk_keycode_state_changed last_keycode_pressed;
struct zmk_keycode_state_changed current_keycode_pressed;
};

static int on_key_repeat_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
const struct device *dev = device_get_binding(binding->behavior_dev);
struct behavior_key_repeat_data *data = dev->data;

if (data->last_keycode_pressed.usage_page == 0) {
return ZMK_BEHAVIOR_OPAQUE;
}

memcpy(&data->current_keycode_pressed, &data->last_keycode_pressed,
sizeof(struct zmk_keycode_state_changed));
data->current_keycode_pressed.timestamp = k_uptime_get();

ZMK_EVENT_RAISE(new_zmk_keycode_state_changed(data->current_keycode_pressed));

return ZMK_BEHAVIOR_OPAQUE;
}

static int on_key_repeat_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
const struct device *dev = device_get_binding(binding->behavior_dev);
struct behavior_key_repeat_data *data = dev->data;

if (data->current_keycode_pressed.usage_page == 0) {
return ZMK_BEHAVIOR_OPAQUE;
}

data->current_keycode_pressed.timestamp = k_uptime_get();
data->current_keycode_pressed.state = false;

ZMK_EVENT_RAISE(new_zmk_keycode_state_changed(data->current_keycode_pressed));
return ZMK_BEHAVIOR_OPAQUE;
}

static const struct behavior_driver_api behavior_key_repeat_driver_api = {
.binding_pressed = on_key_repeat_binding_pressed,
.binding_released = on_key_repeat_binding_released,
};

static int key_repeat_keycode_state_changed_listener(const zmk_event_t *eh);

ZMK_LISTENER(behavior_key_repeat, key_repeat_keycode_state_changed_listener);
ZMK_SUBSCRIPTION(behavior_key_repeat, zmk_keycode_state_changed);

static const struct device *devs[DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)];

static int key_repeat_keycode_state_changed_listener(const zmk_event_t *eh) {
struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh);
if (ev == NULL || !ev->state) {
return ZMK_EV_EVENT_BUBBLE;
}

for (int i = 0; i < DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT); i++) {
const struct device *dev = devs[i];
if (dev == NULL) {
continue;
}

struct behavior_key_repeat_data *data = dev->data;
const struct behavior_key_repeat_config *config = dev->config;

for (int u = 0; u < config->usage_pages_count; u++) {
if (config->usage_pages[u] == ev->usage_page) {
memcpy(&data->last_keycode_pressed, ev, sizeof(struct zmk_keycode_state_changed));
break;
}
}
}

return ZMK_EV_EVENT_BUBBLE;
}

static int behavior_key_repeat_init(const struct device *dev) {
const struct behavior_key_repeat_config *config = dev->config;
devs[config->index] = dev;
return 0;
}

#define KR_INST(n) \
static struct behavior_key_repeat_data behavior_key_repeat_data_##n = {}; \
static struct behavior_key_repeat_config behavior_key_repeat_config_##n = { \
.index = n, \
.usage_pages = DT_INST_PROP(n, usage_pages), \
.usage_pages_count = DT_INST_PROP_LEN(n, usage_pages), \
}; \
DEVICE_DT_INST_DEFINE(n, behavior_key_repeat_init, device_pm_control_nop, \
&behavior_key_repeat_data_##n, &behavior_key_repeat_config_##n, \
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_key_repeat_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KR_INST)

#endif
17 changes: 17 additions & 0 deletions app/tests/key-repeat/behavior_keymap.dtsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>

/ {
keymap {
compatible = "zmk,keymap";
label = "Default keymap";

default_layer {
bindings = <
&key_repeat &kp A
&kp B &kp C_VOL_UP
>;
};
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
pressed: usage_page 0x0c keycode 0xe9 implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x00
released: usage_page 0x0c keycode 0xe9 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

&kscan {
events = <
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

&kscan {
events = <
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
38 changes: 38 additions & 0 deletions docs/docs/behaviors/key-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: Key Repeat Behavior
sidebar_label: Key Repeat
---

## Summary

The key repeat behavior when triggered will send whatever keycode was last sent/triggered.

### Behavior Binding

- Reference: `&key_repeat`

Example:

```
&key_repeat
```

### Configuration

#### Usage Pages

By default, the key repeat will only track the last pressed key from the HID "Key" usage page, and ignore events from other usages, e.g. Consumer page.

If you'd rather have the repeat also capture and send Consumer page usages, you can update the existing behavior:

```
&key_repeat {
usage-pages = <HID_USAGE_KEY HID_USAGE_CONSUMER>;
};
/ {
keymap {
...
};
};
```

0 comments on commit 1af5648

Please sign in to comment.