KarsMulder / evsieve

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

Issue When Mapping Single Key to Output Combination Key #14

Closed aggo15 closed 1 year ago

aggo15 commented 2 years ago

I'm trying to use evsieve to map a single key and generate a combination key. For example, when I press F12 key, I want evsieve to output Ctrl+C key to my device.

Here is the command I use

evsieve --input /dev/input/event0 --map key:f12 key:leftctrl key:c --print --output

However, this command will not generate Ctrl+C key consistently. On the monitor of my device, it some time will output ^C as expected, but most of the time I get [24~ instead.

Did I miss anything? Is there a way to achieve what I want?

KarsMulder commented 2 years ago

First, let's try the easy possible solution and test whether grabbing the input device solves the issue.

evsieve --input /dev/input/event0 grab --map key:f12 key:leftctrl key:c --print --output

Right now, the original input device is not grabbed, so whenever a key like "A" is pressed, two keyboards will appear to simultaneously emit the "A" event, and when F12 is pressed, one keyboard will emit F12 and the other will emit Ctrl+C. I am not sure how this could prevent the Ctrl+C from being received, but maybe it somehow does?

If that does not solve the problem, then this is probably related to issue #8, and the consequence of a in hindsight bad design decision. There is an EV_SYN type events which you do not see in the output of the --print line from evsieve, but can be seen if you use the third-party evtest utility to see which events your devices emit. The intent was for evsieve to handle these events automagically without having to ask the user about how to map those, and therefore the policy is "Whenever an EV_SYN event is read from any input device, send an EV_SYN to all output device which have received events since the last EV_SYN event."

Which means that the output of your virtual keyboard will look something like:

EV_SYN
EV_KEY KEY_LEFTCTRL 1
EV_KEY KEY_C 1
EV_SYN

And the lack of an EV_SYN between the KEY_LEFTCTRL event and the KEY_C event causes some programs to misorder them.

As temporary workaround, you can try building evsieve from the main branch and then use the new --delay argument to delay the C key for a very short period to force an EV_SYN event to show up between the leftctrl and the C key:

evsieve --input /dev/input/event0 grab \
        --map key:f12 key:leftctrl key:c \
        --delay key:c period=0.000000001 \
        --print \
        --output

This is of course not a very elegant solution. I could maybe add a clause like --output syn=always to always generate EV_SYN events between output events, but even if I did, it would be hard to document it in a way that an user could figure out they need that clause when they run into the same issue as you.

I wonder if it is worth breaking backwards compatibility and release an evsieve 2.0 that fixes this issue and a couple of other ones.

aggo15 commented 2 years ago

If a lot of people is using evsieve to output combination key then I think it is good to revisit and fix the issues. Of course that's up to you to decide.

--delay sounds like a good workaround, I will try out --delay option next round. --output syn=always sounds more reliable than --delay but that's just my opinion.

Some additional findings from my side. What I did was make sure my machine only connected with one keyboard, adding grab flag in the command, and then execute evsieve command via ssh. However, I am still getting [24~ on my screen.

After more testing, I found that [24~ only appear if my machine is running on console mode without login. If I login to my user account in console mode and try again, I can see ^C as expected every time I press F12 key. Probably the way OS interpret keyboard input is a little different when machine is in console mode without login? That's beyond me.

PS: I haven't actually work on open source project before, so if you don't mind, I can help to add in combination key use case in your readme file via pull request.

KarsMulder commented 2 years ago

One of the design goals of evsieve was to always do the right thing™ without the user having to tell it to, so if this is regularly causing issues, then the default behaviour should simply be different.

However, I am also trying to maintain backwards compatibility because (1) it takes users some time to figure out how to do what they need to, and (2) if I were a user who wrote a script, and my script broke because of some dumb update, I would not be amused.

One other major pain point right now is the persist=reopen clause. It really should be the default mode of operation on all event devices except those specified by a path like /dev/input/event*, because in the sheer majority of the situations that would be the desirable behaviour. However, it could break somebody's setup if they were to pass some virtual event device to evsieve and expect that destroying that virtual device awould also make evsieve exit.

(I wrote evsieve with the idea of "if this program crashes, the user may lose input to their virtual machine, so don't crash". There is error handling code for many obscure error cases, and it is painfully ironic that the most common error case of a USB device disconnecting is not addressed by default.)

Before releasing version 1.3, I had been thinking a long time about whether I should break backward compatibility to make persist=reopen the default mode, and in the end I decided to stop letting that issue uphold development and just maintain backwards compatibility for now.

Still, the question of whether it is acceptable to break backwards compatibility and release evsieve 2.0 has been on my mind for a pretty long while now, and this issue further tips the scales of balance in favor of "just release 2.0".

(That said, I can actually not imagine any remotely reasonable scenario in which adding some EV_SYN events will break something, so maybe this could be fixed in a minor release, but since I am already contemplating a major release anyway and this fix technically changes the behaviour of existing scripts, it might be a good idea to bundle this with the other breaking changes.)

(Relatedly, there is another bad decision™ I made regarding some undocumented implementation detail that affects hardly any script but is currently preventing me from writing a simple, consistent and bug-free implementation of the new --withhold argument, and this is currently delaying a 1.4 release.)

PS: I haven't actually work on open source project before, so if you don't mind, I can help to add in combination key use case in your readme file via pull request.

Thank you, but no need to, for several reasons. (1. The README must correspond to the last stable release, not the in-development version. 2. I would rather not recommend hacky workarounds in README, at least not until I have decided that there won't be a proper solution for this, and 3. the README is getting pretty unwieldy with the new features getting added. It is probably time I reorganised the documentation into a wiki or something.)

KarsMulder commented 2 years ago

... after some more thought, I am probably overthinking this.

The behaviour of how EV_SYN is currently handled is intended. However, the implications of that behaviour are not. Moreover, the current behaviour is not even documented. Furthermore, fixing this issue would likely not break any script (?). Therefore, the current behaviour can be considered a bug and can be fixed in a minor release.

I'll look further into this tomorrow.

aggo15 commented 2 years ago

Thank you @KarsMulder for looking into this. Look forward for the release.

KarsMulder commented 2 years ago

This issue should now be fixed on the main branch (commit ce0bc3bd1604660f32a10df73aad37ba24898695). The new policy is to use the old behaviour when mapping single events to single events (so a simple --input PATH --output resembles the original device as closely as possible), but to synchronise output devices after each event when mapping a single event to multiple events.

aggo15 commented 2 years ago

I can see the difference of keyboard signal from evtest while using the command evsieve --input /dev/input/event0 grab --print --map key:f12 key:leftctrl key:c --output create-link=/dev/input/guest-keyboard.

evtest output for evsieve-1.3.1

Event: time 1650520786.748753, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70045
Event: time 1650520786.748753, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1
Event: time 1650520786.748753, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1650520786.748753, -------------- SYN_REPORT ------------
Event: time 1650520786.895289, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70045
Event: time 1650520786.895289, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0
Event: time 1650520786.895289, type 1 (EV_KEY), code 46 (KEY_C), value 0
Event: time 1650520786.895289, -------------- SYN_REPORT ------------
Event: time 1650520794.876491, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70045
Event: time 1650520794.876491, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1
Event: time 1650520794.876491, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1650520794.876491, -------------- SYN_REPORT ------------
Event: time 1650520795.024391, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70045
Event: time 1650520795.024391, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0
Event: time 1650520795.024391, type 1 (EV_KEY), code 46 (KEY_C), value 0
Event: time 1650520795.024391, -------------- SYN_REPORT ------------

evtest output for evsieve latest commit

Event: time 1650520835.899233, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70045
Event: time 1650520835.899233, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1
Event: time 1650520835.899233, -------------- SYN_REPORT ------------
Event: time 1650520835.899256, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1650520835.899256, -------------- SYN_REPORT ------------
Event: time 1650520836.046048, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70045
Event: time 1650520836.046048, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0
Event: time 1650520836.046048, -------------- SYN_REPORT ------------
Event: time 1650520836.046067, type 1 (EV_KEY), code 46 (KEY_C), value 0
Event: time 1650520836.046067, -------------- SYN_REPORT ------------
Event: time 1650520839.746667, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70045
Event: time 1650520839.746667, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1
Event: time 1650520839.746667, -------------- SYN_REPORT ------------
Event: time 1650520839.746718, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1650520839.746718, -------------- SYN_REPORT ------------
Event: time 1650520839.886611, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70045
Event: time 1650520839.886611, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0
Event: time 1650520839.886611, -------------- SYN_REPORT ------------
Event: time 1650520839.886631, type 1 (EV_KEY), code 46 (KEY_C), value 0
Event: time 1650520839.886631, -------------- SYN_REPORT ------------

However, the console login screen still not able to capture ^C properly using code from the latest commit.

I capture the signal generated when I manually press ^C on a keyboard. Here is what I get.

Event: time 1650521888.761864, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0
Event: time 1650521888.761864, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1
Event: time 1650521888.761864, -------------- SYN_REPORT ------------
Event: time 1650521888.873843, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1650521888.873843, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1650521888.873843, -------------- SYN_REPORT ------------
Event: time 1650521888.921880, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0
Event: time 1650521888.921880, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0
Event: time 1650521888.921880, -------------- SYN_REPORT ------------
Event: time 1650521888.937811, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1650521888.937811, type 1 (EV_KEY), code 46 (KEY_C), value 0

It seems like EV_MSC is added to C key as well. Could this be the missing signal required?

KarsMulder commented 2 years ago

It looks like at least some part of the linux kernel driver for TTYs is doing something special with scancodes (linux/drivers/tty/vt/keyboard.c), so it is possible that this is indeed related to scancodes.

It's hard to find proper documentaion on how the scancodes exactly work. I have added a half-baked feature that partially emulates the correct scancodes. The feature is neither complete nor correct, but it should at least suffice for figuring out whether scancodes are the cause of this issue*.

Could you try updating your code to the newest version of the main branch, and then recompiling with the "auto-scan" feature enabled?

git pull
cargo build --release --features auto-scan

The resulting binary should then automatically generate scancodes to match the keys send to the output device. You do not have to make any changes to your scripts.

(*EDIT: on a second thought, it might actually not be sufficient, because scan-codes are dependent on the driver of your keyboard. Most keyboards nowadays are USB, so the "auto-scan" feature emulates USB scancodes (the same ones as can be seen in your sample from evtest.) However, since the virtual device is not know to the kernel as an USB device but as an uinput device, it may be possible that the kernel TTY driver doesn't load the right/any scancode->key map. I suppose we'll see after testing.)

aggo15 commented 2 years ago

Hi @KarsMulder, sorry I took a while as I was hit by COVID. I was down for more than a week.

Regarding the auto scan feature, I've verified it and confirm the evtest output for the virtual device is the same as my USB keyboard. I use back the same command evsieve --input /dev/input/event0 grab --print --map key:f12 key:leftctrl key:c --output create-link=/dev/input/guest-keyboard

evtest output with latest commit b7e3c55 with --feature auto-scan added during build

Event: time 1652238456.071122, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0
Event: time 1652238456.071122, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1
Event: time 1652238456.071122, -------------- SYN_REPORT ------------
Event: time 1652238456.071158, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1652238456.071158, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1652238456.071158, -------------- SYN_REPORT ------------
Event: time 1652238456.203005, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0
Event: time 1652238456.203005, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0
Event: time 1652238456.203005, -------------- SYN_REPORT ------------
Event: time 1652238456.203035, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1652238456.203035, type 1 (EV_KEY), code 46 (KEY_C), value 0
Event: time 1652238456.203035, -------------- SYN_REPORT ------------

Anyway, I figure out something else during the test. It seems like during console login screen below. Ctrl+C won't work regardless of which type of keyboard I plug in.

Raspbian GNU/Linux 10 raspberry tty1
raspberry login: 

Ctrl+C will only start work if you already key in a user name and console is expecting password like below.

Raspbian GNU/Linux 10 raspberry tty1
raspberry login: pi
Password:

The tests I did previously was all on the first console login screen where user name is not yet key in to console. It give me a false sense where the code is not working. I did retest 2 previous commits including the stable one, and make sure I do the Ctrl+C test after I key in the user name. All commits works with console able to accept the Ctrl+C signal. The commits I tested are b7e3c55, ce0bc3b and 1.3.1 release.

Sorry for this blunder. Not sure if you will keep the --feature auto-scan option as it proven not impact anything.