This is an old revision of the document!
Table of Contents
Remap keyboard keys in GNU/Linux
How to swap primary and secondary functions of the Fn key.
I have a Teclast F6 notebook where the function keys (F1, F2, … F12) are mapped on the keyboard as secondary: you have to press them together with the Fn key to get the function key. The primary function of the keys are the multimedia actions, like MUTE, VOLUMEDOWN, VOLUMEUP, PREVIOUSSONG, NEXTSONG, etc.
I searched a recipe to swap the first function of the key with the secondary one. The recipe here explained works almost at 100%, both into the textual console and into the X.org graphical environment. Unfortunately I was unable to swap the F1/DISPLAYTOGGLE, F6/BRIGHTNESSDOWN and F7/BRIGHTNESSUP keys.
Pressing the BRIGHTNESSDOWN and BRIGHTNESSUP keys generates ACPI events: this bypass the kernel keboard subsystem. The events are processed before they arrive to the input subsystem, so you can't see them unsing tools like xev or evtest. You can view ACPI events by running acpi_listen (from the acpid Debian package, the acpid service must be started):
acpi_listen video/brightnessdown BRTDN 00000087 00000000 K video/brightnessup BRTUP 00000086 00000000 K
It seems that the LCD (DISPLAYTOGGLE) multimedia function is intercepted by the hardware and it is not handled as an input event nor as an ACPI event by the operating system.
Inspect the events generated by the keyboard
Use the lsinput command line tool (from the input-utils Debian package) to discover the input number associated to the keyboard. In my case it is input #0:
lsinput /dev/input/event0 bustype : BUS_I8042 vendor : 0x1 product : 0x1 version : 43841 name : "AT Translated Set 2 keyboard" phys : "isa0060/serio0/input0" bits ev : (null) (null) (null) (null) (null) ...
Use the evtest command line tool (from the omonymous Debian package) to inspect the input events generated by you keyboard. In my case the keyboard is associated to input device #0, the evtest program is run by root
into a text console or into a terminal:
evtest No device specified, trying to scan all of /dev/input/event* Available devices: /dev/input/event0: AT Translated Set 2 keyboard ... Select the device event number [0-17]: 0
Pressing and releasing the VOLUMEUP key (this is the F4 key without the Fn modifier keys), generates the following events:
Event: time 1620744541.871139, -------------- SYN_REPORT ------------ Event: time 1620744550.791890, type 4 (EV_MSC), code 4 (MSC_SCAN), value b0 Event: time 1620744550.791890, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 1 Event: time 1620744550.791890, -------------- SYN_REPORT ------------ Event: time 1620744550.892937, type 4 (EV_MSC), code 4 (MSC_SCAN), value b0 Event: time 1620744550.892937, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 0
Pressing and releasing the Fn+F4 key generates the following events:
Event: time 1620744539.251257, -------------- SYN_REPORT ------------ Event: time 1620744541.769000, type 4 (EV_MSC), code 4 (MSC_SCAN), value 3e Event: time 1620744541.769000, type 1 (EV_KEY), code 62 (KEY_F4), value 1 Event: time 1620744541.769000, -------------- SYN_REPORT ------------ Event: time 1620744541.871139, type 4 (EV_MSC), code 4 (MSC_SCAN), value 3e Event: time 1620744541.871139, type 1 (EV_KEY), code 62 (KEY_F4), value 0
You have to take note of the MSC_SCAN values (0xb0 and 0x3e respectively) and the EV_KEY labels (KEY_VOLUMEUP and KEY_F4 respectively).
The scancodes shown by the evtest program should be the sames shown by the showkey command line tool (from the kbd Debian package), with the difference that showkey
operates at a lower level :
showkey --scancode
Customize events using udev
You have to create a file, e.g. /etc/udev/hwdb.d/90-custom-keyboard.hwdb with the following:
evdev:atkbd:dmi:* KEYBOARD_KEY_b0=f4 KEYBOARD_KEY_3e=volumeup
As you can see, the scan code and the event code are associated in swapped order. The event code must be written in lowercase and without the KEY_ prefix.
The first line of the file is a device selector, it must match your keyboard. The evdev:atkbd:dmi:* string will eventually match any keyboard attached to the host. If you want to be more specific (e.g. to match only the keyboard of a notebook, but not an external one attached via USB), you can use the evemu-describe tool from the Debian package evemu-tools and look for a more specific descriptor; e.g. to match the keyboard of a Teclast F6 notebook I used the following selector:
evdev:atkbd:dmi:bvnTECLAST:bvr*:bd*:svnTECLAST:pnF6:pvr*
Every line following the device selector, must be indented by one space and cannot contain comments.
You can get the label of event codes (to be placed at the right of the equal sign) by reading the EV_KEY part printed by evtest
or by inspecting the file /usr/include/linux/input-event-codes.h. You must remove the KEY_ prefix and convert the label to lowercase.
To update the hardware database and to trigger a kernel device coldplug event:
systemd-hwdb update udevadm trigger --verbose /dev/input/event0
You can also check that your changes were effective using udevadm:
udevadm info /dev/input/event0 ... P: /devices/platform/i8042/serio0/input/input0/event0 ... E: KEYBOARD_KEY_3e=volumeup E: KEYBOARD_KEY_b0=f4 ...
WARNING: If you remove some key binding from the configuration file, triggering the coldplug event is not sufficient to remove the keybinding from the running kernel; you have to reboot.
Configuration example for the Teclast F6 notebook
The following is the file /etc/udev/hwdb.d/90-custom-keyboard.hwdb which I use on my Teclast F6 notebook:
# /etc/udev/hwdb.d/90-custom-keyboard.hwdb # # Keyboard remapping for the Teclast F6 notebook. # 2021-05-13 Niccolo Rigacci <niccolo@rigacci.org> # # The following udev hwdb configuration swaps the Fn behaviour # on keys F2, F3, F4, F8, F9, F10, F11 and F12. # It also binds the Fn+ESC to Ctrl+LeftWinLogo+Esc, which can # be used as keyboard shortcut into XFCE or other desktop # environments to execute a script and toggle the touchpad. # # Function keys F1, F6 and F7 cannot be remapped using # udev Hardware Database (as far as I know). # # To make this file effective execute: # systemd-hwdb update # udevadm trigger --verbose /dev/input/event0 # # To view current binding: # udevadm info /dev/input/event0 # # * Use evemu-describe to view the keyboard DMI selector. # * Use evtest to view hex codes of the KEYBOARD_KEY_* # (look at the MSC_SCAN value). # * Use evtest or grep /usr/include/linux/input-event-codes.h # to view the EV_KEY labels (remove the 'KEY_' prefix and # convert to lowercase). # # The Fn+ESC key produces three keys: # KEYBOARD_KEY_1d => code 29 KEY_LEFTCTRL # KEYBOARD_KEY_db => code 125 KEY_LEFTMETA (LeftLogo) # KEYBOARD_KEY_76 => code 85 KEY_ZENKAKUHANKAKU # # Function keys F1, F6 and F7 do not generate events # (verified with evtest and showkey), so they cannot # be remapped. # # The following EV_KEY do not work: # KEY_DISPLAYTOGGLE # KEY_BRIGHTNESSDOWN # KEY_BRIGHTNESSUP # evdev:atkbd:dmi:bvnTECLAST:bvr*:bd*:svnTECLAST:pnF6:pvr* KEYBOARD_KEY_a0=f2 KEYBOARD_KEY_ae=f3 KEYBOARD_KEY_b0=f4 KEYBOARD_KEY_90=f8 KEYBOARD_KEY_99=f9 KEYBOARD_KEY_c5=f10 KEYBOARD_KEY_d2=f11 KEYBOARD_KEY_b7=f12 KEYBOARD_KEY_3c=mute KEYBOARD_KEY_3d=volumedown KEYBOARD_KEY_3e=volumeup KEYBOARD_KEY_42=previoussong KEYBOARD_KEY_43=nextsong KEYBOARD_KEY_44=pause KEYBOARD_KEY_57=insert KEYBOARD_KEY_58=sysrq KEYBOARD_KEY_76=esc
If you are interested, look at the page Disable notebook touchpad in GNU/Linx to know how to enable the Fn+ESC touchpad toggle key.
Scancodes and keycodes
Using showkey --scancodes
you can see that some keyboard keys produce scancodes that the kernel does not associate to any keycode (action). You can use setkeycodes
to make such an association.
xmodmap
The xmodmap command is used to modify keymaps in X; this method does not work into the textual console.
The current keymap table (see the
-pke
option below) shows several keysym names for each keycode, e.g. the F5 key has 15 keysyms (are they are associated with different modifiers? Which?):
keycode 71 = F5 F5 F5 F5 F5 F5 XF86Switch_VT_5 F5 F5 XF86Switch_VT_5 F5 F5 F5 F5 XF86Switch_VT_5
A keymap associates keycodes to keysyms. Each keycode can be associated to several keysyms: they are used upon the modifier key that is pressed in conjunction with this key. The modifiers are ( four modifiers, but the table has more!):
- No modfier
- Shift - This is the shift modfier: keys Shift_L keycode 50 (0x32) or Shift_R keycode 62 (0x3e).
- Mode_switch - This is the mod1 modifier: Alt_L keycode 64 (0x40).
- Shift+Mode_switch
To view the modifier map use xmodmap -pm (each modifier can be activated by up to 4 different keys).
WARNING: xmodmap
handles keycodes, which are not the scancodes nor the keycodes shown by showkey
.
To view all the key bindings, e.g. the keycodes and the associated keysyms, execute:
xmodmap -pke
You can see keysym for regualr keys (e.g. F1) and for special multimedia keys (e.g. XF86AudioLowerVolume):
Example: map F5 to VOLUMEDOWN:
xmodmap -e "keycode 71 = XF86AudioLowerVolume"
These keysyms do not produce the expected action; i.e. they don't do anything, despite I associate them to a key and depite that evtest
and xev
do report the expected action:
- XF86TouchpadToggle, XF86TouchpadOn, XF86TouchpadOff
- XF86KbdBrightnessDown, XF86MonBrightnessUp
ACPI and evemu
To be completed
Install the acpid and evemu-tools Debian packages. Using acpi_listen discover what ACPI event is generated by one ACPI key (you can run it as a regular user):
acpi_listen button/mute MUTE 00000080 00000000 K
Then create a file /etc/acpi/events/mute-custom with:
event=button[ /]mute action=/etc/acpi/actions/mute-custom.sh %e
The script will be run whenever that ACPI event is generated, this example will simulate the press and release of the F6 function key:
#!/bin/sh sleep 0.15 evemu-event /dev/input/event3 --type EV_KEY --code KEY_F6 --value 1 --sync evemu-event /dev/input/event3 --type EV_KEY --code KEY_F6 --value 0 --sync
WARNING: The sleep command was determined empirically, may be it is required to let the default ACPI routine to complete (in our case the mute pop-up is shown).
: How to disable the default ACPI handler? How to execute the default ACPI handler on another keypress?