A user-friendly guide to remap keyboard keys in Linux.
Before begin, it's better to explore these resources:
- About sysfs in Wikipedia or Linux kernel docs.
- About udev in Wikipedia or Arch docs.
- What is the sysfs modalias.
- What is the scancodes.
- How to identify scancodes.
- How to map scancodes to keycodes.
In short, keyboards send scancodes to the computer and the Linux kernel maps the scancodes to keycodes. The keycodes are some interpretation of scancodes. They exist because different keyboards can send different scancodes to the same button, or the same scancode to different buttons. On a ThinkPad keyboard, the screenshot tool button sends a scancode of 46, while the Q
button sends the same scancode, for example.
So in this guide we will map scancodes to keycodes using udev. We will take a ThinkPad T14 Gen 3 and map Fn + PrtScr
to the Menu
keycode so that this combination opens a context menu.
First we should select the input device we want to work with. They are listed in the /dev/input
directory as event1
, event2
, event3
etc., and the easiest way to find the one we need is to run the evtest
utility:
# evtest
The utility will print a list of devices, by selecting one of them, we can get reports by pressing buttons on the selected input device.
So we can find that the device AT Translated Set 2 keyboard is listed as /dev/input/event3
on the ThinkPad T14 Gen 3, but note that most ThinkPad keyboards work as two separate input devices: AT Translated Set 2 keyboard and ThinkPad Extra Buttons. So, the AT Translated Set 2 keyboard works like a typical keyboard, and the ThinkPad Extra Buttons represents functions that are available in combination with the Fn
button, such as sleep mode (Fn + 4
) and screenshot (Fn + S
). For a deeper experience, we'll work with the ThinkPad Extra Buttons (/dev/input/event4
).
After choosing the right input device, we need to find the scancode of the button we want to remap. At this stage, by running the evtest
utility again, selecting the right input device and pressing the button, we will get the report like this:
...
Event: time 1726200548.302346, type 4 (EV_MSC), code 4 (MSC_SCAN), value 46
Event: time 1726200548.302346, type 1 (EV_KEY), code 149 (KEY_PROG2), value 1
Event: time 1726200548.302346, -------------- SYN_REPORT ------------
Event: time 1726200548.302363, type 1 (EV_KEY), code 149 (KEY_PROG2), value 0
Event: time 1726200548.302363, -------------- SYN_REPORT ------------
Here we need to look at the value of MSC_SCAN
and as you can see the value of combination Fn + PrtScr
is 46
and the scancode is KEY_PROG2
. Thus, we found out that the scancode 46
of the PrtScr
button of the ThinkPad Extra Buttons is mapped as the KEY_PROG2
keycode.
So we already know the right scancode and the next step is to find the right keycode. A better way is to look at the default Linux keycodes in the /usr/include/linux/input-event-codes.h
file. Here we have to look at the KEY_*
definitions and for our example the variants are: KEY_MENU
, KEY_CONTEXT_MENU
, KEY_ROOT_MENU
, KEY_MEDIA_TOP_MENU
, KEY_BRIGHTNESS_MENU
, KEY_KBD_LCD_MENU1
, KEY_KBD_LCD_MENU2
, KEY_KBD_LCD_MENU3
, KEY_KBD_LCD_MENU4
and KEY_KBD_LCD_MENU5
. The KEY_MENU
and KEY_CONTEXT_MENU
keycodes look right ones, but we can be determine which one will work by trying them. In order, not to waste time the right keycode for our example is KEY_MENU
.
Ok, we know the scancode and what the keycode to map it to, and the mapping is best done for a specific device, but not for all, so we need to find the ID of the input device. There are two options: the kernel modalias and the device name with DMI:
- We can find modalias in the sysfs input directory:
$ cat /sys/class/input/event4/device/modalias
The output for this example will be:
input:b0019v17AAp5054e4101-e0,1,4,5,k71,72,73,78,8C,8E,90,93,94,95,98,9C,9E,AB,AD,BE,BF,C2,CA,CB,CD,D4,D8,D9,DA,DF,E0,E1,E3,E4,EC,ED,EE,F0,168,174,176,1D2,1DB,1DC,246,250,27A,ram4,lsfw3,
which we can shorten to:
input:b0019v17AAp5054e4101*
- For the second option, we can also find in the
/sys/class/input/event4/device/name
and/sys/devices/virtual/dmi/id/modalias
, but it's better to useevemu-describe
command from theevemu
package:
# evemu-describe /dev/input/event4
From the output, we need to look at the DMI and Input device name rows and concatenate them into a string:
name:ThinkPad Extra Buttons:dmi:bvnLENOVO:bvrN3MET09W(1.06):bd10/11/2022:br1.6:efr1.12:svnLENOVO:pn21AH00B9RA:pvrThinkPadT14Gen3:rvnLENOVO:rn21AH00B9RA:rvrNotDefined:cvnLENOVO:ct10:cvrNone:skuLENOVO_MT_21AH_BU_Think_FM_ThinkPadT14Gen3:
which we can shorten to:
name:ThinkPad Extra Buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:*
Before moving to the next step, it's best to check the default keycode mapping for availability of the required input device and use that ID, as the option from the default mapping might intefere with your option. The default keycode mappings are defined in /usr/lib/udev/hwdb.d/60-keyboard.hwdb
. And for our example there is a default mapping:
...
###########################################################
# Lenovo
###########################################################
# thinkpad_acpi driver
evdev:name:ThinkPad Extra Buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:*
KEYBOARD_KEY_01=screenlock
...
KEYBOARD_KEY_46=prog2 # Fn + PrtSc, on Windows: Snipping tool
...
Therefore, it's better to use the name:ThinkPad Extra Buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:*
ID instead of input:b0019v17AAp5054e4101*
.
Finally, we can create a file with our mapping. Each map consists of a pair: KEYBOARD_KEY_
+ scancode and keycode in lowercase:
KEYBOARD_KEY_46=menu
And with the input ID our file will looks like this:
evdev:name:ThinkPad Extra Buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:*
KEYBOARD_KEY_46=menu
Note that before the input ID should be the
evdev:
prefix. So we can create a file for the hardware database with this command:
# echo -e 'evdev:name:ThinkPad Extra Buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:*\n KEYBOARD_KEY_46=menu' > /etc/udev/hwdb.d/90-remap.hwdb
Note that the file name doesn't matter, but the number at beggining of the file name must be greater than the number of all
.hwdb
files that have mappings for the input device.
After creating a file with the remap, we need to update the Hardware Database Index:
# systemd-hwdb update
and reload it:
# udevadm trigger
Now we can check if our remap is applied:
# udevadm info /dev/input/event4 | grep KEYBOARD_KEY
The output should have our remap:
...
E: KEYBOARD_KEY_46=menu
...
We can also check the remap using the evtest
utility.
Next, it's best to reboot the system because X11 and Wayland may not see you changes until the reboot. After rebooting, it's best to check the remap in xev
for X11 or wev
for Wayland, because display servers have their own understanding of keycodes, and things can go wrong.
The display server may identify some keycodes incorrectly because the XKB (from the libxkbcommon
package) has its own understanding of keycodes. In /usr/share/X11/xkb/keycodes/evdev
you can find translations from evdev
keycodes (scancodes) to the xfree86
variant.
So the KEY_MENU
keycode from the example above is indentified as XF86MenuKB
but not as Menu
, and thus doesn't open a context menu in the GUI. In this case, it's better to check the /usr/share/X11/xkb/keycodes/evdev
file and look at the aliases, where we can find the alias for our Menu
:
alias <MENU> = <COMP>;
Which means the Comp
keycode works as Menu
and looking below we can find another one alias:
alias <I135> = <COMP>; // #define KEY_COMPOSE 127
At the end of the line, the comment tells us the KEY_COMPOSE
keycode, and looking in the /usr/include/linux/input-event-codes.h
file, we can find the same code:
#define KEY_COMPOSE 127
So using the KEY_COMPOSE
keycode:
# echo -e 'evdev:name:ThinkPad Extra Buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:*\n KEYBOARD_KEY_46=compose' > /etc/udev/hwdb.d/90-remap.hwdb
solves the problem, but yeah, it's super unfriendly and not unintuitive.
Alternatively, you can try configure XKB.