QMK RGB Tool

Control the RGB lighting on QMK-compatible keyboards. Supports Impact 80 and Rainy 75 out of the box, and any other keyboard running QMK with the RGB Matrix subsystem and VIA support.

Cross-platform CLI for Linux, macOS, and Windows. Designed for automation, scripting, and agent consumption.

Paul Klumpp 98545c8864 plan: implement channel discovery task by task 1 هفته پیش
.claude 08e46f63e6 verify speed by read-back, reject stray arguments, clarify the docs 1 هفته پیش
.opencode b0d6e24f95 Initial commit — Wobkey RGB CLI with LICENSE (GPLv3) 2 هفته پیش
cmd d76531cc5c take hue, saturation and value on the color command 1 هفته پیش
docs 98545c8864 plan: implement channel discovery task by task 1 هفته پیش
internal d76531cc5c take hue, saturation and value on the color command 1 هفته پیش
profiles 32952fe25e rename cmd directory: qmk-rgb -> qmk-rgb-tool 1 هفته پیش
.gitignore be166818c3 stop versioning derived graphify output 1 هفته پیش
AGENTS.md d6848f8ac7 correct the color and speed behaviour the docs claimed 1 هفته پیش
CLAUDE.md b0d6e24f95 Initial commit — Wobkey RGB CLI with LICENSE (GPLv3) 2 هفته پیش
LICENSE b0d6e24f95 Initial commit — Wobkey RGB CLI with LICENSE (GPLv3) 2 هفته پیش
PLAN.md 7acf462346 Document hardware handoff 2 هفته پیش
README.md d6848f8ac7 correct the color and speed behaviour the docs claimed 1 هفته پیش
go.mod 1a2e775d6c rename module path from impact-80 to qmk-rgb 1 هفته پیش
go.sum 5942a43aad migrate HID layer to go-hid for cross-platform support 2 هفته پیش
keyboards.json 0424164351 select keyboards by number instead of HID path 1 هفته پیش

README.md

QMK RGB Tool

Control the RGB lighting on QMK-compatible keyboards. Supports Impact 80 and Rainy 75 out of the box, and any other keyboard running QMK with the RGB Matrix subsystem and VIA support.

Cross-platform CLI for Linux, macOS, and Windows. Designed for automation, scripting, and agent consumption.

Installation

On Linux, libudev-dev is required before building:

sudo apt install -y libudev-dev

Build locally:

git clone <repo-url>
cd qmk-rgb
go build -o qmk-rgb-tool ./cmd/qmk-rgb-tool/

And, to run it from anywhere, install globally (run from the repo root, installs to $GOPATH/bin/):

go install ./cmd/qmk-rgb-tool/

Shell Completion

completion writes an autocompletion script to stdout for bash, zsh, fish or powershell. Each of those subcommands also accepts --no-descriptions to complete without description text. Run qmk-rgb-tool completion <shell> --help for the path your platform expects; for bash and zsh on Linux:

# bash: bash-completion 2.x loads this directory, so no sudo is needed
mkdir -p ~/.local/share/bash-completion/completions
qmk-rgb-tool completion bash > ~/.local/share/bash-completion/completions/qmk-rgb-tool

# zsh: the file name must start with an underscore
mkdir -p ~/.zsh/completions
qmk-rgb-tool completion zsh > ~/.zsh/completions/_qmk-rgb-tool

zsh does not include that directory in $fpath by default, so add it and re-run compinit in ~/.zshrc:

fpath=("$HOME/.zsh/completions" $fpath)
autoload -Uz compinit && compinit

Open a new shell afterwards. The script is generated from the command tree, so re-run the command whenever commands or flags change.

Usage

# Discover connected keyboards
./qmk-rgb-tool keyboard info

# RGB commands target Logo, Backlight, and Side by default
./qmk-rgb-tool effect breathing
./qmk-rgb-tool effect rainbow_moving_chevron
./qmk-rgb-tool effect rainbow_moving_chevron --zone backlight
./qmk-rgb-tool brightness 160
./qmk-rgb-tool speed 2
./qmk-rgb-tool color 00ff00
./qmk-rgb-tool color hsv:85,255,255
# Raw mode IDs are zone-specific; ID 17 is Backlight-only
./qmk-rgb-tool mode 17 --zone backlight
./qmk-rgb-tool enable
./qmk-rgb-tool disable
./qmk-rgb-tool info

# Select exactly one zone
./qmk-rgb-tool brightness 160 --zone side

# Save and load RGB profiles
./qmk-rgb-tool save paul
./qmk-rgb-tool load paul
./qmk-rgb-tool list
./qmk-rgb-tool delete paul

