Compare commits
66
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e81e26fcc | ||
|
|
b97b429684 | ||
|
|
9f06fc63a1 | ||
|
|
d2dd9dcc73 | ||
|
|
6fbcf62d76 | ||
|
|
1a6943fe17 | ||
|
|
cde2343d09 | ||
|
|
c746b30287 | ||
|
|
3582b96dd9 | ||
|
|
d258065e36 | ||
|
|
7627466891 | ||
|
|
c476ee46f8 | ||
|
|
6fc68f4e62 | ||
|
|
75bac1fd4e | ||
|
|
94042375a7 | ||
|
|
103306c6c8 | ||
|
|
6d37d262ea | ||
|
|
473331dd3a | ||
|
|
bc9d03ef48 | ||
|
|
740c88fe0c | ||
|
|
684ef09168 | ||
|
|
e3b08fc15d | ||
|
|
5c931a61b4 | ||
|
|
ad7534d00d | ||
|
|
c81f8699f1 | ||
|
|
2af03eb0dc | ||
|
|
5064ba3f1d | ||
|
|
c5abe47859 | ||
|
|
8fdd0e40f8 | ||
|
|
a6a07ba0d7 | ||
|
|
614fef4189 | ||
|
|
d3e7dba2b7 | ||
|
|
39343c3e9e | ||
|
|
73865fec59 | ||
|
|
b508818f23 | ||
|
|
bef8ae0a2f | ||
|
|
cce2d41500 | ||
|
|
175f3ed338 | ||
|
|
c2cc7c6163 | ||
|
|
8fee041fad | ||
|
|
dfc1f8524a | ||
|
|
752bd2e35a | ||
|
|
01fe0b4630 | ||
|
|
e7f8bc0136 | ||
|
|
370918e07f | ||
|
|
133966677b | ||
|
|
44f84e4781 | ||
|
|
a8354ee046 | ||
|
|
4550e3073a | ||
|
|
abe7a49092 | ||
|
|
82a486bfb6 | ||
|
|
ad06e733f8 | ||
|
|
4306603a28 | ||
|
|
554f834a90 | ||
|
|
7236bec61f | ||
|
|
c799f1bfe9 | ||
|
|
f15c3c1420 | ||
|
|
aa67045330 | ||
|
|
736640f38d | ||
|
|
e9caf663bc | ||
|
|
8afbea53de | ||
|
|
c3d496ad5b | ||
|
|
06501f528d | ||
|
|
f6a678c1a4 | ||
|
|
30690d0ebd | ||
|
|
7a830cddf9 |
+4
-4
@@ -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, #
|
||||
# otherwise run them if the source is part of a merge #
|
||||
# request #
|
||||
# automatically run all jobs on the default branch or #
|
||||
# tag builds, 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_PIPELINE_SOURCE == "merge_request_event")'
|
||||
- if: '$CI_PROJECT_PATH == "CalcProgrammer1/OpenRGB" && ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG || $CI_PIPELINE_SOURCE == "merge_request_event")'
|
||||
when: on_success
|
||||
- !reference [.downstream_rules, rules]
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ 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,6 +26,7 @@ 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;
|
||||
@@ -132,13 +133,79 @@ void CorsairDRAMController::SetColorsPerLED(RGBColor* colors)
|
||||
| bytes, use a second block write to the second |
|
||||
| block write address for the remaining data |
|
||||
\*-------------------------------------------------*/
|
||||
bus->i2c_smbus_write_block_data(dev, CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_1, 32, direct_packet);
|
||||
s32 ret = bus->i2c_smbus_write_block_data(dev, CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_1, 32, direct_packet);
|
||||
|
||||
if(direct_packet_size > 32)
|
||||
if((ret >= 0) && (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,18 +24,15 @@ using namespace std::chrono_literals;
|
||||
|
||||
bool TestForCorsairDRAMController(i2c_smbus_interface *bus, unsigned char address)
|
||||
{
|
||||
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
|
||||
|
||||
LOG_DEBUG("[%s] Trying address %02X", CORSAIR_DRAM_NAME, address);
|
||||
|
||||
int res = bus->i2c_smbus_read_byte_data(address, 0x43);
|
||||
|
||||
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,7 +96,16 @@ 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,6 +35,12 @@ 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;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
@@ -93,14 +99,17 @@ CorsairPeripheralV2Controller::CorsairPeripheralV2Controller(hid_device* dev_han
|
||||
| If lighting control endpoint 2 is unavailable |
|
||||
| then use endpoint 1. |
|
||||
\*---------------------------------------------------------*/
|
||||
result = StartTransaction(0);
|
||||
if(result > 0)
|
||||
if(light_ctrl == CORSAIR_V2_LIGHT_CTRL2)
|
||||
{
|
||||
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
|
||||
StartTransaction(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);
|
||||
}
|
||||
StopTransaction(0);
|
||||
LOG_DEBUG("[%s] Lighting Endpoint set to %02X", device_name.c_str(), light_ctrl);
|
||||
}
|
||||
|
||||
CorsairPeripheralV2Controller::~CorsairPeripheralV2Controller()
|
||||
@@ -171,7 +180,11 @@ void CorsairPeripheralV2Controller::SetRenderMode(corsair_v2_device_mode mode)
|
||||
buffer[5] = mode;
|
||||
|
||||
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
|
||||
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
|
||||
|
||||
if(!skip_reads)
|
||||
{
|
||||
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
void CorsairPeripheralV2Controller::LightingControl(uint8_t opt1)
|
||||
@@ -191,7 +204,11 @@ void CorsairPeripheralV2Controller::LightingControl(uint8_t opt1)
|
||||
buffer[5] = 0x00;
|
||||
|
||||
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
|
||||
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
|
||||
|
||||
if(!skip_reads)
|
||||
{
|
||||
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int CorsairPeripheralV2Controller::GetKeyboardLayout()
|
||||
@@ -240,7 +257,11 @@ unsigned char CorsairPeripheralV2Controller::StartTransaction(uint8_t opt1)
|
||||
buffer[4] = light_ctrl;
|
||||
|
||||
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
|
||||
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
|
||||
|
||||
if(!skip_reads)
|
||||
{
|
||||
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
|
||||
}
|
||||
|
||||
return buffer[2];
|
||||
}
|
||||
@@ -257,11 +278,20 @@ void CorsairPeripheralV2Controller::StopTransaction(uint8_t opt1)
|
||||
buffer[4] = opt1;
|
||||
|
||||
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
|
||||
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
|
||||
|
||||
if(!skip_reads)
|
||||
{
|
||||
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];
|
||||
|
||||
@@ -304,7 +334,11 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
|
||||
memcpy(&buffer[offset1], &data[0], copy_bytes);
|
||||
|
||||
hid_write(dev, buffer, pkt_sze);
|
||||
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
|
||||
|
||||
if(!skip_reads)
|
||||
{
|
||||
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
|
||||
}
|
||||
|
||||
remaining -= copy_bytes;
|
||||
buffer[2] = CORSAIR_V2_CMD_BLK_WN;
|
||||
@@ -325,7 +359,11 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
|
||||
memcpy(&buffer[offset2], &data[index], copy_bytes);
|
||||
|
||||
hid_write(dev, buffer, pkt_sze);
|
||||
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
|
||||
|
||||
if(!skip_reads)
|
||||
{
|
||||
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
|
||||
}
|
||||
|
||||
remaining -= copy_bytes;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ 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,6 +57,7 @@ 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,6 +48,31 @@ 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,
|
||||
@@ -116,12 +141,14 @@ 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, 191, "Logo", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Logo
|
||||
{ 0, 0, 10, 138, "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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -167,10 +194,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
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -772,6 +799,38 @@ 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 |
|
||||
| |
|
||||
@@ -1461,6 +1520,7 @@ 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,6 +69,8 @@ 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
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
@@ -1,342 +0,0 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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,7 +134,14 @@ 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
|
||||
bus->i2c_smbus_write_block_data(dev, 0x03, sz, 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CrucialController::SendEffectColor
|
||||
|
||||
@@ -27,12 +27,6 @@ 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,
|
||||
@@ -81,7 +75,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*
|
||||
* quick write to test for a response, and if so does a simple read at 0xA0 to test *
|
||||
* byte read 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 *
|
||||
* *
|
||||
\******************************************************************************************/
|
||||
@@ -90,19 +84,19 @@ bool TestForCrucialController(i2c_smbus_interface* bus, unsigned char address)
|
||||
{
|
||||
bool pass = false;
|
||||
|
||||
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
|
||||
int res = bus->i2c_smbus_read_byte(address);
|
||||
|
||||
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);
|
||||
|
||||
@@ -178,7 +172,7 @@ void DetectCrucialControllers(std::vector<i2c_smbus_interface*> &busses)
|
||||
{
|
||||
for(unsigned int slot = 0; slot < 4; slot++)
|
||||
{
|
||||
int res = busses[bus]->i2c_smbus_write_quick(0x27, I2C_SMBUS_WRITE);
|
||||
int res = busses[bus]->i2c_smbus_read_byte(0x27);
|
||||
|
||||
if(res < 0)
|
||||
{
|
||||
@@ -192,7 +186,7 @@ void DetectCrucialControllers(std::vector<i2c_smbus_interface*> &busses)
|
||||
|
||||
if(address_list_idx < CRUCIAL_ADDRESS_COUNT)
|
||||
{
|
||||
res = busses[bus]->i2c_smbus_write_quick(crucial_addresses[address_list_idx], I2C_SMBUS_WRITE);
|
||||
res = busses[bus]->i2c_smbus_read_byte(crucial_addresses[address_list_idx]);
|
||||
}
|
||||
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 *
|
||||
* quick write to test for a response, and if so does a simple read at 0xA0 to test *
|
||||
* byte read 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_write_quick(0x77, I2C_SMBUS_WRITE);
|
||||
int res = busses[bus]->i2c_smbus_read_byte(0x77);
|
||||
|
||||
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_write_quick(ene_ram_addresses[address_list_idx], I2C_SMBUS_WRITE);
|
||||
res = busses[bus]->i2c_smbus_read_byte(ene_ram_addresses[address_list_idx]);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -461,6 +461,7 @@ 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);
|
||||
@@ -471,6 +472,7 @@ 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,5 +61,12 @@ 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
|
||||
bus->i2c_smbus_write_block_data(dev, 0x03, sz, 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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 */
|
||||
@@ -0,0 +1,446 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
};
|
||||
+17
-2
@@ -68,9 +68,14 @@ void RGBFusion2BlackwellGPUController::SetMode(uint8_t type, uint8_t zone, uint8
|
||||
if(zone_config.numberOfColors > 0)
|
||||
{
|
||||
int currentPos = 12;
|
||||
if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT)
|
||||
switch(gpu_layout)
|
||||
{
|
||||
currentPos = 11;
|
||||
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;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < zone_config.numberOfColors; i++)
|
||||
@@ -90,6 +95,16 @@ 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;
|
||||
|
||||
+6
-5
@@ -62,11 +62,12 @@ 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_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,
|
||||
};
|
||||
|
||||
class RGBFusion2BlackwellGPUController
|
||||
|
||||
+38
-20
@@ -1,4 +1,4 @@
|
||||
/*---------------------------------------------------------*\
|
||||
/*---------------------------------------------------------*\
|
||||
| GigabyteRGBFusion2BlackwellGPUControllerDetect.cpp |
|
||||
| |
|
||||
| Detector for Gigabyte RGB Fusion 2 Blackwell GPU |
|
||||
@@ -181,30 +181,48 @@ 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 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 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);
|
||||
|
||||
/*-----------------------------------------*\
|
||||
| AMD GPUs |
|
||||
|
||||
+52
-1
@@ -400,6 +400,52 @@ 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();
|
||||
}
|
||||
|
||||
@@ -446,6 +492,10 @@ 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
|
||||
@@ -472,7 +522,8 @@ 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)
|
||||
else if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT ||
|
||||
gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT)
|
||||
{
|
||||
if(zone_idx == 5)
|
||||
{
|
||||
|
||||
@@ -152,6 +152,7 @@ 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);
|
||||
@@ -188,6 +189,7 @@ 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 8
|
||||
#define GB_FUSION2_ZONES_MAX 12
|
||||
|
||||
/*--------------------------------------------------------*\
|
||||
| Base LED mappings found on all controllers. |
|
||||
@@ -89,6 +89,23 @@ 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,10 +95,9 @@ 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())
|
||||
@@ -318,7 +317,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;
|
||||
@@ -362,7 +361,6 @@ 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)
|
||||
@@ -392,16 +390,16 @@ bool RGBFusion2USBController::SetStripBuiltinEffectState(int hdr, bool enable)
|
||||
}
|
||||
}
|
||||
|
||||
int new_effect_disabled = enable
|
||||
? (effect_disabled & ~bitmask)
|
||||
: (effect_disabled | bitmask);
|
||||
int base_mask = (effect_disabled < 0) ? 0 : effect_disabled;
|
||||
int new_effect_disabled = enable
|
||||
? (base_mask & ~bitmask)
|
||||
: (base_mask | bitmask);
|
||||
|
||||
if(!first_call && new_effect_disabled == effect_disabled)
|
||||
// Skip redundant writes only after we have synchronized at least once
|
||||
if(effect_disabled >= 0 && 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));
|
||||
@@ -439,7 +437,7 @@ bool RGBFusion2USBController::EnableLampArray(bool enable)
|
||||
{
|
||||
return SendCCReport(0x48, enable ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
std::string RGBFusion2USBController::GetDeviceName()
|
||||
{
|
||||
return(name);
|
||||
@@ -703,3 +701,115 @@ 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,6 +21,11 @@
|
||||
|
||||
#define FUSION2_USB_BUFFER_SIZE 64
|
||||
|
||||
/*--------------------------------------------------------*\
|
||||
| Gen2 scan/info opcode base |
|
||||
\*--------------------------------------------------------*/
|
||||
#define GEN2_LED_BASE_SCAN 0x38
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Effects mode list |
|
||||
\*---------------------------------------------------------*/
|
||||
@@ -83,6 +88,16 @@ 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)
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
@@ -321,6 +336,8 @@ 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();
|
||||
@@ -330,6 +347,8 @@ 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;
|
||||
@@ -345,7 +364,7 @@ private:
|
||||
int SendPacket(unsigned char* packet);
|
||||
|
||||
hid_device* dev;
|
||||
int device_num;
|
||||
uint8_t device_num;
|
||||
uint16_t product_id;
|
||||
uint32_t effect_zone_mask = 0;
|
||||
int mode;
|
||||
@@ -356,7 +375,7 @@ private:
|
||||
std::string location;
|
||||
std::string version;
|
||||
std::string chip_id;
|
||||
int effect_disabled = 0;
|
||||
int effect_disabled = -1;
|
||||
int report_id = 0xCC;
|
||||
bool report_loaded = false;
|
||||
bool cali_loaded = false;
|
||||
|
||||
+197
-59
@@ -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,8 +39,10 @@ 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;
|
||||
@@ -206,6 +208,13 @@ 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;
|
||||
}
|
||||
|
||||
@@ -214,94 +223,125 @@ RGBController_RGBFusion2USB::~RGBController_RGBFusion2USB()
|
||||
\*---------------------------------------------------------*/
|
||||
void RGBController_RGBFusion2USB::Init_Controller()
|
||||
{
|
||||
const std::string SectionCustom = "CustomLayout";
|
||||
|
||||
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 SectionCalibration = "Calibration";
|
||||
RvrseLedHeaders ReverseLedLookup = reverse_map(LedLookup);
|
||||
SettingsManager* settings_manager = ResourceManager::get()->GetSettingsManager();
|
||||
nlohmann::json device_settings = settings_manager->GetSettings(detector_name);
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Create the custom layout from the generic_device |
|
||||
| Checks for Gen2 support and adds flag to json. |
|
||||
\*---------------------------------------------------------*/
|
||||
gb_fusion2_device* layout = const_cast<gb_fusion2_device*>(gb_fusion2_device_list[device_index]);
|
||||
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;
|
||||
}
|
||||
|
||||
if(!device_settings.contains(SectionCustom))
|
||||
{
|
||||
device_settings[SectionCustom]["Enabled"] = false;
|
||||
device_settings[SectionCustom]["Data"] = BuildCustomLayoutJson(layout, ReverseLedLookup);
|
||||
device_settings[SectionCustom]["Data"] = BuildCustomLayoutJson(src_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_settings.contains(SectionCalibration))
|
||||
if(device_num == 0)
|
||||
{
|
||||
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())
|
||||
if(!device_settings.contains(SectionCalibration))
|
||||
{
|
||||
cal_sec["Data"] = WriteCalJsonFrom(hw_cal);
|
||||
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& cdata = cal_sec["Data"];
|
||||
FillMissingWith(cdata, hw_cal);
|
||||
nlohmann::json& cal_sec = device_settings[SectionCalibration];
|
||||
bool cal_enable = cal_sec.value("Enabled", false);
|
||||
|
||||
if(!cal_enable)
|
||||
if(!cal_sec.contains("Data") || !cal_sec["Data"].is_object())
|
||||
{
|
||||
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";
|
||||
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);
|
||||
}
|
||||
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 |
|
||||
@@ -315,35 +355,81 @@ void RGBController_RGBFusion2USB::Init_Controller()
|
||||
\*-----------------------------------------------------------------*/
|
||||
for(unsigned int i = 0; i < GB_FUSION2_DEVICE_COUNT; i++)
|
||||
{
|
||||
if(gb_fusion2_device_list[i]->name == name)
|
||||
if(gb_fusion2_device_list[i]->name == name &&
|
||||
gb_fusion2_device_list[i]->device_num == device_num)
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| Set device ID |
|
||||
\*---------------------------------------------------------*/
|
||||
device_index = i;
|
||||
layout = const_cast<gb_fusion2_device*>(gb_fusion2_device_list[i]);
|
||||
src_layout = 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(!layout->zones[0][zone_idx])
|
||||
if(!(*instance_layout.zones)[zone_idx])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const gb_fusion2_zone* zone_at_idx = layout->zones[0][zone_idx];
|
||||
const gb_fusion2_zone* zone_at_idx = (*instance_layout.zones)[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 == new_zone.leds_max) ? ZONE_TYPE_SINGLE : ZONE_TYPE_LINEAR;
|
||||
new_zone.type = ((new_zone.leds_min == 1) && (new_zone.leds_max == 1)) ? ZONE_TYPE_SINGLE : ZONE_TYPE_LINEAR;
|
||||
new_zone.matrix_map = NULL;
|
||||
zones.emplace_back(new_zone);
|
||||
}
|
||||
@@ -363,17 +449,68 @@ 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 = gb_fusion2_device_list[device_index]->zones[0][zone_idx];
|
||||
const gb_fusion2_zone* zone_at_idx = (*instance_layout.zones)[zone_idx];
|
||||
if(!zone_at_idx)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool single_zone = (zone_at_idx->leds_min == zone_at_idx->leds_max);
|
||||
bool single_zone = ((zone_at_idx->leds_min == 1) && (zone_at_idx->leds_max == 1));
|
||||
|
||||
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:
|
||||
@@ -826,6 +963,7 @@ 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,17 +62,26 @@ private:
|
||||
std::string detector_name;
|
||||
|
||||
RGBFusion2USBController* controller;
|
||||
int device_num;
|
||||
uint8_t 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,10 +16,21 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static std::map<std::string, unsigned int> govee_led_counts
|
||||
struct GoveeHardwareInfo
|
||||
{
|
||||
{ "H619A", 20 },
|
||||
{ "H70B1", 20 },
|
||||
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
|
||||
};
|
||||
|
||||
RGBController_Govee::RGBController_Govee(GoveeController* controller_ptr)
|
||||
@@ -67,39 +78,64 @@ RGBController_Govee::~RGBController_Govee()
|
||||
|
||||
void RGBController_Govee::SetupZones()
|
||||
{
|
||||
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())
|
||||
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())
|
||||
{
|
||||
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 */
|
||||
hw = it->second;
|
||||
resizable = false;
|
||||
}
|
||||
|
||||
zone strip;
|
||||
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.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.leds_min = 1;
|
||||
strip.leds_max = 255;
|
||||
strip.name = "Govee Strip";
|
||||
strip.type = ZONE_TYPE_LINEAR;
|
||||
strip.matrix_map = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
strip.leds_min = led_count;
|
||||
strip.leds_max = led_count;
|
||||
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.matrix_map = NULL;
|
||||
|
||||
zones.push_back(strip);
|
||||
|
||||
for(std::size_t led_idx = 0; led_idx < strip.leds_count; led_idx++)
|
||||
@@ -114,6 +150,11 @@ 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,28 +30,9 @@ using namespace std::chrono_literals;
|
||||
|
||||
bool TestForHyperXDRAMController(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);
|
||||
|
||||
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);
|
||||
return(res >= 0);
|
||||
|
||||
} /* 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";
|
||||
fan_1_led.name = "Fan 1 LED " + std::to_string(led_idx + 1);
|
||||
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";
|
||||
fan_2_led.name = "Fan 2 LED " + std::to_string(led_idx + 1);
|
||||
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";
|
||||
back_led.name = "Back LED " + std::to_string(led_idx + 1);
|
||||
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";
|
||||
ring_led.name = "Ring LED " + std::to_string(led_idx + 1);
|
||||
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";
|
||||
logo_led.name = "Logo LED " + std::to_string(led_idx + 1);
|
||||
logo_led.value = logo_leds[led_idx];
|
||||
leds.push_back(logo_led);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <thread>
|
||||
#include <hidapi.h>
|
||||
#include "Detector.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "LogManager.h"
|
||||
#include "LogitechProtocolCommon.h"
|
||||
#include "LogitechG203LController.h"
|
||||
@@ -40,6 +41,8 @@
|
||||
#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;
|
||||
|
||||
@@ -84,6 +87,9 @@ 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
|
||||
@@ -135,6 +141,8 @@ 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 |
|
||||
@@ -358,6 +366,106 @@ 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);
|
||||
@@ -650,6 +758,220 @@ 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 |
|
||||
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
@@ -667,7 +989,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", DetectLogitechKeyboardG915, LOGITECH_VID, LOGITECH_G915_RECEIVER_2_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 (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);
|
||||
@@ -683,11 +1005,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);
|
||||
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
|
||||
@@ -890,12 +1212,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);
|
||||
@@ -913,7 +1235,6 @@ 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, 4);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
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, 4);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
@@ -189,7 +189,7 @@ void LogitechG915Controller::BeginModeSet()
|
||||
/*-----------------------------------------------------*\
|
||||
| Send packet |
|
||||
\*-----------------------------------------------------*/
|
||||
hid_write(dev_handle, usb_buf, 4);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ void LogitechG915Controller::InitializeModeSet()
|
||||
/*-----------------------------------------------------*\
|
||||
| Send packet |
|
||||
\*-----------------------------------------------------*/
|
||||
hid_write(dev_handle, usb_buf, 7);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ void LogitechG915Controller::InitializeDirect()
|
||||
/*-----------------------------------------------------*\
|
||||
| Send packet |
|
||||
\*-----------------------------------------------------*/
|
||||
hid_write(dev_handle, usb_buf, 4);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
@@ -259,7 +259,7 @@ void LogitechG915Controller::InitializeDirect()
|
||||
/*-----------------------------------------------------*\
|
||||
| Send packet |
|
||||
\*-----------------------------------------------------*/
|
||||
hid_write(dev_handle, usb_buf, 4);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
@@ -279,7 +279,7 @@ void LogitechG915Controller::InitializeDirect()
|
||||
/*-----------------------------------------------------*\
|
||||
| Send packet |
|
||||
\*-----------------------------------------------------*/
|
||||
hid_write(dev_handle, usb_buf, 17);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
hid_read(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
@@ -300,7 +300,7 @@ void LogitechG915Controller::InitializeDirect()
|
||||
/*-----------------------------------------------------*\
|
||||
| Send packet |
|
||||
\*-----------------------------------------------------*/
|
||||
hid_write(dev_handle, usb_buf, 17);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ void LogitechG915Controller::SendDirectFrame
|
||||
/*-----------------------------------------------------*\
|
||||
| Send packet |
|
||||
\*-----------------------------------------------------*/
|
||||
hid_write(dev_handle, usb_buf, HEADER_SIZE + length);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
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[HEADER_SIZE + 13];
|
||||
unsigned char usb_buf[MESSAGE_LEN];
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Zero out buffer |
|
||||
@@ -433,6 +433,6 @@ void LogitechG915Controller::SendMode
|
||||
/*-----------------------------------------------------*\
|
||||
| Send packet |
|
||||
\*-----------------------------------------------------*/
|
||||
hid_write(dev_handle, usb_buf, HEADER_SIZE + 13);
|
||||
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
|
||||
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
|
||||
}
|
||||
|
||||
+5709
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,874 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
};
|
||||
+433
@@ -0,0 +1,433 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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", ¤t_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;
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
};
|
||||
+1796
File diff suppressed because it is too large
Load Diff
+72
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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,6 +70,12 @@ 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 5
|
||||
#define MSI_LAPTOP_ALC_LIGHTBAR_LEDS 3
|
||||
|
||||
static const msi_laptop_led msi_raider_a18_klc_leds[] =
|
||||
{
|
||||
@@ -140,14 +140,24 @@ 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 },
|
||||
{ "Lightbar 4", 0x03 },
|
||||
{ "Lightbar 5", 0x04 },
|
||||
{ "Logo", 0x05 },
|
||||
{ "Logo", 0x03 },
|
||||
};
|
||||
|
||||
static const MSILaptopModel msi_laptop_models[] =
|
||||
@@ -220,6 +230,6 @@ void DetectMSILaptop(hid_device_info* info, const std::string& name)
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_HID_DETECTOR_I("MSI Laptop Keyboard", DetectMSILaptop, STEELSERIES_VID, STEELSERIES_MSI_RAIDER_A18_KLC_PID, 0);
|
||||
REGISTER_HID_DETECTOR("MSI Laptop Keyboard", DetectMSILaptop, STEELSERIES_VID, STEELSERIES_MSI_RAIDER_A18_KLC_PID);
|
||||
REGISTER_HID_DETECTOR_I("MSI Laptop Lightbar", DetectMSILaptop, STEELSERIES_VID, STEELSERIES_MSI_RAIDER_A18_ALC_PID, 0);
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
@@ -0,0 +1,171 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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();
|
||||
};
|
||||
+1
@@ -307,6 +307,7 @@ 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
|
||||
|
||||
+28
-24
@@ -71,34 +71,38 @@ 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[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)
|
||||
};
|
||||
|
||||
enum MSI_ZONE setup_map [] =
|
||||
|
||||
@@ -209,6 +209,7 @@ 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,12 +17,13 @@
|
||||
#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)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -330,7 +331,14 @@ void NZXTHue2Controller::SetChannelEffect
|
||||
/*-----------------------------------------------------*\
|
||||
| Send effect packet |
|
||||
\*-----------------------------------------------------*/
|
||||
SendEffect(channel, mode, speed, direction, num_colors, &color_data[0]);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
void NZXTHue2Controller::SetChannelLEDs
|
||||
@@ -357,14 +365,15 @@ void NZXTHue2Controller::SetChannelLEDs
|
||||
/*-----------------------------------------------------*\
|
||||
| Send first group of color data |
|
||||
\*-----------------------------------------------------*/
|
||||
SendDirect(channel, 0, 20, &color_data[0]);
|
||||
unsigned char first_color_count = (num_colors > 20) ? 20 : (unsigned char)num_colors;
|
||||
SendDirect(channel, 0, first_color_count, &color_data[0]);
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Send second group of color data if necessary |
|
||||
\*-----------------------------------------------------*/
|
||||
if(num_colors > 20)
|
||||
{
|
||||
SendDirect(channel, 1, 20, &color_data[60]);
|
||||
SendDirect(channel, 1, (unsigned char)(num_colors - 20), &color_data[60]);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
@@ -502,6 +511,99 @@ 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);
|
||||
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();
|
||||
|
||||
std::string GetFirmwareVersion();
|
||||
@@ -129,6 +129,7 @@ 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;
|
||||
|
||||
@@ -155,5 +156,15 @@ 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)
|
||||
static void spawn_hue(hid_device_info* info, const std::string& name, int rgb_channels, int fan_channels, bool use_2023_effects = false)
|
||||
{
|
||||
hid_device* dev = hid_open_path(info->path);
|
||||
|
||||
if(dev)
|
||||
{
|
||||
NZXTHue2Controller* controller = new NZXTHue2Controller(dev, rgb_channels, fan_channels, info->path, name);
|
||||
NZXTHue2Controller* controller = new NZXTHue2Controller(dev, rgb_channels, fan_channels, info->path, name, use_2023_effects);
|
||||
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);
|
||||
spawn_hue(info, name, 2, 2, true);
|
||||
}
|
||||
|
||||
void DetectNZXTFanController(hid_device_info* info, const std::string& name)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
@@ -0,0 +1,533 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,279 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
@@ -0,0 +1,239 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
|
||||
vialrgb_led_info QMKVialRGBController::GetLEDInfo(unsigned short led_index)
|
||||
qmk_rgb_matrix_led_info QMKVialRGBController::GetLEDInfo(unsigned short led_index)
|
||||
{
|
||||
return(led_info[led_index]);
|
||||
}
|
||||
@@ -257,12 +257,12 @@ unsigned short QMKVialRGBController::CmdGetKeycode
|
||||
return(keycode);
|
||||
}
|
||||
|
||||
vialrgb_led_info QMKVialRGBController::CmdGetLEDInfo
|
||||
qmk_rgb_matrix_led_info QMKVialRGBController::CmdGetLEDInfo
|
||||
(
|
||||
unsigned short led_index
|
||||
)
|
||||
{
|
||||
vialrgb_led_info data;
|
||||
qmk_rgb_matrix_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,6 +13,7 @@
|
||||
|
||||
#include <hidapi.h>
|
||||
#include "ResourceManager.h"
|
||||
#include "QMKCommon.h"
|
||||
#include "RGBController.h"
|
||||
|
||||
#define MSG_LEN 32
|
||||
@@ -98,34 +99,25 @@ 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();
|
||||
vialrgb_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();
|
||||
qmk_rgb_matrix_led_info GetLEDInfo(unsigned short led_index);
|
||||
|
||||
void GetMode
|
||||
(
|
||||
@@ -152,22 +144,22 @@ public:
|
||||
);
|
||||
|
||||
private:
|
||||
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;
|
||||
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;
|
||||
|
||||
unsigned short CmdGetKeycode
|
||||
(
|
||||
@@ -176,7 +168,7 @@ private:
|
||||
unsigned char col
|
||||
);
|
||||
|
||||
vialrgb_led_info CmdGetLEDInfo
|
||||
qmk_rgb_matrix_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++)
|
||||
{
|
||||
vialrgb_led_info info = controller->GetLEDInfo(led_index);
|
||||
qmk_rgb_matrix_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++)
|
||||
{
|
||||
vialrgb_led_info info = controller->GetLEDInfo(led_index);
|
||||
qmk_rgb_matrix_led_info info = controller->GetLEDInfo(led_index);
|
||||
|
||||
matrix_map[(width * info.row) + info.col] = (unsigned int)led_index;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,8 @@ void RGBController_Razer::SetupZones()
|
||||
\*---------------------------------------------------------*/
|
||||
if(new_zone.type == ZONE_TYPE_MATRIX)
|
||||
{
|
||||
if(new_zone.name == ZONE_EN_KEYBOARD && device_list[device_index]->layout != NULL)
|
||||
if(device_list[device_index]->layout != NULL &&
|
||||
(new_zone.name == ZONE_EN_KEYBOARD || new_zone.name == "Keypad"))
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| Dynamically generate a keyboard layout |
|
||||
@@ -212,7 +213,8 @@ 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)
|
||||
if(device_list[device_index]->layout->base_size != KEYBOARD_SIZE::KEYBOARD_SIZE_EMPTY ||
|
||||
!device_list[device_index]->layout->edit_keys.empty())
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| Minor adjustments to keyboard layout |
|
||||
|
||||
@@ -443,6 +443,8 @@ 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,13 +237,16 @@ 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", 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, 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 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);
|
||||
|
||||
@@ -77,6 +77,30 @@ 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,
|
||||
@@ -2056,6 +2080,76 @@ 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 |
|
||||
| |
|
||||
@@ -2088,7 +2182,7 @@ static const razer_device blackwidow_x_chroma_device =
|
||||
NULL,
|
||||
NULL
|
||||
},
|
||||
NULL
|
||||
&razer_blackwidow_x_chroma_layout
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------*\
|
||||
@@ -7429,7 +7523,7 @@ static const razer_device tartarus_pro_device =
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------*\
|
||||
| Razer Tartarus V2 1532:0208 |
|
||||
| Razer Tartarus V2 1532:022B |
|
||||
| |
|
||||
| Zone "Keypad" |
|
||||
| Matrix |
|
||||
@@ -9329,6 +9423,8 @@ 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,6 +59,8 @@
|
||||
#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
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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, ¶m);
|
||||
}
|
||||
|
||||
void RGBController_RealtekARGB::DeviceUpdateMode()
|
||||
{
|
||||
if(modes[active_mode].value != REALTEK_ARGB_EFF_NULL)
|
||||
{
|
||||
DeviceUpdateLEDs();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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};
|
||||
};
|
||||
@@ -0,0 +1,715 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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();
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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,6 +28,7 @@
|
||||
#define REDRAGON_M801_PID 0xFC58
|
||||
#define REDRAGON_M810_PID 0xFA7E
|
||||
#define REDRAGON_M987_PID 0xFC69
|
||||
#define REDRAGON_M921_PID 0xFC40
|
||||
|
||||
/******************************************************************************************\
|
||||
* *
|
||||
@@ -62,3 +63,4 @@ 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);
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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;
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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,6 +10,8 @@
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include "RGBController_ZotacBlackwellGPU.h"
|
||||
#include "LogManager.h"
|
||||
#include "pci_ids.h"
|
||||
|
||||
/**------------------------------------------------------------------*\
|
||||
@name ZOTAC RTX 50 series GPU
|
||||
@@ -20,21 +22,60 @@
|
||||
@effects :tools:
|
||||
@detectors DetectZotacBlackwellGPUControllersPCI
|
||||
@comment
|
||||
Supports ZOTAC Blackwell (RTX 50 series) GPUs with 3 zones:
|
||||
Logo, Side Bar, and Infinity Mirror.
|
||||
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.
|
||||
|
||||
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`
|
||||
and detection entries in
|
||||
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
|
||||
`Controllers/ZotacBlackwellGPUController/ZotacBlackwellGPUControllerDetect.cpp`.
|
||||
\*-------------------------------------------------------------------*/
|
||||
|
||||
RGBController_ZotacBlackwellGPU::RGBController_ZotacBlackwellGPU(ZotacBlackwellGPUController* controller_ptr)
|
||||
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)
|
||||
{
|
||||
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() + ")";
|
||||
@@ -252,52 +293,25 @@ RGBController_ZotacBlackwellGPU::~RGBController_ZotacBlackwellGPU()
|
||||
void RGBController_ZotacBlackwellGPU::SetupZones()
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| Zone 0: Logo |
|
||||
| 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 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);
|
||||
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);
|
||||
|
||||
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);
|
||||
led new_led;
|
||||
new_led.name = zone_name + " LED";
|
||||
leds.push_back(new_led);
|
||||
}
|
||||
|
||||
SetupColors();
|
||||
}
|
||||
@@ -362,7 +376,7 @@ void RGBController_ZotacBlackwellGPU::DeviceUpdateZone(int zone)
|
||||
|
||||
void RGBController_ZotacBlackwellGPU::DeviceUpdateMode()
|
||||
{
|
||||
for(unsigned int zone_idx = 0; zone_idx < ZOTAC_BLACKWELL_GPU_NUM_ZONES; zone_idx++)
|
||||
for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++)
|
||||
{
|
||||
DeviceUpdateZone(zone_idx);
|
||||
}
|
||||
|
||||
@@ -11,13 +11,17 @@
|
||||
|
||||
#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);
|
||||
RGBController_ZotacBlackwellGPU(ZotacBlackwellGPUController* controller_ptr,
|
||||
uint16_t device, uint16_t subdevice);
|
||||
~RGBController_ZotacBlackwellGPU();
|
||||
|
||||
void SetupZones();
|
||||
@@ -33,4 +37,17 @@ 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,14 +46,6 @@
|
||||
#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. *
|
||||
* 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. *
|
||||
* *
|
||||
* 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,10 +34,13 @@ 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);
|
||||
RGBController_ZotacBlackwellGPU* rgb_controller = new RGBController_ZotacBlackwellGPU(controller,
|
||||
bus->pci_device,
|
||||
bus->pci_subsystem_device);
|
||||
|
||||
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,8 +1012,20 @@ 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;
|
||||
width = std::max(width, cols);
|
||||
height = std::max(height, rows);
|
||||
|
||||
/*-------------------------------------------------------------------------*\
|
||||
| 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;
|
||||
}
|
||||
|
||||
for(unsigned int r = 0; r < height; r++)
|
||||
{
|
||||
@@ -1027,6 +1039,14 @@ 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)
|
||||
{
|
||||
|
||||
+5
-1
@@ -515,7 +515,11 @@ void NetworkClient::ListenThreadFunction()
|
||||
/*---------------------------------------------------------*\
|
||||
| Header received, now receive the data |
|
||||
\*---------------------------------------------------------*/
|
||||
if(header.pkt_size > 0)
|
||||
if(header.pkt_size > OPENRGB_SDK_MAX_PACKET_SIZE)
|
||||
{
|
||||
goto listen_done;
|
||||
}
|
||||
else if(header.pkt_size > 0)
|
||||
{
|
||||
bytes_read = 0;
|
||||
|
||||
|
||||
+6
-1
@@ -27,7 +27,12 @@
|
||||
/*-----------------------------------------------------*\
|
||||
| Default Interface to bind to. |
|
||||
\*-----------------------------------------------------*/
|
||||
#define OPENRGB_SDK_HOST "0.0.0.0"
|
||||
#define OPENRGB_SDK_HOST "127.0.0.1"
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Default max packet size is 8MB |
|
||||
\*-----------------------------------------------------*/
|
||||
#define OPENRGB_SDK_MAX_PACKET_SIZE (1024 * 1024 * 8)
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Default OpenRGB SDK port is 6742 |
|
||||
|
||||
+6
-1
@@ -627,7 +627,12 @@ void NetworkServer::ListenThreadFunction(NetworkClientInfo * client_info)
|
||||
| Header received, now receive the data |
|
||||
\*---------------------------------------------------------*/
|
||||
bytes_read = 0;
|
||||
if(header.pkt_size > 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)
|
||||
{
|
||||
data = new char[header.pkt_size];
|
||||
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
#-----------------------------------------------------------------------------------------------#
|
||||
MAJOR = 0
|
||||
MINOR = 9
|
||||
SUFFIX = git
|
||||
SUFFIX = 1.0rc3.1
|
||||
|
||||
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/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/SmbusNCT6793.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
@@ -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(filename);
|
||||
filesystem::path profile_path = configuration_directory / filesystem::u8path(StringUtils::make_filename(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(profile_name);
|
||||
filesystem::path filename = configuration_directory / filesystem::u8path(StringUtils::make_filename(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 / profile_name;
|
||||
filesystem::path filename = configuration_directory / StringUtils::make_filename(profile_name);
|
||||
filename.concat(".orp");
|
||||
|
||||
filesystem::remove(filename);
|
||||
|
||||
@@ -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 unofficial Flatpak release is [available on Flathub](https://flathub.org/apps/details/org.openrgb.OpenRGB)
|
||||
* An official 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,6 +165,7 @@ 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
|
||||
|
||||
+40
-17
@@ -30,37 +30,37 @@
|
||||
#include "filesystem.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Translation Strings |
|
||||
| Warning Dialog Strings |
|
||||
\*---------------------------------------------------------*/
|
||||
const char* I2C_ERR_WIN = QT_TRANSLATE_NOOP("ResourceManager",
|
||||
"<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>"
|
||||
"<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>"
|
||||
"<p>On Windows, this is usually caused by a failure to load the PawnIO driver.</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>");
|
||||
"<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>");
|
||||
const char* I2C_ERR_LINUX = QT_TRANSLATE_NOOP("ResourceManager",
|
||||
"<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>"
|
||||
"<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>"
|
||||
"<p>On Linux, this is usually because the i2c-dev module is not loaded.</p>"
|
||||
"<p>You must load the i2c-dev module along with the correct i2c driver for your motherboard. "
|
||||
"<p>For OpenRGB to access these devices, you must load the i2c-dev module along with the correct I2C driver module for your motherboard. "
|
||||
"This is usually i2c-piix4 for AMD systems and i2c-i801 for Intel systems.</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>");
|
||||
"<p>If you are not using any of the devices listed above, you can safely ignore this message.</p>");
|
||||
|
||||
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,6 +190,29 @@ 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
Reference in New Issue
Block a user