VOOZH about

URL: https://wiki.archlinux.org/title/Setkeycodes

⇱ Map scancodes to keycodes - ArchWiki


Jump to content
From ArchWiki
(Redirected from Setkeycodes)

Note that the method described on this page can only be used to change the meaning (character/action) of a keyboard key; see Input remap utilities for programs which allow more complex remaps at a similar low level. This page assumes that you have read Keyboard input, which provides wider context.

Mapping scancodes to keycodes is part of the keyboard driver's job [1]. Since this is achieved in a layer lower than Xorg, Wayland and the Linux console, it will be effective in all. [2][3][4] The per-keyboard scancode-keycode mapping table is stored in memory by the keyboard driver and any changes made are not persistent (i.e. when you unplug the device or reboot, all changes will be lost).

Systemd has a standardized way to make changes to the mapping table effectively permanent #Using udev (this is what you're likely here for). Udev provides an infrastructure to manage devices. That includes running udev rules (tiny scripts) when a device is attached to the computer. Some udev rules perform keyboard remapping using the udev hardware database as a source. With the hardware database, it's possible to target a specific keyboard model for scancode-keycode remappings. Systemd uses this itself to assign "unknown scancodes" of many keyboard models to the correct keycode. That means your keys are recognized out of the box when your keyboard model has been found in the hardware database (the corresponding .hwdb file can be found under /lib/udev/hwdb.d/60-keyboard.hwdb).

Alternatively, you can automatically run remapping tools when your desktop session starts. For example, by #Using setkeycodes or #Using evmap. But this way the remappings won't be applied when you plug your keyboard in after your system has booted up or when you reconnect your keyboard.


Technical background

Since linux kernel version 2.6.37 there are 3 ioctl calls that can change one entry of the mapping table and 3 corresponding calls that can query one entry of the mapping table. Namely,

  • ioctl(console_fd, KDSETKEYCODE, *kbkeycode)
  • ioctl(dev_fd, EVIOCSKEYCODE, (unsigned int[]) {scancode, keycode})
  • ioctl(dev_fd, EVIOCSKEYCODE_V2, *input_keymap_entry)

modify a mapping table entry and

  • ioctl(console_fd, KDGETKEYCODE, *kbkeycode)
  • ioctl(dev_fd, EVIOCGKEYCODE, (unsigned int[]) {scancode, keycode})
  • ioctl(dev_fd, EVIOCGKEYCODE_V2, *input_keymap_entry)

