Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(behaviors): sticky keys no longer sticky after being held #1788

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-sticky-key.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ properties:
release-after-ms:
type: int
required: true
no-sticky-after-hold-ms:
type: int
quick-release:
type: boolean
ignore-modifiers:
Expand Down
12 changes: 12 additions & 0 deletions app/src/behaviors/behavior_sticky_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

struct behavior_sticky_key_config {
uint32_t release_after_ms;
uint32_t no_sticky_after_hold_ms;
bool quick_release;
bool ignore_modifiers;
struct zmk_behavior_binding behavior;
Expand All @@ -41,6 +42,7 @@ struct active_sticky_key {
uint32_t param2;
const struct behavior_sticky_key_config *config;
// timer data.
int64_t no_sticky_after_hold_at;
bool timer_started;
bool timer_cancelled;
int64_t release_at;
Expand All @@ -66,6 +68,7 @@ static struct active_sticky_key *store_sticky_key(uint32_t position, uint32_t pa
sticky_key->param2 = param2;
sticky_key->config = config;
sticky_key->release_at = 0;
sticky_key->no_sticky_after_hold_at = 0;
sticky_key->timer_cancelled = false;
sticky_key->timer_started = false;
sticky_key->modified_key_usage_page = 0;
Expand Down Expand Up @@ -144,6 +147,8 @@ static int on_sticky_key_binding_pressed(struct zmk_behavior_binding *binding,
return ZMK_BEHAVIOR_OPAQUE;
}

sticky_key->no_sticky_after_hold_at = event.timestamp + cfg->no_sticky_after_hold_ms;

press_sticky_key_behavior(sticky_key, event.timestamp);
LOG_DBG("%d new sticky_key", event.position);
return ZMK_BEHAVIOR_OPAQUE;
Expand All @@ -162,6 +167,11 @@ static int on_sticky_key_binding_released(struct zmk_behavior_binding *binding,
return release_sticky_key_behavior(sticky_key, event.timestamp);
}

if (sticky_key->no_sticky_after_hold_at < event.timestamp) {
LOG_DBG("Sticky key %d was held for longer than no-sticky-after-hold-ms.", event.position);
return release_sticky_key_behavior(sticky_key, event.timestamp);
}

// No other key was pressed. Start the timer.
sticky_key->timer_started = true;
sticky_key->release_at = event.timestamp + sticky_key->config->release_after_ms;
Expand Down Expand Up @@ -290,6 +300,8 @@ static struct behavior_sticky_key_data behavior_sticky_key_data;
static struct behavior_sticky_key_config behavior_sticky_key_config_##n = { \
.behavior = ZMK_KEYMAP_EXTRACT_BINDING(0, DT_DRV_INST(n)), \
.release_after_ms = DT_INST_PROP(n, release_after_ms), \
.no_sticky_after_hold_ms = \
DT_INST_PROP_OR(n, no_sticky_after_hold_ms, DT_INST_PROP(n, release_after_ms)), \
.ignore_modifiers = DT_INST_PROP(n, ignore_modifiers), \
.quick_release = DT_INST_PROP(n, quick_release), \
}; \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s/.*hid_listener_keycode_//p
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pressed: usage_page 0x07 keycode 0x08 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 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 0x1C implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x1C 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

/* This test ensures that sticky keys remain active while being pressed. */

/ {
keymap {
compatible = "zmk,keymap";
label ="Default keymap";

default_layer {
bindings = <
&sk E &sl 1
&kp A &kp B>;
};

lower_layer {
bindings = <
&sk LEFT_CONTROL &kp X
&kp Y &kp Z>;
};
};
};

&kscan {
events = <
// Hold &sk E past no-sticky-after-hold-ms
ZMK_MOCK_PRESS(0,0,1200)
// Tap &kp A twice
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
// &sk E should remain active before releasing
ZMK_MOCK_RELEASE(0,0,10)

// Hold &sl 1 past no-sticky-after-hold-ms
ZMK_MOCK_PRESS(0,1,1200)
// Tap &kp Y twice
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
// &sl 1 should remain active before releasing
ZMK_MOCK_RELEASE(0,1,10)
>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s/.*hid_listener_keycode_//p
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x08 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 0x04 implicit_mods 0x00 explicit_mods 0x00
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

/* This test ensures that sticky keys are deactivated immediately upon
* release after being held for longer than `no-sticky-after-hold-ms`.
*/

&sk {
no-sticky-after-hold-ms = <200>;
};

&sl {
no-sticky-after-hold-ms = <200>;
};

/ {
keymap {
compatible = "zmk,keymap";
label ="Default keymap";

default_layer {
bindings = <
&sk E &sl 1
&kp A &kp B>;
};

lower_layer {
bindings = <
&sk LEFT_CONTROL &kp X
&kp Y &kp Z>;
};
};
};

&kscan {
events = <
// Hold &sk E past no-sticky-after-hold-ms
ZMK_MOCK_PRESS(0,0,400)
// Release &sk E
ZMK_MOCK_RELEASE(0,0,10)
// &sk E should now be deactivated
// Tap &kp A right after
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)

// Hold &sl 1 past no-sticky-after-hold-ms
ZMK_MOCK_PRESS(0,1,400)
// Release &sl 1
ZMK_MOCK_RELEASE(0,1,10)
// &sl 1 should now be deactivated
// Tap &kp A right after
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
>;
};
4 changes: 4 additions & 0 deletions docs/docs/behaviors/sticky-key.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ You can use any keycode that works for `&kp` as parameter to `&sk`:

By default, sticky keys stay pressed for a second if you don't press any other key. You can configure this with the `release-after-ms` setting.

#### `no-sticky-after-hold-ms`

Sticky keys that are pressed for longer than `no-sticky-after-hold-ms` will function like regular keys.

#### `quick-release`

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.
Expand Down
Loading