From af11869259ad711b2c4ca23a5d26ec979e40531f Mon Sep 17 00:00:00 2001 From: Jonni Liljamo Date: Tue, 18 Nov 2025 21:40:16 +0200 Subject: [PATCH] feat(lily58): turbo_a --- config/lily58.keymap | 9 +- flake.nix | 1 + patches/003-turbo-key-pr-1414.patch | 639 ++++++++++++++++++++++++++++ 3 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 patches/003-turbo-key-pr-1414.patch diff --git a/config/lily58.keymap b/config/lily58.keymap index e24bf97..774d294 100644 --- a/config/lily58.keymap +++ b/config/lily58.keymap @@ -25,6 +25,13 @@ bindings = <&kp ESC>, <&kp TILDE>; mods = <(MOD_LSFT|MOD_RSFT)>; }; + + turbo_a: turbo_a { + compatible = "zmk,behavior-turbo-key"; + #binding-cells = <0>; + bindings = <&kp A>; + wait-ms = <50>; + }; }; keymap { @@ -44,7 +51,7 @@ bindings = < &bt BT_CLR &bt BT_PRV &bt BT_NXT &none &out OUT_BLE &out OUT_USB &none &none &none &none &none &none &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 &kp F11 &kp F12 -&none &none &none &none &none &none &none &kp INSERT &kp HOME &kp PAGE_UP &none &none +&none &turbo_a &none &none &none &none &none &kp INSERT &kp HOME &kp PAGE_UP &none &none &none &none &none &none &none &none &none &none &none &kp DELETE &kp END &kp PAGE_DOWN &none &none &none &none &none &none &none &none &none &none >; diff --git a/flake.nix b/flake.nix index 18b6e54..7dad2ca 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,7 @@ postConfigure = '' patch -d ../zmk/ -p1 < ${./patches/001-lily58-pin.patch} + patch -d ./zmk/ -p1 < ${./patches/003-turbo-key-pr-1414.patch} ''; meta = { diff --git a/patches/003-turbo-key-pr-1414.patch b/patches/003-turbo-key-pr-1414.patch new file mode 100644 index 0000000..bdf3b77 --- /dev/null +++ b/patches/003-turbo-key-pr-1414.patch @@ -0,0 +1,639 @@ +diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt +index 60c502fc..2361edb7 100644 +--- a/app/CMakeLists.txt ++++ b/app/CMakeLists.txt +@@ -48,6 +48,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) + target_sources(app PRIVATE src/hid.c) + target_sources(app PRIVATE src/behaviors/behavior_key_press.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_KEY_TOGGLE app PRIVATE src/behaviors/behavior_key_toggle.c) ++ target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_TURBO_KEY app PRIVATE src/behaviors/behavior_turbo_key.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_HOLD_TAP app PRIVATE src/behaviors/behavior_hold_tap.c) + target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_STICKY_KEY app PRIVATE src/behaviors/behavior_sticky_key.c) + target_sources(app PRIVATE src/behaviors/behavior_caps_word.c) +diff --git a/app/Kconfig.behaviors b/app/Kconfig.behaviors +index da9bcc41..724c5b17 100644 +--- a/app/Kconfig.behaviors ++++ b/app/Kconfig.behaviors +@@ -134,3 +134,8 @@ config ZMK_BEHAVIOR_MACRO + bool + default y + depends on DT_HAS_ZMK_BEHAVIOR_MACRO_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_MACRO_TWO_PARAM_ENABLED ++ ++config ZMK_BEHAVIOR_TURBO_KEY ++ bool ++ default y ++ depends on DT_HAS_ZMK_BEHAVIOR_TURBO_KEY_ENABLED || DT_HAS_ZMK_BEHAVIOR_TURBO_KEY_ONE_PARAM_ENABLED || DT_HAS_ZMK_BEHAVIOR_TURBO_KEY_TWO_PARAM_ENABLED +diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi +index 653b085d..cd8cfd72 100644 +--- a/app/dts/behaviors.dtsi ++++ b/app/dts/behaviors.dtsi +@@ -28,3 +28,4 @@ + #include + #include + #include ++#include +diff --git a/app/dts/behaviors/turbo.dtsi b/app/dts/behaviors/turbo.dtsi +new file mode 100644 +index 00000000..6e74110c +--- /dev/null ++++ b/app/dts/behaviors/turbo.dtsi +@@ -0,0 +1,52 @@ ++/* ++ * Copyright (c) 2025 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#define TURBO_PLACEHOLDER 0 ++ ++#define ZMK_TURBO(name,...) \ ++name: name { \ ++ compatible = "zmk,behavior-turbo-key"; \ ++ #binding-cells = <0>; \ ++ __VA_ARGS__ \ ++}; ++ ++#define ZMK_TURBO1(name,...) \ ++name: name { \ ++ compatible = "zmk,behavior-turbo-key-one-param"; \ ++ #binding-cells = <1>; \ ++ __VA_ARGS__ \ ++}; ++ ++#define ZMK_TURBO2(name,...) \ ++name: name { \ ++ compatible = "zmk,behavior-turbo-key-two-param"; \ ++ #binding-cells = <2>; \ ++ __VA_ARGS__ \ ++}; ++ ++/ { ++ behaviors { ++ turbo_param_1to1: turbo_param_1to1 { ++ compatible = "zmk,turbo-param-1to1"; ++ #binding-cells = <0>; ++ }; ++ ++ turbo_param_1to2: turbo_param_1to2 { ++ compatible = "zmk,turbo-param-1to2"; ++ #binding-cells = <0>; ++ }; ++ ++ turbo_param_2to1: turbo_param_2to1 { ++ compatible = "zmk,turbo-param-2to1"; ++ #binding-cells = <0>; ++ }; ++ ++ turbo_param_2to2: turbo_param_2to2 { ++ compatible = "zmk,turbo-param-2to2"; ++ #binding-cells = <0>; ++ }; ++ }; ++}; +diff --git a/app/dts/bindings/behaviors/turbo_base.yaml b/app/dts/bindings/behaviors/turbo_base.yaml +new file mode 100644 +index 00000000..6470ce7e +--- /dev/null ++++ b/app/dts/bindings/behaviors/turbo_base.yaml +@@ -0,0 +1,16 @@ ++# Copyright (c) 2025 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++properties: ++ bindings: ++ type: phandle-array ++ required: true ++ wait-ms: ++ type: int ++ default: 200 ++ tap-ms: ++ type: int ++ default: 5 ++ toggle-term-ms: ++ type: int ++ default: -1 +diff --git a/app/dts/bindings/behaviors/zmk,behavior-turbo-key-one-param.yaml b/app/dts/bindings/behaviors/zmk,behavior-turbo-key-one-param.yaml +new file mode 100644 +index 00000000..3001ab55 +--- /dev/null ++++ b/app/dts/bindings/behaviors/zmk,behavior-turbo-key-one-param.yaml +@@ -0,0 +1,8 @@ ++# Copyright (c) 2025 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++description: Turbo key behavior ++ ++compatible: "zmk,behavior-turbo-key-one-param" ++ ++include: [one_param.yaml, turbo_base.yaml] +diff --git a/app/dts/bindings/behaviors/zmk,behavior-turbo-key-two-param.yaml b/app/dts/bindings/behaviors/zmk,behavior-turbo-key-two-param.yaml +new file mode 100644 +index 00000000..15bbaae5 +--- /dev/null ++++ b/app/dts/bindings/behaviors/zmk,behavior-turbo-key-two-param.yaml +@@ -0,0 +1,8 @@ ++# Copyright (c) 2025 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++description: Turbo key behavior ++ ++compatible: "zmk,behavior-turbo-key-two-param" ++ ++include: [two_param.yaml, turbo_base.yaml] +diff --git a/app/dts/bindings/behaviors/zmk,behavior-turbo-key.yaml b/app/dts/bindings/behaviors/zmk,behavior-turbo-key.yaml +new file mode 100644 +index 00000000..8130ac02 +--- /dev/null ++++ b/app/dts/bindings/behaviors/zmk,behavior-turbo-key.yaml +@@ -0,0 +1,8 @@ ++# Copyright (c) 2022 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++description: Turbo key behavior ++ ++compatible: "zmk,behavior-turbo-key" ++ ++include: [zero_param.yaml, turbo_base.yaml] +diff --git a/app/dts/bindings/turbo/zmk,turbo-param-1to1.yaml b/app/dts/bindings/turbo/zmk,turbo-param-1to1.yaml +new file mode 100644 +index 00000000..082a8ac6 +--- /dev/null ++++ b/app/dts/bindings/turbo/zmk,turbo-param-1to1.yaml +@@ -0,0 +1,8 @@ ++# Copyright (c) 2025 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++description: Turbo Parameter One Substituted Into Next Binding's First Parameter ++ ++compatible: "zmk,turbo-param-1to1" ++ ++include: zero_param.yaml +diff --git a/app/dts/bindings/turbo/zmk,turbo-param-1to2.yaml b/app/dts/bindings/turbo/zmk,turbo-param-1to2.yaml +new file mode 100644 +index 00000000..fa601a75 +--- /dev/null ++++ b/app/dts/bindings/turbo/zmk,turbo-param-1to2.yaml +@@ -0,0 +1,8 @@ ++# Copyright (c) 2025 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++description: Turbo Parameter One Substituted Into Next Binding's Second Parameter ++ ++compatible: "zmk,turbo-param-1to2" ++ ++include: zero_param.yaml +diff --git a/app/dts/bindings/turbo/zmk,turbo-param-2to1.yaml b/app/dts/bindings/turbo/zmk,turbo-param-2to1.yaml +new file mode 100644 +index 00000000..9fc4c5ac +--- /dev/null ++++ b/app/dts/bindings/turbo/zmk,turbo-param-2to1.yaml +@@ -0,0 +1,8 @@ ++# Copyright (c) 2025 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++description: Turbo Parameter Two Substituted Into Next Binding's First Parameter ++ ++compatible: "zmk,turbo-param-2to1" ++ ++include: zero_param.yaml +diff --git a/app/dts/bindings/turbo/zmk,turbo-param-2to2.yaml b/app/dts/bindings/turbo/zmk,turbo-param-2to2.yaml +new file mode 100644 +index 00000000..ceea8ed1 +--- /dev/null ++++ b/app/dts/bindings/turbo/zmk,turbo-param-2to2.yaml +@@ -0,0 +1,8 @@ ++# Copyright (c) 2025 The ZMK Contributors ++# SPDX-License-Identifier: MIT ++ ++description: Turbo Parameter Two Substituted Into Next Binding's Second Parameter ++ ++compatible: "zmk,turbo-param-2to2" ++ ++include: zero_param.yaml +diff --git a/app/src/behaviors/behavior_turbo_key.c b/app/src/behaviors/behavior_turbo_key.c +new file mode 100644 +index 00000000..65813c21 +--- /dev/null ++++ b/app/src/behaviors/behavior_turbo_key.c +@@ -0,0 +1,238 @@ ++/* ++ * Copyright (c) 2022 The ZMK Contributors ++ * ++ * SPDX-License-Identifier: MIT ++ */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); ++ ++struct behavior_turbo_data { ++ int32_t tap_ms; ++ int32_t wait_ms; ++ int32_t toggle_term_ms; ++ ++ uint32_t position; ++ bool is_active; ++ bool is_pressed; ++ ++ int32_t press_time; ++ ++ // Timer Data ++ bool timer_started; ++ bool timer_cancelled; ++ bool turbo_decided; ++ int64_t release_at; ++ struct k_work_delayable release_timer; ++ ++ uint32_t binding_count; ++ struct zmk_behavior_binding binding; ++ struct zmk_behavior_binding new_binding; ++ const struct zmk_behavior_binding bindings[]; ++}; ++ ++static int stop_timer(struct behavior_turbo_data *data) { ++ int timer_cancel_result = k_work_cancel_delayable(&data->release_timer); ++ if (timer_cancel_result == -EINPROGRESS) { ++ // too late to cancel, we'll let the timer handler clear up. ++ data->timer_cancelled = true; ++ } ++ return timer_cancel_result; ++} ++ ++static void clear_turbo(struct behavior_turbo_data *data) { ++ LOG_DBG("Turbo deactivated at position %d", data->position); ++ data->is_active = false; ++ stop_timer(data); ++} ++ ++static void reset_timer(struct behavior_turbo_data *data, struct zmk_behavior_binding_event event) { ++ data->release_at = event.timestamp + data->wait_ms; ++ int32_t ms_left = data->release_at - k_uptime_get(); ++ if (ms_left > 0) { ++ k_work_schedule(&data->release_timer, K_MSEC(ms_left)); ++ LOG_DBG("Successfully reset turbo timer at position %d", data->position); ++ } ++} ++ ++static void press_turbo_binding(struct zmk_behavior_binding_event *event, ++ const struct behavior_turbo_data *data) { ++ LOG_DBG("Pressing turbo binding %s, %d, %d", data->binding.behavior_dev, data->binding.param1, ++ data->binding.param2); ++ zmk_behavior_queue_add(event, data->binding, true, data->tap_ms); ++ zmk_behavior_queue_add(event, data->binding, false, 0); ++} ++ ++static void behavior_turbo_timer_handler(struct k_work *item) { ++ struct k_work_delayable *d_work = k_work_delayable_from_work(item); ++ ++ struct behavior_turbo_data *data = ++ CONTAINER_OF(d_work, struct behavior_turbo_data, release_timer); ++ ++ if (!data->is_active || data->timer_cancelled) { ++ return; ++ } ++ ++ LOG_DBG("Turbo timer reached."); ++ struct zmk_behavior_binding_event event = {.position = data->position, ++ .timestamp = k_uptime_get()}; ++ ++ press_turbo_binding(&event, data); ++ reset_timer(data, event); ++} ++ ++#define P1TO1 DEVICE_DT_NAME(DT_INST(0, zmk_turbo_param_1to1)) ++#define P1TO2 DEVICE_DT_NAME(DT_INST(0, zmk_turbo_param_1to2)) ++#define P2TO1 DEVICE_DT_NAME(DT_INST(0, zmk_turbo_param_2to1)) ++#define P2TO2 DEVICE_DT_NAME(DT_INST(0, zmk_turbo_param_2to2)) ++ ++#define ZM_IS_NODE_MATCH(a, b) (strcmp(a, b) == 0) ++ ++#define IS_P1TO1(dev) ZM_IS_NODE_MATCH(dev, P1TO1) ++#define IS_P1TO2(dev) ZM_IS_NODE_MATCH(dev, P1TO2) ++#define IS_P2TO1(dev) ZM_IS_NODE_MATCH(dev, P2TO1) ++#define IS_P2TO2(dev) ZM_IS_NODE_MATCH(dev, P2TO2) ++ ++static bool handle_control_binding(struct behavior_turbo_data *data, ++ struct zmk_behavior_binding *binding, ++ const struct zmk_behavior_binding new_binding) { ++ if (IS_P1TO1(new_binding.behavior_dev)) { ++ data->new_binding.param1 = binding->param1; ++ LOG_DBG("turbo param: 1to1: %d", binding->param1); ++ } else if (IS_P1TO2(new_binding.behavior_dev)) { ++ data->new_binding.param2 = binding->param1; ++ LOG_DBG("turbo param: 1to2"); ++ } else if (IS_P2TO1(new_binding.behavior_dev)) { ++ data->new_binding.param1 = binding->param2; ++ LOG_DBG("turbo param: 2to1"); ++ } else if (IS_P2TO2(new_binding.behavior_dev)) { ++ data->new_binding.param2 = binding->param2; ++ LOG_DBG("turbo param: 2to2"); ++ } else { ++ return false; ++ } ++ ++ return true; ++} ++ ++static uint8_t get_binding_without_parameters_count(struct behavior_turbo_data *data) { ++ uint8_t bindings_without_parameters = 0; ++ ++ for (int i = 0; i < data->binding_count; i++) { ++ struct zmk_behavior_binding binding = data->bindings[i]; ++ if (!handle_control_binding(data, &binding, binding)) { ++ bindings_without_parameters++; ++ } ++ } ++ ++ return bindings_without_parameters; ++} ++ ++static void squash_params(struct behavior_turbo_data *data, struct zmk_behavior_binding *binding, ++ struct zmk_behavior_binding *new_bindings) { ++ uint8_t new_bindings_index = 0; ++ LOG_DBG("turbo bindings count is %d", data->binding_count); ++ ++ for (int i = 0; i < data->binding_count; i++) { ++ bool is_control_binding = handle_control_binding(data, binding, data->bindings[i]); ++ ++ if (!is_control_binding) { ++ data->new_binding.behavior_dev = data->bindings[i].behavior_dev; ++ ++ if (!data->new_binding.param1) { ++ data->new_binding.param1 = data->bindings[i].param1; ++ } ++ ++ if (!data->new_binding.param2) { ++ data->new_binding.param2 = data->bindings[i].param1; ++ } ++ ++ new_bindings[new_bindings_index] = data->new_binding; ++ new_bindings_index++; ++ } ++ ++ LOG_DBG("current turbo binding at index %d is %s, %d, %d", i, ++ data->new_binding.behavior_dev, data->new_binding.param1, data->new_binding.param2); ++ } ++} ++ ++static int on_turbo_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_turbo_data *data = dev->data; ++ ++ struct zmk_behavior_binding new_bindings[get_binding_without_parameters_count(data)]; ++ squash_params(data, binding, new_bindings); ++ ++ data->binding = new_bindings[0]; ++ ++ if (!data->is_active) { ++ data->is_active = true; ++ ++ LOG_DBG("Started new turbo at position %d", event.position); ++ ++ data->press_time = k_uptime_get(); ++ data->position = event.position; ++ ++ press_turbo_binding(&event, data); ++ reset_timer(data, event); ++ } else { ++ clear_turbo(data); ++ } ++ ++ return ZMK_BEHAVIOR_OPAQUE; ++} ++ ++static int on_turbo_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_turbo_data *data = dev->data; ++ ++ if (data->is_active) { ++ data->is_pressed = false; ++ int32_t elapsedTime = k_uptime_get() - data->press_time; ++ LOG_DBG("turbo elapsed time: %d", elapsedTime); ++ if (elapsedTime > data->toggle_term_ms) { ++ clear_turbo(data); ++ } ++ } ++ return 0; ++} ++ ++static int behavior_turbo_key_init(const struct device *dev) { ++ struct behavior_turbo_data *data = dev->data; ++ k_work_init_delayable(&data->release_timer, behavior_turbo_timer_handler); ++ return 0; ++}; ++ ++#define TRANSFORMED_BEHAVIORS(n) \ ++ {LISTIFY(DT_PROP_LEN(n, bindings), ZMK_KEYMAP_EXTRACT_BINDING, (, ), n)} ++ ++static const struct behavior_driver_api behavior_turbo_key_driver_api = { ++ .binding_pressed = on_turbo_binding_pressed, ++ .binding_released = on_turbo_binding_released, ++}; ++ ++#define TURBO_INST(n) \ ++ static struct behavior_turbo_data behavior_turbo_data_##n = { \ ++ .tap_ms = DT_PROP(n, tap_ms), \ ++ .wait_ms = DT_PROP(n, wait_ms), \ ++ .toggle_term_ms = DT_PROP(n, toggle_term_ms), \ ++ .bindings = TRANSFORMED_BEHAVIORS(n), \ ++ .binding_count = DT_PROP_LEN(n, bindings), \ ++ .is_active = false, \ ++ .is_pressed = false}; \ ++ BEHAVIOR_DT_DEFINE(n, behavior_turbo_key_init, NULL, &behavior_turbo_data_##n, NULL, \ ++ POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ ++ &behavior_turbo_key_driver_api); ++ ++DT_FOREACH_STATUS_OKAY(zmk_behavior_turbo_key, TURBO_INST) ++DT_FOREACH_STATUS_OKAY(zmk_behavior_turbo_key_one_param, TURBO_INST) ++DT_FOREACH_STATUS_OKAY(zmk_behavior_turbo_key_two_param, TURBO_INST) +diff --git a/app/tests/turbo/basic/events.patterns b/app/tests/turbo/basic/events.patterns +new file mode 100644 +index 00000000..b1342af4 +--- /dev/null ++++ b/app/tests/turbo/basic/events.patterns +@@ -0,0 +1 @@ ++s/.*hid_listener_keycode_//p +diff --git a/app/tests/turbo/basic/keycode_events.snapshot b/app/tests/turbo/basic/keycode_events.snapshot +new file mode 100644 +index 00000000..d0767ca4 +--- /dev/null ++++ b/app/tests/turbo/basic/keycode_events.snapshot +@@ -0,0 +1,8 @@ ++pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +diff --git a/app/tests/turbo/basic/native_posix_64.keymap b/app/tests/turbo/basic/native_posix_64.keymap +new file mode 100644 +index 00000000..ba4dd794 +--- /dev/null ++++ b/app/tests/turbo/basic/native_posix_64.keymap +@@ -0,0 +1,8 @@ ++#include "../behavior_keymap.dtsi" ++ ++&kscan { ++ events = < ++ ZMK_MOCK_PRESS(0,0,1000) ++ ZMK_MOCK_RELEASE(0,0,10) ++ >; ++}; +diff --git a/app/tests/turbo/behavior_keymap.dtsi b/app/tests/turbo/behavior_keymap.dtsi +new file mode 100644 +index 00000000..e37d4f75 +--- /dev/null ++++ b/app/tests/turbo/behavior_keymap.dtsi +@@ -0,0 +1,36 @@ ++#include ++#include ++#include ++ ++/ { ++ behaviors { ++ turbo: turbo { ++ compatible = "zmk,behavior-turbo-key"; ++ label = "turbo"; ++ #binding-cells = <0>; ++ tap-ms = <5>; ++ wait-ms = <300>; ++ bindings = <&kp C>; ++ }; ++ t2: turbo2 { ++ compatible = "zmk,behavior-turbo-key"; ++ label = "turbo2"; ++ #binding-cells = <0>; ++ tap-ms = <5>; ++ wait-ms = <300>; ++ toggle-term-ms = <50>; ++ bindings = <&kp C>; ++ }; ++ }; ++ ++ keymap { ++ compatible = "zmk,keymap"; ++ label ="Default keymap"; ++ ++ default_layer { ++ bindings = < ++ &turbo &t2 ++ &kp D &kp Q>; ++ }; ++ }; ++}; +diff --git a/app/tests/turbo/toggle/events.patterns b/app/tests/turbo/toggle/events.patterns +new file mode 100644 +index 00000000..b1342af4 +--- /dev/null ++++ b/app/tests/turbo/toggle/events.patterns +@@ -0,0 +1 @@ ++s/.*hid_listener_keycode_//p +diff --git a/app/tests/turbo/toggle/keycode_events.snapshot b/app/tests/turbo/toggle/keycode_events.snapshot +new file mode 100644 +index 00000000..d0767ca4 +--- /dev/null ++++ b/app/tests/turbo/toggle/keycode_events.snapshot +@@ -0,0 +1,8 @@ ++pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 ++released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +diff --git a/app/tests/turbo/toggle/native_posix_64.keymap b/app/tests/turbo/toggle/native_posix_64.keymap +new file mode 100644 +index 00000000..c6745c8e +--- /dev/null ++++ b/app/tests/turbo/toggle/native_posix_64.keymap +@@ -0,0 +1,10 @@ ++#include "../behavior_keymap.dtsi" ++ ++&kscan { ++ events = < ++ ZMK_MOCK_PRESS(0,1,10) ++ ZMK_MOCK_RELEASE(0,1,1000) ++ ZMK_MOCK_PRESS(0,1,10) ++ ZMK_MOCK_RELEASE(0,1,10) ++ >; ++}; +diff --git a/docs/docs/keymaps/behaviors/turbo.md b/docs/docs/keymaps/behaviors/turbo.md +new file mode 100644 +index 00000000..bf771482 +--- /dev/null ++++ b/docs/docs/keymaps/behaviors/turbo.md +@@ -0,0 +1,51 @@ ++--- ++title: Turbo Behavior ++sidebar_label: Turbo Key ++--- ++ ++## Summary ++ ++The turbo behavior will repeatedly trigger a behavior after a specified amount of time. ++ ++### Configuration ++ ++An example of how to implement a turbo key to output `A` every 5 seconds: ++ ++``` ++/ { ++ behaviors { ++ turbo_A: turbo_A { ++ compatible = "zmk,behavior-turbo-key"; ++ label = "TURBO_A"; ++ #binding-cells = <0>; ++ bindings = <&kp A>; ++ wait-ms = <5000>; ++ }; ++ }; ++}; ++``` ++ ++### Behavior Binding ++ ++- Reference: `&turbo_A` ++- Parameter: None ++ ++Example: ++ ++``` ++&turbo_A ++``` ++ ++### Advanced Configuration ++ ++#### `wait-ms` ++ ++Defines how often the behavior will trigger. Defaults to 200ms. ++ ++#### `tap-ms` ++ ++Defines how long the behavior will be held for each press. Defaults to 5ms. ++ ++#### `toggle-term-ms` ++ ++Releasing the turbo key within `toggle-term-ms` will toggle the repeating, removing the need to hold down the key. +diff --git a/docs/sidebars.js b/docs/sidebars.js +index 0a20a29e..4e58632e 100644 +--- a/docs/sidebars.js ++++ b/docs/sidebars.js +@@ -70,6 +70,7 @@ module.exports = { + "keymaps/behaviors/mod-morph", + "keymaps/behaviors/macros", + "keymaps/behaviors/key-toggle", ++ "keymaps/behaviors/turbo", + "keymaps/behaviors/sticky-key", + "keymaps/behaviors/sticky-layer", + "keymaps/behaviors/tap-dance", -- 2.44.1