feat(k2_he): add OpenRGB protocol for per-key RGB control#447
feat(k2_he): add OpenRGB protocol for per-key RGB control#447i-am-logger wants to merge 1 commit intoKeychron:hall_effect_playgroundfrom
Conversation
Adds OpenRGB protocol handler to the Keychron K2 HE, enabling per-key RGB control from Linux/Windows/macOS via the OpenRGB CLI and GUI. Changes: - Add openrgb.c/h protocol handler in keychron/common (reusable for other HE boards) - Add OPENRGB_DIRECT RGB matrix effect for host-controlled per-key colors - Route OpenRGB commands (IDs 1-9) through keychron_raw_hid.c default case - Guard RAW_EPSIZE in usb_descriptor.h with #ifndef for overridability - Set RAW_EPSIZE to 64 for OpenRGB protocol compatibility - Enable all RGB matrix animations - Boot into OPENRGB_DIRECT mode by default - Add OPENRGB_ENABLE build flag gated in keychron_common.mk The OpenRGB protocol (command IDs 1-9) does not conflict with Keychron's existing raw HID protocol (command IDs 0xA0-0xAB). Hall effect features (rapid trigger, adjustable actuation, SOCD) are fully preserved. Tested on Keychron K2 HE ANSI RGB with OpenRGB 1.0rc2 on NixOS Linux.
There was a problem hiding this comment.
Pull request overview
This PR adds OpenRGB (QMK OpenRGB protocol) support for the Keychron K2 HE to enable per-key RGB control via Raw HID, while integrating with QMK’s RGB Matrix system via a new “direct” RGB matrix effect.
Changes:
- Add a reusable OpenRGB protocol handler under
keychron/common/and route OpenRGB command IDs through Keychron’s Raw HID handler default case. - Add a new RGB matrix effect (
OPENRGB_DIRECT) and include it in the compiled RGB matrix effects set. - Allow overriding
RAW_EPSIZEand set it to 64 for the K2 HE ANSI RGB target; default keymap boots into the new direct mode; enable more animations ininfo.json.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tmk_core/protocol/usb_descriptor.h |
Makes RAW_EPSIZE overridable (defaults to 32 if not set). |
quantum/rgb_matrix/animations/rgb_matrix_effects.inc |
Includes the new OpenRGB direct animation header in the effect list. |
quantum/rgb_matrix/animations/openrgb_direct_anim.h |
Adds OPENRGB_DIRECT RGB matrix effect that renders per-LED colors provided by OpenRGB. |
keyboards/keychron/k2_he/rules.mk |
Enables OPENRGB_ENABLE for the K2 HE build. |
keyboards/keychron/k2_he/info.json |
Expands enabled RGB matrix animations for the board. |
keyboards/keychron/k2_he/ansi_rgb/keymaps/default/keymap.c |
Switches to RGB_MATRIX_OPENRGB_DIRECT at boot when OpenRGB is enabled. |
keyboards/keychron/k2_he/ansi_rgb/config.h |
Sets RAW_EPSIZE to 64 for OpenRGB compatibility. |
keyboards/keychron/common/openrgb.h |
Declares OpenRGB protocol constants, IDs, and handler API. |
keyboards/keychron/common/openrgb.c |
Implements OpenRGB command handling and per-LED color storage. |
keyboards/keychron/common/keychron_raw_hid.c |
Dispatches OpenRGB commands from the Keychron Raw HID default case. |
keyboards/keychron/common/keychron_common.mk |
Adds OPENRGB_ENABLE build flag handling, compiles OpenRGB sources, and forces RAW_ENABLE. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const uint8_t number_leds = data[1]; | ||
|
|
||
| for (uint8_t i = 0; i < number_leds; i++) { | ||
| const uint8_t data_idx = i * 4; | ||
| const uint8_t color_idx = data[data_idx + 2]; | ||
|
|
There was a problem hiding this comment.
openrgb_direct_mode_set_leds() trusts number_leds = data[1] and then indexes data[data_idx + 2..5] without clamping to the actual report length. A host can set number_leds large and trigger out-of-bounds reads (and potentially unpredictable behavior). Clamp number_leds to the maximum that fits in the received length (e.g., (length - 2) / 4) and/or return failure when the payload is too short.
| bool openrgb_command_handler(uint8_t *data, uint8_t length) { | ||
| switch (data[0]) { | ||
| case OPENRGB_GET_PROTOCOL_VERSION: | ||
| openrgb_get_protocol_version(); | ||
| break; |
There was a problem hiding this comment.
openrgb_command_handler() (and the per-command helpers it calls) dereference fixed offsets in data (e.g., data[1], data[6], etc.) but never validates length. While some platforms always pass RAW_EPSIZE, others can pass the actual received length; malformed/short reports could cause out-of-bounds reads. Consider rejecting packets where length < the minimum for the specific command (or enforcing length == RAW_EPSIZE) before accessing data[].
Summary
keychron/common/and is reusable for other HE boards — just setOPENRGB_ENABLE = yesinrules.mkkeychron_raw_hid.cdefault case — no conflict with Keychron's protocol (IDs 0xA0-0xAB)Changes
keychron/common/openrgb.c/.hkeychron/common/keychron_common.mkOPENRGB_ENABLEbuild flagkeychron/common/keychron_raw_hid.cquantum/rgb_matrix/animations/openrgb_direct_anim.hquantum/rgb_matrix/animations/rgb_matrix_effects.inctmk_core/protocol/usb_descriptor.hRAW_EPSIZEwith#ifndefk2_he/ansi_rgb/config.hRAW_EPSIZE 64for OpenRGB compatibilityk2_he/ansi_rgb/keymaps/default/keymap.ck2_he/info.jsonk2_he/rules.mkOPENRGB_ENABLEUsage
After flashing, register the keyboard in OpenRGB (Settings > QMK OpenRGB) with:
0x34340x0E20Keychron K2 HEThen control via CLI:
Test plan
make keychron/k2_he/ansi_rgb:default