====== 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.
{{..:teclast:swap-fn-key-notebook-keyboard.jpg?480|Function keys require Fn combination}}
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** key.
Pressing the BRIGHTNESSDOWN and BRIGHTNESSUP keys generates ACPI events: this is a different layer than the //input subsystem//. 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 is possibile to customize the ACPI events to reassign the brightness keys to plain function keys, but it is not the preferred way (see [[#acpi_and_evemu|ACPI and evemu]]), because it requires to run an additional sofware layer (the acpid daemon) and the execution of slow action scripts. The simplest method is to **[[#customize_events_using_udev_and_hwdb|customize input events using udev and hwdb]]**.
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 FIXME:
showkey --scancode
===== Events generated by the brightness keys =====
The BRIGHNETSSDOWN and BRIGHTNESSUP keys are connected to **Intel HID events**, a different input device then the keyboard, use lsinput to view:
lsinput
...
/dev/input/event7
bustype : BUS_HOST
vendor : 0x0
product : 0x0
version : 0
name : "Intel HID events"
bits ev : (null) (null) (null)
...
Event: time 1638867405.216577, type 4 (EV_MSC), code 4 (MSC_SCAN), value 14
Event: time 1638867405.216577, type 1 (EV_KEY), code 224 (KEY_BRIGHTNESSDOWN), value 1
Event: time 1638867405.216577, -------------- SYN_REPORT ------------
Event: time 1638867405.216612, type 1 (EV_KEY), code 224 (KEY_BRIGHTNESSDOWN), value 0
Event: time 1638867405.216612, -------------- SYN_REPORT ------------
Event: time 1638867406.704642, type 4 (EV_MSC), code 4 (MSC_SCAN), value 13
Event: time 1638867406.704642, type 1 (EV_KEY), code 225 (KEY_BRIGHTNESSUP), value 1
Event: time 1638867406.704642, -------------- SYN_REPORT ------------
Event: time 1638867406.704677, type 1 (EV_KEY), code 225 (KEY_BRIGHTNESSUP), value 0
Event: time 1638867406.704677, -------------- SYN_REPORT ------------
===== Customize events using udev and hwdb =====
We will use **udev** and **hwdb** to customize the actions associated to the input events, basically to swap the effects of keys from **F2** to **F12** keys with the effects of the same keys combined with the **Fn** key.
We create a file named **/etc/udev/hwdb.d/90-teclast-f6-keyboard.hwdb** with the following:
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_40=brightnessdown
KEYBOARD_KEY_41=brightnessup
KEYBOARD_KEY_42=previoussong
KEYBOARD_KEY_43=nextsong
KEYBOARD_KEY_44=pause
KEYBOARD_KEY_57=insert
KEYBOARD_KEY_58=sysrq
KEYBOARD_KEY_76=esc
evdev:name:Intel HID events:dmi:bvnTECLAST:bvr*:bd*:svnTECLAST:pnF6:pvr*
KEYBOARD_KEY_14=f6
KEYBOARD_KEY_13=f7
We have two **device selector** lines (the ones starting with ''evdev''). They must match the **AT keyboard** and the **Intel HID events** input devices using the **name** and the **DMI** descriptor (for brevity we used asterisk wildcards). To view both the name and the DMI descriptor, run the **evemu-describe** tool provided by the **evemu-tools** Debian package.
After each selector there is a list of **key=value** entries, one per line; they must be indented by one space and cannot contain comments. The //key// is the **MSC_SCAN** code generated when a key is pressed on the input device. The //value// is an **EV_KEY** event code that will be acted upon by the input subsystem.
To know the **MSC_SCAN** codes (to be placed to the left of the equal sign) you can look at what is reported by the **evtest** command line tool. The numeric code must be prefixed with **KEYBOARD_KEY_**.
Also the **EV_KEY** codes (to be placed to the right of the equal sign) can be obtained from the **evtest** command line tool, they are something line **KEY_F4** or **KEY_BRIGHTNESSUP**. You can know other codes by browsing 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
udevadm trigger --verbose /dev/input/event7
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_3d=volumedown
E: KEYBOARD_KEY_3e=volumeup
E: KEYBOARD_KEY_40=brightnessdown
E: KEYBOARD_KEY_41=brightnessup
...
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-teclast-f6-keyboard.hwdb** which I use on my Teclast F6 notebook:
# /etc/udev/hwdb.d/90-teclast-f6-keyboard.hwdb
#
# Keyboard remapping for the Teclast F6 notebook.
# 2021-12-07 Niccolo Rigacci
#
# The following udev hwdb configuration swaps the Fn behaviour
# on keys F2, F3, F4, F6, F7, 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 key F1 cannot be remapped using udev Hardware
# Database (as far as I know).
#
# To make this file effective execute (please check the
# input number using lsinput):
# systemd-hwdb update
# udevadm trigger --verbose /dev/input/event0
# udevadm trigger --verbose /dev/input/event7
#
# To view current bindings:
# 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
#
# Fn+F6 and Fn+F7 produce keys 64 and 65 (0x40 and 0x41).
#
# Function keys F1 does not generate events (verified with
# evtest and showkey), so it cannot be remapped.
# Teclast AT Keyboard input device.
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_40=brightnessdown
KEYBOARD_KEY_41=brightnessup
KEYBOARD_KEY_42=previoussong
KEYBOARD_KEY_43=nextsong
KEYBOARD_KEY_44=pause
KEYBOARD_KEY_57=insert
KEYBOARD_KEY_58=sysrq
KEYBOARD_KEY_76=esc
# F6 and F7 are connected to input "Intel HID events", not keyboard.
evdev:name:Intel HID events:dmi:bvnTECLAST:bvr*:bd*:svnTECLAST:pnF6:pvr*
KEYBOARD_KEY_14=f6
KEYBOARD_KEY_13=f7
If you are interested, look at the page **[[touchpad_disable]]** 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.
FIXME 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 (FIXME 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 despite that ''evtest'' and ''xev''do report the expected action:
* XF86TouchpadToggle, XF86TouchpadOn, XF86TouchpadOff
* XF86KbdBrightnessDown, XF86MonBrightnessUp
===== ACPI and evemu =====
**WARNING**: This method is deprecated: it cannot actually swap F6/F7 with Fn+F6/Fn+F7. Used alone it can rempap BrightnessDown and BrightnessUp with F6 and F7, but it cannot remap the vice-versa. It also requires the //acpid// service and it requires the execution of slow scripts on each keypress.
With this recipe we simulate a **keyboard event** (e.g. a function key press/release) when an **ACPI event** occurs (eg. when the **BrightnessDown** or **BrightnessUp** buttons are pressed).
Install the **acpid** and **evemu-tools** Debian packages, then enable and start the **acpid** systemd service. Using **acpi_listen** discover what ACPI event is generated by the ACPI key (you can run it as a regular user):
acpi_listen
video/brightnessdown BRTDN 00000087 00000000 K
video/brightnessup BRTUP 00000086 00000000 K
Create the files **/etc/acpi/events/brightness-custom** with:
event=video/brightness.*
action=/etc/acpi/actions/brightness-custom.sh %e
The script **/etc/acpi/actions/brightness-custom.sh** will be run whenever a matching ACPI event is generated (notice the regex match syntax following the ''event=''). The following example will simulate the press and release of the **F6** and **F7** function keys using the **evemu-event** tool. Notice that the **/dev/input/event0** device is actually the keyboard, as reported by **lsinput**:
#!/bin/sh
sleep 0.05
case "$2" in
BRTDN)
evemu-event /dev/input/event0 --type EV_KEY --code KEY_F6 --value 1 --sync
evemu-event /dev/input/event0 --type EV_KEY --code KEY_F6 --value 0 --sync
;;
BRTUP)
evemu-event /dev/input/event0 --type EV_KEY --code KEY_F7 --value 1 --sync
evemu-event /dev/input/event0 --type EV_KEY --code KEY_F7 --value 0 --sync
;;
esac
WARNING: The **sleep** command was determined empirically, may be it is required to let the default ACPI routine to complete.
Once the acpid service is restarted, every time you press the **BrightnessDown** and **BrightnessUp** keys, you get the key **F6** or **F7** simulated events **along with the default brightness adjust**.
To disable (or remap) the default brightness control behaviour we need to customize the input subsystem, disabilng or remapping the **Intel HID events** device. In this case customizing ACPI becomes pointless: you can obtain all the required mapping using only udev and hwdb (see above), leaving ACPI alone.
On different hardware, if the multimedia key generates a plain **keyboard keycode**, you can use ''xmodmap'' to remap it, but the mapping only works under the Xserver environment.
===== Web References =====
* **[[https://wiki.archlinux.org/title/map_scancodes_to_keycodes|Map scancodes to keycodes]]**
* **[[https://wiki.archlinux.org/index.php/Keyboard_input|Keyboard input]]**
* **[[https://wiki.ubuntu.com/Hotkeys/Architecture|Hotkeys Architecture]]**
* **[[https://unix.stackexchange.com/questions/110624/what-do-the-kernel-parameters-acpi-osi-linux-and-acpi-backlight-vendor-do/|What do the kernel parameters acpi_osi=linux and acpi_backlight=vendor do?]]**