Skip to main content

fw16-kbd-uleds

fw16-kbd-uleds is a Framework Laptop 16 keyboard backlight bridge that exposes QMK-controlled input modules as a standard UPower keyboard backlight device. Developed by community member paco3346, it creates a virtual LED device via /dev/uleds named framework::kbd_backlight, translating desktop brightness changes into direct QMK/VIA HID commands applied to detected Framework 16 keyboard, numpad, and RGB macropad modules12.

The Framework Laptop 16 keyboard, numpad, and macropad modules use QMK-based firmware for backlight control. By default, their backlight state is not exposed as a standard org.freedesktop.UPower.KbdBacklight device. As a result, desktop environment tools such as KDE PowerDevil, GNOME settings, and brightnessctl cannot detect or control the keyboard backlight out of the box. This daemon bridges that gap entirely in userspace, with zero runtime dependencies — no qmk_hid binary required12.

Background

Framework Laptop 13 users previously worked around the missing sysfs backlight exposure through DHowett's kernel driver and Framework's qmk_hid CLI tool34. However, the Framework Laptop 16 keyboard is a separate QMK-controlled device whose parameters are not managed by the embedded controller in the same way as the FW13, making those approaches inapplicable5. Community members requested a solution to sync backlight between modules and integrate with the OS6, which motivated the creation of this tool.

Features

FeatureDetails
Automatic device discoveryScans for Framework VID 32ac, PIDs 0012/0018/0019 (keyboards ANSI/ISO/JIS), 0014 (numpad), 0013 (RGB macropad)
Bidirectional hardware syncReads current backlight on startup; monitors hardware shortcut changes (Fn+Space) and updates sysfs, UPower, and PowerDevil
Runtime hotplug handlingUses NETLINK_KOBJECT_UEVENT to handle module changes at runtime
Configurable loggingDebug levels 0 (Quiet) through 3 (D-Bus) via environment variables
Zero runtime dependenciesCommunicates directly via HID; no qmk_hid binary needed

Operation Modes

The daemon supports two modes for how detected modules are presented to the system1:

ModeDescription
unified (default)Groups all modules under a single virtual LED device named framework::kbd_backlight. Brightness changes are synced across all detected modules. Required for KDE PowerDevil, which only supports a single keyboard backlight device.
separateCreates individual virtual LED devices per module type: framework::kbd_backlight (keyboards), framework::numpad_backlight (numpad), framework::macropad_backlight (macropad). Useful for custom scripts or independent control.

Discrete Brightness Levels

The Framework 16 keyboard modules support four discrete brightness levels (Off, 33%, 67%, 100%). The daemon defaults to a max_brightness of 3, resulting in four fixed steps in the UI rather than a continuous slider that does not map cleanly to hardware capability1.

Hardware Synchronization

The daemon periodically polls the hardware for its current backlight level, enabling two features1:

  1. Startup persistence — reads the current backlight level from modules on startup and initializes the virtual LED device with that value, preventing a reset to 0 when the service starts
  2. Bi-directional sync — detects hardware shortcut changes (e.g., Fn + Space) and updates sysfs, notifies UPower via D-Bus and KDE PowerDevil so the UI slider and OSD reflect the new level, and in unified mode propagates changes from the main keyboard to all connected modules

Installation

AUR (Arch Linux)

Available in the AUR as fw16-kbd-uleds-git12:

yay -S fw16-kbd-uleds-git

Manual Build

git clone https://github.com/paco3346/fw16-kbd-uleds.git
cd fw16-kbd-uleds
make
sudo make install PREFIX=/usr

Usage

Enable and start the service:

sudo systemctl enable --now fw16-kbd-uleds.service

The service unit automatically ensures the uleds kernel module is loaded1.

UPower Restart Required

UPower only enumerates keyboard backlight devices at startup. Because this daemon creates the virtual LED device at runtime, UPower must be restarted after the service starts:

sudo systemctl restart upower

KDE Plasma's PowerDevil may also need a one-time restart to detect the newly-added UPower device. Subsequent restarts of fw16-kbd-uleds that require restarting UPower generally do not require restarting PowerDevil again1.

Configuration

The daemon can be configured via command-line options, environment variables, or a configuration file at /etc/fw16-kbd-uleds.conf1:

CLI OptionEnvironment VariableDescriptionDefault
-m, --modeFW16_KBD_ULEDS_MODEOperation mode: unified or separateunified
-v, --vidFW16_KBD_ULEDS_VIDComma-separated VIDs or VID:PID (hex)32ac
-b, --max-brightnessFW16_KBD_ULEDS_MAX_BRIGHTNESSMaximum brightness value3
-p, --poll-msFW16_KBD_ULEDS_POLL_MSHardware polling interval in ms1000
-l, --listList auto-discovered devices and exit
FW16_KBD_ULEDS_DEBUGDebug level: 0 (Quiet), 1 (Info), 2 (Verbose), 3 (D-Bus)0

Use the --list flag to see which devices are auto-discovered and obtain a copy-pasteable configuration string for targeting specific devices1.

Requirements

  • Linux kernel with uleds (userspace LED) and hidraw support
  • libsystemd (for native D-Bus synchronization)
  • systemd (optional; only needed for the provided service unit)1

Technical Details

DetailValue
LanguageC
LicenseMIT
Stars5
Forks2
Commits14
Config file/etc/fw16-kbd-uleds.conf
Virtual device nameframework::kbd_backlight
QMK/VIA protocol derived fromFrameworkComputer/qmk_hid
ProjectDescription
Framework Tool TUITerminal dashboard with keyboard backlight control for FW13/FW16
Input ArchitectInput module management tool for Framework Laptop 16

Footnotes

  1. paco3346/fw16-kbd-uleds — GitHub 2 3 4 5 6 7 8 9 10 11

  2. [SOLVED] FW16 keyboard LEDs — Framework Community (paco3346 announcement) 2 3

  3. Controlling the keyboard backlight through /sys — Framework Community

  4. FrameworkComputer/qmk_hid — Official Framework QMK HID tool (GitHub)

  5. Linux keyboard backlight support — Framework Community (DHowett explanation of FW16 vs FW13 EC differences)

  6. Sync backlight between modules / control backlight from OS — Framework Community