KarsMulder / evsieve

A utility for mapping events from Linux event devices.
GNU General Public License v2.0
199 stars 11 forks source link

caps2esc behaviour possible? #8

Closed fhill2 closed 2 years ago

fhill2 commented 2 years ago

Thank you for this awesome software, been using it for setting keybinds for all my VM inputs at once.
Is caps2esc behaviour possible using esieve, or is it a job for python evdev lib?

in caps2esc: CtrlDown CtrlUp --> Esc CtrlDown d CtrlUp --> Ctrl-d Esc --> grave

KarsMulder commented 2 years ago

There is no easy way to do this, but by getting creative with maps and toggles, it is possible to mostly emulate this behaviour:

evsieve --input  /dev/input/by-id/keyboard grab domain=in persist=reopen \
        --map    key:esc key:grave \
        `# The following list are keys that will not turn the capslock into a ctrl key,` \
        `# so e.g. ctrl+capslock is turned into ctrl+esc instead of ctrl+ctrl.` \
        --map    key:leftctrl   @out \
        --map    key:rightctrl  @out \
        --map    key:leftshift  @out \
        --map    key:rightshift @out \
        --map    key:leftalt    @out \
        --map    key:rightalt   @out \
        --map    key:leftmeta   @out \
                                     \
        `# The main machinery: on capslock press, activate a map that turns all key events` \
        `# into a leftctrl + that event. If no leftctrl event was generated that way, then` \
        `# a capslock release is turned into an esc press+release.` \
        --toggle key:capslock "" @caps-down @ctrl-down mode=passive \
        --toggle @in          "" @caps-down @caps-down mode=consistent \
        --hook   key:capslock:1 toggle=:2 \
        --hook   key:capslock:0 toggle=:1 \
        --map    key:capslock:0@caps-down key:esc:1@out key:esc:0@out \
        --block  key:capslock \
        --map    key@caps-down key:leftctrl "" \
        --hook   key:leftctrl@caps-down toggle=:3 \
                                                  \
        `# The --map is necessary to make the --merge properly merge real leftctrl` \
        `# events with virtual leftctrl events.` \
        --map    "" @out \
        --merge  \
        --output

One problem left with the above script is that after pressing capslock+some key (e.g. Caps+A) and thereby generating Ctrl+A, the Ctrl key stays down until the "A" key is released, even if the capslock key is released earlier.

fhill2 commented 2 years ago

There is no easy way to do this, but by getting creative with maps and toggles, it is possible to mostly emulate this behaviour:

evsieve --input  /dev/input/by-id/keyboard grab domain=in persist=reopen \
        --map    key:esc key:grave \
        `# The following list are keys that will not turn the capslock into a ctrl key,` \
        `# so e.g. ctrl+capslock is turned into ctrl+esc instead of ctrl+ctrl.` \
        --map    key:leftctrl   @out \
        --map    key:rightctrl  @out \
        --map    key:leftshift  @out \
        --map    key:rightshift @out \
        --map    key:leftalt    @out \
        --map    key:rightalt   @out \
        --map    key:leftmeta   @out \
                                     \
        `# The main machinery: on capslock press, activate a map that turns all key events` \
        `# into a leftctrl + that event. If no leftctrl event was generated that way, then` \
        `# a capslock release is turned into an esc press+release.` \
        --toggle key:capslock "" @caps-down @ctrl-down mode=passive \
        --toggle @in          "" @caps-down @caps-down mode=consistent \
        --hook   key:capslock:1 toggle=:2 \
        --hook   key:capslock:0 toggle=:1 \
        --map    key:capslock:0@caps-down key:esc:1@out key:esc:0@out \
        --block  key:capslock \
        --map    key@caps-down key:leftctrl "" \
        --hook   key:leftctrl@caps-down toggle=:3 \
                                                  \
        `# The --map is necessary to make the --merge properly merge real leftctrl` \
        `# events with virtual leftctrl events.` \
        --map    "" @out \
        --merge  \
        --output