# Select a keyboard by the number shown in `keyboard info`
./qmk-rgb-tool --device 1 enable

--zone accepts logo, backlight, or side and may be given on any subcommand. It is not remembered between runs: repeat it on every invocation. Without --zone, commands target Logo, Backlight, and Side in that order. With --zone, commands target exactly the selected zone. An unsupported name for an explicit zone fails before the device is opened; a default command skips unsupported zones and prints a warning on stderr.

All output is machine-parseable JSON when applicable.

Selecting a Keyboard

keyboard info numbers every connected QMK keyboard starting at 1, and --device takes that number:

./qmk-rgb-tool keyboard info
./qmk-rgb-tool --device 2 brightness 160

Without --device, commands that open a keyboard run only when exactly one is connected. With two or more, they fail and list the numbers you can choose from, so a command never hits an unintended keyboard. keyboard info, list and delete never open a keyboard and work with any number of them.

Numbers are assigned in a stable order (vendor ID, product ID, path), but they are only guaranteed for the current session. Keyboards are identified by their HID path, which the operating system reassigns on reboot, and most keyboards report no serial number. Re-run keyboard info after reconnecting a keyboard rather than storing the number.

Color Notations

color accepts three notations for one operation:

qmk-rgb-tool color ff0000            # six digit hex
qmk-rgb-tool color rgb:ff0000        # the same, written out
qmk-rgb-tool color hsv:0,255,255     # hue, saturation, value, each 0-255

hsv: takes the numbers the keyboard itself computes in, so a color can be addressed directly instead of being derived from hex. The hex notations say nothing about brightness and leave it alone.

The keyboard stores hue and saturation only; it has no value register, so the value component of a hex color has nowhere to go and is dropped. The value of an hsv: notation therefore goes to the brightness of the same zones, where the per-channel firmware transform applies — hsv:0,255,200 leaves logo and side at 160 while backlight reaches 255. See "Brightness and Speed Are Not Applied Verbatim" for the table.

Every notation is read back, so the line reports what the keyboard holds:

$ qmk-rgb-tool color 00ff00 --zone backlight
Color set to hue 85 sat 255
$ qmk-rgb-tool color hsv:0,255,200
Color logo hue 0 sat 255 brightness 160 backlight hue 0 sat 255 brightness 255 (requested hue 0 sat 255 brightness 200)

Hex is the wider of the two: at full brightness it reaches 195,841 colors, a pair of 8-bit hue and saturation values 56,654. They cover the same colors, and hsv: trades some of that range for direct addressing.

Brightness and Speed Are Not Applied Verbatim

The keyboard's firmware transforms these values per channel, so the accepted range is 0–255 but the value the keyboard ends up holding is often different. brightness and speed read every zone back and report what it actually applied:

$ qmk-rgb-tool brightness 200
Brightness logo 160 backlight 255 side 160 (requested 200)
$ qmk-rgb-tool brightness 160 --zone logo
Brightness set to 160
$ qmk-rgb-tool speed 60
Speed logo 4 backlight 60 side 4 (requested 60)
$ qmk-rgb-tool speed 60 --zone backlight
Speed set to 60

Observed on the Impact 80:

Zone Channel Brightness Speed
logo 0x02 clamps at 160 0 stops the animation, 1 is the slowest movement, above 1 becomes 4
backlight 0x03 scales up, saturating at 255 applied as given
side 0x04 clamps at 160 0 stops the animation, 1 is the slowest movement, above 1 becomes 4

Speed is not a smooth dial on logo and side. Only three outcomes are reachable there, and the value range the vendor's own VIA definition offers for those two channels is [0, 4], not 0–255:

Requested Stored Effect
0 0 the animation is frozen, the color stands still
1 1 the slowest movement
2 and above 4 twice as fast; this is where every larger request lands

The freeze at 0 was observed on logo; side reports the same range and the same collapse to 4. backlight is the only channel with a usable 0–255 range.

The command exits 0 either way: a value the firmware cannot represent is not a failure, but the summary line always states the value that was actually applied.

Features

  • Cross-platform — Linux, macOS, Windows via hidapi
  • Zone-aware effects — per-zone effect control (Logo, Backlight, Side) with zone-specific effect catalogs
  • Profile system — save, load, list, and delete RGB presets as JSON files in profiles/
  • 46 backlight effects — complete Impact 80 effect family mapped to zone-aware effect names
  • Compatibility aliases — off, breathe, rainbow, solid, static resolve to correct effect IDs per zone
  • Reactive & splash effects — honor color and speed for key-press illumination
  • Machine-parseable output — JSON for keyboard info, info, list, and effect --list
  • Agent-friendly — designed for automation, scripting, and CLI-first workflows
  • Multiple devices — keyboard info numbers each keyboard; --device <n> targets one

