rvaiya / keyd

A key remapping daemon for linux.
MIT License
2.89k stars 171 forks source link

Mapping multiple keys to an action #111

Closed alefbragin closed 2 years ago

alefbragin commented 2 years ago

I have Microsoft Ergonomic Keyboard. It's a good keyboard, but contains two strange and useless keys between right alt and right control: "microsoft office" and "smile".

Panel3-Microsoft-Ergonomic-Keyboard-J-Cen-Feat (photo from microsoft.com).

When the "microsoft office" key is pressed it produces four keys: control, shift, alt and meta.

> sudo systemctl stop keyd && sudo keyd -m
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftcontrol down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftshift down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftalt down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftmeta down

When the "smile" key is pressed it produces five keys, like "microsoft office" but with additional space key.

> sudo systemctl stop keyd && sudo keyd -m
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftcontrol down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftshift down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftalt down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftmeta down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   space down

I just want to bind "microsoft office" to meta, with something like this:

[main]
leftcontrol-leftshift-leftalt-leftmeta = layer(meta)

Can I achive this in the current version? E.g. with some layer configuration. Is there a more direct way to do this planned in future releases?

rvaiya commented 2 years ago
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftcontrol down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftshift down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftalt down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftmeta down

This strikes me as pathological behaviour :P.

With some ingenuity (read: atrocious kludge) you might be able to tame it

E.G

[control]

leftshift = layer(control_shift)

[control_shift:C-S]

leftalt = layer(control_shift_alt)

[control_shift_alt:C-S-A]

leftmeta = macro(Microsoft space is space evil)

note that this will break if you want to assign leftcontrol to something other than control. It is impossible to avoid this since both sequences begin with leftcontrol down which introduces inherent ambiguity.

If you are lucky it may also be possible to change the emitted keycode using the official remapping tool, but it is more likely that this is done in software rather than in the keyboard firmware.

alefbragin commented 2 years ago

This strikes me as pathological behaviour :P.

me too :)

If you are lucky it may also be possible to change the emitted keycode using the official remapping tool, but it is more likely that this is done in software rather than in the keyboard firmware.

That would be great, but i don't think this keyboard has a programmable firmware.


Thanks for the suggested approach! I try it as this:

[control]
leftshift = layer(control_shift)

[control_shift]
leftalt = layer(control_shift_alt)

[control_shift_alt]
leftmeta = layer(meta)

... but the leftcontrol event inserted to any strike of the key:

> sudo keyd -m
keyd virtual keyboard   0fac:0ade   leftcontrol down
keyd virtual keyboard   0fac:0ade   leftmeta down

I slightly modified the configuration:

[main]
leftcontrol = layer(mycontrol)

[mycontrol]
leftshift = layer(control_shift)

[control_shift]
leftalt = layer(control_shift_alt)

[control_shift_alt]
leftmeta = layer(meta)

