feat(behaviors): lazy sticky keys

This commit is contained in:
Theo Lemay 2023-05-28 23:59:48 -07:00 committed by Pete Johanson
parent 8929355ac0
commit 341534aa15
18 changed files with 260 additions and 32 deletions

View File

@ -16,5 +16,7 @@ properties:
required: true required: true
quick-release: quick-release:
type: boolean type: boolean
lazy:
type: boolean
ignore-modifiers: ignore-modifiers:
type: boolean type: boolean

View File

@ -33,6 +33,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct behavior_sticky_key_config { struct behavior_sticky_key_config {
uint32_t release_after_ms; uint32_t release_after_ms;
bool quick_release; bool quick_release;
bool lazy;
bool ignore_modifiers; bool ignore_modifiers;
struct zmk_behavior_binding behavior; struct zmk_behavior_binding behavior;
}; };
@ -146,8 +147,11 @@ static int on_sticky_key_binding_pressed(struct zmk_behavior_binding *binding,
return ZMK_BEHAVIOR_OPAQUE; return ZMK_BEHAVIOR_OPAQUE;
} }
press_sticky_key_behavior(sticky_key, event.timestamp);
LOG_DBG("%d new sticky_key", event.position); LOG_DBG("%d new sticky_key", event.position);
if (!sticky_key->config->lazy) {
// press the key now if it's not lazy
press_sticky_key_behavior(sticky_key, event.timestamp);
}
return ZMK_BEHAVIOR_OPAQUE; return ZMK_BEHAVIOR_OPAQUE;
} }
@ -191,14 +195,21 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) {
return ZMK_EV_EVENT_BUBBLE; return ZMK_EV_EVENT_BUBBLE;
} }
// keep track whether the event has been reraised, so we only reraise it once // we want to make sure every sticky key is given a chance to lazy press their behavior before
bool event_reraised = false; // the event gets reraised, and release their behavior after the event is reraised, so we keep
// track of them. this allows us to ensure the sticky key is pressed and released "around" the
// other key, especially in the case of lazy keys.
struct active_sticky_key *sticky_keys_to_press_before_reraise[ZMK_BHV_STICKY_KEY_MAX_HELD];
struct active_sticky_key *sticky_keys_to_release_after_reraise[ZMK_BHV_STICKY_KEY_MAX_HELD];
// reraising the event frees it, so make a copy of any event data we might // reraising the event frees it, so make a copy of any event data we might
// need after it's been freed. // need after it's been freed.
const struct zmk_keycode_state_changed ev_copy = *ev; const struct zmk_keycode_state_changed ev_copy = *ev;
for (int i = 0; i < ZMK_BHV_STICKY_KEY_MAX_HELD; i++) { for (int i = 0; i < ZMK_BHV_STICKY_KEY_MAX_HELD; i++) {
sticky_keys_to_press_before_reraise[i] = NULL;
sticky_keys_to_release_after_reraise[i] = NULL;
struct active_sticky_key *sticky_key = &active_sticky_keys[i]; struct active_sticky_key *sticky_key = &active_sticky_keys[i];
if (sticky_key->position == ZMK_BHV_STICKY_KEY_POSITION_FREE) { if (sticky_key->position == ZMK_BHV_STICKY_KEY_POSITION_FREE) {
continue; continue;
@ -212,14 +223,6 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) {
continue; continue;
} }
// If this event was queued, the timer may be triggered late or not at all.
// Release the sticky key if the timer should've run out in the meantime.
if (sticky_key->release_at != 0 && ev_copy.timestamp > sticky_key->release_at) {
stop_timer(sticky_key);
release_sticky_key_behavior(sticky_key, sticky_key->release_at);
continue;
}
if (ev_copy.state) { // key down if (ev_copy.state) { // key down
if (sticky_key->config->ignore_modifiers && if (sticky_key->config->ignore_modifiers &&
is_mod(ev_copy.usage_page, ev_copy.keycode)) { is_mod(ev_copy.usage_page, ev_copy.keycode)) {
@ -231,17 +234,30 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) {
// this sticky key is already in use for a keycode // this sticky key is already in use for a keycode
continue; continue;
} }
// we don't want the timer to release the sticky key before the other key is released
stop_timer(sticky_key);
// If this event was queued, the timer may be triggered late or not at all.
// Release the sticky key if the timer should've run out in the meantime.
if (sticky_key->release_at != 0 && ev_copy.timestamp > sticky_key->release_at) {
// If the key is lazy, a release is not needed on timeout
if (sticky_key->config->lazy) {
clear_sticky_key(sticky_key);
} else {
release_sticky_key_behavior(sticky_key, sticky_key->release_at);
}
continue;
}
if (sticky_key->config->lazy) {
// if the sticky key is lazy, we need to press it before the event is reraised
sticky_keys_to_press_before_reraise[i] = sticky_key;
}
if (sticky_key->timer_started) { if (sticky_key->timer_started) {
stop_timer(sticky_key);
if (sticky_key->config->quick_release) { if (sticky_key->config->quick_release) {
// immediately release the sticky key after the key press is handled. // immediately release the sticky key after the key press is handled.
if (!event_reraised) { sticky_keys_to_release_after_reraise[i] = sticky_key;
struct zmk_keycode_state_changed_event dupe_ev =
copy_raised_zmk_keycode_state_changed(ev);
ZMK_EVENT_RAISE_AFTER(dupe_ev, behavior_sticky_key);
event_reraised = true;
}
release_sticky_key_behavior(sticky_key, ev_copy.timestamp);
} }
} }
sticky_key->modified_key_usage_page = ev_copy.usage_page; sticky_key->modified_key_usage_page = ev_copy.usage_page;
@ -251,14 +267,35 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) {
sticky_key->modified_key_usage_page == ev_copy.usage_page && sticky_key->modified_key_usage_page == ev_copy.usage_page &&
sticky_key->modified_key_keycode == ev_copy.keycode) { sticky_key->modified_key_keycode == ev_copy.keycode) {
stop_timer(sticky_key); stop_timer(sticky_key);
release_sticky_key_behavior(sticky_key, ev_copy.timestamp); sticky_keys_to_release_after_reraise[i] = sticky_key;
} }
} }
} }
if (event_reraised) {
return ZMK_EV_EVENT_CAPTURED; // give each sticky key a chance to press their behavior before the event is reraised
for (int i = 0; i < ZMK_BHV_STICKY_KEY_MAX_HELD; i++) {
struct active_sticky_key *sticky_key = sticky_keys_to_press_before_reraise[i];
if (sticky_key) {
press_sticky_key_behavior(sticky_key, ev_copy.timestamp);
}
} }
return ZMK_EV_EVENT_BUBBLE; // give each sticky key a chance to release their behavior after the event is reraised, lazily
// reraising. keep track whether the event has been reraised so we only reraise it once
bool event_reraised = false;
for (int i = 0; i < ZMK_BHV_STICKY_KEY_MAX_HELD; i++) {
struct active_sticky_key *sticky_key = sticky_keys_to_release_after_reraise[i];
if (sticky_key) {
if (!event_reraised) {
struct zmk_keycode_state_changed_event dupe_ev =
copy_raised_zmk_keycode_state_changed(ev);
ZMK_EVENT_RAISE_AFTER(dupe_ev, behavior_sticky_key);
event_reraised = true;
}
release_sticky_key_behavior(sticky_key, ev_copy.timestamp);
}
}
return event_reraised ? ZMK_EV_EVENT_CAPTURED : ZMK_EV_EVENT_BUBBLE;
} }
void behavior_sticky_key_timer_handler(struct k_work *item) { void behavior_sticky_key_timer_handler(struct k_work *item) {
@ -271,7 +308,12 @@ void behavior_sticky_key_timer_handler(struct k_work *item) {
if (sticky_key->timer_cancelled) { if (sticky_key->timer_cancelled) {
sticky_key->timer_cancelled = false; sticky_key->timer_cancelled = false;
} else { } else {
release_sticky_key_behavior(sticky_key, sticky_key->release_at); // If the key is lazy, a release is not needed on timeout
if (sticky_key->config->lazy) {
clear_sticky_key(sticky_key);
} else {
release_sticky_key_behavior(sticky_key, sticky_key->release_at);
}
} }
} }
@ -295,8 +337,9 @@ static struct behavior_sticky_key_data behavior_sticky_key_data;
static struct behavior_sticky_key_config behavior_sticky_key_config_##n = { \ static struct behavior_sticky_key_config behavior_sticky_key_config_##n = { \
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, DT_DRV_INST(n)), \ .behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, DT_DRV_INST(n)), \
.release_after_ms = DT_INST_PROP(n, release_after_ms), \ .release_after_ms = DT_INST_PROP(n, release_after_ms), \
.ignore_modifiers = DT_INST_PROP(n, ignore_modifiers), \
.quick_release = DT_INST_PROP(n, quick_release), \ .quick_release = DT_INST_PROP(n, quick_release), \
.lazy = DT_INST_PROP(n, lazy), \
.ignore_modifiers = DT_INST_PROP(n, ignore_modifiers), \
}; \ }; \
BEHAVIOR_DT_INST_DEFINE(n, behavior_sticky_key_init, NULL, &behavior_sticky_key_data, \ BEHAVIOR_DT_INST_DEFINE(n, behavior_sticky_key_init, NULL, &behavior_sticky_key_data, \
&behavior_sticky_key_config_##n, POST_KERNEL, \ &behavior_sticky_key_config_##n, POST_KERNEL, \

View File

@ -1,8 +1,8 @@
pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x04 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
released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0xE1 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 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00

View File

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

View File

@ -0,0 +1,6 @@
pressed: usage_page 0x07 keycode 0xE0 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
released: usage_page 0x07 keycode 0xE0 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

View File

@ -0,0 +1,37 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/* this test ensures that timing out while the other key is being held results in correct behavior */
&sk {
lazy;
release-after-ms = <50>;
};
/ {
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&sk LEFT_CONTROL &kp A
&sk LEFT_SHIFT &sk LEFT_ALT>;
};
};
};
&kscan {
events = <
/* tap sk LEFT_CONTROL */
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
/* tap A */
ZMK_MOCK_PRESS(0,1,60)
ZMK_MOCK_RELEASE(0,1,10)
/* tap A */
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
>;
};

