Map scancodes to keycodes

Related articles
Xorg/Keyboard configuration
Linux console/Keyboard configuration
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 for simple 1:1 key remaps; see
Input remap utilities
for programs which allow more complex remaps at a similar low level.
The
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
.
Udev
provides an infrastructure to manage devices. That includes matching the device's modalias (a hardware identifier) of every newly connected device against the entries in the udev hardware database. If there's a match, specific things can be applied, including
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 like
setkeycodes(8)
or
evmap
when your desktop session starts. For example, by using a systemd service. 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-given scancodes
Notice how
setkeycodes
wants to be helpful and substracts 0xdf80 (-0xe000 + 128) from every scancode bigger than 0xe000 before calling ioctl. 
But this may not be very helpful at all and is intransparent to users that want to remap, for example, an USB keyboard.
The scancodes of USB keyboards normally consist of a 16-Bit Usage Page and a 16-Bit Usage ID. The Usage Page for
keyboard
is 7, resulting in large scancodes in the form 0x0007XXXX which is above 0xe000 and are therefore modified by
setkeycodes
, resulting in an error.
I'm not too sure why
setkeycodes
subtracts 0xdf80 but I have a theory:
First of all, it's ambiguous what exactly a
scancode
is.
Converting the raw byte stream to a
keycode
stream involves many operations and across different protocolls it can be hard to tell at which step we have a scancode (and what belongs to a scancode).
In this article a
scancode
is a number (i.e. byte array) that corresponds to a key on the keyboard and can be passed to the keyboard driver to query or set the corresponding keycode.
But this doesn't match the definition the
scancode wikipedia article
is giving.
According to the wikipedia article
scancodes
are the key-related bytes that are transfered when a key is pressed or released (refered to as
wikipedia scancode
from now on). The difference between a
wikipedia scancode
and an actual
scancode
is that a
wikipedia scancode
can be more raw (unprocessed) and may contain encoding artifacts. For a given keyboard,
scancodes
are defined by the developer of the keyboard driver and
wikipedia scancodes
are defined by the protocol.
IBM PS/2 was the most widely used protocol for keyboards for the last couple of decades but is currently declining. However it's still sometimes/often used for internal laptop keyboards (If your keyboard is named
AT Translated Set 2 keyboard
it uses the PS/2 protocol).
The byte 0xe0 is used to indicate trailing bytes as part of the encoding.
My guess is that the drivers for PS/2 compatible keyboards process the
wikipedia scancodes
before substituting them with keycodes.
All
wikipedia scancodes
above 0xe000 are subtracted by 0xdf80 to be within the range 0x80 to 0xFF (In the pre-2000s
scancodes
were typically in the range 0x00 to 0x7F so there should be no overlap).
Those
wikipedia scancodes
under 0x80 are just used directly as
scancodes
.
This creates a hole-less range of
scancodes
which is easier to implement (dense mapping table).
Normally, tools that query the
scancodes
of keyboards should show
scancodes
, but my guess is that back in the pre-2000s (the time when
setkeycodes
was written) some tools showed the actual bytes transfered (
wikipedia scancodes
) which people used to pass directly to
setkeycodes
. Since that returned an error,
setkeycodes
wanted to be helpful: Because almost all keyboards used the PS/2 protocol and
scancodes
were typically one byte,
setkeycodes
itself processed too large
scancodes
(that are likely of type
wikipedia scancode
) to
scancodes
that PS/2 keyboard drivers understand. This paragraph is pure speculation, so take it with a grain of salt.
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
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
(see
#setkeycodes modifies user-given 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 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),
keycodes
in decimal or hexadecimal (for which you need to prefix the number with 0x).
You need to pad
scancodes
to full bytes, e.g. instead of
3
write
03
and instead of
123
write
0123
(otherwise you'll get the non-descriptive error message
Invalid definition
).
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" }'
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
.