rvaiya / keyd

A key remapping daemon for linux.
MIT License
2.74k stars 160 forks source link

Map sequence of keys to other keys #236

Closed peplau closed 2 years ago

peplau commented 2 years ago

Would it be possible to allow KEYD to map sequences of keys?

For instance:

rvaiya commented 2 years ago

Can you elaborate on how you would expect this to work? How would you distinguish between <left> <left> <right> and <f12>? Presumably you would need some kind of timeout after each keystroke to avoid extraneous keypresses, but that doesn't really seem practical.

peplau commented 2 years ago

My application is that I have a limited keyboard with only directional arrows, and I want to use some key combinations to inject other keys (or combinations of keys).

So in your example, only when left left right is pressed in sequence (and yes, some timer would be needed), the keystroke F12 would be injected. Any other combination would simply send the original left and right keys.

Some other combinations would send different keystrokes, for instance: left right left => Sends CTRL+0 shortcut

rvaiya commented 2 years ago

If you don't care about left producing left at all, then you use oneshot toggles in conjunction with layers. Alternatively you can use overload to produce different behaviour when a directional key is chorded with another key (rather than tapped).

E.G

[main]

left = oneshot(left)
right = overload(right, right)

[left]

# Real left key
left = left

down = macro(Hello_World)
right = oneshot(right)

[right]

left = macro(Hello_cruel_nested_world)

Will cause either

<left down> <left up> <right down> <right up> <left down> <left up>

or

<right down> <left down> <left up> <right up>

to produce 'Hello_cruel_nested_world'

while

<left down> <left up> <down down> <down up>

will produce 'Hello_world'.

In the above example left can be activated by tapping the left arrow key twice and down can be produced by tapping down with no intervening keys.

