From 73865fec59949a831d177a257673c77a9ccd711d Mon Sep 17 00:00:00 2001 From: Adam Honse Date: Mon, 22 Jun 2026 10:16:06 -0500 Subject: [PATCH] Improve matrix generation for QMK Keychron controller, clean up modes, implement manual save --- .../QMKKeychronController.cpp | 414 ++++++++---------- .../QMKKeychronController.h | 62 +-- .../QMKKeychronControllerDetect.cpp | 4 +- .../RGBController_QMKKeychron.cpp | 277 +++++------- .../RGBController_QMKKeychron.h | 7 +- 5 files changed, 338 insertions(+), 426 deletions(-) diff --git a/Controllers/QMKController/QMKKeychronController/QMKKeychronController.cpp b/Controllers/QMKController/QMKKeychronController/QMKKeychronController.cpp index 05d34c871..4795bd5a7 100644 --- a/Controllers/QMKController/QMKKeychronController/QMKKeychronController.cpp +++ b/Controllers/QMKController/QMKKeychronController/QMKKeychronController.cpp @@ -2,7 +2,9 @@ | QMKKeychronController.cpp | | | | Driver for Keychron QMK-based keyboards | -| (Q1 HE and other KEYCHRON_RGB-enabled models) | +| | +| Amadej Kastelic 21 Jun 2026 | +| Adam Honse 22 Jun 2026 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | @@ -83,6 +85,42 @@ QMKKeychronController::QMKKeychronController(hid_device* dev_handle, const char | Get Keychron protocol version | \*-----------------------------------------------------*/ CmdGetKeychronProtocolVersion(&kc_protocol_version); + + /*-----------------------------------------------------*\ + | Get count of LEDs | + \*-----------------------------------------------------*/ + CmdGetNumberLEDs(&number_leds); + + led_info.resize(number_leds); + keycodes.resize(number_leds); + + for(std::size_t led_idx = 0; led_idx < led_info.size(); led_idx++) + { + led_info[led_idx].valid = false; + } + + /*-----------------------------------------------------*\ + | Get info and keycode for all LEDs | + \*-----------------------------------------------------*/ + for(unsigned char row = 0; row < 32; row++) + { + std::vector row_leds = CmdGetLEDIndexByRow(row); + + for(unsigned char col = 0; col < row_leds.size(); col++) + { + if(row_leds[col] != 0xFF && row_leds[col] < led_info.size() && led_info[row_leds[col]].valid == false) + { + led_info[row_leds[col]].valid = true; + led_info[row_leds[col]].col = col; + led_info[row_leds[col]].row = row; + } + } + } + + for(unsigned short led_index = 0; led_index < number_leds; led_index++) + { + keycodes[led_index] = CmdGetKeycode(0, led_info[led_index].row, led_info[led_index].col); + } } QMKKeychronController::~QMKKeychronController() @@ -124,6 +162,82 @@ bool QMKKeychronController::GetSupported() return(supported); } +unsigned short QMKKeychronController::GetKeycode(unsigned short led_index) +{ + return(keycodes[led_index]); +} + +unsigned short QMKKeychronController::GetLEDCount() +{ + return(number_leds); +} + +kc_led_info QMKKeychronController::GetLEDInfo(unsigned short led_index) +{ + return(led_info[led_index]); +} + +void QMKKeychronController::SaveMode() +{ + CmdSaveMode(); +} + +void QMKKeychronController::SendLEDs(unsigned short number_leds, RGBColor* color_data) +{ + unsigned short led_start_index = 0; + unsigned char number_packet_leds = 9; + + while(led_start_index < number_leds) + { + if((number_leds - led_start_index) < 9) + { + number_packet_leds = (number_leds - led_start_index); + } + + CmdSendLEDs(led_start_index, number_packet_leds, &color_data[led_start_index]); + + led_start_index += number_packet_leds; + } +} + +void QMKKeychronController::SetMode(unsigned short mode, unsigned char speed, unsigned char hue, unsigned char sat, unsigned char val) +{ + if(mode == 0xFFFF) + { + CmdSetRGBMatrixMode(KEYCHRON_QHE_PER_KEY_RGB_EFFECT); + CmdSetPerKeyRGBType(KEYCHRON_PER_KEY_RGB_SOLID); + } + else + { + CmdSetRGBMatrixMode((unsigned char)mode); + CmdSetColorHS(hue, sat); + CmdSetBrightness(val); + CmdSetSpeed(speed); + } +} + +unsigned short QMKKeychronController::CmdGetKeycode + ( + unsigned char layer, + unsigned char row, + unsigned char col + ) +{ + unsigned char args[3]; + unsigned char response[5]; + unsigned short keycode; + + args[0] = layer; + args[1] = row; + args[2] = col; + + ViaSendCommand(QMK_VIA_CMD_VIA_DYNAMIC_KEYMAP_GET_KEYCODE, args, sizeof(args), response, sizeof(response)); + + keycode = ( response[3] << 8 )| response[4]; + + return(keycode); +} + void QMKKeychronController::CmdGetKeychronProtocolVersion ( unsigned char* kc_protocol_version @@ -132,6 +246,44 @@ void QMKKeychronController::CmdGetKeychronProtocolVersion ViaSendCommand(KC_GET_PROTOCOL_VERSION, NULL, 0, (unsigned char*)kc_protocol_version, sizeof(unsigned char)); } +std::vector QMKKeychronController::CmdGetLEDIndexByRow(unsigned char row) +{ + unsigned char args[4]; + unsigned char response[KEYCHRON_QHE_PACKET_SIZE - 2]; + + args[0] = row; + args[1] = 0xFF; + args[2] = 0xFF; + args[3] = 0xFF; + + int bytes_read = ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_LED_IDX, args, sizeof(args), response, sizeof(response)); + + std::vector result; + + if(bytes_read > 0) + { + for(int i = 1; i < bytes_read; i++) + { + result.push_back(response[i] == 0xFF ? -1 : response[i]); + } + } + + return result; +} + +void QMKKeychronController::CmdGetNumberLEDs + ( + unsigned short* number_leds + ) +{ + ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_LED_COUNT, NULL, 0, (unsigned char*)number_leds, sizeof(unsigned short)); + + /*-----------------------------------------------------*\ + | The LED count byte order is reversed | + \*-----------------------------------------------------*/ + *number_leds = ((*number_leds & 0x00FF) << 8) | ((*number_leds & 0xFF00) >> 8); +} + void QMKKeychronController::CmdGetViaProtocolVersion ( unsigned short* via_protocol_version @@ -145,147 +297,42 @@ void QMKKeychronController::CmdGetViaProtocolVersion *via_protocol_version = ((*via_protocol_version & 0x00FF) << 8) | ((*via_protocol_version & 0xFF00) >> 8); } -unsigned int QMKKeychronController::GetLedCount() +void QMKKeychronController::CmdSaveMode() { - unsigned short led_count; - - ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_LED_COUNT, NULL, 0, (unsigned char*)&led_count, sizeof(unsigned short)); - - /*-----------------------------------------------------*\ - | The LED count byte order is reversed | - \*-----------------------------------------------------*/ - led_count = ((led_count & 0x00FF) << 8) | ((led_count & 0xFF00) >> 8); - - return(led_count); + ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_SAVE, NULL, 0, NULL, 0); } -std::vector QMKKeychronController::GetLedNumbersByRow(unsigned char row) -{ - unsigned char args[4]; - unsigned char response[KEYCHRON_QHE_PACKET_SIZE - 2]; - - args[0] = row; - args[1] = 0xFF; - args[2] = 0xFF; - args[3] = 0xFF; - - int bytes_read = ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_LED_IDX, args, sizeof(args), response, sizeof(response)); - - std::vector result; - - if(bytes_read > 0) - { - for(int i = 0; i < bytes_read; i++) - { - result.push_back(response[i] == 0xFF ? -1 : response[i]); - } - } - - return result; -} - -std::vector> QMKKeychronController::GetAllLedNumbers(unsigned char num_rows) -{ - std::vector> all_rows; - - for(unsigned char row = 0; row < num_rows; row++) - { - std::vector row_data = GetLedNumbersByRow(row); - all_rows.push_back(row_data); - } - - return all_rows; -} - -void QMKKeychronController::SetPerKeyRgbColor(unsigned char start, unsigned char count, const std::vector& hsv_data) +void QMKKeychronController::CmdSendLEDs(unsigned char start_index, unsigned char number_leds, RGBColor* color_data) { unsigned char args[KEYCHRON_QHE_PACKET_SIZE - 2]; - args[0] = start; - args[1] = count; + args[0] = start_index; + args[1] = number_leds; - size_t data_offset = 2; - size_t copy_len = hsv_data.size(); - - if(data_offset + copy_len > (KEYCHRON_QHE_PACKET_SIZE - 2)) + if(number_leds > 9) { - copy_len = (KEYCHRON_QHE_PACKET_SIZE - 2) - data_offset; + number_leds = 9; } - memcpy(&args[data_offset], hsv_data.data(), copy_len); + for(unsigned char led_index = 0; led_index < number_leds; led_index++) + { + /*-------------------------------------------------*\ + | VialRGB sends direct packets in HSV for some | + | inexplicable reason, so do the RGB to HSV | + | conversion before sending | + \*-------------------------------------------------*/ + hsv_t hsv_color; + rgb2hsv(color_data[led_index], &hsv_color); + + args[2 + (led_index * 3)] = (unsigned char)((float)hsv_color.hue * (256.0f / 360.0f)); + args[3 + (led_index * 3)] = hsv_color.saturation; + args[4 + (led_index * 3)] = hsv_color.value; + } ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_PER_KEY_SET_COLOR, args, sizeof(args), NULL, 0); } -std::vector QMKKeychronController::GetPerKeyRgbColor(unsigned char start, unsigned char count) -{ - unsigned char args[2]; - unsigned char response[KEYCHRON_QHE_PACKET_SIZE - 2]; - - args[0] = start; - args[1] = count; - - int bytes_read = ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_PER_KEY_GET_COLOR, args, sizeof(args), response, sizeof(response)); - - std::vector result; - size_t color_bytes = (size_t)count * 3; - - if(color_bytes <= (size_t)bytes_read) - { - for(size_t i = 0; i < color_bytes; i++) - { - result.push_back(response[i]); - } - } - - return result; -} - -void QMKKeychronController::SetPerKeyRgbType(unsigned char type) -{ - unsigned char args[1]; - - args[0] = type; - - ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_PER_KEY_SET_TYPE, args, sizeof(args), NULL, 0); -} - -void QMKKeychronController::SetRgbMatrixMode(unsigned char mode) -{ - unsigned char args[3]; - - args[0] = QMK_VIA_RGB_MATRIX_CHANNEL; - args[1] = QMK_VIA_RGB_MATRIX_EFFECT; - args[2] = mode; - - ViaSendCommand(QMK_VIA_CMD_CUSTOM_SET_VALUE, args, sizeof(args), NULL, 0); -} - -unsigned char QMKKeychronController::GetRgbMatrixMode() -{ - unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE]; - memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE); - - cmd[0] = QMK_VIA_CMD_CUSTOM_GET_VALUE; - cmd[1] = QMK_VIA_RGB_MATRIX_CHANNEL; - cmd[2] = QMK_VIA_RGB_MATRIX_EFFECT; - - SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE); - - unsigned char response[KEYCHRON_QHE_PACKET_SIZE]; - memset(response, 0x00, KEYCHRON_QHE_PACKET_SIZE); - - int bytes_read = ReadPacket(response, KEYCHRON_QHE_PACKET_SIZE); - - if(bytes_read > 3) - { - return response[3]; - } - - return 0; -} - -void QMKKeychronController::SetBrightness(unsigned char brightness) +void QMKKeychronController::CmdSetBrightness(unsigned char brightness) { unsigned char args[3]; @@ -296,66 +343,7 @@ void QMKKeychronController::SetBrightness(unsigned char brightness) ViaSendCommand(QMK_VIA_CMD_CUSTOM_SET_VALUE, args, sizeof(args), NULL, 0); } -unsigned char QMKKeychronController::GetBrightness() -{ - unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE]; - memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE); - - cmd[0] = QMK_VIA_CMD_CUSTOM_GET_VALUE; - cmd[1] = QMK_VIA_RGB_MATRIX_CHANNEL; - cmd[2] = QMK_VIA_RGB_MATRIX_BRIGHTNESS; - - SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE); - - unsigned char response[KEYCHRON_QHE_PACKET_SIZE]; - memset(response, 0x00, KEYCHRON_QHE_PACKET_SIZE); - - int bytes_read = ReadPacket(response, KEYCHRON_QHE_PACKET_SIZE); - - if(bytes_read > 3) - { - return response[3]; - } - - return 0; -} - -void QMKKeychronController::SetSpeed(unsigned char speed) -{ - unsigned char args[3]; - - args[0] = QMK_VIA_RGB_MATRIX_CHANNEL; - args[1] = QMK_VIA_RGB_MATRIX_EFFECT_SPEED; - args[2] = speed; - - ViaSendCommand(QMK_VIA_CMD_CUSTOM_SET_VALUE, args, sizeof(args), NULL, 0); -} - -unsigned char QMKKeychronController::GetSpeed() -{ - unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE]; - memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE); - - cmd[0] = QMK_VIA_CMD_CUSTOM_GET_VALUE; - cmd[1] = QMK_VIA_RGB_MATRIX_CHANNEL; - cmd[2] = QMK_VIA_RGB_MATRIX_EFFECT_SPEED; - - SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE); - - unsigned char response[KEYCHRON_QHE_PACKET_SIZE]; - memset(response, 0x00, KEYCHRON_QHE_PACKET_SIZE); - - int bytes_read = ReadPacket(response, KEYCHRON_QHE_PACKET_SIZE); - - if(bytes_read > 3) - { - return response[3]; - } - - return 0; -} - -void QMKKeychronController::SetColorHSV(unsigned char h, unsigned char s) +void QMKKeychronController::CmdSetColorHS(unsigned char h, unsigned char s) { unsigned char args[4]; @@ -367,49 +355,35 @@ void QMKKeychronController::SetColorHSV(unsigned char h, unsigned char s) ViaSendCommand(QMK_VIA_CMD_CUSTOM_SET_VALUE, args, sizeof(args), NULL, 0); } -void QMKKeychronController::SaveLedConf() +void QMKKeychronController::CmdSetPerKeyRGBType(unsigned char type) { - ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_SAVE, NULL, 0, NULL, 0); + unsigned char args[1]; + + args[0] = type; + + ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_PER_KEY_SET_TYPE, args, sizeof(args), NULL, 0); } -void QMKKeychronController::SendPacket(unsigned char* data, size_t len) +void QMKKeychronController::CmdSetRGBMatrixMode(unsigned char mode) { - unsigned char usb_buf[KEYCHRON_QHE_PACKET_SIZE + 1]; - memset(usb_buf, 0x00, sizeof(usb_buf)); + unsigned char args[3]; - usb_buf[0] = 0x00; + args[0] = QMK_VIA_RGB_MATRIX_CHANNEL; + args[1] = QMK_VIA_RGB_MATRIX_EFFECT; + args[2] = mode; - size_t copy_len = len; - if(copy_len > KEYCHRON_QHE_PACKET_SIZE) - { - copy_len = KEYCHRON_QHE_PACKET_SIZE; - } - - memcpy(&usb_buf[1], data, copy_len); - - hid_write(dev, usb_buf, KEYCHRON_QHE_PACKET_SIZE + 1); - - std::this_thread::sleep_for(5ms); + ViaSendCommand(QMK_VIA_CMD_CUSTOM_SET_VALUE, args, sizeof(args), NULL, 0); } -int QMKKeychronController::ReadPacket(unsigned char* buf, size_t buf_len) +void QMKKeychronController::CmdSetSpeed(unsigned char speed) { - unsigned char usb_buf[KEYCHRON_QHE_PACKET_SIZE + 1]; - memset(usb_buf, 0x00, sizeof(usb_buf)); + unsigned char args[3]; - int bytes_read = hid_read_timeout(dev, usb_buf, KEYCHRON_QHE_PACKET_SIZE + 1, KEYCHRON_QHE_HID_READ_TIMEOUT); + args[0] = QMK_VIA_RGB_MATRIX_CHANNEL; + args[1] = QMK_VIA_RGB_MATRIX_EFFECT_SPEED; + args[2] = speed; - if(bytes_read > 0) - { - size_t copy_len = (size_t)bytes_read; - if(copy_len > buf_len) - { - copy_len = buf_len; - } - memcpy(buf, usb_buf, copy_len); - } - - return bytes_read; + ViaSendCommand(QMK_VIA_CMD_CUSTOM_SET_VALUE, args, sizeof(args), NULL, 0); } int QMKKeychronController::ViaSendCommand diff --git a/Controllers/QMKController/QMKKeychronController/QMKKeychronController.h b/Controllers/QMKController/QMKKeychronController/QMKKeychronController.h index ef77ac465..1b5d62173 100644 --- a/Controllers/QMKController/QMKKeychronController/QMKKeychronController.h +++ b/Controllers/QMKController/QMKKeychronController/QMKKeychronController.h @@ -2,7 +2,9 @@ | QMKKeychronController.h | | | | Driver for Keychron QMK-based keyboards | -| (Q1 HE and other KEYCHRON_RGB-enabled models) | +| | +| Amadej Kastelic 21 Jun 2026 | +| Adam Honse 22 Jun 2026 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | @@ -115,6 +117,13 @@ enum KeychronVIABacklightValueID #define KEYCHRON_QHE_MIN_SPEED 0x00 #define KEYCHRON_QHE_MAX_SPEED 0xFF +typedef struct +{ + bool valid; + unsigned char row; + unsigned char col; +} kc_led_info; + class QMKKeychronController { public: @@ -129,49 +138,40 @@ public: bool GetSupported(); - unsigned int GetLedCount(); - std::vector GetLedNumbersByRow(unsigned char row); - std::vector> GetAllLedNumbers(unsigned char num_rows); + unsigned short GetKeycode(unsigned short led_index); + unsigned short GetLEDCount(); + kc_led_info GetLEDInfo(unsigned short led_index); - void SetPerKeyRgbColor(unsigned char start, unsigned char count, const std::vector& hsv_data); - std::vector GetPerKeyRgbColor(unsigned char start, unsigned char count); + void SendLEDs(unsigned short number_leds, RGBColor* color_data); + void SetMode(unsigned short mode, unsigned char speed, unsigned char hue, unsigned char sat, unsigned char val); - void SetRgbMatrixMode(unsigned char mode); - unsigned char GetRgbMatrixMode(); - - void SetPerKeyRgbType(unsigned char type); - - void SetBrightness(unsigned char brightness); - unsigned char GetBrightness(); - - void SetSpeed(unsigned char speed); - unsigned char GetSpeed(); - - void SetColorHSV(unsigned char h, unsigned char s); - void SaveLedConf(); + void SaveMode(); private: hid_device* dev; unsigned char kc_protocol_version; + std::vector keycodes; + std::vector led_info; std::string location; std::string name; + unsigned short number_leds; std::string serial; bool supported; std::string vendor; unsigned short via_protocol_version; - void CmdGetKeychronProtocolVersion - ( - unsigned char* kc_protocol_version - ); - - void CmdGetViaProtocolVersion - ( - unsigned short* via_protocol_version - ); - - void SendPacket(unsigned char* data, size_t len); - int ReadPacket(unsigned char* buf, size_t buf_len); + unsigned short CmdGetKeycode(unsigned char layer, unsigned char row, unsigned char col); + void CmdGetKeychronProtocolVersion(unsigned char* kc_protocol_version); + std::vector CmdGetLEDIndexByRow(unsigned char row); + void CmdGetNumberLEDs(unsigned short* number_leds); + void CmdGetViaProtocolVersion(unsigned short* via_protocol_version); + void CmdSaveMode(); + void CmdSendLEDs(unsigned char start_index, unsigned char number_leds, RGBColor* color_data); + void CmdSetBrightness(unsigned char brightness); + void CmdSetColorHS(unsigned char h, unsigned char s); + void CmdSetPerKeyRGBType(unsigned char type); + void CmdSetRGBMatrixMode(unsigned char mode); + void CmdSetSpeed(unsigned char speed); int ViaSendCommand ( diff --git a/Controllers/QMKController/QMKKeychronController/QMKKeychronControllerDetect.cpp b/Controllers/QMKController/QMKKeychronController/QMKKeychronControllerDetect.cpp index 3d8de2209..b451a9209 100644 --- a/Controllers/QMKController/QMKKeychronController/QMKKeychronControllerDetect.cpp +++ b/Controllers/QMKController/QMKKeychronController/QMKKeychronControllerDetect.cpp @@ -2,7 +2,9 @@ | QMKKeychronControllerDetect.cpp | | | | Detector for Keychron QMK-based keyboards | -| (Q1 HE and other KEYCHRON_RGB-enabled models) | +| | +| Amadej Kastelic 21 Jun 2026 | +| Adam Honse 22 Jun 2026 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | diff --git a/Controllers/QMKController/QMKKeychronController/RGBController_QMKKeychron.cpp b/Controllers/QMKController/QMKKeychronController/RGBController_QMKKeychron.cpp index b583e1fe9..8db7e4e39 100644 --- a/Controllers/QMKController/QMKKeychronController/RGBController_QMKKeychron.cpp +++ b/Controllers/QMKController/QMKKeychronController/RGBController_QMKKeychron.cpp @@ -2,24 +2,22 @@ | RGBController_QMKKeychron.cpp | | | | RGBController for Keychron QMK-based keyboards | -| (Q1 HE and other KEYCHRON_RGB-enabled models) | +| | +| Amadej Kastelic 21 Jun 2026 | +| Adam Honse 22 Jun 2026 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | \*---------------------------------------------------------*/ -#include #include -#include -#include -#include "RGBControllerKeyNames.h" +#include "hsv.h" #include "RGBController_QMKKeychron.h" +#include "QMKKeycodes.h" #include "QMKKeychronController.h" -#define NA 0xFFFFFFFF - /**------------------------------------------------------------------*\ - @name Keychron Q1 HE + @name QMK Keychron @category Keyboard @type USB @save :white_check_mark: @@ -34,33 +32,33 @@ typedef struct std::string name; int value; int flags; -} keychron_qhe_effect; +} kc_effect; -static const keychron_qhe_effect qhe_effects[] = +static const kc_effect kc_effects[] = { - { "Direct", -1, MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS }, - { "Solid Color", 0, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE }, - { "Breathing", 1, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Band Spiral", 2, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Cycle All", 3, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Cycle Left Right", 4, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Cycle Up Down", 5, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Cycle Out In", 6, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Cycle Out In Dual", 7, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Cycle Pinwheel", 8, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Cycle Spiral", 9, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Dual Beacon", 10, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Rainbow Beacon", 11, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Rainbow Moving Chevron", 12, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Jellybean Raindrops", 13, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE }, - { "Pixel Rain", 14, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE }, - { "Typing Heatmap", 15, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE }, - { "Digital Rain", 16, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Solid Reactive Simple", 17, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Solid Reactive Multiwide", 18, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Solid Reactive Multinexus", 19, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Splash", 20, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, - { "Solid Splash", 21, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE }, + { "Direct", 0xFFFF, MODE_FLAG_HAS_PER_LED_COLOR }, + { "Solid Color", 1, MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_MANUAL_SAVE }, + { "Breathing", 2, MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE }, + { "Band Spiral", 3, MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE }, + { "Cycle All", 4, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Cycle Left Right", 5, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Cycle Up Down", 6, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Rainbow Moving Chevron", 7, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Cycle Out In", 8, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Cycle Out In Dual", 9, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Cycle Pinwheel", 10, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Cycle Spiral", 11, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Dual Beacon", 12, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Rainbow Beacon", 13, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Jellybean Raindrops", 14, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Pixel Rain", 15, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Typing Heatmap", 16, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Digital Rain", 17, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE }, + { "Solid Reactive Simple", 18, MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE }, + { "Solid Reactive Multiwide", 19, MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE }, + { "Solid Reactive Multinexus", 20, MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE }, + { "Splash", 21, MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE }, + { "Solid Splash", 22, MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE }, }; RGBController_QMKKeychron::RGBController_QMKKeychron(QMKKeychronController* controller_ptr) @@ -75,7 +73,7 @@ RGBController_QMKKeychron::RGBController_QMKKeychron(QMKKeychronController* cont serial = controller->GetSerial(); version = controller->GetVersion(); - for(const keychron_qhe_effect& effect : qhe_effects) + for(const kc_effect& effect : kc_effects) { mode m; m.name = effect.name; @@ -131,54 +129,68 @@ RGBController_QMKKeychron::~RGBController_QMKKeychron() void RGBController_QMKKeychron::SetupZones() { - unsigned int led_count = controller->GetLedCount(); + /*-----------------------------------------------------*\ + | Build matrix map | + \*-----------------------------------------------------*/ + unsigned char max_col = 0; + unsigned char max_row = 0; - zone keyboard_zone; - keyboard_zone.name = ZONE_EN_KEYBOARD; - keyboard_zone.type = ZONE_TYPE_MATRIX; - - if(led_count > 0) + for(unsigned short led_index = 0; led_index < controller->GetLEDCount(); led_index++) { - keyboard_zone.matrix_map = new matrix_map_type; - keyboard_zone.matrix_map->height = 6; - keyboard_zone.matrix_map->width = 16; + kc_led_info info = controller->GetLEDInfo(led_index); - keyboard_zone.matrix_map->map = new unsigned int[6 * 16]; - memset(keyboard_zone.matrix_map->map, 0xFF, 6 * 16 * sizeof(unsigned int)); - - std::vector> led_map = controller->GetAllLedNumbers(6); - - unsigned int led_idx = 0; - for(unsigned int h = 0; h < 6 && h < led_map.size(); h++) + if(info.col > max_col) { - for(unsigned int w = 0; w < 16 && w < led_map[h].size(); w++) - { - int val = led_map[h][w]; - keyboard_zone.matrix_map->map[h * 16 + w] = (val >= 0) ? (unsigned int)val : NA; - - if(val >= 0) - { - led new_led; - new_led.name = "Key: " + std::to_string(val); - leds.push_back(new_led); - led_idx++; - } - } + max_col = info.col; } - keyboard_zone.leds_min = led_idx; - keyboard_zone.leds_max = led_idx; - keyboard_zone.leds_count = led_idx; - } - else - { - keyboard_zone.matrix_map = nullptr; - keyboard_zone.leds_min = 0; - keyboard_zone.leds_max = 0; - keyboard_zone.leds_count = 0; + if(info.row > max_row) + { + max_row = info.row; + } } - zones.push_back(keyboard_zone); + unsigned char height = max_row + 1; + unsigned char width = max_col + 1; + + unsigned int* matrix_map = new unsigned int[width * height]; + + memset(matrix_map, 0xFF, (sizeof(unsigned int) * (width * height))); + + for(unsigned short led_index = 0; led_index < controller->GetLEDCount(); led_index++) + { + kc_led_info info = controller->GetLEDInfo(led_index); + + matrix_map[(width * info.row) + info.col] = (unsigned int)led_index; + } + + /*-----------------------------------------------------*\ + | Create keyboard zone | + \*-----------------------------------------------------*/ + zone keyboard; + + keyboard.name = "Keyboard"; + keyboard.type = ZONE_TYPE_MATRIX; + keyboard.leds_min = controller->GetLEDCount(); + keyboard.leds_max = controller->GetLEDCount(); + keyboard.leds_count = controller->GetLEDCount(); + keyboard.matrix_map = new matrix_map_type; + keyboard.matrix_map->height = height; + keyboard.matrix_map->width = width; + keyboard.matrix_map->map = matrix_map; + + zones.push_back(keyboard); + + /*-----------------------------------------------------*\ + | Create keyboard LEDs | + \*-----------------------------------------------------*/ + for(unsigned short led_idx = 0; led_idx < controller->GetLEDCount(); led_idx++) + { + led new_led; + new_led.name = qmk_keynames[controller->GetKeycode(led_idx)]; + + leds.push_back(new_led); + } SetupColors(); } @@ -189,116 +201,39 @@ void RGBController_QMKKeychron::ResizeZone(int /*zone*/, int /*new_size*/) void RGBController_QMKKeychron::DeviceUpdateLEDs() { - UpdateZoneLEDs(0); + controller->SendLEDs((unsigned short)colors.size(), colors.data()); } void RGBController_QMKKeychron::UpdateZoneLEDs(int /*zone*/) { - if(active_mode == 0) - { - controller->SetRgbMatrixMode(KEYCHRON_QHE_PER_KEY_RGB_EFFECT); - controller->SetPerKeyRgbType(KEYCHRON_PER_KEY_RGB_SOLID); - - unsigned int total_leds = leds.size(); - unsigned char max_per_packet = 9; - - for(unsigned char start = 0; start < total_leds; start += max_per_packet) - { - unsigned char count = (unsigned char)((total_leds - start) > max_per_packet ? max_per_packet : (total_leds - start)); - - std::vector hsv_data; - hsv_data.reserve(count * 3); - - for(unsigned char i = 0; i < count; i++) - { - RGBColor color = colors[start + i]; - unsigned char r = RGBGetRValue(color); - unsigned char g = RGBGetGValue(color); - unsigned char b = RGBGetBValue(color); - unsigned char h, s, v; - - RGBToHSV(r, g, b, h, s, v); - - hsv_data.push_back(h); - hsv_data.push_back(s); - hsv_data.push_back(v); - } - - controller->SetPerKeyRgbColor(start, count, hsv_data); - } - - controller->SetBrightness(modes[active_mode].brightness); - } - else - { - int effect_id = modes[active_mode].value; - - controller->SetRgbMatrixMode((unsigned char)effect_id); - controller->SetBrightness(modes[active_mode].brightness); - controller->SetSpeed(modes[active_mode].speed); - - if(modes[active_mode].flags & MODE_FLAG_HAS_MODE_SPECIFIC_COLOR) - { - RGBColor color = modes[active_mode].colors[0]; - unsigned char r = RGBGetRValue(color); - unsigned char g = RGBGetGValue(color); - unsigned char b = RGBGetBValue(color); - unsigned char h, s, v; - - RGBToHSV(r, g, b, h, s, v); - - controller->SetColorHSV(h, s); - } - - controller->SaveLedConf(); - } + DeviceUpdateLEDs(); } void RGBController_QMKKeychron::UpdateSingleLED(int /*led*/) { - UpdateZoneLEDs(0); + DeviceUpdateLEDs(); } void RGBController_QMKKeychron::DeviceUpdateMode() { - UpdateZoneLEDs(0); + unsigned char hue = 0; + unsigned char sat = 255; + unsigned char val = modes[active_mode].brightness; + + if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC) + { + hsv_t hsv_color; + rgb2hsv(modes[active_mode].colors[0], &hsv_color); + + hue = (unsigned char)((float)hsv_color.hue * (256.0f / 360.0f)); + sat = hsv_color.saturation; + val = hsv_color.value; + } + + controller->SetMode(modes[active_mode].value, modes[active_mode].speed, hue, sat, val); } -void RGBController_QMKKeychron::RGBToHSV(unsigned char r, unsigned char g, unsigned char b, unsigned char& h, unsigned char& s, unsigned char& v) +void RGBController_QMKKeychron::DeviceSaveMode() { - double rd = r / 255.0; - double gd = g / 255.0; - double bd = b / 255.0; - - double max_val = rd > gd ? (rd > bd ? rd : bd) : (gd > bd ? gd : bd); - double min_val = rd < gd ? (rd < bd ? rd : bd) : (gd < bd ? gd : bd); - double delta = max_val - min_val; - - v = (unsigned char)(max_val * 255.0); - - if(delta < 0.00001) - { - h = 0; - s = 0; - return; - } - - s = (unsigned char)((max_val > 0.0) ? (delta / max_val) * 255.0 : 0.0); - - double hd; - if(max_val == rd) - { - hd = (gd - bd) / delta; - if(gd < bd) hd += 6.0; - } - else if(max_val == gd) - { - hd = (bd - rd) / delta + 2.0; - } - else - { - hd = (rd - gd) / delta + 4.0; - } - - h = (unsigned char)((hd / 6.0) * 255.0); + controller->SaveMode(); } diff --git a/Controllers/QMKController/QMKKeychronController/RGBController_QMKKeychron.h b/Controllers/QMKController/QMKKeychronController/RGBController_QMKKeychron.h index 2b955a7db..3e8f1dc02 100644 --- a/Controllers/QMKController/QMKKeychronController/RGBController_QMKKeychron.h +++ b/Controllers/QMKController/QMKKeychronController/RGBController_QMKKeychron.h @@ -2,7 +2,9 @@ | RGBController_QMKKeychron.h | | | | RGBController for Keychron QMK-based keyboards | -| (Q1 HE and other KEYCHRON_RGB-enabled models) | +| | +| Amadej Kastelic 21 Jun 2026 | +| Adam Honse 22 Jun 2026 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | @@ -27,9 +29,8 @@ public: void UpdateSingleLED(int led); void DeviceUpdateMode(); + void DeviceSaveMode(); private: QMKKeychronController* controller; - - static void RGBToHSV(unsigned char r, unsigned char g, unsigned char b, unsigned char& h, unsigned char& s, unsigned char& v); };