(To avoid the preamble and go straight to the solution, jump to the heading below.)

Last week I took my first steps away from the warm safe and familiar embrace of X11 and into the new frontiers of Wayland, when I installed Pop!_OS Cosmic Alpha on my laptop.

Part of my hesitance to move to Wayland was the fact that I knew this new frontier would bring a myriad of new challenges and that it would inevitably bring some rough edges. But even so, I didn’t think of some of the obvious things that would break, like my keyboard remapping.

Like many users who need keyboard remapping, I have relied on a combination of either Gnome Tweaks or one of the old reliable tools like xmodmap or setxkbmap. These are particularly important for me as someone whose brain has been thoroughly broken by years of Vi/Vim usage, and who barely knows how to function on a keyboard whose Caps Lock key is not remapped to Escape.

When using an external keybaord, I barely need to think about this, since I have been using keybaords with firmware that allows me to remap keys at the hardware level. But when using my laptop, I have to rely on software to do the remapping for me.

I really shouldn’t have been surprised, but nevertheless I was caught off-guard when after isntalling Pop!_OS Cosmic Alpha, and popping open my terminal, and instincually running setxkbmap -option caps:swapescape I got an error message telling me that I was not running in an X environment. Which of course was incredibly obvious, but I realized that I really didn’t know how to remap keys now, and that was going to be a problem.

Like any good nerd, I turned to Stack Overflow Exchange, and found some people asking the same question. And among the various answers, I found one that pointed me towards something that I had never heard of before: the UDev Hardware Database.

I’d assumed that if Wayland didn’t have support for this already, then in all likelihood I’d need to turn to udev, but I really didn’t know that this hwdb thing existed. Before long I realized that it actually might provide a solution for me that was even better than what I had before, if a bit more complex.

A problem I had always had with setxkbmap was that if I used it, it would have to be rerun any time a device got disconnected or reconnected. In the past I had solved this with hooks and other dynamic fixes, but had largely stopped worrying about it once I started just setting my remappings at the hardware level. But that meant that I still needed to use the command if I ever used my laptop without an external keyboard (which is supposedly the point of a laptop). And I couldn’t have any default automation in place when starting my X session, since it would conflict or undo the hardware remapping.

UDev HWDB however, has none of these problems, because it allows targeting specific input devices when assigning remappings. Which means I can continue to use hardware-level remapping in my external keyboards, while letting UDev handle the remapping on my laptop’s keyboard.

UDev HWDB Keyboard Remapping

The method for setting up remappings is relatively simple:

  1. Create a file in /etc/udev/hwdb.d/ with a .hwdb extension, containing the remapping you want to apply. (Prefix it with a number like 60-my-keyboard-mappings.hwdb to ensure it is loaded after the defaults.)
  2. Write a rule in the file that matches the device you want to remap, and assigns the remapping you want to apply. (More on that later)
  3. Run sudo systemd-hwdb update to update the HWDB.
  4. Run sudo udevadm trigger to apply the changes.

Steps 3 & 4 will take care of themselves on the next reboot.

Mapping Rules

The entries in the .hwdb file are written like so:

evdev:<match rule for lookup string>
  KEYBOARD_KEY_3a=esc

The mapping take the form of KEYBOARD_KEY_<hex code>=<key name>. The hex code is the scancode of the key you want to remap, and the key name is the key you want to remap it to. You can find the scancodes for your keys by running evtest which we’ll discuss in the next section. The key names are the same as the ones you would use in setxkbmap.

You can add many mappings under one match rule, and multiple match rules per file. You can find some examples of remappings here.

Identifying the Event Device

The tricky part of setting up hwdb remappings is finding the device you want to remap and its corresponding lookup string.

The first step is to find the appropriate event device for the keyboard you want to remap. You can do this by running sudo evtest /dev/input/event<n>. The <n> is the number of the event device you want to test. You will have to try each one (starting at 0) until you find the one that prints out event data when you press keys on the keyboard you want to remap.

$ sudo evtest /dev/input/event2
Input driver version is 1.0.1
... skipping output ...
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 1 (KEY_ESC)
    Event code 2 (KEY_1)
    Event code 3 (KEY_2)
    ... Here is where you can find the decimal number IDs for the keys ...
    ... skipping output ...
