Browse Source

feat(behaviors): `&key_repeat` behavior + tests.

* Add new `&key_repeat` behavior that captures and re-sends
  the most recently triggered keycode.

Closes: #853
xmkb
Peter Johanson 3 years ago committed by Pete Johanson
parent
commit
70bb7c9334
  1. 1
      app/CMakeLists.txt
  2. 1
      app/dts/behaviors.dtsi
  3. 19
      app/dts/behaviors/key_repeat.dtsi
  4. 13
      app/dts/bindings/behaviors/zmk,behavior-key-repeat.yaml
  5. 124
      app/src/behaviors/behavior_key_repeat.c
  6. 17
      app/tests/key-repeat/behavior_keymap.dtsi
  7. 2
      app/tests/key-repeat/ignore-other-usage-page-events/events.patterns
  8. 12
      app/tests/key-repeat/ignore-other-usage-page-events/keycode_events.snapshot
  9. 15
      app/tests/key-repeat/ignore-other-usage-page-events/native_posix.keymap
  10. 2
      app/tests/key-repeat/press-and-release-after-key-usage/events.patterns
  11. 8
      app/tests/key-repeat/press-and-release-after-key-usage/keycode_events.snapshot
  12. 13
      app/tests/key-repeat/press-and-release-after-key-usage/native_posix.keymap
  13. 2
      app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/events.patterns
  14. 0
      app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/keycode_events.snapshot
  15. 11
      app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/native_posix.keymap
  16. 38
      docs/docs/behaviors/key-repeat.md

1
app/CMakeLists.txt

@ -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_hold_tap.c)
target_sources(app PRIVATE src/behaviors/behavior_sticky_key.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_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_momentary_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c) target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c)
target_sources(app PRIVATE src/behaviors/behavior_outputs.c) target_sources(app PRIVATE src/behaviors/behavior_outputs.c)

1
app/dts/behaviors.dtsi

@ -15,3 +15,4 @@
#include <behaviors/ext_power.dtsi> #include <behaviors/ext_power.dtsi>
#include <behaviors/outputs.dtsi> #include <behaviors/outputs.dtsi>
#include <behaviors/caps_word.dtsi> #include <behaviors/caps_word.dtsi>
#include <behaviors/key_repeat.dtsi>

19
app/dts/behaviors/key_repeat.dtsi

@ -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
app/dts/bindings/behaviors/zmk,behavior-key-repeat.yaml

@ -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
app/src/behaviors/behavior_key_repeat.c

@ -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
app/tests/key-repeat/behavior_keymap.dtsi

@ -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
>;
};
};
};

2
app/tests/key-repeat/ignore-other-usage-page-events/events.patterns

@ -0,0 +1,2 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p

12
app/tests/key-repeat/ignore-other-usage-page-events/keycode_events.snapshot

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

15
app/tests/key-repeat/ignore-other-usage-page-events/native_posix.keymap

@ -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)
>;
};

2
app/tests/key-repeat/press-and-release-after-key-usage/events.patterns

@ -0,0 +1,2 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p

8
app/tests/key-repeat/press-and-release-after-key-usage/keycode_events.snapshot

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

13
app/tests/key-repeat/press-and-release-after-key-usage/native_posix.keymap

@ -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)
>;
};

2
app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/events.patterns

@ -0,0 +1,2 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p

0
app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/keycode_events.snapshot

11
app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/native_posix.keymap

@ -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
docs/docs/behaviors/key-repeat.md

@ -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 {
...
};
};
```
Loading…
Cancel
Save