kmk_firmware icon indicating copy to clipboard operation
kmk_firmware copied to clipboard

Add `Tapping Force Hold` to HoldTap

Open ChrisChrisLoLo opened this issue 1 year ago • 8 comments

Context:

For smaller 40% boards, it is often desirable to have a key such as the backspace key double as a layer key. For example:

KC.MT(KC.BSPC, KC.LSHIFT)

However, tapping and then holding on the backspace key doesn't result in the backspace key register as being auto-pressed. This results in users needing to manually tap the backspace key or use some other method to delete sizable strings of text.

This configuration feature is one of the few outlined in the defacto home row mods article: https://precondition.github.io/home-row-mods#tapping-force-hold. Having this feature, along with Ignore Mod-Tap Interrupt and Permissive Hold can make KMK more than viable for any 40%/sub 40% layout, and could be very attrative to a growing segment of the keyboard community

Solution

Implement a Tapping Force Hold, which is implemented in QMK and ZMK. Tapping force hold effectively "auto-presses" a tapping key if it has been tapped repeatedly. Details about Tapping Force Hold can by found in the QMK docs here. It would be awesome to have this as an optional argument, especially for layouts that are dependent on this feature

ChrisChrisLoLo avatar Sep 18 '22 02:09 ChrisChrisLoLo

https://github.com/KMKfw/kmk_firmware/blob/master/docs/rapidfire.md#rapidfire combine holdtap with rapidfire.

xs5871 avatar Sep 19 '22 08:09 xs5871

Thanks for the fast response! I wasn't aware of this feature, though I seem to get pystack exhaustion when I chain it together with hold tap

Traceback (most recent call last):
  File "code.py", line 147, in <module>
  File "kmk/kmk_keyboard.py", line 440, in go
  File "kmk/kmk_keyboard.py", line 493, in _main_loop
  File "kmk/kmk_keyboard.py", line 107, in _handle_matrix_report
  File "kmk/kmk_keyboard.py", line 157, in _on_matrix_changed
  File "kmk/kmk_keyboard.py", line 188, in pre_process_key
  File "kmk/kmk_keyboard.py", line 196, in process_key
  File "kmk/keys.py", line 488, in on_release
  File "kmk/modules/holdtap.py", line 151, in ht_released
  File "kmk/modules/holdtap.py", line 201, in ht_activate_tap
  File "kmk/kmk_keyboard.py", line 208, in resume_process_key
  File "kmk/kmk_keyboard.py", line 188, in pre_process_key
  File "kmk/kmk_keyboard.py", line 194, in process_key
  File "kmk/keys.py", line 474, in on_press
  File "kmk/modules/rapidfire.py", line 61, in _rf_pressed
  File "kmk/kmk_keyboard.py", line 219, in tap_key
  File "kmk/kmk_keyboard.py", line 216, in add_key
  File "kmk/kmk_keyboard.py", line 194, in process_key
RuntimeError: pystack exhausted

is there a common fix for this? I'm using the RP2040 XIAO currently

ChrisChrisLoLo avatar Sep 19 '22 13:09 ChrisChrisLoLo

I played around with boot.py to try to increase my stack size, though it didn't seem to fix the exhaustion problem. Perhaps I didn't configure my boot.py properly? I wasn't able to easily google how to increase my stack size in circuitpython

import supervisor

supervisor.set_next_stack_limit(4096*3) # set stack to 12k in order to chain rapid fire with hold tap
supervisor.reload()

ChrisChrisLoLo avatar Sep 20 '22 01:09 ChrisChrisLoLo

We have boot.py docs that should cover it fairly well. As for what the right number is, it's hard to say. More stack = less heap, so it's just a balancing act sometimes.

kdb424 avatar Sep 20 '22 01:09 kdb424

RP2040 has more than enough RAM (let's ignore OLED bitmaps for now). On the one hand, we should improve the documentation and explicitly state that stack size reduction is only necessary on certain platforms, and isn't a strict requirement. On the other hand, it would be nice if code would run independently of the heap/stack size limitations (to a reasonable degree).

As for this issue: I tried to, but can not reproduce the stack exhaust. I'm also on RP2040, and even at 5k your example works just fine. Is your repository current?

xs5871 avatar Sep 20 '22 08:09 xs5871

Thanks for the input! Yeah, my kmk repository should be pretty current: it's only about 4 commits behind origin's master.

I don't want this to be a "go through my code and fix it" situation, but if you'd like more context as to what's in my files, here's the link for my keyboard: https://github.com/ChrisChrisLoLo/beyblock20/tree/master/firmware/beyblock20_controller. The largest disclaimer is that I have added a custom i2c scanner file, though I don't think it should tip the scales in terms of stack exhaustion or not. I also don't think it's logic is too awful, since it conforms to the Scanner interface, though I'd have to play around with it more to be certain.

It's a bit of a mess at the moment, fair warning. Perhaps I'm using too many modules? I'm not entirely sure, I'll have to experiment a bit more when I get the chance to.

As a side note, I really appreciate the support from you two! I think you're both doing awesome work, and seeing you actively help with others' issues instills a lot of trust, and makes putting time into trying KMK more than worth it :)