Testing ... (interrupt to exit)
Event: time 1727688778.513025, type 4 (EV_MSC), code 4 (MSC_SCAN), value 22
Event: time 1727688778.513025, type 1 (EV_KEY), code 34 (KEY_G), value 1
Event: time 1727688778.513025, -------------- SYN_REPORT ------------
gEvent: time 1727688778.615442, type 4 (EV_MSC), code 4 (MSC_SCAN), value 22
Event: time 1727688778.615442, type 1 (EV_KEY), code 34 (KEY_G), value 0
Event: time 1727688778.615442, -------------- SYN_REPORT ------------
Event: time 1727688779.209135, type 4 (EV_MSC), code 4 (MSC_SCAN), value 22
Event: time 1727688779.209135, type 1 (EV_KEY), code 34 (KEY_G), value 1
Event: time 1727688779.209135, -------------- SYN_REPORT ------------
gEvent: time 1727688779.315050, type 4 (EV_MSC), code 4 (MSC_SCAN), value 22
Event: time 1727688779.315050, type 1 (EV_KEY), code 34 (KEY_G), value 0

Lines like those above (after Testing .. (interrupt to exit)) are what should get printed out every time you press a key on the keyboard you want to remap.

Finding the Keybaord Key Event Codes

Once you have found the correct event device, you can look at the Event codes printed for Event type 1 (EV_KEY) to find the decimal number IDs for the keys you want to remap. You will however need to convert these to hexadecimal to use them in the .hwdb file.

$ printf "%x\n" 34
22

So if the Event code for the key is 34, you would use 22 in the .hwdb file:

  KEYBOARD_KEY_22=...

Matching Devices with Lookup Strings

Reading man 7 hwdb will explain the basics of how to use the UDev HWDB, and how it works. But one thing it really doesn’t explain is how to identify the lookup strings you want to match against for your remapping.

Most documentation will suggest you use * globs as wildcards in the lookup string, which will allow you to match against multiple devices, or simply make it easier to create a lookup string. But it’s hard to know what kind of match rule to craft if you don’t know what the lookup string is for the device(s) you want to remap.

Some of the Stack Exchange answers I linked above discuss constructing a match rule based on the Input Device ID from the evtest output. But I didn’t have much success with that.

Luckily with the help of another Stack Exchange answer, I was able to find a method for finding the strings that your HWDB entries will be matched against.

Using the event device node you found earlier, replace /dev in the path with /sys/class, and run the following one-liner:

$ sudo udevadm test /sys/class/input/event2 |& grep builtin.command..hwdb | perl -pe "s/^.+'hwdb '(evdev:.+)''/- \1/" | grep '^- evdev'

This will output a list of lookup strings that you can use in your .hwdb for that specific device.

On my Dell XPS, this gave me the following output:

- evdev:atkbd:dmi:bvnDellInc.:bvr1.26.0:bd08/13/2024:br1.26:svnDellInc.:pnXPS159520:pvr:rvnDellInc.:rn0YD3W1:rvrA00:cvnDellInc.:ct10:cvr:sku0B19:

You can use wild-cards/globs to shorten the string, or to match multiple devices. Or just take the whole string (starting at evdev) and use it as is, to match the specific device you want to remap.

If it outputs multiple strings, you can use the most specific one or use wildcards to craft a match rule that matches all of them.

So eventually I ended up with a .hwdb file that looked like this:

evdev:atkbd:dmi:bvnDellInc.:bvr1.26.0:bd08/13/2024:br1.26:svnDellInc.:pnXPS159520:pvr:rvnDellInc.:rn0YD3W1:rvrA00:cvnDellInc.:ct10:cvr:sku0B19:
  KEYBOARD_KEY_3a=esc  # key marked caps lock -> esc

Though on another laptop I used a broad wildcard like so:

evdev:atkbd:*
  KEYBOARD_KEY_3a=esc  # key marked caps lock -> esc

Then I ran sudo systemd-hwdb update and sudo udevadm trigger /sys/class/input/event2 and my remapping was in place. (specifying the event device node from the sysfs is not necessary, but avoids reloading everything)

Summary

UDev HWDB is a powerful tool for remapping keys on your keyboard, and can be used to remap keys on specific devices, without having to worry about conflicts or needing to rerun commands when devices are connected or disconnected. And is also Windowing server agnostic (and even works on the console if you aren’t using a GUI).

The steps for using it to remap keys are:

  1. sudo evtest /dev/input/event<n> Trying each event device until you find the one that prints out event data when you press keys on the keyboard you want to remap.
  2. Convert the key event codes from output of evtest to hexadecimal (for the keys you want to remap).
  3. Run sudo udevadm test /sys/class/input/event2 |& grep builtin.command..hwdb | perl -pe "s/^.+'hwdb '(evdev:.+)''/- \1/" | grep '^- evdev' to find the lookup string for the device you want to remap.
  4. Create a .hwdb file in /etc/udev/hwdb.d/ with the remappings you want to perform for under the match rule.
  5. Run sudo systemd-hwdb update to update the HWDB.
  6. Run sudo udevadm trigger to apply the changes. (Optionally specifying the event device node from the sysfs to avoid reloading everything)

And that’s it! You should now have your keyboard remapped to your liking.