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

fix(behaviors): Make multiple sticky keys work on same key position #2758

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

caksoylar
Copy link
Contributor

@caksoylar caksoylar commented Jan 10, 2025

Sticky keys have logic implemented so that if you press the same sticky key a second time, it releases the first press before activating a sticky key press again. However, multiple sticky keys are only disambiguated by key position, which means that you currently cannot have active multiple sticky keys that are triggered from the same key position. This scenario occurs under a couple conditions:

  • You activate a sticky key (e.g. &sk LCTRL), then activate a different sticky key e.g. (&sk LSHIFT) on a different layer but at the same key position
  • You activate multiple sticky keys on a macro invocation (e.g. with bindings = <&sk LSHIFT>, <&sl 1>;)

Using only the key position is usually enough to disambiguate multiple active instances of a behavior; for instance for hold-taps, you cannot press the same key position again while one hold-tap is active at that position. However this doesn't apply to sticky keys since they stay active after you release the key that activates them.

In order to support above scenarios, add three tests covering them and modify the implementation to use behavior binding (e.g. &kp vs. &mo) and parameter (e.g. LCTRL vs. LSHIFT) in addition to key position to disambiguate between active sticky keys.

Also remove the redundant param2 storage in sticky keys since they only accept one binding cell, can drop this refactor commit if it is unnecessary.

Fixes #508 and #1421.

PR check-list

  • Branch has a clean commit history
  • Additional tests are included, if changing behaviors/core code that is testable.
  • Proper Copyright + License headers added to applicable files (Generally, we stick to "The ZMK Contributors" for copyrights to help avoid churn when files get edited)
  • Pre-commit used to check formatting of files, commit messages, etc.
  • Includes any necessary documentation changes.

@caksoylar caksoylar requested a review from a team as a code owner January 10, 2025 07:13
@Nick-Munnich
Copy link
Contributor

All LGTM, don't think docs changes or copyright stuff is missing

Copy link
Contributor

@urob urob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related: #508 and #1421. Two small comments below.

@caksoylar
Copy link
Contributor Author

@urob thanks for the issue pointers, I had a vague memory of issues with macros but I never thought to search for existing issues!

@caksoylar
Copy link
Contributor Author

Interestingly Okke attempted to solve the issue in #670 by implementing a general trace ID. That might be a more robust solution in general, but this implementation is more self-contained and seems robust enough to me.

@caksoylar caksoylar force-pushed the behaviors/sticky-key-positions branch from 8b8f6ff to 9501d0b Compare January 10, 2025 18:09
@friendlylambda
Copy link

friendlylambda commented Jan 11, 2025

Thank you so much @caksoylar!

I did some manual testing of this change here to verify it and can confirm it's all working as I would expect. Here are my test notes:

Action is the action I took on the keyboard. Result is the key input received by the OS.

Key layout is here: https://my.glove80.com/#/layout/user/2a9bbbdb-ca3d-4bab-b0cd-214c59581ae0

Run 1: Before Patch (24.02)

Current Behavior

Result:

Test 1: Sticky shift -> &sl layer switch -> sticky command (on same key but different layer) -> letter key (on base layer)

Time (Approx) Action Result
0ms sticky shift key down shift DOWN
200ms sticky shift key up
500ms Layer switch key down
600ms Layer switch key up
700ms sticky command key down shift UP
700ms command DOWN
800ms sticky command key up
900ms letter key "h" down h DOWN (OS command+h shortcut triggers)
1000ms letter key "h" up command UP
1000ms h UP

Result: 😞 command+h is triggered because the sticky shift is ended when the sticky command is triggered, even though they're on different layers.

Test 2: Macro that sets sticky shift then switches to a sticky layer

Time (Approx) Action Result
0ms macro button down shift DOWN
100ms macro button up
400ms letter key "j" down "j"
450ms letter key "j" up j UP
450ms shift UP

Result: 😞 lowercase j is outputted instead of an uppercase one due to the sticky layer in the macro ending the sticky shift when triggered

Test 3: Macro that sets sticky shift then sticky alt then switches to a sticky layer (layer 2)

Time (Approx) Action Result
0ms macro button down shift DOWN
0ms shift UP
0ms alt DOWN
0ms alt UP
100ms macro button up
200ms letter key "m" down "m"
450ms letter key "m" up m UP

Result: 😞 lowercase m (which exists only on layer 2) is typed and the modifiers have no effect because they end when the sticky layer is triggered

Run 2: After Patch

Updated Behavior

Test 1: Sticky shift -> &sl layer switch -> sticky command (on same key but different layer) -> letter key (on base layer)

Time (Approx) Action Result
0ms sticky shift key down shift DOWN
200ms sticky shift key up
500ms Layer switch key down
600ms Layer switch key up
700ms sticky command key down command DOWN
800ms sticky command key up
900ms letter key "h" down h DOWN (OS shortcut for command+shift+h triggered)
2000ms letter key "h" up h UP
2000ms shift UP
2000ms command UP

Result: ✅ both sticky modifiers become active and then resolve when the "h" key is pressed, just like they would if they were on different physical keys.

Test 2: Macro that sets sticky shift then switches to a sticky layer

Time (Approx) Action Result
0ms macro button down shift DOWN
100ms macro button up
400ms letter key "j" down "J"
450ms letter key "j" up j UP
450ms shift UP

Result: ✅ capital J is typed

Test 3: Macro that sets sticky shift then sticky alt then switches to a sticky layer

Time (Approx) Action Result
0ms macro button down shift DOWN
0ms alt DOWN
100ms macro button up
400ms letter key "j" down j DOWN (OS shortcut for shift+option+j triggered)
450ms letter key "j" up j UP
450ms shift UP
450ms alt UP

Result: ✅ shift+alt+j shortcut is triggered, just like it would be each sticky modifier and the j key were on different physical keys.

Existing Functionality

Test 4: Sticky shift then a letter

Time (Approx) Action Result
0ms sticky shift key down shift DOWN
100ms letter key "h" down "H"
200ms letter key "h" up h UP
200ms shift UP

Result: ✅

Test 5: Sticky shift then sticky shift (same key)

Time (Approx) Action Result
0ms sticky shift key down shift DOWN
200ms sticky shift key up
400ms sticky shift key down shift UP
400ms shift DOWN
1400ms sticky shift key up shift UP

Result: ✅

Test 6: Sticky shift then sticky gui (different keys)

Time (Approx) Action Result
0ms sticky shift key down shift DOWN
250ms sticky shift key up
500ms sticky gui key down gui DOWN
750ms sticky gui key up
1250ms shift UP
1750ms gui UP

Result: ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Sticky keys: fix multiple sticky keys on the same key position active
4 participants