Jump to content

Map scancodes to keycodes

From ArchWiki

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] Note that this method 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.

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 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 KDSETKEYCODE ioctl and KDGETKEYCODE ioctl calls are used by the tools setkeycodes(8) and getkeycodes(8).
The device-specific EVIOC[SG]KEYCODE_V2 ioctl calls are used by evmap.


setkeycodes modifies user-passed scancodes

Notice how setkeycodes wants to be helpful and internally 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.

To explain why this can be helpful we first have to start with PS/2. IBM's PS/2 protocol was the most widely used keyboard protocol for the last few decades but is barely present nowadays. PS/2 keyboards are handled by the atkbd driver. When atkbd receives scancodes from a keyboard it processes them before mapping each to a keycode using the mapping table. In most cases (to be exact, for scancode set 1 and 2 which are the most popular), PS/2 uses ("make") scancodes of the form 0xe0XX for some keys which atkbd then effectively substracts by 0xdf80 to be within the range 0x80 to 0xFF before mapping them to keycodes [6]. This means that the atkbd driver is using different scancodes internally that the PS/2 keyboard protocol. When a program executes a mapping table change/inquiry ioctl call on a PS/2 keyboard it operates directly on the mapping table so the scancode has to comply with the internally used scancode set of atkbd. At the time when setkeycodes was written PS/2 was the dominant keyboard protocol. setkeycodes took that into account and was therefore processing user-passed PS/2 scancodes of the form 0xe0XX in the same way as atkbd to correctly convert the scancode into the corresponding scancode used internally by atkbd.

To conclude, the reason why setkeycodes is subtracting 0xdf80 is because setkeycodes matured as a keyboard remapping tool for PS/2 keyboards and doesn't want to change its identity, even though the PS/2 protocol is barely present nowadays; only used occasionally for internal laptop keyboards.


Identifying scancodes

You need to know the scancodes of keys you wish to remap. See Keyboard input#Identifying scancodes 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 a tool to update scancode-keycode mappings of a keyboard. It is best to use it with showkey. See Keyboard input#Using showkey and Keyboard input#Identifying keycodes in console.

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.

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.