Framework Laptop 16 — keyboard backlight in GNOME (Fedora 44)

Published 14 June 2026

date
env
linux Fedora 44 (GNOME/Wayland) — Framework Laptop 16, AMD Ryzen AI 300, white Keyboard Module (ISO) macos N/A

The step-by-step Framework doesn’t ship. Tailored to: FW16 (AMD Ryzen AI 300), white Keyboard Module - ISO, Fedora 44, GNOME/Wayland. Last updated: 2026-06-14.

Why this is needed (and why there’s no official guide)

On the FW16 the keyboard is a self-contained QMK/VIA device with its own MCU, deliberately not driven by the embedded controller (EC). That’s what makes the input modules hot-swappable and reflashable — but it means the keyboard looks like a generic USB HID gadget to Linux and is not exposed as a standard org.freedesktop.UPower.KbdBacklight device. So GNOME’s Quick Settings slider, the keyboard-brightness keys, and brightnessctl can’t see it out of the box.

Framework provides no first-party daemon for this on Linux; their engineer (DHowett) said it’s left to community efforts. This guide builds that bridge.

Important trap: framework_tool --kblight does NOT work here

framework_tool --kblight 80 will report 80% but the keys stay dark — it writes an EC-side value that never reaches the QMK keyboard on the FW16. The tool that actually drives the LEDs is qmk_hid (talks to the keyboard over HID).

How it works

GNOME slider / brightness keys
→ UPower.KbdBacklight.SetBrightness
→ /sys/class/leds/framework16::kbd_backlight/brightness (kernel: uleds)
→ bridge daemon (reads the value)
→ qmk_hid via --backlight <0-100>
→ QMK keyboard LEDs

UPower auto-discovers any LED whose name contains kbd_backlight, so creating a virtual one via the kernel uleds interface is enough for native GNOME control.


Step 0 — Sanity check (no install)

Press Fn + Space a few times. This is the QMK hotkey that cycles backlight effects/brightness; one state is fully off. Make sure the keys can glow at all before blaming software.


Step 1 — Install qmk_hid

Not in Fedora repos. Use the official prebuilt binary:

  1. Download the Linux asset from https://github.com/FrameworkComputer/qmk_hid/releases
  2. Install it:
    Terminal window
    chmod +x qmk_hid
    sudo install -m755 qmk_hid /usr/local/bin/qmk_hid
    qmk_hid --version

(Alternative — build it: sudo dnf install systemd-devel hidapi-devel then cargo install --git https://github.com/FrameworkComputer/qmk_hid, then copy ~/.cargo/bin/qmk_hid to /usr/local/bin/.)

Step 2 — Verify the backend actually lights the keyboard

Terminal window
sudo qmk_hid via --backlight 80 --save # --save = persist across kbd power-cycle

The keys should light to ~80%. If they don’t, stop here — the bridge can’t work until this single command does. (Re-check Step 0, firmware version, and that you have the keyboard — not just the macropad — connected.)

Step 3 — Install the bridge daemon + service

The corrected files are staged in ~/Downloads/:

  • fw16-kbd-backlight-bridge.py (white-keyboard backend, qmk_hid via --backlight)
  • fw16-kbd-backlight.service
Terminal window
cd ~/Downloads
sudo install -m755 fw16-kbd-backlight-bridge.py /usr/local/bin/fw16-kbd-backlight-bridge.py
sudo install -m644 fw16-kbd-backlight.service /etc/systemd/system/fw16-kbd-backlight.service
echo uleds | sudo tee /etc/modules-load.d/uleds.conf
sudo modprobe uleds
sudo systemctl daemon-reload
sudo systemctl enable --now fw16-kbd-backlight.service
sudo systemctl restart upower # only needed the FIRST time (UPower scans /sys/class/leds at startup)

On later boots, the service’s Before=upower.service ordering means the virtual LED already exists when UPower starts — no restart needed.

Step 4 — Verify GNOME integration

Terminal window
ls /sys/class/leds/ | grep kbd_backlight
# -> framework16::kbd_backlight
gdbus call --system --dest org.freedesktop.UPower \
--object-path /org/freedesktop/UPower/KbdBacklight \
--method org.freedesktop.UPower.KbdBacklight.GetMaxBrightness
# -> (100,)

Then the GNOME Quick Settings keyboard-backlight slider and the keyboard-brightness keys should drive the keyboard.


Troubleshooting

Terminal window
journalctl -u fw16-kbd-backlight.service -b
  • Slider/LED node missing right after install → you skipped sudo systemctl restart upower (one-time).
  • qmk_hid failed in the journal → confirm /usr/local/bin/qmk_hid exists and Step 2 works manually; check qmk_hid via --help for the exact flag names of your version.
  • After resume from sleep the backlight doesn’t come back — known FW16 firmware behavior, independent of this bridge. Quick fix: sudo systemctl restart fw16-kbd-backlight. To automate, add the optional sleep hook below.

Optional: re-arm after sleep

Create /etc/systemd/system/fw16-kbd-backlight-resume.service:

[Unit]
Description=Re-arm FW16 keyboard backlight bridge after resume
After=suspend.target hibernate.target hybrid-sleep.target
[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl restart fw16-kbd-backlight.service
[Install]
WantedBy=suspend.target hibernate.target hybrid-sleep.target
Terminal window
sudo systemctl daemon-reload
sudo systemctl enable fw16-kbd-backlight-resume.service

Notes / design choices

  • The daemon does not pass --save on every slider change: GNOME/UPower restores the last brightness at login, so writing the keyboard’s EEPROM on each tick would only add flash wear. Use --save manually if you want a value to survive a keyboard power-cycle.
  • uleds struct used: char name[64] + int max_brightness (4-byte int read back for brightness) — matches the Fedora 44 kernel UAPI.
  • A packaged equivalent exists on Arch (fw16-kbd-uleds-git); this is the hand-rolled Fedora version of the same idea.

Alternatives considered

  • framework_tool --kblight — reads/writes EC state; does not light the FW16 keyboard (EC is not wired to the QMK keyboard). Useful for other EC functions, not this.
  • keylightd and other EC-based daemons — FW13-era, EC-driven; don’t apply to the FW16’s separate QMK keyboard.

Sources