Adding to the answer by @meuh about using hwdb, here's a few things I learned about how hwdb works. This is mostly about keyboard scancode mappings from hwdb, but a lot of this will be useful for other hwdb info as well.
Sources:
For a practical example of what I did with the info I collected here, see this post about repurposing an "Ecobutton", a single-button "keyboard" that sends a key sequence when pressed.
What identifiers are used in hwdb?
To actually find out which hwdb identifiers are being used, you can use udevadm
:
$ sudo udevadm test /sys/class/input/event256 |& grep builtin.command..hwdb
event256: /usr/lib/udev/rules.d/60-evdev.rules:8 Importing properties from results of builtin command 'hwdb --subsystem=input --lookup-prefix=evdev:'
event256: /usr/lib/udev/rules.d/60-evdev.rules:18 Importing properties from results of builtin command 'hwdb 'evdev:name:HID 3412:7856:phys:usb-0000:00:14.0-1.1.2.4/input0:ev:120013:dmi:bvnINSYDECorp.:bvr03.17:bd10/27/2022:br3.17:svnFramework:pnLaptop:pvrAB:rvnFramework:rnFRANBMCP0B:rvrAB:cvnFramework:ct10:cvrAB:skuFRANBMCP0B:''
event256: /usr/lib/udev/rules.d/60-evdev.rules:23 Importing properties from results of builtin command 'hwdb 'evdev:name:HID 3412:7856:dmi:bvnINSYDECorp.:bvr03.17:bd10/27/2022:br3.17:svnFramework:pnLaptop:pvrAB:rvnFramework:rnFRANBMCP0B:rvrAB:cvnFramework:ct10:cvrAB:skuFRANBMCP0B:''
event256: /usr/lib/udev/rules.d/60-input-id.rules:6 Importing properties from results of builtin command 'hwdb --subsystem=input --lookup-prefix=id-input:modalias:'
event256: /usr/lib/udev/rules.d/65-libwacom.rules:19 Importing properties from results of builtin command 'hwdb --subsystem=input '--lookup-prefix=libwacom:name:HID 3412:7856:''
Note that this tries three of the four hwdb commands shown in the udev rules above, the evdev:atkbd one is only done for DRIVERS=="atkbd"
and does not apply to this USB keyboard.
For example, the above output contains:
event256: /usr/lib/udev/rules.d/60-evdev.rules:18 Importing properties from results of builtin command 'hwdb 'evdev:name:HID 3412:7856:phys:usb-0000:00:14.0-1.1.2.4/input0:ev:120013:dmi:bvnINSYDECorp.:bvr03.17:bd10/27/2022:br3.17:svnFramework:pnLaptop:pvrAB:rvnFramework:rnFRANBMCP0B:rvrAB:cvnFramework:ct10:cvrAB:skuFRANBMCP0B:''
It seems this contains a lot of identifiers specific to my laptop, not the USB keyboard I've attached, but the USB vidpid (3412:7856) is contained at the start and could be matched like this:
evdev:name:HID 3412:7856:*
KEYBOARD_KEY_70028=backspace
Alternatively, the evdev output also contains (second line is not shown above due to the grep, but it is there in the full output):
event256: /usr/lib/udev/rules.d/60-evdev.rules:8 Importing properties from results of builtin command 'hwdb --subsystem=input --lookup-prefix=evdev:'
event256: hwdb modalias key: "input:b0003v3412p7856e0100-e0,1,4,11,14,k74,75,77,7D,7E,7F,B7,ram4,l0,1,2,3,4,sfw"
It seems that this hwdb call does not get the full match key specified like in the later ones, but it is built from the prefix and (by hwdb) autogenerated modalias key. So the above results in full key of:
evdev:input:b0003v3412p7856e0100-e0,1,4,11,14,k74,75,77,7D,7E,7F,B7,ram4,l0,1,2,3,4,sfw
This match the USB vid/pid from this, you can use something like:
evdev:input:b????v3412p7856e*
KEYBOARD_KEY_70028=backspace
Note that this matches the busnumber using ?
wildcards, to ensure this still matches when you plug into another USB bus/port. It also uses a *
wildcard for the version number and other modalias stuff after it, which is probably not relevant here.
What scancodes should I use?
The most reliable way to get the right scancode seems to be using evtest. Run it, and then press a button gives something like:
sudo evtest --grab /dev/input/event256
[ snip debug output]
Event: time 1675505630.859393, -------------- SYN_REPORT ------------
Event: time 1675505630.867333, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70028
Event: time 1675505630.867333, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0
Here, the 70028
is the scancode (note that it is actually hexadecimal, but this is also what the KEYBOARD_KEY_xx
expects (e.g. KEYBOARD_KEY_70028=backspace
). Note that it seems that USB keyboards use 700xx scancodes, while old-school AT keyboards seem to use lower scancodes, so best just test with your keyboard rather than look up codes online.
Also note that it seems that these scancodes are written in hexadecimal (with the 0x prefix omitted everywhere), while keycodes are written and parsed in decimal, but I'm not 100% sure.
See also this page
What keycodes should I use?
The canonical source of these keycodes is input-event-codes in the kernel, all the KEY_xxx constants.
It seems that the numeric value, the KEY_xxx and just the xxx value are all accepted. E.g. the following are all equivalent to map the enter key to produce backspace:
KEYBOARD_KEY_70028=14
KEYBOARD_KEY_70028=key_backspace
KEYBOARD_KEY_70028=backspace
It seems the same keycodes are shown by evtest
on startup (as supported EV_KEY event codes), in evtest
output when pressing a key (as EV_KEY
event code)
See also this page
How can I see the current scancode mapping for an input device?
I'm assuming this should be query-able using the input device and some ioctl, but I'm not sure what existing commandline tool can do this for you. If you find out, leave a comment.
It does seem that the supported EV_KEY list printed on evtest
startup provides a hint: That list seems to only contain keycodes that have some scancode mapped to it.
How to apply changes to a hwdb file?
You need to recompile the hwdb files into a /etc/udev/hwdb.bin using:
$ sudo systemd-hwdb update
The replug the USB device, or force udev to re-run its rules using:
$ sudo udevadm trigger /sys/class/input/event256
Where is hwdb called from?
Keys and values in hwdb are not hardcoded in udev, but determined by rules files. For example, /usr/lib/udev/rules.d/60-evdev.rules handles input/keyboard rules:
IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=evdev:", \
IMPORT{builtin}="keyboard", GOTO="evdev_end"
# AT keyboard matching by the machine's DMI data
DRIVERS=="atkbd", \
IMPORT{builtin}="hwdb 'evdev:atkbd:$attr{[dmi/id]modalias}'", \
IMPORT{builtin}="keyboard", GOTO="evdev_end"
# device matching the input device name + properties + the machine's DMI data
KERNELS=="input*", \
IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:phys:$attr{phys}:ev:$attr{capabilities/ev}:$attr{[dmi/id]modalias}'", \
IMPORT{builtin}="keyboard", GOTO="evdev_end"
# device matching the input device name and the machine's DMI data
KERNELS=="input*", \
IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:$attr{[dmi/id]modalias}'", \
IMPORT{builtin}="keyboard", GOTO="evdev_end"
So the above rules show exactly how the hwdb identifiers are constructed. Furthermore, the idea is that 60-evdev.rules and 60-keyboard.hwdb are correlated, but that's just convention - all hwdb entries are put into a single database and can be matched by any udev rule that provides the right identifier.
How are hwdb properties processed?
The effect of these IMPORT{builtin}="hwdb ..."
lines is that the hwdb is consulted and any properties listed in there are exposed as regular udev device properties (i.e. you can query them with udevadm info
).
Furthermore, the rules above also contain IMPORT{builtin}="keyboard"
, which calls a udev builtin utility that processes KEYBOARD_*
properties and uses them to configure scancode-to-keycode mappings in the kernel (using the EVIOCSKEYCODE
ioctl on the input device, see source).
Again, using udevadmin info
as shown above is helpful here, as it should show any keymappings that the builtin decides to apply.
$ sudo udevadm test /sys/class/input/event256 |& grep mapping
event256: keyboard: mapping scan code 18 (0x12) to key code 0 (0x0)
event256: keyboard: mapping scan code 458770 (0x70012) to key code 0 (0x0)
event256: keyboard: mapping scan code 36 (0x24) to key code 0 (0x0)
(note that running udevadm test
like this actually does apply these keymappings directly, even though documentation suggests it should only do a dry run. This is probably a bug, because I suspect the "builtins" mechanism was originally intended to collect info only and not apply changes)
Setting hwdb / KEYBOARD properties from udev rules
Hwdb entries essentially just set device properties, just like udev rules can do directly (but I guess hwdb was invented to make it easier to compactly set multiple properties without needing very verbose udev rules). However, this means that it should be possible to do this key remapping using just udev rules instead of the somewhat more complicated hwdb, e.g.:
SUBSYSTEM=="input", KERNEL=="event3", ENV{KEYBOARD_KEY_01}="capslock", ENV{KEYBOARD_KEY_3a}="esc"
SUBSYSTEM=="input", KERNEL=="event3", IMPORT{builtin}="keyboard"
Note that the udev rules in 60-evdev.rules
that run the "keyboard" plugin do this in the same rule as the hwdb builtin. This means, I believe, that the "keyboard" rule is run only if the hwdb builtin found any information.
If your hwdb rules do not match this particular device, the "keyboard" builtin might not run, so I added an explicit rule above to run it (this must be a separate rule, since when they are in the same rule it seems that the ENV
entries are applied only after the builtin is ran, not before).
If your hwdb rules do match this particular device, you can omit the second rule above and rely on 60-evdev.rules
to run the keyboard builtin (but the the above udev rule should be in a rules file that runs before 60-evdev.rules
, not after. It seems that older versions of udev/eudev called the keyboard builtin using RUN
instead of IMPORT
and did not have this ordering constraint).
Source of part of this info is this issue.
Where are these udev builtins documented?
Nowhere, it seems. See this answer for some pointers into the source for these builtins.