How to Customise Device Properties and Keyboard Scancodes in Ubuntu Using systemd-hwdb

Modern Linux distributions rely on the systemd hardware database (hwdb) to associate metadata and device-specific quirks with hardware identified by the kernel. Maintained as part of udev, the hardware database translates low-level hardware identifiers—such as USB vendor IDs, PCI sub-devices, and raw input scancodes—into high-level attributes like standardised Linux keycodes, pointing device DPI acceleration profiles, and optical drive capabilities. When a keyboard key fails to register, sends the incorrect symbol, or requires low-level kernel remapping before reaching the display server, editing the hardware database via systemd-hwdb provides an enduring, system-wide solution.

Understanding Scancodes, Keycodes, and the Hardware Database

When you press a physical key on a keyboard, the hardware controller sends an electrical scancode to the Linux kernel input subsystem. The kernel translates this raw numeric scancode into a standardised Linux input event keycode (such as KEY_VOLUMEUP, KEY_PROG1, or KEY_MUTE). The display server (Wayland or X11) then maps this keycode to a desktop keysym.

While desktop utility tools can remap keys within an active graphical session, they do not take effect inside virtual consoles (TTYs), lock screens, or secondary user accounts. Modifying the hardware database configures the kernel evdev subsystem directly, ensuring that scancode translation occurs automatically as soon as the device is detected by udev.

Identifying the Keyboard Hardware and Scancode

Before writing a custom hardware database rule, you must capture the exact input device identifier and the raw scancode emitted by the target key.

Step 1: Locate the Evdev Device Node

Install the evtest utility to monitor low-level kernel input events in real time:

sudo apt update && sudo apt install -y evtest

Run evtest without arguments to list available input handlers:

sudo evtest

The terminal presents a numbered list of input devices. Select the number corresponding to your keyboard (for example, /dev/input/event3: AT Translated Set 2 keyboard or your USB keyboard name). Once selected, press the specific physical key you intend to remap.

The terminal outputs event blocks similar to the following:

Event: time 1730974821.124501, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70068
Event: time 1730974821.124501, type 1 (EV_KEY), code 183 (KEY_F13), value 1

Note two critical values:

  • MSC_SCAN value: The raw hardware scancode (in this example, 70068).
  • Bus / Vendor / Product: Displayed in the header of the evtest output (for example, bustype: 0x0003, vendor: 0x046d, product: 0xc31c).

Creating a Custom hwdb Configuration File

System-provided hardware database rules reside in /usr/lib/udev/hwdb.d/. Custom administrator overrides must always be placed inside /etc/udev/hwdb.d/ to prevent package updates from overwriting your configurations.

Create a custom configuration file with a descriptive prefix:

sudo nano /etc/udev/hwdb.d/90-custom-keyboard.hwdb

The configuration file uses a strict match-and-property syntax. A typical rule contains a device match header followed by indented property assignment lines:

# Match generic AT keyboard on notebooks
evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*
 KEYBOARD_KEY_70068=prog1

# Match specific USB keyboard by vendor and product ID
evdev:input:b0003v046DpC31C*
 KEYBOARD_KEY_70068=mute

Syntax and Formatting Rules

  • Device Match Header: The first line specifies the subsystem and hardware match pattern. Wildcards (*) match any character string. For USB devices, the format evdev:input:b<bus>v<vendor>p<product>* requires hexadecimal values formatted in uppercase four-digit blocks.
  • Property Indentation: Every property assignment line must begin with exactly one space. Lines without leading whitespace will cause parser syntax errors during compilation.
  • Scancode Prefix: The property key uses the format KEYBOARD_KEY_<scancode>, where the scancode is written in lowercase hexadecimal.
  • Target Keycode: The assigned value must be a valid Linux keycode written in lowercase without the KEY_ prefix (for example, playpause, mute, volumeup, f13, or leftmeta).

Compiling the Hardware Database Binary

The systemd hardware database does not parse plain text files on every device insertion. Instead, rules are compiled into an optimised binary database located at /etc/udev/hwdb.bin (or /usr/lib/udev/hwdb.bin).

To compile your new rule into the binary database, run:

sudo systemd-hwdb update

If any formatting or syntax errors exist, the compiler reports the offending line number. If the command exits without output, the binary compiled successfully.

Applying Changes and Verifying Key Mapping

Once the database is updated, reload the active udev rules and trigger device reconfiguration without rebooting your system:

sudo udevadm control --reload
sudo udevadm trigger --subsystem-match=input

To verify that the kernel input subsystem has accepted your new mapping, query the device properties using udevadm:

udevadm info /dev/input/by-path/*kbd | grep -i KEYBOARD_KEY

The output confirms your custom mapping:

E: KEYBOARD_KEY_70068=mute

Run sudo evtest once more, select the keyboard, and press the modified key. The event output should now display the new assigned keycode (such as KEY_MUTE), confirming that the translation operates at the kernel level across all display servers and console environments.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.