View File

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

View File

@ -0,0 +1,4 @@
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
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00

View File

@ -0,0 +1,43 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/* this test ensures that lazy sticky keys don't emit anything on timeout */
&sk {
lazy;
release-after-ms = <50>;
};
/ {
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&sk LEFT_CONTROL &kp A
&sk LEFT_SHIFT &sk LEFT_ALT>;
};
};
};
&kscan {
events = <
/* tap sk LEFT_CONTROL */
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
/* tap sk LEFT_SHIFT */
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,60)
/* tap sk LEFT_ALT */
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,60)
/* tap A */
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
/* tap A */
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
>;
};

View File

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

View File

@ -0,0 +1,16 @@
pressed: usage_page 0x07 keycode 0xE0 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
released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x1D implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x1D implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0xE1 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
released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0xE1 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

View File

@ -0,0 +1,67 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
/* this test verifies that lazy sticky keys work similarly to regular sticky keys, and includes cases from 10-callum-mods and 8-lsk-osk.
the only difference is that the lazy key does not exit the sticky layer */
&sk {
lazy;
};
/ {
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&sk LEFT_CONTROL &sl 1
&kp A &mo 1>;
};
lower_layer {
bindings = <
&sk LEFT_CONTROL &kp X
&sk LEFT_SHIFT &kp Z>;
};
};
};
&kscan {
events = <
/* tap LEFT_CONTROL on base layer */
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
/* tap A */
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
/* tap sl lower_layer */
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
/* tap sk LEFT_CONTROL */
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
/* tap Z */
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
/* press mo lower_layer */
ZMK_MOCK_PRESS(1,1,10)
/* tap sk LEFT_CONTROL */
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
/* tap sk LEFT_SHIFT */
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
/* release mo lower_layer */
ZMK_MOCK_RELEASE(1,1,10)
/* tap A (with left control and left shift enabled) */
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
/* tap A (no sticky keys anymore) */
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
>;
};

