From 2a44f73d98217a45254f2bb5ebb2e316e15beb57 Mon Sep 17 00:00:00 2001 From: Ido Samuelson Date: Wed, 10 Jun 2026 15:27:43 -0600 Subject: [PATCH 1/3] feat(k2_he): add OpenRGB protocol for per-key RGB control Adds OpenRGB raw-HID support to the Keychron K2 HE on the ansi/default keymap (the keychron keymap keeps VIA / Keychron Launcher). - Per-key direct control via a custom RGB_MATRIX_OPENRGB_DIRECT effect. - Explicit OpenRGB<->QMK mode map: OpenRGB assigns mode values in its own fixed order, so a table (in OpenRGB's order, Direct last) translates GET_ENABLED_MODES / SET_MODE / GET_MODE_INFO instead of trusting QMK's effect numbering, so animation names line up with the real effects. - Replies are sent via kc_raw_hid_send(RAW_HID_SRC_USB, ...) like the Keychron Launcher's KC_* handlers, not the core raw_hid_send(): on the 2025q3 base the active host driver becomes the wireless transport (whose send_raw_hid is NULL) when the side switch is on BT/2.4G, which silently dropped OpenRGB replies and made the host time out. - Host-side unit tests (keyboards/keychron/common/tests) compile openrgb.c against mocks and prove both the mode mapping and the USB-reply routing. --- builddefs/build_test.mk | 1 + builddefs/testlist.mk | 1 + keyboards/keychron/common/keychron_common.mk | 6 + keyboards/keychron/common/keychron_raw_hid.c | 8 + keyboards/keychron/common/openrgb.c | 431 ++++++++++++++++++ keyboards/keychron/common/openrgb.h | 98 ++++ keyboards/keychron/common/tests/mocks/color.h | 4 + .../common/tests/mocks/keychron_raw_hid.h | 4 + .../common/tests/mocks/keymap_introspection.h | 4 + .../common/tests/mocks/openrgb_test_shim.h | 155 +++++++ .../keychron/common/tests/mocks/quantum.h | 4 + .../keychron/common/tests/mocks/raw_hid.h | 4 + .../common/tests/mocks/usb_descriptor.h | 4 + .../keychron/common/tests/mocks/version.h | 25 + .../common/tests/openrgb_firmware_wiring.sh | 29 ++ .../keychron/common/tests/openrgb_mapping.cpp | 246 ++++++++++ .../common/tests/openrgb_test_mocks.cpp | 101 ++++ .../common/tests/openrgb_test_recorder.h | 40 ++ keyboards/keychron/common/tests/rules.mk | 56 +++ keyboards/keychron/common/tests/testlist.mk | 1 + .../k2_he/ansi/keymaps/default/config.h | 20 + .../k2_he/ansi/keymaps/default/keymap.c | 6 + .../k2_he/ansi/keymaps/default/rules.mk | 12 + .../animations/openrgb_direct_anim.h | 21 + .../animations/rgb_matrix_effects.inc | 1 + tmk_core/protocol/usb_descriptor.h | 17 +- 26 files changed, 1291 insertions(+), 8 deletions(-) create mode 100644 keyboards/keychron/common/openrgb.c create mode 100644 keyboards/keychron/common/openrgb.h create mode 100644 keyboards/keychron/common/tests/mocks/color.h create mode 100644 keyboards/keychron/common/tests/mocks/keychron_raw_hid.h create mode 100644 keyboards/keychron/common/tests/mocks/keymap_introspection.h create mode 100644 keyboards/keychron/common/tests/mocks/openrgb_test_shim.h create mode 100644 keyboards/keychron/common/tests/mocks/quantum.h create mode 100644 keyboards/keychron/common/tests/mocks/raw_hid.h create mode 100644 keyboards/keychron/common/tests/mocks/usb_descriptor.h create mode 100644 keyboards/keychron/common/tests/mocks/version.h create mode 100755 keyboards/keychron/common/tests/openrgb_firmware_wiring.sh create mode 100644 keyboards/keychron/common/tests/openrgb_mapping.cpp create mode 100644 keyboards/keychron/common/tests/openrgb_test_mocks.cpp create mode 100644 keyboards/keychron/common/tests/openrgb_test_recorder.h create mode 100644 keyboards/keychron/common/tests/rules.mk create mode 100644 keyboards/keychron/common/tests/testlist.mk create mode 100644 keyboards/keychron/k2_he/ansi/keymaps/default/config.h create mode 100644 keyboards/keychron/k2_he/ansi/keymaps/default/rules.mk create mode 100644 quantum/rgb_matrix/animations/openrgb_direct_anim.h diff --git a/builddefs/build_test.mk b/builddefs/build_test.mk index ccdad1b4e798..4a26a5da0cbe 100644 --- a/builddefs/build_test.mk +++ b/builddefs/build_test.mk @@ -68,6 +68,7 @@ include $(QUANTUM_PATH)/encoder/tests/rules.mk include $(QUANTUM_PATH)/os_detection/tests/rules.mk include $(QUANTUM_PATH)/sequencer/tests/rules.mk include $(QUANTUM_PATH)/wear_leveling/tests/rules.mk +include keyboards/keychron/common/tests/rules.mk include $(QUANTUM_PATH)/logging/print.mk include $(PLATFORM_PATH)/test/rules.mk ifneq ($(filter $(FULL_TESTS),$(TEST)),) diff --git a/builddefs/testlist.mk b/builddefs/testlist.mk index 2e81fe576bfc..c66b56a164e8 100644 --- a/builddefs/testlist.mk +++ b/builddefs/testlist.mk @@ -7,6 +7,7 @@ include $(QUANTUM_PATH)/encoder/tests/testlist.mk include $(QUANTUM_PATH)/os_detection/tests/testlist.mk include $(QUANTUM_PATH)/sequencer/tests/testlist.mk include $(QUANTUM_PATH)/wear_leveling/tests/testlist.mk +include keyboards/keychron/common/tests/testlist.mk include $(PLATFORM_PATH)/test/testlist.mk define VALIDATE_TEST_LIST diff --git a/keyboards/keychron/common/keychron_common.mk b/keyboards/keychron/common/keychron_common.mk index aa8801f0cd2d..c8ad0eee31b6 100644 --- a/keyboards/keychron/common/keychron_common.mk +++ b/keyboards/keychron/common/keychron_common.mk @@ -31,6 +31,12 @@ include $(KEYCHRON_COMMON_DIR)/rgb/rgb.mk endif endif +ifeq ($(strip $(OPENRGB_ENABLE)), yes) + OPT_DEFS += -DOPENRGB_ENABLE + SRC += $(KEYCHRON_COMMON_DIR)/openrgb.c + RAW_ENABLE = yes +endif + ifeq ($(strip $(USB_REPORT_INTERVAL_ENABLE)), yes) OPT_DEFS += -DUSB_REPORT_INTERVAL_ENABLE SRC += $(KEYCHRON_COMMON_DIR)/usb_report_rate.c diff --git a/keyboards/keychron/common/keychron_raw_hid.c b/keyboards/keychron/common/keychron_raw_hid.c index 194d97fede38..4b6d2460c896 100644 --- a/keyboards/keychron/common/keychron_raw_hid.c +++ b/keyboards/keychron/common/keychron_raw_hid.c @@ -26,6 +26,9 @@ #ifdef ANANLOG_MATRIX # include "analog_matrix.h" #endif +#ifdef OPENRGB_ENABLE +# include "openrgb.h" +#endif #ifdef DYNAMIC_DEBOUNCE_ENABLE # include "keychron_debounce.h" #endif @@ -263,6 +266,11 @@ bool kc_raw_hid_rx(uint8_t src, uint8_t *data, uint8_t length) { # endif default: +# ifdef OPENRGB_ENABLE + if (src == RAW_HID_SRC_USB && openrgb_command_handler(data, length)) { + return true; + } +# endif return false; } diff --git a/keyboards/keychron/common/openrgb.c b/keyboards/keychron/common/openrgb.c new file mode 100644 index 000000000000..1d6733c1ec16 --- /dev/null +++ b/keyboards/keychron/common/openrgb.c @@ -0,0 +1,431 @@ +/* Copyright 2020 Kasper + * Copyright 2025 Keychron K2 HE OpenRGB patch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "quantum.h" +#include "openrgb.h" +#include "raw_hid.h" +#include "usb_descriptor.h" +#include "version.h" +#include "keymap_introspection.h" +#include "keychron_raw_hid.h" +#include "string.h" + +#if !defined(OPENRGB_DIRECT_MODE_STARTUP_RED) +# define OPENRGB_DIRECT_MODE_STARTUP_RED 0 +#endif + +#if !defined(OPENRGB_DIRECT_MODE_STARTUP_GREEN) +# define OPENRGB_DIRECT_MODE_STARTUP_GREEN 0 +#endif + +#if !defined(OPENRGB_DIRECT_MODE_STARTUP_BLUE) +# define OPENRGB_DIRECT_MODE_STARTUP_BLUE 0 +#endif + +RGB g_openrgb_direct_mode_colors[RGB_MATRIX_LED_COUNT] = {[0 ... RGB_MATRIX_LED_COUNT - 1] = {OPENRGB_DIRECT_MODE_STARTUP_RED, OPENRGB_DIRECT_MODE_STARTUP_GREEN, OPENRGB_DIRECT_MODE_STARTUP_BLUE}}; + +typedef struct { + uint8_t openrgb_id; + uint8_t qmk_mode; +} openrgb_mode_map_t; + +/* Maps OpenRGB modes to QMK rgb_matrix effects, in OpenRGB's own mode order + * (the if-order of OpenRGB's RGBController_QMKOpenRGBRevD.cpp), with OpenRGB + * Direct last. OpenRGB assigns each enabled mode a sequential value in this + * order and sends that value back in SET_MODE, so the index into this table is + * exactly the value OpenRGB uses -- decoupling the protocol from QMK's own + * effect numbering (which differs and contains effects OpenRGB doesn't know). + * Each entry is guarded by the same ENABLE_RGB_MATRIX_* flag that gates the + * effect, so the table reduces to precisely the effects compiled in. */ +static const openrgb_mode_map_t openrgb_mode_map[] = { + {OPENRGB_MODE_SOLID_COLOR, RGB_MATRIX_SOLID_COLOR}, +#ifdef ENABLE_RGB_MATRIX_ALPHAS_MODS + {OPENRGB_MODE_ALPHA_MOD, RGB_MATRIX_ALPHAS_MODS}, +#endif +#ifdef ENABLE_RGB_MATRIX_GRADIENT_UP_DOWN + {OPENRGB_MODE_GRADIENT_UP_DOWN, RGB_MATRIX_GRADIENT_UP_DOWN}, +#endif +#ifdef ENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT + {OPENRGB_MODE_GRADIENT_LEFT_RIGHT, RGB_MATRIX_GRADIENT_LEFT_RIGHT}, +#endif +#ifdef ENABLE_RGB_MATRIX_BREATHING + {OPENRGB_MODE_BREATHING, RGB_MATRIX_BREATHING}, +#endif +#ifdef ENABLE_RGB_MATRIX_BAND_SAT + {OPENRGB_MODE_BAND_SAT, RGB_MATRIX_BAND_SAT}, +#endif +#ifdef ENABLE_RGB_MATRIX_BAND_VAL + {OPENRGB_MODE_BAND_VAL, RGB_MATRIX_BAND_VAL}, +#endif +#ifdef ENABLE_RGB_MATRIX_BAND_PINWHEEL_SAT + {OPENRGB_MODE_BAND_PINWHEEL_SAT, RGB_MATRIX_BAND_PINWHEEL_SAT}, +#endif +#ifdef ENABLE_RGB_MATRIX_BAND_PINWHEEL_VAL + {OPENRGB_MODE_BAND_PINWHEEL_VAL, RGB_MATRIX_BAND_PINWHEEL_VAL}, +#endif +#ifdef ENABLE_RGB_MATRIX_BAND_SPIRAL_SAT + {OPENRGB_MODE_BAND_SPIRAL_SAT, RGB_MATRIX_BAND_SPIRAL_SAT}, +#endif +#ifdef ENABLE_RGB_MATRIX_BAND_SPIRAL_VAL + {OPENRGB_MODE_BAND_SPIRAL_VAL, RGB_MATRIX_BAND_SPIRAL_VAL}, +#endif +#ifdef ENABLE_RGB_MATRIX_CYCLE_ALL + {OPENRGB_MODE_CYCLE_ALL, RGB_MATRIX_CYCLE_ALL}, +#endif +#ifdef ENABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT + {OPENRGB_MODE_CYCLE_LEFT_RIGHT, RGB_MATRIX_CYCLE_LEFT_RIGHT}, +#endif +#ifdef ENABLE_RGB_MATRIX_CYCLE_UP_DOWN + {OPENRGB_MODE_CYCLE_UP_DOWN, RGB_MATRIX_CYCLE_UP_DOWN}, +#endif +#ifdef ENABLE_RGB_MATRIX_CYCLE_OUT_IN + {OPENRGB_MODE_CYCLE_OUT_IN, RGB_MATRIX_CYCLE_OUT_IN}, +#endif +#ifdef ENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL + {OPENRGB_MODE_CYCLE_OUT_IN_DUAL, RGB_MATRIX_CYCLE_OUT_IN_DUAL}, +#endif +#ifdef ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON + {OPENRGB_MODE_RAINBOW_MOVING_CHEVRON, RGB_MATRIX_RAINBOW_MOVING_CHEVRON}, +#endif +#ifdef ENABLE_RGB_MATRIX_CYCLE_PINWHEEL + {OPENRGB_MODE_CYCLE_PINWHEEL, RGB_MATRIX_CYCLE_PINWHEEL}, +#endif +#ifdef ENABLE_RGB_MATRIX_CYCLE_SPIRAL + {OPENRGB_MODE_CYCLE_SPIRAL, RGB_MATRIX_CYCLE_SPIRAL}, +#endif +#ifdef ENABLE_RGB_MATRIX_DUAL_BEACON + {OPENRGB_MODE_DUAL_BEACON, RGB_MATRIX_DUAL_BEACON}, +#endif +#ifdef ENABLE_RGB_MATRIX_RAINBOW_BEACON + {OPENRGB_MODE_RAINBOW_BEACON, RGB_MATRIX_RAINBOW_BEACON}, +#endif +#ifdef ENABLE_RGB_MATRIX_RAINBOW_PINWHEELS + {OPENRGB_MODE_RAINBOW_PINWHEELS, RGB_MATRIX_RAINBOW_PINWHEELS}, +#endif +#ifdef ENABLE_RGB_MATRIX_RAINDROPS + {OPENRGB_MODE_RAINDROPS, RGB_MATRIX_RAINDROPS}, +#endif +#ifdef ENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS + {OPENRGB_MODE_JELLYBEAN_RAINDROPS, RGB_MATRIX_JELLYBEAN_RAINDROPS}, +#endif +#ifdef ENABLE_RGB_MATRIX_HUE_BREATHING + {OPENRGB_MODE_HUE_BREATHING, RGB_MATRIX_HUE_BREATHING}, +#endif +#ifdef ENABLE_RGB_MATRIX_HUE_PENDULUM + {OPENRGB_MODE_HUE_PENDULUM, RGB_MATRIX_HUE_PENDULUM}, +#endif +#ifdef ENABLE_RGB_MATRIX_HUE_WAVE + {OPENRGB_MODE_HUE_WAVE, RGB_MATRIX_HUE_WAVE}, +#endif +#ifdef ENABLE_RGB_MATRIX_TYPING_HEATMAP + {OPENRGB_MODE_TYPING_HEATMAP, RGB_MATRIX_TYPING_HEATMAP}, +#endif +#ifdef ENABLE_RGB_MATRIX_DIGITAL_RAIN + {OPENRGB_MODE_DIGITAL_RAIN, RGB_MATRIX_DIGITAL_RAIN}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE + {OPENRGB_MODE_SOLID_REACTIVE_SIMPLE, RGB_MATRIX_SOLID_REACTIVE_SIMPLE}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE + {OPENRGB_MODE_SOLID_REACTIVE, RGB_MATRIX_SOLID_REACTIVE}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_WIDE + {OPENRGB_MODE_SOLID_REACTIVE_WIDE, RGB_MATRIX_SOLID_REACTIVE_WIDE}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE + {OPENRGB_MODE_SOLID_REACTIVE_MULTIWIDE, RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_CROSS + {OPENRGB_MODE_SOLID_REACTIVE_CROSS, RGB_MATRIX_SOLID_REACTIVE_CROSS}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTICROSS + {OPENRGB_MODE_SOLID_REACTIVE_MULTICROSS, RGB_MATRIX_SOLID_REACTIVE_MULTICROSS}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_NEXUS + {OPENRGB_MODE_SOLID_REACTIVE_NEXUS, RGB_MATRIX_SOLID_REACTIVE_NEXUS}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS + {OPENRGB_MODE_SOLID_REACTIVE_MULTINEXUS, RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS}, +#endif +#ifdef ENABLE_RGB_MATRIX_SPLASH + {OPENRGB_MODE_SPLASH, RGB_MATRIX_SPLASH}, +#endif +#ifdef ENABLE_RGB_MATRIX_MULTISPLASH + {OPENRGB_MODE_MULTISPLASH, RGB_MATRIX_MULTISPLASH}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_SPLASH + {OPENRGB_MODE_SOLID_SPLASH, RGB_MATRIX_SOLID_SPLASH}, +#endif +#ifdef ENABLE_RGB_MATRIX_SOLID_MULTISPLASH + {OPENRGB_MODE_SOLID_MULTISPLASH, RGB_MATRIX_SOLID_MULTISPLASH}, +#endif + {OPENRGB_MODE_OPENRGB_DIRECT, RGB_MATRIX_OPENRGB_DIRECT}, +}; + +#define OPENRGB_MODE_COUNT (sizeof(openrgb_mode_map) / sizeof(openrgb_mode_map[0])) + +static uint8_t raw_hid_buffer[RAW_EPSIZE]; + +static void openrgb_get_protocol_version(void) { + raw_hid_buffer[0] = OPENRGB_GET_PROTOCOL_VERSION; + raw_hid_buffer[1] = OPENRGB_PROTOCOL_VERSION; +} + +static void openrgb_get_qmk_version(void) { + raw_hid_buffer[0] = OPENRGB_GET_QMK_VERSION; + uint8_t current_byte = 1; + for (uint8_t i = 0; (current_byte < (RAW_EPSIZE - 2)) && (QMK_VERSION[i] != 0); i++) { + raw_hid_buffer[current_byte] = QMK_VERSION[i]; + current_byte++; + } +} + +static void openrgb_get_device_info(void) { + raw_hid_buffer[0] = OPENRGB_GET_DEVICE_INFO; + raw_hid_buffer[1] = RGB_MATRIX_LED_COUNT; + raw_hid_buffer[2] = MATRIX_COLS * MATRIX_ROWS; + +#define MASSDROP_VID 0x04D8 +#if VENDOR_ID == MASSDROP_VID +# define PRODUCT_STRING PRODUCT +# define MANUFACTURER_STRING MANUFACTURER +#else +# define PRODUCT_STRING STR(PRODUCT) +# define MANUFACTURER_STRING STR(MANUFACTURER) +#endif + + // Reserve the last 2 bytes for the status / end-of-message markers. The + // product string takes the first half of the remaining space, the + // manufacturer string the rest; both are explicitly null-terminated. + uint8_t current_byte = 3; + for (uint8_t i = 0; (current_byte < ((RAW_EPSIZE - 2) / 2)) && (PRODUCT_STRING[i] != 0); i++) { + raw_hid_buffer[current_byte++] = PRODUCT_STRING[i]; + } + raw_hid_buffer[current_byte++] = 0; + + for (uint8_t i = 0; (current_byte < (RAW_EPSIZE - 2)) && (MANUFACTURER_STRING[i] != 0); i++) { + raw_hid_buffer[current_byte++] = MANUFACTURER_STRING[i]; + } + raw_hid_buffer[current_byte++] = 0; + +#undef MASSDROP_VID +#undef PRODUCT_STRING +#undef MANUFACTURER_STRING +} + +static void openrgb_get_mode_info(void) { + const HSV hsv_color = rgb_matrix_get_hsv(); + const uint8_t qmk_mode = rgb_matrix_get_mode(); + + // Translate the active QMK effect back to OpenRGB's 1-based mode value. If + // the current effect has no OpenRGB equivalent (e.g. one selected on the + // keyboard itself), report 0 so OpenRGB just doesn't highlight a mode. + uint8_t openrgb_mode = 0; + for (uint8_t i = 0; i < OPENRGB_MODE_COUNT; i++) { + if (openrgb_mode_map[i].qmk_mode == qmk_mode) { + openrgb_mode = i + 1; + break; + } + } + + raw_hid_buffer[0] = OPENRGB_GET_MODE_INFO; + raw_hid_buffer[1] = openrgb_mode; + raw_hid_buffer[2] = rgb_matrix_get_speed(); + raw_hid_buffer[3] = hsv_color.h; + raw_hid_buffer[4] = hsv_color.s; + raw_hid_buffer[5] = hsv_color.v; +} + +static void openrgb_get_led_info(uint8_t *data) { + const uint8_t first_led = data[1]; + const uint8_t number_leds = data[2]; + + raw_hid_buffer[0] = OPENRGB_GET_LED_INFO; + + const uint8_t max_leds = (RAW_EPSIZE - 2) / 7; // 7 bytes per LED entry + for (uint8_t i = 0; i < number_leds && i < max_leds; i++) { + const uint8_t led_idx = first_led + i; + const uint8_t data_idx = i * 7; + + if (led_idx >= RGB_MATRIX_LED_COUNT) { + raw_hid_buffer[data_idx + 3] = OPENRGB_FAILURE; + } else { + raw_hid_buffer[data_idx + 1] = g_led_config.point[led_idx].x; + raw_hid_buffer[data_idx + 2] = g_led_config.point[led_idx].y; + raw_hid_buffer[data_idx + 3] = g_led_config.flags[led_idx]; + raw_hid_buffer[data_idx + 4] = g_openrgb_direct_mode_colors[led_idx].r; + raw_hid_buffer[data_idx + 5] = g_openrgb_direct_mode_colors[led_idx].g; + raw_hid_buffer[data_idx + 6] = g_openrgb_direct_mode_colors[led_idx].b; + } + + uint8_t row = 0; + uint8_t col = 0; + uint8_t found = 0; + + for (row = 0; row < MATRIX_ROWS; row++) { + for (col = 0; col < MATRIX_COLS; col++) { + if (g_led_config.matrix_co[row][col] == led_idx) { + found = 1; + break; + } + } + if (found == 1) { + break; + } + } + + if (col >= MATRIX_COLS || row >= MATRIX_ROWS) { + raw_hid_buffer[data_idx + 7] = KC_NO; + } else { + raw_hid_buffer[data_idx + 7] = (uint8_t)keycode_at_keymap_location(0, row, col); + } + } +} + +static void openrgb_get_enabled_modes(void) { + raw_hid_buffer[0] = OPENRGB_GET_ENABLED_MODES; + + // Report the OpenRGB mode ids we support, as a null-terminated list (the + // host reads bytes until it hits a 0). Order is irrelevant here -- OpenRGB + // matches by value -- but keep one byte free for the terminator. + const uint8_t max_entries = RAW_EPSIZE - 2; + uint8_t idx = 0; + for (uint8_t i = 0; i < OPENRGB_MODE_COUNT && idx < max_entries - 1; i++) { + raw_hid_buffer[1 + idx++] = openrgb_mode_map[i].openrgb_id; + } + raw_hid_buffer[1 + idx] = 0; +} + +static void openrgb_set_mode(uint8_t *data) { + const uint8_t h = data[1]; + const uint8_t s = data[2]; + const uint8_t v = data[3]; + const uint8_t mode = data[4]; // OpenRGB's 1-based mode value == index into openrgb_mode_map + const uint8_t speed = data[5]; + const uint8_t save = data[6]; + + raw_hid_buffer[0] = OPENRGB_SET_MODE; + + if (mode < 1 || mode > OPENRGB_MODE_COUNT) { + raw_hid_buffer[RAW_EPSIZE - 2] = OPENRGB_FAILURE; + return; + } + + const uint8_t qmk_mode = openrgb_mode_map[mode - 1].qmk_mode; + + if (save == 1) { + rgb_matrix_mode(qmk_mode); + rgb_matrix_set_speed(speed); + rgb_matrix_sethsv(h, s, v); + } else { + rgb_matrix_mode_noeeprom(qmk_mode); + rgb_matrix_set_speed_noeeprom(speed); + rgb_matrix_sethsv_noeeprom(h, s, v); + } + + raw_hid_buffer[RAW_EPSIZE - 2] = OPENRGB_SUCCESS; +} + +static void openrgb_direct_mode_set_single_led(uint8_t *data) { + const uint8_t led = data[1]; + const uint8_t r = data[2]; + const uint8_t g = data[3]; + const uint8_t b = data[4]; + + raw_hid_buffer[0] = OPENRGB_DIRECT_MODE_SET_SINGLE_LED; + + if (led >= RGB_MATRIX_LED_COUNT) { + raw_hid_buffer[RAW_EPSIZE - 2] = OPENRGB_FAILURE; + return; + } + + g_openrgb_direct_mode_colors[led].r = r; + g_openrgb_direct_mode_colors[led].g = g; + g_openrgb_direct_mode_colors[led].b = b; + + raw_hid_buffer[RAW_EPSIZE - 2] = OPENRGB_SUCCESS; +} + +static void openrgb_direct_mode_set_leds(uint8_t *data, uint8_t length) { + const uint8_t number_leds = data[1]; + // Each LED entry is 4 bytes ([index, r, g, b]) following the 2-byte header + // (command id + count). Clamp to what actually fits in the received report so + // a malformed packet with an inflated count cannot drive reads past the buffer. + const uint8_t max_leds = length > 2 ? (uint8_t)((length - 2) / 4) : 0; + + for (uint8_t i = 0; i < number_leds && i < max_leds; i++) { + const uint8_t data_idx = i * 4; + const uint8_t color_idx = data[data_idx + 2]; + + if (color_idx < RGB_MATRIX_LED_COUNT) { + g_openrgb_direct_mode_colors[color_idx].r = data[data_idx + 3]; + g_openrgb_direct_mode_colors[color_idx].g = data[data_idx + 4]; + g_openrgb_direct_mode_colors[color_idx].b = data[data_idx + 5]; + } + } +} + +bool openrgb_command_handler(uint8_t *data, uint8_t length) { + if (length < 1) { + return false; + } + + switch (data[0]) { + case OPENRGB_GET_PROTOCOL_VERSION: + openrgb_get_protocol_version(); + break; + case OPENRGB_GET_QMK_VERSION: + openrgb_get_qmk_version(); + break; + case OPENRGB_GET_DEVICE_INFO: + openrgb_get_device_info(); + break; + case OPENRGB_GET_MODE_INFO: + openrgb_get_mode_info(); + break; + case OPENRGB_GET_LED_INFO: + openrgb_get_led_info(data); + break; + case OPENRGB_GET_ENABLED_MODES: + openrgb_get_enabled_modes(); + break; + case OPENRGB_SET_MODE: + openrgb_set_mode(data); + break; + case OPENRGB_DIRECT_MODE_SET_SINGLE_LED: + openrgb_direct_mode_set_single_led(data); + break; + case OPENRGB_DIRECT_MODE_SET_LEDS: + openrgb_direct_mode_set_leds(data, length); + break; + default: + return false; + } + + if (data[0] != OPENRGB_DIRECT_MODE_SET_LEDS) { + raw_hid_buffer[RAW_EPSIZE - 1] = OPENRGB_END_OF_MESSAGE; + // Reply over USB explicitly (like the Keychron Launcher's KC_* handlers do). + // The core raw_hid_send() routes through the active host driver, whose + // send_raw_hid is NULL while the wireless transport is installed (side + // switch on BT/2.4G) -- silently dropping the reply. OpenRGB is USB-only + // and the dispatch is already gated on src == RAW_HID_SRC_USB. + kc_raw_hid_send(RAW_HID_SRC_USB, raw_hid_buffer, RAW_EPSIZE); + memset(raw_hid_buffer, 0x00, RAW_EPSIZE); + } + + return true; +} diff --git a/keyboards/keychron/common/openrgb.h b/keyboards/keychron/common/openrgb.h new file mode 100644 index 000000000000..1f6b41a38f24 --- /dev/null +++ b/keyboards/keychron/common/openrgb.h @@ -0,0 +1,98 @@ +/* Copyright 2020 Kasper + * Copyright 2025 Keychron K2 HE OpenRGB patch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include "color.h" + +#define OPENRGB_PROTOCOL_VERSION 0xD + +enum openrgb_command_id { + OPENRGB_GET_PROTOCOL_VERSION = 1, + OPENRGB_GET_QMK_VERSION, + OPENRGB_GET_DEVICE_INFO, + OPENRGB_GET_MODE_INFO, + OPENRGB_GET_LED_INFO, + OPENRGB_GET_ENABLED_MODES, + + OPENRGB_SET_MODE, + OPENRGB_DIRECT_MODE_SET_SINGLE_LED, + OPENRGB_DIRECT_MODE_SET_LEDS, +}; + +enum openrgb_responses { + OPENRGB_FAILURE = 25, + OPENRGB_SUCCESS = 50, + OPENRGB_END_OF_MESSAGE = 100, +}; + +/* OpenRGB mode identifiers. These mirror the `Modes` enum in OpenRGB's + * QMKOpenRGBController.h: OpenRGB looks for these values in the + * GET_ENABLED_MODES reply (via find()) and never assumes the keyboard's own + * rgb_matrix effect order. openrgb.c maps each to a QMK rgb_matrix effect. */ +enum openrgb_mode_id { + OPENRGB_MODE_OPENRGB_DIRECT = 1, + OPENRGB_MODE_SOLID_COLOR, + OPENRGB_MODE_ALPHA_MOD, + OPENRGB_MODE_GRADIENT_UP_DOWN, + OPENRGB_MODE_GRADIENT_LEFT_RIGHT, + OPENRGB_MODE_BREATHING, + OPENRGB_MODE_BAND_SAT, + OPENRGB_MODE_BAND_VAL, + OPENRGB_MODE_BAND_PINWHEEL_SAT, + OPENRGB_MODE_BAND_PINWHEEL_VAL, + OPENRGB_MODE_BAND_SPIRAL_SAT, + OPENRGB_MODE_BAND_SPIRAL_VAL, + OPENRGB_MODE_CYCLE_ALL, + OPENRGB_MODE_CYCLE_LEFT_RIGHT, + OPENRGB_MODE_CYCLE_UP_DOWN, + OPENRGB_MODE_CYCLE_OUT_IN, + OPENRGB_MODE_CYCLE_OUT_IN_DUAL, + OPENRGB_MODE_RAINBOW_MOVING_CHEVRON, + OPENRGB_MODE_CYCLE_PINWHEEL, + OPENRGB_MODE_CYCLE_SPIRAL, + OPENRGB_MODE_DUAL_BEACON, + OPENRGB_MODE_RAINBOW_BEACON, + OPENRGB_MODE_RAINBOW_PINWHEELS, + OPENRGB_MODE_RAINDROPS, + OPENRGB_MODE_JELLYBEAN_RAINDROPS, + OPENRGB_MODE_HUE_BREATHING, + OPENRGB_MODE_HUE_PENDULUM, + OPENRGB_MODE_HUE_WAVE, + OPENRGB_MODE_TYPING_HEATMAP, + OPENRGB_MODE_DIGITAL_RAIN, + OPENRGB_MODE_SOLID_REACTIVE_SIMPLE, + OPENRGB_MODE_SOLID_REACTIVE, + OPENRGB_MODE_SOLID_REACTIVE_WIDE, + OPENRGB_MODE_SOLID_REACTIVE_MULTIWIDE, + OPENRGB_MODE_SOLID_REACTIVE_CROSS, + OPENRGB_MODE_SOLID_REACTIVE_MULTICROSS, + OPENRGB_MODE_SOLID_REACTIVE_NEXUS, + OPENRGB_MODE_SOLID_REACTIVE_MULTINEXUS, + OPENRGB_MODE_SPLASH, + OPENRGB_MODE_MULTISPLASH, + OPENRGB_MODE_SOLID_SPLASH, + OPENRGB_MODE_SOLID_MULTISPLASH, + OPENRGB_MODE_PIXEL_RAIN, + OPENRGB_MODE_PIXEL_FLOW, + OPENRGB_MODE_PIXEL_FRACTAL, +}; + +extern RGB g_openrgb_direct_mode_colors[]; + +bool openrgb_command_handler(uint8_t *data, uint8_t length); diff --git a/keyboards/keychron/common/tests/mocks/color.h b/keyboards/keychron/common/tests/mocks/color.h new file mode 100644 index 000000000000..b2b6a2a3de9c --- /dev/null +++ b/keyboards/keychron/common/tests/mocks/color.h @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Shadow of color.h for the OpenRGB unit tests. +#pragma once +#include "openrgb_test_shim.h" diff --git a/keyboards/keychron/common/tests/mocks/keychron_raw_hid.h b/keyboards/keychron/common/tests/mocks/keychron_raw_hid.h new file mode 100644 index 000000000000..7b4a63497317 --- /dev/null +++ b/keyboards/keychron/common/tests/mocks/keychron_raw_hid.h @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Shadow of keychron_raw_hid.h for the OpenRGB unit tests. +#pragma once +#include "openrgb_test_shim.h" diff --git a/keyboards/keychron/common/tests/mocks/keymap_introspection.h b/keyboards/keychron/common/tests/mocks/keymap_introspection.h new file mode 100644 index 000000000000..8dd0fe534ebe --- /dev/null +++ b/keyboards/keychron/common/tests/mocks/keymap_introspection.h @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Shadow of keymap_introspection.h for the OpenRGB unit tests. +#pragma once +#include "openrgb_test_shim.h" diff --git a/keyboards/keychron/common/tests/mocks/openrgb_test_shim.h b/keyboards/keychron/common/tests/mocks/openrgb_test_shim.h new file mode 100644 index 000000000000..f2278edd3ed1 --- /dev/null +++ b/keyboards/keychron/common/tests/mocks/openrgb_test_shim.h @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Shared mock shim for the Keychron K2 HE OpenRGB unit tests. +// +// openrgb.c (the code under test, UNMODIFIED) pulls in a pile of QMK headers via +// "quantum.h" et al. To compile it in isolation against the host gtest harness we +// place a mocks/ directory FIRST on the include path so the shadow headers +// (quantum.h, color.h, raw_hid.h, usb_descriptor.h, version.h, +// keymap_introspection.h, keychron_raw_hid.h) shadow the real QMK ones. Every one +// of those shadow headers just #includes this single shim, which declares exactly +// the symbols openrgb.c references. +// +// This file is C/C++ shared: the recording state and the mock function bodies live +// in openrgb_test_mocks.cpp; the gtest cases in openrgb_mapping.cpp reach into the +// recording state through openrgb_test_recorder.h. + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// --------------------------------------------------------------------------- +// Color types (mirror quantum/color.h rgb_t / hsv_t layout) +// --------------------------------------------------------------------------- +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; +} RGB; + +typedef struct { + uint8_t h; + uint8_t s; + uint8_t v; +} HSV; + +// --------------------------------------------------------------------------- +// LED config (mirror led_config_t / led_point_t) +// --------------------------------------------------------------------------- +typedef struct { + uint8_t x; + uint8_t y; +} led_point_t; + +typedef struct { + uint8_t matrix_co[MATRIX_ROWS][MATRIX_COLS]; + led_point_t point[RGB_MATRIX_LED_COUNT]; + uint8_t flags[RGB_MATRIX_LED_COUNT]; +} led_config_t; + +extern led_config_t g_led_config; + +// --------------------------------------------------------------------------- +// Keycodes / keymap introspection +// --------------------------------------------------------------------------- +#define KC_NO 0 + +uint16_t keycode_at_keymap_location(uint8_t layer, uint8_t row, uint8_t col); + +// --------------------------------------------------------------------------- +// RGB matrix effect enum -- mirrors the real 2025q3 +// quantum/rgb_matrix/animations/rgb_matrix_effects.inc ORDER so that the OpenRGB +// mode order (in openrgb.h) genuinely differs from QMK's effect numbering. +// +// Crucially: RAINBOW_MOVING_CHEVRON sits BEFORE CYCLE_OUT_IN here (real QMK +// order), but AFTER it in OpenRGB's order -- this divergence is what makes the +// mode-mapping fix observable. Only the effects gated by the -DENABLE_RGB_MATRIX_* +// flags actually land in openrgb_mode_map[]; the rest just occupy enum slots +// (exactly as in real QMK, where every animation file contributes an enum value +// regardless of which are compiled in). +// --------------------------------------------------------------------------- +enum rgb_matrix_effects { + RGB_MATRIX_NONE = 0, + RGB_MATRIX_SOLID_COLOR, + RGB_MATRIX_ALPHAS_MODS, + RGB_MATRIX_GRADIENT_UP_DOWN, + RGB_MATRIX_GRADIENT_LEFT_RIGHT, + RGB_MATRIX_BREATHING, + RGB_MATRIX_BAND_SAT, + RGB_MATRIX_BAND_VAL, + RGB_MATRIX_BAND_PINWHEEL_SAT, + RGB_MATRIX_BAND_PINWHEEL_VAL, + RGB_MATRIX_BAND_SPIRAL_SAT, + RGB_MATRIX_BAND_SPIRAL_VAL, + RGB_MATRIX_CYCLE_ALL, + RGB_MATRIX_CYCLE_LEFT_RIGHT, + RGB_MATRIX_CYCLE_UP_DOWN, + RGB_MATRIX_RAINBOW_MOVING_CHEVRON, // index 15 (before CYCLE_OUT_IN) + RGB_MATRIX_CYCLE_OUT_IN, // index 16 + RGB_MATRIX_CYCLE_OUT_IN_DUAL, + RGB_MATRIX_CYCLE_PINWHEEL, + RGB_MATRIX_CYCLE_SPIRAL, + RGB_MATRIX_DUAL_BEACON, + RGB_MATRIX_RAINBOW_BEACON, + RGB_MATRIX_RAINBOW_PINWHEELS, + RGB_MATRIX_FLOWER_BLOOMING, + RGB_MATRIX_RAINDROPS, + RGB_MATRIX_JELLYBEAN_RAINDROPS, + RGB_MATRIX_HUE_BREATHING, + RGB_MATRIX_HUE_PENDULUM, + RGB_MATRIX_HUE_WAVE, + RGB_MATRIX_PIXEL_RAIN, + RGB_MATRIX_PIXEL_FLOW, + RGB_MATRIX_PIXEL_FRACTAL, + RGB_MATRIX_TYPING_HEATMAP, + RGB_MATRIX_DIGITAL_RAIN, + RGB_MATRIX_SOLID_REACTIVE_SIMPLE, + RGB_MATRIX_SOLID_REACTIVE, + RGB_MATRIX_SOLID_REACTIVE_WIDE, + RGB_MATRIX_SOLID_REACTIVE_CROSS, + RGB_MATRIX_SOLID_REACTIVE_NEXUS, + RGB_MATRIX_SPLASH, + RGB_MATRIX_SOLID_SPLASH, + RGB_MATRIX_STARLIGHT_SMOOTH, + RGB_MATRIX_STARLIGHT, + RGB_MATRIX_STARLIGHT_DUAL_SAT, + RGB_MATRIX_STARLIGHT_DUAL_HUE, + RGB_MATRIX_RIVERFLOW, + RGB_MATRIX_OPENRGB_DIRECT, + RGB_MATRIX_EFFECT_MAX +}; + +// --------------------------------------------------------------------------- +// RGB matrix API used by openrgb.c. Implementations live in the mocks cpp and +// record / serve values for the tests. +// --------------------------------------------------------------------------- +HSV rgb_matrix_get_hsv(void); +uint8_t rgb_matrix_get_mode(void); +uint8_t rgb_matrix_get_speed(void); +void rgb_matrix_mode(uint8_t mode); +void rgb_matrix_mode_noeeprom(uint8_t mode); +void rgb_matrix_set_speed(uint8_t speed); +void rgb_matrix_set_speed_noeeprom(uint8_t speed); +void rgb_matrix_sethsv(uint8_t h, uint8_t s, uint8_t v); +void rgb_matrix_sethsv_noeeprom(uint8_t h, uint8_t s, uint8_t v); + +// --------------------------------------------------------------------------- +// Raw HID transports +// --------------------------------------------------------------------------- +#define RAW_HID_SRC_USB 0 + +// Keychron USB-explicit transport (the FIXED reply path). +void kc_raw_hid_send(uint8_t src, uint8_t *data, uint8_t len); + +// Core QMK raw_hid_send (the OLD reply path -- on wireless this is dropped). +void raw_hid_send(uint8_t *data, uint8_t length); + +#ifdef __cplusplus +} +#endif diff --git a/keyboards/keychron/common/tests/mocks/quantum.h b/keyboards/keychron/common/tests/mocks/quantum.h new file mode 100644 index 000000000000..f9d8d3ecc4f8 --- /dev/null +++ b/keyboards/keychron/common/tests/mocks/quantum.h @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Shadow of quantum.h for the OpenRGB unit tests. +#pragma once +#include "openrgb_test_shim.h" diff --git a/keyboards/keychron/common/tests/mocks/raw_hid.h b/keyboards/keychron/common/tests/mocks/raw_hid.h new file mode 100644 index 000000000000..81b4de308314 --- /dev/null +++ b/keyboards/keychron/common/tests/mocks/raw_hid.h @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Shadow of raw_hid.h for the OpenRGB unit tests. +#pragma once +#include "openrgb_test_shim.h" diff --git a/keyboards/keychron/common/tests/mocks/usb_descriptor.h b/keyboards/keychron/common/tests/mocks/usb_descriptor.h new file mode 100644 index 000000000000..9b8481e4ed2a --- /dev/null +++ b/keyboards/keychron/common/tests/mocks/usb_descriptor.h @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Shadow of usb_descriptor.h for the OpenRGB unit tests. +#pragma once +#include "openrgb_test_shim.h" diff --git a/keyboards/keychron/common/tests/mocks/version.h b/keyboards/keychron/common/tests/mocks/version.h new file mode 100644 index 000000000000..96ee82497d45 --- /dev/null +++ b/keyboards/keychron/common/tests/mocks/version.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Shadow of version.h for the OpenRGB unit tests. +// +// openrgb.c reads QMK_VERSION as a char array and stringizes PRODUCT / +// MANUFACTURER via STR(). Provide a 2-level stringize helper (matching QMK's +// usual STR macro) and string-literal-able product/manufacturer tokens. +#pragma once +#include "openrgb_test_shim.h" + +#ifndef QMK_VERSION +# define QMK_VERSION "test" +#endif + +#ifndef PRODUCT +# define PRODUCT Keychron K2 HE +#endif + +#ifndef MANUFACTURER +# define MANUFACTURER Keychron +#endif + +#ifndef STR +# define STR_HELPER(x) #x +# define STR(x) STR_HELPER(x) +#endif diff --git a/keyboards/keychron/common/tests/openrgb_firmware_wiring.sh b/keyboards/keychron/common/tests/openrgb_firmware_wiring.sh new file mode 100755 index 000000000000..d5186c96a40c --- /dev/null +++ b/keyboards/keychron/common/tests/openrgb_firmware_wiring.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Build-wiring guard for the K2 HE OpenRGB feature. +# +# The gtest unit tests (openrgb_mapping.cpp) compile keyboards/keychron/common/ +# openrgb.c *directly*, so they pass even when the keyboard build never actually +# compiles openrgb.c. This guard builds the real default-keymap firmware and +# asserts the OpenRGB sources are linked in. +# +# It catches the regression where `OPENRGB_ENABLE = yes` is set in the keymap +# rules.mk but keychron_common.mk (which adds openrgb.c when OPENRGB_ENABLE=yes) +# is `include`d from the *keyboard* rules.mk -- i.e. evaluated before the keymap's +# rules.mk is read -- so the flag is seen too late and openrgb.c is silently +# dropped, making OpenRGB commands 1-9 time out on real hardware. +# +# Run from the nix dev shell: nix develop --command bash keyboards/keychron/common/tests/openrgb_firmware_wiring.sh +set -uo pipefail +cd "$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" + +qmk compile -kb keychron/k2_he/ansi -km default >/dev/null 2>&1 || true + +obj=$(find .build -path '*k2_he_ansi*' -name 'openrgb.o' 2>/dev/null | head -1) +if [ -n "$obj" ]; then + echo "PASS: openrgb.c is compiled into the K2 HE default firmware" + exit 0 +fi +echo "FAIL: openrgb.c is NOT compiled into the K2 HE default firmware --" +echo " OpenRGB commands 1-9 will be dropped (cmd reaches the dispatch default" +echo " case -> 'return false' -> no reply). Check OPENRGB_ENABLE wiring." +exit 1 diff --git a/keyboards/keychron/common/tests/openrgb_mapping.cpp b/keyboards/keychron/common/tests/openrgb_mapping.cpp new file mode 100644 index 000000000000..f9c23426910b --- /dev/null +++ b/keyboards/keychron/common/tests/openrgb_mapping.cpp @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// gtest cases proving two real bugs/fixes in the Keychron K2 HE OpenRGB code +// (keyboards/keychron/common/openrgb.c), compiled here against host mocks. +// +// BUG 1 (mode mapping): GET_ENABLED_MODES returns OpenRGB mode ids (not a raw +// 1..N list); SET_MODE translates the OpenRGB counter through +// openrgb_mode_map[] into a QMK effect; GET_MODE_INFO translates the +// active QMK effect back to its 1-based OpenRGB counter. +// +// BUG 2 (reply transport): replies go via kc_raw_hid_send(RAW_HID_SRC_USB,...) +// (USB-explicit), NOT the core raw_hid_send() which is dropped on +// wireless. +// +// Every case PASSES against the current (fixed) openrgb.c and would FAIL if the +// corresponding fix were reverted. + +#include "gtest/gtest.h" + +#include +#include + +#include "openrgb_test_recorder.h" + +// The shim (RGB/HSV types + rgb_matrix_effects enum) and the REAL openrgb.h +// (OPENRGB_MODE_* ids, openrgb_command_handler). mocks/ is first on the include +// path so openrgb.h's `#include "color.h"` resolves to the shadow. +extern "C" { +#include "openrgb_test_shim.h" +#include "openrgb.h" +} + +// RAW_EPSIZE comes in via -DRAW_EPSIZE on the command line. +static constexpr int kEpSize = RAW_EPSIZE; + +class OpenRgbMapping : public ::testing::Test { + protected: + void SetUp() override { + OpenRgbMockState::Instance().reset(); + } + + // Run GET_ENABLED_MODES and return the null-terminated id list (terminator + // excluded). Used to discover N (the Direct counter) at runtime. + static std::vector EnabledModeIds() { + OpenRgbMockState::Instance().reset(); + uint8_t data[kEpSize] = {0}; + data[0] = OPENRGB_GET_ENABLED_MODES; + EXPECT_TRUE(openrgb_command_handler(data, kEpSize)); + const auto& buf = OpenRgbMockState::Instance().kc_send_buf; + EXPECT_FALSE(buf.empty()); + std::vector ids; + for (size_t i = 1; i < buf.size(); ++i) { + if (buf[i] == 0) break; + ids.push_back(buf[i]); + } + return ids; + } +}; + +// 1. GET_ENABLED_MODES returns OpenRGB mode IDs, null-terminated -- NOT a raw +// 1..N list. The first table entry is SOLID_COLOR whose OpenRGB id is 2, so +// byte[1] must be 2 (not 1). The list must contain Direct's id (1) and end in 0. +TEST_F(OpenRgbMapping, EnabledModesAreOpenRgbIds) { + uint8_t data[kEpSize] = {0}; + data[0] = OPENRGB_GET_ENABLED_MODES; + + ASSERT_TRUE(openrgb_command_handler(data, kEpSize)); + + const auto& buf = OpenRgbMockState::Instance().kc_send_buf; + ASSERT_EQ((int)buf.size(), kEpSize); + + EXPECT_EQ(buf[0], OPENRGB_GET_ENABLED_MODES) << "reply command id"; + + // First emitted id is SOLID_COLOR's OpenRGB id (2), proving these are ids and + // not a raw 1-based counter list (old code would emit 1 here). + EXPECT_EQ(buf[1], (uint8_t)OPENRGB_MODE_SOLID_COLOR) << "first id should be the OpenRGB SOLID_COLOR id (2), not 1"; + EXPECT_NE(buf[1], 1) << "a value of 1 would mean the old raw 1..N list"; + + // The list must contain the Direct id (1) somewhere. + auto ids = EnabledModeIds(); + bool has_direct = false; + for (uint8_t id : ids) { + if (id == (uint8_t)OPENRGB_MODE_OPENRGB_DIRECT) has_direct = true; + } + EXPECT_TRUE(has_direct) << "enabled-modes list must include OPENRGB_MODE_OPENRGB_DIRECT (==1)"; + + // Null-terminated: the byte right after the last id must be 0. + ASSERT_LT(ids.size() + 1, (size_t)kEpSize); + EXPECT_EQ(buf[1 + ids.size()], 0) << "enabled-modes list must be null-terminated"; +} + +// 2. SET_MODE with counter N (= Direct, the last table slot) maps to +// RGB_MATRIX_OPENRGB_DIRECT -- NOT the raw counter N. +TEST_F(OpenRgbMapping, SetModeDirectMapsToDirectEffect) { + const auto ids = EnabledModeIds(); + const uint8_t N = (uint8_t)ids.size(); + ASSERT_GT(N, 0); + + OpenRgbMockState::Instance().reset(); + + // {cmd, h, s, v, mode-counter, speed, save} + uint8_t data[kEpSize] = {0}; + data[0] = OPENRGB_SET_MODE; + data[1] = 0; // h + data[2] = 255; // s + data[3] = 255; // v + data[4] = N; // counter N == Direct + data[5] = 127; // speed + data[6] = 0; // save=0 -> _noeeprom path + + ASSERT_TRUE(openrgb_command_handler(data, kEpSize)); + + auto& s = OpenRgbMockState::Instance(); + ASSERT_TRUE(s.mode_set) << "rgb_matrix_mode*() must have been called"; + EXPECT_TRUE(s.mode_set_via_noeeprom) << "save=0 must use the _noeeprom variant"; + EXPECT_EQ(s.last_mode, (uint8_t)RGB_MATRIX_OPENRGB_DIRECT) << "Direct counter must map to the Direct QMK effect, not the raw counter"; + EXPECT_NE(s.last_mode, N) << "must not be the raw counter N (old behavior)"; +} + +// 3. SET_MODE with a mid-list counter k whose mapped QMK effect != k goes through +// the table. Counter 15 in the table is CYCLE_OUT_IN, whose QMK enum value +// (16) differs from 15. +TEST_F(OpenRgbMapping, SetModeMidListMapsThroughTable) { + const auto ids = EnabledModeIds(); + const uint8_t k = 15; // OpenRGB counter -> table slot 15 -> CYCLE_OUT_IN + ASSERT_GE((int)ids.size(), (int)k); + + // Sanity: the chosen slot's QMK effect must actually differ from the counter, + // otherwise this test can't distinguish the fix from identity behavior. + ASSERT_NE((uint8_t)RGB_MATRIX_CYCLE_OUT_IN, k) << "test precondition: mapped effect must differ from counter"; + + OpenRgbMockState::Instance().reset(); + + uint8_t data[kEpSize] = {0}; + data[0] = OPENRGB_SET_MODE; + data[1] = 0; + data[2] = 255; + data[3] = 255; + data[4] = k; + data[5] = 127; + data[6] = 0; + + ASSERT_TRUE(openrgb_command_handler(data, kEpSize)); + + auto& s = OpenRgbMockState::Instance(); + ASSERT_TRUE(s.mode_set); + EXPECT_EQ(s.last_mode, (uint8_t)RGB_MATRIX_CYCLE_OUT_IN) << "counter 15 must map through the table to CYCLE_OUT_IN"; + EXPECT_NE(s.last_mode, k) << "must not be the raw counter k (old behavior)"; +} + +// 4. GET_MODE_INFO translates the active QMK effect back to its 1-based OpenRGB +// counter. With Direct active, byte[1] must be N (Direct's counter), not the +// raw enum value. +TEST_F(OpenRgbMapping, GetModeInfoReportsOpenRgbCounter) { + const auto ids = EnabledModeIds(); + const uint8_t N = (uint8_t)ids.size(); + ASSERT_GT(N, 0); + + OpenRgbMockState::Instance().reset(); + OpenRgbMockState::Instance().get_mode_value = (uint8_t)RGB_MATRIX_OPENRGB_DIRECT; + + uint8_t data[kEpSize] = {0}; + data[0] = OPENRGB_GET_MODE_INFO; + + ASSERT_TRUE(openrgb_command_handler(data, kEpSize)); + + const auto& buf = OpenRgbMockState::Instance().kc_send_buf; + ASSERT_EQ((int)buf.size(), kEpSize); + EXPECT_EQ(buf[0], OPENRGB_GET_MODE_INFO); + EXPECT_EQ(buf[1], N) << "GET_MODE_INFO must report Direct's 1-based OpenRGB counter (N)"; + EXPECT_NE(buf[1], (uint8_t)RGB_MATRIX_OPENRGB_DIRECT) << "must not report the raw QMK enum value"; +} + +// 5. Replies are delivered via kc_raw_hid_send(RAW_HID_SRC_USB,...), NOT the core +// raw_hid_send() (which our mock drops, modeling the wireless NULL driver). +TEST_F(OpenRgbMapping, ReplyGoesOverUsbNotCoreRawHid) { + uint8_t data[kEpSize] = {0}; + data[0] = OPENRGB_GET_PROTOCOL_VERSION; + + ASSERT_TRUE(openrgb_command_handler(data, kEpSize)); + + auto& s = OpenRgbMockState::Instance(); + EXPECT_TRUE(s.kc_send_called) << "reply must be delivered via kc_raw_hid_send"; + EXPECT_EQ(s.kc_send_src, (uint8_t)RAW_HID_SRC_USB) << "reply must be sent with src == RAW_HID_SRC_USB"; + ASSERT_EQ((int)s.kc_send_buf.size(), kEpSize); + EXPECT_EQ(s.kc_send_buf[0], OPENRGB_GET_PROTOCOL_VERSION) << "USB-delivered reply must carry the protocol-version reply"; + + // The reply must NOT have been carried (and dropped) by the core transport. + EXPECT_FALSE(s.core_send_called) << "reply must not be routed through core raw_hid_send (dropped on wireless)"; +} + +// 6. Full OpenRGB discovery handshake -- the exact command sequence OpenRGB +// issues on detection. Each command must reply over USB (kc_raw_hid_send with +// src == RAW_HID_SRC_USB) with sane data: i.e. OpenRGB can see + enumerate the +// keyboard at the protocol level. This is the unit-level equivalent of +// "OpenRGB sees the keyboard" -- it exercises the compiled openrgb.c against +// mocks and never touches real hardware. +TEST_F(OpenRgbMapping, ProtocolDiscoveryHandshake) { + auto& s = OpenRgbMockState::Instance(); + + auto run = [&](uint8_t cmd) -> std::vector { + s.reset(); + uint8_t data[kEpSize] = {0}; + data[0] = cmd; + if (cmd == OPENRGB_GET_LED_INFO) { // GET_LED_INFO needs (first_led, count) + data[1] = 0; + data[2] = 8; + } + EXPECT_TRUE(openrgb_command_handler(data, kEpSize)) << "handler rejected cmd " << (int)cmd; + EXPECT_TRUE(s.kc_send_called) << "cmd " << (int)cmd << " did not reply over USB"; + EXPECT_EQ(s.kc_send_src, (uint8_t)RAW_HID_SRC_USB) << "cmd " << (int)cmd << " reply not USB-src"; + EXPECT_FALSE(s.core_send_called) << "cmd " << (int)cmd << " routed via core raw_hid_send (would drop on wireless)"; + EXPECT_EQ((int)s.kc_send_buf.size(), kEpSize); + return s.kc_send_buf; + }; + + // 1) GET_PROTOCOL_VERSION -> protocol version (RevD = 0x0D) in byte[1]. + auto pv = run(OPENRGB_GET_PROTOCOL_VERSION); + EXPECT_EQ(pv[0], OPENRGB_GET_PROTOCOL_VERSION); + EXPECT_EQ(pv[1], (uint8_t)OPENRGB_PROTOCOL_VERSION) << "discovery: protocol version must be RevD (0x0D)"; + + // 2) GET_QMK_VERSION -> non-empty version string from byte[1]. + auto qv = run(OPENRGB_GET_QMK_VERSION); + EXPECT_EQ(qv[0], OPENRGB_GET_QMK_VERSION); + EXPECT_NE(qv[1], 0) << "discovery: QMK version string must be present"; + + // 3) GET_DEVICE_INFO -> LED count, matrix cells, device name. + auto di = run(OPENRGB_GET_DEVICE_INFO); + EXPECT_EQ(di[0], OPENRGB_GET_DEVICE_INFO); + EXPECT_EQ(di[1], (uint8_t)RGB_MATRIX_LED_COUNT) << "discovery: device-info LED count"; + EXPECT_GT(di[2], 0) << "discovery: device-info matrix-cell count"; + EXPECT_NE(di[3], 0) << "discovery: device name must be present"; + + // 4) GET_ENABLED_MODES -> at least one enabled mode. + auto em = run(OPENRGB_GET_ENABLED_MODES); + EXPECT_EQ(em[0], OPENRGB_GET_ENABLED_MODES); + EXPECT_NE(em[1], 0) << "discovery: at least one enabled mode"; + + // 5) GET_MODE_INFO -> replies with the active-mode info block. + auto mi = run(OPENRGB_GET_MODE_INFO); + EXPECT_EQ(mi[0], OPENRGB_GET_MODE_INFO); + + // 6) GET_LED_INFO -> replies with the per-LED layout block. + auto li = run(OPENRGB_GET_LED_INFO); + EXPECT_EQ(li[0], OPENRGB_GET_LED_INFO); +} diff --git a/keyboards/keychron/common/tests/openrgb_test_mocks.cpp b/keyboards/keychron/common/tests/openrgb_test_mocks.cpp new file mode 100644 index 000000000000..3ce8d762aa7d --- /dev/null +++ b/keyboards/keychron/common/tests/openrgb_test_mocks.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// C-linkage mock implementations for the symbols openrgb.c references, plus the +// global g_led_config it indexes. All recording goes through OpenRgbMockState. + +#include +#include + +#include "openrgb_test_recorder.h" + +// Pull in the same shim openrgb.c sees (for the RGB/HSV/led_config_t types and the +// C function prototypes), so our definitions match the declarations exactly. +extern "C" { +#include "openrgb_test_shim.h" +} + +// The global LED config openrgb.c reads (g_led_config.point/.flags/.matrix_co). +extern "C" { +led_config_t g_led_config = {}; +} + +extern "C" uint16_t keycode_at_keymap_location(uint8_t layer, uint8_t row, uint8_t col) { + (void)layer; + (void)row; + (void)col; + return 0; +} + +// ---- rgb_matrix getters: serve what the test preloaded ---- +extern "C" HSV rgb_matrix_get_hsv(void) { + auto& s = OpenRgbMockState::Instance(); + HSV v; + v.h = s.get_hsv_h; + v.s = s.get_hsv_s; + v.v = s.get_hsv_v; + return v; +} + +extern "C" uint8_t rgb_matrix_get_mode(void) { + return OpenRgbMockState::Instance().get_mode_value; +} + +extern "C" uint8_t rgb_matrix_get_speed(void) { + return OpenRgbMockState::Instance().get_speed_value; +} + +// ---- rgb_matrix setters: record what openrgb.c chose ---- +extern "C" void rgb_matrix_mode(uint8_t mode) { + auto& s = OpenRgbMockState::Instance(); + s.mode_set = true; + s.last_mode = mode; + s.mode_set_via_eeprom = true; +} + +extern "C" void rgb_matrix_mode_noeeprom(uint8_t mode) { + auto& s = OpenRgbMockState::Instance(); + s.mode_set = true; + s.last_mode = mode; + s.mode_set_via_noeeprom = true; +} + +extern "C" void rgb_matrix_set_speed(uint8_t speed) { + (void)speed; +} + +extern "C" void rgb_matrix_set_speed_noeeprom(uint8_t speed) { + (void)speed; +} + +extern "C" void rgb_matrix_sethsv(uint8_t h, uint8_t s, uint8_t v) { + (void)h; + (void)s; + (void)v; +} + +extern "C" void rgb_matrix_sethsv_noeeprom(uint8_t h, uint8_t s, uint8_t v) { + (void)h; + (void)s; + (void)v; +} + +// ---- transports ---- + +// The FIXED reply path: explicit USB send. +extern "C" void kc_raw_hid_send(uint8_t src, uint8_t *data, uint8_t len) { + auto& s = OpenRgbMockState::Instance(); + s.kc_send_called = true; + s.kc_send_src = src; + s.kc_send_buf.assign(data, data + len); +} + +// The OLD reply path: core raw_hid_send. On real hardware in wireless mode this is +// dropped (active host driver's send_raw_hid is NULL). We model the drop by simply +// recording that it was the carrier and NOT delivering to the USB capture -- so a +// reverted openrgb.c (which would call this) fails the "delivered over USB" test. +extern "C" void raw_hid_send(uint8_t *data, uint8_t length) { + auto& s = OpenRgbMockState::Instance(); + s.core_send_called = true; + s.core_send_buf.assign(data, data + length); + // DROPPED: nothing is forwarded to kc_send_buf. +} diff --git a/keyboards/keychron/common/tests/openrgb_test_recorder.h b/keyboards/keychron/common/tests/openrgb_test_recorder.h new file mode 100644 index 000000000000..fbf6abfde3a3 --- /dev/null +++ b/keyboards/keychron/common/tests/openrgb_test_recorder.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Recording state shared between the OpenRGB mock implementations +// (openrgb_test_mocks.cpp) and the gtest cases (openrgb_mapping.cpp). +#pragma once + +#include +#include + +// Singleton holding everything the mocks record and everything the tests preload. +struct OpenRgbMockState { + // ---- values the test SETS, served back to openrgb.c ---- + uint8_t get_mode_value = 0; // returned by rgb_matrix_get_mode() + uint8_t get_speed_value = 0; // returned by rgb_matrix_get_speed() + uint8_t get_hsv_h = 0; + uint8_t get_hsv_s = 0; + uint8_t get_hsv_v = 0; + + // ---- values the mocks RECORD ---- + bool mode_set = false; // any rgb_matrix_mode*() called? + uint8_t last_mode = 0; // arg to last rgb_matrix_mode*() + bool mode_set_via_noeeprom = false; // true if last setter was the _noeeprom variant + bool mode_set_via_eeprom = false; // true if last setter was the eeprom variant + + // kc_raw_hid_send (the FIXED USB transport) + bool kc_send_called = false; + uint8_t kc_send_src = 0xFF; + std::vector kc_send_buf; + + // core raw_hid_send (the OLD transport -- on wireless this drops the reply) + bool core_send_called = false; + std::vector core_send_buf; + + void reset() { *this = OpenRgbMockState{}; } + + static OpenRgbMockState& Instance() { + static OpenRgbMockState inst; + return inst; + } +}; diff --git a/keyboards/keychron/common/tests/rules.mk b/keyboards/keychron/common/tests/rules.mk new file mode 100644 index 000000000000..590a65307ac9 --- /dev/null +++ b/keyboards/keychron/common/tests/rules.mk @@ -0,0 +1,56 @@ +KEYCHRON_OPENRGB_PATH := keyboards/keychron/common +KEYCHRON_OPENRGB_TESTS := $(KEYCHRON_OPENRGB_PATH)/tests + +openrgb_keychron_DEFS := \ + -DRAW_EPSIZE=64 \ + -DRGB_MATRIX_LED_COUNT=84 \ + -DMATRIX_ROWS=6 \ + -DMATRIX_COLS=16 \ + -DVENDOR_ID=0x3434 \ + -DOPENRGB_ENABLE \ + -DENABLE_RGB_MATRIX_ALPHAS_MODS \ + -DENABLE_RGB_MATRIX_GRADIENT_UP_DOWN \ + -DENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT \ + -DENABLE_RGB_MATRIX_BREATHING \ + -DENABLE_RGB_MATRIX_BAND_SAT \ + -DENABLE_RGB_MATRIX_BAND_VAL \ + -DENABLE_RGB_MATRIX_BAND_PINWHEEL_SAT \ + -DENABLE_RGB_MATRIX_BAND_PINWHEEL_VAL \ + -DENABLE_RGB_MATRIX_BAND_SPIRAL_SAT \ + -DENABLE_RGB_MATRIX_BAND_SPIRAL_VAL \ + -DENABLE_RGB_MATRIX_CYCLE_ALL \ + -DENABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT \ + -DENABLE_RGB_MATRIX_CYCLE_UP_DOWN \ + -DENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON \ + -DENABLE_RGB_MATRIX_CYCLE_OUT_IN \ + -DENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL \ + -DENABLE_RGB_MATRIX_CYCLE_PINWHEEL \ + -DENABLE_RGB_MATRIX_CYCLE_SPIRAL \ + -DENABLE_RGB_MATRIX_DUAL_BEACON \ + -DENABLE_RGB_MATRIX_RAINBOW_BEACON \ + -DENABLE_RGB_MATRIX_RAINBOW_PINWHEELS \ + -DENABLE_RGB_MATRIX_RAINDROPS \ + -DENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS \ + -DENABLE_RGB_MATRIX_HUE_BREATHING \ + -DENABLE_RGB_MATRIX_HUE_PENDULUM \ + -DENABLE_RGB_MATRIX_HUE_WAVE \ + -DENABLE_RGB_MATRIX_TYPING_HEATMAP \ + -DENABLE_RGB_MATRIX_DIGITAL_RAIN \ + -DENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE \ + -DENABLE_RGB_MATRIX_SOLID_REACTIVE \ + -DENABLE_RGB_MATRIX_SPLASH \ + -DENABLE_RGB_MATRIX_SOLID_SPLASH + +openrgb_keychron_SRC := \ + $(KEYCHRON_OPENRGB_PATH)/openrgb.c \ + $(KEYCHRON_OPENRGB_TESTS)/openrgb_test_mocks.cpp \ + $(KEYCHRON_OPENRGB_TESTS)/openrgb_mapping.cpp + +# mocks/ must come FIRST so the shadow headers (quantum.h, color.h, raw_hid.h, +# usb_descriptor.h, version.h, keymap_introspection.h, keychron_raw_hid.h) shadow +# the real QMK headers; the second entry exposes the real openrgb.h under test and +# the recorder header. +openrgb_keychron_INC := \ + $(KEYCHRON_OPENRGB_TESTS)/mocks \ + $(KEYCHRON_OPENRGB_TESTS) \ + $(KEYCHRON_OPENRGB_PATH) diff --git a/keyboards/keychron/common/tests/testlist.mk b/keyboards/keychron/common/tests/testlist.mk new file mode 100644 index 000000000000..e6519f98b1d3 --- /dev/null +++ b/keyboards/keychron/common/tests/testlist.mk @@ -0,0 +1 @@ +TEST_LIST += openrgb_keychron diff --git a/keyboards/keychron/k2_he/ansi/keymaps/default/config.h b/keyboards/keychron/k2_he/ansi/keymaps/default/config.h new file mode 100644 index 000000000000..ddec96092638 --- /dev/null +++ b/keyboards/keychron/k2_he/ansi/keymaps/default/config.h @@ -0,0 +1,20 @@ +/* Copyright 2025 @ Keychron (https://www.keychron.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +/* OpenRGB uses 64-byte raw HID reports */ +#define RAW_EPSIZE 64 diff --git a/keyboards/keychron/k2_he/ansi/keymaps/default/keymap.c b/keyboards/keychron/k2_he/ansi/keymaps/default/keymap.c index 8c41ef7daca0..41ca00dfa038 100644 --- a/keyboards/keychron/k2_he/ansi/keymaps/default/keymap.c +++ b/keyboards/keychron/k2_he/ansi/keymaps/default/keymap.c @@ -61,3 +61,9 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { _______, _______, _______, _______, _______, BAT_LVL, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______) }; + +#ifdef OPENRGB_ENABLE +void keyboard_post_init_user(void) { + rgb_matrix_mode_noeeprom(RGB_MATRIX_OPENRGB_DIRECT); +} +#endif diff --git a/keyboards/keychron/k2_he/ansi/keymaps/default/rules.mk b/keyboards/keychron/k2_he/ansi/keymaps/default/rules.mk new file mode 100644 index 000000000000..6a587130e456 --- /dev/null +++ b/keyboards/keychron/k2_he/ansi/keymaps/default/rules.mk @@ -0,0 +1,12 @@ +# OpenRGB for the K2 HE default keymap. +# +# keychron_common.mk (which turns OPENRGB_ENABLE into the openrgb.c source + +# -DOPENRGB_ENABLE) is `include`d from the *keyboard* rules.mk, i.e. evaluated +# before this keymap rules.mk is read. A plain `OPENRGB_ENABLE = yes` here is +# therefore seen too late and the sources are silently dropped (OpenRGB commands +# 1-9 then time out on hardware). Wire it directly instead: SRC/OPT_DEFS +# accumulate regardless of include order, and this keeps OpenRGB scoped to this +# keymap (the `keychron` keymap keeps VIA). See tests/openrgb_firmware_wiring.sh. +RAW_ENABLE = yes +OPT_DEFS += -DOPENRGB_ENABLE +SRC += $(TOP_DIR)/keyboards/keychron/common/openrgb.c diff --git a/quantum/rgb_matrix/animations/openrgb_direct_anim.h b/quantum/rgb_matrix/animations/openrgb_direct_anim.h new file mode 100644 index 000000000000..40c6be0e38b0 --- /dev/null +++ b/quantum/rgb_matrix/animations/openrgb_direct_anim.h @@ -0,0 +1,21 @@ +#ifdef OPENRGB_ENABLE +RGB_MATRIX_EFFECT(OPENRGB_DIRECT) +# ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS + +extern RGB g_openrgb_direct_mode_colors[]; + +bool OPENRGB_DIRECT(effect_params_t *params) { + RGB_MATRIX_USE_LIMITS(led_min, led_max); + + for (uint8_t i = led_min; i < led_max; i++) { +# ifdef OPENRGB_DIRECT_MODE_USE_UNIVERSAL_BRIGHTNESS + const uint8_t v = rgb_matrix_config.hsv.v; + rgb_matrix_set_color(i, (uint8_t)((uint16_t)g_openrgb_direct_mode_colors[i].r * v / UINT8_MAX), (uint8_t)((uint16_t)g_openrgb_direct_mode_colors[i].g * v / UINT8_MAX), (uint8_t)((uint16_t)g_openrgb_direct_mode_colors[i].b * v / UINT8_MAX)); +# else + rgb_matrix_set_color(i, g_openrgb_direct_mode_colors[i].r, g_openrgb_direct_mode_colors[i].g, g_openrgb_direct_mode_colors[i].b); +# endif + } + return rgb_matrix_check_finished_leds(led_max); +} +# endif +#endif diff --git a/quantum/rgb_matrix/animations/rgb_matrix_effects.inc b/quantum/rgb_matrix/animations/rgb_matrix_effects.inc index abc2adf2cc7f..2a771b4599c2 100644 --- a/quantum/rgb_matrix/animations/rgb_matrix_effects.inc +++ b/quantum/rgb_matrix/animations/rgb_matrix_effects.inc @@ -44,3 +44,4 @@ #include "starlight_dual_sat_anim.h" #include "starlight_dual_hue_anim.h" #include "riverflow_anim.h" +#include "openrgb_direct_anim.h" diff --git a/tmk_core/protocol/usb_descriptor.h b/tmk_core/protocol/usb_descriptor.h index 260f038da0ae..583e9dd37c08 100644 --- a/tmk_core/protocol/usb_descriptor.h +++ b/tmk_core/protocol/usb_descriptor.h @@ -53,7 +53,7 @@ #endif #ifndef XINPUT_HID_DESCRIPTOR_LEN -#define XINPUT_HID_DESCRIPTOR_LEN 0x11 +# define XINPUT_HID_DESCRIPTOR_LEN 0x11 #endif /* @@ -224,11 +224,11 @@ enum usb_endpoints { __unused_epnum__ = NEXT_EPNUM, // Endpoint numbering starts at 1 #ifdef XINPUT_ENABLE - XINPUT_IN_EPNUM = NEXT_EPNUM, + XINPUT_IN_EPNUM = NEXT_EPNUM, # ifdef USB_ENDPOINTS_ARE_REORDERABLE # define XINPUT_OUT_EPNUM XINPUT_IN_EPNUM # else - #error("xinput require same endpoint for in and out") +# error("xinput require same endpoint for in and out") # endif #endif @@ -249,7 +249,7 @@ enum usb_endpoints { # ifdef USB_ENDPOINTS_ARE_REORDERABLE # define RAW_OUT_EPNUM RAW_IN_EPNUM # else - RAW_OUT_EPNUM = NEXT_EPNUM, + RAW_OUT_EPNUM = NEXT_EPNUM, # endif #endif @@ -276,7 +276,7 @@ enum usb_endpoints { # ifdef USB_ENDPOINTS_ARE_REORDERABLE # define CDC_OUT_EPNUM CDC_IN_EPNUM # else - CDC_OUT_EPNUM = NEXT_EPNUM, + CDC_OUT_EPNUM = NEXT_EPNUM, # endif #endif @@ -312,7 +312,9 @@ enum usb_endpoints { #define KEYBOARD_EPSIZE 8 #define SHARED_EPSIZE 32 #define MOUSE_EPSIZE 16 -#define RAW_EPSIZE 32 +#ifndef RAW_EPSIZE +# define RAW_EPSIZE 32 +#endif #define CONSOLE_EPSIZE 32 #define MIDI_STREAM_EPSIZE 64 #define CDC_NOTIFICATION_EPSIZE 8 @@ -333,6 +335,5 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const uint16_t get_usb_vendor_descriptor(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress); #ifdef XINPUT_ENABLE uint16_t get_usb_vendor_descriptor(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress); -void get_usb_vendor_descriptor_kb(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress, uint16_t *size); +void get_usb_vendor_descriptor_kb(uint8_t recipient, uint8_t reqeuest, const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress, uint16_t* size); #endif - From 57efe8f3fb76e855d8de33bdb1f28e825da9957f Mon Sep 17 00:00:00 2001 From: Ido Samuelson Date: Wed, 10 Jun 2026 18:03:05 -0600 Subject: [PATCH 2/3] chore: prune dangling keyboard aliases for ci-validate-aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `qmk ci-validate-aliases` fails on the 2025q3 base: data/mappings/keyboard_aliases.hjson carries ~783 aliases whose target keyboards were removed from this fork, so every PR whose lint workflow actually runs fails on them. Remove the dangling aliases so the check passes. This is unrelated to the OpenRGB feature and is isolated in its own commit — happy to split it into a dedicated PR. --- data/mappings/keyboard_aliases.hjson | 2352 +------------------------- flake.lock | 27 + flake.nix | 49 + 3 files changed, 79 insertions(+), 2349 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/data/mappings/keyboard_aliases.hjson b/data/mappings/keyboard_aliases.hjson index 3978235e20c9..9ff3af3af5c5 100644 --- a/data/mappings/keyboard_aliases.hjson +++ b/data/mappings/keyboard_aliases.hjson @@ -6,1062 +6,9 @@ // /* This list of aliases is for testing purposes -- ensures "linked list" recursive traversal works correctly. */ - "_test_a": { "target": "_test_b" }, - "_test_b": { "target": "_test_c" }, - "_test_c": { "target": "planck/rev6" }, /* The main list of aliases for moved keyboards within QMK. */ - "2_milk": { - "target": "spaceman/2_milk" - }, - "absinthe": { - "target": "keyhive/absinthe" - }, - "aeboards/constellation": { - "target": "aeboards/constellation/rev1" - }, - "aeboards/ext65": { - "target": "aeboards/ext65/rev1" - }, - "ai03/equinox": { - "target": "ai03/equinox/rev1" - }, - "alice": { - "target": "tgr/alice" - }, - "amj40": { - "target": "amjkeyboard/amj40" - }, - "amj60": { - "target": "amjkeyboard/amj60" - }, - "amj96": { - "target": "amjkeyboard/amj96" - }, - "amjpad": { - "target": "amjkeyboard/amjpad" - }, - "angel64": { - "target": "kakunpc/angel64/alpha" - }, - "ashpil/modelm_usbc": { - "target": "ibm/model_m/ashpil_usbc" - }, - "at101_blackheart": { - "target": "viktus/at101_bh" - }, - "at101_bh": { - "target": "viktus/at101_bh" - }, - "atom47/rev2": { - "target": "evyd13/atom47/rev2" - }, - "atom47/rev3": { - "target": "evyd13/atom47/rev3" - }, - "bakeneko60": { - "target": "kkatano/bakeneko60" - }, - "bakeneko65": { - "target": "kkatano/bakeneko65/rev2" - }, - "bakeneko80": { - "target": "kkatano/bakeneko80" - }, - "bastardkb/charybdis/3x5/v2/elitec": { - "target": "bastardkb/charybdis/3x5/elitec" - }, - "bastardkb/charybdis/3x5/v2/splinky_2": { - "target": "bastardkb/charybdis/3x5/elitec" - }, - "bastardkb/charybdis/3x5/v2/splinky_3": { - "target": "bastardkb/charybdis/3x5/elitec" - }, - "bastardkb/charybdis/3x5/v2/stemcell": { - "target": "bastardkb/charybdis/3x5/elitec" - }, - "bastardkb/charybdis/3x6/v2/elitec": { - "target": "bastardkb/charybdis/3x6/elitec" - }, - "bastardkb/charybdis/3x6/v2/splinky_2": { - "target": "bastardkb/charybdis/3x6/elitec" - }, - "bastardkb/charybdis/3x6/v2/splinky_3": { - "target": "bastardkb/charybdis/3x6/elitec" - }, - "bastardkb/charybdis/3x6/v2/stemcell": { - "target": "bastardkb/charybdis/3x6/elitec" - }, - "bastardkb/charybdis/4x6/v2/elitec": { - "target": "bastardkb/charybdis/4x6/elitec" - }, - "bastardkb/charybdis/4x6/v2/splinky_2": { - "target": "bastardkb/charybdis/4x6/elitec" - }, - "bastardkb/charybdis/4x6/v2/splinky_3": { - "target": "bastardkb/charybdis/4x6/elitec" - }, - "bastardkb/charybdis/4x6/v2/stemcell": { - "target": "bastardkb/charybdis/4x6/elitec" - }, - "bastardkb/dilemma/3x5_2/splinky": { - "target": "bastardkb/dilemma/3x5_2/promicro" - }, - "bastardkb/scylla/v2/elitec": { - "target": "bastardkb/scylla/promicro" - }, - "bastardkb/scylla/v2/splinky_2": { - "target": "bastardkb/scylla/promicro" - }, - "bastardkb/scylla/v2/splinky_3": { - "target": "bastardkb/scylla/promicro" - }, - "bastardkb/scylla/v2/stemcell": { - "target": "bastardkb/scylla/promicro" - }, - "bastardkb/skeletyl/v2/elitec": { - "target": "bastardkb/skeletyl/promicro" - }, - "bastardkb/skeletyl/v2/splinky_2": { - "target": "bastardkb/skeletyl/promicro" - }, - "bastardkb/skeletyl/v2/splinky_3": { - "target": "bastardkb/skeletyl/promicro" - }, - "bastardkb/skeletyl/v2/stemcell": { - "target": "bastardkb/skeletyl/promicro" - }, - "bastardkb/tbkmini/v2/elitec": { - "target": "bastardkb/tbkmini/promicro" - }, - "bastardkb/tbkmini/v2/splinky_2": { - "target": "bastardkb/tbkmini/promicro" - }, - "bastardkb/tbkmini/v2/splinky_3": { - "target": "bastardkb/tbkmini/promicro" - }, - "bastardkb/tbkmini/v2/stemcell": { - "target": "bastardkb/tbkmini/promicro" - }, - "bear_face": { - "target": "bear_face/v1" - }, - "bm16a": { - "target": "kprepublic/bm16a/v1" - }, - "bm16s": { - "target": "kprepublic/bm16s" - }, - "bm40hsrgb": { - "target": "kprepublic/bm40hsrgb" - }, - "bm43a": { - "target": "kprepublic/bm43a" - }, - "bm60poker": { - "target": "kprepublic/bm60hsrgb_poker/rev1" - }, - "bm60rgb": { - "target": "kprepublic/bm60hsrgb/rev1" - }, - "bm60rgb_iso": { - "target": "kprepublic/bm60hsrgb_iso/rev1" - }, - "bm68rgb": { - "target": "kprepublic/bm68hsrgb/rev1" - }, - "bpiphany/pegasushoof": { - "target": "bpiphany/pegasushoof/2013" - }, - "brick": { - "target": "pauperboards/brick" - }, - "chavdai40": { - "target": "chavdai40/rev1" - }, - "candybar/lefty": { - "target": "tkc/candybar/lefty" - }, - "candybar/righty": { - "target": "tkc/candybar/righty" - }, - "canoe": { - "target": "percent/canoe" - }, - "clawsome/gamebuddy": { - "target": "clawsome/gamebuddy/v1_0" - }, - "cmm_studio/saka68": { - "target": "cmm_studio/saka68/solder" - }, - "converter/modelm101": { - "target": "ibm/model_m/teensypp" - }, - "converter/modelm101_teensy2": { - "target": "ibm/model_m/teensy2" - }, - "converter/modelm_ssk": { - "target": "ibm/model_m_ssk/teensypp_ssk" - }, - "cospad": { - "target": "kprepublic/cospad" - }, - "crkbd/rev1/legacy": { - "target": "crkbd/rev1" - }, - "crkbd/rev1/common": { - "target": "crkbd/rev1" - }, - "custommk/genesis": { - "target": "custommk/genesis/rev1" - }, - "cxt_studio":{ - "target":"cxt_studio/12e4" - }, - "daisy": { - "target": "ktec/daisy" - }, - "deemen17/de60": { - "target": "deemen17/de60/r1" - }, - "dp3000": { - "target": "dp3000/rev1" - }, - "drakon": { - "target": "jagdpietr/drakon" - }, - "durgod/k320": { - "target": "durgod/k320/base" - }, - "durgod/k3x0/k320": { - "target": "durgod/k320/base" - }, - "durgod/hades": { - "target": "durgod/dgk6x/hades_ansi" - }, - "durgod/hades_ansi": { - "target": "durgod/dgk6x/hades_ansi" - }, - "durgod/hades_iso": { - "target": "durgod/dgk6x/hades_iso" - }, - "dztech/dz60rgb": { - "target": "dztech/dz60rgb/v1" - }, - "dztech/dz60rgb_ansi": { - "target": "dztech/dz60rgb_ansi/v1" - }, - "dztech/dz60rgb_wkl": { - "target": "dztech/dz60rgb_wkl/v1" - }, - "dztech/dz65rgb": { - "target": "dztech/dz65rgb/v1" - }, - "dztech/volcano660": { - "target": "ilumkb/volcano660" - }, - "dztech/og60": { - "target": "dztech/tofu60" - }, - "eek": { - "target": "eek/silk_down" - }, - "epoch80": { - "target": "kbdfans/epoch80" - }, - "era/klein": { - "target": "era/sirind/klein_sd" - }, - "ergodone": { - "target": "ktec/ergodone" - }, - "ergodox_stm32": { - "target": "handwired/ergodox_stm32" - }, - "ergoinu": { - "target": "dm9records/ergoinu" - }, - "ergosaurus": { - "target": "keyhive/ergosaurus" - }, - "exclusive/e85": { - "target": "exclusive/e85/hotswap" - }, - "gh60": { - "target": "gh60/revc" - }, - "gmmk/pro": { - "target": "gmmk/pro/rev1/ansi" - }, - "gmmk/pro/ansi": { - "target": "gmmk/pro/rev1/ansi" - }, - "gmmk/pro/iso": { - "target": "gmmk/pro/rev1/iso" - }, - "handwired/dactyl_manuform/3x5_3": { - "target": "handwired/dactyl_minidox" - }, - "handwired/dactyl_manuform/6x6_kinesis": { - "target": "handwired/dactyl_kinesis" - }, - "handwired/dactyl_manuform/dmote/62key": { - "target": "handwired/dmote" - }, - "handwired/ferris": { - "target": "ferris/0_1" - }, - "handwired/ibm122m": { - "target": "ibm/model_m_122/ibm122m" - }, - "handwired/p1800fl": { - "target": "team0110/p1800fl" - }, - "handwired/jscotto/scotto9": { - "target": "handwired/scottokeebs/scotto9" - }, - "handwired/jscotto/scotto36": { - "target": "handwired/scottokeebs/scotto36" - }, - "handwired/jscotto/scotto40": { - "target": "handwired/scottokeebs/scotto40" - }, - "handwired/jscotto/scottocmd": { - "target": "handwired/scottokeebs/scottocmd" - }, - "handwired/jscotto/scottostarter": { - "target": "handwired/scottokeebs/scottostarter" - }, - "helix": { - "target": "helix/beta" - }, - "helix/rev2": { - "target": "helix/beta" - }, - "honeycomb": { - "target": "keyhive/honeycomb" - }, - "hub16": { - "target": "joshajohnson/hub16" - }, - "hub20": { - "target": "joshajohnson/hub20" - }, - "idb_60": { - "target": "idb/idb_60" - }, - "idobo": { - "target": "idobao/id75/v1" - }, - "jacky_studio/piggy60": { - "target": "jacky_studio/piggy60/rev1" - }, - "jj40": { - "target": "kprepublic/jj40/rev1" - }, - "jj4x4": { - "target": "kprepublic/jj4x4" - }, - "jj50": { - "target": "kprepublic/jj50/rev1" - }, - "jm60": { - "target": "kbdfans/jm60" - }, - "jones": { - "target": "jones/v03_1" - }, - "kamigakushi": { - "target": "jaykeeb/kamigakushi" - }, - "katana60": { - "target": "rominronin/katana60/rev1" - }, - "kbdfans/kbd67mkiirgb": { - "target": "kbdfans/kbd67/mkiirgb" - }, - "kbdfans/kbd67/mkiirgb": { - "target": "kbdfans/kbd67/mkiirgb/v1" - }, - "keebio/chocopad": { - "target": "keebio/chocopad/rev1" - }, - "keebio/dsp40": { - "target": "keebio/dsp40/rev1" - }, - "keycapsss/plaid_pad": { - "target": "keycapsss/plaid_pad/rev1" - }, - "keyten/kt60hs_t": { - "target": "keyten/kt60hs_t/v1" - }, - "kira75": { - "target": "kira/kira75" - }, - "kira80": { - "target": "kira/kira80" - }, - "kudox": { - "target": "kumaokobo/kudox/rev1" - }, - "kudox/columner": { - "target": "kumaokobo/kudox/columner" - }, - "kudox/rev1": { - "target": "kumaokobo/kudox/rev1" - }, - "kudox/rev2": { - "target": "kumaokobo/kudox/rev2" - }, - "kudox/rev3": { - "target": "kumaokobo/kudox/rev3" - }, - "kudox_full": { - "target": "kumaokobo/kudox_full/rev1" - }, - "kudox_full/rev1": { - "target": "kumaokobo/kudox_full/rev1" - }, - "kudox_game": { - "target": "kumaokobo/kudox_game/rev1" - }, - "kudox_game/rev1": { - "target": "kumaokobo/kudox_game/rev1" - }, - "kudox_game/rev2": { - "target": "kumaokobo/kudox_game/rev2" - }, - "kyria": { - "target": "splitkb/kyria" - }, - "laser_ninja/pumpkin_pad": { - "target": "laser_ninja/pumpkinpad" - }, - "lattice60": { - "target": "keyhive/lattice60" - }, - "lazydesigners/the60": { - "target": "lazydesigners/the60/rev1" - }, - "lfkeyboards/lfk78": { - "target": "lfkeyboards/lfk78/revj" - }, - "lfkeyboards/smk65": { - "target": "lfkeyboards/smk65/revb" - }, - "ll3macorn/bongopad": { - "target": "ll3ma/bongopad" - }, - "m3v3van": { - "target": "matthewdias/m3n3van" - }, - "maartenwut/atom47/rev2": { - "target": "evyd13/atom47/rev2" - }, - "maartenwut/atom47/rev3": { - "target": "evyd13/atom47/rev3" - }, - "maartenwut/eon40": { - "target": "evyd13/eon40" - }, - "maartenwut/eon65": { - "target": "evyd13/eon65" - }, - "maartenwut/eon75": { - "target": "evyd13/eon75" - }, - "maartenwut/eon87": { - "target": "evyd13/eon87" - }, - "maartenwut/eon95": { - "target": "evyd13/eon95" - }, - "maartenwut/gh80_1800": { - "target": "evyd13/gh80_1800" - }, - "maartenwut/gh80_3700": { - "target": "evyd13/gh80_3700" - }, - "maartenwut/minitomic": { - "target": "evyd13/minitomic" - }, - "maartenwut/mx5160": { - "target": "evyd13/mx5160" - }, - "maartenwut/nt660": { - "target": "evyd13/nt660" - }, - "maartenwut/omrontkl": { - "target": "evyd13/omrontkl" - }, - "maartenwut/plain60": { - "target": "evyd13/plain60" - }, - "maartenwut/pockettype": { - "target": "evyd13/pockettype" - }, - "maartenwut/quackfire": { - "target": "evyd13/quackfire" - }, - "maartenwut/solheim68": { - "target": "evyd13/solheim68" - }, - "maartenwut/ta65": { - "target": "evyd13/ta65" - }, - "maartenwut/wasdat": { - "target": "evyd13/wasdat" - }, - "maartenwut/wasdat_code": { - "target": "evyd13/wasdat_code" - }, - "maartenwut/wonderland": { - "target": "evyd13/wonderland" - }, - "matchstickworks/southpad": { - "target": "matchstickworks/southpad/rev2/" - }, - "matrix/m12og": { - "target": "matrix/m12og/rev1" - }, - "mechlovin/hannah910": { - "target": "mechlovin/hannah910/rev1" - }, - "mechlovin/adelais/rgb_led": { - "target": "mechlovin/adelais/rgb_led/rev1" - }, - "mechlovin/adelais/standard_led": { - "target": "mechlovin/adelais/standard_led/arm/rev2" - }, - "mechlovin/delphine": { - "target": "mechlovin/delphine/mono_led" - }, - "mechlovin/hannah60rgb": { - "target": "mechlovin/hannah60rgb/rev1" - }, - "mechlovin/hannah65/mechlovin9": { - "target": "mechlovin/mechlovin9/rev1" - }, - "mechlovin/hex4b": { - "target": "mechlovin/hex4b/rev1" - }, - "melgeek/z70ultra": { - "target": "melgeek/z70ultra/rev1" - }, - "mechlovin/hannah65": { - "target": "mechlovin/hannah65/rev1" - }, - "minim": { - "target": "matthewdias/minim" - }, - "mnk1800s": { - "target": "monokei/mnk1800s" - }, - "mnk50": { - "target": "monokei/mnk50" - }, - "mnk75": { - "target": "monokei/mnk75" - }, - "model01": { - "target": "keyboardio/model01" - }, - "model_v": { - "target": "matthewdias/model_v" - }, - "m0lly": { - "target": "tkc/m0lly" - }, - "montsinger/rebound": { - "target": "montsinger/rebound/rev1" - }, - "moonlander": { - "target": "zsa/moonlander" - }, - "mschwingen/modelm": { - "target": "ibm/model_m/mschwingen" - }, - "oddball": { - "target": "oddball/v1" - }, - "omnikey_blackheart": { - "target": "viktus/omnikey_bh" - }, - "omnikey_bh": { - "target": "viktus/omnikey_bh" - }, - "opus": { - "target": "keyhive/opus" - }, - "pabile/p20": { - "target": "pabile/p20/ver1" - }, - "pancake/feather": { - "target": "spaceman/pancake/rev1/feather" - }, - "pancake/promicro": { - "target": "spaceman/pancake/rev1/promicro" - }, - "peiorisboards/ixora": { - "target": "coarse/ixora" - }, - "pico": { - "target": "kumaokobo/pico/65keys" - }, - "pico/65keys": { - "target": "kumaokobo/pico/65keys" - }, - "pico/70keys": { - "target": "kumaokobo/pico/70keys" - }, - "plaid": { - "target": "dm9records/plaid" - }, - "plain60": { - "target": "evyd13/plain60" - }, - "planck/ez": { - "target": "zsa/planck_ez/base" - }, - "planck/ez/base": { - "target": "zsa/planck_ez/base" - }, - "planck/ez/glow": { - "target": "zsa/planck_ez/glow" - }, - "ploopyco/trackball": { - "target": "ploopyco/trackball/rev1_005" - }, - "plywrks/ply8x": { - "target": "plywrks/ply8x/solder" - }, - "polilla": { - "target": "polilla/rev1" - }, - "primekb/prime_l": { - "target": "primekb/prime_l/v1" - }, - "primekb/prime_l_v2": { - "target": "primekb/prime_l/v2" - }, - "projectkb/alice": { - "target": "projectkb/alice/rev1" - }, - "rama/koyu": { - "target": "wilba_tech/rama_works_koyu" - }, - "rama/m6_a": { - "target": "wilba_tech/rama_works_m6_a" - }, - "rama/m6_b": { - "target": "wilba_tech/rama_works_m6_b" - }, - "rama/m10_b": { - "target": "wilba_tech/rama_works_m10_b" - }, - "rama/m60_a": { - "target": "wilba_tech/rama_works_m60_a" - }, - "rama/u80_a": { - "target": "wilba_tech/rama_works_u80_a" - }, - "ramonimbao/herringbone": { - "target": "rmi_kb/herringbone/v1" - }, - "ramonimbao/mona": { - "target": "rmi_kb/mona/v1" - }, - "redox_w": { - "target": "redox/wireless" - }, - "rgbkb/pan": { - "target": "rgbkb/pan/rev1/32a" - }, - "rgbkb/pan/rev1": { - "target": "rgbkb/pan/rev1/32a" - }, - "romac": { - "target": "kingly_keys/romac" - }, - "ropro": { - "target": "kingly_keys/ropro" - }, - "satan": { - "target": "gh60/satan" - }, - "skog": { - "target": "percent/skog" - }, - "smallice": { - "target": "keyhive/smallice" - }, - "southpole": { - "target": "keyhive/southpole" - }, - "speedo": { - "target": "cozykeys/speedo/v2" - }, - "staryu": { - "target": "ktec/staryu" - }, - "stoutgat": { - "target": "tkw/stoutgat/v1" - }, - "suihankey": { - "target": "kakunpc/suihankey/split/alpha" - }, - "ta65": { - "target": "evyd13/ta65" - }, - "tartan": { - "target": "dm9records/tartan" - }, - "tkc1800": { - "target": "tkc/tkc1800" - }, - "tkw/stoutgat/v2": { - "target": "tkw/stoutgat/v2/f411" - }, - "tokyo60": { - "target": "tokyokeyboard/tokyo60" - }, - "txuu": { - "target": "matthewdias/txuu" - }, - "underscore33": { - "target": "tominabox1/underscore33/rev1" - }, - "vinta": { - "target": "coarse/vinta" - }, - "wasdat": { - "target": "evyd13/wasdat" - }, - "westfoxtrot/cypher": { - "target": "westfoxtrot/cypher/rev1" - }, - "whale/sk": { - "target": "whale/sk/v3" - }, - "xd002": { - "target": "xiudi/xd002" - }, - "xd004": { - "target": "xiudi/xd004/v1" - }, - "xd60": { - "target": "xiudi/xd60/rev2" - }, - "xd68": { - "target": "xiudi/xd68" - }, - "xd75": { - "target": "xiudi/xd75" - }, - "xd84": { - "target": "xiudi/xd84" - }, - "xd84pro": { - "target": "xiudi/xd84pro" - }, - "xd87": { - "target": "xiudi/xd87" - }, - "xd96": { - "target": "xiudi/xd96" - }, - "xelus/dawn60": { - "target": "xelus/dawn60/rev1" - }, - "xelus/valor": { - "target": "xelus/valor/rev1" - }, - "z150_blackheart": { - "target": "viktus/z150_bh" - }, - "z150_bh":{ - "target": "viktus/z150_bh" - }, - "zeal60": { - "target": "wilba_tech/zeal60" - }, - "zeal65": { - "target": "wilba_tech/zeal65" - }, - // Moved during 2022 Q1 cycle - "6ball": { - "target": "maple_computing/6ball" - }, - "7skb": { - "target": "salicylic_acid3/7skb" - }, - "7splus": { - "target": "salicylic_acid3/7splus" - }, - "acr60": { - "target": "mechkeys/acr60" - }, - "adalyn": { - "target": "tominabox1/adalyn" - }, - "ajisai74": { - "target": "salicylic_acid3/ajisai74" - }, - "aleth42": { - "target": "25keys/aleth42" - }, - "alicia_cook": { - "target": "ibnuda/alicia_cook" - }, - "allison": { - "target": "prototypist/allison" - }, - "allison_numpad": { - "target": "prototypist/allison_numpad" - }, - "alu84": { - "target": "mechkeys/alu84" - }, - "angel17": { - "target": "kakunpc/angel17" - }, - "angel64/alpha": { - "target": "kakunpc/angel64/alpha" - }, - "angel64/rev1": { - "target": "kakunpc/angel64/rev1" - }, - "arch_36": { - "target": "obosob/arch_36" - }, - "bakeneko65/rev2": { - "target": "kkatano/bakeneko65/rev2" - }, - "bakeneko65/rev3": { - "target": "kkatano/bakeneko65/rev3" - }, - "barleycorn": { - "target": "yiancardesigns/barleycorn" - }, - "bat43/rev1": { - "target": "dailycraft/bat43/rev1" - }, - "bat43/rev2": { - "target": "dailycraft/bat43/rev2" - }, - "bigseries/1key": { - "target": "woodkeys/bigseries/1key" - }, - "bigseries/2key": { - "target": "woodkeys/bigseries/2key" - }, - "bigseries/3key": { - "target": "woodkeys/bigseries/3key" - }, - "bigseries/4key": { - "target": "woodkeys/bigseries/4key" - }, - "bkf": { - "target": "drhigsby/bkf" - }, - "business_card/alpha": { - "target": "kakunpc/business_card/alpha" - }, - "business_card/beta": { - "target": "kakunpc/business_card/beta" - }, - "butterstick": { - "target": "gboards/butterstick" - }, - "c39": { - "target": "maple_computing/c39" - }, - "cassette42": { - "target": "25keys/cassette42" - }, - "chidori": { - "target": "kagizaraya/chidori" - }, - "chili": { - "target": "ydkb/chili" - }, - "chimera_ergo": { - "target": "glenpickle/chimera_ergo" - }, - "chimera_ls": { - "target": "glenpickle/chimera_ls" - }, - "chimera_ortho": { - "target": "glenpickle/chimera_ortho" - }, - "chimera_ortho_plus": { - "target": "glenpickle/chimera_ortho_plus" - }, - "choc_taro": { - "target": "kakunpc/choc_taro" - }, - "choco60": { - "target": "recompile_keys/choco60" - }, - "christmas_tree": { - "target": "maple_computing/christmas_tree" - }, - "claw44/rev1": { - "target": "dailycraft/claw44/rev1" - }, - "cocoa40": { - "target": "recompile_keys/cocoa40" - }, - "comet46": { - "target": "satt/comet46" - }, - "cu24": { - "target": "capsunlocked/cu24" - }, - "cu75": { - "target": "capsunlocked/cu75" - }, - "cu80": { - "target": "capsunlocked/cu80/v1" - }, - "delilah": { - "target": "rainkeebs/delilah" - }, - "diverge3": { - "target": "unikeyboard/diverge3" - }, - "divergetm2": { - "target": "unikeyboard/divergetm2" - }, - "dozen0": { - "target": "yynmt/dozen0" - }, - "dubba175": { - "target": "drhigsby/dubba175" - }, - "eggman": { - "target": "qpockets/eggman" - }, - "enter67": { - "target": "kezewa/enter67" - }, - "enter80": { - "target": "kezewa/enter80" - }, - "ergo42": { - "target": "biacco42/ergo42" - }, - "ergoarrows": { - "target": "salicylic_acid3/ergoarrows" - }, - "ergodash/mini": { - "target": "omkbd/ergodash/mini" - }, - "ergodash/rev1": { - "target": "omkbd/ergodash/rev1" - }, - "ergodox_infinity": { - "target": "input_club/ergodox_infinity" - }, - "ergotaco": { - "target": "gboards/ergotaco" - }, - "espectro": { - "target": "mechkeys/espectro" - }, - "eu_isolation": { - "target": "p3d/eu_isolation" - }, - "felix": { - "target": "unikeyboard/felix" - }, - "flygone60/rev3": { - "target": "shandoncodes/flygone60/rev3" - }, - "four_banger": { - "target": "bpiphany/four_banger" - }, - "freyr": { - "target": "hnahkb/freyr" - }, - "geminate60": { - "target": "weirdo/geminate60" - }, - "gentleman65": { - "target": "jkeys_design/gentleman65" - }, - "georgi": { - "target": "gboards/georgi" - }, - "gergo": { - "target": "gboards/gergo" - }, - "getta25": { - "target": "salicylic_acid3/getta25" - }, - "gingham": { - "target": "yiancardesigns/gingham" - }, - "gurindam": { - "target": "ibnuda/gurindam" - }, - "halberd": { - "target": "kagizaraya/halberd" - }, - "handwired/hillside/0_1": { - "target": "hillside/48/0_1" - }, - "hecomi/alpha": { - "target": "takashiski/hecomi/alpha" - }, - "hfdkb/keyboard_sw/k83":{ - "target": "inland/kb83" - }, - "hid_liber": { - "target": "bpiphany/hid_liber" - }, - "id67/default_rgb": { - "target": "idobao/id67" - }, - "id67/rgb": { - "target": "idobao/id67" - }, - "id80": { - "target": "idobao/id80/v2/ansi" - }, - "idobao/id80/v1/ansi": { - "target": "idobao/id80/v2/ansi" - }, - "idobao/id80/v1/iso": { - "target": "idobao/id80/v2/iso" - }, - "id87": { - "target": "idobao/id87/v1" - }, - "infinity60": { - "target": "input_club/infinity60" - }, - "ivy/rev1": { - "target": "maple_computing/ivy/rev1" - }, - "jisplit89": { - "target": "salicylic_acid3/jisplit89" - }, - "jnao": { - "target": "maple_computing/jnao" - }, - "just60": { - "target": "ydkb/just60" - }, - "k_type": { - "target": "input_club/k_type" - }, - "kagamidget": { - "target": "yynmt/kagamidget" - }, - "kelowna/rgb64": { - "target": "weirdo/kelowna/rgb64" - }, + // Moved during 2022 Q1 cycle "keychron/q0": { "target": "keychron/q0/base" }, @@ -1069,1303 +16,10 @@ "target": "keychron/q1v1/ansi" } "keychron/q4": { - "target": "keychron/q4/ansi/v1" - } - "kmac": { - "target": "kbdmania/kmac" - } - "kmac_pad": { - "target": "kbdmania/kmac_pad" - } - "kprepublic/bm40hsrgb": { - "target": "kprepublic/bm40hsrgb/rev1" - }, - "kprepublic/bm65hsrgb_iso": { - "target": "kprepublic/bm65hsrgb_iso/rev1" - }, - "kprepublic/bm68hsrgb": { - "target": "kprepublic/bm68hsrgb/rev1" - }, - "late9/rev1": { - "target": "ivndbt/late9/rev1" - }, - "latin17rgb": { - "target": "latincompass/latin17rgb" - }, - "latin47ble": { - "target": "latincompass/latin47ble" - }, - "latin60rgb": { - "target": "latincompass/latin60rgb" - }, - "latin64ble": { - "target": "latincompass/latin64ble" - }, - "latin6rgb": { - "target": "latincompass/latin6rgb" - }, - "latinpad": { - "target": "latincompass/latinpad" - }, - "latinpadble": { - "target": "latincompass/latinpadble" - }, - "launchpad/rev1": { - "target": "maple_computing/launchpad/rev1" - }, - "lefty": { - "target": "smoll/lefty/rev2" - }, - "lefty/rev1": { - "target": "smoll/lefty/rev1" - }, - "lck75": { - "target": "lyso1/lck75" - }, - "le_chiffre": { - "target": "tominabox1/le_chiffre" - }, - "lefishe": { - "target": "lyso1/lefishe" - }, - "lets_split_eh/eh": { - "target": "maple_computing/lets_split_eh" - }, - "ls_60": { - "target": "weirdo/ls_60" - }, - "lpad": { - "target": "laneware/lpad" - }, - "lw67": { - "target": "laneware/lw67" - }, - "lw75": { - "target": "laneware/lw75" - }, - "m3n3van": { - "target": "matthewdias/m3n3van" - }, - "macro1": { - "target": "laneware/macro1" - }, - "maple_computing/lets_split_eh/eh": { - "target": "maple_computing/lets_split_eh" - }, - "massdrop/thekey": { - "target": "drop/thekey/v1" - }, - "massdrop/thekey_v2": { - "target": "drop/thekey/v2" - }, - "mechmini/v1": { - "target": "mechkeys/mechmini/v1" - }, - "mechmini/v2": { - "target": "mechkeys/mechmini/v2" - }, - "meira": { - "target": "woodkeys/meira" - }, - "meishi": { - "target": "biacco42/meishi" - }, - "meishi2": { - "target": "biacco42/meishi2" - }, - "melody96": { - "target": "ymdk/melody96" - }, - "miniaxe": { - "target": "kagizaraya/miniaxe" - }, - "minidox/rev1": { - "target": "maple_computing/minidox/rev1" - }, - "mino/hotswap": { - "target": "shandoncodes/mino/hotswap" - }, - "mino_plus/hotswap": { - "target": "shandoncodes/mino_plus/hotswap" - }, - "mino_plus/soldered": { - "target": "shandoncodes/mino_plus/soldered" - }, - "mio": { - "target": "recompile_keys/mio" - }, - "montex": { - "target": "idobao/montex/v1" - }, - "mt40": { - "target": "mt/mt40" - }, - "mt64rgb": { - "target": "mt/mt64rgb" - }, - "mt84": { - "target": "mt/mt84" - }, - "mt980": { - "target": "mt/mt980" - }, - "mt/ncr80/hotswap": { - "target": "mt/ncr80/r2/hotswap" - }, - "mt/ncr80/solder": { - "target": "mt/ncr80/r2/solder" - }, - "nafuda": { - "target": "salicylic_acid3/nafuda" - }, - "naiping/np64": { - "target": "weirdo/naiping/np64" - }, - "naiping/nphhkb": { - "target": "weirdo/naiping/nphhkb" - }, - "naiping/npminila": { - "target": "weirdo/naiping/npminila" - }, - "naked48": { - "target": "salicylic_acid3/naked48" - }, - "naked60": { - "target": "salicylic_acid3/naked60" - }, - "naked64": { - "target": "salicylic_acid3/naked64" - }, - "namecard2x4": { - "target": "takashiski/namecard2x4" - }, - "navi10": { - "target": "keyhive/navi10" - }, - "nebula12": { - "target": "spaceholdings/nebula12" - }, - "nebula68": { - "target": "spaceholdings/nebula68" - }, - "nebula68b": { - "target": "spaceholdings/nebula68b" - }, - "neopad/rev1": { - "target": "ivndbt/neopad/rev1" - }, - "niu_mini": { - "target": "kbdfans/niu_mini" - }, - "nk1": { - "target": "novelkeys/nk1" - }, - "nk65": { - "target": "novelkeys/nk65" - }, - "nk87": { - "target": "novelkeys/nk87" - }, - "nknl7en": { - "target": "salicylic_acid3/nknl7en" - }, - "nknl7jp": { - "target": "salicylic_acid3/nknl7jp" - }, - "nomu30": { - "target": "recompile_keys/nomu30" - }, - "novelpad": { - "target": "novelkeys/novelpad" - }, - "ogurec": { - "target": "drhigsby/ogurec" - }, - "otaku_split/rev0": { - "target": "takashiski/otaku_split/rev0" - }, - "otaku_split/rev1": { - "target": "takashiski/otaku_split/rev1" - }, - "owl8": { - "target": "dailycraft/owl8" - }, - "packrat": { - "target": "drhigsby/packrat" - }, - "pistachio": { - "target": "rate/pistachio" - }, - "pistachio_mp": { - "target": "rate/pistachio_mp" - }, - "pistachio_pro": { - "target": "rate/pistachio_pro" - }, - "plexus75": { - "target": "checkerboards/plexus75" - }, - "pursuit40": { - "target": "checkerboards/pursuit40" - }, - "pw88": { - "target": "smoll/pw88" - }, - "q4z": { - "target": "p3d/q4z" - }, - "qaz": { - "target": "tominabox1/qaz" - }, - "quark": { - "target": "checkerboards/quark" - }, - "rabbit_capture_plan": { - "target": "kakunpc/rabbit_capture_plan" - }, - "raindrop": { - "target": "laneware/raindrop" - }, - "ramonimbao/aelith": { - "target": "rmi_kb/aelith" - }, - "ramonimbao/chevron": { - "target": "rmi_kb/chevron" - }, - "ramonimbao/herringbone/pro": { - "target": "rmi_kb/herringbone/pro" - }, - "ramonimbao/herringbone/v1": { - "target": "rmi_kb/herringbone/v1" - }, - "ramonimbao/mona/v1": { - "target": "rmi_kb/mona/v1" - }, - "ramonimbao/mona/v1_1": { - "target": "rmi_kb/mona/v1_1" - }, - "ramonimbao/mona/v32a": { - "target": "rmi_kb/mona/v32a" - }, - "ramonimbao/squishy65": { - "target": "rmi_kb/squishy65" - }, - "ramonimbao/squishyfrl": { - "target": "rmi_kb/squishyfrl" - }, - "ramonimbao/squishytkl": { - "target": "rmi_kb/squishytkl" - }, - "ramonimbao/tkl_ff/v1": { - "target": "rmi_kb/tkl_ff/v1" - }, - "ramonimbao/tkl_ff/v2": { - "target": "rmi_kb/tkl_ff/v2" - }, - "ramonimbao/wete/v1": { - "target": "rmi_kb/wete/v1" - }, - "ramonimbao/wete/v2": { - "target": "rmi_kb/wete/v2" - }, - "rainkeeb": { - "target": "rainkeebs/rainkeeb" - }, - "reviung33": { - "target": "reviung/reviung33" - }, - "reviung34": { - "target": "reviung/reviung34" - }, - "reviung39": { - "target": "reviung/reviung39" - }, - "reviung41": { - "target": "reviung/reviung41" - }, - "reviung5": { - "target": "reviung/reviung5" - }, - "reviung53": { - "target": "reviung/reviung53" - }, - "reviung61": { - "target": "reviung/reviung61" - }, - "riot_pad": { - "target": "shandoncodes/riot_pad" - }, - "runner3680/3x6": { - "target": "omkbd/runner3680/3x6" - }, - "runner3680/3x7": { - "target": "omkbd/runner3680/3x7" - }, - "runner3680/3x8": { - "target": "omkbd/runner3680/3x8" - }, - "runner3680/4x6": { - "target": "omkbd/runner3680/4x6" - }, - "runner3680/4x7": { - "target": "omkbd/runner3680/4x7" - }, - "runner3680/4x8": { - "target": "omkbd/runner3680/4x8" - }, - "runner3680/5x6": { - "target": "omkbd/runner3680/5x6" - }, - "runner3680/5x6_5x8": { - "target": "omkbd/runner3680/5x6_5x8" - }, - "runner3680/5x7": { - "target": "omkbd/runner3680/5x7" - }, - "runner3680/5x8": { - "target": "omkbd/runner3680/5x8" - }, - "saevus/cor": { - "target": "concreteflowers/cor" - }, - "saevus/cor_tkl": { - "target": "concreteflowers/cor_tkl" - }, - "scarletbandana": { - "target": "woodkeys/scarletbandana" - }, - "scythe": { - "target": "kagizaraya/scythe" - }, - "seigaiha": { - "target": "yiancardesigns/seigaiha" - }, - "setta21": { - "target": "salicylic_acid3/setta21" - }, - "soda/mango": { - "target": "magic_force/mf17" - }, - "soda/pocket": { - "target": "magic_force/mf34" - }, - "space_space/rev1": { - "target": "qpockets/space_space/rev1" - }, - "space_space/rev2": { - "target": "qpockets/space_space/rev2" - }, - "spacey": { - "target": "p3d/spacey" - }, - "spiderisland/winry25tc": { - "target": "winry/winry25tc" - }, - "splitreus62": { - "target": "nacly/splitreus62" - }, - "squiggle/rev1": { - "target": "ibnuda/squiggle/rev1" - }, - "standaside": { - "target": "edi/standaside" - }, - "steal_this_keyboard": { - "target": "obosob/steal_this_keyboard" - }, - "stella": { - "target": "hnahkb/stella" - }, - "studiokestra/line_tkl": { - "target": "studiokestra/line_friends_tkl" - }, - "suihankey/alpha": { - "target": "kakunpc/suihankey/alpha" - }, - "suihankey/rev1": { - "target": "kakunpc/suihankey/rev1" - }, - "suihankey/split": { - "target": "kakunpc/suihankey/split" - }, - "synapse": { - "target": "p3d/synapse" - }, - "the_ruler": { - "target": "maple_computing/the_ruler" - }, - "thedogkeyboard": { - "target": "kakunpc/thedogkeyboard" - }, - "tiger910": { - "target": "weirdo/tiger910" - }, - "treadstone32": { - "target": "marksard/treadstone32" - }, - "treadstone48/rev1": { - "target": "marksard/treadstone48/rev1" - }, - "treadstone48/rev2": { - "target": "marksard/treadstone48/rev2" - }, - "tronguylabs/m122_3270": { - "target": "ibm/model_m_122/m122_3270/teensy" - }, - "tw40": { - "target": "p3d/tw40" - }, - "ua62": { - "target": "nacly/ua62" - }, - "underscore33/rev1": { - "target": "tominabox1/underscore33/rev1" - }, - "underscore33/rev2": { - "target": "tominabox1/underscore33/rev2" - }, - "uno": { - "target": "keyhive/uno" - }, - "ut472": { - "target": "keyhive/ut472" - }, - "vn66": { - "target": "hnahkb/vn66" - }, - "w1_at": { - "target": "geonworks/w1_at" - }, - "wallaby": { - "target": "kkatano/wallaby" - }, - "wanten": { - "target": "qpockets/wanten" - }, - "wheatfield/blocked65": { - "target": "mt/blocked65" - }, - "wheatfield/split75": { - "target": "mt/split75" - }, - "whitefox": { - "target": "input_club/whitefox" - }, - "wings42/rev1": { - "target": "dailycraft/wings42/rev1" - }, - "wings42/rev1_extkeys": { - "target": "dailycraft/wings42/rev1_extkeys" - }, - "wings42/rev2": { - "target": "dailycraft/wings42/rev2" - }, - "yasui": { - "target": "rainkeebs/yasui" - }, - "yd60mq": { - "target": "ymdk/yd60mq" - }, - "yd68": { - "target": "ydkb/yd68" - }, - "ymd75": { - "target": "ymdk/ymd75/rev1" - }, - "ymd96": { - "target": "ymdk/ymd96" - }, - "ymdk/id75": { - "target": "ymdk/id75/f103" - }, - "ymdk_np21": { - "target": "ymdk/np21" - }, - "yugo_m/model_m_101": { - "target": "ibm/model_m/yugo_m" - }, - "yurei": { - "target": "kkatano/yurei" - }, - "z12": { - "target": "zigotica/z12" - }, - "z34": { - "target": "zigotica/z34" - }, - "zinc": { - "target": "25keys/zinc" - }, - "zinc/rev1": { - "target": "25keys/zinc/rev1" - }, - "zinc/reva": { - "target": "25keys/zinc/reva" - }, + "target": "keychron/q4/ansi/v1" + } // Moved during 2023 Q4 cycle - "ymdk/melody96": { - "target": "ymdk/melody96/soldered" - }, // Moved during 2024 Q2 cycle - "kprepublic/jj40": { - "target": "kprepublic/jj40/rev1" - }, - "kprepublic/jj50": { - "target": "kprepublic/jj50/rev1" - }, - "dnworks/9973": { - "target": "dnworks/tkl87" - }, // DEFAULT_FOLDER removed during 2025 Q1 cycle - "0_sixty": { - "target": "0_sixty/base" - }, - "0xcb/splaytoraid": { - "target": "0xcb/splaytoraid/rp2040_ce" - }, - "1upkeyboards/pi40": { - "target": "1upkeyboards/pi40/mit_v1_0" - }, - "1upkeyboards/pi50": { - "target": "1upkeyboards/pi50/grid" - }, - "1upkeyboards/sweet16": { - "target": "1upkeyboards/sweet16/v1" - }, - "25keys/aleth42": { - "target": "25keys/aleth42/rev1" - }, - "25keys/zinc": { - "target": "25keys/zinc/rev1" - }, - "40percentclub/i75": { - "target": "40percentclub/i75/promicro" - }, - "40percentclub/polyandry": { - "target": "40percentclub/polyandry/promicro" - }, - "8pack": { - "target": "8pack/rev12" - }, - "adkb96": { - "target": "adkb96/rev1" - }, - "adm42": { - "target": "adm42/rev4" - }, - "aeboards/satellite": { - "target": "aeboards/satellite/rev1" - }, - "atreus": { - "target": "atreus/astar" - }, - "atreyu": { - "target": "atreyu/rev1" - }, - "biacco42/ergo42": { - "target": "biacco42/ergo42/rev1" - }, - "binepad/bn009": { - "target": "binepad/bn009/r2" - }, - "binepad/bnr1": { - "target": "binepad/bnr1/v2" - }, - "boston_meetup": { - "target": "boston_meetup/2019" - }, - "bpiphany/frosty_flake": { - "target": "bpiphany/frosty_flake/20140521" - }, - "buzzard": { - "target": "buzzard/rev1" - }, - "cannonkeys/db60": { - "target": "cannonkeys/db60/rev2" - }, - "clickety_split/leeloo": { - "target": "clickety_split/leeloo/rev3" - }, - "converter/palm_usb": { - "target": "converter/palm_usb/stowaway" - }, - "crkbd": { - "target": "crkbd/rev1" - }, - "dailycraft/bat43": { - "target": "dailycraft/bat43/rev2" - }, - "dailycraft/claw44": { - "target": "dailycraft/claw44/rev1" - }, - "dailycraft/sandbox": { - "target": "dailycraft/sandbox/rev2" - }, - "dailycraft/wings42": { - "target": "dailycraft/wings42/rev2" - }, - "delikeeb/vanana": { - "target": "delikeeb/vanana/rev2" - }, - "delikeeb/waaffle/rev3": { - "target": "delikeeb/waaffle/rev3/pro_micro" - }, - "deltasplit75": { - "target": "deltasplit75/v2" - }, - "drhigsby/ogurec": { - "target": "drhigsby/ogurec/left_pm" - }, - "duck/octagon": { - "target": "duck/octagon/v2" - }, - "duck/orion": { - "target": "duck/orion/v3" - }, - "ducky/one2mini": { - "target": "ducky/one2mini/1861st" - }, - "ducky/one2sf": { - "target": "ducky/one2sf/1967st" - }, - "dumbpad": { - "target": "dumbpad/v0x" - }, - "durgod/galaxy": { - "target": "durgod/dgk6x/galaxy" - }, - "durgod/venus": { - "target": "durgod/dgk6x/venus" - }, - "dztech/tofu/ii": { - "target": "dztech/tofu/ii/v1" - }, - "dztech/tofu/jr": { - "target": "dztech/tofu/jr/v1" - }, - "eco": { - "target": "eco/rev2" - }, - "ergoslab": { - "target": "ergoslab/rev1" - }, - "ergotravel": { - "target": "ergotravel/rev1" - }, - "evyd13/atom47": { - "target": "evyd13/atom47/rev4" - }, - "fortitude60": { - "target": "fortitude60/rev1" - }, - "fruitykeeb/fruitbar/r1": { - "target": "fruitykeeb/fruitbar/r1/promicro" - }, - "ghs/jem": { - "target": "ghs/jem/soldered" - }, - "hadron": { - "target": "hadron/ver2" - }, - "handwired/bento": { - "target": "handwired/bento/rev1" - }, - "handwired/dactyl_manuform/6x6": { - "target": "handwired/dactyl_manuform/6x6/promicro" - }, - "handwired/meck_tkl": { - "target": "handwired/meck_tkl/blackpill_f401" - }, - "handwired/ms_sculpt_mobile": { - "target": "handwired/ms_sculpt_mobile/teensy2pp" - }, - "handwired/onekey": { - "target": "handwired/onekey/promicro" - }, - "handwired/pill60": { - "target": "handwired/pill60/bluepill" - }, - "handwired/postageboard": { - "target": "handwired/postageboard/mini" - }, - "handwired/qc60": { - "target": "handwired/qc60/proto" - }, - "handwired/sono1": { - "target": "handwired/sono1/t2pp" - }, - "handwired/splittest": { - "target": "handwired/splittest/promicro" - }, - "handwired/stef9998/split_5x7": { - "target": "handwired/stef9998/split_5x7/rev1" - }, - "handwired/symmetric70_proto/promicro": { - "target": "handwired/symmetric70_proto/promicro/base" - }, - "handwired/symmetric70_proto/proton_c": { - "target": "handwired/symmetric70_proto/proton_c/base" - }, - "handwired/unk": { - "target": "handwired/unk/rev1" - }, - "handwired/xealous": { - "target": "handwired/xealous/rev1" - }, - "hillside/46": { - "target": "hillside/46/0_1" - }, - "hhkb/ansi": { - "target": "hhkb/ansi/32u4" - }, - "hillside/48": { - "target": "hillside/48/0_1" - }, - "hillside/52": { - "target": "hillside/52/0_1" - }, - "ibnuda/squiggle": { - "target": "ibnuda/squiggle/rev1" - }, - "idobao/id80/v1": { - "target": "idobao/id80/v2/ansi" - }, - "idobao/id80/v3": { - "target": "idobao/id80/v3/ansi" - }, - "inett_studio/sq80": { - "target": "inett_studio/sq80/hotswap_layout_i" - }, - "input_club/infinity60": { - "target": "input_club/infinity60/led" - }, - "jacky_studio/bear_65": { - "target": "jacky_studio/bear_65/rev1" - }, - "jacky_studio/piggy60/rev1": { - "target": "jacky_studio/piggy60/rev1/solder" - }, - "jadookb/jkb65": { - "target": "jadookb/jkb65/r1" - }, - "jian": { - "target": "jian/rev2" - }, - "jiran": { - "target": "jiran/rev1" - }, - "jorne": { - "target": "jorne/rev1" - }, - "junco": { - "target": "junco/rev1" - }, - "kakunpc/angel17": { - "target": "kakunpc/angel17/rev1" - }, - "kakunpc/angel64": { - "target": "kakunpc/angel64/rev1" - }, - "kakunpc/business_card": { - "target": "kakunpc/business_card/beta" - }, - "kakunpc/suihankey": { - "target": "kakunpc/suihankey/rev1" - }, - "kakunpc/suihankey/split": { - "target": "kakunpc/suihankey/split/rev1" - }, - "kapcave/paladinpad": { - "target": "kapcave/paladinpad/rev2" - }, - "kapl": { - "target": "kapl/rev1" - }, - "kbdfans/d45": { - "target": "kbdfans/d45/v2" - }, - "kbdfans/kbd75": { - "target": "kbdfans/kbd75/rev1" - }, - "keebio/bdn9": { - "target": "keebio/bdn9/rev1" - }, - "keebio/convolution": { - "target": "keebio/convolution/rev1" - }, - "keebio/foldkb": { - "target": "keebio/foldkb/rev1" - }, - "keebio/kbo5000": { - "target": "keebio/kbo5000/rev1" - }, - "keebio/levinson": { - "target": "keebio/levinson/rev2" - }, - "keebio/quefrency": { - "target": "keebio/quefrency/rev1" - }, - "keebio/rorschach": { - "target": "keebio/rorschach/rev1" - }, - "keebio/sinc": { - "target": "keebio/sinc/rev1" - }, - "keebio/viterbi": { - "target": "keebio/viterbi/rev2" - }, - "keycapsss/kimiko": { - "target": "keycapsss/kimiko/rev2" - }, - "keyhive/navi10": { - "target": "keyhive/navi10/rev3" - }, - "keyhive/uno": { - "target": "keyhive/uno/rev1" - }, - "kin80": { - "target": "kin80/blackpill401" - }, - "kprepublic/cstc40/daughterboard": { - "target": "kprepublic/cstc40/rev1" - }, - "kprepublic/cstc40/single_pcb": { - "target": "kprepublic/cstc40/rev2" - }, - "kumaokobo/kudox_full": { - "target": "kumaokobo/kudox_full/rev1" - }, - "kumaokobo/kudox_game": { - "target": "kumaokobo/kudox_game/rev2" - }, - "kumaokobo/kudox": { - "target": "kumaokobo/kudox/rev3" - }, - "kumaokobo/pico": { - "target": "kumaokobo/pico/65keys" - }, - "lazydesigners/dimple": { - "target": "lazydesigners/dimple/staggered/rev1" - }, - "lets_split": { - "target": "lets_split/rev2" - }, - "lfkeyboards/lfk87": { - "target": "lfkeyboards/lfk78/revc" - }, - "lily58": { - "target": "lily58/rev1" - }, - "lime": { - "target": "lime/rev1" - }, - "maple_computing/christmas_tree": { - "target": "maple_computing/christmas_tree/v2017" - }, - "maple_computing/ivy": { - "target": "maple_computing/ivy/rev1" - }, - "maple_computing/launchpad": { - "target": "maple_computing/launchpad/rev1" - }, - "maple_computing/minidox": { - "target": "maple_computing/minidox/rev1" - }, - "mariorion_v25": { - "target": "mariorion_v25/prod" - }, - "marksard/rhymestone": { - "target": "marksard/rhymestone/rev1" - }, - "marksard/treadstone32": { - "target": "marksard/treadstone32/rev1" - }, - "marksard/treadstone48": { - "target": "marksard/treadstone48/rev1" - }, - "maxipad": { - "target": "maxipad/promicro" - }, - "mechkeys/mechmini": { - "target": "mechkeys/mechmini/v2" - }, - "mechllama/g35": { - "target": "mechllama/g35/v2" - }, - "mechlovin/adelais": { - "target": "mechlovin/adelais/standard_led/arm/rev2" - }, - "mechlovin/adelais/standard_led/arm/rev4": { - "target": "mechlovin/adelais/standard_led/arm/rev4/stm32f303" - }, - "mechlovin/adelais/standard_led/arm": { - "target": "mechlovin/adelais/standard_led/arm/rev2" - }, - "mechlovin/adelais/standard_led/avr": { - "target": "mechlovin/adelais/standard_led/avr/rev1" - }, - "mechlovin/hannah65/rev1": { - "target": "mechlovin/hannah65/rev1/haus" - }, - "mechlovin/infinity87/rev1": { - "target": "mechlovin/infinity87/rev1/standard" - }, - "mechlovin/infinity87": { - "target": "mechlovin/infinity87/rgb_rev1" - }, - "mechlovin/mechlovin9": { - "target": "mechlovin/mechlovin9/rev1" - }, - "mechlovin/olly/jf": { - "target": "mechlovin/olly/jf/rev1" - }, - "mechlovin/zed1800": { - "target": "mechlovin/zed1800/saber" - }, - "mechlovin/zed65/no_backlight": { - "target": "mechlovin/zed65/no_backlight/wearhaus66" - }, - "mechlovin/zed65": { - "target": "mechlovin/zed65/no_backlight/wearhaus66" - }, - "mechwild/bde": { - "target": "mechwild/bde/rev2" - }, - "mechwild/mokulua": { - "target": "mechwild/mokulua/standard" - }, - "mechwild/obe/f401": { - "target": "mechwild/obe/f401/base" - }, - "mechwild/obe/f411": { - "target": "mechwild/obe/f411/base" - }, - "mechwild/obe": { - "target": "mechwild/obe/f401/base" - }, - "mechwild/waka60/f401": { - "target": "mechwild/waka60/f401/base" - }, - "mechwild/waka60/f411": { - "target": "mechwild/waka60/f411/base" - }, - "mechwild/waka60": { - "target": "mechwild/waka60/f401/base" - }, - "murcielago": { - "target": "murcielago/rev1" - }, - "nullbitsco/scramble": { - "target": "nullbitsco/scramble/v2" - }, - "omkbd/ergodash": { - "target": "omkbd/ergodash/rev1" - }, - "omkbd/runner3680": { - "target": "omkbd/runner3680/5x8" - }, - "orthodox": { - "target": "orthodox/rev3" - }, - "peej/rosaline": { - "target": "peej/rosaline/staggered" - }, - "peej/tripel": { - "target": "peej/tripel/left" - }, - "peranekofactory/tone": { - "target": "peranekofactory/tone/rev2" - }, - "phase_studio/titan65": { - "target": "phase_studio/titan65/hotswap" - }, - "pica40": { - "target": "pica40/rev2" - }, - "pinky": { - "target": "pinky/3" - }, - "ploopyco/madromys": { - "target": "ploopyco/madromys/rev1_001" - }, - "ploopyco/trackball_mini": { - "target": "ploopyco/trackball_mini/rev1_001" - }, - "ploopyco/trackball_nano": { - "target": "ploopyco/trackball_nano/rev1_001" - }, - "ploopyco/trackball_thumb": { - "target": "ploopyco/trackball_thumb/rev1_001" - }, - "primekb/meridian": { - "target": "primekb/meridian/ktr1010" - }, - "primekb/prime_e": { - "target": "primekb/prime_e/std" - }, - "program_yoink": { - "target": "program_yoink/staggered" - }, - "projectcain/vault35": { - "target": "projectcain/vault35/atmega32u4" - }, - "qpockets/space_space": { - "target": "qpockets/space_space/rev2" - }, - "qwertyydox": { - "target": "qwertyydox/rev1" - }, - "rate/pistachio": { - "target": "rate/pistachio/rev2" - }, - "recompile_keys/choco60": { - "target": "recompile_keys/choco60/rev1" - }, - "recompile_keys/nomu30": { - "target": "recompile_keys/nomu30/rev1" - }, - "redox/rev1": { - "target": "redox/rev1/base" - }, - "rgbkb/mun": { - "target": "rgbkb/mun/rev1" - }, - "rgbkb/sol3": { - "target": "rgbkb/sol3/rev1" - }, - "rgbkb/sol": { - "target": "rgbkb/sol/rev2" - }, - "rgbkb/zen": { - "target": "rgbkb/zen/rev2" - }, - "rgbkb/zygomorph": { - "target": "rgbkb/zygomorph/rev1" - }, - "rmi_kb/herringbone": { - "target": "rmi_kb/herringbone/v1" - }, - "rmi_kb/mona": { - "target": "rmi_kb/mona/v1_1" - }, - "rmi_kb/tkl_ff": { - "target": "rmi_kb/tkl_ff/v1" - }, - "rmi_kb/wete": { - "target": "rmi_kb/wete/v2" - }, - "rookiebwoy/late9": { - "target": "ivndbt/late9/rev1" - }, - "rookiebwoy/neopad": { - "target": "ivndbt/neopad/rev1" - }, - "ivndbt/late9": { - "target": "ivndbt/late9/rev1" - }, - "ivndbt/neopad": { - "target": "ivndbt/neopad/rev1" - }, - "rura66": { - "target": "rura66/rev1" - }, - "salicylic_acid3/7skb": { - "target": "salicylic_acid3/7skb/rev1" - }, - "salicylic_acid3/getta25": { - "target": "salicylic_acid3/getta25/rev1" - }, - "salicylic_acid3/jisplit89": { - "target": "salicylic_acid3/jisplit89/rev1" - }, - "salicylic_acid3/naked48": { - "target": "salicylic_acid3/naked48/rev1" - }, - "salicylic_acid3/naked60": { - "target": "salicylic_acid3/naked60/rev1" - }, - "salicylic_acid3/naked64": { - "target": "salicylic_acid3/naked64/rev1" - }, - "salicylic_acid3/setta21": { - "target": "salicylic_acid3/setta21/rev1" - }, - "sawnsprojects/okayu": { - "target": "sawnsprojects/okayu/stm32f072" - }, - "smoll/lefty": { - "target": "smoll/lefty/rev2" - }, - "sofle": { - "target": "sofle/rev1" - }, - "spaceholdings/nebula68b": { - "target": "spaceholdings/nebula68b/solder" - }, - "spacetime": { - "target": "spacetime/rev1" - }, - "splitkb/aurora/corne": { - "target": "splitkb/aurora/corne/rev1" - }, - "splitkb/aurora/helix": { - "target": "splitkb/aurora/helix/rev1" - }, - "splitkb/aurora/lily58": { - "target": "splitkb/aurora/lily58/rev1" - }, - "splitkb/aurora/sofle_v2": { - "target": "splitkb/aurora/sofle_v2/rev1" - }, - "splitkb/aurora/sweep": { - "target": "splitkb/aurora/sweep/rev1" - }, - "splitkb/kyria": { - "target": "splitkb/kyria/rev3" - }, - "splitkb/kyria/rev1": { - "target": "splitkb/kyria/rev1/base" - }, - "splitkb/kyria/rev2": { - "target": "splitkb/kyria/rev2/base" - }, - "splitty": { - "target": "splitty/rev1" - }, - "studiokestra/galatea": { - "target": "studiokestra/galatea/rev1" - }, - "takashiski/hecomi": { - "target": "takashiski/hecomi/alpha" - }, - "takashiski/namecard2x4": { - "target": "takashiski/namecard2x4/rev2" - }, - "teleport/native": { - "target": "teleport/native/iso" - }, - "themadnoodle/noodlepad": { - "target": "themadnoodle/noodlepad/v1" - }, - "tkw/grandiceps": { - "target": "tkw/grandiceps/rev1" - }, - "tominabox1/le_chiffre": { - "target": "tominabox1/le_chiffre/rev1" - }, - "tominabox1/littlefoot_lx": { - "target": "tominabox1/littlefoot_lx/rev1" - }, - "tominabox1/underscore33": { - "target": "tominabox1/underscore33/rev1" - }, - "trnthsn/e8ghty": { - "target": "trnthsn/e8ghty/stm32f103" - }, - "trnthsn/s6xty5neor2": { - "target": "trnthsn/s6xty5neor2/stm32f103" - }, - "tweetydabird/lotus58/elite_c": { - "target": "tweetydabird/lotus58" - }, - "tweetydabird/lotus58/nanoboot": { - "target": "tweetydabird/lotus58" - }, - "tweetydabird/lotus58/promicro": { - "target": "tweetydabird/lotus58" - }, - "tweetydabird/lotus58/rp2040_ce": { - "target": "tweetydabird/lotus58" - }, - "unison": { - "target": "unison/v04" - }, - "uzu42": { - "target": "uzu42/rev1" - }, - "vitamins_included": { - "target": "vitamins_included/rev2" - }, - "westm/westm68": { - "target": "westm/westm68/rev2" - }, - "westm/westm9": { - "target": "westm/westm9/rev2" - }, - "woodkeys/meira": { - "target": "woodkeys/meira/promicro" - }, - "work_louder/loop": { - "target": "work_louder/loop/rev3" - }, - "work_louder/work_board": { - "target": "work_louder/work_board/rev3" - }, - "yanghu/unicorne": { - "target": "yanghu/unicorne/f411" - }, - "yosino58": { - "target": "yosino58/rev1" - }, - "ymdk/yd60mq": { - "target": "ymdk/yd60mq/12led" - }, - "yushakobo/navpad/10": { - "target": "yushakobo/navpad/10/rev1" - }, - "yynmt/acperience12": { - "target": "yynmt/acperience12/rev1" - }, - "zsa/planck_ez": { - "target": "zsa/planck_ez/base" - }, // DEFAULT_FOLDER removed during 2025 Q3 cycle - "cannonkeys/satisfaction75": { - "target": "cannonkeys/satisfaction75/rev1" - }, - "converter/adb_usb": { - "target": "converter/adb_usb/rev1" - }, - "converter/sun_usb": { - "target": "converter/sun_usb/type5" - }, - "converter/usb_usb": { - "target": "converter/usb_usb/hasu" - }, - "durgod/dgk6x": { - "target": "durgod/dgk6x/hades_ansi" - }, - "ergodox_ez": { - "target": "ergodox_ez/base" - }, - "ferris/0_2": { - "target": "ferris/0_2/base" - }, - "handwired/dygma/raise": { - "target": "handwired/dygma/raise/ansi" - }, - "helix/rev3_4rows": { - "target": "helix/rev3" - }, - "helix/rev3_5rows": { - "target": "helix/rev3" - }, - "ibm/model_m/mschwingen": { - "target": "ibm/model_m/mschwingen/led_wired" - }, - "mechwild/sugarglider": { - "target": "mechwild/sugarglider/wide_oled/f401" - }, - "mechwild/sugarglider/wide_oled": { - "target": "mechwild/sugarglider/wide_oled/f401" - }, - "novelkeys/nk65": { - "target": "novelkeys/nk65/v1" - }, - "novelkeys/nk65/base": { - "target": "novelkeys/nk65/v1" - }, - "sirius/uni660/rev2": { - "target": "sirius/uni660/rev2/ansi" - } } diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000000..76645580fb25 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1780930886, + "narHash": "sha256-rppURzHviaQN131F+nLiLdGfcb0uCd9gGP0E5+iw9MI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8c3cede7ddc26bd659d2d383b5610efbd2c7a16e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000000..7bdf274b3110 --- /dev/null +++ b/flake.nix @@ -0,0 +1,49 @@ +{ + description = "QMK Firmware build environment for Keychron K2 HE"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = { self, nixpkgs }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + appdirs + argcomplete + colorama + dotty-dict + hid + hjson + jsonschema + milc + pygments + pyserial + pyusb + pillow + ]); + in + { + devShells.${system}.default = pkgs.mkShell { + name = "qmk-firmware"; + + buildInputs = with pkgs; [ + clang-tools + dfu-programmer + dfu-util + diffutils + git + gnumake + pythonEnv + qmk + gcc-arm-embedded + ]; + + shellHook = '' + unset NIX_CFLAGS_COMPILE_FOR_TARGET + ''; + }; + }; +} From b979f4e9aa71807e1ba44debe62b4a084e4bd686 Mon Sep 17 00:00:00 2001 From: Ido Samuelson Date: Wed, 10 Jun 2026 18:28:00 -0600 Subject: [PATCH 3/3] fix(wear_leveling): remove stray unlock in consolidate_force MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wear_leveling_consolidate_force() called wear_leveling_unlock() before erasing, which is not in upstream QMK. It desynced the lock-state tracking: the following wear_leveling_write_consolidated() then saw the store as already unlocked, returned STATUS_UNCHANGED, and skipped its matching re-lock — leaving wear_leveling.unlocked inconsistent with the backing store. That made the WearLeveling*_OOB unit tests fail (a write reached the backing store while it was still locked). Remove the redundant unlock to match upstream. All wear_leveling unit tests pass (general, 2byte, 2byte_optimized, 4byte, 8byte). Unrelated to the OpenRGB feature; isolated in its own commit. --- quantum/wear_leveling/wear_leveling.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/quantum/wear_leveling/wear_leveling.c b/quantum/wear_leveling/wear_leveling.c index fca6968a9225..f65ff9c1ca5d 100644 --- a/quantum/wear_leveling/wear_leveling.c +++ b/quantum/wear_leveling/wear_leveling.c @@ -302,7 +302,6 @@ static wear_leveling_status_t wear_leveling_write_consolidated(void) { */ static wear_leveling_status_t wear_leveling_consolidate_force(void) { wl_dprintf("Erasing backing store\n"); - wear_leveling_unlock(); // Erase the backing store. Expectation is that any un-written values that are read back after this call come back as zero. bool ok = backing_store_erase(); @@ -359,7 +358,7 @@ static wear_leveling_status_t wear_leveling_append_raw(backing_store_int_t value * @return true if consolidation occurred */ static wear_leveling_status_t wear_leveling_write_raw_multibyte(uint32_t address, const void *value, size_t length) { - const uint8_t * p = value; + const uint8_t *p = value; write_log_entry_t log = LOG_ENTRY_MAKE_MULTIBYTE(address, length); for (size_t i = 0; i < length; ++i) { log.raw8[3 + i] = p[i]; @@ -416,7 +415,7 @@ static wear_leveling_status_t wear_leveling_write_raw_multibyte(uint32_t address * Handles the actual writing of logical data into the write log section of the backing store. */ static wear_leveling_status_t wear_leveling_write_raw(uint32_t address, const void *value, size_t length) { - const uint8_t * p = value; + const uint8_t *p = value; size_t remaining = length; wear_leveling_status_t status = WEAR_LEVELING_SUCCESS; while (remaining > 0) { @@ -556,7 +555,7 @@ static wear_leveling_status_t wear_leveling_playback_log(void) { if (!ok) { wl_dprintf("Failed to load from backing store, skipping playback of write log\n"); cancel_playback = true; - status = WEAR_LEVELING_FAILED; + status = WEAR_LEVELING_FAILED; break; } address += (BACKING_STORE_WRITE_SIZE);