Skip to content

Commit e48fcff

Browse files
committed
Apply tri-state (PR zmkfirmware#1366)
1 parent a154b8f commit e48fcff

22 files changed

Lines changed: 673 additions & 0 deletions

app/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
5454
target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c)
5555
target_sources(app PRIVATE src/behaviors/behavior_to_layer.c)
5656
target_sources(app PRIVATE src/behaviors/behavior_transparent.c)
57+
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_TRI_STATE app PRIVATE src/behaviors/behavior_tri_state.c)
5758
target_sources(app PRIVATE src/behaviors/behavior_none.c)
5859
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE app PRIVATE src/behaviors/behavior_sensor_rotate.c)
5960
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_VAR app PRIVATE src/behaviors/behavior_sensor_rotate_var.c)

app/Kconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,12 @@ config ZMK_MACRO_DEFAULT_TAP_MS
480480
int "Default time to wait (in milliseconds) between the press and release events of a tapped behavior in macros"
481481
default 30
482482

483+
DT_COMPAT_ZMK_BEHAVIOR_TRI_STATE := zmk,behavior-tri-state
484+
485+
config ZMK_BEHAVIOR_TRI_STATE
486+
bool
487+
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_BEHAVIOR_TRI_STATE))
488+
483489
endmenu
484490

