jtroo / kanata

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

Feature request: Interception as a seperated OS #1160

Open shirok1 opened 1 month ago

shirok1 commented 1 month ago

Is your feature request related to a problem? Please describe.

I'm using a JIS keyboard aside from my ANSI builtin keyboard, and I've set my system-level keyboard layout to ANSI. ScanCode (raw keyboard input, positional) will be converted to OsCode (region-agnoistic) in Windows according to keyboard layout (ANSI or JIS). Currently it is also done in kanata internaly, so that keycodes on Windows can be unified as OsCode. However, this model also killed some interesting applications (e.g. using JIS-only keys to switch layers).

Describe the solution you'd like.

I would like Interception ScanCode promoted to be a seperate OsCode impl (in another word, consider Interception mode as a seperated OS, apart from hook approaches).

Describe alternatives you've considered.

I can not think of a alternative, since it's clear that the convertion between key codes is constraining the target.

Additional context

I recently did some reverse engineering on the Interception driver, turns out it is quite simple. We can simply make a clean room impl of it refering following sources:

https://github.com/baohaojun/system-config/blob/master/gcode/Ctrl2Cap/SYS/ctrl2cap.c

https://github.com/minglinchen/WinKernelDev/blob/master/ctrl2cap/ctrl2cap.c

I'm wondering why no one have make one, since the Interception is buggy, and is banned in some online game. Is it for the driver signature?

jtroo commented 1 month ago

I'm wondering why no one have make one, since the Interception is buggy, and is banned in some online game. Is it for the driver signature?

I would guess that's part of it; another one might be that it exists already and is "good enough" for most use cases. An alternative would be welcome; today I mostly use non-Interception on Windows so I haven't had the motivation.

Currently it is also done in kanata internaly, so that keycodes on Windows can be unified as OsCode. However, this model also killed some interesting applications (e.g. using JIS-only keys to switch layers)

I haven't played around with JIS-only keys at all; does the Interception driver handle them correctly in the first place? I know it's missing proper handling of some keys. If it does, it should be feasible to add code that handles the JIS keys and differentiate it to a unique OsCode, which seems to me would still enable the use case.

shirok1 commented 1 month ago

Does the Interception driver handle them correctly in the first place?

I've succeeded in mapping CONVERT and NOCONVERT (two keys around space key in JIS) to LEFT and RIGHT with capsicain. It also proved my thought that ScanCode is more like a raw keyboard input.

shirok1 commented 1 month ago

BTW, capsicain impl an exit key chord (esc+x), but kanata_wintercept do not (at least this is what I tested out, maybe I did something wrong). Would separating logics would give a chance to it?

jtroo commented 1 month ago

The exit chord is documented here: https://github.com/jtroo/kanata/blob/main/docs/config.adoc#forcefully-exit-kanata

jtroo commented 1 month ago

Also, if you can find which scancode matches convert/noconvert, you should be able to map it here in some way https://github.com/jtroo/kanata/blob/main/src/oskbd/windows/scancode_to_usvk.rs

shirok1 commented 1 month ago

Also, if you can find which scancode matches convert/noconvert, you should be able to map it here in some way https://github.com/jtroo/kanata/blob/main/src/oskbd/windows/scancode_to_usvk.rs

Unfortunatelly CONVERT and NOCONVERT are opted out, see https://github.com/jtroo/kanata/blob/620d82fa04255b34750e9b6b1459381bcec0dd1d/src/oskbd/windows/scancode_to_usvk.rs#L106

2024-07-27T17:00:59.110969+08:00 [DEBUG] (1) kanata_state_machine::kanata::windows::interception: got stroke Keyboard { code: Convert, state: DOWN, information: 0 }
2024-07-27T17:00:59.1115652+08:00 [DEBUG] (1) kanata_state_machine::kanata::windows::interception: could not map code to oscode
2024-07-27T17:00:59.1812403+08:00 [DEBUG] (1) kanata_state_machine::kanata::windows::interception: got stroke Keyboard { code: Convert, state: UP, information: 0 }
2024-07-27T17:00:59.1818902+08:00 [DEBUG] (1) kanata_state_machine::kanata::windows::interception: could not map code to oscode

The exit chord is documented here: https://github.com/jtroo/kanata/blob/main/docs/config.adoc#forcefully-exit-kanata

The chord works in hook versions but not Interception version.

jtroo commented 1 month ago

Would you be able to test if https://github.com/jtroo/kanata/pull/1166 works?

shirok1 commented 1 month ago

Yes it works! I can see it successfully switching layers in my following profile:

(defsrc
  1    2    3    4    5    6    7    8    9    0    -    =    yen  bspc
  tab  q    w    e    r    t    y    u    i    o    p    [    ]
  lctl a    s    d    f    g    h    j    k    l    ;    '         ret
  lsft z    x    c    v    b    n    m    ,    .    /    ro   up   rsft
  caps lmet lalt ncnv         spc         conv ralt      left down rght
)

(deflayer nojis
  1    2    3    4    5    6    7    8    9    0    -    =    grv  bspc 
  tab  q    w    e    r    t    y    u    i    o    p    [    ] 
  @lct a    s    d    f    g    h    j    k    l    ;    '         ret 
  lsft z    x    c    v    b    n    m    ,    .    /    esc  up   rsft 
  caps lmet lalt @ncn         spc         conv ralt      left down rght
)

(deflayer l1
  f1   f2   f3   f4   f5   f6   f7   f8   f9   f10  f11  f12  _    del 
  _    q    w    e    r    t    _    _    _    _    _    _    _ 
  _    a    s    d    f    g    left down up   rght _    _         _  
  @cas z    x    c    v    b    _    _    _    _    _    _    _    _   
  _    _    _    _            @spl        lrld _         @wsl _    @wsr
)

