feat: add Keychron Q1 HE keyboard support and restructure Keychron controllers

This commit is contained in:
Amadej Kastelic
2026-06-22 00:07:08 +00:00
committed by Adam Honse
parent c2cc7c6163
commit 175f3ed338
5 changed files with 909 additions and 0 deletions

View File

@@ -0,0 +1,375 @@
/*---------------------------------------------------------*\
| QMKKeychronController.cpp |
| |
| Driver for Keychron QMK-based keyboards |
| (Q1 HE and other KEYCHRON_RGB-enabled models) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <string.h>
#include "QMKKeychronController.h"
#include "StringUtils.h"
#include "LogManager.h"
using namespace std::chrono_literals;
QMKKeychronController::QMKKeychronController(hid_device* dev_handle, const hid_device_info& info, std::string dev_name)
{
dev = dev_handle;
location = info.path;
name = dev_name;
}
QMKKeychronController::~QMKKeychronController()
{
hid_close(dev);
}
std::string QMKKeychronController::GetDeviceLocation()
{
return("HID: " + location);
}
std::string QMKKeychronController::GetNameString()
{
return(name);
}
std::string QMKKeychronController::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void QMKKeychronController::SendPacket(unsigned char* data, size_t len)
{
unsigned char usb_buf[KEYCHRON_QHE_PACKET_SIZE + 1];
memset(usb_buf, 0x00, sizeof(usb_buf));
usb_buf[0] = 0x00;
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);
}
int QMKKeychronController::ReadPacket(unsigned char* buf, size_t buf_len)
{
unsigned char usb_buf[KEYCHRON_QHE_PACKET_SIZE + 1];
memset(usb_buf, 0x00, sizeof(usb_buf));
int bytes_read = hid_read_timeout(dev, usb_buf, KEYCHRON_QHE_PACKET_SIZE + 1, KEYCHRON_QHE_HID_READ_TIMEOUT);
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;
}
unsigned int QMKKeychronController::GetLedCount()
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_KC_RGB_CMD_GROUP;
cmd[1] = KEYCHRON_KC_RGB_LED_COUNT;
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 && response[0] == KEYCHRON_KC_RGB_CMD_GROUP && response[1] == KEYCHRON_KC_RGB_LED_COUNT)
{
return response[3];
}
return 0;
}
std::vector<int> QMKKeychronController::GetLedNumbersByRow(unsigned char row)
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_KC_RGB_CMD_GROUP;
cmd[1] = KEYCHRON_KC_RGB_LED_IDX;
cmd[2] = row;
cmd[3] = 0xFF;
cmd[4] = 0xFF;
cmd[5] = 0xFF;
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);
std::vector<int> result;
if(bytes_read > 3 && response[0] == KEYCHRON_KC_RGB_CMD_GROUP && response[1] == KEYCHRON_KC_RGB_LED_IDX)
{
for(int i = 3; i < bytes_read; i++)
{
result.push_back(response[i] == 0xFF ? -1 : response[i]);
}
}
return result;
}
std::vector<std::vector<int>> QMKKeychronController::GetAllLedNumbers(unsigned char num_rows)
{
std::vector<std::vector<int>> all_rows;
for(unsigned char row = 0; row < num_rows; row++)
{
std::vector<int> 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<unsigned char>& hsv_data)
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_KC_RGB_CMD_GROUP;
cmd[1] = KEYCHRON_KC_RGB_PER_KEY_SET_COLOR;
cmd[2] = start;
cmd[3] = count;
size_t data_offset = 4;
size_t copy_len = hsv_data.size();
if(data_offset + copy_len > KEYCHRON_QHE_PACKET_SIZE)
{
copy_len = KEYCHRON_QHE_PACKET_SIZE - data_offset;
}
memcpy(&cmd[data_offset], hsv_data.data(), copy_len);
SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE);
unsigned char response[KEYCHRON_QHE_PACKET_SIZE];
ReadPacket(response, KEYCHRON_QHE_PACKET_SIZE);
}
std::vector<unsigned char> QMKKeychronController::GetPerKeyRgbColor(unsigned char start, unsigned char count)
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_KC_RGB_CMD_GROUP;
cmd[1] = KEYCHRON_KC_RGB_PER_KEY_GET_COLOR;
cmd[2] = start;
cmd[3] = count;
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);
std::vector<unsigned char> result;
if(bytes_read > 3 && response[0] == KEYCHRON_KC_RGB_CMD_GROUP && response[1] == KEYCHRON_KC_RGB_PER_KEY_GET_COLOR)
{
size_t color_bytes = (size_t)count * 3;
if(3 + color_bytes <= (size_t)bytes_read)
{
for(size_t i = 3; i < 3 + color_bytes; i++)
{
result.push_back(response[i]);
}
}
}
return result;
}
void QMKKeychronController::SetPerKeyRgbType(unsigned char type)
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_KC_RGB_CMD_GROUP;
cmd[1] = KEYCHRON_KC_RGB_PER_KEY_SET_TYPE;
cmd[2] = type;
SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE);
unsigned char response[KEYCHRON_QHE_PACKET_SIZE];
ReadPacket(response, KEYCHRON_QHE_PACKET_SIZE);
}
void QMKKeychronController::SetRgbMatrixMode(unsigned char mode)
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_VIA_BACKLIGHT_SET_VALUE;
cmd[1] = KEYCHRON_VIA_BACKLIGHT_TYPE_RGB_MATRIX;
cmd[2] = KEYCHRON_VIA_BACKLIGHT_EFFECT;
cmd[3] = mode;
SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE);
unsigned char response[KEYCHRON_QHE_PACKET_SIZE];
ReadPacket(response, KEYCHRON_QHE_PACKET_SIZE);
}
unsigned char QMKKeychronController::GetRgbMatrixMode()
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_VIA_BACKLIGHT_GET_VALUE;
cmd[1] = KEYCHRON_VIA_BACKLIGHT_TYPE_RGB_MATRIX;
cmd[2] = KEYCHRON_VIA_BACKLIGHT_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)
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_VIA_BACKLIGHT_SET_VALUE;
cmd[1] = KEYCHRON_VIA_BACKLIGHT_TYPE_RGB_MATRIX;
cmd[2] = KEYCHRON_VIA_BACKLIGHT_BRIGHTNESS;
cmd[3] = brightness;
SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE);
}
unsigned char QMKKeychronController::GetBrightness()
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_VIA_BACKLIGHT_GET_VALUE;
cmd[1] = KEYCHRON_VIA_BACKLIGHT_TYPE_RGB_MATRIX;
cmd[2] = KEYCHRON_VIA_BACKLIGHT_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 cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_VIA_BACKLIGHT_SET_VALUE;
cmd[1] = KEYCHRON_VIA_BACKLIGHT_TYPE_RGB_MATRIX;
cmd[2] = KEYCHRON_VIA_BACKLIGHT_SPEED;
cmd[3] = speed;
SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE);
}
unsigned char QMKKeychronController::GetSpeed()
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_VIA_BACKLIGHT_GET_VALUE;
cmd[1] = KEYCHRON_VIA_BACKLIGHT_TYPE_RGB_MATRIX;
cmd[2] = KEYCHRON_VIA_BACKLIGHT_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)
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_VIA_BACKLIGHT_SET_VALUE;
cmd[1] = KEYCHRON_VIA_BACKLIGHT_TYPE_RGB_MATRIX;
cmd[2] = KEYCHRON_VIA_BACKLIGHT_COLOR;
cmd[3] = h;
cmd[4] = s;
SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE);
}
void QMKKeychronController::SaveLedConf()
{
unsigned char cmd[KEYCHRON_QHE_PACKET_SIZE];
memset(cmd, 0x00, KEYCHRON_QHE_PACKET_SIZE);
cmd[0] = KEYCHRON_KC_RGB_CMD_GROUP;
cmd[1] = KEYCHRON_KC_RGB_SAVE;
SendPacket(cmd, KEYCHRON_QHE_PACKET_SIZE);
unsigned char response[KEYCHRON_QHE_PACKET_SIZE];
ReadPacket(response, KEYCHRON_QHE_PACKET_SIZE);
}

View File

@@ -0,0 +1,162 @@
/*---------------------------------------------------------*\
| QMKKeychronController.h |
| |
| Driver for Keychron QMK-based keyboards |
| (Q1 HE and other KEYCHRON_RGB-enabled models) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include <string>
#include <hidapi.h>
/*---------------------------------------------------------*\
| Keychron vendor ID |
\*---------------------------------------------------------*/
#define KEYCHRON_VID 0x3434
/*---------------------------------------------------------*\
| Product IDs |
\*---------------------------------------------------------*/
#define KEYCHRON_Q1_HE_PID 0x0B10
/*---------------------------------------------------------*\
| QMK raw HID usage page/usage |
\*---------------------------------------------------------*/
#define KEYCHRON_QMK_USAGE_PAGE 0xFF60
#define KEYCHRON_QMK_USAGE 0x61
/*---------------------------------------------------------*\
| HID packet constants |
\*---------------------------------------------------------*/
#define KEYCHRON_QHE_PACKET_SIZE 32
#define KEYCHRON_QHE_HID_READ_TIMEOUT 1000
/*---------------------------------------------------------*\
| KC_RGB command group (0xA8) |
| |
| Keychron-specific per-key RGB protocol. |
| Matches the firmware enum in |
| keyboards/keychron/common/rgb/keychron_rgb.c |
\*---------------------------------------------------------*/
#define KEYCHRON_KC_RGB_CMD_GROUP 0xA8
enum KeychronKCRGBCommand
{
KEYCHRON_KC_RGB_PROTOCOL_VER = 0x01,
KEYCHRON_KC_RGB_SAVE = 0x02,
KEYCHRON_KC_RGB_GET_INDICATORS = 0x03,
KEYCHRON_KC_RGB_SET_INDICATORS = 0x04,
KEYCHRON_KC_RGB_LED_COUNT = 0x05,
KEYCHRON_KC_RGB_LED_IDX = 0x06,
KEYCHRON_KC_RGB_PER_KEY_GET_TYPE = 0x07,
KEYCHRON_KC_RGB_PER_KEY_SET_TYPE = 0x08,
KEYCHRON_KC_RGB_PER_KEY_GET_COLOR = 0x09,
KEYCHRON_KC_RGB_PER_KEY_SET_COLOR = 0x0A,
KEYCHRON_KC_RGB_MIXED_GET_INFO = 0x0B,
KEYCHRON_KC_RGB_MIXED_GET_REGIONS = 0x0C,
KEYCHRON_KC_RGB_MIXED_SET_REGIONS = 0x0D,
KEYCHRON_KC_RGB_MIXED_GET_EFFECTS = 0x0E,
KEYCHRON_KC_RGB_MIXED_SET_EFFECTS = 0x0F,
};
/*---------------------------------------------------------*\
| Per-key RGB animation types (PER_KEY_RGB_SET_TYPE value) |
\*---------------------------------------------------------*/
enum KeychronPerKeyRgbType
{
KEYCHRON_PER_KEY_RGB_SOLID = 0,
KEYCHRON_PER_KEY_RGB_BREATHING = 1,
KEYCHRON_PER_KEY_RGB_REACTIVE_SIMPLE = 2,
KEYCHRON_PER_KEY_RGB_REACTIVE_WIDE = 3,
KEYCHRON_PER_KEY_RGB_REACTIVE_SPLASH = 4,
};
/*---------------------------------------------------------*\
| VIA backlight config commands |
| |
| Standard QMK/VIA rgb_matrix control. |
| Format: [cmd, value_type, value_id, value] |
| value_type = 3 for rgb_matrix |
\*---------------------------------------------------------*/
enum KeychronVIABacklightCommand
{
KEYCHRON_VIA_BACKLIGHT_SET_VALUE = 0x07,
KEYCHRON_VIA_BACKLIGHT_GET_VALUE = 0x08,
KEYCHRON_VIA_BACKLIGHT_SAVE = 0x09,
};
/*---------------------------------------------------------*\
| VIA backlight value IDs |
\*---------------------------------------------------------*/
#define KEYCHRON_VIA_BACKLIGHT_TYPE_RGB_MATRIX 0x03
enum KeychronVIABacklightValueID
{
KEYCHRON_VIA_BACKLIGHT_BRIGHTNESS = 0x01,
KEYCHRON_VIA_BACKLIGHT_EFFECT = 0x02,
KEYCHRON_VIA_BACKLIGHT_SPEED = 0x03,
KEYCHRON_VIA_BACKLIGHT_COLOR = 0x04,
};
/*---------------------------------------------------------*\
| Q1 HE effect IDs |
| |
| Determined by the number of standard effects enabled |
| in the Q1 HE firmware (info.json animations) + |
| 2 custom effects (PER_KEY_RGB, MIXED_RGB). |
| May need adjustment for other firmware versions. |
\*---------------------------------------------------------*/
#define KEYCHRON_QHE_PER_KEY_RGB_EFFECT 23
/*---------------------------------------------------------*\
| Brightness and speed ranges |
\*---------------------------------------------------------*/
#define KEYCHRON_QHE_MIN_BRIGHTNESS 0x00
#define KEYCHRON_QHE_MAX_BRIGHTNESS 0xFF
#define KEYCHRON_QHE_MIN_SPEED 0x00
#define KEYCHRON_QHE_MAX_SPEED 0xFF
class QMKKeychronController
{
public:
QMKKeychronController(hid_device* dev_handle, const hid_device_info& info, std::string dev_name);
~QMKKeychronController();
std::string GetDeviceLocation();
std::string GetNameString();
std::string GetSerialString();
unsigned int GetLedCount();
std::vector<int> GetLedNumbersByRow(unsigned char row);
std::vector<std::vector<int>> GetAllLedNumbers(unsigned char num_rows);
void SetPerKeyRgbColor(unsigned char start, unsigned char count, const std::vector<unsigned char>& hsv_data);
std::vector<unsigned char> GetPerKeyRgbColor(unsigned char start, unsigned char count);
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();
private:
hid_device* dev;
std::string location;
std::string name;
void SendPacket(unsigned char* data, size_t len);
int ReadPacket(unsigned char* buf, size_t buf_len);
};

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------*\
| QMKKeychronControllerDetect.cpp |
| |
| Detector for Keychron QMK-based keyboards |
| (Q1 HE and other KEYCHRON_RGB-enabled models) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "Detector.h"
#include "QMKKeychronController.h"
#include "RGBController_QMKKeychron.h"
/*---------------------------------------------------------*\
| Q1 HE detector |
\*---------------------------------------------------------*/
void DetectQMKKeychronController(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
QMKKeychronController* controller = new QMKKeychronController(dev, *info, name);
RGBController_QMKKeychron* rgb_controller = new RGBController_QMKKeychron(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
/*---------------------------------------------------------*\
| Register HID detectors |
\*---------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Keychron Q1 HE", DetectQMKKeychronController, KEYCHRON_VID, KEYCHRON_Q1_HE_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);

View File

@@ -0,0 +1,303 @@
/*---------------------------------------------------------*\
| RGBController_QMKKeychron.cpp |
| |
| RGBController for Keychron QMK-based keyboards |
| (Q1 HE and other KEYCHRON_RGB-enabled models) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <chrono>
#include <cstring>
#include <thread>
#include <cmath>
#include "RGBControllerKeyNames.h"
#include "RGBController_QMKKeychron.h"
#include "QMKKeychronController.h"
#define NA 0xFFFFFFFF
/**------------------------------------------------------------------*\
@name Keychron Q1 HE
@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;
} keychron_qhe_effect;
static const keychron_qhe_effect qhe_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 },
};
RGBController_QMKKeychron::RGBController_QMKKeychron(QMKKeychronController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Keychron";
type = DEVICE_TYPE_KEYBOARD;
description = name;
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
for(const keychron_qhe_effect& effect : qhe_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()
{
unsigned int led_count = controller->GetLedCount();
zone keyboard_zone;
keyboard_zone.name = ZONE_EN_KEYBOARD;
keyboard_zone.type = ZONE_TYPE_MATRIX;
if(led_count > 0)
{
keyboard_zone.matrix_map = new matrix_map_type;
keyboard_zone.matrix_map->height = 6;
keyboard_zone.matrix_map->width = 16;
keyboard_zone.matrix_map->map = new unsigned int[6 * 16];
memset(keyboard_zone.matrix_map->map, 0xFF, 6 * 16 * sizeof(unsigned int));
std::vector<std::vector<int>> led_map = controller->GetAllLedNumbers(6);
unsigned int led_idx = 0;
for(unsigned int h = 0; h < 6 && h < led_map.size(); h++)
{
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++;
}
}
}
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;
}
zones.push_back(keyboard_zone);
SetupColors();
}
void RGBController_QMKKeychron::ResizeZone(int /*zone*/, int /*new_size*/)
{
}
void RGBController_QMKKeychron::DeviceUpdateLEDs()
{
UpdateZoneLEDs(0);
}
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<unsigned char> 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();
}
}
void RGBController_QMKKeychron::UpdateSingleLED(int /*led*/)
{
UpdateZoneLEDs(0);
}
void RGBController_QMKKeychron::DeviceUpdateMode()
{
UpdateZoneLEDs(0);
}
void RGBController_QMKKeychron::RGBToHSV(unsigned char r, unsigned char g, unsigned char b, unsigned char& h, unsigned char& s, unsigned char& v)
{
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);
}

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_QMKKeychron.h |
| |
| RGBController for Keychron QMK-based keyboards |
| (Q1 HE and other KEYCHRON_RGB-enabled models) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "QMKKeychronController.h"
class RGBController_QMKKeychron : public RGBController
{
public:
RGBController_QMKKeychron(QMKKeychronController* controller_ptr);
~RGBController_QMKKeychron();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
QMKKeychronController* controller;
static void RGBToHSV(unsigned char r, unsigned char g, unsigned char b, unsigned char& h, unsigned char& s, unsigned char& v);
};