This commit is contained in:
Nick Conway 2025-12-05 12:05:04 -05:00 committed by GitHub
commit fcbd4ecfdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 493 additions and 0 deletions

View File

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

View File

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

View File

@ -28,3 +28,4 @@
#include <behaviors/soft_off.dtsi>
#include <behaviors/studio_unlock.dtsi>
#include <behaviors/mouse_keys.dtsi>
#include <behaviors/turbo.dtsi>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,238 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/behavior.h>
#include <zmk/behavior_queue.h>
#include <zmk/keymap.h>
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)

View File

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

View File

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

View File

@ -0,0 +1,8 @@
#include "../behavior_keymap.dtsi"
&kscan {
events = <
ZMK_MOCK_PRESS(0,0,1000)
ZMK_MOCK_RELEASE(0,0,10)
>;
};

View File

@ -0,0 +1,36 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/ {
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>;
};
};
};

View File

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

View File

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

View File

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

View File

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

View File

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