/*---------------------------------------------------------*\ | RGBController_QMKKeychron.cpp | | | | RGBController for Keychron QMK-based keyboards | | | | 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 "hsv.h" #include "RGBController_QMKKeychron.h" #include "QMKKeycodes.h" #include "QMKKeychronController.h" /**------------------------------------------------------------------*\ @name QMK Keychron @category Keyboard @type USB @save :white_check_mark: @direct :white_check_mark: @effects :white_check_mark: @detectors DetectQMKKeychronController @comment \*-------------------------------------------------------------------*/ typedef struct { std::string name; int value; int flags; } kc_effect; static const kc_effect kc_effects[] = { { "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) { controller = controller_ptr; name = controller->GetName(); vendor = controller->GetVendor(); type = DEVICE_TYPE_KEYBOARD; description = "QMK Keychron Device"; location = controller->GetLocation(); serial = controller->GetSerial(); version = controller->GetVersion(); for(const kc_effect& effect : kc_effects) { mode m; m.name = effect.name; m.value = effect.value; m.flags = effect.flags; if(m.flags & MODE_FLAG_HAS_MODE_SPECIFIC_COLOR) { m.color_mode = MODE_COLORS_MODE_SPECIFIC; m.colors_min = 1; m.colors_max = 1; m.colors.resize(1); } else if(m.flags & MODE_FLAG_HAS_PER_LED_COLOR) { m.color_mode = MODE_COLORS_PER_LED; m.colors_min = 0; m.colors_max = 0; m.colors.resize(0); } else { m.color_mode = MODE_COLORS_NONE; m.colors_min = 0; m.colors_max = 0; m.colors.resize(0); } if(m.flags & MODE_FLAG_HAS_SPEED) { m.speed_min = KEYCHRON_QHE_MIN_SPEED; m.speed_max = KEYCHRON_QHE_MAX_SPEED; m.speed = KEYCHRON_QHE_MAX_SPEED / 2; } if(m.flags & MODE_FLAG_HAS_BRIGHTNESS) { m.brightness_min = KEYCHRON_QHE_MIN_BRIGHTNESS; m.brightness_max = KEYCHRON_QHE_MAX_BRIGHTNESS; m.brightness = KEYCHRON_QHE_MAX_BRIGHTNESS; } modes.push_back(m); } SetupZones(); } RGBController_QMKKeychron::~RGBController_QMKKeychron() { delete controller; } void RGBController_QMKKeychron::SetupZones() { /*-----------------------------------------------------*\ | Build matrix map | \*-----------------------------------------------------*/ unsigned char max_col = 0; unsigned char max_row = 0; for(unsigned short led_index = 0; led_index < controller->GetLEDCount(); led_index++) { kc_led_info info = controller->GetLEDInfo(led_index); if(info.col > max_col) { max_col = info.col; } if(info.row > max_row) { max_row = info.row; } } 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(); } void RGBController_QMKKeychron::ResizeZone(int /*zone*/, int /*new_size*/) { } void RGBController_QMKKeychron::DeviceUpdateLEDs() { controller->SendLEDs((unsigned short)colors.size(), colors.data()); } void RGBController_QMKKeychron::UpdateZoneLEDs(int /*zone*/) { DeviceUpdateLEDs(); } void RGBController_QMKKeychron::UpdateSingleLED(int /*led*/) { DeviceUpdateLEDs(); } void RGBController_QMKKeychron::DeviceUpdateMode() { 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::DeviceSaveMode() { controller->SaveMode(); }