kmk_firmware
kmk_firmware copied to clipboard
Add `Tapping Force Hold` to HoldTap
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
https://github.com/KMKfw/kmk_firmware/blob/master/docs/rapidfire.md#rapidfire combine holdtap with rapidfire.
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
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()
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.
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?
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 :)
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.
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
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.
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.
Does this help with the perceived lag:
KC.TD(KC.MT(KC.BSPC, KC.LSHIFT), KC.BSPC, tap_time=0)
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
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: