summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Johanson <peter@peterjohanson.com>2021-11-29 04:26:22 +0000
committerPete Johanson <peter@peterjohanson.com>2022-01-30 22:47:34 -0500
commit70bb7c93349344e0990f12282abfcd8d00ba7208 (patch)
treeeeb47ee39ec4a887ef6d1edf809b88709e6269bb
parentac3c3170bd70f5186544495cb5ced7bfbf3f6764 (diff)
feat(behaviors): `&key_repeat` behavior + tests.
* Add new `&key_repeat` behavior that captures and re-sends the most recently triggered keycode. Closes: #853
-rw-r--r--app/CMakeLists.txt1
-rw-r--r--app/dts/behaviors.dtsi1
-rw-r--r--app/dts/behaviors/key_repeat.dtsi19
-rw-r--r--app/dts/bindings/behaviors/zmk,behavior-key-repeat.yaml13
-rw-r--r--app/src/behaviors/behavior_key_repeat.c124
-rw-r--r--app/tests/key-repeat/behavior_keymap.dtsi17
-rw-r--r--app/tests/key-repeat/ignore-other-usage-page-events/events.patterns2
-rw-r--r--app/tests/key-repeat/ignore-other-usage-page-events/keycode_events.snapshot12
-rw-r--r--app/tests/key-repeat/ignore-other-usage-page-events/native_posix.keymap15
-rw-r--r--app/tests/key-repeat/press-and-release-after-key-usage/events.patterns2
-rw-r--r--app/tests/key-repeat/press-and-release-after-key-usage/keycode_events.snapshot8
-rw-r--r--app/tests/key-repeat/press-and-release-after-key-usage/native_posix.keymap13
-rw-r--r--app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/events.patterns2
-rw-r--r--app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/keycode_events.snapshot0
-rw-r--r--app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/native_posix.keymap11
-rw-r--r--docs/docs/behaviors/key-repeat.md38
16 files changed, 278 insertions, 0 deletions
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 970c6c2..c5b74aa 100644
--- a/app/CMakeLists.txt
+++ b/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_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)
diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi
index 5b5f72b..0648961 100644
--- a/app/dts/behaviors.dtsi
+++ b/app/dts/behaviors.dtsi
@@ -15,3 +15,4 @@
#include <behaviors/ext_power.dtsi>
#include <behaviors/outputs.dtsi>
#include <behaviors/caps_word.dtsi>
+#include <behaviors/key_repeat.dtsi>
diff --git a/app/dts/behaviors/key_repeat.dtsi b/app/dts/behaviors/key_repeat.dtsi
new file mode 100644
index 0000000..aa8ffa0
--- /dev/null
+++ b/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>;
+ };
+ };
+};
+
diff --git a/app/dts/bindings/behaviors/zmk,behavior-key-repeat.yaml b/app/dts/bindings/behaviors/zmk,behavior-key-repeat.yaml
new file mode 100644
index 0000000..10b3aa0
--- /dev/null
+++ b/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
diff --git a/app/src/behaviors/behavior_key_repeat.c b/app/src/behaviors/behavior_key_repeat.c
new file mode 100644
index 0000000..b2e28a6
--- /dev/null
+++ b/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
diff --git a/app/tests/key-repeat/behavior_keymap.dtsi b/app/tests/key-repeat/behavior_keymap.dtsi
new file mode 100644
index 0000000..93b6d06
--- /dev/null
+++ b/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
+ >;
+ };
+ };
+};
diff --git a/app/tests/key-repeat/ignore-other-usage-page-events/events.patterns b/app/tests/key-repeat/ignore-other-usage-page-events/events.patterns
new file mode 100644
index 0000000..7947192
--- /dev/null
+++ b/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 \ No newline at end of file
diff --git a/app/tests/key-repeat/ignore-other-usage-page-events/keycode_events.snapshot b/app/tests/key-repeat/ignore-other-usage-page-events/keycode_events.snapshot
new file mode 100644
index 0000000..c06d94a
--- /dev/null
+++ b/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
diff --git a/app/tests/key-repeat/ignore-other-usage-page-events/native_posix.keymap b/app/tests/key-repeat/ignore-other-usage-page-events/native_posix.keymap
new file mode 100644
index 0000000..b042e8e
--- /dev/null
+++ b/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)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/key-repeat/press-and-release-after-key-usage/events.patterns b/app/tests/key-repeat/press-and-release-after-key-usage/events.patterns
new file mode 100644
index 0000000..7947192
--- /dev/null
+++ b/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 \ No newline at end of file
diff --git a/app/tests/key-repeat/press-and-release-after-key-usage/keycode_events.snapshot b/app/tests/key-repeat/press-and-release-after-key-usage/keycode_events.snapshot
new file mode 100644
index 0000000..d568d37
--- /dev/null
+++ b/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
diff --git a/app/tests/key-repeat/press-and-release-after-key-usage/native_posix.keymap b/app/tests/key-repeat/press-and-release-after-key-usage/native_posix.keymap
new file mode 100644
index 0000000..98c8f6f
--- /dev/null
+++ b/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)
+ >;
+}; \ No newline at end of file
diff --git a/app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/events.patterns b/app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/events.patterns
new file mode 100644
index 0000000..7947192
--- /dev/null
+++ b/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 \ No newline at end of file
diff --git a/app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/keycode_events.snapshot b/app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/keycode_events.snapshot
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/keycode_events.snapshot
diff --git a/app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/native_posix.keymap b/app/tests/key-repeat/send-nothing-if-no-keys-pressed-yet/native_posix.keymap
new file mode 100644
index 0000000..9ff6446
--- /dev/null
+++ b/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)
+ >;
+}; \ No newline at end of file
diff --git a/docs/docs/behaviors/key-repeat.md b/docs/docs/behaviors/key-repeat.md
new file mode 100644
index 0000000..5217bce
--- /dev/null
+++ b/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 {
+ ...
+ };
+};
+```