View File

@ -1,8 +1,8 @@
pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00

View File

@ -1,4 +1,4 @@
pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00

View File

@ -1,6 +1,6 @@
pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
pressed: 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 pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00

View File

@ -1,8 +1,8 @@
pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0xE0 implicit_mods 0x00 explicit_mods 0x00

View File

@ -34,6 +34,12 @@ By default, sticky keys stay pressed for a second if you don't press any other k
Some typists may find that using a sticky shift key interspersed with rapid typing results in two or more capitalized letters instead of one. This happens as the sticky key is active until the next key is released, under which other keys may be pressed and will receive the modifier. You can enable the `quick-release` setting to instead deactivate the sticky key on the next key being pressed, as opposed to released. Some typists may find that using a sticky shift key interspersed with rapid typing results in two or more capitalized letters instead of one. This happens as the sticky key is active until the next key is released, under which other keys may be pressed and will receive the modifier. You can enable the `quick-release` setting to instead deactivate the sticky key on the next key being pressed, as opposed to released.
#### `lazy`
By default, sticky keys are activated on press until another key is pressed. You can enable the `lazy` setting to instead activate the sticky key right _before_ the other key is pressed. This is useful for mouse interaction or situations where you don't want the host to see anything during a sticky-key timeout, for example `&sk LGUI`, which can trigger a menu if pressed alone.
Note that tapping a lazy sticky key will not trigger other behaviors such as the release of other sticky keys or layers. If you want to use a lazy sticky key to activate the release of a sticky layer, potential solutions include wrappping the sticky key in a simple macro which presses the sticky behavior around the sticky key press, doing the same with `&mo LAYER`, or triggering a tap of some key like `K_CANCEL` on sticky key press.
#### `ignore-modifiers` #### `ignore-modifiers`
This setting is enabled by default. It ensures that if a sticky key modifier is pressed before a previously pressed sticky key is released, the modifiers will get combined so you can add more sticky keys or press a regular key to apply the modifiers. This is to accommodate _callum-style mods_ where you are prone to rolling sticky keys. If you want sticky key modifiers to only chain after release, you can disable this setting. Please note that activating multiple modifiers via [modifier functions](https://zmk.dev/docs/codes/modifiers#modifier-functions) such as `&sk LS(LALT)`, require `ignore-modifiers` enabled in order to function properly. This setting is enabled by default. It ensures that if a sticky key modifier is pressed before a previously pressed sticky key is released, the modifiers will get combined so you can add more sticky keys or press a regular key to apply the modifiers. This is to accommodate _callum-style mods_ where you are prone to rolling sticky keys. If you want sticky key modifiers to only chain after release, you can disable this setting. Please note that activating multiple modifiers via [modifier functions](https://zmk.dev/docs/codes/modifiers#modifier-functions) such as `&sk LS(LALT)`, require `ignore-modifiers` enabled in order to function properly.

View File

@ -230,6 +230,7 @@ Applies to: `compatible = "zmk,behavior-sticky-key"`
| `bindings` | phandle array | A behavior (without parameters) to trigger | | | `bindings` | phandle array | A behavior (without parameters) to trigger | |
| `release-after-ms` | int | Releases the key after this many milliseconds if no other key is pressed | 1000 | | `release-after-ms` | int | Releases the key after this many milliseconds if no other key is pressed | 1000 |
| `quick-release` | bool | Release the sticky key on the next key press instead of release | false | | `quick-release` | bool | Release the sticky key on the next key press instead of release | false |
| `lazy` | bool | Wait until the next key press to activate the sticky key behavior | false |
| `ignore-modifiers` | bool | If enabled, pressing a modifier key does not cancel the sticky key | true | | `ignore-modifiers` | bool | If enabled, pressing a modifier key does not cancel the sticky key | true |
This behavior forwards the one parameter it receives to the parameter of the behavior specified in `bindings`. This behavior forwards the one parameter it receives to the parameter of the behavior specified in `bindings`.