RGB Keyboard Control for the ASUS ROG Flow Z13, No Armoury Crate Required

Posted
July 11, 2026
By
Jacob Lloyd — written with AI assistance, post-project
Read time
7 min read

In plain terms: Free software I wrote to control the glowing keyboard lights on my ASUS laptop under Linux, where the official app doesn't exist. You can change colors from a command line, a desktop app, or a web page — and the color actually survives sleep, reboot, and unplugging the keyboard. It fills a gap the manufacturer left open.

My ASUS ROG Flow Z13 has two RGB zones — the keyboard and a lightbar around the tablet's edge — and stock Linux support for either is "good luck." Armoury Crate doesn't exist here. So I wrote it myself: a CLI, a GTK4 app, and a browser panel that talk straight to the hardware, plus the part nobody bothers with: making the color survive suspend, reboot, and undocking the keyboard.

tl;dr

  • What it is: an Aura HID driver for the Z13's keyboard + lightbar, with a CLI, a GTK4 GUI, a browser panel, an idle daemon, and a new-color-on-every-keypress party mode.
  • What it costs: free. The whole zip is ~40 KB — Python stdlib plus zero-dependency Node.
  • What you need: a ROG Flow Z13 (the 2025 GZ302) running Linux, and five minutes for the installer.
  • What you end up with: a color that's still there after you close the lid, reboot, or rip the keyboard off the tablet and dock it back on.

What you end up with

Here's the browser panel doing its job:

rgb-web control panel in a browser: two zone cards for keyboard and lightbar, both showing ON with brightness 3/3, a preset color row, and All On / All Off buttons
Both zones connected, presets across the top, one button to kill all the lights before a meeting.
Same rgb-web panel at phone width, cards stacked in a single column
Same page at 390px wide. Nothing special was done for mobile — it's just a page that doesn't fight a narrow viewport.

