The K70 RGB PRO V2 (1B1C:1BB3) was detected but never lit: it blanked into software render mode and then fell back to its onboard effect. Getting it working turned up five faults, three of which affect every Corsair V2 device rather than just this keyboard. Packet size is now read from the HID report descriptor before the first write instead of being inferred from a reply. These devices come in a 64 byte and a 1024 byte flavour, and sending a short packet to a 1024 byte endpoint stalls it until the device is power cycled, so the size has to be known up front rather than after two short queries have gone out. CORSAIR_V2_PACKET_SIZE was 1024 while pkt_sze reached 1025, so every read using pkt_sze overran its stack buffer by a byte. The command helpers now use pkt_sze against buffers of the corrected size. The lighting resource probe ran before the device was placed in software render mode, where the answer is meaningless, and treated any error as "use resource 1". Devices that answer invalid or unsupported for resource 1 want the alternate lighting resource, which takes RGB triplets; this keyboard is one of them. The probe now runs after the render mode switch and distinguishes a stale open handle from an unsupported resource. Direct lighting writes were sized from the keymap, which covers only the keys that exist. The hardware expects its full slot count and ignores a short write, so corsair_v2_device carries an optional hw_led_count and the buffer map is padded to it. Set to 193 for this keyboard. The keepalive thread wrote LEDs on its first pass, because last_update_time was left at the clock epoch and so always read as older than the update period. That raced detection on the same HID handle with no locking, which both corrupted the transaction and could abort the process. The clock is now started before the thread runs, device transactions are serialised behind a mutex, and the thread is stopped before Shutdown rather than after. Also fixes an uninitialised stack buffer sent to the device during setup, and widens the K70 RGB PRO matrix to 22 columns so the numpad period key has a position in the layout instead of being dropped. Verified on hardware: repeated colour changes apply and the device stays responsive across runs. Co-Authored-By: Claude Opus 5 <[email protected]>
260 lines
10 KiB
C++
260 lines
10 KiB
C++
/*---------------------------------------------------------*\
|
|
| RGBController_CorsairV2SoftwareController.cpp |
|
|
| |
|
|
| RGBController for Corsair V2 peripherals - software |
|
|
| modes |
|
|
| |
|
|
| Chris M (Dr_No) 11 Aug 2022 |
|
|
| |
|
|
| This file is part of the OpenRGB project |
|
|
| SPDX-License-Identifier: GPL-2.0-or-later |
|
|
\*---------------------------------------------------------*/
|
|
|
|
#include "LogManager.h"
|
|
#include "RGBController_CorsairV2Software.h"
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
/**------------------------------------------------------------------*\
|
|
@name Corsair Peripherals V2 Software
|
|
@category Keyboard
|
|
@type USB
|
|
@save :x:
|
|
@direct :white_check_mark:
|
|
@effects :x:
|
|
@detectors DetectCorsairV2SoftwareControllers
|
|
@comment
|
|
\*-------------------------------------------------------------------*/
|
|
|
|
RGBController_CorsairV2SW::RGBController_CorsairV2SW(CorsairPeripheralV2Controller *controller_ptr)
|
|
{
|
|
controller = controller_ptr;
|
|
const corsair_v2_device* corsair = controller->GetDeviceData();
|
|
|
|
name = controller->GetName();
|
|
vendor = "Corsair";
|
|
description = "Corsair Peripheral V2 SW Device";
|
|
type = corsair->type;
|
|
version = controller->GetFirmwareString();
|
|
location = controller->GetDeviceLocation();
|
|
serial = controller->GetSerialString();
|
|
|
|
mode Direct;
|
|
Direct.name = "Direct";
|
|
Direct.value = CORSAIR_V2_MODE_DIRECT;
|
|
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
|
|
Direct.color_mode = MODE_COLORS_PER_LED;
|
|
modes.push_back(Direct);
|
|
|
|
SetupZones();
|
|
/*-----------------------------------------------------*\
|
|
| The Corsair K55 RGB PRO requires a packet within |
|
|
| 1 minutes of sending the lighting change in order |
|
|
| to not revert back into rainbow mode. Start a thread |
|
|
| to continuously send a keepalive packet every 50 sec |
|
|
\*-----------------------------------------------------*/
|
|
/*-----------------------------------------------------*\
|
|
| Start the keepalive clock before the thread runs. |
|
|
| A default constructed time_point sits at the clock |
|
|
| epoch, which is always older than the update |
|
|
| period, so the thread would otherwise fire a write |
|
|
| on its first pass - during detection, racing |
|
|
| whatever the caller is doing. |
|
|
\*-----------------------------------------------------*/
|
|
last_update_time = std::chrono::steady_clock::now();
|
|
|
|
keepalive_thread_run = true;
|
|
keepalive_thread = new std::thread(&RGBController_CorsairV2SW::KeepaliveThread, this);
|
|
}
|
|
|
|
RGBController_CorsairV2SW::~RGBController_CorsairV2SW()
|
|
{
|
|
/*-----------------------------------------------------*\
|
|
| Close keepalive thread first, so that it cannot issue |
|
|
| a write while the device is being shut down. |
|
|
\*-----------------------------------------------------*/
|
|
keepalive_thread_run = false;
|
|
keepalive_thread->join();
|
|
delete keepalive_thread;
|
|
|
|
Shutdown();
|
|
|
|
delete controller;
|
|
}
|
|
|
|
void RGBController_CorsairV2SW::SetupZones()
|
|
{
|
|
std::string physical_size;
|
|
KEYBOARD_LAYOUT new_layout;
|
|
unsigned int max_led_value = 0;
|
|
const corsair_v2_device* corsair = controller->GetDeviceData();
|
|
unsigned int layout = controller->GetKeyboardLayout();
|
|
|
|
switch(layout)
|
|
{
|
|
case CORSAIR_V2_KB_LAYOUT_ISO:
|
|
new_layout = KEYBOARD_LAYOUT_ISO_QWERTY;
|
|
break;
|
|
|
|
case CORSAIR_V2_KB_LAYOUT_JIS:
|
|
new_layout = KEYBOARD_LAYOUT_JIS;
|
|
break;
|
|
|
|
case CORSAIR_V2_KB_LAYOUT_ANSI:
|
|
case CORSAIR_V2_KB_LAYOUT_ABNT:
|
|
default:
|
|
new_layout = KEYBOARD_LAYOUT_ANSI_QWERTY;
|
|
break;
|
|
}
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Fill in zones from the device data |
|
|
\*---------------------------------------------------------*/
|
|
for(size_t i = 0; i < CORSAIR_ZONES_MAX; i++)
|
|
{
|
|
if(corsair->zones[i] == NULL)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
zone new_zone;
|
|
|
|
new_zone.name = corsair->zones[i]->name;
|
|
new_zone.type = corsair->zones[i]->type;
|
|
|
|
if(new_zone.type == ZONE_TYPE_MATRIX)
|
|
{
|
|
KeyboardLayoutManager new_kb(new_layout, corsair->layout_new->base_size, corsair->layout_new->key_values);
|
|
|
|
if(corsair->layout_new->base_size != KEYBOARD_SIZE_EMPTY)
|
|
{
|
|
/*---------------------------------------------------------*\
|
|
| Minor adjustments to keyboard layout |
|
|
\*---------------------------------------------------------*/
|
|
keyboard_keymap_overlay_values* temp = corsair->layout_new;
|
|
new_kb.ChangeKeys(*temp);
|
|
|
|
new_zone.matrix_map = new_kb.GetKeyMap(KEYBOARD_MAP_FILL_TYPE_COUNT, corsair->zones[i]->rows, corsair->zones[i]->cols);
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Create LEDs for the Matrix zone |
|
|
| Place keys in the layout to populate the matrix |
|
|
\*---------------------------------------------------------*/
|
|
new_zone.leds_count = new_kb.GetKeyCount();
|
|
LOG_DEBUG("[%s] Created KB matrix with %d rows and %d columns containing %d keys",
|
|
controller->GetName().c_str(), new_kb.GetRowCount(), new_kb.GetColumnCount(), new_zone.leds_count);
|
|
|
|
for(unsigned int led_idx = 0; led_idx < new_zone.leds_count; led_idx++)
|
|
{
|
|
led new_led;
|
|
|
|
new_led.name = new_kb.GetKeyNameAt(led_idx);
|
|
new_led.value = new_kb.GetKeyValueAt(led_idx);
|
|
max_led_value = std::max(max_led_value, new_led.value);
|
|
leds.push_back(new_led);
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Add 1 the max_led_value to account for the 0th index |
|
|
\*---------------------------------------------------------*/
|
|
max_led_value++;
|
|
}
|
|
else
|
|
{
|
|
new_zone.leds_count = corsair->zones[i]->rows * corsair->zones[i]->cols;
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Create LEDs for the Linear / Single zone |
|
|
\*---------------------------------------------------------*/
|
|
for(size_t led_idx = 0; led_idx < new_zone.leds_count; led_idx++)
|
|
{
|
|
led new_led;
|
|
|
|
new_led.name = new_zone.name + " ";
|
|
new_led.name.append(std::to_string( led_idx ));
|
|
new_led.value = (unsigned int)leds.size();
|
|
|
|
leds.push_back(new_led);
|
|
}
|
|
|
|
max_led_value = std::max(max_led_value, (unsigned int)leds.size());
|
|
}
|
|
|
|
/*---------------------------------------------------------*\
|
|
| name is not set yet so description is used instead |
|
|
\*---------------------------------------------------------*/
|
|
LOG_DEBUG("[%s] Creating a %s zone: %s with %d LEDs", description.c_str(),
|
|
((new_zone.type == ZONE_TYPE_MATRIX) ? "matrix": "linear"),
|
|
new_zone.name.c_str(), new_zone.leds_count);
|
|
new_zone.leds_min = new_zone.leds_count;
|
|
new_zone.leds_max = new_zone.leds_count;
|
|
zones.push_back(new_zone);
|
|
}
|
|
}
|
|
|
|
SetupColors();
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Pad out to the slot count the device expects. A write |
|
|
| that is short of it is ignored by the hardware. |
|
|
\*---------------------------------------------------------*/
|
|
if(corsair->hw_led_count > max_led_value)
|
|
{
|
|
max_led_value = corsair->hw_led_count;
|
|
}
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Create a buffer map of pointers which contains the |
|
|
| layout order of colors the device expects. |
|
|
\*---------------------------------------------------------*/
|
|
for(size_t led_idx = 0; led_idx < max_led_value; led_idx++)
|
|
{
|
|
buffer_map.push_back(&null_color);
|
|
}
|
|
|
|
for(size_t led_idx = 0; led_idx < leds.size(); led_idx++)
|
|
{
|
|
buffer_map[leds[led_idx].value] = &colors[led_idx];
|
|
}
|
|
}
|
|
|
|
void RGBController_CorsairV2SW::DeviceUpdateLEDs()
|
|
{
|
|
last_update_time = std::chrono::steady_clock::now();
|
|
|
|
controller->SetLedsDirect(buffer_map);
|
|
}
|
|
|
|
void RGBController_CorsairV2SW::DeviceUpdateZoneLEDs(int /*zone*/)
|
|
{
|
|
controller->SetLedsDirect(buffer_map);
|
|
}
|
|
|
|
void RGBController_CorsairV2SW::DeviceUpdateSingleLED(int /*led*/)
|
|
{
|
|
controller->SetLedsDirect(buffer_map);
|
|
}
|
|
|
|
void RGBController_CorsairV2SW::DeviceUpdateMode()
|
|
{
|
|
|
|
}
|
|
|
|
void RGBController_CorsairV2SW::KeepaliveThread()
|
|
{
|
|
while(keepalive_thread_run.load())
|
|
{
|
|
if(active_mode == 0)
|
|
{
|
|
if((std::chrono::steady_clock::now() - last_update_time) >
|
|
std::chrono::milliseconds(CORSAIR_V2_UPDATE_PERIOD))
|
|
{
|
|
DeviceUpdateLEDs();
|
|
}
|
|
}
|
|
std::this_thread::sleep_for(CORSAIR_V2_SLEEP_PERIOD);
|
|
}
|
|
}
|