One problem left with the above script is that after pressing capslock+some key (e.g. Caps+A) and thereby generating Ctrl+A, the Ctrl key stays down until the "A" key is released, even if the capslock key is released earlier.

Thank you, amazing! I tested and it's working exactly like caps2esc (apart from the problem you described)

This also works for Ctrl+LeftMouseClick too. caps2esc had problems with supporting Caps+LeftMouseClick --> Ctrl+LeftMouseClick, and had to mux the mouse & input to get this desired behaviour.

I appreciate this, having all my desired key remaps and persistent virtual devices for qemu in 1 tool really does simplify things.

fhill2 commented 2 years ago

After testing again I've realized this doesn't support Ctrl+LeftMouseClick and Ctrl+RightMouseClick

How would you approach this? Been trying and can't quite figure it out. Here's what I've got so far:

evsieve --input  /dev/input/by-id/keyboard grab domain=in persist=reopen \
        --input  /dev/input/by-id/mouse grab domain=in persist=reopen \
        --map    key:esc key:grave \
        `# The following list are keys that will not turn the capslock into a ctrl key,` \
        `# so e.g. ctrl+capslock is turned into ctrl+esc instead of ctrl+ctrl.` \
        --map    key:leftctrl   @out \
        --map    key:rightctrl  @out \
        --map    key:leftshift  @out \
        --map    key:rightshift @out \
        --map    key:leftalt    @out \
        --map    key:rightalt   @out \
        --map    key:leftmeta   @out \
                                     \
        `# The main machinery: on capslock press, activate a map that turns all key events` \
        `# into a leftctrl + that event. If no leftctrl event was generated that way, then` \
        `# a capslock release is turned into an esc press+release.` \
        --toggle key:capslock "" @caps-down @ctrl-down mode=passive \
        --toggle @in          "" @caps-down @caps-down mode=consistent \
        --hook   key:capslock:1 toggle=:2 \
        --hook   key:capslock:0 toggle=:1 \
        --map    key:capslock:0@caps-down key:esc:1@out key:esc:0@out \
        --map    key@caps-down key:leftctrl "" \
        --block  key:capslock \
        --hook   key:leftctrl@caps-down toggle=:3 \
                                                  \
        `# The --map is necessary to make the --merge properly merge real leftctrl` \
        `# events with virtual leftctrl events.` \
        --map    "" @out \
        --merge  \
        --output

Modifications: -block key:capslock moved 1 line down --input dev/input/by-id/mouse grab domain=in persist=reopen below keyboard --input

KarsMulder commented 2 years ago

Undo moving the --block line and then add a --map btn@caps-down key:leftctrl "" line after --map key@caps-down key:leftctrl "":

evsieve --input  /dev/input/by-id/keyboard grab domain=in persist=reopen \
        --input  /dev/input/by-id/mouse grab domain=in persist=reopen \
        --map    key:esc key:grave \
        `# The following list are keys that will not turn the capslock into a ctrl key,` \
        `# so e.g. ctrl+capslock is turned into ctrl+esc instead of ctrl+ctrl.` \
        --map    key:leftctrl   @out \
        --map    key:rightctrl  @out \
        --map    key:leftshift  @out \
        --map    key:rightshift @out \
        --map    key:leftalt    @out \
        --map    key:rightalt   @out \
        --map    key:leftmeta   @out \
                                     \
        `# The main machinery: on capslock press, activate a map that turns all key events` \
        `# into a leftctrl + that event. If no leftctrl event was generated that way, then` \
        `# a capslock release is turned into an esc press+release.` \
        --toggle key:capslock "" @caps-down @ctrl-down mode=passive \
        --toggle @in          "" @caps-down @caps-down mode=consistent \
        --hook   key:capslock:1 toggle=:2 \
        --hook   key:capslock:0 toggle=:1 \
        --map    key:capslock:0@caps-down key:esc:1@out key:esc:0@out \
        --block  key:capslock \
        --map    key@caps-down key:leftctrl "" \
        --map    btn@caps-down key:leftctrl "" \
        --hook   key:leftctrl@caps-down toggle=:3 \
                                                  \
        `# The --map is necessary to make the --merge properly merge real leftctrl` \
        `# events with virtual leftctrl events.` \
        --map    "" @out \
        --merge  \
        --output