query a mapping table entry. console_fd is a file descriptor pointing to a virtual console (it doesn't matter which virtual console) and dev_fd is a file descriptor pointing to a keyboard device file.

The KDSETKEYCODE and KDGETKEYCODE ioctl calls are legacy calls because they are unreliable and it can be hard to predict which device they'll operate on. To be specific, they will iterate over connected devices that satisfy specific rules (which can also match devices that do not correspond to a physical keyboard) and return as soon as one device driver doesn't return an error when asked to remap/query a mapping entry. To make things more problematic, some device drivers may not return an error even though the call didn't have any effect, letting the ioctl call return successfully without any changes at all. The iteration's order is the order of initalization of the devices within the kernel (which is random for devices that were plugged in since boot) [5].

The legacy KD[SG]ETKEYCODE ioctl calls are used by the tools setkeycodes(8) and getkeycodes(8).
The keyboard-specific EVIOC[SG]KEYCODE_V2 ioctl calls are used by evmap.


setkeycodes modifies user-passed scancodes

Notice how setkeycodes wants to be helpful and substracts 0xdf80 (-0xe000 + 128) from every scancode bigger or equal to 0xe000 passed by the user. But this may not be very helpful at all and is intransparent to users that want to remap, for example, a USB keyboard. A scancode of a USB keyboard (to be exact, of the HID protocol) consists of a 16-Bit Usage Page and a 16-Bit Usage ID. The Usage Page for keyboard is 7, normally resulting in a large scancode in the form 0x0007XXXX for USB keyboard keys. If setkeycodes receives such a scancode it will modify it which will make it invalid in case of USB keyboards. The reason why setkeycodes is subtracting 0xdf80 is because setkeycodes matured as a key remapping tool for PS/2 protocol keyboards and doesn't want to change its identity, even though the PS/2 protocol is barely present nowadays. The following paragraph will go into more detail.

IBM's PS/2 protocol was the most widely used protocol for keyboards for the last few decades but is barely present nowadays anymore; only used occasionally for internal laptop keyboards. It defines 3 scancode sets a PS/2 keyboard (i.e. keyboard using the PS/2 protocol) can use. PS/2 keyboards are handled by the atkbd driver. When atkbd receives scancodes from the keyboard side (normally the motherboard's keyboard controller) it processes them before mapping each to a keycode using the mapping table. For scancode set 1 and 2, PS/2 uses ("make") scancodes of the form 0xe0XX for some keys which atkbd then (bitwise) substracts by 0xdf80 to be within the range 0x80 to 0xFF before mapping them to keycodes [6] (set 3 doesn't have scancodes of the form 0xe0XX). This means that for the mapping table atkbd is using different scancodes than the PS/2 protocol for some scancodes (not only E0 prefixed ones). Each PS/2 scancode set has a corresponding atkbd scancode set and depending on the active PS/2 scancode set (the set used by atkbd to parse incoming data from the keyboard side) the corresponding atkbd scancode set is used by the mapping table (disregarding "break" scancodes which are irrelevant for the mapping table, PS/2's set 3 is actually identical to atkbd's set 3). [7] When a program executes a mapping table entry remap/query ioctl call on a PS/2 keyboard it operates directly on the mapping table so the passed scancode must be an atkbd scancode (or generally speaking, a driver scancode). At the time when setkeycodes was written PS/2 was the dominant keyboard protocol. setkeycodes took that into account and was therefore (and still is) processing user-passed scancodes of the form 0xe0XX into atkbd scancodes with the assumption that they are PS/2 scancodes.


Identifying scancodes and keycodes

For each key remapping you need to know the scancode of the key you want to remap and the keycode of the key you want to remap to. See Keyboard input#Identifying scancodes and Keyboard input#Identifying keycodes in console for details.

Using udev

You can use udev's hardware database to override the default scancodes-to-keycodes mapping by following udev#Remap specific device and the update/reload sections after.

Using setkeycodes

Note You may want to avoid setkeycodes because it's a bit of a gamble whether it will remap your keyboard. And even if it works in one boot session that doesn't 100 % guarantee it will work the next time you boot your system. See #Technical background for an in-depth explanation.

setkeycodes(8) is the traditional tool to update scancode-keycode mappings of a keyboard. It comes within the same package as showkey though in modern age it's better to use evtest to find out scancodes. See #Identifying scancodes and keycodes to find out scancodes and keycodes.

Do note that setkeycodes only works as expected with xx and e0xx formatted hexadecimal scancodes. For USB keyboards that use larger scancodes you can do a little workaround by adding 0xdf80 to the scancode number before passing it to setkeycodes (for the reason see #setkeycodes modifies user-passed scancodes).

setkeycodes usage is:

# setkeycodes scancode1 keycode1 scancode2 keycode2 ...

Scancodes are given in hexadecimal (without a leading 0x prefix), keycodes in decimal.

As stated, just executing this command will result in non-persistent changes. The changes can be made permanent by creating a new service:

/etc/systemd/system/setkeycodes.service
[Unit]
Description=Change keycodes at boot

[Service]
Type=oneshot
ExecStart=/usr/bin/setkeycodes [scancode] [keycode] [scancode] [keycode] [...]

[Install]
WantedBy=multi-user.target

and enabling setkeycodes.service.

Using evmap

Note You have to compile evmap yourself.

evmap is a tool similar to setkeycodes to update scancode-keycode mappings of a keyboard, but it's keyboard specific. That means it requires a device file path of the keyboard you want to apply the remapping on. You'd want to use the corresponding keyboard device file in the folder /dev/input/by-id/ since the device file names in here are generated using hardware information, meaning the names (usually) contain information unique to the specific device model they correspond to.

To find out scancodes and keycodes see #Identifying scancodes and keycodes.

evmap's basic usage is:

# evmap -d /dev/input/by-id/your-device-file-here -s scancode1=keycode1 -s scancode2=keycode2 ...

Scancodes are given in hexadecimal (without a leading 0x prefix), keycode numbers in decimal or hexadecimal (for which you need to prefix the number with 0x). Instead of keycode numbers you can also use keycode names. You can print the list of all recognized keycode names with the command:

gcc -E -dM -x c - <<< '#include <linux/input-event-codes.h>' | perl -ne 'if (/^\#define (KEY_(\w+))\s+\S+/) { print "$2\n" }'

evmap has padding issues: If you get the error message Invalid argument or Invalid definition the quick fix is to zero-pad scancodes to 8 digits (eg. 7002e => 0007002e).

As stated, just executing evmap will result in non-persistent changes. The changes can be made permanent by creating a new service:

/etc/systemd/system/evmap.service
[Unit]
Description=Change keycodes at boot

[Service]
Type=oneshot
ExecStart=/usr/bin/evmap -d /dev/input/by-id/your-device-file-here -s scancode1=keycode1 -s scancode2=keycode2 ...

[Install]
WantedBy=multi-user.target

and enabling evmap.service.