485491
menu "Advanced"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright (c) 2022 The ZMK Contributors
2+
# SPDX-License-Identifier: MIT
3+
4+
description: Tri-State Behavior
5+
6+
compatible: "zmk,behavior-tri-state"
7+
8+
include: zero_param.yaml
9+
10+
properties:
11+
bindings:
12+
type: phandle-array
13+
required: true
14+
ignored-key-positions:
15+
type: array
16+
required: false
17+
default: []
18+
ignored-layers:
19+
type: array
20+
required: false
21+
default: []
22+
timeout-ms:
23+
type: int
24+
default: -1
25+
tap-ms:
26+
type: int
27+
default: 5
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
/*
2+
* Copyright (c) 2022 The ZMK Contributors
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
#define DT_DRV_COMPAT zmk_behavior_tri_state
8+
9+
#include <zephyr/device.h>
10+
#include <drivers/behavior.h>
11+
#include <zephyr/logging/log.h>
12+
#include <zmk/behavior.h>
13+
#include <zmk/behavior_queue.h>
14+
#include <zmk/keymap.h>
15+
#include <zmk/matrix.h>
16+
#include <zmk/event_manager.h>
17+
#include <zmk/events/position_state_changed.h>
18+
#include <zmk/events/layer_state_changed.h>
19+
#include <zmk/hid.h>
20+
21+
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
22+
23+
#define ZMK_BHV_MAX_ACTIVE_TRI_STATES 10
24+
25+
struct behavior_tri_state_config {
26+
int32_t ignored_key_positions_len;
27+
int32_t ignored_layers_len;
28+
struct zmk_behavior_binding start_behavior;
29+
struct zmk_behavior_binding continue_behavior;
30+
struct zmk_behavior_binding end_behavior;
31+
int32_t ignored_layers;
32+
int32_t timeout_ms;
33+
int tap_ms;
34+
uint8_t ignored_key_positions[];
35+
};
36+
37+
struct active_tri_state {
38+
bool is_active;
39+
bool is_pressed;
40+
bool first_press;
41+
uint32_t position;
42+
const struct behavior_tri_state_config *config;
43+
struct k_work_delayable release_timer;
44+
int64_t release_at;
45+
bool timer_started;
46+
bool timer_cancelled;
47+
};
48+
49+
static int stop_timer(struct active_tri_state *tri_state) {
50+
int timer_cancel_result = k_work_cancel_delayable(&tri_state->release_timer);
51+
if (timer_cancel_result == -EINPROGRESS) {
52+
// too late to cancel, we'll let the timer handler clear up.
53+
tri_state->timer_cancelled = true;
54+
}
55+
return timer_cancel_result;
56+
}
57+
58+
static void reset_timer(int32_t timestamp, struct active_tri_state *tri_state) {
59+
tri_state->release_at = timestamp + tri_state->config->timeout_ms;
60+
int32_t ms_left = tri_state->release_at - k_uptime_get();
61+
if (ms_left > 0) {
62+
k_work_schedule(&tri_state->release_timer, K_MSEC(ms_left));
63+
LOG_DBG("Successfully reset tri-state timer");
64+
}
65+
}
66+
67+
void trigger_end_behavior(struct active_tri_state *si) {
68+
zmk_behavior_queue_add(si->position, si->config->end_behavior, true, si->config->tap_ms);
69+
zmk_behavior_queue_add(si->position, si->config->end_behavior, false, 0);
70+
}
71+
72+
void behavior_tri_state_timer_handler(struct k_work *item) {
73+
struct active_tri_state *tri_state = CONTAINER_OF(item, struct active_tri_state, release_timer);
74+
if (!tri_state->is_active || tri_state->timer_cancelled || tri_state->is_pressed) {
75+
return;
76+
}
77+
LOG_DBG("Tri-state deactivated due to timer");
78+
tri_state->is_active = false;
79+
trigger_end_behavior(tri_state);
80+
}
81+
82+
static void clear_tri_state(struct active_tri_state *tri_state) { tri_state->is_active = false; }
83+
84+
struct active_tri_state active_tri_states[ZMK_BHV_MAX_ACTIVE_TRI_STATES] = {};
85+
86+
static struct active_tri_state *find_tri_state(uint32_t position) {
87+
for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) {
88+
if (active_tri_states[i].position == position && active_tri_states[i].is_active) {
89+
return &active_tri_states[i];
90+
}
91+
}
92+
return NULL;
93+
}
94+
95+
static int new_tri_state(uint32_t position, const struct behavior_tri_state_config *config,
96+
struct active_tri_state **tri_state) {
97+
for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) {
98+
struct active_tri_state *const ref_tri_state = &active_tri_states[i];
99+
if (!ref_tri_state->is_active) {
100+
ref_tri_state->position = position;
101+
ref_tri_state->config = config;
102+
ref_tri_state->is_active = true;
103+
ref_tri_state->is_pressed = false;
104+
ref_tri_state->first_press = true;
105+
*tri_state = ref_tri_state;
106+
return 0;
107+
}
108+
}
109+
return -ENOMEM;
110+
}
111+
112+
static bool is_other_key_ignored(struct active_tri_state *tri_state, int32_t position) {
113+
for (int i = 0; i < tri_state->config->ignored_key_positions_len; i++) {
114+
if (tri_state->config->ignored_key_positions[i] == position) {
115+
return true;
116+
}
117+
}
118+
return false;
119+
}
120+
121+
static bool is_layer_ignored(struct active_tri_state *tri_state, int32_t layer) {
122+
if ((BIT(layer) & tri_state->config->ignored_layers) != 0U) {
123+
return true;
124+
}
125+
return false;
126+
}
127+
128+
static int on_tri_state_binding_pressed(struct zmk_behavior_binding *binding,
129+
struct zmk_behavior_binding_event event) {
130+
const struct device *dev = device_get_binding(binding->behavior_dev);
131+
const struct behavior_tri_state_config *cfg = dev->config;
132+
struct active_tri_state *tri_state;
133+
tri_state = find_tri_state(event.position);
134+
if (tri_state == NULL) {
135+
if (new_tri_state(event.position, cfg, &tri_state) == -ENOMEM) {
136+
LOG_ERR("Unable to create new tri_state. Insufficient space in "
137+
"active_tri_states[].");
138+
return ZMK_BEHAVIOR_OPAQUE;
139+
}
140+
LOG_DBG("%d created new tri_state", event.position);
141+
}
142+
LOG_DBG("%d tri_state pressed", event.position);
143+
tri_state->is_pressed = true;
144+
if (tri_state->first_press) {
145+
behavior_keymap_binding_pressed((struct zmk_behavior_binding *)&cfg->start_behavior, event);
146+
behavior_keymap_binding_released((struct zmk_behavior_binding *)&cfg->start_behavior,
147+
event);
148+
tri_state->first_press = false;
149+
}
150+
behavior_keymap_binding_pressed((struct zmk_behavior_binding *)&cfg->continue_behavior, event);
151+
return ZMK_BEHAVIOR_OPAQUE;
152+
}
153+
154+
static void release_tri_state(struct zmk_behavior_binding_event event,
155+
struct zmk_behavior_binding *continue_behavior) {
156+
struct active_tri_state *tri_state = find_tri_state(event.position);
157+
if (tri_state == NULL) {
158+
return;
159+
}
160+
tri_state->is_pressed = false;
161+
behavior_keymap_binding_released(continue_behavior, event);
162+
reset_timer(k_uptime_get(), tri_state);
163+
}
164+
165+
static int on_tri_state_binding_released(struct zmk_behavior_binding *binding,
166+
struct zmk_behavior_binding_event event) {
167+
const struct device *dev = device_get_binding(binding->behavior_dev);
168+
const struct behavior_tri_state_config *cfg = dev->config;
169+
LOG_DBG("%d tri_state keybind released", event.position);
170+
release_tri_state(event, (struct zmk_behavior_binding *)&cfg->continue_behavior);
171+
return ZMK_BEHAVIOR_OPAQUE;
172+
}
173+
174+
static int behavior_tri_state_init(const struct device *dev) {
175+
static bool init_first_run = true;
176+
if (init_first_run) {
177+
for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) {
178+
k_work_init_delayable(&active_tri_states[i].release_timer,
179+
behavior_tri_state_timer_handler);
180+
clear_tri_state(&active_tri_states[i]);
181+
}
182+
}
183+
init_first_run = false;
184+
return 0;
185+
}
186+
187+
static const struct behavior_driver_api behavior_tri_state_driver_api = {
188+
.binding_pressed = on_tri_state_binding_pressed,
189+
.binding_released = on_tri_state_binding_released,
190+
};
191+
192+
static int tri_state_listener(const zmk_event_t *eh);
193+
static int tri_state_position_state_changed_listener(const zmk_event_t *eh);
194+
static int tri_state_layer_state_changed_listener(const zmk_event_t *eh);
195+
196+
ZMK_LISTENER(behavior_tri_state, tri_state_listener);
197+
ZMK_SUBSCRIPTION(behavior_tri_state, zmk_position_state_changed);
198+
ZMK_SUBSCRIPTION(behavior_tri_state, zmk_layer_state_changed);
199+
200+
static int tri_state_listener(const zmk_event_t *eh) {
201+
if (as_zmk_position_state_changed(eh) != NULL) {
202+
return tri_state_position_state_changed_listener(eh);
203+
} else if (as_zmk_layer_state_changed(eh) != NULL) {
204+
return tri_state_layer_state_changed_listener(eh);
205+
}
206+
return ZMK_EV_EVENT_BUBBLE;
207+
}
208+
209+
static int tri_state_position_state_changed_listener(const zmk_event_t *eh) {
210+
struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh);
211+
if (ev == NULL) {
212+
return ZMK_EV_EVENT_BUBBLE;
213+
}
214+
for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) {
215+
struct active_tri_state *tri_state = &active_tri_states[i];
216+
if (!tri_state->is_active) {
217+
continue;
218+
}
219+
if (tri_state->position == ev->position) {
220+
continue;
221+
}
222+
if (!is_other_key_ignored(tri_state, ev->position)) {
223+
LOG_DBG("Tri-State interrupted, ending at %d %d", tri_state->position, ev->position);
224+
tri_state->is_active = false;
225+
struct zmk_behavior_binding_event event = {.position = tri_state->position,
226+
.timestamp = k_uptime_get()};
227+
if (tri_state->is_pressed) {
228+
behavior_keymap_binding_released(
229+
(struct zmk_behavior_binding *)&tri_state->config->continue_behavior, event);
230+
}
231+
trigger_end_behavior(tri_state);
232+
return ZMK_EV_EVENT_BUBBLE;
233+
}
234+
if (ev->state) {
235+
stop_timer(tri_state);
236+
} else {
237+
reset_timer(ev->timestamp, tri_state);
238+
}
239+
}
240+
return ZMK_EV_EVENT_BUBBLE;
241+
}
242+
243+
static int tri_state_layer_state_changed_listener(const zmk_event_t *eh) {
244+
struct zmk_layer_state_changed *ev = as_zmk_layer_state_changed(eh);
245+
if (ev == NULL) {
246+
return ZMK_EV_EVENT_BUBBLE;
247+
}
248+
if (!ev->state) {
249+
return ZMK_EV_EVENT_BUBBLE;
250+
}
251+
for (int i = 0; i < ZMK_BHV_MAX_ACTIVE_TRI_STATES; i++) {
252+
struct active_tri_state *tri_state = &active_tri_states[i];
253+
if (!tri_state->is_active) {
254+
continue;
255+
}
256+
if (!is_layer_ignored(tri_state, ev->layer)) {
257+
LOG_DBG("Tri-State layer changed, ending at %d %d", tri_state->position, ev->layer);
258+
tri_state->is_active = false;
259+
struct zmk_behavior_binding_event event = {.position = tri_state->position,
260+
.timestamp = k_uptime_get()};
261+
if (tri_state->is_pressed) {
262+
behavior_keymap_binding_released(
263+
(struct zmk_behavior_binding *)&tri_state->config->continue_behavior, event);
264+
}
265+
behavior_keymap_binding_pressed(
266+
(struct zmk_behavior_binding *)&tri_state->config->end_behavior, event);
267+
behavior_keymap_binding_released(
268+
(struct zmk_behavior_binding *)&tri_state->config->end_behavior, event);
269+
return ZMK_EV_EVENT_BUBBLE;
270+
}
271+
}
272+
return ZMK_EV_EVENT_BUBBLE;
273+
}
274+
275+
#define _TRANSFORM_ENTRY(idx, node) \
276+
{ \
277+
.behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(node, bindings, idx)), \
278+
.param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \
279+
(DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \
280+
.param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \
281+
(DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \
282+
}
283+
284+
#define IF_BIT(n, prop, i) BIT(DT_PROP_BY_IDX(n, prop, i)) |
285+
286+
#define TRI_STATE_INST(n) \
287+
static struct behavior_tri_state_config behavior_tri_state_config_##n = { \
288+
.ignored_key_positions = DT_INST_PROP(n, ignored_key_positions), \
289+
.ignored_key_positions_len = DT_INST_PROP_LEN(n, ignored_key_positions), \
290+
.ignored_layers = DT_INST_FOREACH_PROP_ELEM(n, ignored_layers, IF_BIT) 0, \
291+
.ignored_layers_len = DT_INST_PROP_LEN(n, ignored_layers), \
292+
.timeout_ms = DT_INST_PROP(n, timeout_ms), \
293+
.tap_ms = DT_INST_PROP(n, tap_ms), \
294+
.start_behavior = _TRANSFORM_ENTRY(0, n), \
295+
.continue_behavior = _TRANSFORM_ENTRY(1, n), \
296+
.end_behavior = _TRANSFORM_ENTRY(2, n)}; \
297+
DEVICE_DT_INST_DEFINE(n, behavior_tri_state_init, NULL, NULL, &behavior_tri_state_config_##n, \
298+
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
299+
&behavior_tri_state_driver_api);
300+
301+
DT_INST_FOREACH_STATUS_OKAY(TRI_STATE_INST)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include <dt-bindings/zmk/keys.h>
2+
#include <behaviors.dtsi>
3+
#include <dt-bindings/zmk/kscan_mock.h>
4+
5+
/ {
6+
behaviors {
7+
swap: swap {
8+
compatible = "zmk,behavior-tri-state";
9+
label = "SWAPPER";
10+
#binding-cells = <0>;
11+
bindings = <&kt LALT>, <&kp TAB>, <&kt LALT>;
12+
ignored-key-positions = <2 3>;
13+
ignored-layers = <1>;
14+
timeout-ms = <200>;
15+
};
16+
};
17+
18+
keymap {
19+
compatible = "zmk,keymap";
20+
label ="Default keymap";
21+
22+
default_layer {
23+
bindings = <
24+
&swap &kp A
25+
&kp B &tog 1>;
26+
};
27+
28+
extra_layer {
29+
bindings = <
30+
&kp A &kp B
31+
&tog 2 &trans>;
32+
};
33+
34+
extra_layer2 {
35+
bindings = <
36+
&kp N1 &kp N2
37+
&trans &kp N3>;
38+
};
39+
};
40+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
s/.*hid_listener_keycode/kp/p
2+
s/.*on_tri_state_binding/tri_state_binding/p

0 commit comments

Comments
 (0)