User Tools

Site Tools


doc:appunti:linux:sa:remap_keyboard_keys

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.

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), 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 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 <niccolo@rigacci.org>
#
# 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 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.

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 xevdo 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

doc/appunti/linux/sa/remap_keyboard_keys.txt · Last modified: 2021/12/07 11:50 by niccolo