# 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: ```bash sudo apt install -y libudev-dev ``` Build locally: ```bash git clone 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/`): ```bash 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 --help` for the path your platform expects; for bash and zsh on Linux: ```bash # 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`: ```zsh 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 ```bash # 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: ```bash ./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: ```bash 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: ```console $ 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: ```console $ 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](https://github.com/libusb/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 ` 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 `. 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/`](https://github.com/qmk/qmk_firmware/tree/master/quantum/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](https://github.com/libusb/hidapi) for cross-platform HID access. Most platforms need no configuration, but Linux requires extra setup. ### Linux To build, `libudev-dev` is required: ```bash 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: ```bash 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: ```bash 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 ` | Set a zone-aware effect by name | | `qmk-rgb-tool effect --list` | List every effect per zone (JSON) | | `qmk-rgb-tool brightness ` | Set brightness (0–255) on selected zones, verified by read-back | | `qmk-rgb-tool speed ` | 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 ` | Set color (e.g. `ff0000`) on selected zones | | `qmk-rgb-tool color rgb:` | The same hex color, written out | | `qmk-rgb-tool color hsv:,,` | Set hue and saturation (0–255) and write `v` to the brightness of the same zones | | `qmk-rgb-tool mode ` | Set a raw zone-specific effect ID | | `qmk-rgb-tool --zone ...` | Target `logo`, `backlight`, or `side` | | `qmk-rgb-tool --device ...` | Target keyboard by number (see `keyboard info`) | | `qmk-rgb-tool save [name]` | Save current RGB state to `profiles/.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 ` | 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.