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

[Feature Request] Vary tapping_term based on next key being tapped. #11372

Closed
1 of 4 tasks
theol0403 opened this issue Dec 31, 2020 · 11 comments
Closed
1 of 4 tasks

[Feature Request] Vary tapping_term based on next key being tapped. #11372

theol0403 opened this issue Dec 31, 2020 · 11 comments
Labels
enhancement help wanted stale Issues or pull requests that have become inactive without resolution.

Comments

@theol0403
Copy link

theol0403 commented Dec 31, 2020

Hello, I have a rather weird feature request that I think will greatly benefit my typing.

I'm looking for a way to control the tapping term not only per-held-key, but per-tapped-key. This way, when a LT and another key is pressed in a rapid sequence, depending on what that other key is, the tapping term can be fast-forwarded and the layer key emitted right away.

Feature Request Type

  • Core functionality
  • Add-on hardware support (eg. audio, RGB, OLED screen, etc.)
  • Alteration (enhancement/optimization) of existing feature(s)
  • New behavior

Description

I have layer-taps on my thumb keys. I want the tapping term to be fairly high, because if I press space or backspace (also on a thumb) too slowly, I don't want it to register as a hold and ignore the space/backspace. However, if I press any other key, I have to wait for the full tapping term to see the symbol, which is annoying.

Certain keys, like opposing thumb keys, almost never roll in regular writing, but they are used together for symbols. For example: I have a symbol layer on my backspace. If I press backspace on its own, I want it to have a high tapping term. If I press any other thumb key while holding the LT, it will output a symbol. I want it to instantly output the symbol without waiting for the full tapping term. The solution for me would be to turn on permissive_hold or hold_on_other_key_press, if I wanted it to apply to all keys on the layer. However, I only want it on certain keys - enabling it on the alphas (ie for the entire layer-tap) will cause typos.

I already have discovered a solution to enabling hold_on_other_key_press depending on the key being pressed. The logic goes as follows:

  • if layer-tap being held is backspace/symbol
    • if key being tapped is a different thumb
      • enable hold_on_other_key_press and instantly activate the layer and press the symbol
    • if key being tapped is any other key
      • wait full tapping term, standard behavior, until you are sure its a hold and not a roll

That has already greatly improved my typing experience, as I can press certain keys together like combos and have instant feedback, yet no typos when typing fast on the keys that like to roll.

However, what I am looking for is to adjust the tapping term based on the key being tapped. I want to be able to implement the following logic:

  • if LT is tapped and released, it must have been held for the full 200ms tapping term to qualify as a hold
  • if LT is held
    • if the next key that is tapped is an alpha key
      • the tapping term becomes 100ms, after which the key on the layer is pressed
    • if the next key is a thumb key
      • the tapping term becomes 0ms, the key on the layer is pressed instantly

I've drafted a way to do this, but due to my lack of understanding of qmk's internals, it does not work as expected.

uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) {
  // I'm looking for a way to access the key being tapped here
  int tapping = get_event_keycode(record->event, false);
  switch (keycode) {
    // if the right backspace LT(SYM, BSPC) is being held
    case THMB_RIGHT:
      // if the key being tapped is the opposing thumb, I don't want to wait nearly as long to trigger the hold
      if(tapping == THMB_LEFT) return 100;
      // if the key being tapped is any other key, then wait for the full tapping term
      return TAPPING_TERM;
  }
  return TAPPING_TERM;
}

Hopefully I made sense, and this functionality should not be too difficult to achieve! I think it will greatly help my typing experience, as certain symbols can be triggered very fast, without messing up the timing of important keys that like to roll while typing.

Thanks!

@purple-rw
Copy link

It seems like PERMISSIVE_HOLD_PER_KEY would work for you. Have you looked into that? On the other hand, there's a bug when you also define TAPPING_TERM_PER_KEY. So, avoid defining both for now, until the bug is fixed.

@getreuer
Copy link
Contributor

I would also like to customize the tapping term based on whether the mod-tap key is in the same vs. opposite hand of the other key that is pressed in combination with the mod-tap.

My motivation: home row mods. This way of setting the tapping term is useful for "home row mods" to prevent accidentally triggering a mod-tap modifier due to rollover. To describe briefly, suppose one has a Dvorak layout with home row "AOEUI DHTNS" where the 'E' key acts as left shift when held (LSFT_T(KC_E)):

  • If I press 'E' and then the 'U' key on the same hand, I'd like a high tapping term to avoid a rolling "eu" from producing "U".
  • But if I press 'E' and then the 'T' key on the opposite hand, I'd like a low tapping term so that capitalized letters can be typed quickly. Because the key is on the opposite hand, it is less likely to have a rollover to accidentally trigger the mod, so we should be able to get away with a lower tapping term.
  • In other words, I intend that left-hand mods should apply with right-hand keys (and symmetrically-defined right-hand mods apply with left-hand keys).

I'm similarly interested in layer-tap keys on the home row.

Bug in action_tapping.c WITHIN_TAPPING_TERM?

I have been trying unsuccessfully so far to do this "opposite hands logic" through get_tapping_term() and making tweaks in the action_tapping code. Like @theol0403, I am stumped about the record arg in

uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record)

The keycode arg gives the mod-tap key (LSFT_T(KC_E) in my example), but what is record? The Tap-Hold Configuration documentation mentions the record arg, but I don't understand from this whether it is supposed to be the mod-top vs. the other key. I was hoping record gives the following key (KC_U in my example) being pressed with the mod-tap, but looking at tmk_core/common/action_tapping.c, the mod-tap key (tapping_key) is unfortunately passed in both args:

#define WITHIN_TAPPING_TERM(e) (TIMER_DIFF_16(e.time, tapping_key.event.time) \
    < get_tapping_term(get_event_keycode(tapping_key.event, false), &tapping_key))

Most of the action_tapping code that follows calls get_tapping_term() through this WITHIN_TAPPING_TERM macro. However, there is one place where curiously get_tapping_term() is called directly as

(get_tapping_term(get_event_keycode(tapping_key.event, false), keyp) >= 500)

in which keyp passed as the record arg is the desired other key. (However, this call site only affects an edge case related to permissive hold, not the behavior generally.)

Is this a bug? Should the WITHIN_TAPPING_TERM macro pass the keyp record to get_tapping_term()? It seems like it ought to be

#define WITHIN_TAPPING_TERM(keyp) (TIMER_DIFF_16((keyp)->event.time, tapping_key.event.time) \
    < get_tapping_term(get_event_keycode(tapping_key.event, false), (keyp)))

and correspondingly update call sites of this macro to pass the keyrecord_t* rather than a keyevent_t.

manna-harbour's bilateral combinations

An entirely different solution is manna-harbour's "bilateral combinations" patch: code, documentation. This patch modifies action_tapping directly by inserting additional logical within process_tapping(). The limitations are that only the last mod-tap is affected, and IMO more importantly, the opposite-hand logic is hardcoded. I'd prefer to code that logic myself, to have the option to exceptionally set lower tapping term for certain hotkey same-hand combos.

Does anyone have insight on the record arg in get_tapping_term() or other ideas on how to achieve this opposite hands logic?

@theol0403
Copy link
Author

An update from me:

While the initial motivation of this PR is still relevant, I have one final use case to justify this feature that is currently the most relevant to my typing.

For someone who types using home row mods, it is in my best interest to keep tapping term as low as possible. This increases the responsiveness and speed at which I can trigger holds. However, reducing tapping term comes at a cost, because rolls can be interpreted at holds. Therefore, I have to find a perfect balance for the tapping term.

To help mitigate this issue, I use "bilateral combinations", which force same-hand rolls to resolve to two taps. However, as @getreuer mentioned, it is not customizable and has some other limitations. To address the first issue, I modified the source code to support user customization (ie "get_bilateral_combinations"), because I wanted it to only force rolls 1-2 keys apart from being handled.

This works quite well for me, but there is still one final issue that is preventing me from driving tapping term any lower: finger action speed. Specifically, even though rolls and fast typing are handled quite well, lowering tapping term causes key presses to disappear. The reason for this is that if my fingers rest on a key for too long, even if I release the key without pressing anything else, it will cause the hold to be triggered instead of the tap, because I held the key down for the entire tapping term.

The solution to this is exactly what "retro tapping" seeks to solve. If you press, hold, and release a mod-tap, it will cause the tap to activate. Therefore, if I enable retro tapping for my home row mods, I can drive lower tapping term significantly! This is because rolls are handled by bilateral-combinations, and slow taps are handled by retro-tapping.

However, the problem is that I don't want retro tapping. I want to be able to release a mod-top without needing to undo the tap.

This is where this feature request comes in: to vary the tapping term based on the next key being tapped, which can be used to achieve a "weak" retro-tap: a retro-tap that has a timeout.

For example: the default tapping term is 400, with no retro-tap. If I press and release the mod-tap, I will get a tap. If I hold and release the mod-tap, I will get a consequence-free mod cancellation.

However, if I hold the mod-tap and press any other key, I want the tapping term to become 120, and therefore rapidly engage the hold. And, as mentioned, bilateral-combinations will handle the rolls, preventing such a low tapping term from accidentally causing holds.

This is essentially a flexible way of creating a retro-tap with a timeout: tapping term is 400 if the key is pressed and released, but 120 if the key is held and then another key is pressed. This is currently the biggest thing I am missing in my typing.

Finally, as mentioned above, not only would this feature request solve the illustrated problem, it could replace and improve bilateral combinations altogether. Because, now I could add logic like:

  • if tap-hold is pressed and released: tapping-term is 400 (retro tap with timeout)
  • if tap-hold is held and a key on the opposite hand is tapped: tapping-term is 120 (speedy mods)
  • if tap-hold is held and a nearby key is tapped: tapping-term is 200 (roll protection)