ChrisChrisLoLo avatar Sep 20 '22 13:09 ChrisChrisLoLo

Ohhhh, so sorry, I misunderstood. The rapidfire won't work in that case anyway, because you can never hold a tap key. What you want is a tapdance: KC.TD(KC.MT(KC.BSPC, KC.LSHIFT), KC.BSPC) for example.

By the way, if you want to contribute the I2C scanner at some point... feel free to do so.

xs5871 avatar Sep 21 '22 07:09 xs5871

Ahhhh, that makes sense, thanks! I'll try that when I get the opportunity, hopefully it won't exhaust the stack as well. I'm a bit busy this week, so I might not be able to respond immediately about this :)

With the I2C scanner, I'd love to contribute it if it's deemed worthy of going into the main branch. I mostly considered it a little hack more than anything, and I was worried that it was too opinionated or potentially clashed with any ideals/existing features that KMK has.

I'd have to write a little section as how to write up an I2C peripheral, but I'd love to add this in if others could use it

ChrisChrisLoLo avatar Sep 21 '22 12:09 ChrisChrisLoLo

Hey! Got the chance to try it!

I tried Tap Dance, and while on paper it's doing what I'd like it to do, I don't think it feels quite the same as QMK. To elaborate, tap dance makes a key like this feel a bit "sluggish" by default. For example, if I send multiple backspaces, then it's not as snappy as it was before. I played around with reducing the tapdance timings, and it can help, but I find myself in situations where it's either still a bit sluggish as opposed to tapping KC.BSPC, or the timing is so short that I find I need to consciously rapidly tap the key twice.

The other edgecase is that if I tap backspace twice, I expect 2 characters to be backspaced, though only one character will get backspaced.

While I do feel though that Tapping Force Hold would be a useful addition to KMK given the above edgecases with current features, the friendly support thus far is much appreciated.

ChrisChrisLoLo avatar Sep 25 '22 12:09 ChrisChrisLoLo

feel a bit "sluggish" by default.

I would love to make suggestions or improvements, but that's too subjective a description for me to know what you mean by that. If you can put that into numbers, diagramms, or even a haiku that gives an objective description of the problem, we can discuss that further.

The other edgecase is that if I tap backspace twice, I expect 2 characters to be backspaced, though only one character will get backspaced.

That's actually a good point; I was about to suggest making the backspace on the second tap a sequence of two backspaces, but after a quick test, that doesn't "hold", i.e. repeat the key. We can look into that.

xs5871 avatar Sep 27 '22 08:09 xs5871

Does this help with the perceived lag: KC.TD(KC.MT(KC.BSPC, KC.LSHIFT), KC.BSPC, tap_time=0)

xs5871 avatar Sep 27 '22 18:09 xs5871

It does help a bit with the perceived lag, though I think it falls under what I mentioned previously

or the timing is so short that I find I need to consciously rapidly tap the key twice.

The problem I'm having with a low tap time is that I need to be very quick in hitting the key quickly twice in order for me to be able to hold it. With QMK, force tap hold provides enough of a window where I can both have a responsive keypress (no need to wait for tap dance timings) as well as a generous enough of a window where I can press it repeatedly without trying to change my typing speed (i.e. race to get two key presses before the tap time expires). I'd imagine the logic in QMK works by sending out the first key press as a tap immediately (rather than wait, and then send like TapDance) and then have a window of time that it waits on to send additional keystrokes if it gets tapped again. I don't know if such behavior can be constructed with existing KMK constructs

ChrisChrisLoLo avatar Sep 29 '22 03:09 ChrisChrisLoLo

The other edgecase is that if I tap backspace twice, I expect 2 characters to be backspaced, though only one character will get backspaced.

This is not an edge case, it's a terribly annoying thing that happens all the time. I used to have multiple MT and LT in QMK and I can't replicate them in KMK because of this :disappointed:

prokopiff avatar Sep 30 '22 10:09 prokopiff