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
| Feature | Details |
|---|---|
| Automatic device discovery | Scans for Framework VID 32ac, PIDs 0012/0018/0019 (keyboards ANSI/ISO/JIS), 0014 (numpad), 0013 (RGB macropad) |
| Bidirectional hardware sync | Reads current backlight on startup; monitors hardware shortcut changes (Fn+Space) and updates sysfs, UPower, and PowerDevil |
| Runtime hotplug handling | Uses NETLINK_KOBJECT_UEVENT to handle module changes at runtime |
| Configurable logging | Debug levels 0 (Quiet) through 3 (D-Bus) via environment variables |
| Zero runtime dependencies | Communicates directly via HID; no qmk_hid binary needed |
Operation Modes
The daemon supports two modes for how detected modules are presented to the system1:
| Mode | Description |
|---|---|
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. |
separate | Creates 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:
- 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
- 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 inunifiedmode 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 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 Option | Environment Variable | Description | Default |
|---|---|---|---|
-m, --mode | FW16_KBD_ULEDS_MODE | Operation mode: unified or separate | unified |
-v, --vid | FW16_KBD_ULEDS_VID | Comma-separated VIDs or VID:PID (hex) | 32ac |
-b, --max-brightness | FW16_KBD_ULEDS_MAX_BRIGHTNESS | Maximum brightness value | 3 |
-p, --poll-ms | FW16_KBD_ULEDS_POLL_MS | Hardware polling interval in ms | 1000 |
-l, --list | List auto-discovered devices and exit | ||
FW16_KBD_ULEDS_DEBUG | Debug 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) andhidrawsupport libsystemd(for native D-Bus synchronization)systemd(optional; only needed for the provided service unit)1
Technical Details
| Detail | Value |
|---|---|
| Language | C |
| License | MIT |
| Stars | 5 |
| Forks | 2 |
| Commits | 14 |
| Config file | /etc/fw16-kbd-uleds.conf |
| Virtual device name | framework::kbd_backlight |
| QMK/VIA protocol derived from | FrameworkComputer/qmk_hid |
Related Projects
| Project | Description |
|---|---|
| Framework Tool TUI | Terminal dashboard with keyboard backlight control for FW13/FW16 |
| Input Architect | Input module management tool for Framework Laptop 16 |