Skip to content

feat(behaviors): Add support for exclude-prior-idle-key-positions for hold-tap#3251

Open
jondonas wants to merge 1 commit intozmkfirmware:mainfrom
jondonas:prior-idle-ignore
Open

feat(behaviors): Add support for exclude-prior-idle-key-positions for hold-tap#3251
jondonas wants to merge 1 commit intozmkfirmware:mainfrom
jondonas:prior-idle-ignore

Conversation

@jondonas
Copy link
Copy Markdown

@jondonas jondonas commented Feb 22, 2026

Adds a new exclude-prior-idle-key-positions property to hold-tap behaviors, allowing specific key positions to be excluded from require-prior-idle-ms idle timer behavior. This enables users to press certain keys (e.g. Backspace) without suppressing hold behavior on the next hold-tap activation.

Addresses #2400

Key behavior:

  • Pressing an excluded key before the hold-tap cancels any existing timer for the hold-tap.
  • Pressing an excluded key as part of the hold-tap combo ignores the timer, and behaves as if no timer is set.
  • Each hold-tap maintains its own exclusion list, so different hold-taps can exclude different positions.

This is particularly useful for home-row mods like Shift, where it's useful to specify an idle timer for fast typing, but you also want certain keys to both cancel the timer (eg. Space, where the next word is commonly a capital) and not be affected by the timer (eg. /, where you may want to immediately Shift to ? after a word).

This feature works well with all existing hold-tap behaviors. I've tested quite thoroughly and have found good use combining this with hold-trigger-key-positions too.

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.

… hold-tap

Adds a new exclude-prior-idle-key-positions property to hold-tap behaviors, allowing specific key positions to be excluded from require-prior-idle-ms idle timer behavior. This enables users to press certain keys (e.g. Backspace) without suppressing hold behavior on the next hold-tap activation.
@jondonas jondonas changed the title feat(behaviors): Add support for prior-idle-ignore-key-positions for hold-tap feat(behaviors): Add support for exclude-prior-idle-key-positions for hold-tap Feb 24, 2026
@nmunnich
Copy link
Copy Markdown
Contributor

Since require-prior-idle-ms is a condition on keycodes rather than positions pressed/behaviors executed, I'm not sure I agree with the choice to exclude based on key position - I'd be more inclined to exclude based on keycode, similar to caps word. Do you have a particular reason for excluding based on key position that I'm missing?

@jondonas
Copy link
Copy Markdown
Author

Biggest reason is there are a lot of hold-tap options to wrap your head around, and I think it lends to simplicity to keep this new feature configured similar to hold-trigger-key-position (which also seems better suited for key codes - but wasn't implemented that way originally?)

I don't feel super strongly about it though. I also personally don't use hold-taps on anything but my base layer, so it's easiest for me to reason about both exclude-prior-idle-key-positions and hold-trigger-key-position as key positions.

@nmunnich
Copy link
Copy Markdown
Contributor

Hold-trigger-key-positions makes sense because a user may have the same key on both halves and only wish to exclude one of them, e.g. B in a qwerty layout.

I agree that there are a lot of hold-tap options going around, but I wouldn't consider that an argument against doing things the "right" way. I think having a default list of such keys would be quite beneficial.

@caksoylar
Copy link
Copy Markdown
Contributor

See #2768. I'd ideally like to see the same logic for combos like in that PR and hold-taps (which is this but keycode-based, like Nick suggests).

@jondonas
Copy link
Copy Markdown
Author

jondonas commented Feb 25, 2026

Gotcha - makes sense for hold-trigger-key-positions having a different use-case!

I could change this to exclude-prior-idle-key-codes. Would you like the idea of that and do you have any other high-level suggestions?

One thing to callout for my implementation that may differ from the examples in the other issues: excluded keys don't just ignore the idle timer, they also cancel the timer when pressed. eg s -> SPACE -> hold-tap you'd want the space to cancel the timer so you can flow into the next hold tap (particularly useful for SHIFT). This behavior is similarly valuable as ignoring keys after the hold-tap (I originally considered two lists to separate these behaviors, but could only think of extremely niche use-cases that didn't seem to justify the extra complexity)

@caksoylar
Copy link
Copy Markdown
Contributor

I could change this to exclude-prior-idle-key-codes.

I would name it differently; require-prior-idle-exclude or require-prior-idle-exclude-keycodes. i.e. prefix is shared, and "keycodes" is always used as a single word in docs. Not sure if we want to include it at all given we probably won't have both types, but no need to bikeshed too much about it right now.

One thing to callout for my implementation that may differ from the examples in the other issues: excluded keys don't just ignore the idle timer, they also cancel the timer when pressed. eg s -> SPACE -> hold-tap you'd want the space to cancel the timer so you can flow into the next hold tap (particularly useful for SHIFT).

Seems reasonable to me. Also given the typical values that are used for the timeout, if you press an ignored key like SPACE between two other presses, cancelling the timer shouldn't make much difference; timeout should typically exceed even if not cancelled?

@jondonas
Copy link
Copy Markdown
Author

Could use your thoughts on this - should we limit exclusions to just specific &kp or match on any binding, eg. &shift_left LSHFT X? Implementation for just &kp seems simple enough. But downside is you wouldn't be able to exclude keys with hold-tap configured (and I can think of uses for that), or anything complex.

Matching on bindings is getting a bit complex and hard for me to think about edge cases. Not sure I have working code for that yet.

@caksoylar
Copy link
Copy Markdown
Contributor

Let me know if I am misunderstanding, but all you need to do should be to check the keycode_state_changed event and the keycode within it to see if it matches the list. There should be a few examples of it in the codebase, like in caps word. In this case it doesn't matter which behavior emitted the keycode, so it should work all the same from &kp or another hold-tap.

@jondonas
Copy link
Copy Markdown
Author

Maybe I'm not understanding this right, but it looks like we only emit keycode_state_changed once the hold-tap is resolved (we only get key position at the beginning of the hold-tap). So a bit chicken and egg because by the time we can determine keycode, the hold-tap evaluation is already complete.

To resolve this at the beginning of hold-tap, we'd need to walk down the layers to determine what actual key code is being pressed. That's where I have uncertainty about edge-cases.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants