jtroo / kanata

Improve keyboard comfort and usability with advanced customization
GNU Lesser General Public License v3.0
3.23k stars 131 forks source link

chordsv2 activation does not trigger early interruption of `tap-hold-press|release` #1110

Closed newsve closed 5 months ago

newsve commented 5 months ago

Requirements

Describe the bug

Best use case to understand this issue is control-backspace or to quickly delete words backward.

With tap-hold-press you should be able to rapidly press control, in our case caps, and right afterwards, and before the hold timeout expired, bspc. This works and feels fast and natural. Now do this with the chord we defined below, j and k. This doesn't work before the hold timeout has expired, only after, which feels kinda sluggish because you have wait a bit till you hit j and k.

Relevant kanata config

(defcfg
  concurrent-tap-hold yes
)

(defsrc
  caps j k bspc
)

(deflayer one
  (tap-hold-press 0 200 esc lctl) j k bspc
)

(defchordsv2-experimental
  (j k) bspc 75 first-release ()
)

To Reproduce

  1. First, try caps and bspc to feel how it should be
  2. Then, try caps and chord j and k to see the difference and all very quickly, otherwise you won't notice

Expected behavior

same as caps and bspc

Kanata version

1.7.0-prelease

Debug logs

I did 1 and then 2 (from "To Reproduce"):

13:56:51.1685 [INFO] Starting kanata proper
13:56:51.1689 [INFO] You may forcefully exit kanata by pressing lctl+spc+esc at any time. These keys refer to defsrc input, meaning BEFORE kanata remaps keys.
13:56:51.1828 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_ENTER (13), value: Release }
13:56:51.1852 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_ENTER (13), value: Release }
13:56:56.6023 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_CAPSLOCK (20), value: Press }
13:56:56.6034 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_CAPSLOCK (20), value: Press }
13:56:56.6543 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_BACKSPACE (8), value: Press }
13:56:56.6558 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_BACKSPACE (8), value: Press }
13:56:56.6562 [DEBUG] (2) kanata_state_machine::kanata: key press     LCtrl
13:56:56.6574 [DEBUG] (2) kanata_state_machine::kanata: key press     BSpace
13:56:56.7293 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_BACKSPACE (8), value: Release }
13:56:56.7303 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_BACKSPACE (8), value: Release }
13:56:56.7306 [DEBUG] (2) kanata_state_machine::kanata: key release   BSpace
13:56:56.8237 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_CAPSLOCK (20), value: Release }
13:56:56.8244 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_CAPSLOCK (20), value: Release }
13:56:56.8247 [DEBUG] (2) kanata_state_machine::kanata: key release   LCtrl
13:56:59.8918 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_CAPSLOCK (20), value: Press }
13:56:59.8929 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_CAPSLOCK (20), value: Press }
13:56:59.9216 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_J (74), value: Press }
13:56:59.9233 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_J (74), value: Press }
13:56:59.9336 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_K (75), value: Press }
13:56:59.9355 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_K (75), value: Press }
13:56:59.9356 [DEBUG] (2) kanata_state_machine::kanata: key press     BSpace
13:57:00.0096 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_K (75), value: Release }
13:57:00.0114 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_K (75), value: Release }
13:57:00.0197 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_J (74), value: Release }
13:57:00.0212 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_J (74), value: Release }
13:57:00.0438 [DEBUG] (1) kanata_state_machine::kanata::windows::llhook: event loop: KeyEvent { code: KEY_CAPSLOCK (20), value: Release }
13:57:00.0477 [DEBUG] (2) kanata_state_machine::kanata: process recv ev KeyEvent { code: KEY_CAPSLOCK (20), value: Release }
13:57:00.0482 [DEBUG] (2) kanata_state_machine::kanata: key press     Escape
13:57:00.0538 [DEBUG] (2) kanata_state_machine::kanata: key release   BSpace
13:57:00.0576 [DEBUG] (2) kanata_state_machine::kanata: key release   Escape

Operating system

Windows 11

Additional context

No response

newsve commented 5 months ago

