feat(behaviors): Adding global-quick-tap-ms for combos
This brings the 'global-quick-tap' functionality to combos by filtering out candidate combos that fell within their own quick tap term. I also replaced `return 0` with `return ZMK_EV_EVENT_BUBBLE` where appropriate. (I assume this was done in past as it is similar to errno returning, but being that this is to signify an event type I find this more clear)
This commit is contained in:
parent
2f6abff3bc
commit
77eb44ba9b
5 changed files with 126 additions and 6 deletions
|
@ -18,6 +18,9 @@ child-binding:
|
||||||
timeout-ms:
|
timeout-ms:
|
||||||
type: int
|
type: int
|
||||||
default: 50
|
default: 50
|
||||||
|
global-quick-tap-ms:
|
||||||
|
type: int
|
||||||
|
default: -1
|
||||||
slow-release:
|
slow-release:
|
||||||
type: boolean
|
type: boolean
|
||||||
layers:
|
layers:
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <zmk/behavior.h>
|
#include <zmk/behavior.h>
|
||||||
#include <zmk/event_manager.h>
|
#include <zmk/event_manager.h>
|
||||||
#include <zmk/events/position_state_changed.h>
|
#include <zmk/events/position_state_changed.h>
|
||||||
|
#include <zmk/events/keycode_state_changed.h>
|
||||||
#include <zmk/hid.h>
|
#include <zmk/hid.h>
|
||||||
#include <zmk/matrix.h>
|
#include <zmk/matrix.h>
|
||||||
#include <zmk/keymap.h>
|
#include <zmk/keymap.h>
|
||||||
|
@ -30,6 +31,7 @@ struct combo_cfg {
|
||||||
int32_t key_position_len;
|
int32_t key_position_len;
|
||||||
struct zmk_behavior_binding behavior;
|
struct zmk_behavior_binding behavior;
|
||||||
int32_t timeout_ms;
|
int32_t timeout_ms;
|
||||||
|
int32_t global_quick_tap_ms;
|
||||||
// if slow release is set, the combo releases when the last key is released.
|
// if slow release is set, the combo releases when the last key is released.
|
||||||
// otherwise, the combo releases when the first key is released.
|
// otherwise, the combo releases when the first key is released.
|
||||||
bool slow_release;
|
bool slow_release;
|
||||||
|
@ -72,6 +74,17 @@ int active_combo_count = 0;
|
||||||
struct k_work_delayable timeout_task;
|
struct k_work_delayable timeout_task;
|
||||||
int64_t timeout_task_timeout_at;
|
int64_t timeout_task_timeout_at;
|
||||||
|
|
||||||
|
// this keeps track of the last non-combo, non-mod key tap
|
||||||
|
int64_t last_tapped_timestamp = INT32_MIN;
|
||||||
|
// this keeps track of the last time a combo was pressed
|
||||||
|
int64_t last_combo_timestamp = INT32_MIN;
|
||||||
|
|
||||||
|
static void store_last_tapped(int64_t timestamp) {
|
||||||
|
if (timestamp > last_combo_timestamp) {
|
||||||
|
last_tapped_timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Store the combo key pointer in the combos array, one pointer for each key position
|
// Store the combo key pointer in the combos array, one pointer for each key position
|
||||||
// The combos are sorted shortest-first, then by virtual-key-position.
|
// The combos are sorted shortest-first, then by virtual-key-position.
|
||||||
static int initialize_combo(struct combo_cfg *new_combo) {
|
static int initialize_combo(struct combo_cfg *new_combo) {
|
||||||
|
@ -122,6 +135,10 @@ static bool combo_active_on_layer(struct combo_cfg *combo, uint8_t layer) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool is_quick_tap(struct combo_cfg *combo, int64_t timestamp) {
|
||||||
|
return (last_tapped_timestamp + combo->global_quick_tap_ms) > timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
static int setup_candidates_for_first_keypress(int32_t position, int64_t timestamp) {
|
static int setup_candidates_for_first_keypress(int32_t position, int64_t timestamp) {
|
||||||
int number_of_combo_candidates = 0;
|
int number_of_combo_candidates = 0;
|
||||||
uint8_t highest_active_layer = zmk_keymap_highest_layer_active();
|
uint8_t highest_active_layer = zmk_keymap_highest_layer_active();
|
||||||
|
@ -130,7 +147,7 @@ static int setup_candidates_for_first_keypress(int32_t position, int64_t timesta
|
||||||
if (combo == NULL) {
|
if (combo == NULL) {
|
||||||
return number_of_combo_candidates;
|
return number_of_combo_candidates;
|
||||||
}
|
}
|
||||||
if (combo_active_on_layer(combo, highest_active_layer)) {
|
if (combo_active_on_layer(combo, highest_active_layer) && !is_quick_tap(combo, timestamp)) {
|
||||||
candidates[number_of_combo_candidates].combo = combo;
|
candidates[number_of_combo_candidates].combo = combo;
|
||||||
candidates[number_of_combo_candidates].timeout_at = timestamp + combo->timeout_ms;
|
candidates[number_of_combo_candidates].timeout_at = timestamp + combo->timeout_ms;
|
||||||
number_of_combo_candidates++;
|
number_of_combo_candidates++;
|
||||||
|
@ -252,7 +269,7 @@ static int capture_pressed_key(const zmk_event_t *ev) {
|
||||||
pressed_keys[i] = ev;
|
pressed_keys[i] = ev;
|
||||||
return ZMK_EV_EVENT_CAPTURED;
|
return ZMK_EV_EVENT_CAPTURED;
|
||||||
}
|
}
|
||||||
return 0;
|
return ZMK_EV_EVENT_BUBBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct zmk_listener zmk_listener_combo;
|
const struct zmk_listener zmk_listener_combo;
|
||||||
|
@ -284,6 +301,8 @@ static inline int press_combo_behavior(struct combo_cfg *combo, int32_t timestam
|
||||||
.timestamp = timestamp,
|
.timestamp = timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
last_combo_timestamp = timestamp;
|
||||||
|
|
||||||
return behavior_keymap_binding_pressed(&combo->behavior, event);
|
return behavior_keymap_binding_pressed(&combo->behavior, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,7 +432,7 @@ static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_
|
||||||
if (candidates[0].combo == NULL) {
|
if (candidates[0].combo == NULL) {
|
||||||
num_candidates = setup_candidates_for_first_keypress(data->position, data->timestamp);
|
num_candidates = setup_candidates_for_first_keypress(data->position, data->timestamp);
|
||||||
if (num_candidates == 0) {
|
if (num_candidates == 0) {
|
||||||
return 0;
|
return ZMK_EV_EVENT_BUBBLE;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
filter_timed_out_candidates(data->timestamp);
|
filter_timed_out_candidates(data->timestamp);
|
||||||
|
@ -453,7 +472,7 @@ static int position_state_up(const zmk_event_t *ev, struct zmk_position_state_ch
|
||||||
ZMK_EVENT_RAISE(ev);
|
ZMK_EVENT_RAISE(ev);
|
||||||
return ZMK_EV_EVENT_CAPTURED;
|
return ZMK_EV_EVENT_CAPTURED;
|
||||||
}
|
}
|
||||||
return 0;
|
return ZMK_EV_EVENT_BUBBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void combo_timeout_handler(struct k_work *item) {
|
static void combo_timeout_handler(struct k_work *item) {
|
||||||
|
@ -470,7 +489,7 @@ static void combo_timeout_handler(struct k_work *item) {
|
||||||
static int position_state_changed_listener(const zmk_event_t *ev) {
|
static int position_state_changed_listener(const zmk_event_t *ev) {
|
||||||
struct zmk_position_state_changed *data = as_zmk_position_state_changed(ev);
|
struct zmk_position_state_changed *data = as_zmk_position_state_changed(ev);
|
||||||
if (data == NULL) {
|
if (data == NULL) {
|
||||||
return 0;
|
return ZMK_EV_EVENT_BUBBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data->state) { // keydown
|
if (data->state) { // keydown
|
||||||
|
@ -480,12 +499,31 @@ static int position_state_changed_listener(const zmk_event_t *ev) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZMK_LISTENER(combo, position_state_changed_listener);
|
static int keycode_state_changed_listener(const zmk_event_t *eh) {
|
||||||
|
struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh);
|
||||||
|
if (ev->state && !is_mod(ev->usage_page, ev->keycode)) {
|
||||||
|
store_last_tapped(ev->timestamp);
|
||||||
|
}
|
||||||
|
return ZMK_EV_EVENT_BUBBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int behavior_combo_listener(const zmk_event_t *eh) {
|
||||||
|
if (as_zmk_position_state_changed(eh) != NULL) {
|
||||||
|
return position_state_changed_listener(eh);
|
||||||
|
} else if (as_zmk_keycode_state_changed(eh) != NULL) {
|
||||||
|
return keycode_state_changed_listener(eh);
|
||||||
|
}
|
||||||
|
return ZMK_EV_EVENT_BUBBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZMK_LISTENER(combo, behavior_combo_listener);
|
||||||
ZMK_SUBSCRIPTION(combo, zmk_position_state_changed);
|
ZMK_SUBSCRIPTION(combo, zmk_position_state_changed);
|
||||||
|
ZMK_SUBSCRIPTION(combo, zmk_keycode_state_changed);
|
||||||
|
|
||||||
#define COMBO_INST(n) \
|
#define COMBO_INST(n) \
|
||||||
static struct combo_cfg combo_config_##n = { \
|
static struct combo_cfg combo_config_##n = { \
|
||||||
.timeout_ms = DT_PROP(n, timeout_ms), \
|
.timeout_ms = DT_PROP(n, timeout_ms), \
|
||||||
|
.global_quick_tap_ms = DT_PROP(n, global_quick_tap_ms), \
|
||||||
.key_positions = DT_PROP(n, key_positions), \
|
.key_positions = DT_PROP(n, key_positions), \
|
||||||
.key_position_len = DT_PROP_LEN(n, key_positions), \
|
.key_position_len = DT_PROP_LEN(n, key_positions), \
|
||||||
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \
|
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, n), \
|
||||||
|
|
1
app/tests/combo/global-quick-tap/events.patterns
Normal file
1
app/tests/combo/global-quick-tap/events.patterns
Normal file
|
@ -0,0 +1 @@
|
||||||
|
s/.*hid_listener_keycode_//p
|
14
app/tests/combo/global-quick-tap/keycode_events.snapshot
Normal file
14
app/tests/combo/global-quick-tap/keycode_events.snapshot
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x1B implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x1B implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x1B implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x1B implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x1C implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x1C implicit_mods 0x00 explicit_mods 0x00
|
64
app/tests/combo/global-quick-tap/native_posix_64.keymap
Normal file
64
app/tests/combo/global-quick-tap/native_posix_64.keymap
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#include <dt-bindings/zmk/keys.h>
|
||||||
|
#include <behaviors.dtsi>
|
||||||
|
#include <dt-bindings/zmk/kscan-mock.h>
|
||||||
|
|
||||||
|
/ {
|
||||||
|
combos {
|
||||||
|
compatible = "zmk,combos";
|
||||||
|
combo_one {
|
||||||
|
timeout-ms = <50>;
|
||||||
|
key-positions = <0 1>;
|
||||||
|
bindings = <&kp X>;
|
||||||
|
global-quick-tap-ms = <100>;
|
||||||
|
};
|
||||||
|
|
||||||
|
combo_two {
|
||||||
|
timeout-ms = <50>;
|
||||||
|
key-positions = <0 2>;
|
||||||
|
bindings = <&kp Y>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
keymap {
|
||||||
|
compatible = "zmk,keymap";
|
||||||
|
label ="Default keymap";
|
||||||
|
|
||||||
|
default_layer {
|
||||||
|
bindings = <
|
||||||
|
&kp A &kp B
|
||||||
|
&kp C &kp D
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&kscan {
|
||||||
|
events = <
|
||||||
|
/* Tap A */
|
||||||
|
ZMK_MOCK_PRESS(0,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,0,60)
|
||||||
|
/* Quick Tap A and B */
|
||||||
|
ZMK_MOCK_PRESS(0,0,10)
|
||||||
|
ZMK_MOCK_PRESS(0,1,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,1,200)
|
||||||
|
/* Combo One */
|
||||||
|
ZMK_MOCK_PRESS(0,0,10)
|
||||||
|
ZMK_MOCK_PRESS(0,1,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,1,10)
|
||||||
|
/* Combo One Again (shouldn't quick tap) */
|
||||||
|
ZMK_MOCK_PRESS(0,0,10)
|
||||||
|
ZMK_MOCK_PRESS(0,1,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,1,10)
|
||||||
|
/* Tap A */
|
||||||
|
ZMK_MOCK_PRESS(0,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,0,60)
|
||||||
|
/* Combo 2 */
|
||||||
|
ZMK_MOCK_PRESS(0,0,10)
|
||||||
|
ZMK_MOCK_PRESS(1,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(1,0,10)
|
||||||
|
>;
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue