====== 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?]]**