The issue is that mouse buttons are called btn:left, btn:right or something. Because they do not start with key:, the map key@caps-down does not apply to them. You need a second map to catch the btn-type events as well.

I agree that this is highly confusing because both key presses and mouse clicks are of type EV_KEY according to the evdev protocol, but the alternative may be even worse. When stabilising type-level maps I had to either choose "--map key does not catch all EV_KEY events" or "--map key applies to btn:something despite its name not starting with key"; both behaviours have the potential to surprise an unsuspecting user.

In the end, I decided that the first option had the least insane behaviour and as such --map key does not catch btn:something. Whether this was the right decision I still do not know.

KarsMulder commented 2 years ago

... the longer I think about it, the crazier the decision do make --map key not match btn:* is starting to look, taking into account that the documentation pretty consistently mentions that key:something means "an event with type EV_KEY and ...".

In fact, it looks like I haven't even documented that --map key does not match btn:* other than the generic sentence "Any part that is not specified will be interpreted to mean "any" for source events." about the key format [which may or may not imply that key means key:*:*@* and therefore not btn:*:*@*.]

Taking that into account, it could be argued that the documentation stating that "key:" is interpreted as "with type EV_KEY..." means that either the current implementation or the current documentation is broken. I wonder what I should do about this...

fhill2 commented 2 years ago

Testing the example you provided above:

--print before --output shows:

Event:  type:code = key:leftctrl   value = 1 (down)    domain = out
Event:  type:code = btn:left       value = 1 (down)    domain = out

yet while evsieve is running: caps+leftclick, links on github, youtube etc don't open in a new tab like default. using physical left-ctrl right-ctrl manually then mouse clicking opens these links in a new tab.

Does this work for you in a web browser? It might have something to do with the virtual key being delayed until the next key, although I moved --block down 1 line and the issue is still there.

On my system, using this browser based key tester: caps-ctrl is registered after the mouse click. using physical left-ctrl right-ctrl, then manually clicking, those keys are registered before the click.

Also tested passed through to a windows vm, same behaviour exists.


Yes using key, and it actually mean key and btn makes less sense imo.


When reading "Any part that is not specified will be interpreted to mean "any" for source events" in the documentation, to me this implies key means key:*:*@* and therefore not btn:*:*@*

ie I was not expecting --map key to match btn events. I simply missed this line due to personal error.

I wouldn't say this is highly confusing. The documentation is very well written and easy to follow along, there is a lot you can do with evsieve, a lot to cover, and therefore a slight learning curve. For the amount of configuration possible, the implementation is very clean imo, and easy to understand through --print, trial and error.

The domains, event filters, sequential processing being used directly from the shell is powerful.

fhill2 commented 2 years ago

I think I've found what's causing the issues with ctrl+leftclick

For comparison, here is a xmodmap+xcape script that provides similar caps2esc behaviour: After running this, Ctrl+Click within browsers, links open in a new tab etc, everything working.

xmodmap -e "clear Lock"
# Set real Caps Lock key to present as (left) control
xmodmap -e "keycode 66 = Control_R"
# Set real Escape key to present as Caps Lock
xmodmap -e "keycode 9 = F14"
# Make a fake key to hold the Escape keysym, so xcape can use it
xmodmap -e "keycode 255 = Escape"
# Make Caps_Lock and Control_L work as one would expect
xmodmap -e "add Control = Control_R"
xcape -e '#66=Escape'

Here are both outputs of xev for (1) xmodmap+xcape (2) evsieve example above