There is currently no way to specify time based sequences in the way that you describe (that is there is no way to specify different actions depending on the time between keys, though it is possible to change behaviour based on how long a key is pressed (see timeout()).

I instinctively feel that this is a bad idea, but I am not opposed to adding such a feature if you can make a case for it in a dedicated issue.

peplau commented 2 years ago

Maybe I can convince you by explaining my application.

I've built a physical teleprompter with a Raspberry PI inside, to use during musical performances and events. The device stays at the floor during the presentations, and I have a USB "footswitch" that works like a real keyboard, injecting keystrokes when the keys are pressed. My foot switch keyboard has only 4 keys (up, down, left, right), all of them are very important.

Because during the presentation I cannot afford using a real keyboard, I'm trying to use combinations of directional keys to inject different keystroke commands.

My teleprompter runs on chrome browser, so I wanted to map the following commands to my directional footswitch keys:

left, right, left - F12 (google fullscreen) left left right - CTRL+plus (Increase zoom) right right left - CTRL+0 (reset zoom to 100%)

Of course, I also want to keep using the directional keys for their own original purposes (directionals), so they should also be respected when not used in a combination.

WOuld that be possible with your current software? Or would you consider adding this feature?

rvaiya commented 2 years ago

I've built a physical teleprompter with a Raspberry PI inside, to use during musical performances and events. The device stays at the floor during the presentations, and I have a USB "footswitch" that works like a real keyboard, injecting keystrokes when the keys are pressed. My foot switch keyboard has only 4 keys (up, down, left, right), all of them are very important.

This is an interesting application indeed. If you were using a normal keyboard I would advocate using chording, since the results would undoubtedly be superior to relying on time as the discriminating factor.

My teleprompter runs on chrome browser, so I wanted to map the following commands to my directional footswitch keys:

left, right, left - F12 (google fullscreen) left left right - CTRL+plus (Increase zoom) right right left - CTRL+0 (reset zoom to 100%)

This bit can already be achieved using the following config

[main]
left = oneshot(left)
right = oneshot(right)

[right]
right = oneshot(right_right)

[left]
right = oneshot(left_right)

[right_right]
left = C-0

[left_right]
left = f12

[left_left]
right = C-+

Of course, I also want to keep using the directional keys for their own original purposes (directionals), so they should also be respected when not used in a combination.

This is the tricky bit and would require some non trivial internal changes. Depending on your precise requirements, you might use timeout() to good effect. It allows you to specify alternate behaviour depending on how long a key is held (though it notably doesn't care about the time between successive keys).

For example, you could do something like:

[main]
left = timeout(left, 1000, f12)

which would produce f12 if the left key is held for more than 1 second.

or

[main]
left = timeout(left, 1000, toggle(hotkeys))

[hotkeys]
right = f12
up = C-0
down = C-+
left = toggle(hotkeys)

Which would allow you enter a dedicated hotkey mode by holding left, after which each directional key would be mapped to a shortcut. The left key could be tapped while in this mode to restore the original directional mappings.

It is worth noting that even if sequence based logic was implemented, it would introduce more latency than the above approach, since there would need to be a pause between each key to establish intent.

peplau commented 2 years ago

I've tried this one, and it works well, thanks so much!

[main] left = timeout(left, 1000, f12)

The only issue is the left now cannot be hold for the original purpose - instead I have to tap it. Which is not any bad!!

PS: Unfortunately it only works with my real keyboard - using my footswitch keyboard doesn't trigget the F12 key for some reason. However, it does triggers the directionals normally. Is there anything I can do to map those as well? By using this url https://en.key-test.ru/ it shows both my real keyboard and the footswitch is triggering the directionals, but only the real keyboard triggers F12 when hold

rvaiya commented 2 years ago

The footswitch is probably not being properly recognized by keyd. Can you post the output of

sudo keyd -m # Tap some foot switches to produce output here

and

sudo journalctl -u keyd -n 30 --no-pager (assuming you are using systemd)

?

Do you have

[ids]
*

at the top of your config file?

peplau commented 2 years ago

Hello again, and thanks for your amazing support.

Here is the content of my config:

[ids]
*
[main]
up = timeout(up, 1000, f11)

And here is the output of the commands you requested:

takeover@takeover:~ $ sudo keyd -m;
device added: 1b4f:9206 (SparkFun SparkFun Pro Micro Keyboard)
device added: 1b4f:9206 (SparkFun SparkFun Pro Micro Mouse)
device added: 6901:2701 (HID 6901:2701 Consumer Control)
device added: 6901:2701 (HID 6901:2701 Mouse)
device added: 6901:2701 (HID 6901:2701)
device added: 04f2:0833 (CHICONY USB Keyboard)
device added: 0fac:0ade (keyd virtual device)
device added: 0000:0000 (vc4)
device added: 0000:0000 (vc4)
keyd virtual device 0fac:0ade down down
keyd virtual device 0fac:0ade down up
keyd virtual device 0fac:0ade down down
keyd virtual device 0fac:0ade down up
keyd virtual device 0fac:0ade down down
keyd virtual device 0fac:0ade down up
keyd virtual device 0fac:0ade up down
keyd virtual device 0fac:0ade up up
keyd virtual device 0fac:0ade up down
keyd virtual device 0fac:0ade up up
keyd virtual device 0fac:0ade up down
keyd virtual device 0fac:0ade up up
keyd virtual device 0fac:0ade up down
keyd virtual device 0fac:0ade up up
keyd virtual device 0fac:0ade up down
keyd virtual device 0fac:0ade up up
keyd virtual device 0fac:0ade up down
keyd virtual device 0fac:0ade up up
keyd virtual device 0fac:0ade down down
keyd virtual device 0fac:0ade down up
keyd virtual device 0fac:0ade down down
keyd virtual device 0fac:0ade down up
keyd virtual device 0fac:0ade down down
keyd virtual device 0fac:0ade down up
keyd virtual device 0fac:0ade down down
keyd virtual device 0fac:0ade down up
keyd virtual device 0fac:0ade leftcontrol down
keyd virtual device 0fac:0ade c down
takeover@takeover:~ $ sudo journalctl -u keyd -n 30 --no-pager
-- Journal begins at Mon 2022-04-04 09:17:02 -03, ends at Sun 2022-06-26 15:50:34 -03. --
jun 22 21:15:43 takeover systemd[1]: keyd.service: Failed with result 'exit-code'.
jun 22 21:15:43 takeover systemd[1]: Stopped key remapping daemon.
-- Boot ac78b733e71745eeaad4f457553b2fb2 --
jun 22 21:15:50 takeover systemd[1]: Started key remapping daemon.
jun 22 21:15:51 takeover keyd[444]: Starting keyd v2.4.0 (5b9de6c)
jun 22 21:15:51 takeover keyd[444]: socket: /var/run/keyd.socket
jun 22 21:15:51 takeover keyd[444]: device added: 6901:2701 HID 6901:2701 (/dev/input/event0)
jun 22 21:15:51 takeover keyd[444]: matched /etc/keyd/default.conf
jun 22 21:15:51 takeover keyd[444]: failed to open /dev/input/by-id
jun 22 21:15:51 takeover keyd[444]: failed to open /dev/input/by-path
jun 26 15:26:13 takeover keyd[444]: device added: 04f2:0833 CHICONY USB Keyboard (/dev/input/event7)
jun 26 15:26:13 takeover keyd[444]: matched /etc/keyd/default.conf
jun 26 15:44:18 takeover keyd[444]: device removed: 04f2:0833 CHICONY USB Keyboard (/dev/input/event7)
jun 26 15:44:19 takeover keyd[444]: device added: 04f2:0833 CHICONY USB Keyboard (/dev/input/event7)
jun 26 15:44:19 takeover keyd[444]: matched /etc/keyd/default.conf
jun 26 15:44:25 takeover keyd[444]: device removed: 04f2:0833 CHICONY USB Keyboard (/dev/input/event7)
jun 26 15:44:25 takeover keyd[444]: device removed: 6901:2701 HID 6901:2701 (/dev/input/event0)
jun 26 15:44:26 takeover keyd[444]: device removed: 6901:2701 HID 6901:2701 Mouse (/dev/input/event1)
jun 26 15:44:26 takeover keyd[444]: device removed: 6901:2701 HID 6901:2701 Consumer Control (/dev/input/event2)
jun 26 15:44:27 takeover keyd[444]: device added: 04f2:0833 CHICONY USB Keyboard (/dev/input/event0)
jun 26 15:44:27 takeover keyd[444]: matched /etc/keyd/default.conf
jun 26 15:44:27 takeover keyd[444]: device added: 1b4f:9206 SparkFun SparkFun Pro Micro (/dev/input/event7)
jun 26 15:44:27 takeover keyd[444]: matched /etc/keyd/default.conf
jun 26 15:44:27 takeover keyd[444]: device added: 6901:2701 HID 6901:2701 (/dev/input/event8)
jun 26 15:44:27 takeover keyd[444]: matched /etc/keyd/default.conf
jun 26 15:44:28 takeover keyd[444]: ioctl: Inappropriate ioctl for device
jun 26 15:44:28 takeover keyd[444]: failed to open /dev/input/by-id
jun 26 15:48:45 takeover keyd[444]: device removed: 1b4f:9206 SparkFun SparkFun Pro Micro (/dev/input/event7)
jun 26 15:49:19 takeover keyd[444]: ioctl: Inappropriate ioctl for device
jun 26 15:49:20 takeover keyd[444]: device added: 1b4f:9206 SparkFun SparkFun Pro Micro Keyboard (/dev/input/event12)
jun 26 15:49:20 takeover keyd[444]: matched /etc/keyd/default.conf
takeover@takeover:~ $

I have pressed the two buttons UP and DOWN on the footswitch

rvaiya commented 2 years ago

It looks like the footswitch is indeed correctly being recognized. I assume holding the switch corresponding to the up key doesn't trigger f11 as expected.

Does the footswitch properly send key state depending on whether or not the switch is depressed? Or does it perhaps send both the up and down events when held? You can test this by running sudo keyd -m while keyd is not running and observing the output while you hold/release the switch.

If the footswitch is behaving as expected you should see something like this:

SparkFun SparkFun Pro Micro Keyboard 1b4f:9206 up down
SparkFun SparkFun Pro Micro Keyboard 1b4f:9206 up up <----- You should only see this *after* releasing the switch.

P.S

Please surround command output with ```, it prevents github from rendering it as markdown. I've edited your post to include them.

peplau commented 2 years ago

You're right, for some reason, the footswitch when held is constantly sending up and down events in sequence. Looks like the timer approach won't be possible for this device then? Any other alternatives?

rvaiya commented 2 years ago

The output would suggest you have it wired up to a microcontroller which you can program, so your best bet would be to just modify it to behave like a normal keyboard.

If you can't do this, then the only solution is a sequence based one like you described. It might also be possible to make use of the repeat interval depending on how the switch behaves when you hold it (i.e does it continue to emit up/down pairs when held, or does it emit such a pair exactly once for each depression?).

peplau commented 2 years ago

Unfortunately, I can't program it.

The current behavior is it continues to emit up/down pars when held, similar to when you tap the button.

How would be the example to have a sequence like: tap UP, tap DOWN be mapped to F11?

rvaiya commented 2 years ago

Depending on the length of the repeat interval (i.e can you intentionally tap the key once?), the simplest approach would be to dedicate one of the keys to a layer toggle like so:

[main]
up = toggle(hotkeys)
[hotkeys]
up = toggle(hotkeys)
down = f11
...

Though it might be better to use something like

[main]
up = oneshot(hotkeys)
[hotkeys]
up = up
down = f11
...

which would allow you to actuate up by tapping up twice, f11 by tapping up down, and the remaining arrow keys by using them in isolation.

peplau commented 2 years ago

The above works fine for my purposes, only the UP key is now taking double-time to scroll up, which is not a problem. Thanks so much!