Impact 80 Zones and Effects

Zone CLI name VIA channel Effect IDs
Logo logo 2 0–6
Backlight backlight 3 0–45
Side side 4 0–6

Logo and Side share this complete effect family:

ID Name
0 none
1 wave
2 fixed_wave
3 spectrum
4 breathing
5 light
6 shutdown

Backlight uses the complete Impact 80 catalog:

ID Name ID Name
0 none 23 flower_blooming
1 solid_color 24 raindrops
2 alphas_mods 25 jellybean_raindrops
3 gradient_up_down 26 hue_breathing
4 gradient_left_right 27 hue_pendulum
5 breathing 28 hue_wave
6 band_sat 29 pixel_flow
7 band_val 30 digital_rain
8 band_pinwheel_sat 31 solid_reactive
9 band_pinwheel_val 32 solid_reactive_wide
10 band_spiral_sat 33 solid_reactive_multiwide
11 band_spiral_val 34 solid_reactive_cross
12 cycle_all 35 solid_reactive_multicross
13 cycle_left_right 36 solid_reactive_nexus
14 cycle_up_down 37 solid_reactive_multinexus
15 cycle_out_in 38 splash
16 cycle_out_in_dual 39 multisplash
17 rainbow_moving_chevron 40 solid_splash
18 cycle_pinwheel 41 solid_multisplash
19 cycle_spiral 42 starlight
20 dual_beacon 43 starlight_dual_hue
21 rainbow_beacon 44 starlight_dual_sat
22 rainbow_pinwheels 45 riverflow

Effect Behavior

Effects fall into categories that behave differently:

  • Static effects (solid_color, none) display a steady output. The color is set by color and is constant.
  • Reactive effects (solid_reactive*) light up on key press. They honor the color set by color — the pressed key's LEDs flash in the configured color. Use speed to adjust how long the illumination lasts.
  • Breathing effects (breathing, hue_breathing) slowly fade in and out, and both honor color. breathing keeps the hue and saturation and pulses the brightness of that color; hue_breathing swings the hue around the hue you set by a narrow amount instead of running through the full range.
  • Dynamic/stream effects (band_*, cycle_*, rainbow_*, starlight*, raindrops, jellybean_raindrops, flower_blooming, pixel_flow, riverflow) animate independently. Most ignore color and use their own color palettes. hue_pendulum and hue_wave cycle through hues.
  • Splash effects (splash, multisplash, solid_splash, solid_multisplash) react to key presses like reactive effects but with a splash pattern. solid_splash and solid_multisplash honor color.
  • Gradient effects (gradient_up_down, gradient_left_right) create a color gradient across the keyboard. They do not honor color.
  • Alphas mods (alphas_mods) colors modifier keys differently from alphanumeric keys. The colors are built-in and cannot be changed.

To set a permanent color, use solid_color and then color <hex>.

The logo and side catalogs hold only seven effects each, and the color you set is shown by three of them: fixed_wave, breathing and light. wave, spectrum and shutdown run their own color sequence, so a color set on them is stored but not displayed.

Compatibility Aliases

Effect names are resolved independently for each target zone. Compatibility aliases are off → none, breathe → breathing, rainbow → spectrum for Logo/Side and rainbow_moving_chevron for Backlight, rainbow_wave → wave for Logo/Side, and solid → light for Logo/Side and solid_color for Backlight. The legacy static alias remains accepted as a compatibility alias for solid.

The effect catalog is consistent with the QMK RGB Matrix firmware. The keyboard implements the VIA protocol (version 3) and its 46 backlight effect names match the QMK RGB Matrix effect catalog exactly — verified through live testing. The animations are defined in QMK rgb_matrix/animations/, where each effect has its own header file (e.g. digital_rain_anim.h, solid_reactive_anim.h, riverflow_anim.h).

ID 39 is multisplash; ID 41 is the distinct solid_multisplash name.

info reports a zones array with each zone's channel, enabled state, effect name and ID, brightness, speed, and color. The top-level enabled, mode, brightness, and speed fields summarize the first selected zone. If one zone cannot be queried, its record contains an error, other records are retained, and the process exits non-zero after printing the JSON.

Platform Setup

The tool uses hidapi for cross-platform HID access. Most platforms need no configuration, but Linux requires extra setup.

Linux