Didn't realize, we face ofc the same issue for tap-hold-release, which is used for the HRMs, use cases there are similar, e.g. a fast Shift-Enter will mostly fail when mapped similarly (f is shift and chording k and l is enter). Hence, I'll edit the title.

Edit: I'd assume all tap-hold derivates are affected, I've tested -press and -release.

jtroo commented 5 months ago

One thing to note is that chordsv2 intentionally does not wait for tap-hold activations today. So to make thing happen in your desired sequence does take some hacking to get working even with the fix in the PR.

    (defcfg concurrent-tap-hold yes)
    (defsrc caps j k bspc)
    (deflayer one (tap-hold-release 0 200 esc lctl) j k bspc)
    (defvirtualkeys bspc bspc)
    (defchordsv2-experimental
      (j k) (multi (on-press press-vkey bspc) (on-release release-vkey bspc)) 75 first-release ()
    )

I suppose this behaviour could be turned into a configuration if there is enough desire for it.

newsve commented 5 months ago

Thanks for getting into this! Now, it works really well and hitting caps + j + k is extremely satisfying!

chordsv2 intentionally does not wait for tap-hold activations today

Ah ok, makes sense since the output of of chords isn't a real press and release. The fix and simulating a press and release is prretty clever!

I suppose this behaviour could be turned into a configuration if there is enough desire for it.

You mean shortening the (multi... to something like (press-release-vkey bspc)?

In this context, does it make sense to let the chords v2 to output always a press and release? Or better not, I actually like the current solution being pretty explicit and low-level and giving more flexibility for more edge cases.

jtroo commented 5 months ago

The configuration I was thinking of would be something like: chordsv2-pause-with-pending-actions so that action activations are paused by pending actions, which would be

newsve commented 5 months ago

chordsv2-pause-with-pending-actions as keyword sounds good to me!

FWIW and maybe relevant, just tried to rapidly backspace in Edge (the Windows browser) and it duplicated the tab, because in Edge the shortcut shift ctrl k is duplicating a tab and I have a tap-hold-release 0 200 f rsft and a tap-hold-release 0 200 k rmet. (edited)

It didn't matter how fast and/or precise I control-backspaced, I always duplicated a tab in Edge. I assume that shift ctrl k seems trigger faster than ctrl backspace for whatever reason.

newsve commented 5 months ago

Here a test snippet (edited, I had the old version pasted before):

(defcfg
  concurrent-tap-hold yes
)

(defsrc
  caps j k bspc
)

(defalias
  j (tap-hold-press 0 200 j rsft)
  k (tap-hold-release 0 200 k rmet)
)

(deflayer one
  (tap-hold-press 0 200 esc lctl) @j @k bspc
)

(defvirtualkeys
  bspc bspc
)

(defchordsv2-experimental
  (j k) (multi (on-press press-vkey bspc) (on-release release-vkey bspc)) 75 first-release ()
)
newsve commented 5 months ago

Wait, it is way more nuanced, I just realized that I have a tap-hold-press on j, once I change that to release, the tab doesn't duplicate that often anymore, it works then quite reliable. Once I use not caps but e.g. a where I have a tao-hold-release 0 200 a lctl I get again this behavior, updated snippet:

(defcfg
  concurrent-tap-hold yes
)

(defsrc
  caps j k bspc
)

(defalias
  a (tap-hold-release 0 200 a lctl )
  j (tap-hold-release 0 200 j rsft)
  k (tap-hold-release 0 200 k rmet)
)

(deflayer one
  (tap-hold-press 0 200 esc lctl) @a @j @k bspc
)

(defvirtualkeys
  bspc bspc
)

(defchordsv2-experimental
  (j k) (multi (on-press press-vkey bspc) (on-release release-vkey bspc)) 75 first-release ()
)
newsve commented 5 months ago

I replaced j and k with...

  j (tap-hold-except-keys 0 200 j rsft (k))
  k (tap-hold-except-keys 0 200 k rmet (j))

...and now it does emit ctrl a on whena and j and k are pressed quickly.

newsve commented 5 months ago

I'm a so stupid, forget everything I wrote, I had set chords-v2-min-idle-experimental to 100, hence all those weird behaviors. If I set it back 5, all works fine again. I need to find the sweet spot though where fast typing still works too...