KMKfw / kmk_firmware

Clackety Keyboards Powered by Python
https://kmkfw.zulipchat.com
Other
1.32k stars 458 forks source link

Add 'unhold' keycode to HoldTap key config (fire keycode when uninterrupted 'hold' is released), so one can combine homerow mods and autoshift #954

Closed lxyd closed 3 months ago

lxyd commented 3 months ago

When reviewing, please view commits one-by-one for better understandig.

Backward compatibility is preserved: old code using holdtap module will continue working without changes.

  1. First two commits are just refactoring for future convinience. Reasons for refactoring are: a) 'activated' field values are inconsistent: serving repeat function it can contain 'HOLD_TIMEOUT' and 'INTERRUPTED' values while actual key state is 'released', and for 'tap' action in similar case it separates released state with 'RELEASED' value; b) keycode to be inactivated and its repeatetivenes are calculated at the ht_released although these values were already calculated earlier (when correspondig keycode was activated), so there is some logic duplication

This complicates the module to the extent where adding new functionality is quite difficult and error prone. My idea was to store active keycode and repeatetivenes inside the state object and make the 'activated' field more consistent (after refactoring it just reflects key state as it is)

  1. Third commit is adding 'unhold' behaviour itself. The most obvious application of this functionality is to implement combination of autoshift and homerow mods. For example, let's combine letter 'a' and LCTL:
A_CTL = KC.HT(KC.A, KC.LCTL, unhold = KC.LSFT(KC.A), repeat = HoldTapRepeat.TAP | HoldTapRepeat.UNHOLD, tap_interrupted=True, prefer_hold=True)

So, short press produces 'a' letter, long press produces 'A' letter, and a+s produces ctrl+s hotkey.

  1. Though there is one caveat addressed in the fourth commit. Consider assigning 'g' with autoshift to homerow LGUI mod:
    G_GUI = KC.HT(KC.G, KC.LGUI, unhold = KC.LSFT(KC.G), repeat = HoldTapRepeat.TAP | HoldTapRepeat.UNHOLD, tap_interrupted=True, prefer_hold=True)

    In such a case, long-pressing it produces the following event sequence: lgui press/release, than shift+g press/release. But Windows and Gnome (at least) have special meaning for LGUI tap so our key will behave quite naughty.

Good solution to this problem is to hold down activating the actual 'hold' keycode until some other key is pressed (i.e. 'hold' is 'interrupted'). But this breaks current holdtap usecases like, for example, sharing q+esc on a single button.

In order to preserve backward compatibility, this behaviour is made customizable via additional 'dont_hold_until_interrupted' flag. So both the following keys will work as expected:

G_GUI = KC.HT(KC.G, KC.LGUI, unhold = KC.LSFT(KC.G), repeat = HoldTapRepeat.TAP | HoldTapRepeat.UNHOLD, tap_interrupted=True, prefer_hold=True, dont_hold_until_interrupted=True)
Q_ESC = KC.HT(KC.Q, KC.ESC, repeat = HoldTapRepeat.ALL, tap_interrupted=True, prefer_hold=False)
lxyd commented 3 months ago

Found a bug, will fix and than reopen