To build, libudev-dev is required:

sudo apt install -y libudev-dev

To run without sudo, grant your user group access to hidraw devices. By default, hidraw devices are owned by root (600). Choose one of the methods below.

Option 1: Quick (Per Session)

Grant your user group access to all HIDRAW devices:

sudo chown root:adm /dev/hidraw*
sudo chmod 660 /dev/hidraw*

This works for the current session only. Permissions reset after reboot.

Option 2: Permanent (udev Rule)

Create a persistent rule for the Impact 80:

echo 'SUBSYSTEM=="hidraw", ATTRS{idVendor}=="36b0", ATTRS{idProduct}=="309f", MODE="0660", GROUP="adm"' \
  | sudo tee /etc/udev/rules.d/99-qmk-rgb-tool.rules

sudo udevadm control --reload-rules
sudo udevadm trigger
sudo udevadm reload

This makes the Impact 80 accessible to members of the adm group on every boot.

macOS

No setup required. macOS applications access HID devices directly through IOKit.

Windows

No setup required. Windows applications access HID devices directly through the Windows HID API.

After Linux setup, run the tool as your regular user (no sudo needed).

Known Keyboards

Any keyboard running QMK with the RGB Matrix subsystem and VIA support is detected automatically via the QMK Raw HID signature (Usage Page 0xFF60, Usage 0x61). No manual configuration required.

keyboards.json is optional. When present it only supplies friendly names: keyboard info reports "known": true for models listed there and "known": false for every other QMK keyboard. Both are fully controllable — known describes the name lookup, not compatibility. Deleting or corrupting the file costs you names only.

Two models are listed in keyboards.json:

Keyboard VID PID
Wobkey Rainy 75 0x6666 0x0001
Wobkey Impact 80 0x36B0 0x309F

CLI Reference

Command Description
qmk-rgb-tool keyboard info Discover connected QMK keyboards
qmk-rgb-tool enable Enable selected lighting zones
qmk-rgb-tool disable Disable selected lighting zones
qmk-rgb-tool info Show per-zone RGB state (JSON)
qmk-rgb-tool effect <name> Set a zone-aware effect by name
qmk-rgb-tool effect --list List every effect per zone (JSON)
qmk-rgb-tool brightness <val> Set brightness (0–255) on selected zones, verified by read-back
qmk-rgb-tool speed <val> Set effect speed (0–255) on selected zones, verified by read-back; on logo and side only 0, 1 and 4 are reachable
qmk-rgb-tool color <hex> Set color (e.g. ff0000) on selected zones
qmk-rgb-tool color rgb:<hex> The same hex color, written out
qmk-rgb-tool color hsv:<h>,<s>,<v> Set hue and saturation (0–255) and write v to the brightness of the same zones
qmk-rgb-tool mode <index> Set a raw zone-specific effect ID
qmk-rgb-tool --zone <zone> ... Target logo, backlight, or side
qmk-rgb-tool --device <n> ... Target keyboard by number (see keyboard info)
qmk-rgb-tool save [name] Save current RGB state to profiles/<name>.json (name lowercased, non-[a-z0-9-_] mapped to -)
qmk-rgb-tool load [name] Load and apply a profile from profiles/
qmk-rgb-tool list List saved profiles
qmk-rgb-tool delete [name] Delete a saved profile
qmk-rgb-tool completion <shell> Write an autocompletion script for bash, zsh, fish or powershell to stdout

enable, disable, info and list take no arguments and reject a stray token. effect, load, save and delete accept an optional name. brightness, speed, color and mode require exactly one argument.

Profiles

Profiles store RGB state (effect, brightness, speed, color) per zone as JSON files in the profiles/ directory. They are the only way to persist RGB settings across reboots. The keyboard's VIA protocol does not expose a standard command to save RGB state to internal EEPROM, so profiles rely on files instead.

Architecture

cmd/qmk-rgb-tool/  # Cobra-based CLI
internal/device/  # HID discovery + keyboards.json loader
internal/hid/     # Cross-platform HID access (hidapi)
internal/rgb/     # Effects, colors, state
internal/via/     # VIA protocol implementation

Protocol

Communicates via the QMK Raw HID interface (Usage Page 0xFF60, Usage 0x61) with 32-byte reports, report number 0.

  • 0x07 — Custom set value
  • 0x08 — Custom get value
  • Lighting channels: 0x02 logo, 0x03 backlight, 0x04 side lighting
  • RGB values: brightness 0x01, effect 0x02, speed 0x03, color 0x04

enable and disable use no separate handshake: they set the effect ID and brightness, and nothing else.