Honest note: both zones show white (#ffffff) up there. Not a bug. I captured these from a sandboxed instance with an empty state file, and the Aura protocol can't be asked what color it's currently showing. Whatever the tool last wrote is the only truth that exists — which is exactly the design problem this whole project is built around. More below.

Four ways to change the color

Same backend, four front doors:

InterfaceCommand / accessBest for
CLIrgb set all '#00ddcc'scripts, muscle memory, SSH
GTK4 GUIrgb-guia real color picker with sliders, no terminal
Web panelhttp://127.0.0.1:3003phone on the same desk, no app to install
Party modergb-reactive.pya new random color on every keypress, for fun
# the CLI in one breath
rgb list                       # what zones exist, current known state
rgb set all '#00ddcc'          # set every zone to teal
rgb brightness keyboard 2      # 0-3
rgb on / rgb off               # per-zone or all
rgb restore                    # reapply the last saved color (what the restore service runs)

The GUI wraps the same operations in a color dialog per zone, brightness sliders, nine preset swatches, and a sync switch that applies one change to all zones. The web panel is the same idea in a tab: color picker, hex box, brightness slider, off toggle, preset row, All On / All Off.

The Aura HID protocol

Both zones — keyboard (USB 0b05:1a30) and lightbar (USB 0b05:18c6) — speak the Aura HID protocol that the g-helper and z13ctl projects reverse-engineered first. Nothing official documents it; people figured it out staring at USB captures, and this tool builds on their work rather than redoing it.

The mechanics are almost insultingly simple:

  • 64-byte output reports, report ID 0x5D, zero-padded.
  • SetMode packet: 5d b3 <zone> <mode> <r> <g> <b> <speed> — mode 0x00 is static color, which is all this tool uses.
  • Then a Save packet (5d b5) and an Apply packet (5d b4) to commit.
  • Zone byte: 0x00 keyboard, 0x01 lightbar. Both hidraw nodes sit behind one USB-multiplexed "N-KEY Device," and each quietly ignores packets addressed to the other zone — so the same three-packet write works on either node.

The fact that drives every design decision here: the protocol is write-only. You can set a color; you can never read one back. So the tool keeps its own memory — every applied color and brightness lands in ~/.local/state/rgb-control/state.json, and that file, not the hardware, is the only record of what your keyboard is supposed to look like. The restore service, the idle daemon, the re-dock handling: all of it exists to push that file back at hardware that keeps forgetting.

Discovery walks /sys/class/hidraw, matches vendor/product ID, then checks that report ID 0x5D actually exists in the node's report descriptor before trusting it. There's also a fallback backend: the kernel's asus-wmi driver exposes a multicolor LED class at /sys/class/leds/asus-*:rgb:*, with multi_intensity and brightness files. Hidraw does the writing when it's available; sysfs supplies initial state, since unlike HID it can be read.

How a click becomes a color

The full path from click to LED:

Surviving suspend and re-dock

Every RGB tool I found on Linux could set a color. None remembered it past the next suspend, reboot, or — the Z13's keyboard being detachable — the next re-dock. Each of those resets the hardware to dark, and a write-only protocol means it has no idea what it was showing. The fix has to live outside the hardware:

The ~6-second retry budget matters: a freshly docked keyboard takes a moment to show up on the bus, and udev fires a burst of events while it enumerates. The restore unit sleeps 1 second to let the burst settle, then retries discovery instead of quitting on the first miss.

There's also an idle daemon, rgb-idled, GNOME/Wayland only because it listens for two specific D-Bus signals: org.gnome.ScreenSaver ActiveChanged (lock the screen → dim to brightness 1) and org.gnome.Mutter.IdleMonitor WatchFired (the same idle clock GNOME uses to blank the display → lights off). Unlock and it restores from state.json. The detail I actually like: dim and off use a persist=False path, so those transient states never overwrite your saved color. Wake up and it's the color you picked, not whatever the daemon last did.

Setup

Quick test, no install:

cd rgb-control
./rgb-cli list
./rgb-cli set all '#00ddcc'

Full install wires up the udev rule, rgb-restore.service, the sleep hook, a tmpfiles rule, a ~/.local/bin/rgb symlink, a desktop entry, and the rgb-idled user service:

cd rgb-control
sudo ./install.sh

Installer paths are templated (__RGB_DIR__, __RGB_STATE__) and filled in at install time, so it works from wherever you unzipped it. The web panel is a separate, optional step:

cd rgb-web
node server.js
# → http://127.0.0.1:3003
# PORT= and RGB_CLI= env vars override the defaults

It also supports systemd socket activation — zero resources until the first request hits it, exits after 10 idle minutes. And party mode, a fresh random color on every keystroke, is one command away: ./rgb-toggle.sh start (it'll ask for sudo).

Gotchas

  • Permissions on stricter distros. On Fedora/Bazzite, /dev/hidraw* is world-writable out of the box, so this just works. Other distros usually need a udev uaccess rule (example in the setup guide). This is the number-one "it does nothing" cause.
  • The sysfs LED nodes are root-owned. The shipped udev rule chgrps them to wheel. Not in that group? Add yourself or edit the rule.
  • LED node names carry a machine-specific suffix. Something like asus-0003:0B05:18C6.0008 — that trailing number can differ per machine. Check ls /sys/class/leds and adjust the config if the fallback backend finds nothing.
  • rpm-ostree machines (Bazzite, Silverblue) have a read-only /usr. The installer deliberately targets /etc for the sleep hook. Don't "fix" it back.
  • rgb-idled only fires on GNOME. On KDE or bare X11 it starts fine and never does anything. Expected, not broken.
  • The web panel has no login, on purpose. It binds 127.0.0.1 only, because it shells out to hardware control with no auth in front. The server code says it plainly: don't bind 0.0.0.0 without adding authentication first. There's a path-traversal guard on static files, but a guard isn't a login.
  • Re-docking races itself a little. One physical re-dock can fire several udev events; the restore path sleeps briefly to collapse them, so the lights may blink back a beat late. That's the collapse window working, not lag.

Downloads

Free for personal use. If it saves you an afternoon, the coffee button's nearby.


← More Tools & Downloads