(1) xmodmap+xcape

KeyPress event, serial 33, synthetic NO, window 0x2000001,
    root 0x721, subw 0x0, time 2144143, (1030,1158), root:(4870,1158),
    state 0x0, keycode 66 (keysym 0xffe4, Control_R), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

ButtonPress event, serial 33, synthetic NO, window 0x2000001,
    root 0x721, subw 0x0, time 2144814, (1030,1158), root:(4870,1158),
    state 0x4, button 1, same_screen YES

ButtonRelease event, serial 33, synthetic NO, window 0x2000001,
    root 0x721, subw 0x0, time 2144934, (1030,1158), root:(4870,1158),
    state 0x104, button 1, same_screen YES

KeyRelease event, serial 33, synthetic NO, window 0x2000001,
    root 0x721, subw 0x0, time 2145552, (1030,1158), root:(4870,1158),
    state 0x4, keycode 66 (keysym 0xffe4, Control_R), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

(2) evsieve example above

KeyPress event, serial 33, synthetic NO, window 0x1c00001,
    root 0x721, subw 0x0, time 2050890, (2112,1074), root:(5952,1074),
    state 0x0, keycode 37 (keysym 0xffe3, Control_L), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

ButtonPress event, serial 33, synthetic NO, window 0x1c00001,
    root 0x721, subw 0x0, time 2050890, (2112,1074), root:(5952,1074),
    state 0x4, button 1, same_screen YES

KeyRelease event, serial 33, synthetic NO, window 0x1c00001,
    root 0x721, subw 0x0, time 2051003, (2112,1074), root:(5952,1074),
    state 0x104, keycode 37 (keysym 0xffe3, Control_L), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

ButtonRelease event, serial 33, synthetic NO, window 0x1c00001,
    root 0x721, subw 0x0, time 2051003, (2112,1074), root:(5952,1074),
    state 0x100, button 1, same_screen YES

So, the solution could be changing the release order from CtrlUp BtnUp to BtnUp CtrlUp. will give it a try.

KarsMulder commented 2 years ago

Issue confirmed. Strangely enough, the issue still happens for me on Firefox even if I were to make the CtrlUp happen after the BtnUp using the following less-than-elegant solution:

evsieve  --input  /dev/input/by-id/keyboard grab domain=in persist=reopen \
        --input  /dev/input/by-id/mouse grab domain=in persist=reopen \
        --map    key:esc key:grave \
        `# The following list are keys that will not turn the capslock into a ctrl key,` \
        `# so e.g. ctrl+capslock is turned into ctrl+esc instead of ctrl+ctrl.` \
        --map    key:leftctrl   @out \
        --map    key:rightctrl  @out \
        --map    key:leftshift  @out \
        --map    key:rightshift @out \
        --map    key:leftalt    @out \
        --map    key:rightalt   @out \
        --map    key:leftmeta   @out \
                                     \
        `# The main machinery: on capslock press, activate a map that turns all key events` \
        `# into a leftctrl + that event. If no leftctrl event was generated that way, then` \
        `# a capslock release is turned into an esc press+release.` \
        --toggle key:capslock "" @caps-down @ctrl-down mode=passive \
        --toggle @in          "" @caps-down @caps-down mode=consistent \
        --hook   key:capslock:1 toggle=:2 \
        --hook   key:capslock:0 toggle=:1 \
        --map    key:capslock:0@caps-down key:esc:1@out key:esc:0@out \
        --block  key:capslock \
        --map    key@caps-down key:leftctrl "" \
        --map    btn:left:1@caps-down key:leftctrl "" \
        --map    btn:left:0@caps-down "" key:leftctrl \
        --map    btn:right:1@caps-down key:leftctrl "" \
        --map    btn:right:0@caps-down "" key:leftctrl \
        --map    btn:middle:1@caps-down key:leftctrl "" \
        --map    btn:middle:0@caps-down "" key:leftctrl \
        --hook   key:leftctrl@caps-down toggle=:3 \
                                                  \
        `# The --map is necessary to make the --merge properly merge real leftctrl` \
        `# events with virtual leftctrl events.` \
        --map    "" @out \
        --merge  \
        --output

