Fake key code for remapped keyboard keys

I have remapped my caps lock to backspace.

/etc/default/keyboard

XKBLAYOUT="us"
XKBVARIANT="altgr-intl"
BACKSPACE="guess"
XKBOPTIONS="caps:backspace"

This works pretty great, except that it doesn’t work for some specific tools. I used xev to find out what’s going on.

Backspace pressed

KeyPress event, serial 38, synthetic NO, window 0x2400001,
    root 0x159, subw 0x0, time 1028211, (335,635), root:(452,749),
    state 0x10, keycode 22 (keysym 0xff08, BackSpace), same_screen YES,
    XLookupString gives 1 bytes: (08) "
    XmbLookupString gives 1 bytes: (08) "
    XFilterEvent returns: False

KeyRelease event, serial 38, synthetic NO, window 0x2400001,
    root 0x159, subw 0x0, time 1028272, (335,635), root:(452,749),
    state 0x10, keycode 22 (keysym 0xff08, BackSpace), same_screen YES,
    XLookupString gives 1 bytes: (08) "
    XFilterEvent returns: False

Caps lock pressed

KeyPress event, serial 38, synthetic NO, window 0x2400001,
    root 0x159, subw 0x0, time 859789, (391,558), root:(508,672),
    state 0x10, keycode 66 (keysym 0xff08, BackSpace), same_screen YES,
    XKeysymToKeycode returns keycode: 22
    XLookupString gives 1 bytes: (08) "
    XmbLookupString gives 1 bytes: (08) "
    XFilterEvent returns: False

KeyRelease event, serial 38, synthetic NO, window 0x2400001,
    root 0x159, subw 0x0, time 859875, (391,558), root:(508,672),
    state 0x10, keycode 66 (keysym 0xff08, BackSpace), same_screen YES,
    XKeysymToKeycode returns keycode: 22
    XLookupString gives 1 bytes: (08) "
    XFilterEvent returns: False

I also tried it in the browser using

addEventListener('keyup', event => {
  console.log(event.keyCode, event.key, event.code)
})

This logs the following when I press backspace and caps lock

8 "Backspace" "Backspace"
8 "Backspace" "CapsLock"

So basically my caps lock is remapped to backspace, but it only works if tools and websites use proper checks. I don’s want to file a bug report for every tool or website I ever use that implements this incorrectly.

Is it possible to map caps lock in such a way that it fully emulates a backspace instead of acting like a remapped caps lock?


First, let's see how a key press is processed (taken from this answer):

/keyboard/ → scancode → /input driver/ → keycode → /X server XKB/ → keysym

The scancode is a device specific code that is bound to a specific key and can differ between different vendors/products. keycode and keysym are propagated to applications. The keycode serves as an abstraction layer as it is device agnostic and locale agnostic. The same keycode can produce different keysyms, depending on the locale and the state of modifier keys. That is the reason why some applications only look for the keycode, especially when dealing with keyboard shortcuts.

So our goal is to map the scancode of your CapsLock key to the keycode of the BackSpace key. Applications will then see the same keycode and keysym regardless whether you press BackSpace or CapsLock.

This mapping is done by udev using the hardwaredatabase file (hwdb.bin) which is compiled from .hwdb files in both /lib/udev/hwdb.d/ and /etc/udev/hwdb.d/.


How to change scancode->keycode mapping

Gather required information

First you have to determine the bustype, vendor, product and version of your input device (keyboard), as well as the scancode of the key you want to remap and the key code identifier you want to map it to.

Run evtest (you may have to install it first) and identify your keyboard in the list of devices. On keyboards with additional keys like Play/Pause, WWW, etc. these keys are often exposed as a different input device. If you don't get any output when pressing a key, hit Control+C and try a different device. Once you have identified your keyboard, remember the first column (/dev/input/eventX) and press the key you want to remap. The value after (MSC_SCAN) is the scancode. On my keyboard:

$ evtest
Available devices:
/dev/input/event0:  Power Button
/dev/input/event1:  Power Button
/dev/input/event2:  G19 Gaming Keyboard
/dev/input/event3:  G19 Gaming Keyboard
...
Select the device event number [0-18]:2
...
Event: time 1522111203.117945, -------------- SYN_REPORT ------------
Event: time 1522111220.778787, type 4 (EV_MSC), code 4 (MSC_SCAN),value 70039
Event: time 1522111220.778787, type 1 (EV_KEY), code 14 (KEY_BACKSPACE), value 1

... the scancode is 70039.

Now run the following command, where eventX is the one you chose before:

$ grep "" /sys/class/input/eventX/device/id/*

The output for my keyboard is

/sys/class/input/event2/device/id/bustype:0003
/sys/class/input/event2/device/id/product:c228
/sys/class/input/event2/device/id/vendor:046d
/sys/class/input/event2/device/id/version:0110

To get the key code identifier, either use the output of evtest or look at the Keys and buttons section in /usr/include/linux/input-event-codes.h for a complete list. The identifier is the part after KEY_ converted to lowercase, e.g. KEY_BACKSPACE becomes backspace.

Configure udev

Take a look at /lib/udev/hwdb.d/. We will create a text file in /etc/udev/hwdb.d/ with a filename beginning with a number greater than the file corresponding to our device type. For a keyboard, this can be any number greater than 60, while for a pointing stick it should be greater than 70. For example 65-keyboard-custom.hwdb. Use your favorite text editor, but keep in mind that you have to start it as root, e.g.

$ sudo gedit /etc/udev/hwdb.d/65-keyboard-custom.hwdb

Add the following content

evdev:input:b[bustype]v[vendor]p[product]e[version]*
 KEYBOARD_KEY_[scancode]=[key code identifier]

...where

  • [bustype], [vendor], [product] and [version] have exactly 4 characters (pad with zeros if needed) and letters need to be uppercase
  • [scancode] does not need padding but letters have to be lowercase
  • the evdev:... line has no preceding space
  • the KEYBOARD_KEY... line has exactly one preceding space

In my example, the file looks like this:

evdev:input:b0003v046DpC228e0110*
 KEYBOARD_KEY_70039=backspace   # map CapsLock to BackSpace

The first line will be matched to your device. You can specify additional evdev: lines and you can use more than one wildcard (*) to match additional devices, but keep in mind that scancodes are device specific. You can also add more than one scancode mapping. Have a look at /lib/udev/hwdb.d/60-keyboard.hwdb for inspiration. A more detailed and up-to-date version of that file can be found in the online repository.

Apply new configuration

Compile the new configuration to the hardware database:

$ sudo systemd-hwdb update

If you want to apply the changes immediately, inform udev:

$ sudo udevadm trigger

Please note that configuration values can only be added or changed while the system is running. If you remove a configuration (e.g. scancode mapping), you have to reboot for the changes to take effect.

Remember to also revert the remapping you did before (using /etc/default/keyboard), because that will still be applied to all keyboards.