-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
New Feature: required keys for tap-hold behaviors #835
Conversation
I like the general idea, but maybe we should change the meaning of the list from "required_keys_for_hold" to "hold-keys". Any combinations of a hold-tap + a key (position) from this list can trigger a hold, but any keys not in the list won't trigger the hold That would enable us to do "left-side hold taps" and "right side hold taps", that would only trigger for keys on the opposite side of the board for example. The current implementation requires all keys on the list to be pressed, for which I can't really find a usecase. |
@@ -30,3 +30,7 @@ properties: | |||
- "tap-preferred" | |||
retro-tap: | |||
type: boolean | |||
required_keys_for_hold: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
required-keys-for-hold
@@ -90,6 +92,8 @@ struct last_tapped { | |||
|
|||
struct last_tapped last_tapped; | |||
|
|||
bool is_key_held[ZMK_KEYMAP_LEN] = {0}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe rename to key_position_pressed
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about is_key_position_pressed? I do like the "is_" prefix, IMO it helps imply bool
I played around with this more, and you're right, AND on all the keys in the list isn't as useful as OR. As for naming, hold-enabler-keys? |
d951396
to
538cf3c
Compare
538cf3c
to
acabc7a
Compare
Great! I think there's a bug in the current implementation. If one of the 'hold-enabled keys' is pressed before the hold-tap, the hold will be triggered even though the actual key being pressed is not a hold-enabled key. Let's say 1_down, 0_down, 2_down, 2_up, 0_up => "wE" instead of "wqe". As a possible fix, we could move the Please write some test to cover the scenarios you described above, and this one. |
The original intent was indeed to produce "wE", and not "wqe". It was intended to only consider the pressed state, and be agnostic to the order of pressing. BTW, this is why the variable was named hold_enableR_keys, rather than hold_enableD_keys I think it might be better to be order agnostic when using conditional hold/tap for situations like home-row mods. For example
Then the sequence Let me know what you think? These are tricky for me to reason about. |
A concern I have with the proposal as it stands now, is that there are two distinct scenarios that are covered. I'd like to keep the set of behaviors and options we have as orthogonal as possible and combinable (e.g., there should be one way to do something, and many 'simple' behaviors build up to a more complex whole) Scenario A: conditional key first
Scenario B: hold-tap first
Scenario A could be covered by making X a layer key (if it needs to have other behaviors too that could be arranged with a macro-like behavior). Scenario B, where it depends which key is pressed to decide on the hold-tap, would be something that we can not do yet. It's also a often requested feature, for hold-taps that only hold for the other side of a split board for example. |
Updated such that keypresses are order dependent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in general, this looks good. I think we can simplify somewhat.
Haven't looked into the tests and documentation yet.
thanks! Based on your feedback, I was able to find an implementation that doesn't require us to store anything additional in the active_hold_tap struct. Instead, it simply sets active_hold_tap.status to STATUS_TAP under the appropriate conditions. |
decide_tap_preferred(hold_tap, decision_moment); | ||
// For conditional hold-tap behaviors that have failed to meet the required prerequisites | ||
// for a hold decision, force a tap decision. | ||
if (decision_moment == HT_OTHER_KEY_DOWN && hold_tap->config->hold_enabler_keys_len > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this if statement is correct for all 'flavors'. For example, the balanced
never decides on HT_OTHER_KEY_DOWN; it decides on HT_OTHER_KEY_UP. I think we should put this code into a function is_hold_enabled_key(struct active_hold_tap *hold_tap, int32 other_key_decision)
, and call it from each of the decide_*
methods.
Somewhat tricky would be the behavior of tap-preferred
. I think it should act like this:
- if the timer has not run out (e.g., we have not seen
HT_TIMER_EVENT
, always trigger tap. (doesn't matter which key is pressed) - if the timer did run out (the HT_TIMER_EVENT was seen, maybe setting STATUS_UNDECIDED_TIMER_SEEN), trigger HOLD if the key is a hold_enabler_key, otherwise trigger TAP
For balanced, the decision_moment to check for hold_enabler_keys would be on HT_OTHER_KEY_UP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right, I'll fix. I'll also make the testing more robust to catch this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'll still need to store position_of_first_other_key_pressed in the active_hold_tap struct, because in tap-preferred situation 4a, I don't see any other way of verifying whether the first other key that was pressed was an enabler key, especially if a second key is pressed before tapping term expires
rewrote the feature again. Did a couple things:
To do:
|
Conditional -> positional in docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! One minor grammar nitpick.
docs/docs/behaviors/hold-tap.md
Outdated
|
||
* Positional hold-taps can be useful with home-row modifiers. By using a positional hold-tap behavior for home-row modifiers on the left hand, and setting `hold-trigger-key-positions` to the keys under the right hand, positional hold-tap will only allow hold behaviors to occur with cross-hand keypresses. | ||
|
||
* Note that for regular hold-tap behaviors a shorter `tapping-term` encourages hold decisions. However the opposite is true for positional hold-tap behaviors, where a shorter `tapping-term` actually encourages tap decisions. This is because when the `tapping-term` expires, this triggers the behavior to decide as either a tap or a hold. But if the user has not yet had time to press one of the `hold-trigger-key-positions`, then with positional hold-tap the behavior will decide as a tap. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, we should instead only check the postiion-hold-keys when the current status is STATUS_HOLD_INTERRUPT
which keeps te behavior more in line with the normal behavior of the different flavors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that makes a lot of sense!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, actually, in situation 4b of tap-preferred, the hold/tap is decided into STATUS_HOLD_TIMER by HT_TIMER_EVENT. Let's say "J" is one of the hold-trigger-keys. In that situation, I think the most intuitive behavior would be to decide into a hold and not a tap no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
Sooo, where does that lead us. it means we need to work on both STATUS_HOLD_TIMER and STATUS_HOLD_INTERRUPT, but we only want to trigger the TAP change if a key was pressed.
…into conditional_mod_tap
LGTM @petejohanson ready from merge as far as I'm concerned. |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, and thanks for the test coverage!
Adds the ability to for users to configure hold-tap behaviors so that the only resolve to hold behaviors if specified keys are pressed. Let's say that we set up a keymap this way:
Where cmt is defined:
These are the expected behaviors:
key_0_down, key_1_down, key_1_up, key_0_up => "W"
Because the sequence is executed in longer than 1 ms, and key_1 is pressed when the hold/tap behavior is decided, the hold/tap behavior is resolved into a hold. This is the same behavior as would occur with the current logic as well.
0_down, 2_down, 2_up, 0_up => "qe"
Because the sequence is executed in longer than 1 ms, but key_1 is NOT pressed when the hold/tap behavior is decided, the hold/tap behavior is resolved into a tap. This differs from the behavior that would occur with the current logic, which would not have a requirement that key_1 be pressed to resolve into a hold behavior, and would therefore emit "E".
This is extremely useful for my situation:
< NUM CLCK
, such that the sequence <caps_down, j_down, j_up, caps_up> produces "4"&mt LSFT A