This would also have many other benefits to bilateral-combinations: the timeout follows proper tap-hold logic, code duplication is reduced, and customization is easy.

I will eventually try to take a dive into the code and see if this feature request is easy to achieve, but I am just looking for comments and advice from mantainers.

Thanks!

@theol0403 theol0403 changed the title [Feature Request] Vary tapping_term based on key being tapped. [Feature Request] Vary tapping_term based on next key being tapped. Apr 16, 2021
@theol0403
Copy link
Author

theol0403 commented Apr 16, 2021

@getreuer

Regarding the keyp thing, that was exactly what I had to change to get hold_on_other_key_press to know the next key being tapped (pass keyp rather than tapping_key). Permissive hold already had keyp being passed to it.

In my first post, I said:

I already have discovered a solution to enabling hold_on_other_key_press depending on the key being pressed. The logic goes as follows:

if layer-tap being held is backspace/symbol
    if key being tapped is a different thumb
        enable hold_on_other_key_press and instantly activate the layer and press the symbol
    if key being tapped is any other key
        wait full tapping term, standard behavior, until you are sure its a hold and not a roll

This was with the patched behavior, but it also works by default with get_permissive_hold:

bool get_permissive_hold(uint16_t keycode, keyrecord_t *record) {
  int tapping = get_event_keycode(record->event, false);
  switch (keycode) {
    case U_H: // left shift
      switch (tapping) {
        case U_I: // right I
          return true;
      }
  }
  return false;
}

This would enable permissive hold when left shift was held I was tapped, speeding up my typing because I no longer need to wait quite as long to trigger the I.

The reason I made this issue was that I was unable to achieve this behavior with tapping-term, because if I could replicate this behavior with a varying tapping term, I could have a lot more control.

Also, this feature request could also make hold_on_other_key_press (#9404) unneeded - just make the tapping term 0 depending on the next key.

@yanghu
Copy link

yanghu commented Apr 17, 2021

I asked about this exact feature in the qmk discord a while ago, and this is IMO a vital feature to make home row mods working properly. I'm so frustrated with all the mistyped "I/si", "s;/:", etc. It's impossible to find a working tapping term to satisfy the need to type some keys quickly (like "s+; = :" or "s + i = I") while preventing same handing rolling like typing "E" when trying to do "se", not to say short tapping term tend to missed inputs, when you don't want retro tapping.

I tried to look into the implementation of actions and modify it, but failed, I'm not really familiar with different concepts of qmk internals and probably due to the "keyp" thing mentioned above.

If you have a working branch I'm happy to try it out and provide feedback.

@IsaacElenbaas
Copy link
Contributor

a retro-tap that has a timeout
a consequence-free mod cancellation

@theol0403 I'm surprised that this isn't how retro tapping already works. manna-harbour's original retro shift pull had nothing be sent upon release if RETRO_SHIFT was assigned to a number and it was exceeded for use with the mouse, and I never looked at retro tapping to see if that was in the normal feature. That should probably be added to regular retro tapping; I believe it would just be one more if here.

Related to what you two have been saying, I don't understand why there is no way to define behavior for all rolls and all nested taps. With a low TAPPING_TERM, IGNORE_MOD_TAP_INTERRUPT is a necessity, but then trying to use modifiers under TAPPING_TERM causes two taps. I never accidentally roll when I mean to use a modifer on a key or accidentally do a nested tap, a #define to make all rolls two taps would be a better approach for me at least and if you two are similar, fix all of these problems. The implementation isn't even hard - I had to do it for #11059 and it was probably the easiest part of the feature.

@elkowar
Copy link

elkowar commented Jun 13, 2021

i would LOVE to have this! I have some specific rolls that I constantly misstrigger my home-row mods with, that I"d love to set the tapping term a bit stricter for

@stale
Copy link

stale bot commented Sep 14, 2021

This issue has been automatically marked as stale because it has not had activity in the last 90 days. It will be closed in the next 30 days unless it is tagged properly or other activity occurs.
For maintainers: Please label with bug, in progress, on hold, discussion or to do to prevent the issue from being re-flagged.

@stale stale bot added the stale Issues or pull requests that have become inactive without resolution. label Sep 14, 2021
@stale
Copy link

stale bot commented Jan 9, 2022

This issue has been automatically closed because it has not had activity in the last 30 days. If this issue is still valid, re-open the issue and let us know.

@stale stale bot closed this as completed Jan 9, 2022
@cburroughs
Copy link

@theol0403
Copy link
Author

It looks like this has been implemented very cleanly at https://getreuer.info/posts/keyboards/achordion/index.html !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement help wanted stale Issues or pull requests that have become inactive without resolution.
Projects
None yet
Development

No branches or pull requests

7 participants