... but I lost the left control as expected (e.g. LCtrl-o doesn't produce any events).

Also I try leverage layouts actions, but it didn't lead to any meaningful results.

rvaiya commented 2 years ago

Thanks for the suggested approach! I try it as this:

[control]
leftshift = layer(control_shift)

[control_shift]
leftalt = layer(control_shift_alt)

[control_shift_alt]
leftmeta = layer(meta)

This should be

[control]
leftshift = layer(control_shift)

[control_shift:C-S]
leftalt = layer(control_shift_alt)

[control_shift_alt:C-S-A]
leftmeta = layer(meta)

The modifiers at the end (e.g :C-S) are important if you want to preserve normal modifier behaviour (see the man page).

rvaiya commented 2 years ago

... but the leftcontrol event inserted to any strike of the key:

sudo keyd -m keyd virtual keyboard 0fac:0ade leftcontrol down keyd virtual keyboard 0fac:0ade leftmeta down

I should clarify that the config I provided will still depress leftcontrol when it receives leftcontrol down (this is inevitable), but it will compensate for this when it activates the final bindings. For example

[control]

leftshift = layer(control_shift)

[control_shift:C-S]

leftalt = layer(control_shift_alt)

[control_shift_alt:C-S-A]

leftmeta = macro(Hello space world)

Should produce something like the following:

leftcontrol down
leftshift down
leftalt down
leftcontrol up
leftshift up
leftalt up
H down
H up
e down
e up
....

Note that won't work if you assign leftmeta to a modifier layer like meta since it will combine with the other active modifiers by design. If you want to avoid this you will have to define a custom layer which explicitly maps meta keys.

E.G

[fakemeta]

a = M-a
b = M-b
c = M-c
....

[control]

leftshift = layer(control_shift)

[control_shift:C-S]

leftalt = layer(control_shift_alt)

[control_shift_alt:C-S-A]

leftmeta = layer(fakemeta)

This also means it won't combine properly with other modifers, but there are inevitably going to be some tradeoffs.

alefbragin commented 2 years ago

Probably I was too abstract in the issue title: Mapping multiple keys to an action. Indeed I want to map multiple keys (leftcontrol, leftshift, leftalt, leftmeta) to the one key -- to the meta key. I.e. I want to ignore leftcontrol, leftshift, leftalt when it was pressed simultaneously with leftmeta. My motivation is to abiltiy to hold with the right hand the meta key (which is missing on my keyboard in favor to the strange office keys), when left hand pressed `, tab etc (GNOME hotkeys for windows switching).

The closest config which I can to invent is:

[main]
leftcontrol = layer(mycontrol)

[mycontrol]
leftshift = layer(control_shift)

[control_shift]
leftalt = layer(control_shift_alt)

[control_shift_alt]
leftmeta = layer(meta)

... but at the cost of left control lack.

I have also tried [fakemeta] approach with ` = M-` and tab = M-tab, but it doesn't work as smoothly as the config above.

I have no idea is this possible at all or possible with keyd, or belongs to keyd philosophy. If it is not possible at all or doesn't belong to keyd philosophy, then it make sense to close this issue.

rvaiya commented 2 years ago

The closest config which I can to invent is:

[main] 

leftcontrol = layer(mycontrol)

[mycontrol] 

leftshift = layer(control_shift)

[control_shift] 

leftalt = layer(control_shift_alt)

[control_shift_alt] 

leftmeta = layer(meta) 

This config will completely break leftcontrol. It shouldn't be necessary to this. The config I provided should preserve leftcontrol and allow you to use the key which generates multiple keycodes as meta (with some caveats).

E.G

[fakemeta] 

a = M-a
b = M-b

# etc...

[control] 

leftshift = layer(control_shift)

[control_shift:C-S] 

leftalt = layer(control_shift_alt)

[control_shift_alt:C-S-A] 

leftmeta = layer(fakemeta) 

I have also tried [fakemeta] approach with = M- and tab = M-tab, but it doesn't work as smoothly as the config above.

What specific problem are you encountering?

I have no idea is this possible at all or possible with keyd

The example config I provided should mostly work, though I can't test it.

alefbragin commented 2 years ago

What specific problem are you encountering?

When I pressed "fakemeta" with the tab key, GNOME windows switching overlay blinked for a moment and with some chance switch the window. I thrown away that config and kept experimenting. Sorry, I can't reproduce that config now.

The example config I provided should mostly work ...

The config:

[fakemeta] 
a = M-a
b = M-b

[control] 
leftshift = layer(control_shift)

[control_shift:C-S] 
leftalt = layer(control_shift_alt)

[control_shift_alt:C-S-A] 
leftmeta = layer(fakemeta) 

... produces:

keyd virtual keyboard   0fac:0ade   leftcontrol down
keyd virtual keyboard   0fac:0ade   leftshift down
keyd virtual keyboard   0fac:0ade   leftalt down
keyd virtual keyboard   0fac:0ade   leftmeta down
keyd virtual keyboard   0fac:0ade   a down
keyd virtual keyboard   0fac:0ade   a up
keyd virtual keyboard   0fac:0ade   leftcontrol up
keyd virtual keyboard   0fac:0ade   leftshift up
keyd virtual keyboard   0fac:0ade   leftalt up
keyd virtual keyboard   0fac:0ade   leftmeta up

... when I press the "office key". This behavior is not differ from standard behavour without any config:

Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftcontrol down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftshift down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftalt down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftmeta down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   a down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   a up
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftcontrol up
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftshift up
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftalt up
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftmeta up

Moreover any hitting (without any modifiers) to the "A" (or "B") key produces:

keyd virtual keyboard   0fac:0ade   leftmeta down
keyd virtual keyboard   0fac:0ade   a down
keyd virtual keyboard   0fac:0ade   a up
keyd virtual keyboard   0fac:0ade   leftmeta up

..., though I can't test it.

I think it can be tested on any standard keyboard by pressing leftcontrol, leftshift, leftalt, leftmeta simultaneously. It is what the Microsoft Ergonomic Keyboard actually does when "office" key is pressed.

rvaiya commented 2 years ago

... produces:...

You didn't specify what key combination you are using to produce this. I assume it is the microsoft office key + a. You should be seeing something like:

Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftcontrol down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftshift down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftalt down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftmeta down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftcontrol up
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftshift up
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftalt up
Microsoft Microsoft Ergonomic Keyboard  045e:082c   a down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   a up
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftcontrol down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftshift down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftalt down
Microsoft Microsoft Ergonomic Keyboard  045e:082c   leftmeta up

Did you check the log output for errors? (journalctl -fu keyd).

... when I press the "office key". This behavior is not differ from standard behavour without any config:

Yes, something appears to be wrong with your config. I just tested this and it worked as expected.

rvaiya commented 2 years ago

I just tried copying and pasting the config I suggested, it seems some additional spaces were added at the end of the lines which may be causing your problem (you can confirm this in your system journal). I've also pushed a patch to automatically ignore empty space at the end of lines in the latest version.

alefbragin commented 2 years ago

I just tried copying and pasting the config I suggested, it seems some additional spaces were added at the end of the lines which may be causing your problem (you can confirm this in your system journal). I've also pushed a patch to automatically ignore empty space at the end of lines in the latest version.

Yes, it is my case) Thank you!

I have tryed this config:

[fakemeta]
` = M-`
tab = M-tab
space = M-space

[control]
leftshift = layer(control_shift)

[control_shift:C-S]
leftalt = layer(control_shift_alt)

[control_shift_alt:C-S-A]
leftmeta = layer(fakemeta)

It is very close to what I want to achieve, but it is still doesn't cover all use cases. As I said earlier, I was hoping to use office key as meta for windows switching. It does the trick for "one hit" case (e.g. just hit meta+tab for app switching). But there is a behavior for windows (or app) switching, when you hold the meta key and tap `( or tab) sequentially. This does not work as expected for obvious reasons: leftcontrol, leftshift, leftalt become down after `( or tab) is released. Actual log is very similar to what you expected. When I hold office, then tap tab, then tap tab again, then release office key, the log is:

keyd virtual keyboard   0fac:0ade   leftcontrol down
keyd virtual keyboard   0fac:0ade   leftshift down
keyd virtual keyboard   0fac:0ade   leftalt down
keyd virtual keyboard   0fac:0ade   leftcontrol up
keyd virtual keyboard   0fac:0ade   leftshift up
keyd virtual keyboard   0fac:0ade   leftmeta down
keyd virtual keyboard   0fac:0ade   leftalt up
keyd virtual keyboard   0fac:0ade   tab down
keyd virtual keyboard   0fac:0ade   tab up
keyd virtual keyboard   0fac:0ade   leftcontrol down
keyd virtual keyboard   0fac:0ade   leftshift down
keyd virtual keyboard   0fac:0ade   leftmeta up
keyd virtual keyboard   0fac:0ade   leftalt down
keyd virtual keyboard   0fac:0ade   leftcontrol up
keyd virtual keyboard   0fac:0ade   leftshift up
keyd virtual keyboard   0fac:0ade   leftmeta down
keyd virtual keyboard   0fac:0ade   leftalt up
keyd virtual keyboard   0fac:0ade   tab down
keyd virtual keyboard   0fac:0ade   tab up
keyd virtual keyboard   0fac:0ade   leftcontrol down
keyd virtual keyboard   0fac:0ade   leftshift down
keyd virtual keyboard   0fac:0ade   leftmeta up
keyd virtual keyboard   0fac:0ade   leftalt down
keyd virtual keyboard   0fac:0ade   leftcontrol up
keyd virtual keyboard   0fac:0ade   leftshift up
keyd virtual keyboard   0fac:0ade   leftalt up

I believe that the ideal behavior is:

keyd virtual keyboard   0fac:0ade   leftcontrol down
keyd virtual keyboard   0fac:0ade   leftshift down
keyd virtual keyboard   0fac:0ade   leftalt down
keyd virtual keyboard   0fac:0ade   leftcontrol up
keyd virtual keyboard   0fac:0ade   leftshift up
keyd virtual keyboard   0fac:0ade   leftalt up
keyd virtual keyboard   0fac:0ade   leftmeta down
keyd virtual keyboard   0fac:0ade   tab down
keyd virtual keyboard   0fac:0ade   tab up
keyd virtual keyboard   0fac:0ade   tab down
keyd virtual keyboard   0fac:0ade   tab up
keyd virtual keyboard   0fac:0ade   leftmeta up

Is it possible to get close to this behavior?

Currently I'm happy with an alternative key mapping that doesn't invlove the "office" key at all. But this case might be helpful for users of the Microsoft Egronomic Keyboard who want to achieve a more classic mapping. And if you willing improve the config for this case, I can test it on my system.

rvaiya commented 2 years ago

I have tryed this config: ... It does the trick for "one hit" case (e.g. just hit meta+tab for app switching).

Yes, this is one of the caveats I alluded to. Another problem is that you won't be able to combine it with other modifier keys (e.g S-M-a).

I believe that the ideal behavior is:

....

Is it possible to get close to this behavior?

You can achieve something close with swap, but it will have a side effect on control combinations. Specifically once you press another modifier you won't be able to deactivate it until you release the first key in the activation path.

This may or may not be a problem for you. It is only an issue if you are in the habit of holding control + another modifier and then releasing the other modifier without releasing control before you strike another key.

E.G

[control]
leftshift = swap(control_shift)

[control_shift:C-S]
leftalt = swap(control_shift_alt)

[control_shift_alt:C-S-A]
leftmeta = swap(meta)

This has the additional advantage of working in combination with other modifier keys.

Note that for the this to work you will need the latest version. I have just released a patch which allows for swap actions to be nested.

And if you willing improve the config for this case, I can test it on my system.

You are running up against the limit of sequence based detection logic. The only way to mitigate this problem is to add an additional timeout to disambiguate the real control key from the synthetic event (since they are otherwise indisguishable).

In this case you would need something which activates a layer only if another key is struck within a certain time interval. This is currently the opposite of how overload works (since you almost never want this behaviour). I am considering adding a more generic timeout mechanism to keyd but I haven't settled on a design yet and I'm not sure if I will end up including one.

81 is tangentially related.

rvaiya commented 2 years ago

Did you ever get this working? The last config should have come quite close.

andrey-shigantsov commented 2 years ago

I think I found a bug not related to the keyboard. (Tell me if you need to create a new task)

If you press a modifier key and then immediately press the same modifier on the other side, then releasing one of them turns off the mode.

$ sudo KEYD_DEBUG=1 keyd
Debug mode enabled.
Starting keyd v2.2.4-beta (cc84f3db9d77c724a7b2e56d04696927f5c71dfd).
Parsing /etc/keyd/ms-ergo-lxm-00001.conf
...
Awaiting keyboard neutrality.
Keyboard neutrality achieved
No config found for Corsair CORSAIR M55 RGB PRO Gaming Mouse (1b1c:1b70)
Managing Microsoft Microsoft Ergonomic Keyboard (045e:082c) (/etc/keyd/ms-ergo-lxm-00001.conf)
Activating layer S // leftshift down
Active mods: S
Activating layer S // rightshift down
Deactivating layer S // leftshift up
Active mods: 
Deactivating layer S // rightshift up
rvaiya commented 2 years ago

@andrey-shigantsov This is expected behaviour and has been addressed in #85. Feel free to post there if it is actually causing you grief :P.

alefbragin commented 2 years ago

Did you ever get this working? The last config should have come quite close.

Yes! It's works as I expect in my GNOME environment: "office" key is felt as meta key.

My full config is:

[ids]
*

[main]
capslock = esc
esc = capslock
rightalt = layer(alt)

[control]
leftshift = swap(control_shift)

[control_shift:C-S]
leftalt = swap(control_shift_alt)

[control_shift_alt:C-S-A]
leftmeta = swap(meta)

I feel fully comfortable with it in GNOME.

rvaiya commented 2 years ago

Thanks for closing the loop :). I am going to close this for now.

rvaiya commented 2 years ago

The new timeout functionality might be a better way to solve this.

Something like this should do the trick:

leftcontrol = timeout(noop, 1, layer(control))
leftshift = timeout(noop, 1, layer(shift))
leftalt = timeout(layer(office_key), 1, layer(alt))

[office_key]

# Paydirt, map this to whatever you want
leftmeta = macro(buy_m$ft)

You may have to increase the timeout values, depending on how fast the keyboard emits successive events, but the end result should be fairly reliable since the threshold will probably be well below what you can achieve manually.

andrey-shigantsov commented 2 years ago

Thanks! It works like a charm!

# Microsoft Ergonomic Keyboard (LXM-00001)
[ids]
045e:082c

[main]

[control]
leftshift = timeout(swap(for_fakemeta_C_S), 1, layer(shift))

[for_fakemeta_C_S:C-S]
leftalt = timeout(swap(for_fakemeta_C_S_A), 1, layer(alt))

[for_fakemeta_C_S_A:C-S-A]
leftmeta = swap(fakemeta)

[fakemeta:M]

PS I will test more)