(defalias
  lct (tap-hold-press 200 200 esc lctl)
  ncn (layer-while-held l1)
  ;; ...
)

However, there are two cool keys left in JIS layout that I also wanted (but lower priority anyway, since they are on the side), the YEN key and RO (capsician calls it ABNT_C1) key.

windows_key_tester.exe and windows_key_tester_winIOv2.exe see multiple key strokes even if I'm hitting YEN once:

# hitting YEN
18:42:40 [INFO] 0, 256, false, InputEvent { code: 255, up: false }
18:42:40 [INFO] 0, 256, true, InputEvent { code: 39, up: false }
18:42:40 [INFO] 0, 257, false, InputEvent { code: 255, up: true }
18:42:40 [INFO] 0, 257, true, InputEvent { code: 39, up: true }
# hitting RO
18:42:44 [INFO] 0, 256, false, InputEvent { code: 193, up: false }
18:42:44 [INFO] 0, 257, false, InputEvent { code: 193, up: true }

windows_key_tester_interception.exe recognize these two keys as ESC

# hitting YEN
18:44:28 [INFO] got stroke Keyboard { code: Esc, state: DOWN, information: 0 }: num: 27
18:44:28 [INFO] got stroke Keyboard { code: Esc, state: UP, information: 0 }: num: 27
# hitting RO
18:44:29 [INFO] got stroke Keyboard { code: Esc, state: DOWN, information: 0 }: num: 27
18:44:29 [INFO] got stroke Keyboard { code: Esc, state: UP, information: 0 }: num: 27
# hitting the actual ESC
18:44:30 [INFO] got stroke Keyboard { code: Esc, state: DOWN, information: 0 }: num: 27
18:44:30 [INFO] got stroke Keyboard { code: Esc, state: UP, information: 0 }: num: 27

Meanwhile, capsician works just fine:

 [7d 1=   YEN >        `^ ] [M:     T:     D:      ]   --  `^ {`^ #0}
 [7d 0=   YEN >        `v ] [M:     T:     D:      ]   --  `v {`v #1}
 [7d 1=   YEN >        `^ ] [M:     T:     D:      ]   --  `^ {`^ #0} (tap)
 [7d 0=   YEN >        `v ] [M:     T:     D:      ]   --  `v {`v #1}
 [7d 1=   YEN >        `^ ] [M:     T:     D:      ]   --  `^ {`^ #0} (tap)
 [7d 0=   YEN >        `v ] [M:     T:     D:      ]   --  `v {`v #1}
 [7d 1=   YEN >        `^ ] [M:     T:     D:      ]   --  `^ {`^ #0} (tap)
 [7d 0=   YEN >        `v ] [M:     T:     D:      ]   --  `v {`v #1}
 [7d 1=   YEN >        `^ ] [M:     T:     D:      ]   --  `^ {`^ #0} (tap)
 [73 0= ABNT_C1 >     RSHFv ] [M:  10 T:     D:      ]   --  RSHFv {RSHFv #1}
 [73 1= ABNT_C1 >     RSHF^ ] [M:     T:  10 D:      ]   --  RSHF^ {RSHF^ #0} (tap)
 [73 0= ABNT_C1 >     RSHFv ] [M:  10 T:  10 D:      ]   --  RSHFv {RSHFv #1}
 [73 1= ABNT_C1 >     RSHF^ ] [M:     T:  10 D:      ]   --  RSHF^ {RSHF^ #0} (tap)
 [73 0= ABNT_C1 >     RSHFv ] [M:  10 T:  10 D:      ]   --  RSHFv {RSHFv #1}
 [73 1= ABNT_C1 >     RSHF^ ] [M:     T:  10 D:      ]   --  RSHF^ {RSHF^ #0} (tap)
 [73 0= ABNT_C1 >     RSHFv ] [M:  10 T:  10 D:      ]   --  RSHFv {RSHFv #1}
 [73 1= ABNT_C1 >     RSHF^ ] [M:     T:  10 D:      ]   --  RSHF^ {RSHF^ #0} (tap)
 [73 0= ABNT_C1 >     RSHFv ] [M:  10 T:  10 D:      ]   --  RSHFv {RSHFv #1}
 [73 1= ABNT_C1 >     RSHF^ ] [M:     T:  10 D:      ]   --  RSHF^ {RSHF^ #0} (tap)
 [73 0= ABNT_C1 >     RSHFv ] [M:  10 T:  10 D:      ]   --  RSHFv {RSHFv #1}
 [73 1= ABNT_C1 >     RSHF^ ] [M:     T:  10 D:      ]   --  RSHF^ {RSHF^ #0} (tap)
 [73 0= ABNT_C1 >     RSHFv ] [M:  10 T:  10 D:      ]   --  RSHFv {RSHFv #1}
 [73 1= ABNT_C1 >     RSHF^ ] [M:     T:  10 D:      ]   --  RSHF^ {RSHF^ #0} (tap)
shirok1 commented 1 month ago

Their location on my NuType F1 JIS:

image

FYI they are almost the cheapest low profile mechanical keyboards you can find in China. You can grab one used for only like 18 USD (JIS models only, ANSI model ls are priced 30 USD and more so not a economical choice)

jtroo commented 1 month ago

Looks like the Rust interception library I vendored doesn't handle unknown keys well. Can probably just fill in the gaps with arbitrary names similar to the KEY_N naming scheme keys/mod.rs.

https://github.com/jtroo/kanata/blob/main/interception/src/scancode.rs