Compare commits

..
118 changed files with 5409 additions and 23789 deletions
+4 -4
View File
@@ -14,9 +14,9 @@
# run only if manually started to save CI minutes if #
# GitLab hosted runners #
# * For commits to the CalcProgrammer1/OpenRGB repository, #
# automatically run all jobs on the default branch or #
# tag builds, otherwise run them if the source is part #
# of a merge request #
# automatically run all jobs on the default branch, #
# otherwise run them if the source is part of a merge #
# request #
#-----------------------------------------------------------#
.downstream_rules:
rules:
@@ -26,7 +26,7 @@
.upstream_rules:
rules:
- if: '$CI_PROJECT_PATH == "CalcProgrammer1/OpenRGB" && ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG || $CI_PIPELINE_SOURCE == "merge_request_event")'
- if: '$CI_PROJECT_PATH == "CalcProgrammer1/OpenRGB" && ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event")'
when: on_success
- !reference [.downstream_rules, rules]
@@ -73,7 +73,6 @@ REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4080 Ultra W OC-V", Detec
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4090 Advanced OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4090_ADVANCED_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4090 Advanced OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4090_ADVANCED_OCV2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ultra W OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060_ULTRAW_OC, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ultra W OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060_ULTRAW_OC_2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ti Ultra W DUO OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060TI_ULTRAW_DUO_OC, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ti Ultra W DUO OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060TI_ULTRAW_DUO_OC_2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5070 Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5070_ULTRAW_OCV, 0x61);
@@ -26,7 +26,6 @@ CorsairDRAMController::CorsairDRAMController(i2c_smbus_interface *bus, corsair_d
this->bus = bus;
this->dev = dev;
device_index = 0;
direct_mode = true;
pid = 0;
vid = 0;
protocol_version = 0;
@@ -133,79 +132,13 @@ void CorsairDRAMController::SetColorsPerLED(RGBColor* colors)
| bytes, use a second block write to the second |
| block write address for the remaining data |
\*-------------------------------------------------*/
s32 ret = bus->i2c_smbus_write_block_data(dev, CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_1, 32, direct_packet);
bus->i2c_smbus_write_block_data(dev, CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_1, 32, direct_packet);
if((ret >= 0) && (direct_packet_size > 32))
if(direct_packet_size > 32)
{
bus->i2c_smbus_write_block_data(dev, CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_2, direct_packet_size - 32, direct_packet + 32);
}
/*-------------------------------------------------*\
| Corsair DRAM supports an alternate means of |
| writing block data without using SMBus block |
| operations. If block operations are not |
| available, fall back to this scheme. |
| |
| Blocks are split up into word data writes. |
| Some data bytes are packed into the lower nibble |
| of the register address byte. |
\*-------------------------------------------------*/
if(ret < 0)
{
unsigned int block_index = 0;
bool even_frame = true;
bool first_frame = true;
while(block_index < direct_packet_size)
{
unsigned char reg_value_0 = 0xA0;
unsigned char reg_value_1 = 0x00;
unsigned short word_value_0 = 0;
unsigned short word_value_1 = 0;
if(block_index == 0)
{
reg_value_0 = 0x90;
}
word_value_0 = (direct_packet[block_index]);
block_index++;
if(block_index < direct_packet_size)
{
word_value_0 |= (direct_packet[block_index] << 8);
block_index++;
}
if(block_index < direct_packet_size)
{
reg_value_1 = 0xA0;
reg_value_0 |= (direct_packet[block_index] & 0x0F);
reg_value_1 |= (direct_packet[block_index] & 0xF0) >> 4;
block_index++;
}
if(block_index < direct_packet_size)
{
word_value_1 = (direct_packet[block_index]);
block_index++;
}
if(block_index < direct_packet_size)
{
word_value_1 |= (direct_packet[block_index] << 8);
block_index++;
}
bus->i2c_smbus_write_word_data(dev, reg_value_0, word_value_0);
if(reg_value_1 > 0)
{
bus->i2c_smbus_write_word_data(dev, reg_value_1, word_value_1);
}
}
}
/*-------------------------------------------------*\
| Remember to delete the data buffer |
\*-------------------------------------------------*/
@@ -24,15 +24,18 @@ using namespace std::chrono_literals;
bool TestForCorsairDRAMController(i2c_smbus_interface *bus, unsigned char address)
{
LOG_DEBUG("[%s] Trying address %02X", CORSAIR_DRAM_NAME, address);
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
int res = bus->i2c_smbus_read_byte_data(address, 0x43);
LOG_DEBUG("[%s] Trying address %02X", CORSAIR_DRAM_NAME, address);
if(res < 0)
{
LOG_DEBUG("[%s] Failed: res was %04X", CORSAIR_DRAM_NAME, res);
return false;
}
res = bus->i2c_smbus_read_byte_data(address, 0x43);
if(!(res == 0x1A || res == 0x1B || res == 0x1C))
{
LOG_DEBUG("[%s] Failed: expected 0x1A, 0x1B, or 0x1C, got %04X", CORSAIR_DRAM_NAME, res);
@@ -96,16 +96,7 @@ void RGBController_CorsairICueLink::SetupZones()
lcd_zone.leds_min = controller->GetEndpoints()[lcd_idx]->led_channels;
lcd_zone.leds_max = lcd_zone.leds_min;
lcd_zone.leds_count = lcd_zone.leds_min;
zones.push_back(lcd_zone);
for(unsigned int led_idx = 0; led_idx < lcd_zone.leds_count; led_idx++)
{
led new_led;
new_led.name = "LED " + std::to_string(led_idx + 1);
leds.push_back(new_led);
}
}
}
}
@@ -35,12 +35,6 @@ CorsairPeripheralV2Controller::CorsairPeripheralV2Controller(hid_device* dev_han
write_cmd = CORSAIR_V2_WRITE_WIRELESS_ID;
pid = GetAddress(0x12);
break;
case CORSAIR_K57_RGB_WIRED_PID:
write_cmd = 0x80;
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
skip_reads = true;
break;
}
/*---------------------------------------------------------*\
@@ -99,17 +93,14 @@ CorsairPeripheralV2Controller::CorsairPeripheralV2Controller(hid_device* dev_han
| If lighting control endpoint 2 is unavailable |
| then use endpoint 1. |
\*---------------------------------------------------------*/
if(light_ctrl == CORSAIR_V2_LIGHT_CTRL2)
result = StartTransaction(0);
if(result > 0)
{
result = StartTransaction(0);
if(result > 0)
{
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
StartTransaction(0);
}
StopTransaction(0);
LOG_DEBUG("[%s] Lighting Endpoint set to %02X", device_name.c_str(), light_ctrl);
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
StartTransaction(0);
}
StopTransaction(0);
LOG_DEBUG("[%s] Lighting Endpoint set to %02X", device_name.c_str(), light_ctrl);
}
CorsairPeripheralV2Controller::~CorsairPeripheralV2Controller()
@@ -180,11 +171,7 @@ void CorsairPeripheralV2Controller::SetRenderMode(corsair_v2_device_mode mode)
buffer[5] = mode;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
void CorsairPeripheralV2Controller::LightingControl(uint8_t opt1)
@@ -204,11 +191,7 @@ void CorsairPeripheralV2Controller::LightingControl(uint8_t opt1)
buffer[5] = 0x00;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
unsigned int CorsairPeripheralV2Controller::GetKeyboardLayout()
@@ -257,11 +240,7 @@ unsigned char CorsairPeripheralV2Controller::StartTransaction(uint8_t opt1)
buffer[4] = light_ctrl;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
return buffer[2];
}
@@ -278,20 +257,11 @@ void CorsairPeripheralV2Controller::StopTransaction(uint8_t opt1)
buffer[4] = opt1;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
void CorsairPeripheralV2Controller::ClearPacketBuffer()
{
if(skip_reads)
{
return;
}
uint8_t result = 0;
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
@@ -334,11 +304,7 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
memcpy(&buffer[offset1], &data[0], copy_bytes);
hid_write(dev, buffer, pkt_sze);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
}
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
remaining -= copy_bytes;
buffer[2] = CORSAIR_V2_CMD_BLK_WN;
@@ -359,11 +325,7 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
memcpy(&buffer[offset2], &data[index], copy_bytes);
hid_write(dev, buffer, pkt_sze);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
}
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
remaining -= copy_bytes;
}
@@ -109,7 +109,6 @@ private:
uint8_t write_cmd = CORSAIR_V2_WRITE_WIRED_ID;
uint16_t pkt_sze = CORSAIR_V2_WRITE_SIZE;
bool skip_reads = false;
std::string firmware_version;
std::string location;
};
@@ -57,7 +57,6 @@ void DetectCorsairV2SoftwareControllers(hid_device_info* info, const std::string
| Keyboards |
\*-----------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IP("Corsair K55 RGB PRO", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K55_RGB_PRO_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K57 RGB (Wired)", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K57_RGB_WIRED_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO Low Profile", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_LP_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO TKL Black", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_TKL_B_PID, 1, 0xFF42);
@@ -48,31 +48,6 @@ std::vector<unsigned int> corsair_full_size_values =
/*-------------------------------------------------------------------------*\
| KEYMAPS |
\*-------------------------------------------------------------------------*/
keyboard_keymap_overlay_values corsair_K57_layout
{
KEYBOARD_SIZE::KEYBOARD_SIZE_FULL,
{
corsair_full_size_values,
{
/* Add more regional layout fixes here */
}
},
{
/*-------------------------------------------------------------------------------------------------------------------------------------*\
| Edit Keys |
| Zone, Row, Column, Value, Name, Alternate Name, OpCode |
\*-------------------------------------------------------------------------------------------------------------------------------------*/
{ 0, 0, 17, 0, "Power/Wireless Indicator", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_ROW, }, // Insert Profile into new row
{ 0, 0, 18, 1, "Lock/Macro Indicator", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Light key
{ 0, 1, 0, 131, "Key: G1", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G1 key
{ 0, 2, 0, 132, "Key: G2", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G2 key
{ 0, 3, 0, 133, "Key: G3", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G3 key
{ 0, 4, 0, 134, "Key: G4", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G4 key
{ 0, 5, 0, 135, "Key: G5", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G5 key
{ 0, 6, 0, 136, "Key: G6", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G6 key
}
};
keyboard_keymap_overlay_values corsair_K60_layout
{
KEYBOARD_SIZE::KEYBOARD_SIZE_FULL,
@@ -141,14 +116,12 @@ keyboard_keymap_overlay_values corsair_k70_pro_layout
{ 0, 0, 0, 128, "Profile", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_ROW, }, // Insert Profile into new row
{ 0, 0, 1, 113, "Light", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Light key
{ 0, 0, 2, 114, "Lock", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Lock Key
{ 0, 0, 10, 138, "Logo", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Logo
{ 0, 0, 10, 191, "Logo", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Logo
{ 0, 0, 18, 102, KEY_EN_MEDIA_MUTE, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Mute Key
{ 0, 1, 17, 123, KEY_EN_MEDIA_STOP, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Stop Key
{ 0, 1, 18, 126, KEY_EN_MEDIA_PREVIOUS, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Previous Track Key
{ 0, 1, 19, 124, KEY_EN_MEDIA_PLAY_PAUSE, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Play Pause Key
{ 0, 1, 20, 125, KEY_EN_MEDIA_NEXT, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Next Tack Key
{ 0, 6, 5, 140, KEY_EN_SPACE, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert spacebar L
{ 0, 6, 7, 141, KEY_EN_SPACE, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert spacebar R
}
};
@@ -194,10 +167,10 @@ keyboard_keymap_overlay_values corsair_K70_CORE_TKL_layout
| Edit Keys |
| Zone, Row, Column, Value, Name, Alternate Name, OpCode, |
\*-------------------------------------------------------------------------------------------------------------------------------------*/
{ 0, 0, 14, 70, KEY_EN_MEDIA_MUTE, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Swap PRSC with Mute
{ 0, 0, 14, 70, KEY_EN_MEDIA_MUTE, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Swap PRSC with Mute
{ 0, 0, 15, 71, KEY_EN_UNUSED, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Remove SCLK
{ 0, 0, 16, 72, KEY_EN_MEDIA_VOLUME_UP, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Swap PSBK with Volume Potion Up
{ 0, 0, 16, 72, KEY_EN_MEDIA_VOLUME_UP, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Swap PSBK with Volume Potion Up
}
};
@@ -799,38 +772,6 @@ static const corsair_v2_device k55_rgb_pro_device =
nullptr
};
/*-------------------------------------------------------------*\
| Corsair K57 RGB (Wired) 1B1C:1B6E |
| |
| Zone "Keyboard" |
| Matrix |
| 7 Rows, 22 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_zone k57_rgb_wired_zone =
{
ZONE_EN_KEYBOARD,
ZONE_TYPE_MATRIX,
7,
22
};
static const corsair_v2_device k57_rgb_wired_device =
{
CORSAIR_K57_RGB_WIRED_PID,
DEVICE_TYPE_KEYBOARD,
7,
22,
{
&k57_rgb_wired_zone,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr
},
&corsair_K57_layout
};
/*-------------------------------------------------------------*\
| Corsair K60 RGB Pro 1B1C:1BA0 |
| |
@@ -1520,7 +1461,6 @@ const corsair_v2_device* corsair_v2_device_list_data[] =
| KEYBOARDS |
\*-----------------------------------------------------------------*/
&k55_rgb_pro_device,
&k57_rgb_wired_device,
&k60_rgb_pro_device,
&k60_rgb_pro_lp_device,
&k60_rgb_pro_tkl_device_b,
@@ -69,8 +69,6 @@ typedef struct
| Corsair V2 Protocol Keyboards |
\*-----------------------------------------------------*/
#define CORSAIR_K55_RGB_PRO_PID 0x1BA4
#define CORSAIR_K57_RGB_WIRED_PID 0x1B6E
#define CORSAIR_K57_RGB_WIRELESS_PID 0x1B62
#define CORSAIR_K60_RGB_PRO_PID 0x1BA0
#define CORSAIR_K60_RGB_PRO_LP_PID 0x1BAD
#define CORSAIR_K60_RGB_PRO_TKL_B_PID 0x1BC7
@@ -0,0 +1,217 @@
/*---------------------------------------------------------*\
| CorsairWirelessController.cpp |
| |
| Driver for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "CorsairWirelessController.h"
#include "StringUtils.h"
using namespace std::chrono_literals;
CorsairWirelessController::CorsairWirelessController(hid_device* dev_handle, const char* path)
{
dev = dev_handle;
location = path;
type = DEVICE_TYPE_KEYBOARD;
EnterDirectMode();
}
CorsairWirelessController::~CorsairWirelessController()
{
hid_close(dev);
}
device_type CorsairWirelessController::GetDeviceType()
{
return type;
}
std::string CorsairWirelessController::GetDeviceLocation()
{
return("HID: " + location);
}
std::string CorsairWirelessController::GetFirmwareString()
{
return firmware_version;
}
std::string CorsairWirelessController::GetName()
{
return name;
}
std::string CorsairWirelessController::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 CorsairWirelessController::SetLEDs(std::vector<RGBColor>colors)
{
unsigned char buf[3 * 137];
for(int color = 0; color < 137; color++)
{
buf[0 + color] = RGBGetRValue(colors[color]);
buf[137 + color] = RGBGetGValue(colors[color]);
buf[274+color] = RGBGetBValue(colors[color]);
}
StartDirectMode();
SendDirectFrame(true, &buf[0]);
SendDirectFrame(false, &buf[57]);
SendDirectFrame(false, &buf[118]);
SendDirectFrame(false, &buf[179]);
SendDirectFrame(false, &buf[240]);
SendDirectFrame(false, &buf[301]);
SendDirectFrame(false, &buf[362]);
}
void CorsairWirelessController::SetName(std::string device_name)
{
name = device_name;
}
/*-------------------------------------------------------------------------------------------------*\
| Private packet sending functions. |
\*-------------------------------------------------------------------------------------------------*/
void CorsairWirelessController::EnterDirectMode()
{
unsigned char usb_buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Submit Mouse Colors packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x80;
usb_buf[0x02] = 0x01;
usb_buf[0x03] = 0x03;
usb_buf[0x04] = 0x00;
usb_buf[0x05] = 0x02;
/*-----------------------------------------------------*\
| Send packet using feature reports, as headset stand |
| seems to not update completely using HID writes |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 65);
}
void CorsairWirelessController::StartDirectMode()
{
unsigned char usb_buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Submit Mouse Colors packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x80;
usb_buf[0x02] = 0x0D;
usb_buf[0x03] = 0x00;
usb_buf[0x04] = 0x01;
/*-----------------------------------------------------*\
| Send packet using feature reports, as headset stand |
| seems to not update completely using HID writes |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 65);
}
void CorsairWirelessController::ExitDirectMode()
{
unsigned char usb_buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Submit Mouse Colors packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x80;
usb_buf[0x02] = 0x01;
usb_buf[0x03] = 0x03;
usb_buf[0x04] = 0x00;
usb_buf[0x05] = 0x01;
/*-----------------------------------------------------*\
| Send packet using feature reports, as headset stand |
| seems to not update completely using HID writes |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 65);
}
void CorsairWirelessController::SendDirectFrame(bool first_frame, unsigned char* data)
{
unsigned char usb_buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Submit Mouse Colors packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x80;
usb_buf[0x02] = first_frame ? 0x06 : 0x07;
usb_buf[0x03] = 0x00;
if(first_frame)
{
usb_buf[0x04] = 0x9B;
usb_buf[0x05] = 0x01;
}
/*-----------------------------------------------------*\
| Copy in colors in <RED> <GREEN> <BLUE> order |
\*-----------------------------------------------------*/
if(first_frame)
{
memcpy(&usb_buf[0x08], data, 57);
}
else
{
memcpy(&usb_buf[0x04], data, 61);
}
/*-----------------------------------------------------*\
| Send packet using feature reports, as headset stand |
| seems to not update completely using HID writes |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 65);
}
@@ -0,0 +1,45 @@
/*---------------------------------------------------------*\
| CorsairWirelessController.h |
| |
| Driver for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
class CorsairWirelessController
{
public:
CorsairWirelessController(hid_device* dev_handle, const char* path);
~CorsairWirelessController();
device_type GetDeviceType();
std::string GetDeviceLocation();
std::string GetFirmwareString();
std::string GetName();
std::string GetSerialString();
void SetLEDs(std::vector<RGBColor> colors);
void SetName(std::string device_name);
private:
hid_device* dev;
std::string firmware_version;
std::string location;
std::string name;
device_type type;
void EnterDirectMode();
void ExitDirectMode();
void StartDirectMode();
void SendDirectFrame(bool first_frame, unsigned char* data);
};
@@ -0,0 +1,62 @@
/*---------------------------------------------------------*\
| CorsairWirelessControllerDetect.cpp |
| |
| Detector for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "Detector.h"
#include "CorsairWirelessController.h"
#include "RGBController.h"
#include "RGBController_CorsairWireless.h"
/*-----------------------------------------------------*\
| Corsair vendor ID |
\*-----------------------------------------------------*/
#define CORSAIR_VID 0x1B1C
/*-----------------------------------------------------*\
| Keyboard product IDs |
\*-----------------------------------------------------*/
#define CORSAIR_K57_RGB_WIRED_PID 0x1B6E
#define CORSAIR_K57_RGB_WIRELESS_PID 0x1B62
/******************************************************************************************\
* *
* DetectCorsairWirelessControllers *
* *
* Tests the USB address to see if a Corsair RGB Keyboard controller exists there. *
* *
\******************************************************************************************/
void DetectCorsairWirelessControllers(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
CorsairWirelessController* controller = new CorsairWirelessController(dev, info->path);
controller->SetName(name);
if(controller->GetDeviceType() != DEVICE_TYPE_UNKNOWN)
{
RGBController_CorsairWireless* rgb_controller = new RGBController_CorsairWireless(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else
{
delete controller;
}
}
} /* DetectCorsairWirelessControllers() */
/*-----------------------------------------------------------------------------------------------------*\
| Keyboards |
\*-----------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Corsair K57 RGB (Wired)", DetectCorsairWirelessControllers, CORSAIR_VID, CORSAIR_K57_RGB_WIRED_PID, 1, 0xFF42, 1);
//REGISTER_HID_DETECTOR_IPU("Corsair K57 RGB (Wireless)", DetectCorsairWirelessControllers, CORSAIR_VID, CORSAIR_K57_RGB_WIRELESS_PID, 1, 0xFF42, 1);
@@ -0,0 +1,342 @@
/*---------------------------------------------------------*\
| RGBController_CorsairWireless.cpp |
| |
| RGBController for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBControllerKeyNames.h"
#include "RGBController_CorsairWireless.h"
using namespace std::chrono_literals;
//0xFFFFFFFF indicates an unused entry in matrix
#define NA 0xFFFFFFFF
static unsigned int matrix_map[7][24] =
{ { NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 0, 1, NA, NA, NA },
{ 131, 41, NA, 58, 59, 60, 61, NA, 62, 63, 64, 65, NA, 66, 67, 68, 69, 70, 71, 72, NA, NA, NA, NA },
{ 132, 53, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, NA, 45, 46, 42, NA, 73, 74, 75, 83, 84, 85, 86 },
{ 133, 43, NA, 20, 26, 8, 21, NA, 23, 28, 24, 12, 18, 19, 47, 48, 49, 76, 77, 78, 95, 96, 97, 87 },
{ 134, 57, NA, 4, 22, 7, 9, NA, 10, 11, 13, 14, 15, 51, 52, NA, 40, NA, NA, NA, 92, 93, 94, NA },
{ 135, 106, NA, 29, 27, 6, 25, NA, 5, NA, 17, 16, 54, 55, 56, 110, NA, NA, 82, NA, 89, 90, 91, 88 },
{ 136, 105, 108, 107, NA, NA, NA, NA, 44, NA, NA, NA, NA, 111, 122, 101, 109, 80, 81, 79, 98, NA, 99, NA } };
static const char* zone_names[] =
{
ZONE_EN_KEYBOARD,
};
static const unsigned int zone_sizes[] =
{
137
};
static const zone_type zone_types[] =
{
ZONE_TYPE_MATRIX,
};
static const char* led_names[] =
{
"Power/Wireless Indicator",
"Lock/Macro Indicator",
"N/A",
"N/A",
KEY_EN_A,
KEY_EN_B,
KEY_EN_C,
KEY_EN_D,
KEY_EN_E,
KEY_EN_F,
KEY_EN_G,
KEY_EN_H,
KEY_EN_I,
KEY_EN_J,
KEY_EN_K,
KEY_EN_L,
KEY_EN_M,
KEY_EN_N,
KEY_EN_O,
KEY_EN_P,
KEY_EN_Q,
KEY_EN_R,
KEY_EN_S,
KEY_EN_T,
KEY_EN_U,
KEY_EN_V,
KEY_EN_W,
KEY_EN_X,
KEY_EN_Y,
KEY_EN_Z,
KEY_EN_1,
KEY_EN_2,
KEY_EN_3,
KEY_EN_4,
KEY_EN_5,
KEY_EN_6,
KEY_EN_7,
KEY_EN_8,
KEY_EN_9,
KEY_EN_0,
KEY_EN_ANSI_ENTER,
KEY_EN_ESCAPE,
KEY_EN_BACKSPACE,
KEY_EN_TAB,
KEY_EN_SPACE,
KEY_EN_MINUS,
KEY_EN_EQUALS,
KEY_EN_LEFT_BRACKET,
KEY_EN_RIGHT_BRACKET,
KEY_EN_ANSI_BACK_SLASH,
KEY_EN_UNUSED,
KEY_EN_SEMICOLON,
KEY_EN_QUOTE,
KEY_EN_BACK_TICK,
KEY_EN_COMMA,
KEY_EN_PERIOD,
KEY_EN_FORWARD_SLASH,
KEY_EN_CAPS_LOCK,
KEY_EN_F1,
KEY_EN_F2,
KEY_EN_F3,
KEY_EN_F4,
KEY_EN_F5,
KEY_EN_F6,
KEY_EN_F7,
KEY_EN_F8,
KEY_EN_F9,
KEY_EN_F10,
KEY_EN_F11,
KEY_EN_F12,
KEY_EN_PRINT_SCREEN,
KEY_EN_SCROLL_LOCK,
KEY_EN_PAUSE_BREAK,
KEY_EN_INSERT,
KEY_EN_HOME,
KEY_EN_PAGE_UP,
KEY_EN_DELETE,
KEY_EN_END,
KEY_EN_PAGE_DOWN,
KEY_EN_RIGHT_ARROW,
KEY_EN_LEFT_ARROW,
KEY_EN_DOWN_ARROW,
KEY_EN_UP_ARROW,
KEY_EN_NUMPAD_LOCK,
KEY_EN_NUMPAD_DIVIDE,
KEY_EN_NUMPAD_TIMES,
KEY_EN_NUMPAD_MINUS,
KEY_EN_NUMPAD_PLUS,
KEY_EN_NUMPAD_ENTER,
KEY_EN_NUMPAD_1,
KEY_EN_NUMPAD_2,
KEY_EN_NUMPAD_3,
KEY_EN_NUMPAD_4,
KEY_EN_NUMPAD_5,
KEY_EN_NUMPAD_6,
KEY_EN_NUMPAD_7,
KEY_EN_NUMPAD_8,
KEY_EN_NUMPAD_9,
KEY_EN_NUMPAD_0,
KEY_EN_NUMPAD_PERIOD,
KEY_EN_UNUSED,
KEY_EN_MENU,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_LEFT_CONTROL,
KEY_EN_LEFT_SHIFT,
KEY_EN_LEFT_ALT,
KEY_EN_LEFT_WINDOWS,
KEY_EN_RIGHT_CONTROL,
KEY_EN_RIGHT_SHIFT,
KEY_EN_RIGHT_ALT,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_RIGHT_FUNCTION,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
"Key: G1",
"Key: G2",
"Key: G3",
"Key: G4",
"Key: G5",
"Key: G6",
};
/**------------------------------------------------------------------*\
@name Corsair Wireless Peripheral
@category Keyboard
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectCorsairWirelessControllers
@comment
\*-------------------------------------------------------------------*/
RGBController_CorsairWireless::RGBController_CorsairWireless(CorsairWirelessController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetName();
vendor = "Corsair";
description = "Corsair RGB Peripheral Device";
type = controller->GetDeviceType();
version = controller->GetFirmwareString();
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = 0;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
/*-----------------------------------------------------*\
| The Corsair Lighting Node Pro requires a packet within|
| 20 seconds of sending the lighting change in order |
| to not revert back into rainbow mode. Start a thread |
| to continuously send a keepalive packet every 5s |
\*-----------------------------------------------------*/
keepalive_thread_run = true;
keepalive_thread = new std::thread(&RGBController_CorsairWireless::KeepaliveThread, this);
}
RGBController_CorsairWireless::~RGBController_CorsairWireless()
{
/*-----------------------------------------------------*\
| Close keepalive thread |
\*-----------------------------------------------------*/
keepalive_thread_run = false;
keepalive_thread->join();
delete keepalive_thread;
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
delete zones[zone_index].matrix_map;
}
}
delete controller;
}
void RGBController_CorsairWireless::SetupZones()
{
/*---------------------------------------------------------*\
| Determine number of zones |
| For now, keyboard has 2 zones and mousemat has 1 |
\*---------------------------------------------------------*/
unsigned int num_zones = 1;
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
unsigned int total_led_count = 0;
for(unsigned int zone_idx = 0; zone_idx < num_zones; zone_idx++)
{
zone new_zone;
new_zone.name = zone_names[zone_idx];
new_zone.type = zone_types[zone_idx];
new_zone.leds_min = zone_sizes[zone_idx];
new_zone.leds_max = zone_sizes[zone_idx];
new_zone.leds_count = zone_sizes[zone_idx];
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map = new matrix_map_type;
new_zone.matrix_map->height = 7;
new_zone.matrix_map->width = 24;
new_zone.matrix_map->map = (unsigned int *)&matrix_map;
}
else
{
new_zone.matrix_map = NULL;
}
zones.push_back(new_zone);
total_led_count += new_zone.leds_count;
}
for(unsigned int led_idx = 0; led_idx < total_led_count; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx];
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_CorsairWireless::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_CorsairWireless::DeviceUpdateLEDs()
{
last_update_time = std::chrono::steady_clock::now();
controller->SetLEDs(colors);
}
void RGBController_CorsairWireless::UpdateZoneLEDs(int /*zone*/)
{
controller->SetLEDs(colors);
}
void RGBController_CorsairWireless::UpdateSingleLED(int /*led*/)
{
controller->SetLEDs(colors);
}
void RGBController_CorsairWireless::DeviceUpdateMode()
{
}
void RGBController_CorsairWireless::KeepaliveThread()
{
while(keepalive_thread_run.load())
{
if(active_mode == 0)
{
if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(5000))
{
DeviceUpdateLEDs();
}
}
std::this_thread::sleep_for(1000ms);
}
}
@@ -0,0 +1,43 @@
/*---------------------------------------------------------*\
| RGBController_CorsairWireless.h |
| |
| RGBController for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "CorsairWirelessController.h"
class RGBController_CorsairWireless : public RGBController
{
public:
RGBController_CorsairWireless(CorsairWirelessController* controller_ptr);
~RGBController_CorsairWireless();
int physical_layout;
int logical_layout;
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void KeepaliveThread();
private:
CorsairWirelessController* controller;
std::thread* keepalive_thread;
std::atomic<bool> keepalive_thread_run;
std::chrono::time_point<std::chrono::steady_clock> last_update_time;
};
@@ -134,14 +134,7 @@ void CrucialController::CrucialRegisterWriteBlock(crucial_register reg, unsigned
bus->i2c_smbus_write_word_data(dev, 0x00, ((reg << 8) & 0xFF00) | ((reg >> 8) & 0x00FF));
//Write Crucial block data
if(bus->i2c_smbus_write_block_data(dev, 0x03, sz, data) == -1)
{
//Fall back to individual byte operations if the block operation fails
for(unsigned int block_byte = 0; block_byte < sz; block_byte++)
{
bus->i2c_smbus_write_byte_data(dev, 0x01, data[block_byte]);
}
}
bus->i2c_smbus_write_block_data(dev, 0x03, sz, data);
}
void CrucialController::SendEffectColor
@@ -27,6 +27,12 @@ using namespace std::chrono_literals;
static const unsigned char crucial_addresses[] =
{
/*-----------------------------------------------------*\
| These addresses have been disabled due to conflict |
| with ASUS Aura DRAM. Since the detection scheme is |
| the same, Aura RAM will be detected as Crucial. |
| We need to improve the Crucial detection scheme. |
\*-----------------------------------------------------*/
0x39,
0x3A,
0x3B,
@@ -75,7 +81,7 @@ unsigned char CrucialRegisterRead(i2c_smbus_interface* bus, crucial_dev_id dev,
* TestForCrucialController *
* *
* Tests the given address to see if an Crucial controller exists there. First does a*
* byte read to test for a response, and if so does a simple read at 0xA0 to test *
* quick write to test for a response, and if so does a simple read at 0xA0 to test *
* for incrementing values 0...F which was observed at this location during data dump *
* *
\******************************************************************************************/
@@ -84,19 +90,19 @@ bool TestForCrucialController(i2c_smbus_interface* bus, unsigned char address)
{
bool pass = false;
int res = bus->i2c_smbus_read_byte(address);
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
if(res >= 0)
if (res >= 0)
{
pass = true;
LOG_DEBUG("[%s] Detected an I2C device at address %02X", CRUCIAL_CONTROLLER_NAME, address);
for(int i = 0xA0; i < 0xB0; i++)
for (int i = 0xA0; i < 0xB0; i++)
{
res = bus->i2c_smbus_read_byte_data(address, i);
if(res != (i - 0xA0))
if (res != (i - 0xA0))
{
LOG_VERBOSE("[%s] Detection failed testing register %02X. Expected %02X, got %02X.", CRUCIAL_CONTROLLER_NAME, i, (i - 0xA0), res);
@@ -172,7 +178,7 @@ void DetectCrucialControllers(std::vector<i2c_smbus_interface*> &busses)
{
for(unsigned int slot = 0; slot < 4; slot++)
{
int res = busses[bus]->i2c_smbus_read_byte(0x27);
int res = busses[bus]->i2c_smbus_write_quick(0x27, I2C_SMBUS_WRITE);
if(res < 0)
{
@@ -186,7 +192,7 @@ void DetectCrucialControllers(std::vector<i2c_smbus_interface*> &busses)
if(address_list_idx < CRUCIAL_ADDRESS_COUNT)
{
res = busses[bus]->i2c_smbus_read_byte(crucial_addresses[address_list_idx]);
res = busses[bus]->i2c_smbus_write_quick(crucial_addresses[address_list_idx], I2C_SMBUS_WRITE);
}
else
{
@@ -112,7 +112,7 @@ static void ENERegisterWrite(i2c_smbus_interface* bus, ene_dev_id dev, ene_regis
* TestForENESMBusController *
* *
* Tests the given address to see if an ENE controller exists there. First does a *
* byte read to test for a response, and if so does a simple read at 0xA0 to test *
* quick write to test for a response, and if so does a simple read at 0xA0 to test *
* for incrementing values 0...F which was observed at this location during data dump *
* *
* Also tests for the string "Micron" in the ENE register space. Crucial (Micron) *
@@ -202,7 +202,7 @@ void DetectENESMBusDRAMControllers(std::vector<i2c_smbus_interface*> &busses)
for (unsigned int slot = 0; slot < 8; slot++)
{
int res = busses[bus]->i2c_smbus_read_byte(0x77);
int res = busses[bus]->i2c_smbus_write_quick(0x77, I2C_SMBUS_WRITE);
if(res < 0)
{
@@ -219,7 +219,7 @@ void DetectENESMBusDRAMControllers(std::vector<i2c_smbus_interface*> &busses)
{
LOG_DEBUG("[ENE SMBus DRAM] Testing address %02X to see if there is a device there", ene_ram_addresses[address_list_idx]);
res = busses[bus]->i2c_smbus_read_byte(ene_ram_addresses[address_list_idx]);
res = busses[bus]->i2c_smbus_write_quick(ene_ram_addresses[address_list_idx], I2C_SMBUS_WRITE);
}
else
{
@@ -461,7 +461,6 @@ REGISTER_I2C_PCI_DETECTOR("ASUS ROG MATRIX PLATINUM GeForce RTX 4090",
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5060 Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5060_O8G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5070 Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070_O12G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5070 Ti Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070TI_O16G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5070 Ti Gaming BTF White OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070TI_O16G_GAMING_BTF_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5070 Ti Gaming White OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070TI_O16G_GAMING_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 5070 Ti Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_5070TI_O16G_GAMING_OC, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5080 Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5080_O16G_GAMING, 0x67);
@@ -472,7 +471,6 @@ REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5080 OC WHITE",
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5080", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5080_16G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5080 WHITE", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5080_16G_GAMING_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_2, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC BTF", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_BTF, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC WHITE", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL LC GeForce RTX 5090 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_LC_RTX_5090_O32G_GAMING, 0x67);
@@ -61,12 +61,5 @@ void ENESMBusInterface_i2c_smbus::ENERegisterWriteBlock(ene_dev_id dev, ene_regi
bus->i2c_smbus_write_word_data(dev, 0x00, ((reg << 8) & 0xFF00) | ((reg >> 8) & 0x00FF));
//Write ENE block data
if(bus->i2c_smbus_write_block_data(dev, 0x03, sz, data) == -1)
{
//Fall back to individual byte operations if the block operation fails
for(unsigned int block_byte = 0; block_byte < sz; block_byte++)
{
bus->i2c_smbus_write_byte_data(dev, 0x01, data[block_byte]);
}
}
bus->i2c_smbus_write_block_data(dev, 0x03, sz, data);
}
@@ -1,238 +0,0 @@
/*---------------------------------------------------------*\
| GigabyteCastor3Controller.cpp |
| |
| Driver for Gigabyte Aorus Waterforce X II 360 AIO |
| (Castor3 USB HID controller) |
| |
| RGB ring control only — LCD not implemented |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "GigabyteCastor3Controller.h"
#include "StringUtils.h"
GigabyteCastor3Controller::GigabyteCastor3Controller(hid_device* dev_handle, const char* path)
{
dev = dev_handle;
location = path;
/*---------------------------------------------------------*\
| Query firmware version via de 00 |
| Response: de [version_byte] |
| Observed: de 02 -> version 2 |
\*---------------------------------------------------------*/
unsigned char de_payload[] = { 0xDE, 0x00 };
SendPacket(de_payload, 2);
unsigned char response[CASTOR3_IN_SIZE];
int bytes_read = hid_read_timeout(dev, response, CASTOR3_IN_SIZE, CASTOR3_IN_TIMEOUT_MS);
if(bytes_read > 1)
{
/*-----------------------------------------------------*\
| Strip report ID 0x99 if present on IN packets |
\*-----------------------------------------------------*/
int offset = 0;
if(response[0] == CASTOR3_REPORT_ID)
{
offset = 1;
}
if(response[offset] == 0xDE && bytes_read > offset + 1)
{
firmware_version = std::to_string(response[offset + 1]);
}
}
}
GigabyteCastor3Controller::~GigabyteCastor3Controller()
{
hid_close(dev);
}
std::string GigabyteCastor3Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string GigabyteCastor3Controller::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));
}
std::string GigabyteCastor3Controller::GetFirmwareVersion()
{
return firmware_version;
}
void GigabyteCastor3Controller::SetEffect
(
unsigned char style,
unsigned char speed,
unsigned char brightness,
unsigned char b4,
unsigned char b5,
castor3_color_type color_type,
std::vector<RGBColor> colors
)
{
/*---------------------------------------------------------*\
| Wire protocol for LED effect apply |
| (from castor3.py led_set_effect, confirmed from |
| led_profile.pcapng + .cled.dat cross-reference) |
| |
| Step 1: c9 [Style] [Speed*20] [Bright] [b4] [b5] |
| Step 2: cd [R] [G] [B] (single-color only) |
| -OR- b0..b3 palette registers (palette effects) |
| Step 3: b6 (commit) |
\*---------------------------------------------------------*/
/*---------------------------------------------------------*\
| Step 1: c9 — effect select + parameters |
\*---------------------------------------------------------*/
unsigned char c9_payload[] =
{
0xC9,
style,
(unsigned char)(speed * 20),
brightness,
b4,
b5
};
SendPacket(c9_payload, sizeof(c9_payload));
/*---------------------------------------------------------*\
| Step 2a: cd — primary color for single-color effects |
| Effects: static, pulse, flash, dflash, gradient, off |
\*---------------------------------------------------------*/
if(color_type == CASTOR3_COLORS_SINGLE)
{
unsigned char r = 0xFF, g = 0x66, b = 0x00;
if(colors.size() > 0)
{
r = RGBGetRValue(colors[0]);
g = RGBGetGValue(colors[0]);
b = RGBGetBValue(colors[0]);
}
unsigned char cd_payload[] = { 0xCD, r, g, b };
SendPacket(cd_payload, sizeof(cd_payload));
}
/*---------------------------------------------------------*\
| Step 2b: b0..b3 — palette registers for multi-color |
| Each register carries 2 colors (6 bytes RGB + 1 pad): |
| bN [Style] [R1 G1 B1] [R2 G2 B2] 0x00 |
| b0=colors 1+2, b1=colors 3+4, b2=colors 5+6, b3=7+8 |
| |
| Effects: colorshift(8), tricolor(3), spin(3), switch(2) |
\*---------------------------------------------------------*/
else if(color_type == CASTOR3_COLORS_PALETTE)
{
/*-----------------------------------------------------*\
| Build palette: pad to 8 colors by repeating last |
\*-----------------------------------------------------*/
RGBColor palette[8];
for(int i = 0; i < 8; i++)
{
if(i < (int)colors.size())
{
palette[i] = colors[i];
}
else if(colors.size() > 0)
{
palette[i] = colors[colors.size() - 1];
}
else
{
palette[i] = ToRGBColor(0xFF, 0x00, 0x00);
}
}
unsigned char regs[] = { 0xB0, 0xB1, 0xB2, 0xB3 };
for(int i = 0; i < 4; i++)
{
RGBColor c1 = palette[i * 2];
RGBColor c2 = palette[i * 2 + 1];
unsigned char bN_payload[] =
{
regs[i],
style,
(unsigned char)RGBGetRValue(c1), (unsigned char)RGBGetGValue(c1), (unsigned char)RGBGetBValue(c1),
(unsigned char)RGBGetRValue(c2), (unsigned char)RGBGetGValue(c2), (unsigned char)RGBGetBValue(c2),
0x00
};
SendPacket(bN_payload, sizeof(bN_payload));
}
}
/*---------------------------------------------------------*\
| Step 3: b6 — commit/apply |
\*---------------------------------------------------------*/
unsigned char b6_payload[] = { 0xB6 };
SendPacket(b6_payload, sizeof(b6_payload));
}
void GigabyteCastor3Controller::SetOff()
{
/*---------------------------------------------------------*\
| Off = Style 0x01 (Static) with Speed=0 |
| From castor3.py: off uses style 0x01, speed_wire=0 |
\*---------------------------------------------------------*/
std::vector<RGBColor> black;
black.push_back(ToRGBColor(0x00, 0x00, 0x00));
unsigned char c9_payload[] =
{
0xC9,
CASTOR3_STYLE_STATIC,
0x00, /* speed_wire = 0 for off */
CASTOR3_BRIGHTNESS_DEFAULT,
0x02, /* b4 */
0x01 /* b5 */
};
SendPacket(c9_payload, sizeof(c9_payload));
unsigned char cd_payload[] = { 0xCD, 0x00, 0x00, 0x00 };
SendPacket(cd_payload, sizeof(cd_payload));
unsigned char b6_payload[] = { 0xB6 };
SendPacket(b6_payload, sizeof(b6_payload));
}
void GigabyteCastor3Controller::SendPacket(const unsigned char* payload, unsigned int payload_len)
{
/*---------------------------------------------------------*\
| Castor3 HID OUT transport: |
| - Byte 0: Report ID (0x99) |
| - Bytes 1..6143: payload, zero-padded |
| Total write size: 6144 bytes |
\*---------------------------------------------------------*/
unsigned char buf[CASTOR3_OUT_TOTAL];
memset(buf, 0x00, CASTOR3_OUT_TOTAL);
buf[0] = CASTOR3_REPORT_ID;
if(payload_len > CASTOR3_OUT_PAYLOAD)
{
payload_len = CASTOR3_OUT_PAYLOAD;
}
memcpy(&buf[1], payload, payload_len);
hid_write(dev, buf, CASTOR3_OUT_TOTAL);
}
@@ -1,123 +0,0 @@
/*---------------------------------------------------------*\
| GigabyteCastor3Controller.h |
| |
| Driver for Gigabyte Aorus Waterforce X II 360 AIO |
| (Castor3 USB HID controller) |
| |
| RGB ring control only — LCD not implemented |
| |
| Protocol reference: castor3.py reverse engineering |
| VID=0x0414 PID=0x7A5E Report ID=0x99 |
| OUT size: 6144 (report ID + 6143 payload) |
| IN size: 255 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <hidapi.h>
#include "RGBController.h"
/*---------------------------------------------------------*\
| Castor3 USB IDs |
\*---------------------------------------------------------*/
#define CASTOR3_VID 0x0414
#define CASTOR3_PID 0x7A5E
/*---------------------------------------------------------*\
| Castor3 HID transport |
\*---------------------------------------------------------*/
#define CASTOR3_REPORT_ID 0x99
#define CASTOR3_OUT_PAYLOAD 6143 /* payload after report ID */
#define CASTOR3_OUT_TOTAL 6144 /* report ID + payload */
#define CASTOR3_IN_SIZE 255
#define CASTOR3_IN_TIMEOUT_MS 2000
/*---------------------------------------------------------*\
| LED effect style IDs (c9 byte[1]) |
| From castor3.py LED_EFFECTS, cross-referenced with |
| .cled.dat profile files and led_profile.pcapng |
\*---------------------------------------------------------*/
#define CASTOR3_STYLE_STATIC 0x01
#define CASTOR3_STYLE_PULSE 0x02
#define CASTOR3_STYLE_CYCLE 0x03
#define CASTOR3_STYLE_FLASH 0x04
#define CASTOR3_STYLE_DFLASH 0x05
#define CASTOR3_STYLE_GRADIENT 0x06
#define CASTOR3_STYLE_COLORSHIFT 0x07
#define CASTOR3_STYLE_WAVE 0x08
#define CASTOR3_STYLE_RAINBOW 0x0A
#define CASTOR3_STYLE_TRICOLOR 0x0B
#define CASTOR3_STYLE_SPIN 0x0C
#define CASTOR3_STYLE_SWITCH 0x0D
/*---------------------------------------------------------*\
| LED speed range: 1-5 (wire value = speed * 20) |
| LED brightness range: 1-10 (direct on wire) |
\*---------------------------------------------------------*/
#define CASTOR3_SPEED_MIN 1
#define CASTOR3_SPEED_MAX 5
#define CASTOR3_SPEED_DEFAULT 5
#define CASTOR3_BRIGHTNESS_MIN 1
#define CASTOR3_BRIGHTNESS_MAX 10
#define CASTOR3_BRIGHTNESS_DEFAULT 10
/*---------------------------------------------------------*\
| Color type for effects |
\*---------------------------------------------------------*/
enum castor3_color_type
{
CASTOR3_COLORS_NONE = 0, /* cycle, wave, rainbow */
CASTOR3_COLORS_SINGLE = 1, /* static, pulse, flash... */
CASTOR3_COLORS_PALETTE = 2, /* colorshift, tricolor... */
};
class GigabyteCastor3Controller
{
public:
GigabyteCastor3Controller(hid_device* dev_handle, const char* path);
~GigabyteCastor3Controller();
std::string GetDeviceLocation();
std::string GetSerialString();
std::string GetFirmwareVersion();
/*---------------------------------------------------------*\
| LED effect application |
| |
| Wire sequence per effect apply: |
| c9 [Style] [Speed*20] [Bright] [b4] [b5] |
| cd [R] [G] [B] — single-color only |
| b0 [Style] [R1G1B1] [R2G2B2] 00 — palette colors 1+2 |
| b1 [Style] [R3G3B3] [R4G4B4] 00 — colors 3+4 |
| b2 [Style] [R5G5B5] [R6G6B6] 00 — colors 5+6 |
| b3 [Style] [R7G7B7] [R8G8B8] 00 — colors 7+8 |
| b6 — commit/apply |
\*---------------------------------------------------------*/
void SetEffect(unsigned char style,
unsigned char speed,
unsigned char brightness,
unsigned char b4,
unsigned char b5,
castor3_color_type color_type,
std::vector<RGBColor> colors);
void SetOff();
private:
hid_device* dev;
std::string location;
std::string firmware_version;
void SendPacket(const unsigned char* payload, unsigned int payload_len);
};
@@ -1,39 +0,0 @@
/*---------------------------------------------------------*\
| GigabyteCastor3ControllerDetect.cpp |
| |
| Detector for Gigabyte Aorus Waterforce X II 360 AIO |
| (Castor3 USB HID controller) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "Detector.h"
#include "GigabyteCastor3Controller.h"
#include "RGBController_GigabyteCastor3.h"
#define GIGABYTE_CASTOR3_VID 0x0414
#define GIGABYTE_CASTOR3_PID 0x7A5E
void DetectGigabyteCastor3Controllers(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
GigabyteCastor3Controller* controller = new GigabyteCastor3Controller(dev, info->path);
RGBController_GigabyteCastor3* rgb_controller = new RGBController_GigabyteCastor3(controller);
rgb_controller->name = name;
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
REGISTER_HID_DETECTOR_IPU("Gigabyte Aorus Waterforce X II 360",
DetectGigabyteCastor3Controllers,
GIGABYTE_CASTOR3_VID,
GIGABYTE_CASTOR3_PID,
0, /* interface 0 */
0x0000, /* usage_page */
0x0002); /* usage */
@@ -1,446 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_GigabyteCastor3.cpp |
| |
| RGBController for Gigabyte Aorus Waterforce X II 360 |
| AIO Cooler (Castor3 controller) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_GigabyteCastor3.h"
/**------------------------------------------------------------------*\
@name Gigabyte Aorus Waterforce X II 360
@category Cooler
@type USB
@save :o:
@direct :o:
@effects :white_check_mark:
@detectors DetectGigabyteCastor3Controllers
@comment Controls the LED ring on the pump head of the
Gigabyte Aorus Waterforce X II 360 AIO cooler.
Uses the Castor3 USB HID controller (VID 0x0414, PID 0x7A5E).
The LED ring and fans share the same controller — they are
not independently addressable and follow the same effect.
LCD display control is not implemented.
No direct/per-LED mode available — all modes are hardware
effects with a single color or palette.
\*-------------------------------------------------------------------*/
RGBController_GigabyteCastor3::RGBController_GigabyteCastor3(GigabyteCastor3Controller* controller_ptr)
{
controller = controller_ptr;
name = "Gigabyte Aorus Waterforce X II 360";
vendor = "Gigabyte";
type = DEVICE_TYPE_COOLER;
description = "Gigabyte Aorus Waterforce X II 360 AIO Cooler";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
version = controller->GetFirmwareVersion();
/*---------------------------------------------------------*\
| All modes from castor3.py LED_EFFECTS (12 effects + off) |
| |
| The Castor3 has no direct/per-LED mode. All effects are |
| hardware-driven, either single-color, palette, or |
| firmware-color (no user color). |
| |
| Speed: 1-5 mapped to MODE_FLAG_HAS_SPEED |
| Brightness: 1-10 mapped to MODE_FLAG_HAS_BRIGHTNESS |
| |
| mode.value stores the style byte for the c9 command. |
| mode.speed stores 1-5 (scaled to speed*20 on wire). |
| mode.brightness stores 1-10 (direct on wire). |
\*---------------------------------------------------------*/
/*---------------------------------------------------------*\
| Off |
| Wire: Style=0x01 (Static) with speed_wire=0 |
\*---------------------------------------------------------*/
mode Off;
Off.name = "Off";
Off.value = 0xFF; /* sentinel — handled specially */
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
/*---------------------------------------------------------*\
| Static — single color, no speed |
\*---------------------------------------------------------*/
mode Static;
Static.name = "Static";
Static.value = CASTOR3_STYLE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Static.colors_min = 1;
Static.colors_max = 1;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Static.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Static.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Static.colors.resize(1);
modes.push_back(Static);
/*---------------------------------------------------------*\
| Pulse — single color, speed + brightness |
\*---------------------------------------------------------*/
mode Pulse;
Pulse.name = "Pulse";
Pulse.value = CASTOR3_STYLE_PULSE;
Pulse.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Pulse.speed_min = CASTOR3_SPEED_MIN;
Pulse.speed_max = CASTOR3_SPEED_MAX;
Pulse.speed = CASTOR3_SPEED_DEFAULT;
Pulse.colors_min = 1;
Pulse.colors_max = 1;
Pulse.color_mode = MODE_COLORS_MODE_SPECIFIC;
Pulse.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Pulse.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Pulse.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Pulse.colors.resize(1);
modes.push_back(Pulse);
/*---------------------------------------------------------*\
| Flash — single color, speed + brightness |
\*---------------------------------------------------------*/
mode Flash;
Flash.name = "Flash";
Flash.value = CASTOR3_STYLE_FLASH;
Flash.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Flash.speed_min = CASTOR3_SPEED_MIN;
Flash.speed_max = CASTOR3_SPEED_MAX;
Flash.speed = CASTOR3_SPEED_DEFAULT;
Flash.colors_min = 1;
Flash.colors_max = 1;
Flash.color_mode = MODE_COLORS_MODE_SPECIFIC;
Flash.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Flash.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Flash.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Flash.colors.resize(1);
modes.push_back(Flash);
/*---------------------------------------------------------*\
| Double Flash — single color, speed + brightness |
\*---------------------------------------------------------*/
mode DFlash;
DFlash.name = "Double Flash";
DFlash.value = CASTOR3_STYLE_DFLASH;
DFlash.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
DFlash.speed_min = CASTOR3_SPEED_MIN;
DFlash.speed_max = CASTOR3_SPEED_MAX;
DFlash.speed = CASTOR3_SPEED_DEFAULT;
DFlash.colors_min = 1;
DFlash.colors_max = 1;
DFlash.color_mode = MODE_COLORS_MODE_SPECIFIC;
DFlash.brightness_min = CASTOR3_BRIGHTNESS_MIN;
DFlash.brightness_max = CASTOR3_BRIGHTNESS_MAX;
DFlash.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
DFlash.colors.resize(1);
modes.push_back(DFlash);
/*---------------------------------------------------------*\
| Cycle — firmware colors, speed only |
\*---------------------------------------------------------*/
mode Cycle;
Cycle.name = "Spectrum Cycle";
Cycle.value = CASTOR3_STYLE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Cycle.speed_min = CASTOR3_SPEED_MIN;
Cycle.speed_max = CASTOR3_SPEED_MAX;
Cycle.speed = CASTOR3_SPEED_DEFAULT;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Cycle.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Cycle.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
modes.push_back(Cycle);
/*---------------------------------------------------------*\
| Gradient — single color, speed + brightness |
| Note: b4 = brightness on wire (special case) |
\*---------------------------------------------------------*/
mode Gradient;
Gradient.name = "Gradient";
Gradient.value = CASTOR3_STYLE_GRADIENT;
Gradient.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Gradient.speed_min = CASTOR3_SPEED_MIN;
Gradient.speed_max = CASTOR3_SPEED_MAX;
Gradient.speed = CASTOR3_SPEED_DEFAULT;
Gradient.colors_min = 1;
Gradient.colors_max = 1;
Gradient.color_mode = MODE_COLORS_MODE_SPECIFIC;
Gradient.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Gradient.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Gradient.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Gradient.colors.resize(1);
modes.push_back(Gradient);
/*---------------------------------------------------------*\
| Color Shift — 8-color palette, speed + brightness |
\*---------------------------------------------------------*/
mode ColorShift;
ColorShift.name = "Color Shift";
ColorShift.value = CASTOR3_STYLE_COLORSHIFT;
ColorShift.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
ColorShift.speed_min = CASTOR3_SPEED_MIN;
ColorShift.speed_max = CASTOR3_SPEED_MAX;
ColorShift.speed = CASTOR3_SPEED_DEFAULT;
ColorShift.colors_min = 1;
ColorShift.colors_max = 8;
ColorShift.color_mode = MODE_COLORS_MODE_SPECIFIC;
ColorShift.brightness_min = CASTOR3_BRIGHTNESS_MIN;
ColorShift.brightness_max = CASTOR3_BRIGHTNESS_MAX;
ColorShift.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
ColorShift.colors.resize(8);
ColorShift.colors[0] = ToRGBColor(0xFF, 0x00, 0x00);
ColorShift.colors[1] = ToRGBColor(0xFF, 0x72, 0x00);
ColorShift.colors[2] = ToRGBColor(0xFF, 0xFF, 0x00);
ColorShift.colors[3] = ToRGBColor(0x00, 0xFF, 0x00);
ColorShift.colors[4] = ToRGBColor(0x00, 0xFF, 0xFF);
ColorShift.colors[5] = ToRGBColor(0x00, 0x00, 0xFF);
ColorShift.colors[6] = ToRGBColor(0xFF, 0x00, 0xFF);
ColorShift.colors[7] = ToRGBColor(0xFF, 0x80, 0x80);
modes.push_back(ColorShift);
/*---------------------------------------------------------*\
| Wave — firmware colors, speed only |
\*---------------------------------------------------------*/
mode Wave;
Wave.name = "Wave";
Wave.value = CASTOR3_STYLE_WAVE;
Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Wave.speed_min = CASTOR3_SPEED_MIN;
Wave.speed_max = CASTOR3_SPEED_MAX;
Wave.speed = CASTOR3_SPEED_DEFAULT;
Wave.color_mode = MODE_COLORS_NONE;
Wave.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Wave.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Wave.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
modes.push_back(Wave);
/*---------------------------------------------------------*\
| Rainbow — firmware colors, speed only |
\*---------------------------------------------------------*/
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = CASTOR3_STYLE_RAINBOW;
Rainbow.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Rainbow.speed_min = CASTOR3_SPEED_MIN;
Rainbow.speed_max = CASTOR3_SPEED_MAX;
Rainbow.speed = CASTOR3_SPEED_DEFAULT;
Rainbow.color_mode = MODE_COLORS_NONE;
Rainbow.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Rainbow.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Rainbow.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
modes.push_back(Rainbow);
/*---------------------------------------------------------*\
| Tri-Color — 3-color palette, speed + brightness |
\*---------------------------------------------------------*/
mode TriColor;
TriColor.name = "Tri-Color";
TriColor.value = CASTOR3_STYLE_TRICOLOR;
TriColor.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
TriColor.speed_min = CASTOR3_SPEED_MIN;
TriColor.speed_max = CASTOR3_SPEED_MAX;
TriColor.speed = CASTOR3_SPEED_DEFAULT;
TriColor.colors_min = 1;
TriColor.colors_max = 3;
TriColor.color_mode = MODE_COLORS_MODE_SPECIFIC;
TriColor.brightness_min = CASTOR3_BRIGHTNESS_MIN;
TriColor.brightness_max = CASTOR3_BRIGHTNESS_MAX;
TriColor.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
TriColor.colors.resize(3);
TriColor.colors[0] = ToRGBColor(0x00, 0x00, 0xFF);
TriColor.colors[1] = ToRGBColor(0x7D, 0x00, 0xFF);
TriColor.colors[2] = ToRGBColor(0xFF, 0x00, 0xFF);
modes.push_back(TriColor);
/*---------------------------------------------------------*\
| Spin — 3-color palette, speed + brightness |
\*---------------------------------------------------------*/
mode Spin;
Spin.name = "Spin";
Spin.value = CASTOR3_STYLE_SPIN;
Spin.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Spin.speed_min = CASTOR3_SPEED_MIN;
Spin.speed_max = CASTOR3_SPEED_MAX;
Spin.speed = CASTOR3_SPEED_DEFAULT;
Spin.colors_min = 1;
Spin.colors_max = 3;
Spin.color_mode = MODE_COLORS_MODE_SPECIFIC;
Spin.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Spin.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Spin.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Spin.colors.resize(3);
Spin.colors[0] = ToRGBColor(0xFF, 0x00, 0xFE);
Spin.colors[1] = ToRGBColor(0x00, 0xFF, 0xFB);
Spin.colors[2] = ToRGBColor(0xFF, 0xFF, 0x00);
modes.push_back(Spin);
/*---------------------------------------------------------*\
| Switch — 2-color palette, speed + brightness |
\*---------------------------------------------------------*/
mode Switch;
Switch.name = "Switch";
Switch.value = CASTOR3_STYLE_SWITCH;
Switch.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Switch.speed_min = CASTOR3_SPEED_MIN;
Switch.speed_max = CASTOR3_SPEED_MAX;
Switch.speed = CASTOR3_SPEED_DEFAULT;
Switch.colors_min = 1;
Switch.colors_max = 2;
Switch.color_mode = MODE_COLORS_MODE_SPECIFIC;
Switch.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Switch.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Switch.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Switch.colors.resize(2);
Switch.colors[0] = ToRGBColor(0xFF, 0x00, 0xFE);
Switch.colors[1] = ToRGBColor(0x00, 0xFF, 0xFB);
modes.push_back(Switch);
SetupZones();
}
RGBController_GigabyteCastor3::~RGBController_GigabyteCastor3()
{
delete controller;
}
void RGBController_GigabyteCastor3::SetupZones()
{
/*---------------------------------------------------------*\
| Single zone: LED ring + fans (not independently |
| addressable — fans follow the pump ring effect) |
| |
| This is a SINGLE zone because the protocol does not |
| expose per-LED addressing. All LEDs display the same |
| hardware effect simultaneously. |
\*---------------------------------------------------------*/
zone led_ring;
led_ring.name = "LED Ring";
led_ring.type = ZONE_TYPE_SINGLE;
led_ring.leds_min = 1;
led_ring.leds_max = 1;
led_ring.leds_count = 1;
led_ring.matrix_map = NULL;
zones.push_back(led_ring);
led new_led;
new_led.name = "LED Ring";
leds.push_back(new_led);
SetupColors();
}
void RGBController_GigabyteCastor3::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| Fixed zone size — not resizable |
\*---------------------------------------------------------*/
}
void RGBController_GigabyteCastor3::DeviceUpdateLEDs()
{
DeviceUpdateMode();
}
void RGBController_GigabyteCastor3::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_GigabyteCastor3::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_GigabyteCastor3::DeviceUpdateMode()
{
unsigned char style = (unsigned char)modes[active_mode].value;
/*---------------------------------------------------------*\
| Handle Off mode (sentinel value 0xFF) |
\*---------------------------------------------------------*/
if(style == 0xFF)
{
controller->SetOff();
return;
}
/*---------------------------------------------------------*\
| Determine speed, brightness, b4, b5, and color type |
| from the effect lookup table matching castor3.py |
\*---------------------------------------------------------*/
unsigned char speed = CASTOR3_SPEED_DEFAULT;
unsigned char brightness = CASTOR3_BRIGHTNESS_DEFAULT;
if(modes[active_mode].flags & MODE_FLAG_HAS_SPEED)
{
speed = (unsigned char)modes[active_mode].speed;
}
if(modes[active_mode].flags & MODE_FLAG_HAS_BRIGHTNESS)
{
brightness = (unsigned char)modes[active_mode].brightness;
}
/*---------------------------------------------------------*\
| Look up b4, b5, and color_type per effect |
| These match the LED_EFFECTS dict in castor3.py |
\*---------------------------------------------------------*/
unsigned char b4 = 0x02;
unsigned char b5 = 0x01;
castor3_color_type color_type = CASTOR3_COLORS_NONE;
switch(style)
{
case CASTOR3_STYLE_STATIC:
case CASTOR3_STYLE_PULSE:
case CASTOR3_STYLE_FLASH:
case CASTOR3_STYLE_DFLASH:
b4 = 0x02;
b5 = 0x01;
color_type = CASTOR3_COLORS_SINGLE;
break;
case CASTOR3_STYLE_GRADIENT:
/*-------------------------------------------------*\
| Gradient: b4 = brightness on wire (special case) |
\*-------------------------------------------------*/
b4 = brightness;
b5 = 0x01;
color_type = CASTOR3_COLORS_SINGLE;
break;
case CASTOR3_STYLE_CYCLE:
case CASTOR3_STYLE_WAVE:
case CASTOR3_STYLE_RAINBOW:
b4 = 0x02;
b5 = 0x01;
color_type = CASTOR3_COLORS_NONE;
break;
case CASTOR3_STYLE_COLORSHIFT:
b4 = 0x08; /* ClrCount=8 */
b5 = 0x02; /* CmbIndex+1=2 */
color_type = CASTOR3_COLORS_PALETTE;
break;
case CASTOR3_STYLE_TRICOLOR:
case CASTOR3_STYLE_SPIN:
b4 = 0x02;
b5 = 0x01;
color_type = CASTOR3_COLORS_PALETTE;
break;
case CASTOR3_STYLE_SWITCH:
b4 = 0x02;
b5 = 0x01;
color_type = CASTOR3_COLORS_PALETTE;
break;
}
controller->SetEffect(style, speed, brightness, b4, b5,
color_type, modes[active_mode].colors);
}
@@ -1,33 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_GigabyteCastor3.h |
| |
| RGBController for Gigabyte Aorus Waterforce X II 360 |
| AIO Cooler (Castor3 controller) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "GigabyteCastor3Controller.h"
class RGBController_GigabyteCastor3 : public RGBController
{
public:
RGBController_GigabyteCastor3(GigabyteCastor3Controller* controller_ptr);
~RGBController_GigabyteCastor3();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
GigabyteCastor3Controller* controller;
};
@@ -68,14 +68,9 @@ void RGBFusion2BlackwellGPUController::SetMode(uint8_t type, uint8_t zone, uint8
if(zone_config.numberOfColors > 0)
{
int currentPos = 12;
switch(gpu_layout)
if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT)
{
case RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT:
case RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT:
currentPos = 11;
break;
default:
break;
currentPos = 11;
}
for(uint8_t i = 0; i < zone_config.numberOfColors; i++)
@@ -95,16 +90,6 @@ void RGBFusion2BlackwellGPUController::SetZone(uint8_t zone, uint8_t mode, fusio
if(mode == RGB_FUSION2_BLACKWELL_GPU_MODE_BREATHING)
zone_config.brightness = RGB_FUSION2_BLACKWELL_GPU_BRIGHTNESS_MAX;
switch(gpu_layout)
{
case RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT:
if(mode == RGB_FUSION2_BLACKWELL_GPU_MODE_DIRECT)
mode = RGB_FUSION2_BLACKWELL_GPU_MODE_STATIC;
break;
default:
break;
}
uint8_t type = RGB_FUSION2_BLACKWELL_GPU_REG_COLOR;
if(mode != RGB_FUSION2_BLACKWELL_GPU_MODE_DIRECT)
type = RGB_FUSION2_BLACKWELL_GPU_REG_MODE;
@@ -62,12 +62,11 @@ enum
enum
{
RGB_FUSION2_BLACKWELL_GPU_SINGLE_ZONE = 0,
RGB_FUSION2_BLACKWELL_GPU_GAMING_LAYOUT = 1,
RGB_FUSION2_BLACKWELL_GPU_WATERFORCE_LAYOUT = 2,
RGB_FUSION2_BLACKWELL_GPU_AORUS_WATERFORCE_LAYOUT = 3,
RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT = 4,
RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT = 5,
RGB_FUSION2_BLACKWELL_GPU_SINGLE_ZONE = 0,
RGB_FUSION2_BLACKWELL_GPU_GAMING_LAYOUT = 1,
RGB_FUSION2_BLACKWELL_GPU_WATERFORCE_LAYOUT = 2,
RGB_FUSION2_BLACKWELL_GPU_AORUS_WATERFORCE_LAYOUT = 3,
RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT = 4,
};
class RGBFusion2BlackwellGPUController
@@ -1,4 +1,4 @@
/*---------------------------------------------------------*\
/*---------------------------------------------------------*\
| GigabyteRGBFusion2BlackwellGPUControllerDetect.cpp |
| |
| Detector for Gigabyte RGB Fusion 2 Blackwell GPU |
@@ -181,48 +181,30 @@ void DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers(i2c_sm
DetectGigabyteRGBFusion2BlackwellGPUControllers(bus, i2c_addr, name, RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT);
} /* DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers() */
/*******************************************************************************************\
* *
* DetectGigabyteRGBFusion2BlackwellAorusMaster5090DV2IceLayoutGPUControllers *
* *
* Detect GigabyteRGB Fusion2 controllers with AORUS master 5090 D V2 ICE layout on *
* enumerated I2C busses. *
* *
* bus - pointer to i2c_smbus_interface where RGB Fusion2 device is connected *
* dev - I2C address of RGB Fusion2 device *
* *
\*******************************************************************************************/
void DetectGigabyteRGBFusion2BlackwellAorusMaster5090DV2IceLayoutGPUControllers(i2c_smbus_interface* bus, uint8_t i2c_addr, const std::string& name)
{
DetectGigabyteRGBFusion2BlackwellGPUControllers(bus, i2c_addr, name, RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT);
} /* DetectGigabyteRGBFusion2BlackwellAorusMaster5090DV2IceLayoutGPUControllers() */
/*-----------------------------------------*\
| Nvidia GPUs |
\*-----------------------------------------*/
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5060 Ti Gaming OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5060TI_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_AERO_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Eagle OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_EAGLE_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Eagle OC ICE", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_EAGLE_OC_ICE_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_GAMING_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Eagle OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_EAGLE_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Eagle OC ICE", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_EAGLE_OC_ICE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_AERO_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_AERO_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 MASTER", DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_MASTER_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellAorusWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_XTREME_WATERFORCE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_XTREME_WATERFORCE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 MASTER ICE", DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_MASTER_ICE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_GAMING_OC_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_XTREME_WATERFORCE_32G_SUB_DEV1, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_XTREME_WATERFORCE_32G_SUB_DEV2, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 MASTER", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090_MASTER_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 MASTER ICE", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090_MASTER_ICE_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 D V2 MASTER ICE", DetectGigabyteRGBFusion2BlackwellAorusMaster5090DV2IceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090D_V2_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090D_V2_MASTER_ICE_24G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5060 Ti Gaming OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5060TI_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_AERO_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Eagle OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_EAGLE_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_GAMING_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Eagle OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_EAGLE_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Eagle OC ICE", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_EAGLE_OC_ICE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_AERO_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_AERO_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 MASTER", DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_MASTER_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellAorusWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_XTREME_WATERFORCE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_XTREME_WATERFORCE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 MASTER ICE", DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_MASTER_ICE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_GAMING_OC_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_XTREME_WATERFORCE_32G_SUB_DEV1, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_XTREME_WATERFORCE_32G_SUB_DEV2, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 MASTER", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090_MASTER_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 MASTER ICE", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090_MASTER_ICE_32G_SUB_DEV, 0x75);
/*-----------------------------------------*\
| AMD GPUs |
@@ -400,52 +400,6 @@ void RGBController_RGBFusion2BlackwellGPU::SetupZones()
leds.push_back(top_led);
}
else if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT)
{
const char * fan_names[] = { "Right Fan", "Left Fan", "Middle Fan" };
for(int i = 0; i < 3; i++)
{
zone fan_zone;
fan_zone.name = fan_names[i];
fan_zone.type = ZONE_TYPE_SINGLE;
fan_zone.leds_min = 1;
fan_zone.leds_max = 1;
fan_zone.leds_count = 1;
fan_zone.matrix_map = NULL;
zones.push_back(fan_zone);
led new_led;
new_led.name = fan_names[i];
leds.push_back(new_led);
}
zone backplate;
backplate.name = "Backplate";
backplate.type = ZONE_TYPE_SINGLE;
backplate.leds_min = 1;
backplate.leds_max = 1;
backplate.leds_count = 1;
backplate.matrix_map = NULL;
zones.push_back(backplate);
led bp_led;
bp_led.name = "Backplate";
leds.push_back(bp_led);
zone side_logo;
side_logo.name = "Side Logo";
side_logo.type = ZONE_TYPE_SINGLE;
side_logo.leds_min = 1;
side_logo.leds_max = 1;
side_logo.leds_count = 1;
side_logo.matrix_map = NULL;
zones.push_back(side_logo);
led sl_led;
sl_led.name = "Side Logo";
leds.push_back(sl_led);
}
SetupColors();
}
@@ -492,10 +446,6 @@ void RGBController_RGBFusion2BlackwellGPU::DeviceUpdateLEDs()
gpu_zones = 6; // Zones 4 and 5 refer to ui zone 4
break;
case RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT:
gpu_zones = 6;
break;
default:
LOG_TRACE("[%s] Invalid GPU layout (%d) when updating LEDs.", name.c_str(), gpu_layout);
return; // should not happen
@@ -522,8 +472,7 @@ void RGBController_RGBFusion2BlackwellGPU::DeviceUpdateLEDs()
}
ui_zone_idx = zone_idx - 1; // Map: HW zone 1->UI zone 0, HW zone 2->UI zone 1, HW zone 3->UI zone 2, HW zone 4->UI zone 3
}
else if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT ||
gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT)
else if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT)
{
if(zone_idx == 5)
{
@@ -152,7 +152,6 @@ REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 3090 XTREME WATERFORCE",
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 3090 XTREME WATERFORCE WB", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX3090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX3090_XTREME_WATERFORCE_WB_SUB_DEV, 0x64);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 GAMING OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070_GAMING_OC_12G, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 GAMING OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070_GAMING_OC_12G_V2, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 GAMING OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_AD103_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070_GAMING_OC_12G_V2, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Geforce RTX 4070 Aero OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070_AERO_OC_12G_SUB_DEV, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 SUPER GAMING OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070S_GAMING_OC_12G, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 SUPER Aero OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070S_AERO_OC_12G, 0x71);
@@ -189,7 +188,6 @@ REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7600 GAMING OC 8G",
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7600 GAMING OC 8G", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI33_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7600_GAMING_OC_8G_SUB_DEV2, 0x55);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7600 XT GAMING OC 16G", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI33_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7600XT_GAMING_OC_16G_SUB_DEV, 0x55);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 6700 XT GAMING OC 12G", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI22_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX6700XT_GAMING_OC_12G_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 6800 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI21_DEV1, GIGABYTE_SUB_VEN, GIGABYTE_RX6800XT_GAMING_OC_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 6900 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI21_DEV1, GIGABYTE_SUB_VEN, GIGABYTE_RX6900XT_GAMING_OC_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7700 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI32_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7700XT_GAMING_OC_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7800 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI32_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7800XT_GAMING_OC_16G_SUB_DEV, 0x62);
File diff suppressed because it is too large Load Diff
@@ -16,7 +16,7 @@
#include <map>
#include "RGBController.h"
#define GB_FUSION2_ZONES_MAX 12
#define GB_FUSION2_ZONES_MAX 8
/*--------------------------------------------------------*\
| Base LED mappings found on all controllers. |
@@ -89,23 +89,6 @@ const FwdLedHeaders LedLookup =
\*-------------------------------------------------*/
};
/*--------------------------------------------------------*\
| The layout_id masks for supported effects. |
\*--------------------------------------------------------*/
enum GB_LID_EFFECTS_MASKS : uint32_t
{
GB_EFF_BREATH = 0x001,
GB_EFF_BEAT = 0x002,
GB_EFF_CYCLE = 0x004,
GB_EFF_FLASH = 0x008,
GB_EFF_RANDOM = 0x010,
GB_EFF_WAVE = 0x020,
GB_EFF_DFLASH = 0x040,
GB_EFF_WAVE1 = 0x080,
GB_EFF_WAVE2 = 0x100,
GB_EFF_CORE_MASK = 0x1FF
};
typedef struct
{
GB_FUSION2_LED_IDX idx;
@@ -95,9 +95,10 @@ bool RGBFusion2USBController::RefreshHardwareInfo()
return false;
}
IT8297Report report;
std::memcpy(&report, buffer, sizeof(IT8297Report));
report_loaded = true;
device_num = report.device_num;
description = std::string(report.str_product, 28);
if(std::string::iterator nul = std::find(description.begin(), description.end(), '\0');
nul != description.end())
@@ -317,7 +318,7 @@ bool RGBFusion2USBController::SetCalibration(const EncodedCalibration& cal, bool
cal_data.mainboard = desired.c.rgb_cali;
cal_data.spare[0] = desired.c.c_spare0;
cal_data.spare[1] = desired.c.c_spare1;
if(product_id == 0x5711)
{
cal_data.dled[2] = desired.c.d_strip_c2;
@@ -361,6 +362,7 @@ void RGBFusion2USBController::SetLedCount(unsigned int c0, unsigned int c1, unsi
\*---------------------------------------------------------*/
bool RGBFusion2USBController::SetStripBuiltinEffectState(int hdr, bool enable)
{
static bool first_call = true;
int bitmask = 0;
if(hdr == -1)
@@ -390,16 +392,16 @@ bool RGBFusion2USBController::SetStripBuiltinEffectState(int hdr, bool enable)
}
}
int base_mask = (effect_disabled < 0) ? 0 : effect_disabled;
int new_effect_disabled = enable
? (base_mask & ~bitmask)
: (base_mask | bitmask);
int new_effect_disabled = enable
? (effect_disabled & ~bitmask)
: (effect_disabled | bitmask);
// Skip redundant writes only after we have synchronized at least once
if(effect_disabled >= 0 && new_effect_disabled == effect_disabled)
if(!first_call && new_effect_disabled == effect_disabled)
{
return true;
}
first_call = false;
effect_disabled = new_effect_disabled;
int res = SendCCReport(0x32, effect_disabled);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
@@ -437,7 +439,7 @@ bool RGBFusion2USBController::EnableLampArray(bool enable)
{
return SendCCReport(0x48, enable ? 1 : 0);
}
std::string RGBFusion2USBController::GetDeviceName()
{
return(name);
@@ -701,115 +703,3 @@ void RGBFusion2USBController::ResetController()
}
ApplyEffect(true);
}
/*---------------------------------------------------------*\
| Check controller for gen2 ARGB support |
| Checks for supported device number |
| Then checks for supported controllers |
| Then checks for supported feature bit (SaveLEDState) |
| Finally checks for strip detection value. |
\*---------------------------------------------------------*/
bool RGBFusion2USBController::SupportsGen2() const
{
bool supports_gen2 = false;
supports_gen2 = (device_num == 0x00)
&& (product_id == 0x5702 || product_id==0x5711 || product_id==0x8950)
&& (report.support_cmd_flag & 0x01)
&& (report.strip_detect == 0x01);
return supports_gen2;
}
std::vector<Gen2StripInfo> RGBFusion2USBController::ExportGen2Strips() const
{
size_t count = (product_id == 0x5711) ? 4u : 2u;
std::vector<Gen2StripInfo> out;
out.reserve(count);
for(size_t i = 0; i < count; ++i)
{
out.push_back(g2_strip_info[i]);
}
return out;
}
/*---------------------------------------------------------*\
| Scan Headers for Gen2 Devices |
\*---------------------------------------------------------*/
bool RGBFusion2USBController::ScanGen2Strips()
{
for(unsigned i = 0; i < 4; ++i)
{
if(g2_strip_info[i].LedsOfStrip.capacity() < 15)
{
g2_strip_info[i].LedsOfStrip.reserve(15);
}
g2_strip_info[i].numStrip = 0;
g2_strip_info[i].totalLeds = 0;
g2_strip_info[i].LedsOfStrip.resize(0);
}
const unsigned int hdr_lim = (product_id == 0x5711) ? 4u : 2u;
for(unsigned int slot = 0; slot < hdr_lim; ++slot)
{
static constexpr uint8_t delta[4] = {4, 5, 0, 1};
uint8_t scan_cmd = static_cast<uint8_t>(GEN2_LED_BASE_SCAN + delta[slot]);
uint8_t info_cmd = static_cast<uint8_t>(scan_cmd + 2);
if(!SendCCReport(scan_cmd, 0x00, 0x00))
{
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(700));
if(!SendCCReport(info_cmd, 0x00, 0x00))
{
return false;
}
unsigned char feature_buf[64] = {0};
feature_buf[0] = report_id;
int recv_len = hid_get_feature_report(dev, feature_buf, sizeof(feature_buf));
if(recv_len < 64)
{
return false;
}
int seg_count = static_cast<int>(feature_buf[1]);
if(seg_count < 0)
{
seg_count = 0;
}
if(seg_count > 15)
{
seg_count = 15;
}
Gen2StripInfo& dst = g2_strip_info[slot];
dst.numStrip = static_cast<uint8_t>(seg_count);
dst.LedsOfStrip.resize(static_cast<size_t>(seg_count));
uint32_t total_leds = 0;
const int counts_base = 2;
for(int k = 0; k < seg_count; ++k)
{
const int off = counts_base + (k * 2);
uint16_t lo = static_cast<uint16_t>(feature_buf[off + 0]);
uint16_t hi = static_cast<uint16_t>(feature_buf[off + 1]);
uint16_t cnt = static_cast<uint16_t>(lo | (hi << 8));
dst.LedsOfStrip[static_cast<size_t>(k)] = cnt;
total_leds += cnt;
}
dst.totalLeds = total_leds;
SetLedCount(0, 0, 0, 0);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
SaveLEDState(false);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
return true;
}
@@ -21,11 +21,6 @@
#define FUSION2_USB_BUFFER_SIZE 64
/*--------------------------------------------------------*\
| Gen2 scan/info opcode base |
\*--------------------------------------------------------*/
#define GEN2_LED_BASE_SCAN 0x38
/*---------------------------------------------------------*\
| Effects mode list |
\*---------------------------------------------------------*/
@@ -88,16 +83,6 @@ struct EncodedCalibration
std::string mainboard;
};
/*---------------------------------------------------------*\
| High level struct to contain Gen2 Header Data |
\*---------------------------------------------------------*/
struct Gen2StripInfo
{
uint8_t numStrip = 0;
std::vector<uint16_t> LedsOfStrip;
uint32_t totalLeds = 0;
};
#pragma pack(push, 1)
/*---------------------------------------------------------*\
@@ -336,8 +321,6 @@ public:
void SetLEDEffect(int led, int mode, unsigned int speed, unsigned char brightness, bool random, uint32_t* color);
bool SetStripBuiltinEffectState(int hdr, bool enable);
void SetStripColors(unsigned int hdr, RGBColor * colors, unsigned int num_colors, int single_led = -1);
bool SupportsGen2() const;
bool ScanGen2Strips();
EncodedCalibration GetCalibration(bool refresh_from_hw = false);
std::string GetDeviceName();
@@ -347,8 +330,6 @@ public:
std::string GetFWVersion();
uint16_t GetProductID();
std::string GetSerial();
std::vector<Gen2StripInfo> ExportGen2Strips() const;
Gen2StripInfo g2_strip_info[4];
private:
std::string DecodeCalibrationBuffer(uint32_t value) const;
@@ -364,7 +345,7 @@ private:
int SendPacket(unsigned char* packet);
hid_device* dev;
uint8_t device_num;
int device_num;
uint16_t product_id;
uint32_t effect_zone_mask = 0;
int mode;
@@ -375,7 +356,7 @@ private:
std::string location;
std::string version;
std::string chip_id;
int effect_disabled = -1;
int effect_disabled = 0;
int report_id = 0xCC;
bool report_loaded = false;
bool cali_loaded = false;
@@ -25,7 +25,7 @@
@effects :white_check_mark:
@detectors DetectGigabyteRGBFusion2USBControllers
@comment The Fusion 2 USB controller applies to most AMD and
Intel mainboards from the X570 and z390 chipsets onwards.
Intel mainboards from the x570 and z390 chipsets onwards.
\*-------------------------------------------------------------------*/
RGBController_RGBFusion2USB::RGBController_RGBFusion2USB(RGBFusion2USBController* controller_ptr, std::string detector)
@@ -39,10 +39,8 @@ RGBController_RGBFusion2USB::RGBController_RGBFusion2USB(RGBFusion2USBController
version = controller->GetFWVersion();
location = controller->GetDeviceLocation();
serial = controller->GetSerial();
product_id = controller->GetProductID();
device_num = controller->GetDeviceNum();
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFFFF;
@@ -208,13 +206,6 @@ RGBController_RGBFusion2USB::RGBController_RGBFusion2USB(RGBFusion2USBController
RGBController_RGBFusion2USB::~RGBController_RGBFusion2USB()
{
// Free any zones we allocated for the per-instance layout
for(gb_fusion2_zone* z : allocated_zones)
{
delete z;
}
allocated_zones.clear();
delete controller;
}
@@ -223,125 +214,94 @@ RGBController_RGBFusion2USB::~RGBController_RGBFusion2USB()
\*---------------------------------------------------------*/
void RGBController_RGBFusion2USB::Init_Controller()
{
const gb_fusion2_device* src_layout = gb_fusion2_device_list[device_index];
const std::string SectionGen2 = "Gigabyte-Gen2-ARGB";
const std::string SectionCustomBase = "CustomLayout";
const std::string SectionCustom = SectionCustomBase + std::to_string(device_num);
const std::string SectionCustom = "CustomLayout";
const std::string SectionCalibration = "Calibration";
RvrseLedHeaders ReverseLedLookup = reverse_map(LedLookup);
SettingsManager* settings_manager = ResourceManager::get()->GetSettingsManager();
nlohmann::json device_settings = settings_manager->GetSettings(detector_name);
/*---------------------------------------------------------*\
| Checks for Gen2 support and adds flag to json. |
| Create the custom layout from the generic_device |
\*---------------------------------------------------------*/
if(controller->SupportsGen2())
{
if(!device_settings.contains(SectionGen2))
{
device_settings[SectionGen2]["Enabled"] = false;
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
supports_gen2 = device_settings[SectionGen2]["Enabled"];
if(supports_gen2)
{
controller->ScanGen2Strips();
}
}
/*---------------------------------------------------------*\
| Create the custom layout from the generic layout |
\*---------------------------------------------------------*/
switch(product_id)
{
case 0x8950:
src_layout = gb_fusion2_device_list[device_index + 1];
break;
case 0x5711:
src_layout = gb_fusion2_device_list[device_index + 2];
break;
default:
break;
}
gb_fusion2_device* layout = const_cast<gb_fusion2_device*>(gb_fusion2_device_list[device_index]);
if(!device_settings.contains(SectionCustom))
{
device_settings[SectionCustom]["Enabled"] = false;
device_settings[SectionCustom]["Data"] = BuildCustomLayoutJson(src_layout, ReverseLedLookup);
device_settings[SectionCustom]["Data"] = BuildCustomLayoutJson(layout, ReverseLedLookup);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
bool custom_layout = device_settings[SectionCustom]["Enabled"];
if(custom_layout)
{
LoadCustomLayoutFromJson(device_settings[SectionCustom]["Data"], LedLookup, layout);
}
EncodedCalibration hw_cal = controller->GetCalibration(false);
if(device_num == 0)
if(!device_settings.contains(SectionCalibration))
{
if(!device_settings.contains(SectionCalibration))
device_settings[SectionCalibration]["Enabled"] = false;
device_settings[SectionCalibration]["Data"] = WriteCalJsonFrom(hw_cal);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
else
{
nlohmann::json& cal_sec = device_settings[SectionCalibration];
bool cal_enable = cal_sec.value("Enabled", false);
if(!cal_sec.contains("Data") || !cal_sec["Data"].is_object())
{
device_settings[SectionCalibration]["Enabled"] = false;
device_settings[SectionCalibration]["Data"] = WriteCalJsonFrom(hw_cal);
cal_sec["Data"] = WriteCalJsonFrom(hw_cal);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
else
{
nlohmann::json& cal_sec = device_settings[SectionCalibration];
bool cal_enable = cal_sec.value("Enabled", false);
nlohmann::json& cdata = cal_sec["Data"];
FillMissingWith(cdata, hw_cal);
if(!cal_sec.contains("Data") || !cal_sec["Data"].is_object())
if(!cal_enable)
{
cal_sec["Data"] = WriteCalJsonFrom(hw_cal);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
}
if(cal_enable)
{
const nlohmann::json& cdata = cal_sec["Data"];
EncodedCalibration desired;
desired.dled[0] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED1");
desired.dled[1] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED2");
desired.mainboard = GET_JSON_VAL_ELSE_OFF(cdata, "Mainboard");
desired.spare[0] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare0");
desired.spare[1] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare1");
if(controller->GetProductID() == 0x5711)
{
desired.dled[2] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED3");
desired.dled[3] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED4");
desired.spare[2] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare2");
desired.spare[3] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare3");
}
else
{
nlohmann::json& cdata = cal_sec["Data"];
FillMissingWith(cdata, hw_cal);
if(!cal_enable)
{
cal_sec["Data"] = WriteCalJsonFrom(hw_cal);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
}
if(cal_enable)
{
const nlohmann::json& cdata = cal_sec["Data"];
EncodedCalibration desired;
desired.dled[0] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED1");
desired.dled[1] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED2");
desired.mainboard = GET_JSON_VAL_ELSE_OFF(cdata, "Mainboard");
desired.spare[0] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare0");
desired.spare[1] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare1");
if(controller->GetProductID() == 0x5711)
{
desired.dled[2] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED3");
desired.dled[3] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED4");
desired.spare[2] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare2");
desired.spare[3] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare3");
}
else
{
desired.dled[2] = "OFF";
desired.dled[3] = "OFF";
desired.spare[2] = "OFF";
desired.spare[3] = "OFF";
}
controller->SetCalibration(desired, false);
desired.dled[2] = "OFF";
desired.dled[3] = "OFF";
desired.spare[2] = "OFF";
desired.spare[3] = "OFF";
}
controller->SetCalibration(desired, false);
}
}
/*---------------------------------------------------------------------*\
| When no match found the first entry (generic_device) will be used |
| otherwise look up channel map based on device name |
@@ -355,81 +315,35 @@ void RGBController_RGBFusion2USB::Init_Controller()
\*-----------------------------------------------------------------*/
for(unsigned int i = 0; i < GB_FUSION2_DEVICE_COUNT; i++)
{
if(gb_fusion2_device_list[i]->name == name &&
gb_fusion2_device_list[i]->device_num == device_num)
if(gb_fusion2_device_list[i]->name == name)
{
/*---------------------------------------------------------*\
| Set device ID |
\*---------------------------------------------------------*/
device_index = i;
src_layout = gb_fusion2_device_list[i];
layout = const_cast<gb_fusion2_device*>(gb_fusion2_device_list[i]);
break;
}
}
}
/*---------------------------------------------------------------------*\
| Creates per instance copy of layouts. |
\*---------------------------------------------------------------------*/
instance_layout.zones = &instance_zones;
instance_layout.layout_id = src_layout->layout_id;
instance_layout.device_num = src_layout->device_num;
instance_layout.name = src_layout->name;
for(uint8_t zi = 0; zi < GB_FUSION2_ZONES_MAX; ++zi)
{
(*instance_layout.zones)[zi] = (*src_layout->zones)[zi];
}
if(custom_layout)
{
LoadCustomLayoutFromJson(device_settings[SectionCustom]["Data"], LedLookup, &instance_layout);
}
/*---------------------------------------------------------------------*\
| Culls the mode support based on layout_id. |
\*---------------------------------------------------------------------*/
const uint32_t effect_mask = instance_layout.layout_id & GB_EFF_CORE_MASK;
modes.erase(std::remove_if(modes.begin(), modes.end(),
[effect_mask](const mode& m)
{
if(m.value == 0xFFFF /* Direct */) { return false; }
if(m.value == EFFECT_STATIC) { return false; }
uint32_t bit = 0u;
switch(m.value)
{
case EFFECT_PULSE: bit = GB_EFF_BREATH; break;
case EFFECT_COLORCYCLE: bit = GB_EFF_CYCLE; break;
case EFFECT_BLINKING: bit = GB_EFF_FLASH; break;
case EFFECT_RANDOM: bit = GB_EFF_RANDOM; break;
case EFFECT_WAVE: bit = GB_EFF_WAVE; break;
case EFFECT_DFLASH: bit = GB_EFF_DFLASH; break;
case EFFECT_WAVE1: bit = GB_EFF_WAVE1; break;
case EFFECT_WAVE2: bit = GB_EFF_WAVE2; break;
case EFFECT_WAVE3: bit = GB_EFF_WAVE1; break;
case EFFECT_WAVE4: bit = GB_EFF_WAVE2; break;
default: bit = 0u; break;
}
return (bit == 0u) || ((effect_mask & bit) == 0u);
}),
modes.end());
/*---------------------------------------------------------*\
| Iterate through layout and process each zone |
\*---------------------------------------------------------*/
for(uint8_t zone_idx = 0; zone_idx < GB_FUSION2_ZONES_MAX; zone_idx++)
{
if(!(*instance_layout.zones)[zone_idx])
if(!layout->zones[0][zone_idx])
{
continue;
}
const gb_fusion2_zone* zone_at_idx = (*instance_layout.zones)[zone_idx];
const gb_fusion2_zone* zone_at_idx = layout->zones[0][zone_idx];
zone new_zone;
new_zone.name = zone_at_idx->name;
new_zone.leds_min = zone_at_idx->leds_min;
new_zone.leds_max = zone_at_idx->leds_max;
new_zone.leds_count = new_zone.leds_min;
new_zone.type = ((new_zone.leds_min == 1) && (new_zone.leds_max == 1)) ? ZONE_TYPE_SINGLE : ZONE_TYPE_LINEAR;
new_zone.type = (new_zone.leds_min == new_zone.leds_max) ? ZONE_TYPE_SINGLE : ZONE_TYPE_LINEAR;
new_zone.matrix_map = NULL;
zones.emplace_back(new_zone);
}
@@ -449,68 +363,17 @@ void RGBController_RGBFusion2USB::SetupZones()
| Set up zones (Fixed so as to not spam the controller) |
\*---------------------------------------------------------*/
std::vector<Gen2StripInfo> strips = controller->ExportGen2Strips();
for(uint8_t zone_idx = 0; zone_idx < GB_FUSION2_ZONES_MAX; zone_idx++)
{
const gb_fusion2_zone* zone_at_idx = (*instance_layout.zones)[zone_idx];
const gb_fusion2_zone* zone_at_idx = gb_fusion2_device_list[device_index]->zones[0][zone_idx];
if(!zone_at_idx)
{
continue;
}
bool single_zone = ((zone_at_idx->leds_min == 1) && (zone_at_idx->leds_max == 1));
bool single_zone = (zone_at_idx->leds_min == zone_at_idx->leds_max);
if(!single_zone)
{
if(supports_gen2)
{
int slot = 0;
switch(zone_at_idx->idx)
{
case LED4:
case HDR_D_LED2:
slot = 1;
break;
case HDR_D_LED3:
slot = 2;
break;
case HDR_D_LED4:
slot = 3;
break;
default:
break;
}
if(slot >= 0 && static_cast<size_t>(slot) < strips.size())
{
const Gen2StripInfo& info = strips[static_cast<size_t>(slot)];
if(info.totalLeds > 0u)
{
zones[zone_idx].leds_count = static_cast<unsigned int>(info.totalLeds);
zones[zone_idx].leds_min = static_cast<unsigned int>(info.totalLeds);
zones[zone_idx].leds_max = static_cast<unsigned int>(info.totalLeds);
zones[zone_idx].segments.clear();
zones[zone_idx].segments.reserve(info.LedsOfStrip.size());
unsigned int offset = 0;
for(size_t si = 0; si < info.LedsOfStrip.size(); ++si)
{
const uint16_t cnt = info.LedsOfStrip[si];
if(cnt == 0) continue;
segment seg;
seg.name = std::string("Segment ") + std::to_string(si);
seg.type = ZONE_TYPE_LINEAR;
seg.start_idx = offset;
seg.leds_count = static_cast<unsigned int>(cnt);
zones[zone_idx].segments.push_back(seg);
offset += static_cast<unsigned int>(cnt);
}
}
}
}
switch(zone_at_idx->idx)
{
case LED4:
@@ -963,7 +826,6 @@ void RGBController_RGBFusion2USB::LoadCustomLayoutFromJson(
&& new_zone->idx <= GB_FUSION2_LED_IDX::LED11)
{
layout->zones[0][zone_idx] = new_zone;
allocated_zones.push_back(new_zone);
}
else
{
@@ -62,26 +62,17 @@ private:
std::string detector_name;
RGBFusion2USBController* controller;
uint8_t device_num;
int device_num;
RGBColor null_color = 0;
bool supports_gen2 = 0;
/*---------------------------------------------------------*\
| The intial value of device_index should point to the |
| layout for the generic_device |
\*---------------------------------------------------------*/
uint32_t device_index = 0;
uint16_t product_id = 0;
uint32_t effects_mask = 0;
void Init_Controller();
int GetLED_Zone(int led_idx);
/*---------------------------------------------------------*\
| Per instance layout lookup tables. |
\*---------------------------------------------------------*/
gb_fusion2_device instance_layout{};
gb_fusion2_layout instance_zones{};
std::vector<gb_fusion2_zone*> allocated_zones;
nlohmann::json WriteCalJsonFrom(
const EncodedCalibration& src);
void FillMissingWith(
@@ -16,21 +16,10 @@
using namespace std::chrono_literals;
struct GoveeHardwareInfo
static std::map<std::string, unsigned int> govee_led_counts
{
unsigned int led_count;
unsigned int matrix_row_len; // 0 = linear
};
const unsigned int GOVEE_FALLBACK_LED_COUNT = 20;
static std::map<std::string, GoveeHardwareInfo> govee_hardware_info
{
{ "H6022", { 132, 12 } }, // Govee Smart Table Lamp 2
{ "H612F", { 12, 0 } }, // Govee Strip Light S (3m)
{ "H619A", { 20, 0 } }, // Govee RGBIC Led Strip Lights
{ "H70B1", { 20, 0 } }, // Govee LED Curtain Lights
{ "H607C", { 174, 0 } }, // Govee Floor Lamp 2
{ "H619A", 20 },
{ "H70B1", 20 },
};
RGBController_Govee::RGBController_Govee(GoveeController* controller_ptr)
@@ -78,64 +67,39 @@ RGBController_Govee::~RGBController_Govee()
void RGBController_Govee::SetupZones()
{
GoveeHardwareInfo hw = { GOVEE_FALLBACK_LED_COUNT, 0 };
bool resizable = true;
std::map<std::string, GoveeHardwareInfo>::iterator it = govee_hardware_info.find(controller->GetSku());
if(it != govee_hardware_info.end())
unsigned int led_count = 0;
std::map<std::string, unsigned int>::iterator it = govee_led_counts.find(controller->GetSku());
if(it != govee_led_counts.end())
{
hw = it->second;
resizable = false;
led_count = it->second;
}
/*-----------------------------------------------------*\
| Fallback so Direct mode is usable even if SKU isn't |
| in the table |
\*-----------------------------------------------------*/
if(led_count == 0)
{
led_count = 20; /* safe default; user can resize in UI */
}
zone strip;
strip.leds_count = hw.led_count;
strip.leds_min = resizable ? 0 : hw.led_count;
strip.leds_max = resizable ? 255 : hw.led_count;
if(hw.matrix_row_len == 0)
strip.name = "Govee Strip";
strip.type = ZONE_TYPE_LINEAR;
strip.leds_count = led_count;
/*-----------------------------------------------------*\
| Only make resizable for unknown SKUs |
\*-----------------------------------------------------*/
if(govee_led_counts.find(controller->GetSku()) == govee_led_counts.end())
{
strip.name = "Govee Strip";
strip.type = ZONE_TYPE_LINEAR;
strip.matrix_map = NULL;
strip.leds_min = 1;
strip.leds_max = 255;
}
else
{
strip.name = "Govee Matrix";
strip.type = ZONE_TYPE_MATRIX;
unsigned int width = hw.matrix_row_len;
unsigned int height = hw.led_count / width;
strip.matrix_map = new matrix_map_type;
strip.matrix_map->height = height;
strip.matrix_map->width = width;
strip.matrix_map->map = new unsigned int[hw.led_count];
/*-----------------------------------------------------*\
| On H6022, LEDs indexed bottom to top, alternating |
| clockwise and counterclockwise for each row. |
\*-----------------------------------------------------*/
for(unsigned int y = 0; y < height; y++)
{
/*-------------------------------------------------*\
| LEDs numbered bottom to top, opposite of matrix |
| clockwise and counterclockwise for each row. |
\*-------------------------------------------------*/
unsigned int led_y = (height - 1) - y;
for(unsigned int x = 0; x < width; x++)
{
/*---------------------------------------------*\
| LED is right-to-left for even rows, including |
| first one |
\*---------------------------------------------*/
unsigned int led_x = led_y & 1 ? x : (width - 1) - x;
strip.matrix_map->map[y * width + x] = led_y * width + led_x;
}
}
strip.leds_min = led_count;
strip.leds_max = led_count;
}
strip.matrix_map = NULL;
zones.push_back(strip);
for(std::size_t led_idx = 0; led_idx < strip.leds_count; led_idx++)
@@ -150,11 +114,6 @@ void RGBController_Govee::SetupZones()
void RGBController_Govee::ResizeZone(int zone, int new_size)
{
if(zones[zone].type == ZONE_TYPE_MATRIX)
{
return;
}
if(zone < 0 || zone >= (int)zones.size() || new_size <= 0)
{
return;
@@ -30,9 +30,28 @@ using namespace std::chrono_literals;
bool TestForHyperXDRAMController(i2c_smbus_interface* bus, unsigned char address)
{
int res = bus->i2c_smbus_read_byte(address);
bool pass = false;
return(res >= 0);
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
LOG_DEBUG("[%s] Writing at address %02X, res=%02X", HYPERX_CONTROLLER_NAME, address, res);
if (res >= 0)
{
pass = true;
for (int i = 0xA0; i < 0xB0; i++)
{
res = bus->i2c_smbus_read_byte_data(address, i);
if (res != i)
{
pass = false;
}
}
}
return(pass);
} /* TestForHyperXDRAMController() */
@@ -118,7 +118,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 16; led_idx++)
{
led fan_1_led;
fan_1_led.name = "Fan 1 LED " + std::to_string(led_idx + 1);
fan_1_led.name = "Fan 1 LED";
fan_1_led.value = fan_1_leds[led_idx];
leds.push_back(fan_1_led);
}
@@ -126,7 +126,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 16; led_idx++)
{
led fan_2_led;
fan_2_led.name = "Fan 2 LED " + std::to_string(led_idx + 1);
fan_2_led.name = "Fan 2 LED";
fan_2_led.value = fan_2_leds[led_idx];
leds.push_back(fan_2_led);
}
@@ -134,7 +134,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 8; led_idx++)
{
led back_led;
back_led.name = "Back LED " + std::to_string(led_idx + 1);
back_led.name = "Back LED";
back_led.value = back_leds[led_idx];
leds.push_back(back_led);
}
@@ -142,7 +142,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 50; led_idx++)
{
led ring_led;
ring_led.name = "Ring LED " + std::to_string(led_idx + 1);
ring_led.name = "Ring LED";
ring_led.value = ring_leds[led_idx];
leds.push_back(ring_led);
}
@@ -150,7 +150,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 1; led_idx++)
{
led logo_led;
logo_led.name = "Logo LED " + std::to_string(led_idx + 1);
logo_led.name = "Logo LED";
logo_led.value = logo_leds[led_idx];
leds.push_back(logo_led);
}
@@ -10,7 +10,6 @@
#include <thread>
#include <hidapi.h>
#include "Detector.h"
#include "ResourceManager.h"
#include "LogManager.h"
#include "LogitechProtocolCommon.h"
#include "LogitechG203LController.h"
@@ -41,8 +40,6 @@
#include "RGBController_LogitechLightspeed.h"
#include "RGBController_LogitechGPowerPlay.h" // Linux-only
#include "RGBController_LogitechX56.h"
#include "LogitechHIDPP20Controller.h"
#include "RGBController_LogitechHIDPP20.h"
using namespace std::chrono_literals;
@@ -87,9 +84,6 @@ using namespace std::chrono_literals;
#define LOGITECH_G502_HERO_PID 0xC08B
#define LOGITECH_G502_LIGHTSPEED_PID 0xC08D
#define LOGITECH_G502_X_PLUS_PID 0xC095
#define LOGITECH_G515_LS_TKL_PID 0xC355
#define LOGITECH_G522_LIGHTSPEED_USB_PID 0x0B19
#define LOGITECH_G522_LIGHTSPEED_DONGLE_PID 0x0B18
#define LOGITECH_G600_PID 0xC24A
#define LOGITECH_G703_LIGHTSPEED_PID 0xC087
#define LOGITECH_G703_HERO_LIGHTSPEED_PID 0xC090
@@ -141,8 +135,6 @@ using namespace std::chrono_literals;
#define LOGITECH_G903_LIGHTSPEED_VIRTUAL_HERO_PID 0x4087
#define LOGITECH_G_PRO_WIRELESS_VIRTUAL_PID 0x4079
#define LOGITECH_POWERPLAY_MAT_VIRTUAL_PID 0x405F
#define LOGITECH_G502_X_PLUS_LIGHTSPEED_VIRTUAL_PID 0x4099
#define LOGITECH_G515_LS_TKL_LIGHTSPEED_VIRTUAL_PID 0x40B4
/*-----------------------------------------------------*\
| Joystick product IDs |
@@ -366,106 +358,6 @@ void DetectLogitechKeyboardG915(hid_device_info* info, const std::string& name)
}
}
static bool ProbeG915ReceiverName(hid_device* dev, std::string& out_name)
{
/*---------------------------------------------------------*\n | HID++ short message name probe. |
| Request: 10 01 03 0E 00 00 00 (get name length) |
| Request: 10 01 03 1E 00 00 00 (get name string) |
| Response: 11 01 03 1E <name bytes...> |
| Verified against G915 TKL (PID 0xC547) which returns |
| "G915 TKL LIGHTSP" (truncated, full: G915 TKL LIGHTSPEED) |
\*---------------------------------------------------------*/
const unsigned char req_len[7] = { 0x10, 0x01, 0x03, 0x0E, 0x00, 0x00, 0x00 };
const unsigned char req_name[7] = { 0x10, 0x01, 0x03, 0x1E, 0x00, 0x00, 0x00 };
unsigned char resp[64] = { 0 };
hid_write(dev, req_len, sizeof(req_len));
hid_read_timeout(dev, resp, sizeof(resp), 100);
hid_write(dev, req_name, sizeof(req_name));
for(int attempt = 0; attempt < 3; attempt++)
{
int rd = hid_read_timeout(dev, resp, sizeof(resp), 200);
if(rd < 8)
{
continue;
}
if(resp[0] == 0x11 && resp[1] == 0x01 && resp[2] == 0x03 && resp[3] == 0x1E)
{
std::string name_str;
for(int i = 4; i < rd; i++)
{
if(resp[i] == 0x00)
{
break;
}
name_str.push_back(static_cast<char>(resp[i]));
}
out_name = name_str;
return true;
}
}
return false;
}
void DetectLogitechKeyboardG915Receiver2(hid_device_info* info, const std::string& name)
{
/*---------------------------------------------------------*\
| PID 0xC547 is shared by multiple Logitech keyboards. |
| Use a HID++ name probe to identify the exact device and |
| route to the correct controller. |
| |
| Known devices behind this PID: |
| "G915 TKL LIGHTSP..." -> G915 TKL (is_tkl = true) |
| "G915 LIGHTSP..." -> G915 full-size (is_tkl = false)|
| "G515..." -> G515 (not handled here) |
\*---------------------------------------------------------*/
hid_device* dev = hid_open_path(info->path);
if(!dev)
{
return;
}
std::string probed_name;
bool ok = ProbeG915ReceiverName(dev, probed_name);
if(!ok)
{
LOG_DEBUG("[LogitechControllerDetect] 0xC547 name probe failed, skipping device");
hid_close(dev);
return;
}
LOG_DEBUG("[LogitechControllerDetect] 0xC547 probe returned name=\"%s\"", probed_name.c_str());
/*---------------------------------------------------------*\
| Route based on probed name. Check for TKL before full |
| G915 since both contain "G915". |
\*---------------------------------------------------------*/
if(probed_name.find("G915 TKL") != std::string::npos)
{
LogitechG915Controller* controller = new LogitechG915Controller(dev, false, name);
RGBController_LogitechG915* rgb_controller = new RGBController_LogitechG915(controller, true);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else if(probed_name.find("G915") != std::string::npos)
{
LogitechG915Controller* controller = new LogitechG915Controller(dev, false, name);
RGBController_LogitechG915* rgb_controller = new RGBController_LogitechG915(controller, false);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else
{
/*-----------------------------------------------------*\
| Unknown device (e.g. G515 or future hardware). |
| Close and leave it for another detector to claim. |
\*-----------------------------------------------------*/
LOG_DEBUG("[LogitechControllerDetect] 0xC547 unrecognised device name \"%s\", skipping",
probed_name.c_str());
hid_close(dev);
}
}
void DetectLogitechKeyboardG915Wired(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
@@ -758,220 +650,6 @@ void DetectLogitechX56(hid_device_info* info, const std::string& name)
}
}
/*------------------------------------------------------------------------------*\
| Unified HID++ 2.0 Detection |
| Probes IRoot (feature 0x0000) to determine if the device speaks HID++ 2.0. |
| If it does and has RGB features, the unified controller handles it. |
| If not, the device is released for legacy controllers. |
\*------------------------------------------------------------------------------*/
void DetectLogitechHIDPP20(hid_device_info* info, const std::string& /*name*/)
{
hid_device* dev = hid_open_path(info->path);
if(!dev)
{
return;
}
LogitechHIDPP20Controller* controller = new LogitechHIDPP20Controller(
dev, info->path, LOGITECH_DEFAULT_DEVICE_INDEX, false, nullptr,
info->usage_page);
if(controller->Probe())
{
controller->Initialize();
const HIDPP20DeviceCapabilities& caps = controller->GetCapabilities();
if(caps.has_zone_effects || caps.has_perkey)
{
/*-------------------------------------------------*\
| Device has RGB features — create and register |
| RGBController for the UI. |
\*-------------------------------------------------*/
RGBController_LogitechHIDPP20* rgb_controller = new RGBController_LogitechHIDPP20(controller);
LOG_INFO("[%s] Registering RGB controller", caps.device_name.c_str());
ResourceManager::get()->RegisterRGBController(rgb_controller);
/*--------------------------------------------------*\
| Start reader + power threads immediately so we |
| detect connection events and handle power mgmt |
| from the start — not deferred to DeviceUpdateMode. |
\*--------------------------------------------------*/
if(caps.has_power_mgmt || caps.idx_wireless_status != 0)
{
controller->StartPowerManager();
if(!caps.has_power_mgmt && caps.idx_wireless_status != 0)
{
controller->StartEventWatcher();
}
}
}
else if(controller->HasBridge())
{
/*--------------------------------------------------*\
| Centurion dongle with no sub-device — keep the |
| controller alive and start reader thread to watch |
| for sub-device connection events. |
\*--------------------------------------------------*/
LOG_INFO("[%s] Dongle registered, watching for sub-device",
caps.device_name.c_str());
controller->SetRegisterCallback([](RGBController* rgb)
{
ResourceManager::get()->RegisterRGBController(rgb);
});
controller->StartEventWatcher();
}
else
{
/*--------------------------------------------------*\
| Device probed successfully but has no RGB and no |
| bridge — nothing to do (e.g., headset without RGB) |
\*--------------------------------------------------*/
LOG_INFO("[%s] No RGB features, skipping", caps.device_name.c_str());
delete controller;
}
}
else
{
/*--------------------------------------------------*\
| Probe failed. Could be an offline paired device, |
| a stale pairing slot, or a receiver itself. |
| |
| Only skip if the name explicitly says "Receiver". |
| Everything else gets a watcher — devices can come |
| back at any time (power cycle, dongle swap, etc.) |
\*--------------------------------------------------*/
std::string hid_name;
if(info->product_string)
{
std::wstring ws(info->product_string);
hid_name = std::string(ws.begin(), ws.end());
}
if(hid_name.find("Receiver") != std::string::npos ||
hid_name.find("receiver") != std::string::npos)
{
delete controller;
}
else
{
LOG_INFO("[HID++2.0 %s] Probe failed — watching for device (name='%s')",
info->path, hid_name.c_str());
controller->SetRegisterCallback([](RGBController* rgb)
{
ResourceManager::get()->RegisterRGBController(rgb);
});
controller->StartProbeWatcher();
}
}
}
#if defined(_WIN32) || defined(__APPLE__)
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Unified HID++ 2.0 Lightspeed Receiver Detection (Windows / macOS) |
| |
| On Linux, hid-logitech-dj splits receiver traffic into per-slot virtual child hidraw nodes with their own 0x40XX PIDs, so Linux detection can |
| use DetectLogitechHIDPP20 directly against the virtual PIDs. Windows and macOS have no DJ driver — the receiver appears as a single HID device |
| and we must probe each paired slot by hand, addressing it via the HID++ device_index header byte. |
| |
| Iterates device indices 0x01..0x06. c547 is dual-pair, but the loop covers Unifying-style receivers and any future wider-pair variants. Each |
| responding slot gets its own hid_device handle and a shared std::mutex so sibling slots serialize HID writes — matching the pattern in the |
| legacy LogitechLightspeedController (see CreateLogitechLightspeedDevice around line 860). |
| |
| TODO (untested on Windows): runtime reader-thread coordination. Each slot controller starts its own reader thread; on a shared receiver both |
| threads will see both slots' incoming packets. The reader needs to drop frames whose device_index doesn't match its own, or dispatch across |
| sibling controllers. Safe during probe (mutex serializes writes, reads are direct); becomes an issue post-StartPowerManager. |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
void DetectLogitechHIDPP20LightspeedReceiver(hid_device_info* info, const std::string& /*name*/)
{
std::shared_ptr<std::mutex> receiver_mutex = std::make_shared<std::mutex>();
for(uint8_t idx = 0x01; idx <= 0x06; idx++)
{
hid_device* dev = hid_open_path(info->path);
if(!dev)
{
continue;
}
LogitechHIDPP20Controller* controller = new LogitechHIDPP20Controller(
dev, info->path, idx, true, receiver_mutex, info->usage_page);
if(!controller->Probe())
{
/*--------------------------------------------------*\
| Slot is empty, stale, or not HID++ 2.0. Destructor |
| closes the per-slot handle we opened above. |
\*--------------------------------------------------*/
delete controller;
continue;
}
controller->Initialize();
const HIDPP20DeviceCapabilities& caps = controller->GetCapabilities();
if(caps.has_zone_effects || caps.has_perkey)
{
RGBController_LogitechHIDPP20* rgb_controller = new RGBController_LogitechHIDPP20(controller);
LOG_INFO("[%s slot=%u] Registering RGB controller", caps.device_name.c_str(), idx);
ResourceManager::get()->RegisterRGBController(rgb_controller);
if(caps.has_power_mgmt || caps.idx_wireless_status != 0)
{
controller->StartPowerManager();
if(!caps.has_power_mgmt && caps.idx_wireless_status != 0)
{
controller->StartEventWatcher();
}
}
}
else
{
LOG_INFO("[%s slot=%u] No RGB features, skipping", caps.device_name.c_str(), idx);
delete controller;
}
}
}
#endif
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Unified HID++ 2.0 Devices |
| PID-specific registrations for devices tested with the unified controller. |
| Wired paths use the device's own USB PID; wireless paths on Linux use the hid-logitech-dj virtual child PIDs (0x40XX range). |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 G502 X Plus (wired)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G502_X_PLUS_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 G515 LS TKL (wired)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G515_LS_TKL_PID, 2, 0xFF00, 2);
#ifdef __linux__
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 G502 X Plus (wireless)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G502_X_PLUS_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 G515 LS TKL (wireless)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G515_LS_TKL_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
#endif
#if defined(_WIN32) || defined(__APPLE__)
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 Lightspeed Receiver (C547)", DetectLogitechHIDPP20LightspeedReceiver, LOGITECH_VID, 0xC547, 2, 0xFF00, 2);
#endif
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Centurion-transport devices (63-byte reports on usage page 0xFFA0, 0x50 addressed or 0x51 direct). |
| Centurion receivers are not DJ-style Lightspeed receivers and are not split by hid-logitech-dj — the dongle PID enumerates as a single hidraw |
| on all platforms. The controller's DiscoverTransport parses the report descriptor to pick the 0x50/0x51 variant and runs a 0x00..0xFF address |
| sweep for the 0x50 (addressed) variant, so the detector only needs VID/PID + usage page 0xFFA0. |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_P("Logitech HID++ 2.0 G522 Lightspeed (wired)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G522_LIGHTSPEED_USB_PID, 0xFFA0);
REGISTER_HID_DETECTOR_P("Logitech HID++ 2.0 G522 Lightspeed (dongle)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G522_LIGHTSPEED_DONGLE_PID, 0xFFA0);
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Keyboards |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
@@ -989,7 +667,7 @@ REGISTER_HID_DETECTOR_IP ("Logitech G910 Orion Spectrum", Dete
REGISTER_HID_DETECTOR_IP ("Logitech G Pro RGB Mechanical Gaming Keyboard", DetectLogitechKeyboardGPro, LOGITECH_VID, LOGITECH_GPRO_KEYBOARD_1_PID, 1, 0xFF43);
REGISTER_HID_DETECTOR_IPU("Logitech G915 Wireless RGB Mechanical Gaming Keyboard", DetectLogitechKeyboardG915, LOGITECH_VID, LOGITECH_G915_RECEIVER_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915 Wireless RGB Mechanical Gaming Keyboard (Receiver 2)", DetectLogitechKeyboardG915Receiver2, LOGITECH_VID, LOGITECH_G915_RECEIVER_2_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915 Wireless RGB Mechanical Gaming Keyboard", DetectLogitechKeyboardG915, LOGITECH_VID, LOGITECH_G915_RECEIVER_2_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915 Wireless RGB Mechanical Gaming Keyboard (Wired)", DetectLogitechKeyboardG915Wired, LOGITECH_VID, LOGITECH_G915_WIRED_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915TKL Wireless RGB Mechanical Gaming Keyboard", DetectLogitechKeyboardG915, LOGITECH_VID, LOGITECH_G915TKL_RECEIVER_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915TKL Wireless RGB Mechanical Gaming Keyboard (Wired)", DetectLogitechKeyboardG915Wired, LOGITECH_VID, LOGITECH_G915TKL_WIRED_PID, 2, 0xFF00, 2);
@@ -1005,11 +683,11 @@ REGISTER_HID_DETECTOR_IP ("Logitech G600 Gaming Mouse", Dete
REGISTER_HID_DETECTOR_IP ("Logitech G Pro Gaming Mouse", DetectLogitechMouseGPRO, LOGITECH_VID, LOGITECH_G_PRO_PID, 1, 0xFF00);
REGISTER_HID_DETECTOR_IP ("Logitech G Pro HERO Gaming Mouse", DetectLogitechMouseGPRO, LOGITECH_VID, LOGITECH_G_PRO_HERO_PID, 1, 0xFF00);
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Speakers |
| Speakers |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Logitech G560 Lightsync Speaker", DetectLogitechG560, LOGITECH_VID, LOGITECH_G560_PID, 2, 0xFF43, 514);
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Headsets |
| Headsets |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Logitech G933 Lightsync Headset", DetectLogitechG933, LOGITECH_VID, LOGITECH_G933_PID, 3, 0xFF43, 514);
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
@@ -1212,12 +890,12 @@ void DetectLogitechWireless(hid_device_info* info, const std::string& /*name*/)
}
}
/*--------------------------------------------------------------------------------------------------------------------------------------------------*\
| Lightspeed Devices (Linux Wireless) |
| |
| DUMMY_DEVICE_DETECTOR("Logitech G Lightspeed Receiver", DetectLogitechWireless, 0x046D, 0xC539 ) |
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Lightspeed Devices (Linux Wireless) |
| |
| DUMMY_DEVICE_DETECTOR("Logitech G Lightspeed Receiver", DetectLogitechWireless, 0x046D, 0xC539 ) |
| DUMMY_DEVICE_DETECTOR("Logitech Powerplay Mat Receiver", DetectLogitechWireless, 0x046D, 0xC53A ) |
\*--------------------------------------------------------------------------------------------------------------------------------------------------*/
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Logitech G403 Wireless Gaming Mouse", DetectLogitechWireless, LOGITECH_VID, LOGITECH_G403_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G502 Wireless Gaming Mouse", DetectLogitechWireless, LOGITECH_VID, LOGITECH_G502_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G703 Wireless Gaming Mouse", DetectLogitechWireless, LOGITECH_VID, LOGITECH_G703_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
@@ -1235,6 +913,7 @@ REGISTER_HID_DETECTOR_IPU("Logitech Powerplay Mat",
| G502 changed to PU to accomodate old and new firmware. Other devices may require similar update #4627 |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_PU("Logitech G502 Wireless Gaming Mouse (wired)", DetectLogitechWired, LOGITECH_VID, LOGITECH_G502_LIGHTSPEED_PID, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G502 X Plus (wired)", DetectLogitechWired, LOGITECH_VID, LOGITECH_G502_X_PLUS_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G502 Proteus Spectrum Gaming Mouse", DetectLogitechWired, LOGITECH_VID, LOGITECH_G502_PROTEUS_SPECTRUM_PID, 1, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G502 HERO Gaming Mouse", DetectLogitechWired, LOGITECH_VID, LOGITECH_G502_HERO_PID, 1, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G403 Prodigy Gaming Mouse", DetectLogitechWired, LOGITECH_VID, LOGITECH_G403_PID, 1, 0xFF00, 2);
@@ -146,7 +146,7 @@ void LogitechG915Controller::SendCommit()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, 4);
hid_read_timeout(dev_handle, usb_buf, RESPONSE_LEN, LOGITECH_READ_TIMEOUT);
}
@@ -170,7 +170,7 @@ void LogitechG915Controller::BeginModeSet()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, 4);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
@@ -189,7 +189,7 @@ void LogitechG915Controller::BeginModeSet()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, 4);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -216,7 +216,7 @@ void LogitechG915Controller::InitializeModeSet()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, 7);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -240,7 +240,7 @@ void LogitechG915Controller::InitializeDirect()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, 4);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
@@ -259,7 +259,7 @@ void LogitechG915Controller::InitializeDirect()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, 4);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
@@ -279,7 +279,7 @@ void LogitechG915Controller::InitializeDirect()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, 17);
hid_read(dev_handle, usb_buf, MESSAGE_LEN);
/*-----------------------------------------------------*\
@@ -300,7 +300,7 @@ void LogitechG915Controller::InitializeDirect()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, 17);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -355,7 +355,7 @@ void LogitechG915Controller::SendDirectFrame
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, HEADER_SIZE + length);
hid_read_timeout(dev_handle, usb_buf, RESPONSE_LEN, LOGITECH_READ_TIMEOUT);
}
@@ -370,7 +370,7 @@ void LogitechG915Controller::SendMode
unsigned char blue
)
{
unsigned char usb_buf[MESSAGE_LEN];
unsigned char usb_buf[HEADER_SIZE + 13];
/*-----------------------------------------------------*\
| Zero out buffer |
@@ -433,6 +433,6 @@ void LogitechG915Controller::SendMode
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_write(dev_handle, usb_buf, HEADER_SIZE + 13);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -1,874 +0,0 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller.h |
| |
| Unified Logitech HID++ 2.0 controller |
| |
| Uses feature discovery (IRoot 0x0000) to dynamically |
| determine device capabilities and adapt to any HID++ |
| 2.0 device with RGB lighting support. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <map>
#include <mutex>
#include <memory>
#include <thread>
#include <atomic>
#include <functional>
#include <chrono>
#include <deque>
#include <condition_variable>
#include <hidapi.h>
#include "RGBController.h"
#include "LogitechProtocolCommon.h"
/*-----------------------------------------------------*\
| HID++ 2.0 Feature Page IDs |
\*-----------------------------------------------------*/
#define HIDPP20_FEAT_IROOT 0x0000
#define HIDPP20_FEAT_FEATURE_SET 0x0001
#define HIDPP20_FEAT_DEVICE_NAME_TYPE 0x0005
#define HIDPP20_FEAT_ONBOARD_PROFILES 0x8100
#define HIDPP20_FEAT_PROFILE_MANAGEMENT 0x8101
#define HIDPP20_FEAT_FIRMWARE_INFO 0x0003
#define HIDPP20_FEAT_CENTPPBRIDGE 0x0003 /* same ID, different meaning on Centurion */
#define HIDPP20_FEAT_COLOR_LED_EFFECTS 0x8070
#define HIDPP20_FEAT_RGB_EFFECTS 0x8071
#define HIDPP20_FEAT_PER_KEY_LIGHTING_V1 0x8080
#define HIDPP20_FEAT_PER_KEY_LIGHTING_V2 0x8081
#define HIDPP20_FEAT_KEYBOARD_LAYOUT 0x4540
#define HIDPP20_FEAT_DISABLE_KEYS_BY_USAGE 0x4522
#define HIDPP20_FEAT_CENTURION_RGB 0x0600
#define HIDPP20_FEAT_HEADSET_RGB_HOSTMODE 0x0620
#define HIDPP20_FEAT_CENTURION_DEVICE_INFO 0x0100
#define HIDPP20_FEAT_CENTURION_DEVICE_NAME 0x0101
#define HIDPP20_FEAT_UNIFIED_BATTERY 0x1004
#define HIDPP20_FEAT_WIRELESS_STATUS 0x1D4B
/*-----------------------------------------------------*\
| HID++ 2.0 Function IDs (byte 3 high nibble) |
| Function ID is shifted left 4 bits, low nibble = swID |
\*-----------------------------------------------------*/
/*-----------------------------------------------------*\
| HID++ Software ID — identifies our responses. |
| Must avoid: 0x00 (firmware), 0x01 (vendor app), |
| 0x02-0x0F (Solaar cycles these). |
| There are only 16 values (4-bit field), and all are |
| claimed. We pick a fixed value and will coordinate |
| with Solaar to exclude it from its cycle. |
\*-----------------------------------------------------*/
#define HIDPP20_SW_ID 0x07
/*-----------------------------------------------------*\
| Feature 0x8071 functions |
\*-----------------------------------------------------*/
#define FN_8071_GET_INFO 0x00
#define FN_8071_SET_EFFECT 0x10
#define FN_8071_SET_PATTERN 0x20
#define FN_8071_NV_CONFIG 0x30
#define FN_8071_BIN_INFO 0x40
#define FN_8071_SW_CONTROL 0x50
#define FN_8071_SYNC 0x60
#define FN_8071_PWR_CONFIG 0x70
#define FN_8071_PWR_MODE 0x80
/*-----------------------------------------------------*\
| Feature 0x8081 functions |
\*-----------------------------------------------------*/
#define FN_8081_GET_INFO 0x00
#define FN_8081_SET_INDIVIDUAL 0x10
#define FN_8081_SET_CONSECUTIVE 0x20
#define FN_8081_SET_DELTA_5BIT 0x30
#define FN_8081_SET_DELTA_4BIT 0x40
#define FN_8081_SET_RANGE 0x50
#define FN_8081_SET_SINGLE_VALUE 0x60
#define FN_8081_FRAME_END 0x70
/*-----------------------------------------------------*\
| Feature 0x0620 functions (headset RGB hostmode) |
\*-----------------------------------------------------*/
#define FN_0620_GET_INFO 0x00
#define FN_0620_GET_RGB_ZONE_INFO 0x10
#define FN_0620_SET_INDIVIDUAL_RGB_ZONES 0x20
#define FN_0620_SET_CONSECUTIVE_RGB_ZONES 0x30
#define FN_0620_SET_RANGE_RGB_ZONES 0x40
#define FN_0620_SET_RGB_ZONES_SINGLE_VALUE 0x50
#define FN_0620_FRAME_END 0x60
#define FN_0620_GET_HOST_MODE_STATE 0x70
#define FN_0620_SET_HOST_MODE_STATE 0x80
/*-----------------------------------------------------*\
| Feature 0x8100 functions |
\*-----------------------------------------------------*/
#define FN_8100_SET_ONBOARD_MODE 0x10
#define FN_8100_GET_ONBOARD_MODE 0x20
/*-----------------------------------------------------*\
| Feature 0x8101 functions |
\*-----------------------------------------------------*/
#define FN_8101_GET_SET_MODE 0x60
#define FN_8101_LOAD 0x80 // load(partition, sector, size)
#define FN_8101_READBUFFER 0xC0 // readBuffer(offset)
/*-----------------------------------------------------*\
| Zone cluster effect entry |
\*-----------------------------------------------------*/
struct HIDPP20Effect
{
uint8_t index;
uint16_t effect_id;
uint16_t capabilities;
uint16_t default_period;
};
/*-----------------------------------------------------*\
| Zone cluster info from 0x8071 GetRgbClusterInfo |
\*-----------------------------------------------------*/
struct HIDPP20ZoneCluster
{
uint8_t index;
uint16_t location;
uint8_t effect_count;
std::vector<HIDPP20Effect> effects;
};
/*-----------------------------------------------------*\
| Per-model device quirks — behavioral differences that |
| can't be detected via feature probing. |
\*-----------------------------------------------------*/
enum HIDPP20DeviceQuirks : uint32_t
{
HIDPP20_QUIRK_NONE = 0,
HIDPP20_QUIRK_FADE_ACCEPTS_WRITES = (1 << 0), // firmware accepts host frames during sleep fade without waking
};
struct HIDPP20DeviceQuirkEntry
{
uint16_t pid_wireless;
uint16_t pid_wired;
uint32_t quirks;
};
/*---------------------------------------------------------*\
| Default: suppress frames while SLEEPING. Safe everywhere |
| — it cannot wake a device that treats writes as activity. |
| Devices listed here opt out of suppression because their |
| firmware accepts writes during the fade without |
| cancelling sleep. |
\*---------------------------------------------------------*/
static constexpr HIDPP20DeviceQuirkEntry HIDPP20_DEVICE_QUIRK_TABLE[] =
{
{ 0x40B4, 0xC355, HIDPP20_QUIRK_FADE_ACCEPTS_WRITES }, // G515 LS TKL
};
/*-----------------------------------------------------*\
| Device capabilities discovered via feature probing |
\*-----------------------------------------------------*/
struct HIDPP20DeviceCapabilities
{
std::string device_name;
uint8_t device_type;
std::string firmware_version;
std::string serial_number;
std::string unit_id; // stable hardware ID (from FirmwareInfo fn0)
uint16_t pid_wireless; // wireless virtual PID (from FirmwareInfo fn0)
uint16_t pid_wired; // wired USB PID (from FirmwareInfo fn0)
uint32_t quirks; // resolved from HIDPP20_DEVICE_QUIRK_TABLE after PID discovery
/*--------------------------------------------------*\
| Complete feature map (feature_id → runtime index) |
| Built once by EnumerateFeatures, used by all |
| subsequent GetFeatureIndex lookups (no wire). |
\*--------------------------------------------------*/
std::map<uint16_t, uint8_t> feature_map;
std::map<uint16_t, uint8_t> feature_versions; /* feature_id -> version byte */
bool feature_map_complete; // true after bulk enumeration
/*-------------------------------------------------*\
| Feature indices (0 = not supported) |
\*-------------------------------------------------*/
uint8_t idx_onboard_profiles;
uint8_t idx_profile_management;
uint8_t idx_rgb_effects;
uint8_t idx_headset_rgb_hostmode; /* 0x0620 — Centurion headset RGB */
uint8_t idx_perkey_v2;
uint8_t idx_perkey_v1;
uint8_t idx_wireless_status;
uint8_t idx_disable_keys_by_usage; /* 0x4522 — keyboard-family handshake */
uint16_t rgb_feature_page;
/*--------------------------------------------------*\
| Resolved function IDs for the RGB effects feature |
| Varies between 0x8070, 0x8071, 0x0600 |
\*--------------------------------------------------*/
uint8_t fn_set_effect;
uint8_t fn_sw_control;
uint8_t fn_pwr_config;
uint8_t fn_pwr_mode;
bool has_power_mgmt;
bool sw_control_simple;
/*--------------------------------------------------*\
| Persistent NV settings read from RGBEffects fn3 |
| (FN_8071_NV_CONFIG). Capability 0x0020 is the |
| sleep ramp / off-ramp transition (enabled + |
| ramp_seconds). |
\*--------------------------------------------------*/
bool nv_sleep_ramp_known;
bool nv_sleep_ramp_enabled;
uint8_t nv_sleep_ramp_seconds;
/*---------------------------------------------------*\
| Device-firmware effect cards (0x8071 fn0 |
| GetEffectSpecificInfo). Populated by |
| DiscoverEffectCards at feature-discovery time. |
| |
| has_effect_cards — probe returned a valid response |
| for firmware card 0 page 1 (no InvalidArgument). |
| effect_card_template[0..1] — device-wide constant |
| bytes read from that response at data[10..11]. |
| Echoed back into prep1 of DoObservedPerKeyPrep |
| at SetEffectByIndex params[6..7]. |
| |
| Gate for the observed per-key prep is now |
| has_effect_cards — replaces the earlier |
| "effects.size() < 5" heuristic, which was a proxy |
| that correlated with "has cards" on the devices we |
| happened to know but had no principled meaning. |
\*---------------------------------------------------*/
bool has_effect_cards;
uint8_t effect_card_template[2];
/*-------------------------------------------------*\
| Discovered zone and LED data |
\*-------------------------------------------------*/
std::vector<HIDPP20ZoneCluster> zone_clusters;
std::vector<uint16_t> perkey_zone_ids;
std::vector<uint8_t> headset_rgb_hostmode_zone_ids; /* 0x0620 fn1 result */
bool has_perkey;
bool has_zone_effects;
bool is_headset_rgb_hostmode; /* 0x0620 path selected */
bool has_numpad;
uint8_t keyboard_layout_code;
};
/*------------------------------------------------------*\
| Transport type — determines wire framing |
\*------------------------------------------------------*/
enum HIDPP20TransportType
{
HIDPP20_TRANSPORT_STANDARD, // 0xFF00/0xFF43: report IDs 0x10/0x11, 7/20 bytes
HIDPP20_TRANSPORT_CENTURION // 0xFFA0: report ID 0x51 or 0x50, 64 bytes,
// with CPL framing and CentPPBridge sub-device routing
};
/*------------------------------------------------------*\
| Transport layer — abstracts wire format differences |
| |
| Standard HID++ and Centurion both carry the same |
| feature/function/data payload, but with different |
| report framing. This struct holds transport state |
| so SendMessage/ReadMessage can adapt. |
\*------------------------------------------------------*/
struct HIDPP20Transport
{
HIDPP20TransportType type;
uint16_t usage_page; // 0xFF00, 0xFF43, or 0xFFA0
uint8_t report_id; // 0x10/0x11 for standard, 0x51/0x50 for Centurion
bool addressed; // Centurion 0x50 has device address byte
uint8_t device_address; // Centurion 0x50: device address (e.g., 0x23)
uint8_t bridge_feat_idx; // CentPPBridge feature index on parent (0 if N/A)
uint8_t sub_device_id; // CentPPBridge sub-device ID (typically 0)
uint16_t bridge_mtu; // CentPPBridge MTU from getConnectionInfo:
// 0 = no sub-device, sendFragment will fail
// >0 = sub-device present, payload size in bytes
};
/*-----------------------------------------------------*\
| Power management state machine |
| Matches Solaar's RGBPowerManager states |
\*-----------------------------------------------------*/
enum HIDPP20PowerState
{
HIDPP20_POWER_ACTIVE = 0,
HIDPP20_POWER_DIMMING = 1,
HIDPP20_POWER_IDLE = 2,
HIDPP20_POWER_SLEEPING = 3,
};
/*-----------------------------------------------------*\
| Parsed HID++ message for the response queue |
\*-----------------------------------------------------*/
struct HIDPP20RawMessage
{
uint8_t feat;
uint8_t func;
uint8_t data[60];
int result;
};
/*------------------------------------------------------*\
| Per-key write tracking. SendPerKeyData is fire-and- |
| forget at the wire layer; we track which zones each |
| outstanding write covers so PerKeyFrameEnd can match |
| ACKs back by FIFO order and report which zones the |
| firmware actually committed. |
\*------------------------------------------------------*/
struct OutstandingPerKeyWrite
{
uint8_t function; // FN_8081_* (high nibble carries the type)
std::vector<uint8_t> zone_ids; // zones covered by this packet
};
/*-----------------------------------------------------*\
| Result of a per-key frame commit. The caller uses |
| these to update its delta-tracking state: |
| - frame_end_acked: did the FrameEnd packet ACK? |
| - acked_zones: zones whose write packet ACKed |
| - attempted_zones: every zone written this frame |
\*-----------------------------------------------------*/
struct PerKeyFrameResult
{
bool frame_end_acked;
std::vector<uint8_t> acked_zones;
std::vector<uint8_t> attempted_zones;
};
/*-------------------------------------------------------*\
| Retry policy for SendAcked. |
| |
| Controls the send/read/retry loop for a single HID++ |
| request. Three canned policies cover all use cases: |
| |
| Reliable: probe/discovery/set/get (~6s worst case) |
| FrameEnd: per-key commit gate (~230ms worst) |
| Streaming: per-key write inside the animation loop |
| (~80ms worst) |
| |
| backoff_ms[i] is the delay applied BEFORE the i-th |
| attempt. backoff_ms[0] is normally 0 (no delay before |
| the first send). The schedule mirrors the firmware's |
| own event burst pattern (63→125→250→500→1000→2000ms, |
| "catch at least one of N"). |
\*-------------------------------------------------------*/
struct HIDPP20RetryPolicy
{
const uint16_t* backoff_ms; // schedule[i] = delay before attempt i
uint8_t attempts; // length of backoff_ms (>=1)
uint16_t read_window_ms; // per-attempt read budget
bool flush_before; // flush response queue at call start
bool retry_on_busy; // BUSY (0x08) -> retry the send
const char* name; // for logging ("reliable", etc.)
};
/*------------------------------------------------------*\
| Canned backoff schedules. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_BACKOFF_RELIABLE[] =
{ 0, 63, 125, 250, 500, 1000, 2000 };
static constexpr uint16_t HIDPP20_BACKOFF_PROBE[] =
{ 0, 100 };
/*------------------------------------------------------*\
| SW-control reclaim backoff. Used by ReconnectDevice |
| to retry the claim+push sequence after a wireless |
| reconnect, racing the firmware boot animation. The |
| vendor app typically lands control in ~50ms; this |
| schedule fits |
| 6 attempts inside ~620ms so the animation never gets |
| a chance to become visible. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_RECLAIM_BACKOFF_MS[] =
{ 0, 20, 40, 80, 160, 320 };
/*------------------------------------------------------*\
| FrameEnd BUSY retry backoff. Used by PerKeyFrameEnd |
| when the firmware returns HID++ error 0x08 (BUSY) |
| because it's still draining the per-key write queue. |
| First retry is fast (~2 USB round trips) for the |
| common case where BUSY was transient; subsequent |
| retries give actual drain headroom. Total worst case |
| ~180ms, fits inside the PerKeyFrameEnd 300ms deadline |
| with margin for the eventual ACK to land. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_FRAME_END_BUSY_BACKOFF_MS[] =
{ 30, 60, 90 };
/*-----------------------------------------------------*\
| Deep-sleep detection threshold. After StartSleep() |
| commands the firmware fade, the device eventually |
| enters deep sleep and returns BUSY to every FrameEnd. |
| Once this many consecutive FrameEnd attempts exhaust |
| all BUSY retries while power_state == SLEEPING, we |
| suppress further frame sends until Wake() fires. |
\*-----------------------------------------------------*/
static constexpr int HIDPP20_DEEP_SLEEP_FAILURE_THRESHOLD = 5;
/*------------------------------------------------------*\
| Per-key frame retry backoff. Used when a full |
| DeviceUpdateLEDs pass completes with some zones |
| unacked (partial commit). The retry re-runs a whole |
| frame from the power thread, so the backoff is |
| between full frames, not individual packets. |
| |
| First value aligned to the power thread's 50ms poll |
| cadence — anything shorter rounds up anyway, and |
| matching the tick makes latency predictable. |
| |
| Worst case: 5 retries, cumulative ~1550ms. This |
| covers the reconnect-transient window where the G502 |
| firmware silently drops per-key writes for several |
| hundred ms after the wireless link re-establishes. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_REPAINT_RETRY_BACKOFF_MS[] =
{ 50, 100, 200, 400, 800 };
static constexpr HIDPP20RetryPolicy HIDPP20_POLICY_RELIABLE = {
HIDPP20_BACKOFF_RELIABLE,
sizeof(HIDPP20_BACKOFF_RELIABLE) / sizeof(uint16_t),
300, // read window
true, // flush_before
true, // retry_on_busy
"reliable"
};
/*------------------------------------------------------*\
| Probe policy: tight budget for is-this-HID++ checks |
| during initial discovery. ~500ms worst case for dead |
| devices, vs ~6s for reliable. One retry handles a |
| transient hiccup on the first IRoot call (e.g. on a |
| busy mouse), but we bail fast on truly non-responsive |
| or non-HID++ hidraws so probe latency stays bounded. |
\*------------------------------------------------------*/
static constexpr HIDPP20RetryPolicy HIDPP20_POLICY_PROBE = {
HIDPP20_BACKOFF_PROBE,
sizeof(HIDPP20_BACKOFF_PROBE) / sizeof(uint16_t),
200, // read window
true, // flush_before
true, // retry_on_busy
"probe"
};
class LogitechHIDPP20Controller
{
public:
LogitechHIDPP20Controller(hid_device* dev, const char* path,
uint8_t device_index, bool wireless,
std::shared_ptr<std::mutex> mutex_ptr,
uint16_t usage_page = 0xFF00);
~LogitechHIDPP20Controller();
/*-------------------------------------------------*\
| Lifecycle |
\*-------------------------------------------------*/
bool Probe();
void Initialize();
void Shutdown();
/*-------------------------------------------------*\
| Accessors |
\*-------------------------------------------------*/
const HIDPP20DeviceCapabilities& GetCapabilities() const;
std::string GetDeviceLocation();
std::string GetSerialString();
uint32_t GetInitGeneration() const;
/*--------------------------------------------------*\
| Per-key lighting (0x8081) |
| |
| Per-key writes are fire-and-forget at the wire |
| layer. SendPerKeyData enqueues an outstanding |
| write entry; PerKeyFrameEnd drains the response |
| queue, matches ACKs by FIFO, and returns which |
| zones the firmware actually committed plus |
| whether the FrameEnd itself ACKed. |
\*--------------------------------------------------*/
void SetPerKeyColors(const std::vector<std::pair<uint16_t, RGBColor>>& zone_colors);
void SetAllPerKeyColor(RGBColor color);
void SendPerKeyData(uint8_t perkey_idx, uint8_t function,
const uint8_t* data, size_t len,
const std::vector<uint8_t>& zone_ids);
PerKeyFrameResult PerKeyFrameEnd();
/*-------------------------------------------------*\
| Zone effects (0x8071 / 0x8070) |
\*-------------------------------------------------*/
void SetZoneEffect(uint8_t cluster_idx, uint8_t effect_idx,
uint16_t effect_id,
unsigned char r, unsigned char g, unsigned char b,
uint16_t period, unsigned char brightness,
unsigned char direction, bool persist);
/*---------------------------------------------------*\
| Headset RGB hostmode (0x0620). |
| Sticky-claim model: SetHostMode() claims once via |
| fn8, then each write is fn5 (single-value) or fn2 |
| (individual) + fn6 FrameEnd[0x01]. 0x02 persist was |
| tested and does not work on G522 firmware. |
\*---------------------------------------------------*/
void SetHeadsetRGBHostmodeColors(const std::vector<RGBColor>& zone_colors);
/*-------------------------------------------------*\
| SW control management |
\*-------------------------------------------------*/
int SetSWControl(uint8_t mode, uint8_t flags);
void SetRGBPowerMode(uint8_t mode);
void SetHostMode();
bool ClaimSWControlIfNeeded();
void UpgradeSwControlAfterFirstPaint();
/*-------------------------------------------------*\
| Keyboard-family handshake (0x4522 fn3 + fn1). |
| G815 / G915 / G Pro send this before any mode |
| write. Feature-gated no-op on devices (G502 / |
| G515) that don't enumerate 0x4522. |
\*-------------------------------------------------*/
void DoDisableKeysByUsageHandshake();
/*--------------------------------------------------*\
| Keyboard-family per-key takeover prep. |
| Per-cluster SetEffectByIndex(effectIdx=0=Off, |
| persist=1) + primer key via SetIndividualRgbZones |
| + FrameEnd. Matches G815 / G915 InitializeDirect. |
| Gated on 0x4522 + per-key V2 presence. |
\*--------------------------------------------------*/
void DoKeyboardFamilyPerKeyPrep();
/*--------------------------------------------------*\
| Wake-repaint flag. Set by Wake() before calling |
| request_repaint_fn so the repaint callback knows |
| to invalidate sent_colors (force a full per-key |
| push) without triggering the claim/prep sequence. |
\*--------------------------------------------------*/
bool ConsumeWakeFullRepaint();
bool NeedsPrepSequence() const { return sw_control_needs_upgrade_to_5; }
/*--------------------------------------------------*\
| Per-key retry scheduling. Called by the RGB |
| controller's DeviceUpdateLEDs on partial-commit |
| frames; the power thread polls and fires |
| request_repaint_fn when a retry deadline expires. |
\*--------------------------------------------------*/
void ScheduleRetryPaint();
void CancelRetryPaint();
void TickRetryPaintIfPending();
/*--------------------------------------------------*\
| Observed per-key prep sequence. Two |
| SetEffectByIndex calls cloned byte-for-byte from a |
| wire capture on a G502 X PLUS. |
| Used in place of the Static-pass-through prep when |
| the device's RGBEffects enumeration matches the |
| G502 shape — see DeviceUpdateLEDs for gating. |
\*--------------------------------------------------*/
void DoObservedPerKeyPrep();
/*--------------------------------------------------*\
| Power management (idle/dim/sleep/wake) |
\*--------------------------------------------------*/
void StartPowerManager();
void StopPowerManager();
void StartEventWatcher();
void StartProbeWatcher();
bool HasBridge() const;
void SetRepaintCallback(std::function<void()> repaint);
void SetReapplyActiveModeCallback(std::function<bool()> cb);
void SetRegisterCallback(std::function<void(RGBController*)> cb);
HIDPP20PowerState GetPowerState() const;
int GetDimBrightness() const;
bool IsOnline() const;
bool IsDeepSleep() const;
void SetWireless(bool w) { wireless = w; }
bool QueryWirelessStatus();
bool QueryExternalPower();
void FlushResponseQueue();
private:
hid_device* dev;
std::string location;
uint8_t device_index;
bool wireless;
std::shared_ptr<std::mutex> mutex;
HIDPP20DeviceCapabilities caps;
HIDPP20Transport transport;
bool initialized;
bool sw_control_claimed;
bool sw_control_needs_upgrade_to_5;
uint32_t frame_counter;
/*--------------------------------------------------*\
| Retry-paint state (partial-commit recovery). |
| retry_paint_deadline_ zero = no retry pending. |
| retry_paint_attempt_ indexes into |
| HIDPP20_REPAINT_RETRY_BACKOFF_MS; once it reaches |
| the array length, we give up for this sequence. |
| Atomic so both the paint thread (RGB controller) |
| and the power thread can access without locks. |
\*--------------------------------------------------*/
std::atomic<std::chrono::steady_clock::time_point> retry_paint_deadline_;
std::atomic<uint8_t> retry_paint_attempt_;
std::atomic<bool> wake_full_repaint_pending_;
uint32_t init_generation;
std::string log_tag;
/*---------------------------------------------------*\
| Transport-layer I/O |
| |
| SendMessage/ReadMessage handle wire framing based |
| on transport.type. Upper layers pass feature index, |
| function ID, and payload — the transport layer |
| wraps them in the correct report format. |
\*---------------------------------------------------*/
int SendMessage(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadMessage(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms = LOGITECH_PROTOCOL_TIMEOUT);
/*--------------------------------------------------*\
| Standard HID++ transport (0xFF00/0xFF43) |
\*--------------------------------------------------*/
int SendStandard(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadStandardDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*--------------------------------------------------*\
| Centurion transport (0xFFA0) |
| Wraps messages in CPL framing, routes through |
| CentPPBridge for sub-device access. |
\*--------------------------------------------------*/
int SendCenturion(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadCenturionDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*--------------------------------------------------*\
| Reader thread dispatch layer |
| ReadHIDDirect: raw HID read (used by reader thread |
| and during Probe before reader starts). |
| ReadFromQueue: waits on response queue filled by |
| the reader thread. |
\*--------------------------------------------------*/
int ReadHIDDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
int ReadFromQueue(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*---------------------------------------------------*\
| High-level helpers |
| SendAndReceive is retained as a thin wrapper |
| around SendAcked with the reliable policy, for |
| call-site stability. |
\*---------------------------------------------------*/
int SendAndReceive(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
uint8_t* recv_data, size_t recv_max);
/*---------------------------------------------------*\
| Unified send-and-ack primitive with retry policy. |
| All command paths converge here. Returns: |
| >0 : bytes copied into recv_data |
| 0 : timeout / BUSY exhaustion |
| -1 : non-BUSY HID++ error |
| -2 : wire error (SendMessage failed) |
| If hidpp20_error_out is non-null and return is -1, |
| the HID++ error code is stored there. |
\*---------------------------------------------------*/
int SendAcked(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
uint8_t* recv_data, size_t recv_max,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE,
uint8_t* hidpp20_error_out = nullptr);
/*--------------------------------------------------*\
| Compatibility shim: same as SendAcked but writes |
| the response into a blankFAPmessage. Used by |
| callers that inherited the SendLong+ReadResponse |
| interface and inspect response.data[] downstream. |
\*--------------------------------------------------*/
int SendAckedIntoFAP(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
blankFAPmessage& response,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE);
/*-------------------------------------------------*\
| Feature discovery |
\*-------------------------------------------------*/
uint8_t GetFeatureIndex(uint16_t feature_page,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE);
uint8_t GetFeatureVersion(uint16_t feature_page) const;
void DiscoverTransport();
void DiscoverDeviceName();
void DiscoverDeviceType();
void EnumerateFeatures(uint8_t feature_set_idx);
void DiscoverFirmwareInfo();
void DiscoverRGBEffects();
void DiscoverEffectCards();
void DiscoverHeadsetRGBHostmode();
void DiscoverPerKeyZones();
void DiscoverKeyboardLayout();
/*-------------------------------------------------*\
| Power management internals |
\*-------------------------------------------------*/
void ReaderThreadFunc();
void PowerThreadFunc();
void DispatchEvent(uint8_t feat, uint8_t func, const uint8_t* data);
void OnUserActivity(uint8_t activity_type);
void StartDimRamp();
void DimRampStep();
void StartSleep();
void Wake();
void ReadFirmwareTimers();
void ReadNvSleepRampConfig();
void WritePowerConfig(uint16_t idle_s, uint16_t sleep_s);
void ReadActiveProfileSector();
void ReprobeSubDevice();
void ReconnectDevice();
void FullReprobe();
void RediscoverFeatures();
/*----------------------------------------------------*\
| Platform-specific. ScanForDevice walks the OS-level |
| HID enumeration to find the same physical device on |
| a new path (USB<->wireless transitions). Linux uses |
| sysfs; Windows and macOS use hidapi + serial_number |
| matching. Bodies live in |
| LogitechHIDPP20Controller_Linux.cpp and |
| LogitechHIDPP20Controller_Windows_MacOS.cpp. |
\*----------------------------------------------------*/
bool ScanForDevice(bool force = false);
/*----------------------------------------------------*\
| Platform-specific. Returns the friendly name for a |
| Centurion sub-device at the given hidapi path, or "" |
| if no name is available. Linux reads HID_NAME from |
| sysfs; Windows uses hid_device_info::product_string. |
\*----------------------------------------------------*/
std::string GetCenturionSubDeviceName(const std::string& path);
void SwapHIDHandle(hid_device* new_dev, const std::string& new_path);
/*-------------------------------------------------*\
| Reader thread + response queue |
\*-------------------------------------------------*/
std::thread* reader_thread;
std::atomic<bool> reader_running;
std::mutex response_mutex;
std::condition_variable response_cv;
std::deque<HIDPP20RawMessage> response_queue;
/*-------------------------------------------------*\
| Power thread (state machine + command sender) |
\*-------------------------------------------------*/
std::thread* power_thread;
std::atomic<bool> power_thread_running;
std::atomic<int> pending_activity; // -1=none, 0=idle, 1+=active
std::atomic<int> pending_connection; // 0=none, +1=connected, -1=disconnected
std::atomic<int> pending_path_check; // HID++1.0 DJ notification → force-scan retries remaining (0=idle)
std::atomic<bool> device_online; // false when device is unreachable
std::atomic<int> consecutive_timeouts; // reset on successful response
std::atomic<bool> watcher_mode; // true when retrying failed probe
/*-------------------------------------------------*\
| Power management state |
\*-------------------------------------------------*/
HIDPP20PowerState power_state;
std::mutex power_mutex;
std::atomic<bool> deep_sleep; // true once device stops responding after StartSleep()
std::atomic<int> consecutive_frame_end_failures; // FrameEnd BUSY exhaustions while SLEEPING
/*-------------------------------------------------*\
| Dim ramp state |
| dim_brightness_pct is applied by DeviceUpdateLEDs |
| to scale colors before pushing to device. |
| This is our own host-side animation, independent |
| of any firmware dim/sleep timers. |
\*-------------------------------------------------*/
#define DIM_STEPS 25
#define DIM_INTERVAL_MS 200
#define DIM_TARGET_PCT 50
std::atomic<int> dim_brightness_pct; // 100=full, 50=dimmed
int dim_step;
std::chrono::steady_clock::time_point next_dim_time;
/*-------------------------------------------------*\
| Sleep timer |
\*-------------------------------------------------*/
std::chrono::steady_clock::time_point sleep_deadline;
/*--------------------------------------------------*\
| Last idle-settings re-read timestamp. Drives the |
| 500ms poll in PowerThreadFunc that re-reads the |
| LogitechHIDPP20IdleSettings JSON key so updates |
| from the plugin (or manual edits) apply within |
| about half a second without any callback plumbing. |
\*--------------------------------------------------*/
std::chrono::steady_clock::time_point last_idle_poll;
/*-------------------------------------------------*\
| Effective idle/sleep timers used by the state |
| machine. Populated from the firmware snapshot by |
| default, then possibly overridden by profile |
| values in ApplyPowerSavingProfile. |
\*-------------------------------------------------*/
uint16_t idle_timeout_s;
uint16_t sleep_timeout_s;
/*--------------------------------------------------*\
| Firmware-configured timer snapshot. Read at init |
| (and on reconnect) by ReadFirmwareTimers and |
| never overwritten by profile application, so |
| that the unconfigured fallback path and transition |
| back from a user profile both have a clean set |
| of defaults to return to. |
\*--------------------------------------------------*/
uint16_t fw_idle_timeout_s = 60;
uint16_t fw_sleep_timeout_s = 300;
uint16_t written_idle_s = 0; // last value written to device RAM (0 = not written yet)
uint16_t written_sleep_s = 0;
/*--------------------------------------------------*\
| Host-side idle/dim/sleep state. |
| Populated from LogitechHIDPP20IdleSettings on each |
| ApplyPowerSavingProfile() invocation. |
\*--------------------------------------------------*/
bool ps_dim_enabled = false;
int ps_dim_target_pct = DIM_TARGET_PCT;
bool ps_sleep_enabled = false;
bool ps_on_external_power = false;
bool ps_last_logged_external = false;
int ps_last_logged_pct = -1;
int ps_last_logged_idle = -1;
int ps_last_logged_sleep = -1;
uint16_t last_power_raw = 0xFFFF; // dedup for QueryExternalPower TRACE
uint8_t idx_unified_battery = 0;
void ApplyPowerSavingProfile();
bool IsCurrentlyWireless() const;
/*-------------------------------------------------*\
| Per-key write tracking (per active frame) |
| Populated by SendPerKeyData, drained by |
| PerKeyFrameEnd. Single-threaded — only the RGB |
| controller thread touches per-key state. |
\*-------------------------------------------------*/
std::vector<OutstandingPerKeyWrite> outstanding_writes;
/*-------------------------------------------------*\
| Callbacks |
\*-------------------------------------------------*/
std::function<void()> request_repaint_fn;
std::function<bool()> reapply_active_mode_fn;
std::function<void(RGBController*)> register_controller_fn;
};
@@ -1,433 +0,0 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller_Linux.cpp |
| |
| Linux-specific path-migration and device-name lookup |
| for the unified Logitech HID++ 2.0 controller. |
| |
| Uses sysfs (/sys/class/hidraw) to find the same |
| physical device on a new hidraw path after a USB <-> |
| wireless transition, and to read Centurion sub-device |
| friendly names from HID_NAME uevent fields. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <cctype>
#include <cstdio>
#include <chrono>
#include <vector>
#include <string>
#include <fstream>
#include <dirent.h>
#include "LogitechHIDPP20Controller.h"
#include "LogManager.h"
#define LOG_TAG log_tag.c_str()
std::string LogitechHIDPP20Controller::GetCenturionSubDeviceName(const std::string& path)
{
/*--------------------------------------------------------*\
| Centurion sub-device friendly name comes from sysfs |
| HID_NAME=, same field Solaar reads. The `path` arg is a |
| hidraw dev path like /dev/hidraw5; extract the hidrawN |
| basename and read /sys/class/hidraw/<basename>/device/ |
| uevent. |
\*--------------------------------------------------------*/
std::string sysfs_name;
size_t pos = path.rfind("hidraw");
if(pos == std::string::npos)
{
return sysfs_name;
}
std::string uevent_path = "/sys/class/hidraw/" + path.substr(pos) + "/device/uevent";
FILE* f = fopen(uevent_path.c_str(), "r");
if(!f)
{
return sysfs_name;
}
char line[256];
while(fgets(line, sizeof(line), f))
{
if(strncmp(line, "HID_NAME=", 9) == 0)
{
sysfs_name = line + 9;
while(!sysfs_name.empty() && (sysfs_name.back() == '\n' || sysfs_name.back() == '\r'))
{
sysfs_name.pop_back();
}
break;
}
}
fclose(f);
return sysfs_name;
}
bool LogitechHIDPP20Controller::ScanForDevice(bool force)
{
/*---------------------------------------------------------*\
| Scan sysfs for a hidraw with matching unitId. Called by |
| the power thread either periodically (normal reactive |
| mode — only when device_online==false, USB/wireless |
| transition or physical unplug) or on demand from the |
| reader thread after a HID++1.0 Device Connection |
| notification arrives (force=true, bypasses the online |
| gate). Finds the device on its new connection path. |
| |
| Reactive-only: we never migrate while the current path |
| still works. On devices like the G502 X PLUS that expose |
| both a USB-direct hidraw AND a wireless-via-dongle hidraw |
| simultaneously (when paired to the receiver and plugged |
| in at the same time), eager migration would bounce us off |
| a working path onto one the firmware has actively muted, |
| breaking control. The device signals which path is |
| active by returning errors/going silent on the inactive |
| path — our reader thread picks that up as a hid_read |
| failure and flips device_online=false, at which point the |
| power thread calls this function to pick the new path. |
| |
| Linux implementation: walks /sys/class/hidraw and matches |
| on HID_UNIQ. The Windows counterpart (same class method, |
| different .cpp file) uses hid_enumerate + serial_number. |
| |
| Match criteria: |
| - device_online == false (caller should already ensure) |
| - HID_UNIQ matches our unitId |
| - Logitech VID (046D) |
| - HID++ interface (usage_page 0xFF00, usage 2) |
| - Different from our current path (device moved) |
\*---------------------------------------------------------*/
if(caps.unit_id.empty() || caps.unit_id == "00000000")
{
return false;
}
/*---------------------------------------------------------*\
| Online guard: never migrate off a working path unless |
| the caller explicitly forces a re-check. The normal |
| periodic scan path stays gated on device_online==false |
| so it's a no-op when everything is healthy. |
| |
| The reader thread bypasses this guard (force=true) after |
| seeing a HID++1.0 Device Connection Status notification |
| from the Lightspeed receiver, which fires BEFORE the |
| firmware fully switches its data flow from wireless to |
| USB. At that moment device_online is still true (the |
| current path hasn't failed yet), but we want the scan to |
| run so we can migrate to the new path proactively. |
\*---------------------------------------------------------*/
if(!force && device_online.load())
{
return false;
}
/*---------------------------------------------------------*\
| Normalize our unitId to lowercase hex without dashes for |
| comparison. Sysfs HID_UNIQ varies between drivers: |
| - Lightspeed virtual: "0d-12-5d-47" (dashes) |
| - USB direct: "0D125D47" (no dashes) |
| We strip dashes and lowercase both sides for matching. |
\*---------------------------------------------------------*/
std::string target_norm;
for(char c : caps.unit_id)
{
if(c != '-')
{
target_norm += (char)tolower(c);
}
}
/*---------------------------------------------------------*\
| Read our current PID from sysfs. Only migrate to paths |
| with a DIFFERENT PID — same PID means same receiver, |
| just a different pairing slot (e.g., stale pairing). |
\*---------------------------------------------------------*/
unsigned int current_pid = 0;
{
std::string cur_hidraw = location.substr(location.rfind('/') + 1);
std::string cur_uevent = "/sys/class/hidraw/" + cur_hidraw + "/device/uevent";
std::ifstream cur_file(cur_uevent);
std::string line;
while(std::getline(cur_file, line))
{
if(line.compare(0, 7, "HID_ID=") == 0)
{
size_t lc = line.rfind(':');
if(lc != std::string::npos)
{
sscanf(line.c_str() + lc + 1, "%x", &current_pid);
}
break;
}
}
}
/*----------------------------------------------------------*\
| Collect ALL sysfs hidraws with matching unit_id + Logitech |
| VID + different PID. With multiple Lightspeed dongles in |
| the system, several hidraws can share the same HID_UNIQ: |
| one is the live virt-slot on the connected dongle, others |
| are stale virt-slots on dongles where our device is not |
| actually present. We have to probe each one to find out |
| which slot is real — sysfs alone can't tell them apart. |
\*----------------------------------------------------------*/
struct Candidate
{
std::string dev_path;
unsigned int pid;
};
std::vector<Candidate> candidates;
DIR* dir = opendir("/sys/class/hidraw");
if(!dir)
{
return false;
}
struct dirent* entry;
while((entry = readdir(dir)) != nullptr)
{
if(strncmp(entry->d_name, "hidraw", 6) != 0)
{
continue;
}
std::string uevent_path = "/sys/class/hidraw/" +
std::string(entry->d_name) + "/device/uevent";
std::ifstream uevent(uevent_path);
if(!uevent.is_open())
{
continue;
}
std::string line;
std::string hid_uniq;
std::string hid_id;
while(std::getline(uevent, line))
{
if(line.compare(0, 9, "HID_UNIQ=") == 0)
{
hid_uniq = line.substr(9);
}
else if(line.compare(0, 7, "HID_ID=") == 0)
{
hid_id = line.substr(7);
}
}
std::string uniq_norm;
for(char c : hid_uniq)
{
if(c != '-')
{
uniq_norm += (char)tolower(c);
}
}
if(uniq_norm != target_norm)
{
continue;
}
if(hid_id.find("0000046D") == std::string::npos &&
hid_id.find("0000046d") == std::string::npos)
{
continue;
}
std::string dev_path = "/dev/" + std::string(entry->d_name);
if(dev_path == location)
{
continue;
}
size_t last_colon = hid_id.rfind(':');
if(last_colon == std::string::npos)
{
continue;
}
unsigned int pid = 0;
sscanf(hid_id.c_str() + last_colon + 1, "%x", &pid);
if(pid == current_pid)
{
continue;
}
candidates.push_back({dev_path, pid});
}
closedir(dir);
if(candidates.empty())
{
return false;
}
/*----------------------------------------------------------*\
| Probe each candidate before committing. Two-dongle systems |
| can expose multiple sysfs hidraws with the same HID_UNIQ: |
| one is the live slot, others are stale pairings that |
| respond with short-format UNKNOWN_DEVICE errors. Issue a |
| cheap IRoot GetFeature (feat 0x0001) to distinguish them. |
| A live slot returns a long-form response with feat_idx=0 |
| and feat_byte matching the sub-index we asked for. A stale |
| slot returns r[10 xx 8F 00 00 08 ...] (short error, code |
| 0x08 UNKNOWN_DEVICE). We accept the first candidate that |
| passes; the overall pending_path_check retry loop will |
| naturally re-probe all candidates on subsequent scans if |
| none pass on the current pass (e.g., mid-transition). |
\*----------------------------------------------------------*/
std::string found_path;
hid_device* found_dev = nullptr;
for(size_t c = 0; c < candidates.size(); c++)
{
const Candidate& cand = candidates[c];
LOG_DEBUG("%s Scan: migration candidate at %s (pid=0x%04X, current=%s pid=0x%04X)",
LOG_TAG, cand.dev_path.c_str(), cand.pid,
location.c_str(), current_pid);
/*-----------------------------------------------------*\
| Multi-dongle caveat: hid_enumerate returns entries |
| for ALL dongles with this PID (046D:4099 Lightspeed |
| can appear several times in a single machine). Match |
| strictly on the sysfs candidate's dev_path so each |
| candidate resolves to its OWN dongle's HID++ |
| interface — not the first match we stumble across. |
\*-----------------------------------------------------*/
hid_device_info* devs = hid_enumerate(0x046D, (uint16_t)cand.pid);
std::string hidpp20_path;
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
LOG_TRACE("%s Scan: enumerate PID=0x%04X path=%s page=0x%04X usage=%d",
LOG_TAG, cand.pid, d->path, d->usage_page, d->usage);
if(std::string(d->path) == cand.dev_path &&
d->usage_page == 0xFF00 && d->usage == 2 &&
std::string(d->path) != location)
{
hidpp20_path = d->path;
break;
}
}
hid_free_enumeration(devs);
if(hidpp20_path.empty())
{
LOG_DEBUG("%s Scan: %s no matching LogitechHID++ interface in enum",
LOG_TAG, cand.dev_path.c_str());
continue;
}
hid_device* test_dev = hid_open_path(hidpp20_path.c_str());
if(!test_dev)
{
LOG_DEBUG("%s Scan: %s failed to open", LOG_TAG, hidpp20_path.c_str());
continue;
}
/*------------------------------------------------------*\
| Probe: HID++2.0 IRoot GetFeature for feat 0x0001 |
| (IFeatureSet). Wire: w[10 FF 0000 000100] |
| - report_id 0x10 (short) |
| - device_index 0xFF |
| - feat_idx 0x00 (IRoot) |
| - address 0x00 (func 0 GetFeature, sw_id 0) |
| - payload 00 01 00 (feat_id 0x0001) |
| |
| A live device returns r[11 xx 00 0X ...] — long form, |
| feat_idx 0x00, the address byte we sent back, and the |
| feature index in the payload. |
| A stale slot returns r[10 xx 8F 00 00 08 ...] — short |
| error form. Reject anything that starts with 0x10. |
\*------------------------------------------------------*/
uint8_t probe[7] = {0x10, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00};
uint8_t reply[20] = {};
int write_rc = hid_write(test_dev, probe, sizeof(probe));
bool probe_ok = false;
if(write_rc >= 0)
{
/*-----------------------------------------------------*\
| Drain up to ~100ms, looking for a long-form response |
| matching our probe. Skip stray reports from unrelated |
| firmware events that might be queued on the hidraw. |
\*-----------------------------------------------------*/
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(100);
while(std::chrono::steady_clock::now() < deadline)
{
int read_rc = hid_read_timeout(test_dev, reply, sizeof(reply), 50);
if(read_rc <= 0)
{
continue;
}
if(reply[0] == 0x11 && reply[2] == 0x00 && reply[3] == 0x00)
{
probe_ok = true;
break;
}
if(reply[0] == 0x10 && reply[2] == 0x8F)
{
LOG_DEBUG("%s Scan: %s probe rejected — err=0x%02X (stale slot)",
LOG_TAG, hidpp20_path.c_str(), reply[5]);
break;
}
}
}
if(!probe_ok)
{
hid_close(test_dev);
continue;
}
LOG_DEBUG("%s Scan: %s probe accepted (feat_idx=0x%02X)",
LOG_TAG, hidpp20_path.c_str(), reply[4]);
found_path = hidpp20_path;
found_dev = test_dev;
break;
}
if(!found_dev)
{
return false;
}
LOG_INFO("%s Device migrated: %s -> %s", LOG_TAG, location.c_str(), found_path.c_str());
SwapHIDHandle(found_dev, found_path);
return true;
}
@@ -1,302 +0,0 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller_Windows_MacOS.cpp |
| |
| Path-migration and device-name lookup for the unified |
| Logitech HID++ 2.0 controller on Windows and macOS. |
| |
| Uses hidapi's hid_enumerate + hid_device_info fields |
| (serial_number, product_string, product_id, usage_page) |
| on platforms with no /sys/class/hidraw equivalent. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <algorithm>
#include <chrono>
#include <cstring>
#include <string>
#include <vector>
#include "LogitechHIDPP20Controller.h"
#include "LogManager.h"
#include "StringUtils.h"
#define LOG_TAG log_tag.c_str()
std::string LogitechHIDPP20Controller::GetCenturionSubDeviceName(const std::string& path)
{
/*---------------------------------------------------------*\
| On Windows, hidapi's hid_device_info carries a |
| product_string field (wchar_t*). Enumerate all Logitech |
| devices and find the one whose path matches `path`. |
| |
| Caveat: Windows hidapi typically returns the parent |
| product string on every (interface, usage_page, usage) |
| entry that maps to the same USB device, so Centurion |
| sub-devices may share a name with the parent dongle. |
| That's a less specific name than Linux's HID_NAME, but |
| still better than "Logitech Centurion Device". |
\*---------------------------------------------------------*/
std::string friendly;
hid_device_info* devs = hid_enumerate(0x046D, 0x0000);
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path == nullptr)
{
continue;
}
if(std::string(d->path) != path)
{
continue;
}
friendly = StringUtils::wchar_to_string(d->product_string);
break;
}
hid_free_enumeration(devs);
return friendly;
}
bool LogitechHIDPP20Controller::ScanForDevice(bool force)
{
/*---------------------------------------------------------*\
| Scan hidapi for a Logitech device with matching unitId. |
| Called by the power thread either periodically (normal |
| reactive mode — only when device_online==false, USB/ |
| wireless transition or physical unplug) or on demand |
| from the reader thread after a HID++1.0 Device |
| Connection notification arrives (force=true, bypasses |
| the online gate). Finds the device on its new |
| connection path. |
| |
| Reactive-only: we never migrate while the current path |
| still works (same rationale as Linux — see the Linux |
| companion file for the full explanation). |
| |
| Windows implementation: walks hid_enumerate(046D, *) and |
| matches on hid_device_info::serial_number, which for |
| Logitech devices generally corresponds to the same |
| stable identity as the HID++-reported unit_id. If |
| serial_number matching yields nothing (hidapi may not |
| populate it for some Logitech devices on Windows), we |
| fall through to IRoot probing every candidate on the |
| matching usage_page/usage so we can still find the |
| device albeit more slowly. |
\*---------------------------------------------------------*/
if(caps.unit_id.empty() || caps.unit_id == "00000000")
{
return false;
}
if(!force && device_online.load())
{
return false;
}
std::string target_norm = StringUtils::normalize_hex_id(caps.unit_id);
/*---------------------------------------------------------*\
| Candidate = {path, pid, has_serial_match}. Serial matches |
| sort first so we try the cheapest/most-likely candidate. |
\*---------------------------------------------------------*/
struct Candidate
{
std::string dev_path;
unsigned int pid;
bool serial_match;
};
std::vector<Candidate> candidates;
unsigned int current_pid = 0;
hid_device_info* devs = hid_enumerate(0x046D, 0x0000);
/*---------------------------------------------------------*\
| First pass: find the entry matching our current path and |
| record its product_id so we can skip same-PID candidates. |
\*---------------------------------------------------------*/
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path != nullptr && std::string(d->path) == location)
{
current_pid = d->product_id;
break;
}
}
/*---------------------------------------------------------*\
| Second pass: collect candidates that (a) aren't our |
| current path, (b) speak the HID++ interface, and (c) have |
| a plausible identity match — either the serial_number |
| matches our unit_id, or at minimum their PID differs from |
| ours so they can't be another slot on the same dongle. |
\*---------------------------------------------------------*/
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path == nullptr)
{
continue;
}
if(std::string(d->path) == location)
{
continue;
}
/*-----------------------------------------------------*\
| Only HID++ interface (usage_page 0xFF00, usage 2). |
| Note: on Windows hidapi may or may not populate |
| usage / usage_page consistently. If either is zero, |
| fall through — we'll probe unconditionally in that |
| case rather than dropping a potential candidate. |
\*-----------------------------------------------------*/
bool usage_known = (d->usage_page != 0 || d->usage != 0);
if(usage_known && (d->usage_page != 0xFF00 || d->usage != 2))
{
continue;
}
/*-----------------------------------------------------*\
| Compare serial_number (wchar_t*) against our unit_id. |
| Normalize both and do an exact-match comparison. |
| Empty/missing serial is allowed — falls through as a |
| serial_match=false candidate so the probe path can |
| still find it via a different-PID filter. |
\*-----------------------------------------------------*/
bool serial_match = false;
if(d->serial_number != nullptr && d->serial_number[0] != L'\0')
{
std::string sn = StringUtils::wchar_to_string(d->serial_number);
std::string sn_norm = StringUtils::normalize_hex_id(sn);
if(!sn_norm.empty() && sn_norm == target_norm)
{
serial_match = true;
}
}
/*-----------------------------------------------------*\
| If we have no serial match AND this path shares the |
| current PID, skip. Same PID with no identity evidence |
| is probably a sibling slot on the same dongle, not a |
| valid migration target. |
\*-----------------------------------------------------*/
if(!serial_match && d->product_id == current_pid && current_pid != 0)
{
continue;
}
Candidate c;
c.dev_path = d->path;
c.pid = d->product_id;
c.serial_match = serial_match;
candidates.push_back(c);
}
hid_free_enumeration(devs);
if(candidates.empty())
{
return false;
}
/*---------------------------------------------------------*\
| Sort so serial-matched candidates get probed first. |
\*---------------------------------------------------------*/
std::stable_sort(candidates.begin(), candidates.end(),
[](const Candidate& a, const Candidate& b)
{
return a.serial_match && !b.serial_match;
});
/*---------------------------------------------------------*\
| Probe each candidate with IRoot GetFeature feat 0x0001. |
| A live slot returns a long-form (0x11) response; a stale |
| slot returns a short-form (0x10) error. See the Linux |
| companion file for the wire-level explanation. |
\*---------------------------------------------------------*/
std::string found_path;
hid_device* found_dev = nullptr;
for(size_t c = 0; c < candidates.size(); c++)
{
const Candidate& cand = candidates[c];
LOG_DEBUG("%s Scan: migration candidate at %s (pid=0x%04X, serial_match=%d)",
LOG_TAG, cand.dev_path.c_str(), cand.pid,
cand.serial_match ? 1 : 0);
hid_device* test_dev = hid_open_path(cand.dev_path.c_str());
if(!test_dev)
{
LOG_DEBUG("%s Scan: %s failed to open",
LOG_TAG, cand.dev_path.c_str());
continue;
}
uint8_t probe[7] = {0x10, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00};
uint8_t reply[20] = {};
int write_rc = hid_write(test_dev, probe, sizeof(probe));
bool probe_ok = false;
if(write_rc >= 0)
{
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(100);
while(std::chrono::steady_clock::now() < deadline)
{
int read_rc = hid_read_timeout(test_dev, reply, sizeof(reply), 50);
if(read_rc <= 0)
{
continue;
}
if(reply[0] == 0x11 && reply[2] == 0x00 && reply[3] == 0x00)
{
probe_ok = true;
break;
}
if(reply[0] == 0x10 && reply[2] == 0x8F)
{
LOG_DEBUG("%s Scan: %s probe rejected — err=0x%02X (stale slot)",
LOG_TAG, cand.dev_path.c_str(), reply[5]);
break;
}
}
}
if(!probe_ok)
{
hid_close(test_dev);
continue;
}
LOG_DEBUG("%s Scan: %s probe accepted (feat_idx=0x%02X)",
LOG_TAG, cand.dev_path.c_str(), reply[4]);
found_path = cand.dev_path;
found_dev = test_dev;
break;
}
if(!found_dev)
{
return false;
}
LOG_INFO("%s Device migrated: %s -> %s",
LOG_TAG, location.c_str(), found_path.c_str());
SwapHIDHandle(found_dev, found_path);
return true;
}
@@ -1,146 +0,0 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20IdleSettings.cpp |
| |
| Host-side idle/dim/sleep settings storage helper for |
| Logitech HID++ 2.0 devices. Loads the configuration |
| block from SettingsManager JSON, caches it, and writes |
| changes back through SettingsManager::SetSettings(). |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "LogitechHIDPP20IdleSettings.h"
#include "ResourceManager.h"
#include "SettingsManager.h"
static const char* SETTINGS_KEY = "LogitechHIDPP20IdleSettings";
LogitechHIDPP20IdleSettings* LogitechHIDPP20IdleSettings::instance()
{
static LogitechHIDPP20IdleSettings inst;
return &inst;
}
/*---------------------------------------------------------*\
| Minimum idle / sleep values the controller will honor. |
| Matches what Logitech firmware typically clamps to on |
| HID++ 2.0 devices, and guarantees the skip-dim path's |
| sleep_delay math always produces a positive window. |
\*---------------------------------------------------------*/
static const int MIN_IDLE_S = 15;
static const int MIN_SLEEP_S = 45;
static const int MIN_DIM_WINDOW = 30; // sleep must exceed idle by at least this
static void ClampProfile(LogitechHIDPP20IdleProfile& p)
{
if(p.dim_brightness < 0) p.dim_brightness = 0;
if(p.dim_brightness > 100) p.dim_brightness = 100;
if(p.idle_timeout_s < MIN_IDLE_S)
{
p.idle_timeout_s = MIN_IDLE_S;
}
if(p.sleep_timeout_s < MIN_SLEEP_S)
{
p.sleep_timeout_s = MIN_SLEEP_S;
}
if(p.sleep_timeout_s < p.idle_timeout_s + MIN_DIM_WINDOW)
{
p.sleep_timeout_s = p.idle_timeout_s + MIN_DIM_WINDOW;
}
}
static LogitechHIDPP20IdleProfile ProfileFromJson(const json& j)
{
LogitechHIDPP20IdleProfile p;
if(j.contains("dim_when_idle")) p.dim_when_idle = j["dim_when_idle"];
if(j.contains("dim_brightness")) p.dim_brightness = j["dim_brightness"];
if(j.contains("idle_timeout_s")) p.idle_timeout_s = j["idle_timeout_s"];
if(j.contains("allow_sleep")) p.allow_sleep = j["allow_sleep"];
if(j.contains("sleep_timeout_s")) p.sleep_timeout_s = j["sleep_timeout_s"];
ClampProfile(p);
return p;
}
static json ProfileToJson(const LogitechHIDPP20IdleProfile& p)
{
json j;
j["dim_when_idle"] = p.dim_when_idle;
j["dim_brightness"] = p.dim_brightness;
j["idle_timeout_s"] = p.idle_timeout_s;
j["allow_sleep"] = p.allow_sleep;
j["sleep_timeout_s"] = p.sleep_timeout_s;
return j;
}
void LogitechHIDPP20IdleSettings::load()
{
json settings = ResourceManager::get()->GetSettingsManager()->GetSettings(SETTINGS_KEY);
/*---------------------------------------------------------*\
| Empty / missing key means the plugin is not in use. |
| Reset both profiles to defaults with configured=false so |
| the controller defers to firmware. |
\*---------------------------------------------------------*/
if(!settings.is_object() || settings.empty())
{
configured = false;
on_battery = LogitechHIDPP20IdleProfile{};
plugged_in = LogitechHIDPP20IdleProfile{};
return;
}
configured = true;
if(settings.contains("on_battery"))
{
on_battery = ProfileFromJson(settings["on_battery"]);
}
else
{
on_battery = LogitechHIDPP20IdleProfile{};
}
if(settings.contains("plugged_in"))
{
plugged_in = ProfileFromJson(settings["plugged_in"]);
}
else
{
plugged_in = LogitechHIDPP20IdleProfile{};
}
}
void LogitechHIDPP20IdleSettings::save()
{
json settings;
settings["on_battery"] = ProfileToJson(on_battery);
settings["plugged_in"] = ProfileToJson(plugged_in);
SettingsManager* mgr = ResourceManager::get()->GetSettingsManager();
mgr->SetSettings(SETTINGS_KEY, settings);
mgr->SaveSettings();
configured = true;
}
void LogitechHIDPP20IdleSettings::setOnBattery(const LogitechHIDPP20IdleProfile& p)
{
on_battery = p;
configured = true;
}
void LogitechHIDPP20IdleSettings::setPluggedIn(const LogitechHIDPP20IdleProfile& p)
{
plugged_in = p;
configured = true;
}
@@ -1,47 +0,0 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20IdleSettings.h |
| |
| Host-side idle/dim/sleep configuration for Logitech |
| HID++ 2.0 devices. Two profiles (on_battery, plugged_in)|
| selected at runtime based on the device's external- |
| power flag. `configured == false` means the JSON key |
| is absent entirely — the controller defers to firmware. |
| Qt-free so the controller can consume it directly. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
struct LogitechHIDPP20IdleProfile
{
bool dim_when_idle = false;
int dim_brightness = 50;
int idle_timeout_s = 60;
bool allow_sleep = false;
int sleep_timeout_s = 300;
};
class LogitechHIDPP20IdleSettings
{
public:
static LogitechHIDPP20IdleSettings* instance();
void load();
void save();
bool isConfigured() const { return configured; }
const LogitechHIDPP20IdleProfile& onBattery() const { return on_battery; }
const LogitechHIDPP20IdleProfile& pluggedIn() const { return plugged_in; }
void setOnBattery(const LogitechHIDPP20IdleProfile& p);
void setPluggedIn(const LogitechHIDPP20IdleProfile& p);
private:
LogitechHIDPP20IdleSettings() = default;
bool configured = false;
LogitechHIDPP20IdleProfile on_battery;
LogitechHIDPP20IdleProfile plugged_in;
};
@@ -1,72 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_LogitechHIDPP20.h |
| |
| RGBController for unified Logitech HID++ 2.0 devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechHIDPP20Controller.h"
class RGBController_LogitechHIDPP20 : public RGBController
{
public:
RGBController_LogitechHIDPP20(LogitechHIDPP20Controller* controller_ptr);
~RGBController_LogitechHIDPP20();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void DeviceSaveMode();
bool ReapplyActiveMode();
private:
LogitechHIDPP20Controller* controller;
/*---------------------------------------------------------*\
| Repaint callback handler. Registered with the controller |
| as request_repaint_fn and invoked from the power thread |
| for dim/wake when no animation is driving updates. |
\*---------------------------------------------------------*/
void OnRepaintRequest();
/*---------------------------------------------------------*\
| When true, the next DeviceUpdateMode cycle sends its |
| SetZoneEffect calls with persist=true instead of the |
| default ephemeral write. Used by DeviceSaveMode to replay |
| the active mode as a NVM-committed effect on 0x8070 |
| devices, which default to non-persistent live writes. |
\*---------------------------------------------------------*/
bool save_pending = false;
/*---------------------------------------------------------*\
| Maps OpenRGB LED index -> HID++ per-key zone ID |
\*---------------------------------------------------------*/
std::vector<uint16_t> led_to_zone_id;
/*---------------------------------------------------------*\
| Reverse map: zone_id -> LED index (-1 if no LED). |
| Indexed 0..255 (zone IDs are bytes). Built once in |
| SetupZones to avoid scanning led_to_zone_id at commit |
| time, which would be O(N) per acked zone. |
\*---------------------------------------------------------*/
std::vector<int> zone_id_to_led_idx;
/*---------------------------------------------------------*\
| Last successfully committed colors for delta updates. |
| An entry of HIDPP20_UNCOMMITTED (0xFF000000) marks an LED |
| whose last write didn't ACK and which therefore needs to |
| be re-pushed in the next frame regardless of color delta. |
| The high byte (0xFF) is impossible for any value produced |
| by ToRGBColor() so it never collides with a real color. |
\*---------------------------------------------------------*/
std::vector<RGBColor> sent_colors;
uint32_t last_init_gen = 0;
};
@@ -70,12 +70,6 @@ void MSILaptopController::SetLEDs(std::vector<led> leds, std::vector<RGBColor> c
buf[0x01] = MSI_LAPTOP_COMMAND;
buf[0x03] = (type == MSI_LAPTOP_KLC) ? MSI_LAPTOP_KLC_PACKET_ID : MSI_LAPTOP_ALC_PACKET_ID;
// Fill unused LED IDs with 0xFF so they are ignored by the controller
for(int i = 0; i < (MSI_LAPTOP_PACKET_SIZE - MSI_LAPTOP_PAYLOAD_OFFSET) / 4; i++)
{
buf[MSI_LAPTOP_PAYLOAD_OFFSET + (i * 4)] = 0xFF;
}
for(unsigned int led_idx = 0; led_idx < led_count; led_idx++)
{
unsigned int offset = MSI_LAPTOP_PAYLOAD_OFFSET + (led_idx * 4);
@@ -22,7 +22,7 @@
#define NA 0xFFFFFFFF
#define MSI_LAPTOP_KLC_MATRIX_HEIGHT 6
#define MSI_LAPTOP_KLC_MATRIX_WIDTH 23
#define MSI_LAPTOP_ALC_LIGHTBAR_LEDS 3
#define MSI_LAPTOP_ALC_LIGHTBAR_LEDS 5
static const msi_laptop_led msi_raider_a18_klc_leds[] =
{
@@ -140,24 +140,14 @@ static unsigned int msi_raider_a18_klc_matrix_map[MSI_LAPTOP_KLC_MATRIX_HEIGHT][
{ 90, 93, 92, NA, NA, NA, 38, NA, NA, NA,101, 94, 95, NA, 69, 70, 68, NA, 87, NA, 88, NA, NA },
};
/*---------------------------------------------------------*\
| Note on Raider A18 HX ALC (Lightbar) LED mappings: |
| Initial reverse engineering packet captures showed 6 zones|
| being updated (indexes 0x00 to 0x05). |
| However, indexes 0x04 (L4) and 0x05 (L5) are "dummy" zones|
| on this specific model and do not correspond to physical |
| LEDs. To avoid user confusion, they are omitted from the |
| UI profile here. |
| The SetLEDs function handles zero-padding the unused |
| payload slots with 0xFF so that omitting these zones |
| doesn't accidentally overwrite the 0x00 index (L1). |
\*---------------------------------------------------------*/
static const msi_laptop_led msi_raider_a18_alc_leds[] =
{
{ "Lightbar 1", 0x00 },
{ "Lightbar 2", 0x01 },
{ "Lightbar 3", 0x02 },
{ "Logo", 0x03 },
{ "Lightbar 4", 0x03 },
{ "Lightbar 5", 0x04 },
{ "Logo", 0x05 },
};
static const MSILaptopModel msi_laptop_models[] =
@@ -230,6 +220,6 @@ void DetectMSILaptop(hid_device_info* info, const std::string& name)
}
}
REGISTER_HID_DETECTOR("MSI Laptop Keyboard", DetectMSILaptop, STEELSERIES_VID, STEELSERIES_MSI_RAIDER_A18_KLC_PID);
REGISTER_HID_DETECTOR_I("MSI Laptop Keyboard", DetectMSILaptop, STEELSERIES_VID, STEELSERIES_MSI_RAIDER_A18_KLC_PID, 0);
REGISTER_HID_DETECTOR_I("MSI Laptop Lightbar", DetectMSILaptop, STEELSERIES_VID, STEELSERIES_MSI_RAIDER_A18_ALC_PID, 0);
@@ -1,121 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_MSIMonitor.cpp |
| |
| RGBController for MSI monitor (gaming controller) |
| |
| Andy Herbert 2026 June 4 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <string.h>
#include "MSIMonitorController.h"
#include "StringUtils.h"
MSIMonitorController::MSIMonitorController(hid_device* dev_handle, const hid_device_info& info, std::string dev_name)
{
dev = dev_handle;
location = info.path;
name = dev_name;
}
MSIMonitorController::~MSIMonitorController()
{
hid_close(dev);
}
std::string MSIMonitorController::GetDeviceLocation()
{
return("HID: " + location);
}
std::string MSIMonitorController::GetNameString()
{
return(name);
}
std::string MSIMonitorController::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 MSIMonitorController::Set(uint8_t mode_value, const std::vector<RGBColor> colors, uint8_t last_bit)
{
/*---------------------------------------------------------*\
| Prepare color data |
\*---------------------------------------------------------*/
uint8_t data[MSI_MONITOR_PACKET_SIZE];
memset(data, 0x00, MSI_MONITOR_PACKET_SIZE);
unsigned int offset = 0;
data[offset++] = 0x71;
data[offset++] = 0x01;
for(int i = 0; i < 3; i++)
{
data[offset++] = 0x00;
}
data[offset++] = 0x01;
data[offset++] = 0x64;
for(int i = 0; i < 5; i++)
{
data[offset++] = 0x00;
}
/*---------------------------------------------------------*\
| put mode_value |
\*---------------------------------------------------------*/
data[offset++] = mode_value;
for(int i = 0; i < 3; i++)
{
data[offset++] = 0x00;
}
data[offset++] = 0x01;
data[offset++] = 0x64;
for(int i = 0; i < 5; i++)
{
data[offset++] = 0x00;
}
/*-----------------------------------------------------------------------*\
| this data looks like placeholder for additional LEDs in other monitors |
\*-----------------------------------------------------------------------*/
for(int i = 0; i < 9; i++)
{
data[offset++] = 0xff;
data[offset++] = 0x00;
data[offset++] = 0x00;
}
//RGB values begin
for(const RGBColor color: colors)
{
data[offset++] = RGBGetRValue(color);
data[offset++] = RGBGetGValue(color);
data[offset++] = RGBGetBValue(color);
}
/*-------------------------------------------------------------------------------*\
| last bit is probably a write to device bit- 0x01 saves to device, 0x00 doesn't. |
| For direct mode, bit is set to 0x00, otherwise lights will flicker |
\*-------------------------------------------------------------------------------*/
data[offset++] = last_bit;
/*---------------------------------------------------------*\
| Send the data (1 packet) |
\*---------------------------------------------------------*/
hid_send_feature_report(dev, data, MSI_MONITOR_PACKET_SIZE);
}
@@ -1,53 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_MSIMonitor.cpp |
| |
| RGBController for MSI monitor (gaming controller) |
| |
| Andy Herbert 2026 June 1 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
#define MSI_MONITOR_LEDS 9
#define MSI_MONITOR_PACKET_SIZE 78
enum
{
MSI_MONITOR_OFF_MODE_VALUE = 0x00,
MSI_MONITOR_STATIC_MODE_VALUE = 0x01,
MSI_MONITOR_BREATHING_MODE_VALUE = 0x02,
MSI_MONITOR_FLASHING_MODE_VALUE = 0x03,
MSI_MONITOR_LIGHTNING_MODE_VALUE = 0x05,
MSI_MONITOR_MARQUEE_MODE_VALUE = 0x06,
MSI_MONITOR_METEOR_MODE_VALUE = 0x08,
MSI_MONITOR_RAINBOW_MODE_VALUE = 0x1A,
MSI_MONITOR_RANDOM_MODE_VALUE = 0x1F
};
class MSIMonitorController
{
public:
MSIMonitorController(hid_device *dev_handle, const hid_device_info &info, std::string dev_name);
~MSIMonitorController();
std::string GetDeviceLocation();
std::string GetFirmwareVersion();
std::string GetNameString();
std::string GetSerialString();
void Set(uint8_t mode_value, const std::vector<RGBColor> colors, uint8_t last_bit);
private:
hid_device *dev;
std::string description;
std::string location;
std::string name;
std::string version;
};
@@ -1,38 +0,0 @@
/*---------------------------------------------------------*\
| MSIKeyboardControllerDetect.cpp |
| |
| Detector for MSI monitor (MSI Gaming Controller) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "Detector.h"
#include "MSIMonitorController.h"
#include "RGBController_MSIMonitor.h"
#define MSI_USB_VID 0x1462
#define MSI_USB_PID 0x3FA4
/*----------------------------------------------------------*\
| |
| DetectMSIMonitorController |
| |
| Detect MSI monitor and maybe others |
| |
\*----------------------------------------------------------*/
static void DetectMSIMonitorController(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
MSIMonitorController* controller = new MSIMonitorController(dev, *info, name);
RGBController_MSIMonitor* rgb_controller = new RGBController_MSIMonitor(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
REGISTER_HID_DETECTOR_IPU("MSI Monitor (Gaming Controller)", DetectMSIMonitorController, MSI_USB_VID, MSI_USB_PID, 0, 0x01, 0);
@@ -1,171 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_MSIMonitor.cpp |
| |
| RGBController for MSI monitor (gaming controller) |
| |
| Andy Herbert 2026 June 1 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <chrono>
#include <thread>
#include "RGBController_MSIMonitor.h"
using namespace std::chrono_literals;
/**------------------------------------------------------------------*\
@name MSIMonitor
@category Accessory
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectMSIMonitorController
@comment Developed with MSI MAG272CQR
\*-------------------------------------------------------------------*/
RGBController_MSIMonitor::RGBController_MSIMonitor(MSIMonitorController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "MSI";
type = DEVICE_TYPE_MONITOR;
description = "MSI Monitor (Gaming Controller)";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = MSI_MONITOR_STATIC_MODE_VALUE;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = MSI_MONITOR_STATIC_MODE_VALUE;
Static.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Static.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Static);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = MSI_MONITOR_BREATHING_MODE_VALUE;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Breathing.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Breathing);
mode Flashing;
Flashing.name = "Flashing";
Flashing.value = MSI_MONITOR_FLASHING_MODE_VALUE;
Flashing.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Flashing.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Flashing);
mode Lightning;
Lightning.name = "Lightning";
Lightning.value = MSI_MONITOR_LIGHTNING_MODE_VALUE;
Lightning.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Lightning.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Lightning);
mode Marquee;
Marquee.name = "Marquee";
Marquee.value = MSI_MONITOR_MARQUEE_MODE_VALUE;
Marquee.flags = MODE_FLAG_AUTOMATIC_SAVE;
Marquee.color_mode = MODE_COLORS_NONE;
modes.push_back(Marquee);
mode Meteor;
Meteor.name = "Meteor";
Meteor.value = MSI_MONITOR_METEOR_MODE_VALUE;
Meteor.flags = MODE_FLAG_AUTOMATIC_SAVE;
Meteor.color_mode = MODE_COLORS_NONE;
modes.push_back(Meteor);
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = MSI_MONITOR_RAINBOW_MODE_VALUE;
Rainbow.flags = MODE_FLAG_AUTOMATIC_SAVE;
Rainbow.color_mode = MODE_COLORS_NONE;
modes.push_back(Rainbow);
mode Random;
Random.name = "Random";
Random.value = MSI_MONITOR_RANDOM_MODE_VALUE;
Random.flags = MODE_FLAG_AUTOMATIC_SAVE;
Random.color_mode = MODE_COLORS_RANDOM;
modes.push_back(Random);
mode Off;
Off.name = "Off";
Off.value = MSI_MONITOR_OFF_MODE_VALUE;
Off.flags = MODE_FLAG_AUTOMATIC_SAVE;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
SetupZones();
}
RGBController_MSIMonitor::~RGBController_MSIMonitor()
{
keepalive_thread_run = 0;
keepalive_thread->join();
delete keepalive_thread;
delete controller;
}
void RGBController_MSIMonitor::SetupZones()
{
zone new_zone;
new_zone.name = "Rear";
new_zone.type = ZONE_TYPE_LINEAR;
new_zone.leds_min = 9;
new_zone.leds_max = 9;
new_zone.leds_count = 9;
new_zone.matrix_map = nullptr;
zones.emplace_back(new_zone);
for(unsigned int i = 0 ; i < 9; i ++)
{
led new_led;
new_led.name = "LED " + std::to_string(i + 1);
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_MSIMonitor::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_MSIMonitor::DeviceUpdateLEDs()
{
last_update_time = std::chrono::steady_clock::now();
controller->Set(modes[active_mode].value, colors, active_mode == 0 ? 0x00 : 0x01);
}
void RGBController_MSIMonitor::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_MSIMonitor::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_MSIMonitor::DeviceUpdateMode()
{
controller->Set(modes[active_mode].value, colors, 0x01);
}
@@ -1,38 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_MSIMonitor.cpp |
| |
| RGBController for MSI monitor (gaming controller) |
| |
| Andy Herbert 2026 May 16 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <chrono>
#include "RGBController.h"
#include "MSIMonitorController.h"
class RGBController_MSIMonitor : public RGBController
{
public:
RGBController_MSIMonitor(MSIMonitorController* controller_ptr);
~RGBController_MSIMonitor();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
MSIMonitorController* controller;
std::thread* keepalive_thread;
std::atomic<bool> keepalive_thread_run;
std::chrono::time_point<std::chrono::steady_clock> last_update_time;
void KeepaliveThread();
};
@@ -307,7 +307,6 @@ static const mystic_light_185_config board_configs[] =
{ 0x7D32, 1, 0, 0, 1, &zones_set10, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MAG Z690 TOMAHAWK WIFI DDR4
{ 0x7D33, 6, 0, 0, 2, &zones_set5, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // PRO Z790-VC WIFI
{ 0x7D36, 6, 0, 0, 2, &zones_set5, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // PRO Z690-P DDR4
{ 0x7D37, 6, 0, 0, 2, &zones_set11, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // PRO B760M-VC WIFI (MS-7D37) - fans on JRAINBOW2
{ 0x7D38, 0, 0, 0, 1, &zones_set1, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MEG Z590 UNIFY-X
{ 0x7D40, 0, 0, 0, 1, &zones_set5, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MPG B760i EDGE WIFI DDR4
{ 0x7D41, 6, 0, 0, 2, &zones_set13, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MAG B660M TOMAHAWK WIFI DDR4
@@ -71,38 +71,34 @@ static const std::string board_names[] =
"MSI PRO B850M-VC WIFI6E (MS-7E71)",
"MSI MAG B850 TOMAHAWK WIFI (MS-7E53)",
"MSI MEG Z890 UNIFY-X (MS-7E20)",
"MSI PRO X870E-S EVO WIFI (MS-7E86)",
"MSI PRO Z890-P WIFI (MS-7E34)",
};
static const mystic_light_761_config board_configs[] =
{
{ &(board_names[0]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI X870 TOMAHAWK WIFI
{ &(board_names[1]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG B850M MORTAR WIFI
{ &(board_names[2]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG B850I EDGE TI WIFI
{ &(board_names[3]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI X870 GAMING PLUS WIFI
{ &(board_names[4]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI B850 GAMING PLUS WIFI6E
{ &(board_names[5]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI B850M GAMING PLUS WIFI6E
{ &(board_names[6]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG X870E CARBON WIFI
{ &(board_names[7]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI Z890 GAMING PLUS WIFI
{ &(board_names[8]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI X870E GAMING PLUS WIFI
{ &(board_names[9]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG X870E TOMAHAWK WIFI
{ &(board_names[10]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO B850-P WIFI (MS-7E56)
{ &(board_names[11]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI B850M GAMING PLUS WIFI
{ &(board_names[12]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO X870E-P WIFI
{ &(board_names[13]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG X870I EDGE TI EVO WIFI (MS-7E50)
{ &(board_names[14]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI B850 GAMING PLUS WIFI (MS-7E56)
{ &(board_names[15]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO X870-P WIFI
{ &(board_names[16]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG X870E EDGE TI WIFI
{ &(board_names[17]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG B850 TOMAHAWK MAX WIFI
{ &(board_names[18]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO B850M-P WIFI (MS-7E71)
{ &(board_names[19]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG Z890 TOMAHAWK WIFI (MS-7E32)
{ &(board_names[20]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG B850 EDGE TI WIFI (MS-7E62)
{ &(board_names[21]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO B850M-VC WIFI6E (MS-7E71)
{ &(board_names[22]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG B850 TOMAHAWK WIFI (MS-7E53)
{ &(board_names[23]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MEG Z890 UNIFY-X (MS-7E20)
{ &(board_names[24]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO X870E-S EVO WIFI (MS-7E86)
{ &(board_names[25]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO Z890-P WIFI (MS-7E34)
{ &(board_names[0]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI X870 TOMAHAWK WIFI
{ &(board_names[1]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG B850M MORTAR WIFI
{ &(board_names[2]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG B850I EDGE TI WIFI
{ &(board_names[3]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI X870 GAMING PLUS WIFI
{ &(board_names[4]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI B850 GAMING PLUS WIFI6E
{ &(board_names[5]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI B850M GAMING PLUS WIFI6E
{ &(board_names[6]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG X870E CARBON WIFI
{ &(board_names[7]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI Z890 GAMING PLUS WIFI
{ &(board_names[8]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI X870E GAMING PLUS WIFI
{ &(board_names[9]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG X870E TOMAHAWK WIFI
{ &(board_names[10]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO B850-P WIFI (MS-7E56)
{ &(board_names[11]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI B850M GAMING PLUS WIFI
{ &(board_names[12]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO X870E-P WIFI
{ &(board_names[13]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG X870I EDGE TI EVO WIFI (MS-7E50)
{ &(board_names[14]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI B850 GAMING PLUS WIFI (MS-7E56)
{ &(board_names[15]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO X870-P WIFI
{ &(board_names[16]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG X870E EDGE TI WIFI
{ &(board_names[17]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG B850 TOMAHAWK MAX WIFI
{ &(board_names[18]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO B850M-P WIFI (MS-7E71)
{ &(board_names[19]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG Z890 TOMAHAWK WIFI (MS-7E32)
{ &(board_names[20]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MPG B850 EDGE TI WIFI (MS-7E62)
{ &(board_names[21]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO B850M-VC WIFI6E (MS-7E71)
{ &(board_names[22]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MAG B850 TOMAHAWK WIFI (MS-7E53)
{ &(board_names[23]), 0, 0, 0, 1, &zone_set1, MSIMysticLight761Controller::DIRECT_MODE_ZONE_BASED }, // MSI MEG Z890 UNIFY-X (MS-7E20)
};
enum MSI_ZONE setup_map [] =
@@ -209,7 +209,6 @@ REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7D31", DetectMSIMysticLightCont
REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7D32", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7D32, 0x0001, 0x00);
REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7D33", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7D33, 0x0001, 0x00);
REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7D36", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7D36, 0x0001, 0x00);
REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7D37", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7D37, 0x0001, 0x00);
REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7D38", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7D38, 0x0001, 0x00);
REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7D40", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7D40, 0x0001, 0x00);
REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7D41", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7D41, 0x0001, 0x00);
@@ -17,13 +17,12 @@
#include "NZXTHue2Controller.h"
#include "StringUtils.h"
NZXTHue2Controller::NZXTHue2Controller(hid_device* dev_handle, unsigned int rgb_channels, unsigned int fan_channels, const char* path, std::string dev_name, bool use_2023_effects_val)
NZXTHue2Controller::NZXTHue2Controller(hid_device* dev_handle, unsigned int rgb_channels, unsigned int fan_channels, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
use_2023_effects = use_2023_effects_val;
num_fan_channels = fan_channels;
num_rgb_channels = rgb_channels;
@@ -331,14 +330,7 @@ void NZXTHue2Controller::SetChannelEffect
/*-----------------------------------------------------*\
| Send effect packet |
\*-----------------------------------------------------*/
if(use_2023_effects)
{
SendEffect2023(channel, mode, speed, direction, num_colors, &color_data[0]);
}
else
{
SendEffect(channel, mode, speed, direction, num_colors, &color_data[0]);
}
SendEffect(channel, mode, speed, direction, num_colors, &color_data[0]);
}
void NZXTHue2Controller::SetChannelLEDs
@@ -365,15 +357,14 @@ void NZXTHue2Controller::SetChannelLEDs
/*-----------------------------------------------------*\
| Send first group of color data |
\*-----------------------------------------------------*/
unsigned char first_color_count = (num_colors > 20) ? 20 : (unsigned char)num_colors;
SendDirect(channel, 0, first_color_count, &color_data[0]);
SendDirect(channel, 0, 20, &color_data[0]);
/*-----------------------------------------------------*\
| Send second group of color data if necessary |
\*-----------------------------------------------------*/
if(num_colors > 20)
{
SendDirect(channel, 1, (unsigned char)(num_colors - 20), &color_data[60]);
SendDirect(channel, 1, 20, &color_data[60]);
}
/*-----------------------------------------------------*\
@@ -511,99 +502,6 @@ void NZXTHue2Controller::SendEffect
//hid_read(dev, usb_buf, 64);
}
void NZXTHue2Controller::SendEffect2023
(
unsigned char channel,
unsigned char mode,
unsigned char speed,
bool direction,
unsigned char color_count,
unsigned char* color_data
)
{
unsigned char usb_buf[64];
unsigned char speed_data[2] = { 0x32, 0x00 };
unsigned char mode_modifier = 0x00;
memset(usb_buf, 0x00, sizeof(usb_buf));
if(speed > HUE_2_SPEED_FASTEST)
{
speed = HUE_2_SPEED_NORMAL;
}
switch(mode)
{
case HUE_2_MODE_FADING:
{
const unsigned char values[5][2] = { {0x50, 0x00}, {0x3C, 0x00}, {0x28, 0x00}, {0x14, 0x00}, {0x0A, 0x00} };
speed_data[0] = values[speed][0];
speed_data[1] = values[speed][1];
mode_modifier = 0x08;
}
break;
case HUE_2_MODE_SPECTRUM:
case HUE_2_MODE_RAINBOW_FLOW:
case HUE_2_MODE_SUPER_RAINBOW:
case HUE_2_MODE_RAINBOW_PULSE:
{
const unsigned char values[5][2] = { {0x5E, 0x01}, {0x2C, 0x01}, {0xFA, 0x00}, {0x96, 0x00}, {0x50, 0x00} };
speed_data[0] = values[speed][0];
speed_data[1] = values[speed][1];
}
break;
case HUE_2_MODE_ALTERNATING:
{
const unsigned char values[5][2] = { {0x40, 0x06}, {0x14, 0x05}, {0xE8, 0x03}, {0x20, 0x03}, {0x58, 0x02} };
speed_data[0] = values[speed][0];
speed_data[1] = values[speed][1];
}
break;
case HUE_2_MODE_PULSING:
case HUE_2_MODE_STARRY_NIGHT:
{
const unsigned char values[5][2] = { {0x19, 0x00}, {0x14, 0x00}, {0x0F, 0x00}, {0x07, 0x00}, {0x04, 0x00} };
speed_data[0] = values[speed][0];
speed_data[1] = values[speed][1];
mode_modifier = (mode == HUE_2_MODE_PULSING) ? 0x08 : 0x00;
}
break;
case HUE_2_MODE_BREATHING:
{
const unsigned char values[5][2] = { {0x28, 0x00}, {0x1E, 0x00}, {0x14, 0x00}, {0x0A, 0x00}, {0x04, 0x00} };
speed_data[0] = values[speed][0];
speed_data[1] = values[speed][1];
mode_modifier = 0x08;
}
break;
default:
break;
}
usb_buf[0x00] = 0x2A;
usb_buf[0x01] = 0x04;
usb_buf[0x02] = (unsigned char)(1 << channel);
usb_buf[0x03] = usb_buf[0x02];
usb_buf[0x04] = mode;
usb_buf[0x05] = speed_data[0];
usb_buf[0x06] = speed_data[1];
memcpy(&usb_buf[0x07], color_data, color_count * 3);
usb_buf[0x37] = direction ? 0x02 : 0x00;
usb_buf[0x38] = color_count;
usb_buf[0x39] = mode_modifier;
usb_buf[0x3A] = 0x08;
usb_buf[0x3B] = 0x03;
hid_write(dev, usb_buf, 64);
}
void NZXTHue2Controller::SendFirmwareRequest()
{
unsigned char usb_buf[64];
@@ -66,7 +66,7 @@ enum
class NZXTHue2Controller
{
public:
NZXTHue2Controller(hid_device* dev_handle, unsigned int rgb_channels, unsigned int fan_channels, const char* path, std::string dev_name, bool use_2023_effects = false);
NZXTHue2Controller(hid_device* dev_handle, unsigned int rgb_channels, unsigned int fan_channels, const char* path, std::string dev_name);
~NZXTHue2Controller();
std::string GetFirmwareVersion();
@@ -129,7 +129,6 @@ private:
char firmware_version[16];
std::string location;
std::string name;
bool use_2023_effects;
unsigned int num_fan_channels;
unsigned int num_rgb_channels;
@@ -156,15 +155,5 @@ private:
unsigned char* color_data
);
void SendEffect2023
(
unsigned char channel,
unsigned char mode,
unsigned char speed,
bool direction,
unsigned char color_count,
unsigned char* color_data
);
void SendFirmwareRequest();
};
@@ -38,13 +38,13 @@
#define NZXT_SMART_DEVICE_V2_1_PID 0x200D
#define NZXT_SMART_DEVICE_V2_2_PID 0x200F
static void spawn_hue(hid_device_info* info, const std::string& name, int rgb_channels, int fan_channels, bool use_2023_effects = false)
static void spawn_hue(hid_device_info* info, const std::string& name, int rgb_channels, int fan_channels)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
NZXTHue2Controller* controller = new NZXTHue2Controller(dev, rgb_channels, fan_channels, info->path, name, use_2023_effects);
NZXTHue2Controller* controller = new NZXTHue2Controller(dev, rgb_channels, fan_channels, info->path, name);
RGBController_NZXTHue2* rgb_controller = new RGBController_NZXTHue2(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
@@ -83,7 +83,7 @@ void DetectNZXTKrakenX3(hid_device_info* info, const std::string& name)
void DetectNZXTKrakenElite(hid_device_info* info, const std::string& name)
{
spawn_hue(info, name, 2, 2, true);
spawn_hue(info, name, 2, 2);
}
void DetectNZXTFanController(hid_device_info* info, const std::string& name)
-22
View File
@@ -1,22 +0,0 @@
/*---------------------------------------------------------*\
| QMKCommon.h |
| |
| Common QMK definitions |
| |
| Adam Honse <calcprogrammer1@gmail.com) 24 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
typedef struct
{
unsigned char x; /* X position in RGB matrix (0-224) */
unsigned char y; /* Y position in RGB matrix (0-64) */
unsigned char flags; /* LED flags */
unsigned char row; /* Row in key matrix */
unsigned char col; /* Column in key matrix */
bool valid; /* Is this LED valid? */
} qmk_rgb_matrix_led_info;
@@ -1,533 +0,0 @@
/*---------------------------------------------------------*\
| QMKKeychronController.cpp |
| |
| Driver for Keychron QMK-based keyboards |
| |
| Amadej Kastelic 21 Jun 2026 |
| Adam Honse <calcprogrammer1@gmail.com> 22 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <string.h>
#include "hsv.h"
#include "QMKKeychronController.h"
#include "QMKViaCommands.h"
#include "StringUtils.h"
#include "LogManager.h"
using namespace std::chrono_literals;
QMKKeychronController::QMKKeychronController(hid_device* dev_handle, const char *path)
{
/*-----------------------------------------------------*\
| Initialize controller fields |
\*-----------------------------------------------------*/
dev = dev_handle;
location = path;
kc_protocol_version = 0;
supported_features = 0;
via_protocol_version = 0;
/*-----------------------------------------------------*\
| Read product string |
\*-----------------------------------------------------*/
wchar_t product_string[256];
int ret = hid_get_product_string(dev, product_string, 256);
if(ret != 0)
{
name = "";
}
else
{
name = StringUtils::wstring_to_string(product_string);
}
/*-----------------------------------------------------*\
| Read vendor string |
\*-----------------------------------------------------*/
wchar_t vendor_string[256];
ret = hid_get_manufacturer_string(dev, vendor_string, 256);
if(ret != 0)
{
vendor = "";
}
else
{
vendor = StringUtils::wstring_to_string(vendor_string);
}
/*-----------------------------------------------------*\
| Read serial string |
\*-----------------------------------------------------*/
wchar_t serial_string[256];
ret = hid_get_serial_number_string(dev, serial_string, 256);
if(ret != 0)
{
serial = "";
}
else
{
serial = StringUtils::wstring_to_string(serial_string);
}
/*-----------------------------------------------------*\
| Get VIA protocol version |
\*-----------------------------------------------------*/
CmdGetViaProtocolVersion(&via_protocol_version);
/*-----------------------------------------------------*\
| Get Keychron protocol version |
\*-----------------------------------------------------*/
CmdGetKeychronProtocolVersion(&kc_protocol_version);
/*-----------------------------------------------------*\
| Get Keychron firmware version |
\*-----------------------------------------------------*/
kc_firmware_version = CmdGetKeychronFirmwareVersion();
/*-----------------------------------------------------*\
| Get supported Keychron features |
\*-----------------------------------------------------*/
CmdGetSupportFeature(&supported_features);
if(!GetSupported())
{
return;
}
/*-----------------------------------------------------*\
| Get Keychron RGB protocol version |
\*-----------------------------------------------------*/
CmdGetKeychronRGBProtocolVersion(&kc_rgb_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<unsigned char> 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()
{
hid_close(dev);
}
std::string QMKKeychronController::GetLocation()
{
return("HID: " + location);
}
std::string QMKKeychronController::GetName()
{
return(name);
}
std::string QMKKeychronController::GetSerial()
{
return(serial);
}
std::string QMKKeychronController::GetVendor()
{
return(vendor);
}
std::string QMKKeychronController::GetVersion()
{
/*-----------------------------------------------------*\
| Format multi-line version text |
\*-----------------------------------------------------*/
return("VIA: " + std::to_string(via_protocol_version) + "\r\n" +
"Keychron: " + std::to_string(kc_protocol_version) + "\r\n" +
"Keychron RGB: " + std::to_string(kc_rgb_protocol_version) + "\r\n" +
"Keychron FW: " + kc_firmware_version);
}
bool QMKKeychronController::GetSupported()
{
return(supported_features & KC_FEATURE_KEYCHRON_RGB);
}
unsigned short QMKKeychronController::GetKeycode(unsigned short led_index)
{
return(keycodes[led_index]);
}
unsigned short QMKKeychronController::GetLEDCount()
{
return(number_leds);
}
qmk_rgb_matrix_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);
}
std::string QMKKeychronController::CmdGetKeychronFirmwareVersion()
{
char response[30];
ViaSendCommand(KC_GET_FIRMWARE_VERSION, NULL, 0, (unsigned char*)response, sizeof(response));
/*-----------------------------------------------------*\
| Ensure response null termination |
\*-----------------------------------------------------*/
response[29] = 0;
return(std::string(response));
}
void QMKKeychronController::CmdGetKeychronProtocolVersion
(
unsigned char* kc_protocol_version
)
{
ViaSendCommand(KC_GET_PROTOCOL_VERSION, NULL, 0, (unsigned char*)kc_protocol_version, sizeof(unsigned char));
}
void QMKKeychronController::CmdGetKeychronRGBProtocolVersion(unsigned short* kc_rgb_protocol_version)
{
ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_PROTOCOL_VER, NULL, 0, (unsigned char*)kc_rgb_protocol_version, sizeof(unsigned short));
/*-----------------------------------------------------*\
| The RGB protocol version byte order is reversed |
\*-----------------------------------------------------*/
*kc_rgb_protocol_version = ((*kc_rgb_protocol_version & 0x00FF) << 8) | ((*kc_rgb_protocol_version & 0xFF00) >> 8);
}
std::vector<unsigned char> 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<unsigned char> 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::CmdGetSupportFeature(unsigned short* supported_features)
{
ViaSendCommand(KC_GET_SUPPORT_FEATURE, NULL, 0, (unsigned char*)supported_features, sizeof(unsigned short));
/*-----------------------------------------------------*\
| The supported features byte order is reversed |
\*-----------------------------------------------------*/
*supported_features = ((*supported_features & 0x00FF) << 8) | ((*supported_features & 0xFF00) >> 8);
}
void QMKKeychronController::CmdGetViaProtocolVersion
(
unsigned short* via_protocol_version
)
{
ViaSendCommand(QMK_VIA_CMD_GET_PROTOCOL_VERSION, NULL, 0, (unsigned char*)via_protocol_version, sizeof(unsigned short));
/*-----------------------------------------------------*\
| The protocol version byte order is reversed |
\*-----------------------------------------------------*/
*via_protocol_version = ((*via_protocol_version & 0x00FF) << 8) | ((*via_protocol_version & 0xFF00) >> 8);
}
void QMKKeychronController::CmdSaveMode()
{
ViaSendCommandSub(KC_KEYCHRON_RGB, KEYCHRON_RGB_SAVE, NULL, 0, NULL, 0);
}
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_index;
args[1] = number_leds;
if(number_leds > 9)
{
number_leds = 9;
}
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);
}
void QMKKeychronController::CmdSetBrightness(unsigned char brightness)
{
unsigned char args[3];
args[0] = QMK_VIA_RGB_MATRIX_CHANNEL;
args[1] = QMK_VIA_RGB_MATRIX_BRIGHTNESS;
args[2] = brightness;
ViaSendCommand(QMK_VIA_CMD_CUSTOM_SET_VALUE, args, sizeof(args), NULL, 0);
}
void QMKKeychronController::CmdSetColorHS(unsigned char h, unsigned char s)
{
unsigned char args[4];
args[0] = QMK_VIA_RGB_MATRIX_CHANNEL;
args[1] = QMK_VIA_RGB_MATRIX_COLOR;
args[2] = h;
args[3] = s;
ViaSendCommand(QMK_VIA_CMD_CUSTOM_SET_VALUE, args, sizeof(args), NULL, 0);
}
void QMKKeychronController::CmdSetPerKeyRGBType(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::CmdSetRGBMatrixMode(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);
}
void QMKKeychronController::CmdSetSpeed(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);
}
int QMKKeychronController::ViaSendCommand
(
unsigned char cmd,
unsigned char* data_in,
unsigned char data_in_size,
unsigned char* data_out,
unsigned char data_out_size
)
{
/*-----------------------------------------------------*\
| Standard VIA command with no sub-command |
| |
| Byte 0: Command |
| Byte 1+: Data |
\*-----------------------------------------------------*/
unsigned char usb_buf[KEYCHRON_QHE_PACKET_SIZE + 1];
memset(usb_buf, 0, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Write command, offsetting by 1 for HID report ID |
\*-----------------------------------------------------*/
usb_buf[1] = cmd;
memcpy(&usb_buf[2], data_in, data_in_size);
hid_write(dev, usb_buf, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Read response |
\*-----------------------------------------------------*/
int bytes_received = hid_read_timeout(dev, usb_buf, sizeof(usb_buf) - 1, 1000);
if(usb_buf[0] != cmd)
{
return(-1);
}
memcpy(data_out, &usb_buf[1], data_out_size);
return(bytes_received - 1);
}
int QMKKeychronController::ViaSendCommandSub
(
unsigned char cmd,
unsigned char subcmd,
unsigned char* data_in,
unsigned char data_in_size,
unsigned char* data_out,
unsigned char data_out_size
)
{
/*-----------------------------------------------------*\
| Standard VIA command with sub-command |
| |
| Byte 0: Command |
| Byte 1: Sub-Command |
| Byte 2+: Data |
\*-----------------------------------------------------*/
unsigned char usb_buf[KEYCHRON_QHE_PACKET_SIZE + 1];
memset(usb_buf, 0, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Write command, offsetting by 1 for HID report ID |
\*-----------------------------------------------------*/
usb_buf[1] = cmd;
usb_buf[2] = subcmd;
memcpy(&usb_buf[3], data_in, data_in_size);
hid_write(dev, usb_buf, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Read response |
\*-----------------------------------------------------*/
int bytes_received = hid_read_timeout(dev, usb_buf, sizeof(usb_buf) - 1, 1000);
if(usb_buf[0] != cmd || usb_buf[1] != subcmd)
{
return(-1);
}
memcpy(data_out, &usb_buf[2], data_out_size);
return(bytes_received - 2);
}
@@ -1,448 +0,0 @@
/*---------------------------------------------------------*\
| QMKKeychronController.h |
| |
| Driver for Keychron QMK-based keyboards |
| |
| Amadej Kastelic 21 Jun 2026 |
| Adam Honse <calcprogrammer1@gmail.com> 22 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "QMKCommon.h"
#include "RGBController.h"
/*---------------------------------------------------------*\
| Keychron vendor ID |
\*---------------------------------------------------------*/
#define KEYCHRON_VID 0x3434
/*---------------------------------------------------------*\
| Product IDs |
\*---------------------------------------------------------*/
#define KEYCHRON_C1_PRO_ANSI_RGB_PID 0x0510
#define KEYCHRON_C1_PRO_8K_ANSI_PID 0x0521
#define KEYCHRON_C1_PRO_8K_ISO_PID 0x051D
#define KEYCHRON_C1_PRO_8K_JIS_PID 0x051E
#define KEYCHRON_C1_PRO_V2_ANSI_RGB_PID 0x0516
#define KEYCHRON_C2_PRO_ANSI_RGB_PID 0x0520
#define KEYCHRON_C2_PRO_8K_ANSI_PID 0x0522
#define KEYCHRON_C2_PRO_8K_ISO_PID 0x052D
#define KEYCHRON_C2_PRO_V2_ANSI_RGB_PID 0x0526
#define KEYCHRON_C3_PRO_ANSI_RGB_PID 0x0433
#define KEYCHRON_C3_PRO_8K_ANSI_PID 0x0530
#define KEYCHRON_C3_PRO_8K_ISO_PID 0x0531
#define KEYCHRON_C3_PRO_8K_JIS_PID 0x0532
#define KEYCHRON_K0_MAX_PID 0x0A06
#define KEYCHRON_K1_MAX_ANSI_RGB_PID 0x0A10
#define KEYCHRON_K1_MAX_ISO_RGB_PID 0x0A11
#define KEYCHRON_K1_MAX_JIS_RGB_PID 0x0A12
#define KEYCHRON_K1_V6_ANSI_RGB_PID 0x0D10
#define KEYCHRON_K1_V6_ISO_RGB_PID 0x0D11
#define KEYCHRON_K1_V6_JIS_RGB_PID 0x0D12
#define KEYCHRON_K2_HE_ANSI_PID 0x0E20
#define KEYCHRON_K2_HE_ISO_PID 0x0E21
#define KEYCHRON_K2_HE_JIS_PID 0x0E22
#define KEYCHRON_K2_MAX_ANSI_RGB_PID 0x0A20
#define KEYCHRON_K2_MAX_ISO_RGB_PID 0x0A21
#define KEYCHRON_K2_MAX_JIS_RGB_PID 0x0A22
#define KEYCHRON_K2_V3_ANSI_RGB_PID 0x0D20
#define KEYCHRON_K2_V3_ISO_RGB_PID 0x0D21
#define KEYCHRON_K2_V3_JIS_RGB_PID 0x0D22
#define KEYCHRON_K3_MAX_ANSI_RGB_PID 0x0A30
#define KEYCHRON_K3_MAX_ISO_RGB_PID 0x0A31
#define KEYCHRON_K3_MAX_JIS_RGB_PID 0x0A32
#define KEYCHRON_K3_V3_ANSI_RGB_PID 0x0D30
#define KEYCHRON_K3_V3_ISO_RGB_PID 0x0D31
#define KEYCHRON_K3_V3_JIS_RGB_PID 0x0D32
#define KEYCHRON_K4_HE_ANSI_PID 0x0E40
#define KEYCHRON_K4_HE_ISO_PID 0x0E41
#define KEYCHRON_K4_HE_JIS_PID 0x0E42
#define KEYCHRON_K4_MAX_ANSI_RGB_PID 0x0A40
#define KEYCHRON_K4_MAX_ISO_RGB_PID 0x0A41
#define KEYCHRON_K4_MAX_JIS_RGB_PID 0x0A42
#define KEYCHRON_K4_V3_ANSI_RGB_PID 0x0D40
#define KEYCHRON_K4_V3_ISO_RGB_PID 0x0D41
#define KEYCHRON_K4_V3_JIS_RGB_PID 0x0D42
#define KEYCHRON_K5_MAX_ANSI_RGB_PID 0x0A50
#define KEYCHRON_K5_MAX_ISO_RGB_PID 0x0A51
#define KEYCHRON_K5_MAX_JIS_RGB_PID 0x0A52
#define KEYCHRON_K5_MAX_JIS_V2_RGB_PID 0x0A58
#define KEYCHRON_K5_V2_ANSI_RGB_PID 0x0D50
#define KEYCHRON_K5_V2_ISO_RGB_PID 0x0D51
#define KEYCHRON_K5_V2_JIS_RGB_PID 0x0D52
#define KEYCHRON_K6_HE_ANSI_PID 0x0E60
#define KEYCHRON_K7_MAX_ANSI_RGB_PID 0x0A70
#define KEYCHRON_K7_MAX_ISO_RGB_PID 0x0A71
#define KEYCHRON_K7_MAX_JIS_RGB_PID 0x0A72
#define KEYCHRON_K7_MAX_JIS_V2_RGB_PID 0x0A76
#define KEYCHRON_K8_HE_ANSI_PID 0x0E80
#define KEYCHRON_K8_HE_ISO_PID 0x0E81
#define KEYCHRON_K8_HE_JIS_PID 0x0E82
#define KEYCHRON_K8_MAX_ANSI_RGB_PID 0x0A80
#define KEYCHRON_K8_MAX_ISO_RGB_PID 0x0A81
#define KEYCHRON_K8_MAX_JIS_RGB_PID 0x0A82
#define KEYCHRON_K8_PRO_ANSI_RGB_PID 0x0280
#define KEYCHRON_K8_PRO_ISO_RGB_PID 0x0281
#define KEYCHRON_K8_PRO_JIS_RGB_PID 0x0282
#define KEYCHRON_K8_V2_ANSI_RGB_PID 0x0D80
#define KEYCHRON_K8_V2_ISO_RGB_PID 0x0D81
#define KEYCHRON_K8_V2_JIS_RGB_PID 0x0D82
#define KEYCHRON_K9_MAX_ANSI_RGB_PID 0x0A90
#define KEYCHRON_K10_HE_ANSI_PID 0x0EA0
#define KEYCHRON_K10_HE_ISO_PID 0x0EA1
#define KEYCHRON_K10_MAX_ANSI_RGB_PID 0x0AA0
#define KEYCHRON_K10_MAX_ISO_RGB_PID 0x0AA1
#define KEYCHRON_K10_MAX_JIS_RGB_PID 0x0AA2
#define KEYCHRON_K10_V2_ANSI_RGB_PID 0x0DA0
#define KEYCHRON_K10_V2_ISO_RGB_PID 0x0DA1
#define KEYCHRON_K10_V2_JIS_RGB_PID 0x0DA2
#define KEYCHRON_K11_MAX_ANSI_ENCODER_RGB_PID 0x0AB3
#define KEYCHRON_K11_MAX_ISO_ENCODER_RGB_PID 0x0AB4
#define KEYCHRON_K11_MAX_JIS_ENCODER_RGB_PID 0x0AB5
#define KEYCHRON_K13_MAX_ANSI_RGB_PID 0x0AD0
#define KEYCHRON_K13_MAX_ISO_RGB_PID 0x0AD1
#define KEYCHRON_K13_MAX_JIS_RGB_PID 0x0AD2
#define KEYCHRON_K15_MAX_ANSI_ENCODER_RGB_PID 0x0AF0
#define KEYCHRON_K15_MAX_ISO_ENCODER_RGB_PID 0x0AF1
#define KEYCHRON_K17_MAX_ANSI_ENCODER_RGB_PID 0x0A00
#define KEYCHRON_K17_MAX_ISO_ENCODER_RGB_PID 0x0A01
#define KEYCHRON_K17_MAX_JIS_ENCODER_RGB_PID 0x0A02
#define KEYCHRON_Q0_BASE_PID 0x0130
#define KEYCHRON_Q0_PLUS_PID 0x0131
#define KEYCHRON_Q0_MAX_ENCODER_PID 0x0800
#define KEYCHRON_Q1_HE_ANSI_ENCODER_PID 0x0B10
#define KEYCHRON_Q1_HE_ISO_ENCODER_PID 0x0B11
#define KEYCHRON_Q1_HE_JIS_ENCODER_PID 0x0B12
#define KEYCHRON_Q1_MAX_ANSI_ENCODER_PID 0x0810
#define KEYCHRON_Q1_MAX_ISO_ENCODER_PID 0x0811
#define KEYCHRON_Q1_MAX_JIS_ENCODER_PID 0x0812
#define KEYCHRON_Q1_V1_ANSI_PID 0x0100
#define KEYCHRON_Q1_V1_ANSI_ENCODER_PID 0x0101
#define KEYCHRON_Q1_V1_ISO_PID 0x0102
#define KEYCHRON_Q1_V1_ISO_ENCODER_PID 0x0103
#define KEYCHRON_Q1_V2_ANSI_PID 0x0106
#define KEYCHRON_Q1_V2_ANSI_ENCODER_PID 0x0107
#define KEYCHRON_Q1_V2_ISO_PID 0x0108
#define KEYCHRON_Q1_V2_ISO_ENCODER_PID 0x0109
#define KEYCHRON_Q1_V2_JIS_PID 0x010A
#define KEYCHRON_Q1_V2_JIS_ENCODER_PID 0x010B
#define KEYCHRON_Q2_ANSI_PID 0x0110
#define KEYCHRON_Q2_ANSI_ENCODER_PID 0x0111
#define KEYCHRON_Q2_ISO_PID 0x0112
#define KEYCHRON_Q2_ISO_ENCODER_PID 0x0113
#define KEYCHRON_Q2_JIS_PID 0x0114
#define KEYCHRON_Q2_JIS_ENCODER_PID 0x0115
#define KEYCHRON_Q2_HE_ANSI_ENCODER_PID 0x0B20
#define KEYCHRON_Q2_MAX_ANSI_ENCODER_PID 0x0820
#define KEYCHRON_Q2_MAX_ISO_ENCODER_PID 0x0821
#define KEYCHRON_Q3_ANSI_PID 0x0120
#define KEYCHRON_Q3_ANSI_ENCODER_PID 0x0121
#define KEYCHRON_Q3_ISO_PID 0x0122
#define KEYCHRON_Q3_ISO_ENCODER_PID 0x0123
#define KEYCHRON_Q3_JIS_PID 0x0124
#define KEYCHRON_Q3_JIS_ENCODER_PID 0x0125
#define KEYCHRON_Q3_HE_ANSI_ENCODER_PID 0x0B30
#define KEYCHRON_Q3_HE_ISO_ENCODER_PID 0x0B31
#define KEYCHRON_Q3_HE_JIS_ENCODER_PID 0x0B32
#define KEYCHRON_Q3_MAX_ANSI_ENCODER_PID 0x0830
#define KEYCHRON_Q3_MAX_ISO_ENCODER_PID 0x0831
#define KEYCHRON_Q4_ANSI_PID 0x0140
#define KEYCHRON_Q4_ISO_PID 0x0142
#define KEYCHRON_Q4_HE_ANSI_PID 0x0B40
#define KEYCHRON_Q5_ANSI_PID 0x0150
#define KEYCHRON_Q5_ANSI_ENCODER_PID 0x0151
#define KEYCHRON_Q5_ISO_PID 0x0152
#define KEYCHRON_Q5_ISO_ENCODER_PID 0x0153
#define KEYCHRON_Q5_HE_ANSI_ENCODER_PID 0x0B50
#define KEYCHRON_Q5_HE_ISO_ENCODER_PID 0x0B51
#define KEYCHRON_Q5_HE_JIS_ENCODER_PID 0x0B52
#define KEYCHRON_Q5_MAX_ANSI_ENCODER_PID 0x0850
#define KEYCHRON_Q5_MAX_ISO_ENCODER_PID 0x0851
#define KEYCHRON_Q5_MAX_JIS_ENCODER_PID 0x0852
#define KEYCHRON_Q6_ANSI_PID 0x0160
#define KEYCHRON_Q6_ANSI_ENCODER_PID 0x0161
#define KEYCHRON_Q6_ISO_PID 0x0162
#define KEYCHRON_Q6_ISO_ENCODER_PID 0x0163
#define KEYCHRON_Q6_HE_ANSI_ENCODER_PID 0x0B60
#define KEYCHRON_Q6_HE_ISO_ENCODER_PID 0x0B61
#define KEYCHRON_Q6_HE_JIS_ENCODER_PID 0x0B62
#define KEYCHRON_Q6_MAX_ANSI_ENCODER_PID 0x0860
#define KEYCHRON_Q6_MAX_ISO_ENCODER_PID 0x0861
#define KEYCHRON_Q7_ANSI_PID 0x0170
#define KEYCHRON_Q7_ISO_PID 0x0172
#define KEYCHRON_Q8_ANSI_PID 0x0180
#define KEYCHRON_Q8_ANSI_ENCODER_PID 0x0181
#define KEYCHRON_Q8_ISO_PID 0x0182
#define KEYCHRON_Q8_ISO_ENCODER_PID 0x0183
#define KEYCHRON_Q8_MAX_ANSI_ENCODER_PID 0x0880
#define KEYCHRON_Q9_ANSI_PID 0x0190
#define KEYCHRON_Q9_ANSI_ENCODER_PID 0x0191
#define KEYCHRON_Q9_ISO_PID 0x0192
#define KEYCHRON_Q9_ISO_ENCODER_PID 0x0193
#define KEYCHRON_Q9_PLUS_ANSI_ENCODER_PID 0x0194
#define KEYCHRON_Q10_ANSI_ENCODER_PID 0x01A1
#define KEYCHRON_Q10_ISO_ENCODER_PID 0x01A3
#define KEYCHRON_Q10_MAX_ANSI_ENCODER_PID 0x08A0
#define KEYCHRON_Q10_MAX_ISO_ENCODER_PID 0x08A1
#define KEYCHRON_Q11_ANSI_ENCODER_PID 0x01E0
#define KEYCHRON_Q11_ISO_ENCODER_PID 0x01E1
#define KEYCHRON_Q12_ANSI_ENCODER_PID 0x01D1
#define KEYCHRON_Q12_ISO_ENCODER_PID 0x01D3
#define KEYCHRON_Q12_HE_ANSI_ENCODER_PID 0x0BC0
#define KEYCHRON_Q12_HE_ISO_ENCODER_PID 0x0BC1
#define KEYCHRON_Q12_MAX_ANSI_ENCODER_PID 0x08C3
#define KEYCHRON_Q12_MAX_ISO_ENCODER_PID 0x08C4
#define KEYCHRON_Q13_MAX_ANSI_ENCODER_PID 0x08D0
#define KEYCHRON_Q13_MAX_JIS_ENCODER_PID 0x08D2
#define KEYCHRON_Q14_MAX_ANSI_ENCODER_PID 0x08E0
#define KEYCHRON_Q15_MAX_ANSI_ENCODER_PID 0x08F0
#define KEYCHRON_Q60_MAX_ANSI_PID 0x08C0
#define KEYCHRON_Q65_MAX_ANSI_ENCODER_PID 0x08B0
#define KEYCHRON_S1_ANSI_RGB_PID 0x0410
#define KEYCHRON_V1_ABNT2_ENCODER_PID 0x0317
#define KEYCHRON_V1_ANSI_PID 0x0310
#define KEYCHRON_V1_ANSI_ENCODER_PID 0x0311
#define KEYCHRON_V1_ISO_PID 0x0312
#define KEYCHRON_V1_ISO_ENCODER_PID 0x0313
#define KEYCHRON_V1_JIS_PID 0x0314
#define KEYCHRON_V1_JIS_ENCODER_PID 0x0315
#define KEYCHRON_V1_8K_ANSI_ENCODER_PID 0x0F10
#define KEYCHRON_V1_MAX_ANSI_ENCODER_PID 0x0913
#define KEYCHRON_V1_MAX_ISO_ENCODER_PID 0x0914
#define KEYCHRON_V1_MAX_JIS_ENCODER_PID 0x0915
#define KEYCHRON_V2_ABNT2_ENCODER_PID 0x0327
#define KEYCHRON_V2_ANSI_PID 0x0320
#define KEYCHRON_V2_ANSI_ENCODER_PID 0x0321
#define KEYCHRON_V2_ISO_PID 0x0322
#define KEYCHRON_V2_ISO_ENCODER_PID 0x0323
#define KEYCHRON_V2_JIS_PID 0x0324
#define KEYCHRON_V2_JIS_ENCODER_PID 0x0325
#define KEYCHRON_V2_MAX_ANSI_ENCODER_PID 0x0920
#define KEYCHRON_V2_MAX_ISO_ENCODER_PID 0x0921
#define KEYCHRON_V2_MAX_JIS_ENCODER_PID 0x0922
#define KEYCHRON_V3_ABNT2_ENCODER_PID 0x0337
#define KEYCHRON_V3_ANSI_PID 0x0330
#define KEYCHRON_V3_ANSI_ENCODER_PID 0x0331
#define KEYCHRON_V3_ISO_PID 0x0332
#define KEYCHRON_V3_ISO_ENCODER_PID 0x0333
#define KEYCHRON_V3_JIS_PID 0x0334
#define KEYCHRON_V3_JIS_ENCODER_PID 0x0335
#define KEYCHRON_V3_MAX_ANSI_ENCODER_PID 0x0933
#define KEYCHRON_V3_MAX_ISO_ENCODER_PID 0x0934
#define KEYCHRON_V3_MAX_JIS_ENCODER_PID 0x0935
#define KEYCHRON_V4_ANSI_PID 0x0340
#define KEYCHRON_V4_ISO_PID 0x0342
#define KEYCHRON_V4_MAX_ANSI_PID 0x0940
#define KEYCHRON_V4_MAX_ISO_PID 0x0941
#define KEYCHRON_V5_ANSI_PID 0x0350
#define KEYCHRON_V5_ANSI_ENCODER_PID 0x0351
#define KEYCHRON_V5_ISO_PID 0x0352
#define KEYCHRON_V5_ISO_ENCODER_PID 0x0353
#define KEYCHRON_V5_MAX_ANSI_ENCODER_PID 0x0950
#define KEYCHRON_V5_MAX_ISO_ENCODER_PID 0x0951
#define KEYCHRON_V5_MAX_JIS_ENCODER_PID 0x0952
#define KEYCHRON_V6_ABNT2_ENCODER_PID 0x0367
#define KEYCHRON_V6_ANSI_PID 0x0360
#define KEYCHRON_V6_ANSI_ENCODER_PID 0x0361
#define KEYCHRON_V6_ISO_PID 0x0362
#define KEYCHRON_V6_ISO_ENCODER_PID 0x0363
#define KEYCHRON_V6_MAX_ANSI_ENCODER_PID 0x0960
#define KEYCHRON_V6_MAX_ISO_ENCODER_PID 0x0961
#define KEYCHRON_V6_MAX_JIS_ENCODER_PID 0x0962
#define KEYCHRON_V6_V2_ISO_ENCODER_PID 0x0368
#define KEYCHRON_V7_ANSI_PID 0x0370
#define KEYCHRON_V7_ISO_PID 0x0372
#define KEYCHRON_V8_ANSI_PID 0x0380
#define KEYCHRON_V8_ANSI_ENCODER_PID 0x0381
#define KEYCHRON_V8_ISO_PID 0x0382
#define KEYCHRON_V8_ISO_ENCODER_PID 0x0383
#define KEYCHRON_V8_MAX_ANSI_ENCODER_PID 0x0980
#define KEYCHRON_V8_MAX_ISO_ENCODER_PID 0x0981
#define KEYCHRON_V10_ANSI_ENCODER_PID 0x03A1
#define KEYCHRON_V10_ISO_ENCODER_PID 0x03A3
#define KEYCHRON_V10_MAX_ANSI_ENCODER_PID 0x09A0
#define KEYCHRON_V10_MAX_ISO_ENCODER_PID 0x09A1
/*---------------------------------------------------------*\
| 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
/*---------------------------------------------------------*\
| Keychron-specific VIA protocol extension |
\*---------------------------------------------------------*/
enum
{
KC_GET_PROTOCOL_VERSION = 0xA0,
KC_GET_FIRMWARE_VERSION = 0xA1,
KC_GET_SUPPORT_FEATURE = 0xA2,
KC_GET_DEFAULT_LAYER = 0xA3,
KC_MISC_CMD_GROUP = 0xA7,
KC_KEYCHRON_RGB = 0xA8,
KC_ANALOG_MATRIX = 0xA9,
KC_WIRELESS_DFU = 0xAA,
KC_FACTORY_TEST = 0xAB
};
enum KeychronKCRGBCommand
{
KEYCHRON_RGB_PROTOCOL_VER = 0x01,
KEYCHRON_RGB_SAVE = 0x02,
KEYCHRON_RGB_GET_INDICATORS = 0x03,
KEYCHRON_RGB_SET_INDICATORS = 0x04,
KEYCHRON_RGB_LED_COUNT = 0x05,
KEYCHRON_RGB_LED_IDX = 0x06,
KEYCHRON_RGB_PER_KEY_GET_TYPE = 0x07,
KEYCHRON_RGB_PER_KEY_SET_TYPE = 0x08,
KEYCHRON_RGB_PER_KEY_GET_COLOR = 0x09,
KEYCHRON_RGB_PER_KEY_SET_COLOR = 0x0A,
KEYCHRON_RGB_MIXED_GET_INFO = 0x0B,
KEYCHRON_RGB_MIXED_GET_REGIONS = 0x0C,
KEYCHRON_RGB_MIXED_SET_REGIONS = 0x0D,
KEYCHRON_RGB_MIXED_GET_EFFECTS = 0x0E,
KEYCHRON_RGB_MIXED_SET_EFFECTS = 0x0F,
};
enum
{
KC_FEATURE_DEFAULT_LAYER = ( 1 << 0 ),
KC_FEATURE_BLUETOOTH = ( 1 << 1 ),
KC_FEATURE_P24G = ( 1 << 2 ),
KC_FEATURE_ANALOG_MATRIX = ( 1 << 3 ),
KC_FEATURE_STATE_NOTIFY = ( 1 << 4 ),
KC_FEATURE_DYNAMIC_DEBOUNCE = ( 1 << 5 ),
KC_FEATURE_SNAP_CLICK = ( 1 << 6 ),
KC_FEATURE_KEYCHRON_RGB = ( 1 << 7 ),
KC_FEATURE_QUICK_START = ( 1 << 8 ),
KC_FEATURE_NKRO = ( 1 << 9 ),
};
/*---------------------------------------------------------*\
| 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 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 char *path);
~QMKKeychronController();
std::string GetLocation();
std::string GetName();
std::string GetSerial();
std::string GetVendor();
std::string GetVersion();
bool GetSupported();
unsigned short GetKeycode(unsigned short led_index);
unsigned short GetLEDCount();
qmk_rgb_matrix_led_info GetLEDInfo(unsigned short led_index);
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 SaveMode();
private:
hid_device* dev;
std::string kc_firmware_version;
unsigned char kc_protocol_version;
unsigned short kc_rgb_protocol_version;
std::vector<unsigned short> keycodes;
std::vector<qmk_rgb_matrix_led_info> led_info;
std::string location;
std::string name;
unsigned short number_leds;
std::string serial;
unsigned short supported_features;
std::string vendor;
unsigned short via_protocol_version;
unsigned short CmdGetKeycode(unsigned char layer, unsigned char row, unsigned char col);
std::string CmdGetKeychronFirmwareVersion();
void CmdGetKeychronProtocolVersion(unsigned char* kc_protocol_version);
void CmdGetKeychronRGBProtocolVersion(unsigned short* kc_rgb_protocol_version);
std::vector<unsigned char> CmdGetLEDIndexByRow(unsigned char row);
void CmdGetNumberLEDs(unsigned short* number_leds);
void CmdGetSupportFeature(unsigned short* supported_features);
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
(
unsigned char cmd,
unsigned char* data_in,
unsigned char data_in_size,
unsigned char* data_out,
unsigned char data_out_size
);
int ViaSendCommandSub
(
unsigned char cmd,
unsigned char subcmd,
unsigned char* data_in,
unsigned char data_in_size,
unsigned char* data_out,
unsigned char data_out_size
);
};
@@ -1,279 +0,0 @@
/*---------------------------------------------------------*\
| QMKKeychronControllerDetect.cpp |
| |
| Detector for Keychron QMK-based keyboards |
| |
| Amadej Kastelic 21 Jun 2026 |
| Adam Honse <calcprogrammer1@gmail.com> 22 Jun 2026 |
| |
| 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"
void DetectQMKKeychronControllers(hid_device_info *info, const std::string&)
{
hid_device *dev = hid_open_path(info->path);
if(dev)
{
QMKKeychronController* controller = new QMKKeychronController(dev, info->path);
if(controller->GetSupported())
{
RGBController_QMKKeychron* rgb_controller = new RGBController_QMKKeychron(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else
{
delete controller;
}
}
}
REGISTER_HID_DETECTOR_IPU("Keychron C1 Pro", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C1_PRO_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C1 Pro 8K", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C1_PRO_8K_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C1 Pro 8K", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C1_PRO_8K_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C1 Pro 8K", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C1_PRO_8K_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C1 Pro V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C1_PRO_V2_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C2 Pro", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C2_PRO_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C2 Pro 8K", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C2_PRO_8K_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C2 Pro 8K", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C2_PRO_8K_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C2 Pro V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C2_PRO_V2_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C3 Pro", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C3_PRO_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C3 Pro 8K", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C3_PRO_8K_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C3 Pro 8K", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C3_PRO_8K_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron C3 Pro 8K", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_C3_PRO_8K_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K0 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K0_MAX_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K1_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K1_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K1_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K1 V6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K1_V6_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K1 V6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K1_V6_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K1 V6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K1_V6_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_HE_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_HE_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_HE_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_V3_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_V3_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K2 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K2_V3_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K3 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K3_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K3 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K3_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K3 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K3_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K3 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K3_V3_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K3 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K3_V3_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K3 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K3_V3_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_HE_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_HE_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_HE_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_V3_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_V3_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K4 V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K4_V3_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K5_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K5_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K5_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K5_MAX_JIS_V2_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K5 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K5_V2_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K5 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K5_V2_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K5 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K5_V2_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K6 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K6_HE_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K7 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K7_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K7 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K7_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K7 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K7_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K7 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K7_MAX_JIS_V2_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_HE_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_HE_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_HE_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 Pro", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_PRO_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 Pro", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_PRO_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 Pro", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_PRO_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_V2_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_V2_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K8 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K8_V2_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K9 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K9_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K10 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K10_HE_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K10 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K10_HE_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K10 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K10_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K10 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K10_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K10 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K10_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K10 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K10_V2_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K10 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K10_V2_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K10 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K10_V2_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K11 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K11_MAX_ANSI_ENCODER_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K11 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K11_MAX_ISO_ENCODER_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K11 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K11_MAX_JIS_ENCODER_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K13 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K13_MAX_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K13 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K13_MAX_ISO_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K13 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K13_MAX_JIS_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K15 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K15_MAX_ANSI_ENCODER_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K15 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K15_MAX_ISO_ENCODER_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K17 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K17_MAX_ANSI_ENCODER_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K17 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K17_MAX_ISO_ENCODER_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron K17 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_K17_MAX_JIS_ENCODER_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q0", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q0_BASE_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q0 Plus", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q0_PLUS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q0 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q0_MAX_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_HE_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_HE_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_HE_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_MAX_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V1_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V1_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V1_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V1_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V2_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V2_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V2_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V2_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V2_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q1 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q1_V2_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_HE_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q2 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q2_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_HE_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_HE_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_HE_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q3 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q3_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q4", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q4_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q4", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q4_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q4 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q4_HE_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_HE_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_HE_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_HE_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q5_MAX_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_HE_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_HE_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_HE_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q6 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q6_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q7", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q7_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q7", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q7_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q8", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q8_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q8", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q8_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q8", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q8_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q8", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q8_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q8 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q8_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q9", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q9_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q9", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q9_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q9", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q9_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q9", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q9_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q9 Plus", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q9_PLUS_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q10", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q10_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q10", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q10_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q10 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q10_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q10 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q10_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q11", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q11_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q11", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q11_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q12", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q12_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q12", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q12_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q12 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q12_HE_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q12 HE", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q12_HE_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q12 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q12_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q12 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q12_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q13 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q13_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q13 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q13_MAX_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q14 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q14_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q15 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q15_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q60 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q60_MAX_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron Q65 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_Q65_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron S1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_S1_ANSI_RGB_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_ABNT2_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_8K_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V1 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V1_MAX_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_ABNT2_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V2 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V2_MAX_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_ABNT2_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_JIS_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V3 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V3_MAX_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V4", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V4_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V4", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V4_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V4 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V4_MAX_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V4 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V4_MAX_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V5", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V5_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V5", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V5_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V5", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V5_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V5", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V5_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V5_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V5_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V5 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V5_MAX_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_ABNT2_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_MAX_JIS_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V6 V2", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V6_V2_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V7", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V7_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V7", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V7_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V8", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V8_ANSI_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V8", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V8_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V8", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V8_ISO_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V8", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V8_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V8 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V8_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V8 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V8_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V10", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V10_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V10", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V10_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V10 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V10_MAX_ANSI_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
REGISTER_HID_DETECTOR_IPU("Keychron V10 Max", DetectQMKKeychronControllers, KEYCHRON_VID, KEYCHRON_V10_MAX_ISO_ENCODER_PID, 1, KEYCHRON_QMK_USAGE_PAGE, KEYCHRON_QMK_USAGE);
@@ -1,239 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_QMKKeychron.cpp |
| |
| RGBController for Keychron QMK-based keyboards |
| |
| Amadej Kastelic 21 Jun 2026 |
| Adam Honse <calcprogrammer1@gmail.com> 22 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#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++)
{
qmk_rgb_matrix_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++)
{
qmk_rgb_matrix_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();
}
@@ -1,36 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_QMKKeychron.h |
| |
| RGBController for Keychron QMK-based keyboards |
| |
| Amadej Kastelic 21 Jun 2026 |
| Adam Honse <calcprogrammer1@gmail.com> 22 Jun 2026 |
| |
| 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();
void DeviceSaveMode();
private:
QMKKeychronController* controller;
};
@@ -1,71 +0,0 @@
/*---------------------------------------------------------*\
| QMKViaCommands.h |
| |
| List of QMK VIA command values |
| |
| Adam Honse <calcprogrammer1@gmail.com) 21 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
enum
{
QMK_VIA_CMD_GET_PROTOCOL_VERSION = 0x01,
QMK_VIA_CMD_GET_KEYBOARD_VALUE = 0x02,
QMK_VIA_CMD_SET_KEYBOARD_VALUE = 0x03,
QMK_VIA_CMD_VIA_DYNAMIC_KEYMAP_GET_KEYCODE = 0x04,
QMK_VIA_CMD_VIA_DYNAMIC_KEYMAP_SET_KEYCODE = 0x05,
QMK_VIA_CMD_VIA_DYNAMIC_KEYMAP_RESET = 0x06,
QMK_VIA_CMD_CUSTOM_SET_VALUE = 0x07,
QMK_VIA_CMD_CUSTOM_GET_VALUE = 0x08,
QMK_VIA_CMD_CUSTOM_SAVE = 0x09,
QMK_VIA_CMD_EEPROM_RESET = 0x0A,
};
enum
{
QMK_VIA_CUSTOM_CHANNEL = 0,
QMK_VIA_BACKLIGHT_CHANNEL = 1,
QMK_VIA_RGBLIGHT_CHANNEL = 2,
QMK_VIA_RGB_MATRIX_CHANNEL = 3,
QMK_VIA_AUDIO_CHANNEL = 4,
QMK_VIA_LED_MATRIX_CHANNEL = 5,
};
enum
{
QMK_VIA_BACKLIGHT_BRIGHTNESS = 1,
QMK_VIA_BACKLIGHT_EFFECT = 2,
};
enum
{
QMK_VIA_RGBLIGHT_BRIGHTNESS = 1,
QMK_VIA_RGBLIGHT_EFFECT = 2,
QMK_VIA_RGBLIGHT_EFFECT_SPEED = 3,
QMK_VIA_RGBLIGHT_COLOR = 4,
};
enum
{
QMK_VIA_RGB_MATRIX_BRIGHTNESS = 1,
QMK_VIA_RGB_MATRIX_EFFECT = 2,
QMK_VIA_RGB_MATRIX_EFFECT_SPEED = 3,
QMK_VIA_RGB_MATRIX_COLOR = 4,
};
enum
{
QMK_VIA_LED_MATRIX_BRIGHTNESS = 1,
QMK_VIA_LED_MATRIX_EFFECT = 2,
QMK_VIA_LED_MATRIX_EFFECT_SPEED = 3,
};
enum
{
QMK_VIA_AUDIO_ENABLE = 1,
QMK_VIA_AUDIO_CLICKY_ENABLE = 2,
};
@@ -186,7 +186,7 @@ unsigned short QMKVialRGBController::GetLEDCount()
return(number_leds);
}
qmk_rgb_matrix_led_info QMKVialRGBController::GetLEDInfo(unsigned short led_index)
vialrgb_led_info QMKVialRGBController::GetLEDInfo(unsigned short led_index)
{
return(led_info[led_index]);
}
@@ -257,12 +257,12 @@ unsigned short QMKVialRGBController::CmdGetKeycode
return(keycode);
}
qmk_rgb_matrix_led_info QMKVialRGBController::CmdGetLEDInfo
vialrgb_led_info QMKVialRGBController::CmdGetLEDInfo
(
unsigned short led_index
)
{
qmk_rgb_matrix_led_info data;
vialrgb_led_info data;
SendCheckCommand(CMD_LIGHTING_GET_VALUE, VIALRGB_GET_LED_INFO, (unsigned char*)&led_index, sizeof(led_index), (unsigned char*)&data, sizeof(data));
@@ -13,7 +13,6 @@
#include <hidapi.h>
#include "ResourceManager.h"
#include "QMKCommon.h"
#include "RGBController.h"
#define MSG_LEN 32
@@ -99,25 +98,34 @@ enum
VIALRGB_EFFECT_SKIP = 0xFFFF
};
typedef struct
{
unsigned char x;
unsigned char y;
unsigned char flags;
unsigned char row;
unsigned char col;
} vialrgb_led_info;
class QMKVialRGBController
{
public:
QMKVialRGBController(hid_device *dev_handle, const char *path);
~QMKVialRGBController();
std::string GetLocation();
std::string GetName();
std::string GetSerial();
std::string GetVendor();
std::string GetVersion();
std::string GetLocation();
std::string GetName();
std::string GetSerial();
std::string GetVendor();
std::string GetVersion();
bool GetSupported();
bool GetSupported();
unsigned short GetEffect(std::size_t effect_idx);
std::size_t GetEffectCount();
unsigned short GetKeycode(unsigned short led_index);
unsigned short GetLEDCount();
qmk_rgb_matrix_led_info GetLEDInfo(unsigned short led_index);
unsigned short GetEffect(std::size_t effect_idx);
std::size_t GetEffectCount();
unsigned short GetKeycode(unsigned short led_index);
unsigned short GetLEDCount();
vialrgb_led_info GetLEDInfo(unsigned short led_index);
void GetMode
(
@@ -144,22 +152,22 @@ public:
);
private:
hid_device* dev;
unsigned long long keyboard_uid;
std::vector<unsigned short> keycodes;
std::vector<qmk_rgb_matrix_led_info> led_info;
std::string location;
unsigned char maximum_brightness;
std::string name;
unsigned short number_leds;
std::string serial;
bool supported;
std::vector<unsigned short> supported_effects;
std::string vendor;
unsigned short via_protocol_version;
unsigned int vial_protocol_version;
unsigned short vialrgb_protocol_version;
unsigned char vialrgb_flag;
hid_device* dev;
unsigned long long keyboard_uid;
std::vector<unsigned short> keycodes;
std::vector<vialrgb_led_info> led_info;
std::string location;
unsigned char maximum_brightness;
std::string name;
unsigned short number_leds;
std::string serial;
bool supported;
std::vector<unsigned short> supported_effects;
std::string vendor;
unsigned short via_protocol_version;
unsigned int vial_protocol_version;
unsigned short vialrgb_protocol_version;
unsigned char vialrgb_flag;
unsigned short CmdGetKeycode
(
@@ -168,7 +176,7 @@ private:
unsigned char col
);
qmk_rgb_matrix_led_info CmdGetLEDInfo
vialrgb_led_info CmdGetLEDInfo
(
unsigned short led_index
);
@@ -171,7 +171,7 @@ void RGBController_QMKVialRGB::SetupZones()
for(unsigned short led_index = 0; led_index < controller->GetLEDCount(); led_index++)
{
qmk_rgb_matrix_led_info info = controller->GetLEDInfo(led_index);
vialrgb_led_info info = controller->GetLEDInfo(led_index);
if(info.col > max_col)
{
@@ -193,7 +193,7 @@ void RGBController_QMKVialRGB::SetupZones()
for(unsigned short led_index = 0; led_index < controller->GetLEDCount(); led_index++)
{
qmk_rgb_matrix_led_info info = controller->GetLEDInfo(led_index);
vialrgb_led_info info = controller->GetLEDInfo(led_index);
matrix_map[(width * info.row) + info.col] = (unsigned int)led_index;
}
@@ -175,8 +175,7 @@ void RGBController_Razer::SetupZones()
\*---------------------------------------------------------*/
if(new_zone.type == ZONE_TYPE_MATRIX)
{
if(device_list[device_index]->layout != NULL &&
(new_zone.name == ZONE_EN_KEYBOARD || new_zone.name == "Keypad"))
if(new_zone.name == ZONE_EN_KEYBOARD && device_list[device_index]->layout != NULL)
{
/*---------------------------------------------------------*\
| Dynamically generate a keyboard layout |
@@ -213,8 +212,7 @@ void RGBController_Razer::SetupZones()
new_map->width = device_list[device_index]->zones[zone_id]->cols;
new_map->map = new unsigned int[new_map->height * new_map->width];
if(device_list[device_index]->layout->base_size != KEYBOARD_SIZE::KEYBOARD_SIZE_EMPTY ||
!device_list[device_index]->layout->edit_keys.empty())
if(device_list[device_index]->layout->base_size != KEYBOARD_SIZE::KEYBOARD_SIZE_EMPTY)
{
/*---------------------------------------------------------*\
| Minor adjustments to keyboard layout |
@@ -443,8 +443,6 @@ bool RazerController::SupportsWave()
case RAZER_BLACKWIDOW_V4_X_PID:
case RAZER_BLACKWIDOW_V4_TKL_WIRED_PID:
case RAZER_BLACKWIDOW_V4_TKL_WIRELESS_PID:
case RAZER_BLACKWIDOW_V4_LOWPROFILE_TKL_WIRED_PID:
case RAZER_BLACKWIDOW_V4_LOWPROFILE_TKL_WIRELESS_PID:
case RAZER_BLACKWIDOW_X_CHROMA_PID:
case RAZER_BLACKWIDOW_X_CHROMA_TE_PID:
case RAZER_BLADE_2016_PID:
@@ -237,16 +237,13 @@ REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V3 TKL", Det
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V3 Mini (Wired)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V3_MINI_WIRED_PID, 0x03, 0x0C, 0x01);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V3 Mini (Wireless)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V3_MINI_WIRELESS_PID, 0x03, 0x0C, 0x01);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_PID, 0x03, 0x01, 0x00);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 Pro", /* firmware < 1.5 */ DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_PRO_PID, 0x03, 0x01, 0x00);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 Pro", /* firmware >= 1.5 */ DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_PRO_PID, 0x03, 0x0C, 0x01);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 Pro", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_PRO_PID, 0x03, 0x01, 0x00);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 Pro 75% (Wired)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_PRO_75_WIRED_PID, 0x03, 0x0C, 0x01);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 Pro 75% (Wireless)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_PRO_75_WIRELESS_PID, 0x02, 0x01, 0x02);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 75% (Wired)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_75_WIRED_PID, 0x03, 0x01, 0x00);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 X", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_X_PID, 0x02, 0x01, 0x02);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 TKL (Wired)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_TKL_WIRED_PID, 0x03, 0x0C, 0x01);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 TKL (Wireless)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_TKL_WIRELESS_PID, 0x02, 0x01, 0x02);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 Low Profile TKL (Wired)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_LOWPROFILE_TKL_WIRED_PID, 0x03, 0x0C, 0x01);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow V4 Low Profile TKL (Wireless)", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_V4_LOWPROFILE_TKL_WIRELESS_PID, 0x02, 0x01, 0x02);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow X Chroma", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_X_CHROMA_PID, 0x02, 0x01, 0x02);
REGISTER_HID_DETECTOR_IPU("Razer Blackwidow X Chroma Tournament Edition", DetectRazerControllers, RAZER_VID, RAZER_BLACKWIDOW_X_CHROMA_TE_PID, 0x02, 0x01, 0x02);
REGISTER_HID_DETECTOR_IPU("Razer Cynosa Chroma", DetectRazerControllers, RAZER_VID, RAZER_CYNOSA_CHROMA_PID, 0x02, 0x01, 0x02);
+2 -98
View File
@@ -77,30 +77,6 @@ keyboard_keymap_overlay_values razer_blackwidow_2019_layout
}
};
keyboard_keymap_overlay_values razer_blackwidow_x_chroma_layout
{
KEYBOARD_SIZE::KEYBOARD_SIZE_FULL,
{
{ /* ANSI Value set not used */ },
{
/* Add more regional layout fixes here */
}
},
{
/*-------------------------------------------------------------------------------------------------------------------------------------*\
| Edit Keys |
| Zone, Row, Column, Value, Name, Alternate Name, OpCode |
\*-------------------------------------------------------------------------------------------------------------------------------------*/
{ 0, 0, 0, 0, KEY_EN_UNUSED, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Move 'Esc' 1 right (Shifts row)
{ 0, 1, 0, 0, KEY_EN_UNUSED, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Move Backtick 1 right (Shifts row)
{ 0, 2, 0, 0, KEY_EN_UNUSED, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Move Tab 1 right (Shifts row)
{ 0, 3, 0, 0, KEY_EN_UNUSED, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Move Caps 1 right (Shifts row)
{ 0, 4, 0, 0, KEY_EN_UNUSED, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Move LFT_SHFT 1 right (Shifts row)
{ 0, 5, 0, 0, KEY_EN_UNUSED, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Move LFT_CTRL 1 right (Shifts row)
{ 0, 0, 20, 0, "Logo", KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Insert 'Logo' key
}
};
keyboard_keymap_overlay_values razer_blackwidow_chroma_layout
{
KEYBOARD_SIZE::KEYBOARD_SIZE_FULL,
@@ -2080,76 +2056,6 @@ static const razer_device blackwidow_v4_tkl_wireless_device =
&razer_blackwidow_v4_tkl_layout
};
/*-------------------------------------------------------------*\
| Razer Blackwidow V4 Low Profile TKL (Wired) 1532:02D4 |
| |
| Zone "Keyboard" |
| Matrix |
| 6 Rows, 18 Columns |
\*-------------------------------------------------------------*/
static const razer_zone blackwidow_v4_lowprofile_tkl_wired_zone =
{
ZONE_EN_KEYBOARD,
ZONE_TYPE_MATRIX,
6,
18
};
static const razer_device blackwidow_v4_lowprofile_tkl_wired_device =
{
"Razer Blackwidow V4 Low Profile TKL (Wired)",
RAZER_BLACKWIDOW_V4_LOWPROFILE_TKL_WIRED_PID,
DEVICE_TYPE_KEYBOARD,
RAZER_MATRIX_TYPE_EXTENDED,
0x1F,
6,
18,
{
&blackwidow_v4_tkl_wired_zone,
NULL,
NULL,
NULL,
NULL,
NULL
},
&razer_blackwidow_v4_tkl_layout //same layout as v4 (just low profile)
};
/*-------------------------------------------------------------*\
| Razer Blackwidow V4 Low Profile TKL (Wireless) 1532:02D2 |
| |
| Zone "Keyboard" |
| Matrix |
| 6 Rows, 18 Columns |
\*-------------------------------------------------------------*/
static const razer_zone blackwidow_v4_lowprofile_tkl_wireless_zone =
{
ZONE_EN_KEYBOARD,
ZONE_TYPE_MATRIX,
6,
18
};
static const razer_device blackwidow_v4_lowprofile_tkl_wireless_device =
{
"Razer Blackwidow V4 Low Profile TKL (Wireless)",
RAZER_BLACKWIDOW_V4_LOWPROFILE_TKL_WIRELESS_PID,
DEVICE_TYPE_KEYBOARD,
RAZER_MATRIX_TYPE_EXTENDED,
0x9F,
6,
18,
{
&blackwidow_v4_lowprofile_tkl_wireless_zone,
NULL,
NULL,
NULL,
NULL,
NULL
},
&razer_blackwidow_v4_tkl_layout //same layout as v4 (just low profile)
};
/*-------------------------------------------------------------*\
| Razer Blackwidow X Chroma 1532:0216 |
| |
@@ -2182,7 +2088,7 @@ static const razer_device blackwidow_x_chroma_device =
NULL,
NULL
},
&razer_blackwidow_x_chroma_layout
NULL
};
/*-------------------------------------------------------------*\
@@ -7523,7 +7429,7 @@ static const razer_device tartarus_pro_device =
};
/*-------------------------------------------------------------*\
| Razer Tartarus V2 1532:022B |
| Razer Tartarus V2 1532:0208 |
| |
| Zone "Keypad" |
| Matrix |
@@ -9423,8 +9329,6 @@ const razer_device* razer_device_list[] =
&blackwidow_v4_x_device,
&blackwidow_v4_tkl_wired_device,
&blackwidow_v4_tkl_wireless_device,
&blackwidow_v4_lowprofile_tkl_wired_device,
&blackwidow_v4_lowprofile_tkl_wireless_device,
&blackwidow_x_chroma_device,
&blackwidow_x_chroma_te_device,
&cynosa_chroma_device,
@@ -59,8 +59,6 @@
#define RAZER_BLACKWIDOW_V4_75_WIRED_PID 0x02A5
#define RAZER_BLACKWIDOW_V4_TKL_WIRED_PID 0x02D7
#define RAZER_BLACKWIDOW_V4_TKL_WIRELESS_PID 0x02D5
#define RAZER_BLACKWIDOW_V4_LOWPROFILE_TKL_WIRED_PID 0x02D4
#define RAZER_BLACKWIDOW_V4_LOWPROFILE_TKL_WIRELESS_PID 0x02D2
#define RAZER_BLACKWIDOW_X_CHROMA_PID 0x0216
#define RAZER_BLACKWIDOW_X_CHROMA_TE_PID 0x021A
#define RAZER_BLADE_2016_PID 0x020F
@@ -1,523 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_RealtekARGB.cpp |
| |
| RGBController for Realtek USB ARGB ICs |
| |
| Jerry Fan (JerryFan0612) 13 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_RealtekARGB.h"
/**------------------------------------------------------------------*\
@name Realtek ARGB Device
@category LEDStrip
@type USB
@save :x:
@direct :rotating_light:
@effects :white_check_mark:
@detectors RealtekARGBControllerDetect
@comment
\*-------------------------------------------------------------------*/
RGBController_RealtekARGB::RGBController_RealtekARGB(RealtekARGBController* controller_ptr)
{
controller = controller_ptr;
name = controller_ptr->get_dev_name();
vendor = controller_ptr->get_manu_name();
location = controller_ptr->get_dev_loc();
serial = controller_ptr->get_sn();
version = controller_ptr->get_fw_ver();
description = vendor + "ARGB Device";
type = DEVICE_TYPE_LEDSTRIP;
std::fill(std::begin(ready_to_reboot), std::end(ready_to_reboot), false);
SetupModes();
SetupZones();
}
void RGBController_RealtekARGB::SetupModes()
{
int brightness = controller->get_argb_brightness(0) >> 8;
mode Direct;
Direct.name = "Direct";
Direct.value = REALTEK_ARGB_EFF_NULL;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Direct.color_mode = MODE_COLORS_PER_LED;
Direct.brightness_min = 0;
Direct.brightness_max = 255;
Direct.brightness = brightness;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = REALTEK_ARGB_EFF_ALWAYS_ON;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Static.colors_min = 1;
Static.colors_max = 1;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.colors.resize(1);
Static.brightness_min = 0;
Static.brightness_max = 255;
Static.brightness = brightness;
modes.push_back(Static);
mode Blink;
Blink.name = "Blink";
Blink.value = REALTEK_ARGB_EFF_BLINK;
Blink.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Blink.speed_min = REALTEK_ARGB_SPEED_MIN;
Blink.speed_max = REALTEK_ARGB_SPEED_MAX;
Blink.speed = REALTEK_ARGB_SPEED_NORMAL;
Blink.colors_min = 1;
Blink.colors_max = 2;
Blink.color_mode = MODE_COLORS_MODE_SPECIFIC;
Blink.colors.resize(2);
Blink.brightness_min = 0;
Blink.brightness_max = 255;
Blink.brightness = brightness;
modes.push_back(Blink);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = REALTEK_ARGB_EFF_BREATH;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Breathing.speed_min = REALTEK_ARGB_SPEED_MIN;
Breathing.speed_max = REALTEK_ARGB_SPEED_MAX;
Breathing.speed = REALTEK_ARGB_SPEED_NORMAL;
Breathing.colors_min = 1;
Breathing.colors_max = 2;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.colors.resize(2);
Breathing.brightness_min = 0;
Breathing.brightness_max = 255;
Breathing.brightness = brightness;
modes.push_back(Breathing);
mode Spectrum;
Spectrum.name = "Spectrum";
Spectrum.value = REALTEK_ARGB_EFF_SPECTRUM;
Spectrum.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Spectrum.speed_min = REALTEK_ARGB_SPEED_MIN;
Spectrum.speed_max = REALTEK_ARGB_SPEED_MAX;
Spectrum.speed = REALTEK_ARGB_SPEED_NORMAL;
Spectrum.color_mode = MODE_COLORS_NONE;
Spectrum.brightness_min = 0;
Spectrum.brightness_max = 255;
Spectrum.brightness = brightness;
modes.push_back(Spectrum);
mode Scroll;
Scroll.name = "Scroll";
Scroll.value = REALTEK_ARGB_EFF_SCROLL;
Scroll.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Scroll.speed_min = REALTEK_ARGB_SPEED_MIN;
Scroll.speed_max = REALTEK_ARGB_SPEED_MAX;
Scroll.speed = REALTEK_ARGB_SPEED_NORMAL;
Scroll.colors_min = 1;
Scroll.colors_max = 2;
Scroll.color_mode = MODE_COLORS_MODE_SPECIFIC;
Scroll.colors.resize(2);
Scroll.brightness_min = 0;
Scroll.brightness_max = 255;
Scroll.brightness = brightness;
modes.push_back(Scroll);
mode RainbowScroll;
RainbowScroll.name = "Rainbow Scroll";
RainbowScroll.value = REALTEK_ARGB_EFF_RAINBOW_SCROLL;
RainbowScroll.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
RainbowScroll.speed_min = REALTEK_ARGB_SPEED_MIN;
RainbowScroll.speed_max = REALTEK_ARGB_SPEED_MAX;
RainbowScroll.speed = REALTEK_ARGB_SPEED_NORMAL;
RainbowScroll.color_mode = MODE_COLORS_NONE;
RainbowScroll.brightness_min = 0;
RainbowScroll.brightness_max = 255;
RainbowScroll.brightness = brightness;
modes.push_back(RainbowScroll);
mode RunningWater;
RunningWater.name = "Running Water";
RunningWater.value = REALTEK_ARGB_EFF_RUNNING_WATER;
RunningWater.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
RunningWater.speed_min = REALTEK_ARGB_SPEED_MIN;
RunningWater.speed_max = REALTEK_ARGB_SPEED_MAX;
RunningWater.speed = REALTEK_ARGB_SPEED_NORMAL;
RunningWater.colors_min = 1;
RunningWater.colors_max = 2;
RunningWater.color_mode = MODE_COLORS_MODE_SPECIFIC;
RunningWater.colors.resize(2);
RunningWater.brightness_min = 0;
RunningWater.brightness_max = 255;
RunningWater.brightness = brightness;
modes.push_back(RunningWater);
mode Sliding;
Sliding.name = "Sliding";
Sliding.value = REALTEK_ARGB_EFF_SLIDING;
Sliding.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR;
Sliding.speed_min = REALTEK_ARGB_SPEED_MIN;
Sliding.speed_max = REALTEK_ARGB_SPEED_MAX;
Sliding.speed = REALTEK_ARGB_SPEED_NORMAL;
Sliding.colors_min = 1;
Sliding.colors_max = 2;
Sliding.color_mode = MODE_COLORS_MODE_SPECIFIC;
Sliding.colors.resize(2);
Sliding.brightness_min = 0;
Sliding.brightness_max = 255;
Sliding.brightness = brightness;
modes.push_back(Sliding);
mode WideSliding;
WideSliding.name = "Wide Sliding";
WideSliding.value = REALTEK_ARGB_EFF_WIDE_SLIDING;
WideSliding.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR;
WideSliding.speed_min = REALTEK_ARGB_SPEED_MIN;
WideSliding.speed_max = REALTEK_ARGB_SPEED_MAX;
WideSliding.speed = REALTEK_ARGB_SPEED_NORMAL;
WideSliding.colors_min = 1;
WideSliding.colors_max = 2;
WideSliding.color_mode = MODE_COLORS_MODE_SPECIFIC;
WideSliding.colors.resize(2);
WideSliding.brightness_min = 0;
WideSliding.brightness_max = 255;
WideSliding.brightness = brightness;
modes.push_back(WideSliding);
mode RainbowSliding;
RainbowSliding.name = "Rainbow Sliding";
RainbowSliding.value = REALTEK_ARGB_EFF_RAINBOW_SLIDING;
RainbowSliding.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR;
RainbowSliding.speed_min = REALTEK_ARGB_SPEED_MIN;
RainbowSliding.speed_max = REALTEK_ARGB_SPEED_MAX;
RainbowSliding.speed = REALTEK_ARGB_SPEED_NORMAL;
RainbowSliding.color_mode = MODE_COLORS_NONE;
RainbowSliding.brightness_min = 0;
RainbowSliding.brightness_max = 255;
RainbowSliding.brightness = brightness;
modes.push_back(RainbowSliding);
mode RainbowFadeSliding;
RainbowFadeSliding.name = "Rainbow Fade Sliding";
RainbowFadeSliding.value = REALTEK_ARGB_EFF_RAINBOW_FADE_SLIDING;
RainbowFadeSliding.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
RainbowFadeSliding.speed_min = REALTEK_ARGB_SPEED_MIN;
RainbowFadeSliding.speed_max = REALTEK_ARGB_SPEED_MAX;
RainbowFadeSliding.speed = REALTEK_ARGB_SPEED_NORMAL;
RainbowFadeSliding.color_mode = MODE_COLORS_NONE;
RainbowFadeSliding.brightness_min = 0;
RainbowFadeSliding.brightness_max = 255;
RainbowFadeSliding.brightness = brightness;
modes.push_back(RainbowFadeSliding);
mode NewtonCradle;
NewtonCradle.name = "Newton Cradle";
NewtonCradle.value = REALTEK_ARGB_EFF_NEWTON_CRADLE;
NewtonCradle.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
NewtonCradle.speed_min = REALTEK_ARGB_SPEED_MIN;
NewtonCradle.speed_max = REALTEK_ARGB_SPEED_MAX;
NewtonCradle.speed = REALTEK_ARGB_SPEED_NORMAL;
NewtonCradle.colors_min = 1;
NewtonCradle.colors_max = 2;
NewtonCradle.color_mode = MODE_COLORS_MODE_SPECIFIC;
NewtonCradle.colors.resize(2);
NewtonCradle.brightness_min = 0;
NewtonCradle.brightness_max = 255;
NewtonCradle.brightness = brightness;
modes.push_back(NewtonCradle);
mode Meteor;
Meteor.name = "Meteor";
Meteor.value = REALTEK_ARGB_EFF_METEOR;
Meteor.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Meteor.speed_min = REALTEK_ARGB_SPEED_MIN;
Meteor.speed_max = REALTEK_ARGB_SPEED_MAX;
Meteor.speed = REALTEK_ARGB_SPEED_NORMAL;
Meteor.colors_min = 1;
Meteor.colors_max = 2;
Meteor.color_mode = MODE_COLORS_MODE_SPECIFIC;
Meteor.colors.resize(2);
Meteor.brightness_min = 0;
Meteor.brightness_max = 255;
Meteor.brightness = brightness;
modes.push_back(Meteor);
mode ZigZag;
ZigZag.name = "ZigZag";
ZigZag.value = REALTEK_ARGB_EFF_ZIGZAG;
ZigZag.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_RANDOM_COLOR;
ZigZag.speed_min = REALTEK_ARGB_SPEED_MIN;
ZigZag.speed_max = REALTEK_ARGB_SPEED_MAX;
ZigZag.speed = REALTEK_ARGB_SPEED_NORMAL;
ZigZag.colors_min = 1;
ZigZag.colors_max = 2;
ZigZag.color_mode = MODE_COLORS_MODE_SPECIFIC;
ZigZag.colors.resize(2);
ZigZag.brightness_min = 0;
ZigZag.brightness_max = 255;
ZigZag.brightness = brightness;
modes.push_back(ZigZag);
mode StarryNight;
StarryNight.name = "Starry Night";
StarryNight.value = REALTEK_ARGB_EFF_STARRY_NIGHT;
StarryNight.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_RANDOM_COLOR;
StarryNight.speed_min = REALTEK_ARGB_SPEED_MIN;
StarryNight.speed_max = REALTEK_ARGB_SPEED_MAX;
StarryNight.speed = REALTEK_ARGB_SPEED_NORMAL;
StarryNight.colors_min = 1;
StarryNight.colors_max = 1;
StarryNight.color_mode = MODE_COLORS_MODE_SPECIFIC;
StarryNight.colors.resize(1);
StarryNight.brightness_min = 0;
StarryNight.brightness_max = 255;
StarryNight.brightness = brightness;
modes.push_back(StarryNight);
mode Stack;
Stack.name = "Stack";
Stack.value = REALTEK_ARGB_EFF_STACK;
Stack.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Stack.colors_min = 1;
Stack.colors_max = 2;
Stack.color_mode = MODE_COLORS_MODE_SPECIFIC;
Stack.colors.resize(2);
Stack.brightness_min = 0;
Stack.brightness_max = 255;
Stack.brightness = brightness;
modes.push_back(Stack);
}
RGBController_RealtekARGB::~RGBController_RealtekARGB()
{
delete controller;
}
void RGBController_RealtekARGB::SetupZones()
{
/*-------------------------------------------------*\
| Only set LED count on the first run |
\*-------------------------------------------------*/
bool first_run = false;
int idx = 0;
int argb_num = 0;
int fix_grps = 0;
int num_fixgrp = 0;
int argb_num_fixgrp = 0;
if(zones.size() == 0)
{
first_run = true;
}
/*-------------------------------------------------*\
| Clear any existing color/LED configuration |
\*-------------------------------------------------*/
leds.clear();
colors.clear();
valid_grp.clear();
fix_grps = controller->get_fix_grps();
for(int grp_num = 0; grp_num < REALTEK_ARGB_NUM_ARGB_GRP; grp_num++)
{
if(controller->get_zone_enable(grp_num))
{
valid_grp.push_back(grp_num);
if(fix_grps & (0x1 << grp_num))
{
num_fixgrp++;
argb_num_fixgrp += controller->get_argb_num(grp_num);
}
}
}
/*-------------------------------------------------*\
| Set zones and leds |
\*-------------------------------------------------*/
zones.resize(valid_grp.size());
for(int grp_num : valid_grp)
{
argb_num = controller->get_argb_num(grp_num);
zones[idx].name = "strip " + std::to_string(idx + 1);
zones[idx].type = ZONE_TYPE_LINEAR;
if(fix_grps & (0x1 << grp_num))
{
zones[idx].leds_count = argb_num;
zones[idx].leds_min = argb_num;
zones[idx].leds_max = argb_num;
}
else
{
if(first_run)
{
zones[idx].leds_count = 0;
}
else
{
zones[idx].leds_count = argb_num;
}
zones[idx].leds_min = 0;
zones[idx].leds_max = (REALTEK_ARGB_MAX - argb_num_fixgrp) / ((int)valid_grp.size() - num_fixgrp);
}
zones[idx].matrix_map = NULL;
for(unsigned int led_idx = 0; led_idx < zones[idx].leds_count; led_idx++)
{
led myled;
myled.name = zones[idx].name + " led_";
myled.name.append(std::to_string(led_idx + 1));
leds.push_back(myled);
}
idx++;
}
SetupColors();
}
void RGBController_RealtekARGB::ResizeZone(int zone, int new_size)
{
int fix_grps = controller->get_fix_grps();
int orig_num = controller->get_argb_num(valid_grp[zone]);
bool need_reboot = true;
if((size_t) zone >= zones.size())
{
return;
}
if((new_size == orig_num) && zones[zone].leds_count)
{
return;
}
if(((unsigned int)new_size >= zones[zone].leds_min) && ((unsigned int)new_size <= zones[zone].leds_max))
{
int total_argb_num = 0;
std::vector<RGBColor> rtk_colors(orig_num, 0);
for(int grp_num = 0; grp_num < REALTEK_ARGB_NUM_ARGB_GRP; grp_num++)
{
if(grp_num == valid_grp[zone])
{
total_argb_num += new_size;
}
else
{
total_argb_num += controller->get_argb_num(grp_num);
}
}
if(total_argb_num > REALTEK_ARGB_MAX)
{
return;
}
controller->set_argb_direct(valid_grp[zone], rtk_colors, 0xFF);
controller->set_argb_num(valid_grp[zone], new_size);
ready_to_reboot[valid_grp[zone]] = true;
for(int grp_num = 0; grp_num < REALTEK_ARGB_NUM_ARGB_GRP; grp_num++)
{
if(!ready_to_reboot[grp_num] && !(fix_grps & (0x1 << grp_num)))
{
need_reboot = false;
break;
}
}
if(need_reboot)
{
controller->device_reboot();
controller->device_rescan_trigger();
}
SetupZones();
}
}
void RGBController_RealtekARGB::DeviceUpdateLEDs()
{
for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
if(zones[zone_idx].leds_count > 0)
{
UpdateZoneLEDs((int)zone_idx);
}
}
}
void RGBController_RealtekARGB::UpdateZoneLEDs(int zone)
{
unsigned short brightness = 0xFF;
mode& curr_mode = modes[active_mode];
std::vector<RGBColor> color_buf;
if(curr_mode.color_mode == MODE_COLORS_PER_LED &&
curr_mode.value == REALTEK_ARGB_EFF_NULL) //direct mode
{
if(curr_mode.flags & MODE_FLAG_HAS_BRIGHTNESS)
{
brightness = curr_mode.brightness;
}
color_buf.resize(zones[zone].leds_count);
for(unsigned int i = 0; i < zones[zone].leds_count; i++)
color_buf[i] = zones[zone].colors[i];
controller->set_argb_direct(valid_grp[zone], color_buf, brightness);
}
else
{
UpdateSingleLED(zone);
}
}
void RGBController_RealtekARGB::UpdateSingleLED(int zone)
{
mode& curr_mode = modes[active_mode];
std::vector<RGBColor> rtk_colors = curr_mode.colors;
struct RealtekARGBControllerSetEffParam param;
param.speed = REALTEK_ARGB_SPEED_NORMAL;
param.brightness = 0xFF;
param.dir = 0;
param.random_color = 0;
if(curr_mode.flags & MODE_FLAG_HAS_SPEED)
{
param.speed = curr_mode.speed;
}
if(curr_mode.flags & MODE_FLAG_HAS_BRIGHTNESS)
{
param.brightness = curr_mode.brightness;
}
if(curr_mode.flags & MODE_FLAG_HAS_DIRECTION_LR)
{
if(curr_mode.direction == MODE_DIRECTION_RIGHT)
{
param.dir = 1;
}
}
if(curr_mode.flags & MODE_FLAG_HAS_RANDOM_COLOR)
{
if(curr_mode.color_mode == MODE_COLORS_RANDOM)
{
param.random_color = 1;
}
}
if(curr_mode.color_mode == MODE_COLORS_PER_LED)
{
rtk_colors = colors;
}
else if(curr_mode.color_mode == MODE_COLORS_NONE)
{
rtk_colors.clear();
}
controller->set_argb_effect(valid_grp[zone], curr_mode.value, rtk_colors, &param);
}
void RGBController_RealtekARGB::DeviceUpdateMode()
{
if(modes[active_mode].value != REALTEK_ARGB_EFF_NULL)
{
DeviceUpdateLEDs();
}
}
@@ -1,36 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_RealtekARGB.h |
| |
| RGBController for Realtek USB ARGB ICs |
| |
| Jerry Fan (JerryFan0612) 13 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "RealtekARGBController.h"
class RGBController_RealtekARGB : public RGBController
{
public:
RGBController_RealtekARGB(RealtekARGBController* controller_ptr);
~RGBController_RealtekARGB();
void SetupModes();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int zone);
void DeviceUpdateMode();
private:
RealtekARGBController* controller;
std::vector<int> valid_grp;
bool ready_to_reboot[REALTEK_ARGB_NUM_ARGB_GRP] = {false};
};
@@ -1,715 +0,0 @@
/*---------------------------------------------------------*\
| RealtekARGBController.cpp |
| |
| Controller for Realtek USB ARGB ICs |
| |
| Jerry Fan (JerryFan0612) 13 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RealtekARGBController.h"
#include "MathUtils.h"
#include "StringUtils.h"
using namespace std::chrono_literals;
static const unsigned char hid_set_packet[] =
{
0x56, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xE3,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const unsigned char hid_get_packet[] =
{
0x56, 0x53, 0x42, 0x43, 0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x10, 0xE2,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const unsigned char hid_end_packet[] =
{
0x58, 0x53, 0x42, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
RealtekARGBController::RealtekARGBController(hid_device* dev, hid_device_info* info)
{
hdev = dev;
hidinfo = info;
for(int i = 0; i < REALTEK_ARGB_NUM_ARGB_GRP; i++)
{
argbctl_data[i] = (unsigned char*)calloc(REALTEK_ARGB_CTL_DATA_SIZE, 1);
memset(argbctl_data[i], 0, REALTEK_ARGB_CTL_DATA_SIZE);
prev_bright[i] = 0xFFFF;
}
device_init();
keepalive_thread_run = false;
keepalive_thread = NULL;
}
RealtekARGBController::~RealtekARGBController()
{
/*-----------------------------------------------------*\
| Close keepalive thread |
\*-----------------------------------------------------*/
if(keepalive_thread != NULL)
{
keepalive_thread_run = false;
keepalive_thread->join();
delete keepalive_thread;
}
if(hdev)
{
for(int i = 0; i < REALTEK_ARGB_NUM_ARGB_GRP; i++)
{
int ret = set_appctl(i, REALTEK_ARGB_LED_CTL_FW);
if(!ret)
{
appctl[i] = REALTEK_ARGB_LED_CTL_FW;
}
}
hid_close(hdev);
hdev = NULL;
}
memset(argbctl_hdr, 0, REALTEK_ARGB_CTL_HDR_SIZE);
for(int i = 0; i < REALTEK_ARGB_NUM_ARGB_GRP; i++)
free(argbctl_data[i]);
}
void RealtekARGBController::KeepaliveThreadFunction()
{
/*-----------------------------------------------------------------*\
| One shot thread to rescan device |
\*-----------------------------------------------------------------*/
while(keepalive_thread_run.load())
{
std::this_thread::sleep_for(2s);
device_rescan();
keepalive_thread_run = false;
}
}
int RealtekARGBController::usb_hid_ioctl(unsigned char* usb_buf, unsigned char* data, int data_len,
unsigned int offset, unsigned char is_in)
{
int id;
int ret = 0;
int buf_len = usb_hid_get_report(data_len, &id);
if(hdev == NULL)
{
free(usb_buf);
return -1;
}
if(!is_in && data_len)
{
usb_buf[0x04] = data[0];
}
memcpy(&usb_buf[0x08], &data_len, sizeof(data_len));
memcpy(&usb_buf[0x1B], &data_len, sizeof(data_len));
ret = hid_send_feature_report(hdev, usb_buf, sizeof(hid_get_packet));
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, data, data_len);
usb_buf[0x00] = id;
if(is_in)
{
ret = hid_get_feature_report(hdev, usb_buf, buf_len);
memcpy(data, usb_buf + offset, data_len);
}
else
{
ret = hid_send_feature_report(hdev, usb_buf, buf_len);
}
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_end_packet, sizeof(hid_end_packet));
ret = hid_get_feature_report(hdev, usb_buf, sizeof(hid_end_packet));
free(usb_buf);
return (ret > 0) ? 0 : ret;
}
int RealtekARGBController::usb_hid_get_report(int data_len, int* id)
{
int retlen = 0;
if(data_len <= REALTEK_ARGB_HID_DATALEN_CH2)
{
*id = REALTEK_ARGB_HID_ID_DATA_CH2;
retlen = REALTEK_ARGB_HID_DATALEN_CH2;
}
else if(data_len <= REALTEK_ARGB_HID_DATALEN_CH3)
{
*id = REALTEK_ARGB_HID_ID_DATA_CH3;
retlen = REALTEK_ARGB_HID_DATALEN_CH3;
}
else if(data_len <= REALTEK_ARGB_HID_DATALEN_CH4)
{
*id = REALTEK_ARGB_HID_ID_DATA_CH4;
retlen = REALTEK_ARGB_HID_DATALEN_CH4;
}
else if(data_len <= REALTEK_ARGB_HID_DATALEN_CH5)
{
*id = REALTEK_ARGB_HID_ID_DATA_CH5;
retlen = REALTEK_ARGB_HID_DATALEN_CH5;
}
else
{
*id = REALTEK_ARGB_HID_ID_DATA_CH1;
retlen = REALTEK_ARGB_HID_DATALEN_CH1;
}
return retlen;
}
void RealtekARGBController::device_init()
{
set_write_unlock();
get_argbctl_hdr();
get_argbctl_data();
for(int i = 0; i < REALTEK_ARGB_NUM_ARGB_GRP; i++)
{
int ret = set_appctl(i, REALTEK_ARGB_LED_CTL_FW);
if(!ret)
{
appctl[i] = REALTEK_ARGB_LED_CTL_FW;
}
}
}
int RealtekARGBController::set_write_unlock()
{
int ret = 0;
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
int data_len = 96;
unsigned int addr = 0xAC004000;
unsigned char* data = (unsigned char*)calloc(data_len, 1);
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_ioctl function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_get_packet, sizeof(hid_get_packet));
usb_buf[0x13] = 0x92;
memcpy(&usb_buf[0x17], &addr, sizeof(addr));
ret = usb_hid_ioctl(usb_buf, data, data_len, 0, true);
free(data);
return ret;
}
unsigned char RealtekARGBController::get_support_openrgb()
{
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned char is_support = 0;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_ioctl function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_get_packet, sizeof(hid_get_packet));
usb_buf[0x13] = 0xCC;
usb_buf[0x14] = 0x04;
usb_buf[0x15] = REALTEK_ARGB_SYNC_METHOD_OPENRGB;
if(usb_hid_ioctl(usb_buf, &is_support, sizeof(is_support), 0, true))
{
is_support = 0;
}
return is_support;
}
std::string RealtekARGBController::get_manu_name()
{
return StringUtils::wchar_to_char(hidinfo->manufacturer_string);
}
std::string RealtekARGBController::get_product_name()
{
return StringUtils::wchar_to_char(hidinfo->product_string);
}
std::string RealtekARGBController::get_sn()
{
return StringUtils::wchar_to_char(hidinfo->serial_number);
}
std::string RealtekARGBController::get_dev_loc()
{
return hidinfo->path;
}
std::string RealtekARGBController::get_fw_ver()
{
std::string ver = "";
struct RealtekARGBControllerFWVersion fw_ver;
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_ioctl function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_get_packet, sizeof(hid_get_packet));
usb_buf[0x13] = 0xA5;
if(!usb_hid_ioctl(usb_buf, (unsigned char*)&fw_ver, sizeof(fw_ver), 0, true))
{
ver += std::to_string(fw_ver.fw_major_ver) + "." +
std::to_string(fw_ver.fw_minor_ver) + "." +
std::to_string(fw_ver.fw_extra_ver) + "." +
std::to_string(fw_ver.fw_build_date);
}
return ver;
}
std::string RealtekARGBController::get_ic_uuid()
{
unsigned int uuid = 0;
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_ioctl function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_get_packet, sizeof(hid_get_packet));
usb_buf[0x13] = 0xC3;
usb_hid_ioctl(usb_buf, (unsigned char*)&uuid, sizeof(uuid), 0, true);
return StringUtils::u32int_to_hexString(uuid);
}
std::string RealtekARGBController::get_dev_name()
{
bool got_custled = false;
std::string devname = get_product_name();
if(custled[0])
{
got_custled = true;
}
else
{
if(!get_custled(custled, sizeof(custled)))
{
got_custled = true;
}
}
if(got_custled)
{
if(custled[6] == REALTEK_ARGB_CUST_DEVNAME_MANU_UUID)
{
devname = get_manu_name() + get_ic_uuid();
}
}
return devname;
}
int RealtekARGBController::get_fix_grps()
{
bool got_custled = false;
static int fix_grps = 0;
if(custled[0])
{
got_custled = true;
}
else
{
if(!get_custled(custled, sizeof(custled)))
{
got_custled = true;
}
}
if(got_custled)
{
fix_grps = custled[4];
}
return fix_grps;
}
bool RealtekARGBController::get_zone_enable(int grp_num)
{
bool got_custled = false;
bool en = false;
if(custled[0])
{
got_custled = true;
}
else
{
if(!get_custled(custled, sizeof(custled)))
{
got_custled = true;
}
}
if(got_custled)
{
en = (custled[5] & (0x1 << grp_num)) ? true : false;
}
return en;
}
int RealtekARGBController::get_argb_num(int grp_num)
{
int num_rgb = 0;
memcpy(&num_rgb, &argbctl_hdr[32 + grp_num * 2], 2);
return num_rgb;
}
int RealtekARGBController::get_argb_brightness(int grp_num)
{
int bright = 0;
memcpy(&bright, &argbctl_hdr[42 + grp_num * 2], 2);
return bright;
}
int RealtekARGBController::set_argb_brightness(int grp_num, unsigned short bright)
{
memcpy(&argbctl_hdr[42 + grp_num * sizeof(bright)], &bright, sizeof(bright));
set_argbctl_hdr();
return 0;
}
int RealtekARGBController::set_appctl(unsigned char grp_num, unsigned char ctl_sts)
{
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_ioctl function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_set_packet, sizeof(hid_set_packet));
usb_buf[0x13] = 0x4C;
usb_buf[0x14] = 0x01;
usb_buf[0x15] = ctl_sts;
usb_buf[0x16] = grp_num;
return usb_hid_ioctl(usb_buf, &ctl_sts, sizeof(ctl_sts), 0, false);
}
int RealtekARGBController::get_custled(unsigned char* cust, unsigned int cust_len)
{
int ret = 0;
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned int addr = 0xAC004000;
unsigned int offset = 0x800;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len + offset, 1);// will release in usb_hid_ioctl function
unsigned char* data = (unsigned char*)calloc(buf_len, 1);
memset(data, 0x00, buf_len);
memset(usb_buf, 0x00, buf_len + offset);
memcpy(usb_buf, hid_get_packet, sizeof(hid_get_packet));
usb_buf[0x13] = 0x92;
memcpy(&usb_buf[0x17], &addr, sizeof(addr));
ret = usb_hid_ioctl(usb_buf, data, buf_len, offset, true);
if(!ret)
{
memcpy(cust, &data[70], cust_len);
}
free(data);
return ret;
}
int RealtekARGBController::get_argbctl_hdr()
{
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_ioctl function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_get_packet, sizeof(hid_get_packet));
usb_buf[0x13] = 0xCC;
usb_buf[0x14] = 0x02;
return usb_hid_ioctl(usb_buf, argbctl_hdr, REALTEK_ARGB_CTL_HDR_SIZE, 0, true);
}
int RealtekARGBController::set_argbctl_hdr()
{
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_send/usb_hid_get function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_set_packet, sizeof(hid_set_packet));
usb_buf[0x13] = 0x4C;
usb_buf[0x14] = 0x02;
return usb_hid_ioctl(usb_buf, argbctl_hdr, REALTEK_ARGB_CTL_HDR_SIZE, 0, false);
}
int RealtekARGBController::get_argbctl_data()
{
int ret = 0;
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
int offset = REALTEK_ARGB_CTL_HDR_SIZE;
unsigned char* usb_buf;
for(int i = 0; i < REALTEK_ARGB_NUM_ARGB_GRP; i++)
{
usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_send/usb_hid_get function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_get_packet, sizeof(hid_get_packet));
usb_buf[0x13] = 0xCC;
usb_buf[0x14] = 0x02;
memcpy(&usb_buf[0x17], &offset, sizeof(offset));
ret = usb_hid_ioctl(usb_buf, argbctl_data[i], REALTEK_ARGB_CTL_DATA_SIZE, 0, true);
offset += REALTEK_ARGB_CTL_DATA_SIZE;
}
return ret;
}
int RealtekARGBController::set_argbctl_data(unsigned char grp_num)
{
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
int offset = REALTEK_ARGB_CTL_HDR_SIZE + REALTEK_ARGB_CTL_DATA_SIZE * grp_num;
unsigned char* usb_buf;
usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_send/usb_hid_get function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_set_packet, sizeof(hid_set_packet));
usb_buf[0x13] = 0x4C;
usb_buf[0x14] = 0x02;
memcpy(&usb_buf[0x17], &offset, sizeof(offset));
return usb_hid_ioctl(usb_buf, argbctl_data[grp_num], REALTEK_ARGB_CTL_DATA_SIZE, 0, false);
}
int RealtekARGBController::set_eff_id(unsigned char grp_num, unsigned short effid)
{
memcpy(argbctl_data[grp_num], &effid, sizeof(effid));
return 0;
}
int RealtekARGBController::set_p_color(unsigned char grp_num, RGBColor color)
{
memcpy(argbctl_data[grp_num] + 8, &color, sizeof(color));
return 0;
}
int RealtekARGBController::set_s_color(unsigned char grp_num, RGBColor color)
{
memcpy(argbctl_data[grp_num] + 12, &color, sizeof(color));
return 0;
}
int RealtekARGBController::set_cycle(unsigned char grp_num, unsigned short cycle)
{
memcpy(argbctl_data[grp_num] + 2, &cycle, sizeof(cycle));
return 0;
}
int RealtekARGBController::set_ramp(unsigned char grp_num, unsigned short ramp)
{
memcpy(argbctl_data[grp_num] + 4, &ramp, sizeof(ramp));
return 0;
}
int RealtekARGBController::set_stable(unsigned char grp_num, unsigned short stable)
{
memcpy(argbctl_data[grp_num] + 6, &stable, sizeof(stable));
return 0;
}
int RealtekARGBController::set_subcmd(unsigned char grp_num, int subcmd)
{
memcpy(argbctl_data[grp_num] + 16, &subcmd, sizeof(subcmd));
return 0;
}
int RealtekARGBController::set_direct(unsigned char* color, int color_num, unsigned char grp_num)
{
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
int data_len = color_num * REALTEK_ARGB_COLOR_DEPTH;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_ioctl function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_set_packet, sizeof(hid_set_packet));
usb_buf[0x13] = 0x4C;
usb_buf[0x14] = 0x03;
usb_buf[0x15] = REALTEK_ARGB_SYNC_METHOD_OPENRGB;
usb_buf[0x16] = grp_num;
memcpy(&usb_buf[0x17], &color_num, sizeof(color_num));
return usb_hid_ioctl(usb_buf, color, data_len, 0, false);
}
int RealtekARGBController::get_flash_argbctl_hdr(unsigned char* data)
{
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned int addr = 0xAC000000;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_send/usb_hid_get function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_get_packet, sizeof(hid_get_packet));
usb_buf[0x13] = 0x92;
memcpy(&usb_buf[0x17], &addr, sizeof(addr));
return usb_hid_ioctl(usb_buf, data, buf_len, 0, true);
}
int RealtekARGBController::set_flash_argbctl_hdr(unsigned int offset, unsigned int data_len)
{
int ret = 0;
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
unsigned int addr = 0xAC000000;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_send/usb_hid_get function
unsigned char* data = (unsigned char*)calloc(buf_len, 1);
memset(data, 0, buf_len);
ret = get_flash_argbctl_hdr(data);
if(!ret)
{
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_set_packet, sizeof(hid_set_packet));
usb_buf[0x13] = 0x10;
memcpy(&usb_buf[0x17], &addr, sizeof(addr));
ret = usb_hid_ioctl(usb_buf, (unsigned char*)&addr, sizeof(addr), 0, false);
usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_send/usb_hid_get function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_set_packet, sizeof(hid_set_packet));
usb_buf[0x13] = 0x11;
memcpy(data + offset, argbctl_hdr + offset, data_len);
ret = usb_hid_ioctl(usb_buf, data, buf_len, 0, false);
}
free(data);
return ret;
}
int RealtekARGBController::set_argb_direct(int grp_num, std::vector<RGBColor> color_buf, unsigned short brightness)
{
int ret = -1;
size_t color_num = color_buf.size();
size_t buf_len = color_num * REALTEK_ARGB_COLOR_DEPTH;
unsigned char* buf;
std::lock_guard<std::mutex> lock(my_mutex);
if(color_num == 0)
{
goto exit;
}
if(appctl[grp_num] != REALTEK_ARGB_LED_CTL_APP)
{
ret = set_appctl(grp_num, REALTEK_ARGB_LED_CTL_APP);
if(ret)
{
goto exit;
}
else
{
appctl[grp_num] = REALTEK_ARGB_LED_CTL_APP;
}
}
if(prev_bright[grp_num] != brightness)
{
prev_bright[grp_num] = brightness;
ret = set_argb_brightness(grp_num, brightness << 8);
if(ret)
{
goto exit;
}
}
buf = (uint8_t*)malloc(buf_len);
memset(buf, 0, buf_len);
for(size_t i = 0; i < color_num; i++)
{
buf[i * REALTEK_ARGB_COLOR_DEPTH + 0] = RGBGetRValue(color_buf[i]);
buf[i * REALTEK_ARGB_COLOR_DEPTH + 1] = RGBGetGValue(color_buf[i]);
buf[i * REALTEK_ARGB_COLOR_DEPTH + 2] = RGBGetBValue(color_buf[i]);
}
ret = set_direct(buf, (int)color_num, grp_num);
free(buf);
exit:
return ret;
}
int RealtekARGBController::set_argb_effect(int grp_num, uint8_t mode, std::vector<RGBColor> color_buf, struct RealtekARGBControllerSetEffParam* param)
{
int ret = -1;
int cycle = MathUtils::IntInterpolate(REALTEK_ARGB_CYCLE_MAX, REALTEK_ARGB_CYCLE_MIN, 0, REALTEK_ARGB_SPEED_MAX, param->speed);
std::lock_guard<std::mutex> lock(my_mutex);
if(mode == REALTEK_ARGB_EFF_ALWAYS_ON || mode == REALTEK_ARGB_EFF_STACK)
{
cycle = 0;
}
if(color_buf.size() >= 1)
{
set_p_color(grp_num, color_buf[0]);
}
if(color_buf.size() >= 2)
{
set_s_color(grp_num, color_buf[1]);
}
set_eff_id(grp_num, mode);
set_cycle(grp_num, cycle);
set_ramp(grp_num, 0);
set_stable(grp_num, 0);
set_subcmd(grp_num, (param->dir || param->random_color) ? 0x1 : 0x0);
set_argb_brightness(grp_num, param->brightness << 8);
set_argbctl_data(grp_num);
ret = set_appctl(grp_num, REALTEK_ARGB_LED_CTL_FW);
if(!ret)
{
appctl[grp_num] = REALTEK_ARGB_LED_CTL_FW;
}
return ret;
}
int RealtekARGBController::set_argb_num(int grp_num, unsigned short new_num)
{
memcpy(&argbctl_hdr[32 + grp_num * sizeof(new_num)], &new_num, sizeof(new_num));
set_flash_argbctl_hdr(32 + grp_num * sizeof(new_num), sizeof(new_num));
return 0;
}
int RealtekARGBController::device_reboot()
{
std::lock_guard<std::mutex> lock(my_mutex);
int ret = 0;
int buf_len = REALTEK_ARGB_HID_DATALEN_CH1;
int reboot_type = 1;
unsigned char* usb_buf = (unsigned char*)calloc(buf_len, 1);// will release in usb_hid_send/usb_hid_get function
memset(usb_buf, 0x00, buf_len);
memcpy(usb_buf, hid_set_packet, sizeof(hid_set_packet));
usb_buf[0x13] = 0x30;
usb_buf[0x14] = reboot_type;
ret = usb_hid_ioctl(usb_buf, (unsigned char*)&reboot_type, sizeof(reboot_type), 0, false);
hid_close(hdev);
hdev = NULL;
return ret;
}
void RealtekARGBController::device_rescan_trigger()
{
keepalive_thread_run = true;
keepalive_thread = new std::thread(&RealtekARGBController::KeepaliveThreadFunction, this);
}
void RealtekARGBController::device_rescan()
{
hid_device_info* info_full = hid_enumerate(REALTEK_ARGB_VID, REALTEK_ARGB_PID);
hid_device_info* info_temp = info_full;
while(info_temp)
{
if(info_temp->vendor_id == REALTEK_ARGB_VID &&
info_temp->product_id == REALTEK_ARGB_PID &&
info_temp->usage == REALTEK_ARGB_HID2SCSI_USAGE &&
info_temp->usage_page == REALTEK_ARGB_HID2SCSI_PG)
{
hid_device* dev_temp = hid_open_path(info_temp->path);
if(dev_temp)
{
hdev = dev_temp;
if(get_support_openrgb())
{
device_init();
break;
}
else
{
hid_close(hdev);
hdev = NULL;
}
}
}
info_temp = info_temp->next;
}
hid_free_enumeration(info_full);
}
@@ -1,182 +0,0 @@
/*---------------------------------------------------------*\
| RealtekARGBController.h |
| |
| Controller for Realtek USB ARGB ICs |
| |
| Jerry Fan (JerryFan0612) 13 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <cmath>
#include <cstring>
#include <vector>
#include <thread>
#include <hidapi.h>
#include "RGBController.h"
#define REALTEK_ARGB_VID 0x0BDA
#define REALTEK_ARGB_PID 0x9209
#define REALTEK_ARGB_HID2SCSI_PG 0xFF00
#define REALTEK_ARGB_HID2SCSI_USAGE 0x0001
#define REALTEK_ARGB_NUM_ARGB_GRP 5
#define REALTEK_ARGB_BRINTF_TYPE_HID 1
#define REALTEK_ARGB_SYNC_METHOD_OPENRGB 6
#define REALTEK_ARGB_COLOR_DEPTH 3
#define REALTEK_ARGB_MAX 400
#define REALTEK_ARGB_CTL_HDR_SIZE 80
#define REALTEK_ARGB_CTL_DATA_SIZE 96
#define REALTEK_ARGB_HID_DATALEN_CH1 4096
#define REALTEK_ARGB_HID_DATALEN_CH2 64
#define REALTEK_ARGB_HID_DATALEN_CH3 512
#define REALTEK_ARGB_HID_DATALEN_CH4 1200
#define REALTEK_ARGB_HID_DATALEN_CH5 2048
#define REALTEK_ARGB_HID_ID_DATA_CH1 0x57
#define REALTEK_ARGB_HID_ID_DATA_CH2 0x59
#define REALTEK_ARGB_HID_ID_DATA_CH3 0x5A
#define REALTEK_ARGB_HID_ID_DATA_CH4 0x5B
#define REALTEK_ARGB_HID_ID_DATA_CH5 0x5C
enum REALTEK_ARGB_LED_CTL
{
REALTEK_ARGB_LED_CTL_FW,
REALTEK_ARGB_LED_CTL_APP,
REALTEK_ARGB_LED_CTL_LAMP,
};
enum REALTEK_ARGB_EFFECT_ID
{
REALTEK_ARGB_EFF_NULL,
REALTEK_ARGB_EFF_ALWAYS_ON,
REALTEK_ARGB_EFF_BLINK,
REALTEK_ARGB_EFF_BREATH,
REALTEK_ARGB_EFF_SPECTRUM,
REALTEK_ARGB_EFF_SCROLL,
REALTEK_ARGB_EFF_RAINBOW_SCROLL,
REALTEK_ARGB_EFF_RUNNING_WATER,
REALTEK_ARGB_EFF_SLIDING,
REALTEK_ARGB_EFF_NEWTON_CRADLE,
REALTEK_ARGB_EFF_METEOR,
REALTEK_ARGB_EFF_RAINBOW_SLIDING,
REALTEK_ARGB_EFF_RAINBOW_FADE_SLIDING,
REALTEK_ARGB_EFF_WIDE_SLIDING,
REALTEK_ARGB_EFF_DOT_MATRIX, // reserved: Not supported by OpenRGB
REALTEK_ARGB_EFF_DOT_MATRIX_BREATH, // reserved: Not supported by OpenRGB
REALTEK_ARGB_EFF_ZIGZAG,
REALTEK_ARGB_EFF_STARRY_NIGHT,
REALTEK_ARGB_EFF_STACK,
};
enum REALTEK_ARGB_SPEED
{
REALTEK_ARGB_SPEED_MIN = 1,
REALTEK_ARGB_SPEED_NORMAL = 50,
REALTEK_ARGB_SPEED_MAX = 100,
};
enum REALTEK_ARGB_CYCLE_MS
{
REALTEK_ARGB_CYCLE_MIN = 200,
REALTEK_ARGB_CYCLE_NORMAL = 2000,
REALTEK_ARGB_CYCLE_MAX = 10000,
};
enum REALTEK_ARGB_CUST_DEVNAME
{
REALTEK_ARGB_CUST_DEVNAME_NULL = 0x0,
REALTEK_ARGB_CUST_DEVNAME_MANU_UUID = 0x1,
};
struct RealtekARGBControllerSetEffParam
{
unsigned int speed;
unsigned short brightness;
unsigned char dir;
unsigned char random_color;
};
struct RealtekARGBControllerFWVersion
{
unsigned int fw_major_ver;
unsigned int fw_minor_ver;
unsigned int fw_extra_ver;
unsigned int fw_build_ver;
unsigned int fw_build_date;
};
class RealtekARGBController
{
public:
RealtekARGBController(hid_device* dev, hid_device_info* info);
~RealtekARGBController();
unsigned char get_support_openrgb();
std::string get_manu_name();
std::string get_product_name();
std::string get_sn();
std::string get_dev_loc();
std::string get_fw_ver();
std::string get_ic_uuid();
std::string get_dev_name();
int get_fix_grps();
bool get_zone_enable(int grp_num);
int get_argb_num(int grp_num);
int get_argb_brightness(int grp_num);
int set_argb_brightness(int grp_num, unsigned short bright);
int set_argb_direct(int grp_num, std::vector<RGBColor> color_buf, unsigned short brightness);
int set_argb_effect(int grp_num, uint8_t mode, std::vector<RGBColor> color_buf, struct RealtekARGBControllerSetEffParam* param);
int set_argb_num(int grp_num, unsigned short new_num);
int device_reboot();
void device_rescan_trigger();
private:
hid_device* hdev;
std::mutex my_mutex;
unsigned char argbctl_hdr[REALTEK_ARGB_CTL_HDR_SIZE] = {0};
unsigned char* argbctl_data[REALTEK_ARGB_NUM_ARGB_GRP] = {0};
hid_device_info* hidinfo = NULL;
int appctl[REALTEK_ARGB_NUM_ARGB_GRP] = {REALTEK_ARGB_LED_CTL_FW};
unsigned short prev_bright[REALTEK_ARGB_NUM_ARGB_GRP] = {0};
std::atomic<bool> keepalive_thread_run;
std::thread* keepalive_thread;
unsigned char custled[16] = {0};
void KeepaliveThreadFunction();
int usb_hid_ioctl(unsigned char* usb_buf, unsigned char* data, int data_len,
unsigned int offset, unsigned char is_in);
int usb_hid_get_report(int data_len, int* id);
void device_init();
int set_write_unlock();
int set_appctl(unsigned char grp_num, unsigned char ctl_sts);
int set_argbctl_data(unsigned char* data, int data_len, int offset);
int get_custled(unsigned char* cust, unsigned int cust_len);
int get_argbctl_hdr();
int set_argbctl_hdr();
int get_argbctl_data();
int set_argbctl_data(unsigned char grp_num);
int set_eff_id(unsigned char grp_num, unsigned short effid);
int set_p_color(unsigned char grp_num, RGBColor color);
int set_s_color(unsigned char grp_num, RGBColor color);
int set_cycle(unsigned char grp_num, unsigned short cycle);
int set_ramp(unsigned char grp_num, unsigned short ramp);
int set_stable(unsigned char grp_num, unsigned short stable);
int set_subcmd(unsigned char grp_num, int subcmd);
int set_direct(unsigned char* color, int color_num, unsigned char grp_num);
int get_flash_argbctl_hdr(unsigned char* data);
int set_flash_argbctl_hdr(unsigned int offset, unsigned int data_len);
void device_rescan();
};
@@ -1,52 +0,0 @@
/*---------------------------------------------------------*\
| RealtekARGBControllerDetect.cpp |
| |
| Detector for Realtek USB ARGB ICs |
| |
| Jerry Fan (JerryFan0612) 13 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "Detector.h"
#include "RGBController.h"
#include "RGBController_RealtekARGB.h"
/******************************************************************************************\
* *
* DetectRealtekARGBControllers *
* *
* Tests the USB address to see if an Realtek ARGB controller exists there *
* *
\******************************************************************************************/
void DetectRealtekARGBControllers(hid_device_info* info, const std::string& /*name*/)
{
RealtekARGBController* controller = NULL;
RGBController_RealtekARGB* rgb_controller = NULL;
hid_device* dev = hid_open_path(info->path);
if(dev)
{
controller = new RealtekARGBController(dev, info);
if(controller->get_support_openrgb())
{
rgb_controller = new RGBController_RealtekARGB(controller);
if(rgb_controller->type != DEVICE_TYPE_UNKNOWN)
{
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else
{
delete rgb_controller;
}
}
else
{
delete controller;
}
}
return;
}
REGISTER_HID_DETECTOR_PU("RTL9209", DetectRealtekARGBControllers, REALTEK_ARGB_VID, REALTEK_ARGB_PID, REALTEK_ARGB_HID2SCSI_PG, REALTEK_ARGB_HID2SCSI_USAGE);
@@ -28,7 +28,6 @@
#define REDRAGON_M801_PID 0xFC58
#define REDRAGON_M810_PID 0xFA7E
#define REDRAGON_M987_PID 0xFC69
#define REDRAGON_M921_PID 0xFC40
/******************************************************************************************\
* *
@@ -63,4 +62,3 @@ REGISTER_HID_DETECTOR_IP("Redragon M808 Storm", DetectRedragonMice, RE
REGISTER_HID_DETECTOR_IP("Redragon M801 Sniper", DetectRedragonMice, REDRAGON_MOUSE_VID, REDRAGON_M801_PID, 2, REDRAGON_MOUSE_USAGE_PAGE);
REGISTER_HID_DETECTOR_IP("Redragon M810 Taipan", DetectRedragonMice, REDRAGON_MOUSE_VID, REDRAGON_M810_PID, 2, REDRAGON_MOUSE_USAGE_PAGE);
REGISTER_HID_DETECTOR_IP("Redragon M987 Reaping", DetectRedragonMice, REDRAGON_MOUSE_VID, REDRAGON_M987_PID, 2, REDRAGON_MOUSE_USAGE_PAGE);
REGISTER_HID_DETECTOR_IP("Redragon M921 Azzinoth", DetectRedragonMice, REDRAGON_MOUSE_VID, REDRAGON_M921_PID, 2, REDRAGON_MOUSE_USAGE_PAGE);
@@ -1,169 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_SayoDevice.cpp |
| |
| Controller for Sayo Devices |
| |
| Richard Harris 24 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_SayoDevice.h"
#include "SayoDeviceController.h"
#include "LogManager.h"
/**--------------------------------------------------------------------*\
@name SayoDevice E1
@category Keyboard
@type USB
@save :white_check_mark:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectSayoDeviceController
@comment
\*---------------------------------------------------------------------*/
RGBController_SayoDevice::RGBController_SayoDevice(SayoDeviceController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "SayoDevice";
description = "SayoDevice E1 Knob";
type = DEVICE_TYPE_KEYBOARD;
location = controller->GetDeviceLocation();
mode Direct;
Direct.name = "Direct";
Direct.value = SAYO_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
/*-----------------------------------------------------*\
| Breathing - pulses a single color |
\*-----------------------------------------------------*/
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = SAYO_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_RANDOM_COLOR;
Breathing.color_mode = MODE_COLORS_PER_LED;
Breathing.speed_min = 0;
Breathing.speed_max = 3;
Breathing.speed = 1;
modes.push_back(Breathing);
/*-----------------------------------------------------*\
| Wave - fades through multiple colors |
\*-----------------------------------------------------*/
mode Wave;
Wave.name = "Wave";
Wave.value = SAYO_MODE_WAVE;
Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_RANDOM_COLOR;
Wave.color_mode = MODE_COLORS_NONE;
Wave.speed_min = 0;
Wave.speed_max = 3;
Wave.speed = 1;
modes.push_back(Wave);
/*-----------------------------------------------------*\
| Switch - alternates multiple colors |
\*-----------------------------------------------------*/
mode Switch;
Switch.name = "Switch";
Switch.value = SAYO_MODE_SWITCH;
Switch.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_RANDOM_COLOR;
Switch.color_mode = MODE_COLORS_NONE;
Switch.speed_min = 0;
Switch.speed_max = 3;
Switch.speed = 1;
modes.push_back(Switch);
/*-----------------------------------------------------*\
| Blink - blinks on and off |
\*-----------------------------------------------------*/
mode Blink;
Blink.name = "Blink";
Blink.value = SAYO_MODE_BLINK;
Blink.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_RANDOM_COLOR;
Blink.color_mode = MODE_COLORS_PER_LED;
Blink.speed_min = 0;
Blink.speed_max = 3;
Blink.speed = 1;
modes.push_back(Blink);
SetupZones();
};
RGBController_SayoDevice::~RGBController_SayoDevice()
{
delete controller;
}
void RGBController_SayoDevice::SetupZones()
{
const int led_count = 1;
zone zone;
zone.name = "Underglow";
zone.type = ZONE_TYPE_SINGLE;
zone.leds_count = led_count;
zone.leds_min = led_count;
zone.leds_max = led_count;
zone.matrix_map = NULL;
zones.clear();
zones.push_back(zone);
leds.clear();
leds.resize(led_count);
for(int i = 0; i < led_count; i++)
{
led& new_led = leds[i];
new_led.name = "LED " + std::to_string(i + 1);
new_led.value = i;
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_SayoDevice::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*-----------------------------------------------------*\
| This device does not support resizing zones |
\*-----------------------------------------------------*/
}
void RGBController_SayoDevice::DeviceUpdateLEDs()
{
unsigned int hw_mode = (modes[active_mode].value == SAYO_MODE_DIRECT) ? SAYO_MODE_STATIC : modes[active_mode].value;
bool random = (modes[active_mode].color_mode == MODE_COLORS_RANDOM);
unsigned int speed = 3 - modes[active_mode].speed;
controller->SetMode(hw_mode, speed, colors[0], random);
}
void RGBController_SayoDevice::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_SayoDevice::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_SayoDevice::DeviceUpdateMode()
{
/*-----------------------------------------------------*\
| Mode and color are always set together. |
\*-----------------------------------------------------*/
DeviceUpdateLEDs();
}
void RGBController_SayoDevice::DeviceSaveMode()
{
controller->Save();
}
@@ -1,36 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_SayoDevice.h |
| |
| Controller for Sayo Devices |
| |
| Richard Harris 24 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "SayoDeviceController.h"
class RGBController_SayoDevice : public RGBController
{
public:
RGBController_SayoDevice(SayoDeviceController* controller_ptr);
~RGBController_SayoDevice();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void DeviceSaveMode();
private:
SayoDeviceController* controller;
};
@@ -1,128 +0,0 @@
/*---------------------------------------------------------*\
| SayoDeviceController.cpp |
| |
| Controller for Sayo Devices (USB HID) |
| |
| Richard Harris 24 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <vector>
#include "LogManager.h"
#include "SayoDeviceController.h"
#include "RGBController.h"
#include "Colors.h"
SayoDeviceController::SayoDeviceController(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
}
SayoDeviceController::~SayoDeviceController()
{
hid_close(dev);
}
std::string SayoDeviceController::GetDeviceLocation()
{
return location;
}
std::string SayoDeviceController::GetDeviceName()
{
return name;
}
void SayoDeviceController::SetMode(unsigned int mode, unsigned int speed, RGBColor color, bool random)
{
/*-----------------------------------------------------*\
| Loop color table creates a rainbow effect which looks |
| better than truly random colors, so use that. |
\*-----------------------------------------------------*/
unsigned int color_mode = random ? SAYO_COLOR_LOOP_TABLE : SAYO_COLOR_STATIC;
unsigned char mode_byte = SAYO_MODE_PACK(speed, color_mode, mode);
/*-----------------------------------------------------*\
| 0x1C (0x00) is potentially length (28) as the payload |
| is 27 bytes in size. Could include a zero as many |
| fields seem to be 2-byte. |
| Other fields have not been mapped out at this time. |
\*-----------------------------------------------------*/
std::vector<unsigned char> payload =
{
0x1C, 0x00, SAYO_CMD_LIGHTING_SET, 0x00, 0x01, 0x00, 0x00, 0x00,
0x15, 0x00, 0x28, 0x00, 0x26, 0x00, 0x4C, 0x00,
0x26, 0x00, 0x00, 0x00, mode_byte, 0x00, 0x80, 0x80,
(unsigned char)RGBGetRValue(color),
(unsigned char)RGBGetGValue(color),
(unsigned char)RGBGetBValue(color),
};
SendPacket(payload, true);
}
/*---------------------------------------------------------*\
| Persist LED settings to flash memory. |
\*---------------------------------------------------------*/
void SayoDeviceController::Save()
{
/*-----------------------------------------------------*\
| 0x06 0x00 feels like a length too - 6 bytes |
\*-----------------------------------------------------*/
std::vector<unsigned char> payload = { 0x06, 0x00, 0x0d, 0x00, 0x96, 0x72 };
SendPacket(payload, true);
}
void SayoDeviceController::SendPacket(const std::vector<unsigned char>& command, bool flush)
{
unsigned char length = (unsigned char)(command.size() + 4);
if(length > 64)
{
LOG_ERROR("[SayoDevice] SendPacket: command size (%d) is too large, rejecting", (int)command.size());
return;
}
std::vector<unsigned char> packet = {0x21, 0x12};
/*-----------------------------------------------------*\
| Checksum of header + command, as little endian 2-byte |
| pairs. Checksum is in same format. |
\*-----------------------------------------------------*/
unsigned short checksum = (unsigned short)(packet[0] | (packet[1] << 8));
for(std::size_t i = 0; i < command.size(); i += 2)
{
unsigned short word = command[i];
if(i + 1 < command.size())
{
word |= (unsigned short)(command[i + 1] << 8);
}
checksum = (checksum + word) & 0xFFFF;
}
packet.push_back(checksum & 0xFF);
packet.push_back(checksum >> 8);
packet.insert(packet.end(), command.begin(), command.end());
packet.resize(64, 0u);
hid_write(dev, packet.data(), packet.size());
if(flush)
{
/*-------------------------------------------------*\
| Flush away any awaiting IN packets |
\*-------------------------------------------------*/
unsigned char buf[64];
int res = 1;
while(res > 0)
{
res = hid_read_timeout(dev, buf, 64, 0);
}
}
}
@@ -1,103 +0,0 @@
/*---------------------------------------------------------*\
| SayoDeviceController.h |
| |
| Controller for Sayo Devices |
| |
| Richard Harris 24 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <hidapi.h>
#include "RGBController.h"
/*-----------------------------------------*\
| Lighting modes |
\*-----------------------------------------*/
enum
{
SAYO_MODE_STATIC = 0x00,
SAYO_MODE_INDICATOR = 0x01,
SAYO_MODE_BREATHING = 0x02,
SAYO_MODE_BREATHING_ONCE = 0x03,
SAYO_MODE_WAVE = 0x04,
SAYO_MODE_SWITCH = 0x06,
SAYO_MODE_SWITCH_ONCE = 0x07,
SAYO_MODE_BLINK = 0x08,
SAYO_MODE_BLINK_ONCE = 0x09,
SAYO_MODE_FADE_OUT = 0x0E,
SAYO_MODE_FADE_IN = 0x0F,
/*-------------------------------------*\
| Virtual modes |
\*-------------------------------------*/
SAYO_MODE_DIRECT = 0xFF,
};
/*-----------------------------------------*\
| Animation speeds |
\*-----------------------------------------*/
enum
{
SAYO_SPEED_1X = 3,
SAYO_SPEED_2X = 2,
SAYO_SPEED_4X = 1,
SAYO_SPEED_8X = 0,
};
/*-----------------------------------------*\
| Animation color mode |
| TABLE modes either loop through or pick |
| randomly from a palette of colors. |
| RANDOM is truly random. |
\*-----------------------------------------*/
enum
{
SAYO_COLOR_STATIC = 0,
SAYO_COLOR_LOOP_TABLE = 1,
SAYO_COLOR_RANDOM_TABLE = 2,
SAYO_COLOR_RANDOM = 3,
};
#define SAYO_MODE_PACK(speed, color_mode, mode) \
((unsigned char)((((speed) & 0x3) << 6) | \
(((color_mode) & 0x3) << 4) | \
((mode) & 0xF)))
/*-----------------------------------------*\
| Commands |
\*-----------------------------------------*/
enum
{
SAYO_CMD_API_LIST = 0x00,
SAYO_CMD_KEY_GET = 0x02, // maybe?
SAYO_CMD_SETTINGS = 0x03,
SAYO_CMD_KEY_SET = 0x10,
SAYO_CMD_LIGHTING_SET = 0x11,
SAYO_CMD_REBOOT = 0x0E,
};
class SayoDeviceController
{
public:
SayoDeviceController(hid_device* dev_handle, const char* path, std::string dev_name);
~SayoDeviceController();
std::string GetDeviceLocation();
std::string GetDeviceName();
void SetMode(unsigned int mode, unsigned int speed, RGBColor color, bool random);
void Save();
private:
hid_device* dev;
std::string name;
std::string location;
void SendPacket(const std::vector<unsigned char>& command, bool flush = true);
};
@@ -1,42 +0,0 @@
/*---------------------------------------------------------*\
| SayoDeviceControllerDetect.cpp |
| |
| Detector for Sayo Devices |
| |
| Richard Harris 24 Jun 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "Detector.h"
#include "SayoDeviceController.h"
#include "RGBController_SayoDevice.h"
#define SAYO_USB_VID 0x8089
#define SAYO_USB_PID_E1 0x0007
/*----------------------------------------------------------*\
| |
| DetectSayoDevice Controller |
| |
| Detect Sayo Devices |
| |
\*----------------------------------------------------------*/
void DetectSayoDeviceController
(
hid_device_info* info,
const std::string& name
)
{
hid_device* dev = hid_open_path(info->path);
if(dev != nullptr)
{
SayoDeviceController* controller = new SayoDeviceController(dev, info->path, name);
RGBController_SayoDevice *rgb_controller = new RGBController_SayoDevice(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
REGISTER_HID_DETECTOR_PU("SayoDevice E1", DetectSayoDeviceController, SAYO_USB_VID, SAYO_USB_PID_E1, 0xFF11, 0x0002);
@@ -120,13 +120,13 @@
#define STEELSERIES_APEX_PRO_TKL_2023_PID 0x1628
#define STEELSERIES_APEX_PRO_TKL_2023_WL_PID_1 0x1630
#define STEELSERIES_APEX_PRO_TKL_2023_WL_PID_2 0x1632
#define STEELSERIES_APEX_PRO_TKL_GEN3_PID 0x1642
#define STEELSERIES_APEX_PRO_TKL_GEN3_WL_PID_1 0x1644
#define STEELSERIES_APEX_PRO_TKL_GEN3_WL_PID_2 0x1646
#define STEELSERIES_APEX_M750_PID 0x0616
#define STEELSERIES_APEX_OG_PID 0x1202
#define STEELSERIES_APEX_350_PID 0x1206
#define STEELSERIES_APEX_PRO3_PID 0x1640
#define STEELSERIES_APEX_PRO_TKL_GEN3_PID 0x1642
#define STEELSERIES_APEX_PRO_TKL_GEN3_WL_PID_1 0x1644
#define STEELSERIES_APEX_PRO_TKL_GEN3_WL_PID_2 0x1646
void DetectSteelSeriesAerox3(hid_device_info* info, const std::string& name)
{
@@ -516,10 +516,10 @@ REGISTER_HID_DETECTOR_I ("SteelSeries Apex Pro TKL", Dete
REGISTER_HID_DETECTOR_I ("SteelSeries Apex Pro TKL 2023 Wired", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_2023_PID, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Apex Pro TKL 2023 Wireless", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_2023_WL_PID_1, 3, 0xFFC0, 1);
REGISTER_HID_DETECTOR_IPU("SteelSeries Apex Pro TKL 2023 Wireless", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_2023_WL_PID_2, 3, 0xFFC0, 1);
REGISTER_HID_DETECTOR_I ("SteelSeries Apex Pro TKL Gen 3 Wired", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_GEN3_PID, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Apex Pro TKL Gen 3 Wireless", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_GEN3_WL_PID_1, 3, 0xFFC0, 1);
REGISTER_HID_DETECTOR_IPU("SteelSeries Apex Pro TKL Gen 3 Wireless", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_GEN3_WL_PID_2, 3, 0xFFC0, 1);
REGISTER_HID_DETECTOR_I ("SteelSeries Apex M750", DetectSteelSeriesApexM, STEELSERIES_VID, STEELSERIES_APEX_M750_PID, 2 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex (OG)/Apex Fnatic", DetectSteelSeriesApexOld, STEELSERIES_VID, STEELSERIES_APEX_OG_PID, 0 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex 350", DetectSteelSeriesApexOld, STEELSERIES_VID, STEELSERIES_APEX_350_PID, 0 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex Pro 3", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO3_PID, 1 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex Pro TKL Gen 3", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_GEN3_PID, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Apex Pro TKL Gen 3 Wireless", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_GEN3_WL_PID_1, 3, 0xFFC0, 1);
REGISTER_HID_DETECTOR_IPU("SteelSeries Apex Pro TKL Gen 3 Wireless", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_GEN3_WL_PID_2, 3, 0xFFC0, 1);
@@ -10,8 +10,6 @@
\*---------------------------------------------------------*/
#include "RGBController_ZotacBlackwellGPU.h"
#include "LogManager.h"
#include "pci_ids.h"
/**------------------------------------------------------------------*\
@name ZOTAC RTX 50 series GPU
@@ -22,60 +20,21 @@
@effects :tools:
@detectors DetectZotacBlackwellGPUControllersPCI
@comment
Supports ZOTAC Blackwell (RTX 50 series) GPUs. The zone layout
varies per card and is resolved from a static table keyed on
PCI device and sub-device IDs.
Supports ZOTAC Blackwell (RTX 50 series) GPUs with 3 zones:
Logo, Side Bar, and Infinity Mirror.
The controller uses individual SMBus byte writes (registers
0x20-0x2F) with a 3ms delay between each transaction.
To add new cards, add PCI ID entries in `pci_ids/pci_ids.h`,
a zone config entry in `device_zone_configs` below, and a
`REGISTER_I2C_PCI_DETECTOR` line in
To add new cards, add PCI ID entries in `pci_ids/pci_ids.h`
and detection entries in
`Controllers/ZotacBlackwellGPUController/ZotacBlackwellGPUControllerDetect.cpp`.
\*-------------------------------------------------------------------*/
const RGBController_ZotacBlackwellGPU::DeviceZoneConfig RGBController_ZotacBlackwellGPU::device_zone_configs[] =
{
{ NVIDIA_RTX5080_DEV, ZOTAC_RTX5080_AMP_EXTREME_SUB_DEV, { "Logo", "Side Bar", "Infinity Mirror" }, 3 },
{ NVIDIA_RTX5090_DEV, ZOTAC_RTX5090_SOLID_OC_SUB_DEV, { "ZOTAC Gaming", "Logo" }, 2 },
{ 0, 0, { nullptr }, 0 }
};
const RGBController_ZotacBlackwellGPU::DeviceZoneConfig* RGBController_ZotacBlackwellGPU::FindZoneConfig(uint16_t device, uint16_t subdevice)
{
for(const DeviceZoneConfig* cfg = device_zone_configs; cfg->zone_count != 0; cfg++)
{
if(cfg->device == device && cfg->subdevice == subdevice)
{
return cfg;
}
}
return nullptr;
}
RGBController_ZotacBlackwellGPU::RGBController_ZotacBlackwellGPU(ZotacBlackwellGPUController* controller_ptr,
uint16_t device, uint16_t subdevice)
RGBController_ZotacBlackwellGPU::RGBController_ZotacBlackwellGPU(ZotacBlackwellGPUController* controller_ptr)
{
controller = controller_ptr;
const DeviceZoneConfig* cfg = FindZoneConfig(device, subdevice);
if(cfg == nullptr)
{
LOG_ERROR("[%s] Unrecognized PCI device/subdevice: %04X/%04X. Falling back to three generic zones.",
controller->GetName().c_str(), device, subdevice);
zone_names.push_back("Zone 0");
zone_names.push_back("Zone 1");
zone_names.push_back("Zone 2");
}
else
{
for(uint8_t z = 0; z < cfg->zone_count; z++)
{
zone_names.push_back(cfg->zones[z]);
}
}
name = controller->GetName();
vendor = "ZOTAC";
description = "ZOTAC RTX 50 series RGB GPU Device (" + controller->GetVersion() + ")";
@@ -293,25 +252,52 @@ RGBController_ZotacBlackwellGPU::~RGBController_ZotacBlackwellGPU()
void RGBController_ZotacBlackwellGPU::SetupZones()
{
/*---------------------------------------------------------*\
| One single-LED zone per name from the device_zone_configs |
| table. The zone's index is its position here, which is |
| written verbatim to the zone register (0x21) on update. |
| Zone 0: Logo |
\*---------------------------------------------------------*/
for(const std::string& zone_name : zone_names)
{
zone new_zone;
new_zone.name = zone_name;
new_zone.type = ZONE_TYPE_SINGLE;
new_zone.leds_min = 1;
new_zone.leds_max = 1;
new_zone.leds_count = 1;
new_zone.matrix_map = NULL;
zones.push_back(new_zone);
zone logo_zone;
logo_zone.name = "Logo";
logo_zone.type = ZONE_TYPE_SINGLE;
logo_zone.leds_min = 1;
logo_zone.leds_max = 1;
logo_zone.leds_count = 1;
logo_zone.matrix_map = NULL;
zones.push_back(logo_zone);
led new_led;
new_led.name = zone_name + " LED";
leds.push_back(new_led);
}
led logo_led;
logo_led.name = "Logo LED";
leds.push_back(logo_led);
/*---------------------------------------------------------*\
| Zone 1: Side Bar |
\*---------------------------------------------------------*/
zone sidebar_zone;
sidebar_zone.name = "Side Bar";
sidebar_zone.type = ZONE_TYPE_SINGLE;
sidebar_zone.leds_min = 1;
sidebar_zone.leds_max = 1;
sidebar_zone.leds_count = 1;
sidebar_zone.matrix_map = NULL;
zones.push_back(sidebar_zone);
led sidebar_led;
sidebar_led.name = "Side Bar LED";
leds.push_back(sidebar_led);
/*---------------------------------------------------------*\
| Zone 2: Infinity Mirror |
\*---------------------------------------------------------*/
zone infinity_zone;
infinity_zone.name = "Infinity Mirror";
infinity_zone.type = ZONE_TYPE_SINGLE;
infinity_zone.leds_min = 1;
infinity_zone.leds_max = 1;
infinity_zone.leds_count = 1;
infinity_zone.matrix_map = NULL;
zones.push_back(infinity_zone);
led infinity_led;
infinity_led.name = "Infinity Mirror LED";
leds.push_back(infinity_led);
SetupColors();
}
@@ -376,7 +362,7 @@ void RGBController_ZotacBlackwellGPU::DeviceUpdateZone(int zone)
void RGBController_ZotacBlackwellGPU::DeviceUpdateMode()
{
for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++)
for(unsigned int zone_idx = 0; zone_idx < ZOTAC_BLACKWELL_GPU_NUM_ZONES; zone_idx++)
{
DeviceUpdateZone(zone_idx);
}
@@ -11,17 +11,13 @@
#pragma once
#include <stdint.h>
#include <string>
#include <vector>
#include "RGBController.h"
#include "ZotacBlackwellGPUController.h"
class RGBController_ZotacBlackwellGPU : public RGBController
{
public:
RGBController_ZotacBlackwellGPU(ZotacBlackwellGPUController* controller_ptr,
uint16_t device, uint16_t subdevice);
RGBController_ZotacBlackwellGPU(ZotacBlackwellGPUController* controller_ptr);
~RGBController_ZotacBlackwellGPU();
void SetupZones();
@@ -37,17 +33,4 @@ public:
private:
ZotacBlackwellGPUController* controller;
std::vector<std::string> zone_names;
struct DeviceZoneConfig
{
uint16_t device;
uint16_t subdevice;
const char* zones[4];
uint8_t zone_count;
};
static const DeviceZoneConfig device_zone_configs[];
static const DeviceZoneConfig* FindZoneConfig(uint16_t device, uint16_t subdevice);
};
@@ -46,6 +46,14 @@
#define ZOTAC_BLACKWELL_GPU_REG_RELOAD 0x11
#define ZOTAC_BLACKWELL_GPU_REG_COMMIT 0x17
/*---------------------------------------------------------*\
| Zone indices |
\*---------------------------------------------------------*/
#define ZOTAC_BLACKWELL_GPU_ZONE_LOGO 0x00
#define ZOTAC_BLACKWELL_GPU_ZONE_SIDEBAR 0x01
#define ZOTAC_BLACKWELL_GPU_ZONE_INFINITY 0x02
#define ZOTAC_BLACKWELL_GPU_NUM_ZONES 3
/*---------------------------------------------------------*\
| Mode values (from Firestorm V5.0.0.012E reverse eng.) |
\*---------------------------------------------------------*/
@@ -20,13 +20,13 @@
* DetectZotacBlackwellGPUControllersPCI *
* *
* Detect ZOTAC Blackwell (RTX 50 series) RGB controllers on the enumerated *
* I2C busses at address 0x4B. Zone configuration is resolved inside the *
* RGBController by looking up the PCI device/sub-device IDs in a static table. *
* I2C busses at address 0x4B. *
* *
* bus - pointer to i2c_smbus_interface where RGB device is connected *
* dev - I2C address of RGB device *
* *
\******************************************************************************************/
void DetectZotacBlackwellGPUControllersPCI(i2c_smbus_interface* bus, u8 i2c_addr, const std::string& name)
{
s32 result = bus->i2c_smbus_read_byte_data(i2c_addr, 0x10);
@@ -34,13 +34,10 @@ void DetectZotacBlackwellGPUControllersPCI(i2c_smbus_interface* bus, u8 i2c_addr
if(result >= 0)
{
ZotacBlackwellGPUController* controller = new ZotacBlackwellGPUController(bus, i2c_addr, name);
RGBController_ZotacBlackwellGPU* rgb_controller = new RGBController_ZotacBlackwellGPU(controller,
bus->pci_device,
bus->pci_subsystem_device);
RGBController_ZotacBlackwellGPU* rgb_controller = new RGBController_ZotacBlackwellGPU(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
REGISTER_I2C_PCI_DETECTOR("ZOTAC GAMING GeForce RTX 5080 AMP Extreme INFINITY", DetectZotacBlackwellGPUControllersPCI, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ZOTAC_SUB_VEN, ZOTAC_RTX5080_AMP_EXTREME_SUB_DEV, 0x4B);
REGISTER_I2C_PCI_DETECTOR("ZOTAC GAMING GeForce RTX 5090 SOLID OC", DetectZotacBlackwellGPUControllersPCI, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ZOTAC_SUB_VEN, ZOTAC_RTX5090_SOLID_OC_SUB_DEV, 0x4B);
@@ -1012,20 +1012,8 @@ void KeyboardLayoutManager::GetKeyMap(unsigned int* map_ptr, KEYBOARD_MAP_FILL_T
void KeyboardLayoutManager::GetKeyMap(unsigned int* map_ptr, KEYBOARD_MAP_FILL_TYPE fill_type, uint8_t height = 0, uint8_t width = 0)
{
unsigned int no_key = -1;
/*-------------------------------------------------------------------------*\
| If explicit dimensions are passed (non-zero), use them as-is. |
| Only fall back to internal dimensions when zero is passed. |
| This ensures we don't write beyond the caller's allocated buffer. |
\*-------------------------------------------------------------------------*/
if(width == 0)
{
width = cols;
}
if(height == 0)
{
height = rows;
}
width = std::max(width, cols);
height = std::max(height, rows);
for(unsigned int r = 0; r < height; r++)
{
@@ -1039,14 +1027,6 @@ void KeyboardLayoutManager::GetKeyMap(unsigned int* map_ptr, KEYBOARD_MAP_FILL_T
for(unsigned int i = 0; i < (unsigned int)keymap.size(); i++)
{
/*---------------------------------------------------------------------*\
| Skip keys that fall outside the requested map dimensions |
\*---------------------------------------------------------------------*/
if(keymap[i].row >= height || keymap[i].col >= width)
{
continue;
}
unsigned int offset = (keymap[i].row * width) + keymap[i].col;
switch(fill_type)
{
+1 -5
View File
@@ -515,11 +515,7 @@ void NetworkClient::ListenThreadFunction()
/*---------------------------------------------------------*\
| Header received, now receive the data |
\*---------------------------------------------------------*/
if(header.pkt_size > OPENRGB_SDK_MAX_PACKET_SIZE)
{
goto listen_done;
}
else if(header.pkt_size > 0)
if(header.pkt_size > 0)
{
bytes_read = 0;
+1 -6
View File
@@ -27,12 +27,7 @@
/*-----------------------------------------------------*\
| Default Interface to bind to. |
\*-----------------------------------------------------*/
#define OPENRGB_SDK_HOST "127.0.0.1"
/*-----------------------------------------------------*\
| Default max packet size is 8MB |
\*-----------------------------------------------------*/
#define OPENRGB_SDK_MAX_PACKET_SIZE (1024 * 1024 * 8)
#define OPENRGB_SDK_HOST "0.0.0.0"
/*-----------------------------------------------------*\
| Default OpenRGB SDK port is 6742 |
+1 -6
View File
@@ -627,12 +627,7 @@ void NetworkServer::ListenThreadFunction(NetworkClientInfo * client_info)
| Header received, now receive the data |
\*---------------------------------------------------------*/
bytes_read = 0;
if(header.pkt_size > OPENRGB_SDK_MAX_PACKET_SIZE)
{
LOG_ERROR("[NetworkServer] received too large packet, closing listener");
goto listen_done;
}
else if(header.pkt_size > 0)
if(header.pkt_size > 0)
{
data = new char[header.pkt_size];
+2 -2
View File
@@ -26,7 +26,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#-----------------------------------------------------------------------------------------------#
MAJOR = 0
MINOR = 9
SUFFIX = 1.0rc3.1
SUFFIX = git
SHORTHASH = $$system("git rev-parse --short=7 HEAD")
LASTTAG = "release_"$$MAJOR"."$$MINOR
@@ -438,8 +438,8 @@ win32:contains(QMAKE_TARGET.arch, x86_64) {
copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/PawnIOLib.dll )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t)
copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusPIIX4.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t)
copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusI801.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t)
copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusIntelSkylakeIMC.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t)
copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusNCT6793.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t)
copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusIntelSkylakeIMC.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t)
copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/LpcIO.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t)
first.depends = $(first) copydata
export(first.depends)
+3 -3
View File
@@ -66,7 +66,7 @@ bool ProfileManager::SaveProfile(std::string profile_name, bool sizes)
/*---------------------------------------------------------*\
| Open an output file in binary mode |
\*---------------------------------------------------------*/
filesystem::path profile_path = configuration_directory / filesystem::u8path(StringUtils::make_filename(filename));
filesystem::path profile_path = configuration_directory / filesystem::u8path(filename);
std::ofstream controller_file(profile_path, std::ios::out | std::ios::binary | std::ios::trunc);
/*---------------------------------------------------------*\
@@ -149,7 +149,7 @@ std::vector<RGBController*> ProfileManager::LoadProfileToList
unsigned int controller_size;
unsigned int controller_offset = 0;
filesystem::path filename = configuration_directory / filesystem::u8path(StringUtils::make_filename(profile_name));
filesystem::path filename = configuration_directory / filesystem::u8path(profile_name);
/*---------------------------------------------------------*\
| Determine file extension |
@@ -437,7 +437,7 @@ void ProfileManager::DeleteProfile(std::string profile_name)
{
profile_name = StringUtils::remove_null_terminating_chars(profile_name);
filesystem::path filename = configuration_directory / StringUtils::make_filename(profile_name);
filesystem::path filename = configuration_directory / profile_name;
filename.concat(".orp");
filesystem::remove(filename);
+1 -2
View File
@@ -39,7 +39,7 @@ This project interacts directly with hardware using reverse engineered protocols
* Released versions are available to download on [OpenRGB.org](https://openrgb.org/releases.html) or under [Releases](https://gitlab.com/CalcProgrammer1/OpenRGB/-/releases/permalink/latest).
* Experimental (aka Pipeline) versions are available to download on [OpenRGB.org](https://openrgb.org/index.html#pl).
* On Windows, you will need the **Microsoft Visual 2019 C++ runtime** installed. You can get it [here](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-supported-redistributable-version).
* An official Flatpak release is [available on Flathub](https://flathub.org/apps/details/org.openrgb.OpenRGB)
* An unofficial Flatpak release is [available on Flathub](https://flathub.org/apps/details/org.openrgb.OpenRGB)
* Note: If using the AppImage or Flatpak versions, ensure you [install the latest udev rules](Documentation/UdevRules.md).
* Released versions are officially packaged for various distributions including Alpine, Fedora, and Arch.
* Arch users can also install from the [Extra repository](https://archlinux.org/packages/extra/x86_64/openrgb/) or from the AUR for the [pipeline](https://aur.archlinux.org/packages/openrgb-git/) version.
@@ -165,7 +165,6 @@ This project interacts directly with hardware using reverse engineered protocols
* VRMTool: https://github.com/rbrune/VRMtool
* g810-led: https://github.com/MatMoul/g810-led
* liquidctl: https://github.com/jonasmalacofilho/liquidctl
* kraken-unleashed: https://github.com/Cesarsk/kraken-unleashed
* Annemone: https://github.com/manualmanul/Annemone
* libcmmk: https://github.com/chmod222/libcmmk
* Signal RGB Plugins: https://gitlab.com/signalrgb/signal-plugins/-/tree/master/Plugins
+17 -40
View File
@@ -30,37 +30,37 @@
#include "filesystem.h"
#include "StringUtils.h"
#ifdef __linux__
#include <sys/resource.h>
#endif
/*---------------------------------------------------------*\
| Warning Dialog Strings |
| Translation Strings |
\*---------------------------------------------------------*/
const char* I2C_ERR_WIN = QT_TRANSLATE_NOOP("ResourceManager",
"<h2>Warning:</h2>"
"<p>One or more I2C/SMBus interfaces failed to initialize.</p>"
"<p>Depending on which interfaces failed to initialize, some RGB DRAM modules, some motherboards' onboard RGB lighting, and RGB graphics cards may not be available in OpenRGB.</p>"
"<h2>Some internal devices may not be detected:</h2>"
"<p>One or more I2C or SMBus interfaces failed to initialize.</p>"
"<p><b>RGB DRAM modules, some motherboards' onboard RGB lighting, and RGB Graphics Cards, will not be available in OpenRGB</b> without I2C or SMBus.</p>"
"<h4>How to fix this:</h4>"
"<p>On Windows, this is usually caused by a failure to load the PawnIO driver.</p>"
"<p>For OpenRGB to access these devices, you must install PawnIO from <a href='https://pawnio.eu/'>https://pawnio.eu</a> and run OpenRGB as administrator or as a system service.</p>"
"<p>If you are not using any of the devices listed above, you can safely ignore this message.</p>");
"<p>You must first install <a href='https://pawnio.eu/'>PawnIO</a>, then you must OpenRGB as administrator in order to access these devices.</p>"
"<p>See <a href='https://help.openrgb.org/'>help.openrgb.org</a> for additional troubleshooting steps if you keep seeing this message.<br></p>"
"<h3>If you are not using internal RGB on a desktop this message is not important to you.</h3>");
const char* I2C_ERR_LINUX = QT_TRANSLATE_NOOP("ResourceManager",
"<h2>Warning:</h2>"
"<p>One or more I2C/SMBus interfaces failed to initialize.</p>"
"<p>Depending on which interfaces failed to initialize, some RGB DRAM modules, some motherboards' onboard RGB lighting, and RGB graphics cards may not be available in OpenRGB.</p>"
"<h2>Some internal devices may not be detected:</h2>"
"<p>One or more I2C or SMBus interfaces failed to initialize.</p>"
"<p><b>RGB DRAM modules, some motherboards' onboard RGB lighting, and RGB Graphics Cards, will not be available in OpenRGB</b> without I2C or SMBus.</p>"
"<h4>How to fix this:</h4>"
"<p>On Linux, this is usually because the i2c-dev module is not loaded.</p>"
"<p>For OpenRGB to access these devices, you must load the i2c-dev module along with the correct I2C driver module for your motherboard. "
"<p>You must load the i2c-dev module along with the correct i2c driver for your motherboard. "
"This is usually i2c-piix4 for AMD systems and i2c-i801 for Intel systems.</p>"
"<p>If you are not using any of the devices listed above, you can safely ignore this message.</p>");
"<p>See <a href='https://help.openrgb.org/'>help.openrgb.org</a> for additional troubleshooting steps if you keep seeing this message.<br></p>"
"<h3>If you are not using internal RGB on a desktop this message is not important to you.</h3>");
const char* UDEV_MISSING = QT_TRANSLATE_NOOP("ResourceManager",
"<h2>Warning:</h2>"
"<h2>WARNING:</h2>"
"<p>The OpenRGB udev rules are not installed.</p>"
"<p>Most devices will not be available unless running OpenRGB as root.</p>"
"<p>If using AppImage, Flatpak, or self-compiled versions of OpenRGB you must install the udev rules manually</p>"
"<p>See <a href='https://openrgb.org/udev'>https://openrgb.org/udev</a> to install the udev rules manually</p>");
const char* UDEV_MUTLI = QT_TRANSLATE_NOOP("ResourceManager",
"<h2>Warning:</h2>"
"<h2>WARNING:</h2>"
"<p>Multiple OpenRGB udev rules are installed.</p>"
"<p>The udev rules file 60-openrgb.rules is installed in both /etc/udev/rules.d and /usr/lib/udev/rules.d.</p>"
"<p>Multiple udev rules files can conflict, it is recommended to remove one of them.</p>");
@@ -190,29 +190,6 @@ ResourceManager::ResourceManager()
profile_manager = new ProfileManager(GetConfigurationDirectory());
server->SetProfileManager(profile_manager);
rgb_controllers_sizes = profile_manager->LoadProfileToList("sizes", true);
/*-----------------------------------------------------*\
| If configured, lower process priority to potentially |
| reduce interference with other programs. Positive |
| nice values decrease priority on Linux and MacOS. |
\*-----------------------------------------------------*/
json general_settings = settings_manager->GetSettings("General");
bool low_priority = false;
if(general_settings.contains("low_priority"))
{
low_priority = general_settings["low_priority"];
}
if(low_priority)
{
#if defined(__linux__) || defined(__APPLE__)
setpriority(PRIO_PROCESS, 0, 10);
#endif
#ifdef _WIN32
SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
#endif
}
}
ResourceManager::~ResourceManager()

Some files were not shown because too many files have changed in this diff Show More