(Feature TODO: consider adding some way to match btn::1 without having to spell out every possible key code.)

I think the issue is that the CtrlUp and the BtnUp are getting sent in the same synchronisation report. There is no way around this on the stable version of evsieve; I'll work on a solution.

fhill2 commented 2 years ago

Okay, on my system example above is still showing original behaviour in xev: Ctrl-Down Btn-Down Ctrl-Up Btn-Up


FYI, I thought I'd test caps2esc and view the xev results.

Using Interception Tools + caps2esc:

/etc/interception/udevmon.yaml

- CMD: mux -c caps2esc
- JOB: mux -i caps2esc | caps2esc -m 1 | uinput -d /dev/input/by-id/keyboard -d /dev/input/by-id/mouse
- JOB: intercept -g $DEVNODE | mux -o caps2esc
  DEVICE:
    LINK: /dev/input/by-id/keyboard

- JOB: intercept -g $DEVNODE | mux -o caps2esc
  DEVICE:
    LINK: /dev/input/by-id/mouse

This shows Ctrl-Down Btn-Down Btn-Up Ctrl-Up in xev, same as xmodmap+xcape. And clicking links in chrome with Caps-Click open in their new tab.

KarsMulder commented 2 years ago

I added a rudimentary implementation* of a --delay argument to the main branch (current commit: 97b7c46). It can delay events for a specified amount of seconds. For example, the following script delays the CtrlUp event by a millisecond to make sure it happens after the BtnUp event. It solves the Ctrl+Click problem for me.

evsieve --input  /dev/input/by-id/keyboard grab domain=in persist=reopen \
        --input  /dev/input/by-id/mouse grab domain=in persist=reopen \
        --map    key:esc key:grave \
        `# The following list are keys that will not turn the capslock into a ctrl key,` \
        `# so e.g. ctrl+capslock is turned into ctrl+esc instead of ctrl+ctrl.` \
        --map    key:leftctrl   @out \
        --map    key:rightctrl  @out \
        --map    key:leftshift  @out \
        --map    key:rightshift @out \
        --map    key:leftalt    @out \
        --map    key:rightalt   @out \
        --map    key:leftmeta   @out \
                                     \
        `# The main machinery: on capslock press, activate a map that turns all key events` \
        `# into a leftctrl + that event. If no leftctrl event was generated that way, then` \
        `# a capslock release is turned into an esc press+release.` \
        --toggle key:capslock "" @caps-down @ctrl-down mode=passive \
        --toggle @in          "" @caps-down @caps-down mode=consistent \
        --hook   key:capslock:1 toggle=:2 \
        --hook   key:capslock:0 toggle=:1 \
        --map    key:capslock:0@caps-down key:esc:1@out key:esc:0@out \
        --block  key:capslock \
        --map    key@caps-down key:leftctrl "" \
        --map    btn@caps-down key:leftctrl "" \
        --hook   key:leftctrl@caps-down toggle=:3 \
        --delay  key:leftctrl:0@caps-down period=0.001 \
        `# The --map is necessary to make the --merge properly merge real leftctrl` \
        `# events with virtual leftctrl events.` \
        --map    "" @out \
        --merge  \
        --output

(*Rudimentary implementation: not properly tested; has at least one known bug that won't matter for your use case; still subject to redesign; etc.)

This solution could possibly be made better by adding an option like period=syn to make --delay wait until some input device synchronises instead of waiting a fixed amount of time, to increase the responsivity by a millisecond or something, but that is still subject to some design questions.

fhill2 commented 2 years ago

Great, just tested. I can confirm it makes Caps-Click behaviour work correctly for the above script.

Thank you for implementing this feature.