-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b276a3b
commit 595fc42
Showing
13 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Copyright (c) 2022 The ZMK Contributors | ||
# SPDX-License-Identifier: MIT | ||
|
||
description: Turbo key behavior | ||
|
||
compatible: "zmk,behavior-turbo-key" | ||
|
||
include: zero_param.yaml | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
/* | ||
* Copyright (c) 2022 The ZMK Contributors | ||
* | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
#define DT_DRV_COMPAT zmk_behavior_turbo_key | ||
|
||
#include <zephyr/device.h> | ||
#include <drivers/behavior.h> | ||
#include <zephyr/logging/log.h> | ||
|
||
#include <zmk/hid.h> | ||
#include <zmk/event_manager.h> | ||
#include <zmk/events/keycode_state_changed.h> | ||
#include <zmk/behavior.h> | ||
#include <zmk/behavior_queue.h> | ||
|
||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); | ||
|
||
struct behavior_turbo_config { | ||
int tap_ms; | ||
int wait_ms; | ||
int toggle_term_ms; | ||
const struct zmk_behavior_binding binding; | ||
}; | ||
|
||
#define ZMK_BHV_TURBO_MAX_ACTIVE 5 | ||
|
||
struct active_turbo { | ||
const struct behavior_turbo_config *config; | ||
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; | ||
}; | ||
|
||
struct active_turbo active_turbos[ZMK_BHV_TURBO_MAX_ACTIVE] = {}; | ||
|
||
static struct active_turbo *find_active_turbo(uint32_t position) { | ||
for (int i = 0; i < ZMK_BHV_TURBO_MAX_ACTIVE; i++) { | ||
if (active_turbos[i].is_active) { | ||
return &active_turbos[i]; | ||
} | ||
} | ||
return NULL; | ||
} | ||
|
||
static int new_turbo(uint32_t position, const struct behavior_turbo_config *config, | ||
struct active_turbo **turbo) { | ||
for (int i = 0; i < ZMK_BHV_TURBO_MAX_ACTIVE; i++) { | ||
struct active_turbo *const ref_turbo = &active_turbos[i]; | ||
if (!ref_turbo->is_active) { | ||
ref_turbo->is_active = true; | ||
ref_turbo->position = position; | ||
ref_turbo->config = config; | ||
ref_turbo->is_pressed = true; | ||
ref_turbo->press_time = k_uptime_get(); | ||
ref_turbo->release_at = 0; | ||
ref_turbo->timer_started = true; | ||
ref_turbo->timer_cancelled = false; | ||
*turbo = ref_turbo; | ||
return 0; | ||
} | ||
} | ||
return -ENOMEM; | ||
}; | ||
|
||
static int stop_timer(struct active_turbo *turbo) { | ||
int timer_cancel_result = k_work_cancel_delayable(&turbo->release_timer); | ||
if (timer_cancel_result == -EINPROGRESS) { | ||
// too late to cancel, we'll let the timer handler clear up. | ||
turbo->timer_cancelled = true; | ||
} | ||
return timer_cancel_result; | ||
} | ||
|
||
static void clear_turbo(struct active_turbo *turbo) { | ||
LOG_DBG("Turbo deactivated"); | ||
turbo->is_active = false; | ||
stop_timer(turbo); | ||
} | ||
|
||
static void reset_timer(struct active_turbo *turbo, struct zmk_behavior_binding_event event) { | ||
turbo->release_at = event.timestamp + turbo->config->wait_ms; | ||
int32_t ms_left = turbo->release_at - k_uptime_get(); | ||
if (ms_left > 0) { | ||
k_work_schedule(&turbo->release_timer, K_MSEC(ms_left)); | ||
LOG_DBG("Successfully reset turbo timer at position %d", turbo->position); | ||
} | ||
} | ||
|
||
static void behavior_turbo_timer_handler(struct k_work *item) { | ||
struct active_turbo *turbo = CONTAINER_OF(item, struct active_turbo, release_timer); | ||
if (!turbo->is_active) { | ||
return; | ||
} | ||
if (turbo->timer_cancelled) { | ||
return; | ||
} | ||
LOG_DBG("Turbo timer reached."); | ||
struct zmk_behavior_binding_event event = {.position = turbo->position, | ||
.timestamp = k_uptime_get()}; | ||
zmk_behavior_queue_add(event.position, turbo->config->binding, true, turbo->config->tap_ms); | ||
zmk_behavior_queue_add(event.position, turbo->config->binding, false, 0); | ||
reset_timer(turbo, event); | ||
} | ||
|
||
static int behavior_turbo_key_init(const struct device *dev) { | ||
static bool init_first_run = true; | ||
if (init_first_run) { | ||
for (int i = 0; i < ZMK_BHV_TURBO_MAX_ACTIVE; i++) { | ||
k_work_init_delayable(&active_turbos[i].release_timer, behavior_turbo_timer_handler); | ||
clear_turbo(&active_turbos[i]); | ||
} | ||
} | ||
init_first_run = false; | ||
return 0; | ||
}; | ||
|
||
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, | ||
struct zmk_behavior_binding_event event) { | ||
const struct device *dev = device_get_binding(binding->behavior_dev); | ||
const struct behavior_turbo_config *cfg = dev->config; | ||
|
||
struct active_turbo *turbo; | ||
turbo = find_active_turbo(event.position); | ||
if (turbo == NULL) { | ||
if (new_turbo(event.position, cfg, &turbo) == -ENOMEM) { | ||
LOG_ERR("Unable to create new turbo. Insufficient space in active_turbos[]."); | ||
return ZMK_BEHAVIOR_OPAQUE; | ||
} | ||
LOG_DBG("%d created new turbo", event.position); | ||
zmk_behavior_queue_add(event.position, turbo->config->binding, true, turbo->config->tap_ms); | ||
zmk_behavior_queue_add(event.position, turbo->config->binding, false, 0); | ||
reset_timer(turbo, event); | ||
} else { | ||
clear_turbo(turbo); | ||
} | ||
return ZMK_BEHAVIOR_OPAQUE; | ||
} | ||
|
||
static int on_keymap_binding_released(struct zmk_behavior_binding *binding, | ||
struct zmk_behavior_binding_event event) { | ||
const struct device *dev = device_get_binding(binding->behavior_dev); | ||
const struct behavior_turbo_config *cfg = dev->config; | ||
|
||
struct active_turbo *turbo; | ||
turbo = find_active_turbo(event.position); | ||
if (turbo != NULL) { | ||
turbo->is_pressed = false; | ||
int32_t elapsedTime = k_uptime_get() - turbo->press_time; | ||
LOG_DBG("turbo elapsed time: %d", elapsedTime); | ||
if (elapsedTime > cfg->toggle_term_ms) { | ||
clear_turbo(turbo); | ||
} | ||
} | ||
return 0; | ||
} | ||
|
||
static const struct behavior_driver_api behavior_turbo_key_driver_api = { | ||
.binding_pressed = on_keymap_binding_pressed, | ||
.binding_released = on_keymap_binding_released, | ||
}; | ||
|
||
#define _TRANSFORM_ENTRY(idx, node) \ | ||
{ \ | ||
.behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(node, bindings, idx)), \ | ||
.param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \ | ||
(DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \ | ||
.param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \ | ||
(DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \ | ||
} | ||
|
||
#define TURBO_INST(n) \ | ||
static struct behavior_turbo_config behavior_turbo_config_##n = { \ | ||
.tap_ms = DT_INST_PROP(n, tap_ms), \ | ||
.wait_ms = DT_INST_PROP(n, wait_ms), \ | ||
.toggle_term_ms = DT_INST_PROP(n, toggle_term_ms), \ | ||
.binding = _TRANSFORM_ENTRY(0, n)}; \ | ||
DEVICE_DT_INST_DEFINE(n, behavior_turbo_key_init, NULL, NULL, &behavior_turbo_config_##n, \ | ||
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ | ||
&behavior_turbo_key_driver_api); | ||
|
||
DT_INST_FOREACH_STATUS_OKAY(TURBO_INST) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
s/.*hid_listener_keycode_//p |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#include "../behavior_keymap.dtsi" | ||
|
||
&kscan { | ||
events = < | ||
ZMK_MOCK_PRESS(0,0,1000) | ||
ZMK_MOCK_RELEASE(0,0,10) | ||
>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = "turbo"; | ||
#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>; | ||
}; | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
s/.*hid_listener_keycode_//p |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters