Compare commits

...
Author SHA1 Message Date
Adam Honse bfaef77719 API5 PLUGIN CHANGE: Add function for plugins to save profiles, add RGBController static functions to plugin API 2026-08-03 22:58:02 -05:00
Adam Honse 02d0e16a72 Clean up SteelSeriesApexBaseController 2026-08-03 21:33:23 -05:00
Alessandro Di Ronza 6ebf3573af Add illumination brightness control to the Apex keyboards 2026-08-03 21:24:43 -05:00
Barry H 5aef451e81 Add edifier halo pixelbar 2026-08-03 20:59:57 -05:00
Adam Honse 1880961afa Royuan keyboard controller cleanup 2026-08-03 20:41:15 -05:00
Maylton Fernandes fb5a723193 Consolidate ROYUAN keyboard protocol support 2026-08-03 20:32:40 -05:00
Maylton Fernandes 88002a4abf Add Akko Multi-modes Keyboard-B RGB support 2026-08-03 20:32:40 -05:00
Maylton Fernandes 289dbc675a Add Akko Multi-modes Keyboard-B RGB support 2026-08-03 20:32:40 -05:00
Daniel Clark bcfaa7e8a7 fix Z490 Vision D layout 2026-08-03 16:50:05 -05:00
Adam Honse bf62ead713 Migrate controller manual configurations (zone sizes) from legacy sizes.ors 2026-08-03 16:46:19 -05:00
Adam Honse 8a7cda7495 Rework profile migration, match new function style for reading the legacy file into a controller list, handle migration after local client connection so that profiles can upload to server 2026-08-02 23:47:57 -05:00
Adiker 84cf974d70 ProfileManager: migrate legacy binary profiles to JSON 2026-08-02 18:03:34 -05:00
Lord258 afc54314b5 Add support for Nanoleaf Lines (NL59) 2026-08-02 17:20:22 -05:00
Valentin Lobstein bd8db75106 Add ITE 829x (PID 0x8910) per-key RGB keyboard support + diagonal direction core support 2026-08-02 15:45:01 -05:00
Valentin Lobstein 9da692b12f SDK6 PROTOCOL CHANGE: Add diagonal direction support to OpenRGB core 2026-08-02 14:13:19 -05:00
Andrew Ingram 4ee39b0dc8 Add SteelSeries Rival 5 support 2026-08-02 00:23:32 -05:00
Norman Rasmussen 286514b509 Skip plugin entries that are incompatible during duplicate plugin detection 2026-08-01 19:01:51 -05:00
Diogo Trindade c7140baa33 Fix device IDs in worker thread acknowledgements 2026-08-01 18:56:59 +01:00
Ken Sanislo 2a9889b194 NetworkClient: break the listener/GUI deadlock structurally 2026-08-01 11:16:20 -05:00
Norman Rasmussen 2afbd64c8e Release internal locks in RGBController_Virtual before calling functions that will call back into public APIs 2026-07-31 14:23:36 -05:00
Adam Honse 8be6aba43a SDK6 PROTOCOL CHANGE - Change from 32-bit to 16-bit device/bus/port count size on the device info network packets for consistency with other array packets 2026-07-31 11:45:51 -05:00
Maylton Fernandes 565f1dad74 Add ASRock RX 9070 XT Steel Legend RGB support 2026-07-31 10:19:59 -05:00
aestheticjmack 67a81f219d Add SteelSeries Aerox 3 Wireless Gen 2 support 2026-07-31 07:58:14 -05:00
Adam Honse ccd103a29f Enable systemd hardening in openrgb.service 2026-07-30 23:04:03 -05:00
Adam Honse c5d088b69d SDK6 PROTOCOL CHANGE - Flip order of active mode and mode count in zone description for consistency 2026-07-30 22:43:56 -05:00
Adam Honse 6362054368 Don't attempt auto connect when started in server mode 2026-07-30 22:29:42 -05:00
Adam Honse 2dab17f5dc Error out if setting RGBController description parsing fails in NetworkClient 2026-07-30 19:14:49 -05:00
Justo Enriquez c545305825 Fix Gigabyte Radeon RX 9070 XT not detected by Blackwell GPU controller 2026-07-30 18:59:19 -05:00
Adam Honse 578c81784b Fixups around hiding/unhiding controllers, create a new function that only handles showing and hiding for SignalUpdate callbacks, fix pointer dereference in NetworkServer, fix same-thread method invoke 2026-07-30 18:49:06 -05:00
Adam Honse 1cd9b63c3e Fix errors in updating device list when hidden devices exist 2026-07-30 16:49:59 -05:00
Ken Sanislo 7b1817a0b8 ResourceManager/GUI: leave client mode cleanly on primary disconnect
Two problems surfaced once disconnect stopped crashing.

Disconnecting the automatic local connection left auto_connection_client
pointing at the freed client and auto_connection_active still true.
RescanDevices and GetDetectionPercent/String trust those, so a rescan
afterward called into freed memory and aborted (std::system_error from
send_in_progress.lock() on a destroyed mutex). Clear the pointer and the
flag in the teardown when the removed client is the auto connection.

onDetectionEnded never lowered the detection view; only the progress
handler did, at 100 percent. A real detection reaches 100, but a client
teardown borrows DETECTION_STARTED/COMPLETE to make plugins release the
controllers and sends no progress, so the "detecting devices" overlay
stuck on after a disconnect until Cancel. Lower it unconditionally when
the sequence ends.
2026-07-29 23:36:02 -07:00
Ken Sanislo fb0e4f3ad9 Tear down lost and closed client connections safely
When a connection dropped, the listener thread deleted all of that
server's controllers immediately, before anything else was notified, so
the GUI, ResourceManager, and plugins kept dereferencing freed
controllers. Clicking Disconnect had the same problem and also ran the
teardown inline on the GUI thread, where UpdateDeviceList's
DEVICE_LIST_UPDATED is a BlockingQueuedConnection back to that thread,
so the device pages were not torn down before the controllers were
freed and a later queued onDetectionEnded dereferenced them.

Neither path frees controllers inline now. NetworkClient moves them to
an orphaned list and a dedicated teardown thread frees them: plugins
are warned first, then the same DETECTION_STARTED, UpdateDeviceList,
DETECTION_COMPLETE sequence a rescan runs tears down the device pages
that still reference the controllers, and only then are they freed, so
a controller always outlives its page. Both the connection-loss path
and the disconnect button queue to that thread, so the blocking handler
runs on the GUI thread while the controllers are still alive, and
StopClient's join stays off the GUI and listener threads.
2026-07-29 23:36:02 -07:00
Adrian Liunardo 09a5b414b6 Add support for iGame GeForce RTX 4070 Ultra W OC-V 2026-07-30 00:28:59 -05:00
Shalev e5dc869c9b Add ASUS TUF GeForce RTX 5060 Ti Gaming OC support 2026-07-29 17:05:21 -05:00
Ken Sanislo 2687ff57aa NetworkClient: initialize client_active and profilemanager_thread
StopClient guards profilemanager_thread and nulls it after the delete,
but nothing ever set it, so destroying a client that never started
read an uninitialized pointer. client_active was unset until
StartClient and is read on the same path.
2026-07-29 11:25:06 -05:00
Ken Sanislo 52138b4cbd NetworkServer: initialize profilemanager_thread
StopServer guards the pointer and nulls it after the delete, but
nothing ever set it, so destroying a server that never started read an
uninitialized pointer.
2026-07-29 11:25:06 -05:00
Ken Sanislo b3a2cccfed NetworkServer: per-client send thread for SignalUpdate notifications
RGBController_UpdateCallback sent the SignalUpdate packet synchronously
via SendRequest_RGBController_SignalUpdate, which runs inside
RGBController::SignalUpdate. A blocking send there (slow client) keeps
SignalCalls > 0, so WaitSignalCalls -- the callback drain that
Unregister/ClearCallbacks/Shutdown run while tearing callbacks down on a
rescan -- waits forever. That is the client/server rescan deadlock.

Each client now owns an outbound queue and a dedicated send thread. The
signal path builds the packet (brief AccessMutex, as before) and queues
it; the client's own thread does the blocking send. A slow client backs
up only its own queue and blocks only its own thread, so the signal path
and the callback drain are never blocked.

UPDATELEDS packets coalesce per controller (a newer frame replaces the
pending one), so a stalled client's queue stays bounded to one packet
per controller instead of growing without bound; non-coalescable events
fall back to a hard cap with drop-oldest. The client destructor stops
the thread with shutdown(SD_BOTH), which unblocks an in-flight send, so
the join at teardown cannot hang. A queue entry owns its packet header
and data buffer; the send thread frees the buffer after the send, so
there is no copy and the packet outlives the controller safely.

Follow-up: the send still takes the shared send_in_progress, so a slow
client can still stall sends to others; routing response sends through
the same per-client path would make that a per-client lock.

net_port: add SD_BOTH (SHUT_RDWR) for the POSIX socket shims.
2026-07-29 11:25:06 -05:00
someone-ua 15a8abf2c9 Detect AppImage path in AutoStart::GetExePath() 2026-07-29 07:20:01 -05:00
Daniel Clark d16b474aa8 Fix X870E Aorus Xtreme AI Top Layout and fix fixed linear zone layout mapping. 2026-07-29 07:15:31 -05:00
Marek Pokropiński 760758230a Initial support for Acer Nitro 16s 2026-07-28 08:38:25 -05:00
Adam Honse 358fc7b007 Implement a response queue to prevent a NetworkClient deadlock when multiple packets are waited on 2026-07-27 23:33:40 -05:00
81 changed files with 5802 additions and 1005 deletions
+15
View File
@@ -119,6 +119,21 @@ bool AutoStart::IsAutoStartEnabled()
std::string AutoStart::GetExePath()
{
/*-----------------------------------------------------*\
| When running from an AppImage, the AppImage runtime |
| sets the APPIMAGE environment variable to the path of |
| the AppImage file itself. /proc/self/exe would |
| instead point at the extracted binary inside the |
| temporary squashfs mount, which disappears once the |
| AppImage is closed, so prefer APPIMAGE when it is set |
\*-----------------------------------------------------*/
const char* appimage_path = getenv("APPIMAGE");
if(appimage_path != NULL)
{
return(std::string(appimage_path));
}
/*-----------------------------------------------------*\
| Create the OpenRGB executable path |
\*-----------------------------------------------------*/
@@ -0,0 +1,199 @@
/*---------------------------------------------------------*\
| ASRockGPUSMBusController.cpp |
| |
| Driver for ASRock GPU RGB controllers |
| |
| Based on SignalRGB ASRock GPU.js plugin |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "ASRockGPUSMBusController.h"
#include "LogManager.h"
#include <chrono>
#include <cstdio>
#include <cstring>
#include <thread>
using namespace std::chrono_literals;
ASRockGPUSMBusController::ASRockGPUSMBusController(i2c_smbus_interface* bus, asrock_gpu_dev_id dev, std::string name)
{
this->bus = bus;
this->dev = dev;
this->device_name = name;
this->num_channels = 0;
memset(enabled_channels, 0, sizeof(enabled_channels));
memset(led_counts, 0, sizeof(led_counts));
}
ASRockGPUSMBusController::~ASRockGPUSMBusController()
{
}
std::string ASRockGPUSMBusController::GetDeviceLocation()
{
std::string return_string(bus->info.device_name);
char addr[5];
snprintf(addr, 5, "0x%02X", dev);
return_string.append(", address ");
return_string.append(addr);
return("I2C: " + return_string);
}
std::string ASRockGPUSMBusController::GetDeviceName()
{
return(device_name);
}
bool ASRockGPUSMBusController::WriteBlock(uint8_t command, uint8_t subcommand, const uint8_t* data, uint8_t len)
{
/*-----------------------------------------------------*\
| SignalRGB packet format |
| [command, 0, subcommand, data...] |
| Uses pure I2C rather than SMBus |
\*-----------------------------------------------------*/
uint8_t packet[12];
memset(packet, 0, sizeof(packet));
packet[0] = command;
packet[1] = 0x00;
packet[2] = subcommand;
if(data != nullptr && len > 0)
{
uint8_t copy_len = (len > 9) ? 9 : len;
memcpy(&packet[3], data, copy_len);
}
int size = 12;
int result = bus->i2c_write_block(dev, size, packet);
if(result < 0)
{
LOG_DEBUG("[ASRock GPU] Write block failed: %d", result);
return(false);
}
return(true);
}
bool ASRockGPUSMBusController::ReadBlock(uint8_t* response, uint8_t len)
{
memset(response, 0, len);
int size = len;
int result = bus->i2c_read_block(dev, &size, response);
if(result < 0)
{
LOG_DEBUG("[ASRock GPU] Read block failed: %d", result);
return(false);
}
return(true);
}
bool ASRockGPUSMBusController::DetectChannels()
{
/*-----------------------------------------------------*\
| Get enabled channels |
| Command 0x14, subcommand 0x01 |
\*-----------------------------------------------------*/
if(!WriteBlock(0x14, 0x01, nullptr, 0))
{
LOG_WARNING("[ASRock GPU] Failed to request enabled channels");
return(false);
}
std::this_thread::sleep_for(20ms);
uint8_t response[I2C_SMBUS_BLOCK_MAX];
if(!ReadBlock(response, sizeof(response)))
{
LOG_WARNING("[ASRock GPU] Failed to read enabled channels");
return(false);
}
LOG_TRACE("[ASRock GPU] Enabled channels response: %02X %02X %02X %02X %02X %02X", response[0], response[1], response[2], response[3], response[4], response[5]);
/*-----------------------------------------------------*\
| Response format |
| Bytes 4-5 contain the channel bitmap |
\*-----------------------------------------------------*/
uint16_t channel_bits = response[4] | (response[5] << 8);
LOG_TRACE("[ASRock GPU] Channel bits: 0x%04X", channel_bits);
/*-----------------------------------------------------*\
| Get LED counts |
| Command 0x14, subcommand 0x02 |
\*-----------------------------------------------------*/
if(!WriteBlock(0x14, 0x02, nullptr, 0))
{
LOG_WARNING("[ASRock GPU] Failed to request LED counts");
return(false);
}
std::this_thread::sleep_for(20ms);
if(!ReadBlock(response, sizeof(response)))
{
LOG_WARNING("[ASRock GPU] Failed to read LED counts");
return(false);
}
num_channels = 0;
for(int i = 0; i < 16; i++)
{
if(channel_bits & (1 << i))
{
enabled_channels[num_channels] = i;
led_counts[num_channels] = response[4 + i];
const char* name = "Unknown";
if(i == ASROCK_GPU_CHANNEL_ARGB_HEADER)
{
name = "ARGB Header";
}
else if(i == ASROCK_GPU_CHANNEL_TOP_SIDE)
{
name = "Top Side";
}
else if(i == ASROCK_GPU_CHANNEL_FAN)
{
name = "Fan";
}
LOG_DEBUG("[ASRock GPU] Channel %d (%s) active with %d LEDs", i, name, response[4 + i]);
num_channels++;
}
}
LOG_DEBUG("[ASRock GPU] Found %d active channels", num_channels);
return(num_channels > 0);
}
void ASRockGPUSMBusController::SetChannelColor(uint8_t channel_idx, uint8_t red, uint8_t green, uint8_t blue)
{
/*-----------------------------------------------------*\
| SignalRGB color packet |
| Command 0x10, subcommand is the channel index |
\*-----------------------------------------------------*/
uint8_t data[8] =
{
0x01, // Mode: direct/static
red,
green,
blue,
0x80, // Brightness
0xFF, // Effect speed
0x00, // Effect direction
0x1A // Unknown constant
};
if(!WriteBlock(0x10, channel_idx, data, sizeof(data)))
{
LOG_WARNING("[ASRock GPU] Failed to set color for channel %d", channel_idx);
}
}
@@ -0,0 +1,55 @@
/*---------------------------------------------------------*\
| ASRockGPUSMBusController.h |
| |
| Driver for ASRock GPU RGB controllers |
| |
| Based on SignalRGB ASRock GPU.js plugin |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include "i2c_smbus.h"
typedef uint8_t asrock_gpu_dev_id;
#define ASROCK_GPU_CONTROLLER_NAME "ASRock GPU SMBus Controller"
#define ASROCK_GPU_SMBUS_ADDRESS 0x36
/*-----------------------------------------------------*\
| ASRock GPU Channel Names (from SignalRGB) |
\*-----------------------------------------------------*/
enum
{
ASROCK_GPU_CHANNEL_ARGB_HEADER = 3,
ASROCK_GPU_CHANNEL_TOP_SIDE = 6,
ASROCK_GPU_CHANNEL_FAN = 7,
};
class ASRockGPUSMBusController
{
public:
ASRockGPUSMBusController(i2c_smbus_interface* bus, asrock_gpu_dev_id dev, std::string name);
~ASRockGPUSMBusController();
std::string GetDeviceLocation();
std::string GetDeviceName();
void SetChannelColor(uint8_t channel_idx, uint8_t red, uint8_t green, uint8_t blue);
bool DetectChannels();
uint8_t enabled_channels[16];
uint8_t led_counts[16];
uint8_t num_channels;
private:
i2c_smbus_interface* bus;
asrock_gpu_dev_id dev;
std::string device_name;
bool WriteBlock(uint8_t command, uint8_t subcommand, const uint8_t* data, uint8_t len);
bool ReadBlock(uint8_t* response, uint8_t len);
};
@@ -0,0 +1,46 @@
/*---------------------------------------------------------*\
| ASRockGPUSMBusControllerDetect.cpp |
| |
| Detector for ASRock GPU RGB controllers |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "DetectionManager.h"
#include "ASRockGPUSMBusController.h"
#include "LogManager.h"
#include "RGBController_ASRockGPUSMBus.h"
#include "i2c_smbus.h"
#include "pci_ids.h"
DetectedControllers DetectASRockGPUSMBusControllers(i2c_smbus_interface* bus, uint8_t address, const std::string& name)
{
DetectedControllers detected_controllers;
LOG_DEBUG("[ASRock GPU] Checking GPU I2C bus for RGB controller at 0x%02X", address);
if(bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE) < 0)
{
LOG_DEBUG("[ASRock GPU] No response at address 0x%02X", address);
return(detected_controllers);
}
LOG_DEBUG("[ASRock GPU] Device responded at 0x%02X, creating controller", address);
ASRockGPUSMBusController* controller = new ASRockGPUSMBusController(bus, address, name);
if(controller->DetectChannels())
{
detected_controllers.push_back(new RGBController_ASRockGPUSMBus(controller));
}
else
{
LOG_WARNING("[ASRock GPU] No active channels found, device not registered");
delete controller;
}
return(detected_controllers);
}
REGISTER_I2C_PCI_DETECTOR("ASRock Radeon RX 9070 XT Steel Legend", DetectASRockGPUSMBusControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, ASROCK_SUB_VEN, 0x5403, ASROCK_GPU_SMBUS_ADDRESS);
@@ -0,0 +1,127 @@
/*---------------------------------------------------------*\
| RGBController_ASRockGPUSMBus.cpp |
| |
| RGBController for ASRock GPU RGB controllers |
| |
| Based on SignalRGB ASRock GPU.js plugin |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_ASRockGPUSMBus.h"
RGBController_ASRockGPUSMBus::RGBController_ASRockGPUSMBus(ASRockGPUSMBusController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "ASRock";
type = DEVICE_TYPE_GPU;
description = "ASRock GPU RGB Controller";
location = controller->GetDeviceLocation();
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();
}
RGBController_ASRockGPUSMBus::~RGBController_ASRockGPUSMBus()
{
delete controller;
}
void RGBController_ASRockGPUSMBus::SetupZones()
{
/*-----------------------------------------------------*\
| Set up zones - one zone per channel |
\*-----------------------------------------------------*/
for(unsigned int i = 0; i < controller->num_channels; i++)
{
uint8_t channel_idx = controller->enabled_channels[i];
zone new_zone;
new_zone.type = ZONE_TYPE_SINGLE;
new_zone.leds_min = 1;
new_zone.leds_max = 1;
new_zone.leds_count = 1;
/*-----------------------------------------------------*\
| Channel names from SignalRGB |
\*-----------------------------------------------------*/
if(channel_idx == ASROCK_GPU_CHANNEL_ARGB_HEADER)
{
new_zone.name = "ARGB Header";
}
else if(channel_idx == ASROCK_GPU_CHANNEL_TOP_SIDE)
{
new_zone.name = "Top Side";
}
else if(channel_idx == ASROCK_GPU_CHANNEL_FAN)
{
new_zone.name = "Fan";
}
else
{
new_zone.name = "Channel " + std::to_string(channel_idx);
}
zones.push_back(new_zone);
/*-----------------------------------------------------*\
| Set up LED for this zone |
\*-----------------------------------------------------*/
led new_led;
new_led.name = new_zone.name;
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_ASRockGPUSMBus::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*-----------------------------------------------------*\
| This device does not support resizing zones |
\*-----------------------------------------------------*/
}
void RGBController_ASRockGPUSMBus::DeviceUpdateLEDs()
{
for(unsigned int i = 0; i < controller->num_channels; i++)
{
uint8_t channel_idx = controller->enabled_channels[i];
RGBColor color = colors[i];
controller->SetChannelColor(channel_idx,
RGBGetRValue(color),
RGBGetGValue(color),
RGBGetBValue(color));
}
}
void RGBController_ASRockGPUSMBus::UpdateZoneLEDs(int zone)
{
uint8_t channel_idx = controller->enabled_channels[zone];
RGBColor color = colors[zone];
controller->SetChannelColor(channel_idx,
RGBGetRValue(color),
RGBGetGValue(color),
RGBGetBValue(color));
}
void RGBController_ASRockGPUSMBus::UpdateSingleLED(int led)
{
UpdateZoneLEDs(led);
}
void RGBController_ASRockGPUSMBus::DeviceUpdateMode()
{
DeviceUpdateLEDs();
}
@@ -0,0 +1,34 @@
/*---------------------------------------------------------*\
| RGBController_ASRockGPUSMBus.h |
| |
| RGBController for ASRock GPU RGB controllers |
| |
| Based on SignalRGB ASRock GPU.js plugin |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "ASRockGPUSMBusController.h"
class RGBController_ASRockGPUSMBus : public RGBController
{
public:
RGBController_ASRockGPUSMBus(ASRockGPUSMBusController* controller_ptr);
~RGBController_ASRockGPUSMBus();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
ASRockGPUSMBusController* controller;
};
@@ -0,0 +1,50 @@
/*---------------------------------------------------------*\
| RGBController_AcerNitroHidKeyboard.cpp |
| |
| Detector for Acer Nitro HID Keyboard |
| |
| Marek Pokropiński Jul 27 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <vector>
#include "DetectionManager.h"
#include "AcerNitroHidKeyboardController.h"
#include "RGBController_AcerNitroHidKeyboard.h"
#include "RGBController_AcerNitroHidLed.h"
/*---------------------------------------------------------*\
| HID vendor and product IDs for ENEK EC in Acer Nitro |
\*---------------------------------------------------------*/
#define ACER_ENEC_VID 0x0CF2
#define ACER_ENEC_PID 0x5130
#define ACER_ENEC_USAGE_PAGE 0xFF5A
#define ACER_ENEC_USAGE 0x01
DetectedControllers DetectAcerHIDControllers(hid_device_info* info, const std::string&)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
/*-------------------------------------------------*\
| Controller is shared between keyboard and onboard |
| led |
\*-------------------------------------------------*/
std::shared_ptr<AcerNitroHidKeyboardController> controller = std::make_shared<AcerNitroHidKeyboardController>(dev, info->path);
RGBController_AcerNitroHidKeyboard* rgb_kb_controller = new RGBController_AcerNitroHidKeyboard(controller);
RGBController_AcerNitroHidLed* rgb_led_controller = new RGBController_AcerNitroHidLed(controller);
detected_controllers.push_back(rgb_kb_controller);
detected_controllers.push_back(rgb_led_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_PU("AcerNitroHidKeyboardController", DetectAcerHIDControllers, ACER_ENEC_VID, ACER_ENEC_PID, ACER_ENEC_USAGE_PAGE, ACER_ENEC_USAGE);
@@ -0,0 +1,80 @@
/*---------------------------------------------------------*\
| RGBController_AcerNitroHidKeyboard.cpp |
| |
| Driver for Acer Nitro Keyboard |
| |
| Marek Pokropiński Jul 27 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "AcerNitroHidKeyboardController.h"
AcerNitroHidKeyboardController::AcerNitroHidKeyboardController(hid_device *dev_handle, const char *path)
{
this->location = path;
this->dev = dev_handle;
device_name = "Acer Nitro HID Keyboard";
}
AcerNitroHidKeyboardController::~AcerNitroHidKeyboardController()
{
if(dev)
{
hid_close(dev);
}
}
std::string AcerNitroHidKeyboardController::GetLocation() const
{
return("HID: " + location);
}
std::string AcerNitroHidKeyboardController::GetDeviceName() const
{
return(device_name);
}
void AcerNitroHidKeyboardController::SetLEDs(const std::vector<RGBColor>& colors, unsigned char brightness, unsigned char device_id)
{
std::lock_guard<std::mutex> guard(device_mutex);
for(unsigned int led_idx = 0; (led_idx < colors.size()) && (led_idx < ACER_HID_NUM_ZONES); led_idx++)
{
/*-------------------------------------------------*\
| Convert colors to RGB format |
\*-------------------------------------------------*/
unsigned char r = RGBGetRValue(colors[led_idx]);
unsigned char g = RGBGetGValue(colors[led_idx]);
unsigned char b = RGBGetBValue(colors[led_idx]);
/*-------------------------------------------------*\
| Send packet |
\*-------------------------------------------------*/
unsigned char zone_select = (unsigned char)(1<<led_idx);
unsigned char packet[ACER_HID_PACKET_SIZE] = {0xA4, device_id, ACER_HID_MODE_STATIC, brightness, 0, 1, r, g, b, zone_select, 0};
hid_send_feature_report(dev, packet, ACER_HID_PACKET_SIZE);
}
}
void AcerNitroHidKeyboardController::SetAllLEDs(const RGBColor &color, unsigned char brightness, unsigned char speed, unsigned char direction, unsigned char mode, unsigned char device_id)
{
std::lock_guard<std::mutex> guard(device_mutex);
/*-----------------------------------------------------*\
| Convert color to RGB format |
\*-----------------------------------------------------*/
unsigned char r = RGBGetRValue(color);
unsigned char g = RGBGetGValue(color);
unsigned char b = RGBGetBValue(color);
/*-----------------------------------------------------*\
| Send packet |
| Zone select = All zones |
\*-----------------------------------------------------*/
unsigned char zone_select = 0x0F;
unsigned char packet[ACER_HID_PACKET_SIZE] = {0xA4, device_id, mode, brightness, speed, direction, r, g, b, zone_select, 0};
hid_send_feature_report(dev, packet, ACER_HID_PACKET_SIZE);
}
@@ -0,0 +1,56 @@
/*---------------------------------------------------------*\
| RGBController_AcerNitroHidKeyboard.cpp |
| |
| Driver for Acer Nitro Keyboard |
| |
| Marek Pokropiński Jul 27 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
enum
{
ACER_HID_MODE_DIRECT = 1,
ACER_HID_MODE_STATIC = 2,
ACER_HID_MODE_BREATHING = 4,
ACER_HID_MODE_NEON = 5,
ACER_HID_MODE_OPMODE = 6,
ACER_HID_MODE_WAVE = 7,
ACER_HID_MODE_SHIFTING = 8,
ACER_HID_MODE_ZOOM = 9,
ACER_HID_MODE_METEOR = 10,
ACER_HID_MODE_TWINKLING = 11,
};
#define ACER_HID_NAME_BUFFER_SIZE 255
#define ACER_HID_NUM_ZONES 4
#define ACER_HID_PACKET_SIZE 11
#include <mutex>
#include <string>
#include <vector>
#include <hidapi.h>
#include "RGBControllerInterface.h"
class AcerNitroHidKeyboardController
{
public:
AcerNitroHidKeyboardController(hid_device *dev_handle, const char *path);
~AcerNitroHidKeyboardController();
std::string GetDeviceName() const;
std::string GetSerial() const;
std::string GetLocation() const;
void SetLEDs(const std::vector<RGBColor> &colors, unsigned char brightness, unsigned char device_id);
void SetAllLEDs(const RGBColor &color, unsigned char brightness, unsigned char speed, unsigned char direction, unsigned char mode, unsigned char device_id);
private:
hid_device *dev;
std::string device_name;
std::string location;
std::mutex device_mutex;
};
@@ -0,0 +1,231 @@
/*---------------------------------------------------------*\
| RGBController_AcerNitroHidKeyboard.cpp |
| |
| RGBController for Acer Nitro HID Keyboard |
| |
| Marek Pokropiński Jul 27 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_AcerNitroHidKeyboard.h"
// /**------------------------------------------------------------------*\
// @name Acer Nitro HID Keyboard
// @category Laptop
// @type HID
// @save :x:
// @direct :white_check_mark:
// @effects :white_check_mark:
// @detectors DetectAcerHIDControllers
// @comment
// \*-------------------------------------------------------------------*/
RGBController_AcerNitroHidKeyboard::RGBController_AcerNitroHidKeyboard(std::shared_ptr<AcerNitroHidKeyboardController> controller)
{
this->controller = controller;
device_id = 0x21;
name = "Acer Nitro HID Keyboard";
vendor = "Acer";
type = DEVICE_TYPE_LAPTOP;
description = "Acer Nitro HID Keyboard Device";
location = controller->GetLocation();
mode Direct;
Direct.name = "Direct";
Direct.value = ACER_HID_MODE_DIRECT;
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 = 100;
Direct.brightness = 100;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = ACER_HID_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.brightness_min = 0;
Static.brightness_max = 100;
Static.brightness = 100;
Static.colors_min = 1;
Static.colors_max = 1;
Static.colors.resize(1);
Static.speed = 0;
modes.push_back(Static);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = ACER_HID_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.brightness_min = 0;
Breathing.brightness_max = 100;
Breathing.brightness = 100;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.speed_min = 1;
Breathing.speed_max = 9;
Breathing.speed = 4;
Breathing.colors.resize(1);
modes.push_back(Breathing);
mode Neon;
Neon.name = "Neon";
Neon.value = ACER_HID_MODE_NEON;
Neon.flags = MODE_FLAG_REQUIRES_ENTIRE_DEVICE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Neon.color_mode = MODE_COLORS_NONE;
Neon.brightness_min = 0;
Neon.brightness_max = 100;
Neon.brightness = 100;
Neon.speed_min = 1;
Neon.speed_max = 9;
Neon.speed = 4;
modes.push_back(Neon);
mode Wave;
Wave.name = "Wave";
Wave.value = ACER_HID_MODE_WAVE;
Wave.flags = MODE_FLAG_REQUIRES_ENTIRE_DEVICE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR;
Wave.color_mode = MODE_COLORS_NONE;
Wave.brightness_min = 0;
Wave.brightness_max = 100;
Wave.brightness = 100;
Wave.speed_min = 1;
Wave.speed_max = 9;
Wave.speed = 4;
Wave.direction = 0;
modes.push_back(Wave);
mode Shifting;
Shifting.name = "Shifting";
Shifting.value = ACER_HID_MODE_SHIFTING;
Shifting.flags = MODE_FLAG_REQUIRES_ENTIRE_DEVICE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Shifting.color_mode = MODE_COLORS_NONE;
Shifting.brightness_min = 0;
Shifting.brightness_max = 100;
Shifting.brightness = 100;
Shifting.speed_min = 1;
Shifting.speed_max = 9;
Shifting.speed = 4;
modes.push_back(Shifting);
mode Zoom;
Zoom.name = "Zoom";
Zoom.value = ACER_HID_MODE_ZOOM;
Zoom.flags = MODE_FLAG_REQUIRES_ENTIRE_DEVICE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Zoom.color_mode = MODE_COLORS_NONE;
Zoom.brightness_min = 0;
Zoom.brightness_max = 100;
Zoom.brightness = 100;
Zoom.speed_min = 1;
Zoom.speed_max = 9;
Zoom.speed = 4;
modes.push_back(Zoom);
mode Meteor;
Meteor.name = "Meteor";
Meteor.value = ACER_HID_MODE_METEOR;
Meteor.flags = MODE_FLAG_REQUIRES_ENTIRE_DEVICE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Meteor.color_mode = MODE_COLORS_NONE;
Meteor.brightness_min = 0;
Meteor.brightness_max = 100;
Meteor.brightness = 100;
Meteor.speed_min = 1;
Meteor.speed_max = 9;
Meteor.speed = 4;
modes.push_back(Meteor);
mode Twinkling;
Twinkling.name = "Twinkling";
Twinkling.value = ACER_HID_MODE_TWINKLING;
Twinkling.flags = MODE_FLAG_REQUIRES_ENTIRE_DEVICE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Twinkling.color_mode = MODE_COLORS_NONE;
Twinkling.brightness_min = 0;
Twinkling.brightness_max = 100;
Twinkling.brightness = 100;
Twinkling.colors_min = 1;
Twinkling.colors_max = 1;
Twinkling.speed_min = 1;
Twinkling.speed_max = 9;
Twinkling.speed = 4;
modes.push_back(Twinkling);
SetupZones();
}
RGBController_AcerNitroHidKeyboard::~RGBController_AcerNitroHidKeyboard()
{
Shutdown();
}
void RGBController_AcerNitroHidKeyboard::SetupZones()
{
zone* new_zone = new zone;
new_zone->name = "Keyboard Backlight Zone";
new_zone->type = ZONE_TYPE_LINEAR;
new_zone->leds_min = 4;
new_zone->leds_max = 4;
new_zone->leds_count = 4;
zones.push_back(*new_zone);
for(std::size_t zone_idx = 0; zone_idx < ACER_HID_NUM_ZONES; zone_idx++)
{
led* new_led = new led();
new_led->name = "Keyboard Backlight Zone ";
new_led->name.append(std::to_string(zone_idx + 1));
new_led->value = (unsigned int)leds.size();
leds.push_back(*new_led);
}
SetupColors();
}
void RGBController_AcerNitroHidKeyboard::DeviceUpdateLEDs()
{
mode& current_mode = modes[active_mode];
if(current_mode.value == ACER_HID_MODE_DIRECT)
{
controller->SetLEDs(colors, current_mode.brightness, device_id);
}
else
{
RGBColor color = current_mode.colors.size()==0? (RGBColor)0 : current_mode.colors[0];
unsigned char direction = 1;
if((modes[active_mode].flags & MODE_FLAG_HAS_DIRECTION_LR) && modes[active_mode].direction)
{
direction = 2;
}
controller->SetAllLEDs(color, current_mode.brightness, current_mode.speed, direction, current_mode.value, device_id);
}
}
void RGBController_AcerNitroHidKeyboard::DeviceUpdateZoneLEDs(int)
{
DeviceUpdateLEDs();
}
void RGBController_AcerNitroHidKeyboard::DeviceUpdateSingleLED(int)
{
DeviceUpdateLEDs();
}
void RGBController_AcerNitroHidKeyboard::DeviceUpdateMode()
{
DeviceUpdateLEDs();
}
@@ -0,0 +1,33 @@
/*---------------------------------------------------------*\
| RGBController_AcerNitroHidKeyboard.cpp |
| |
| RGBController for Acer Nitro HID Keyboard |
| |
| Marek Pokropiński Jul 27 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "AcerNitroHidKeyboardController.h"
class RGBController_AcerNitroHidKeyboard : public RGBController
{
public:
RGBController_AcerNitroHidKeyboard(std::shared_ptr<AcerNitroHidKeyboardController> controller);
~RGBController_AcerNitroHidKeyboard();
void SetupZones();
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
private:
std::shared_ptr<AcerNitroHidKeyboardController> controller;
unsigned int device_id;
};
@@ -0,0 +1,152 @@
/*---------------------------------------------------------*\
| RGBController_AcerNitroHidLed.cpp |
| |
| RGBController for Acer Nitro HID LED |
| |
| Marek Pokropiński Jul 27 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_AcerNitroHidLed.h"
// /**------------------------------------------------------------------*\
// @name Acer Nitro HID Led
// @category Laptop
// @type HID
// @save :x:
// @direct :white_check_mark:
// @effects :white_check_mark:
// @detectors DetectAcerHIDControllers
// @comment
// \*-------------------------------------------------------------------*/
RGBController_AcerNitroHidLed::RGBController_AcerNitroHidLed(std::shared_ptr<AcerNitroHidKeyboardController> controller)
{
this->controller = controller;
device_id = 0x65;
name = "Acer Nitro HID LED Device";
vendor = "Acer";
type = DEVICE_TYPE_LIGHT;
description = "Acer Nitro HID LED Device";
location = controller->GetLocation();
mode Direct;
Direct.name = "Direct";
Direct.value = ACER_HID_MODE_DIRECT;
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 = 100;
Direct.brightness = 100;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = ACER_HID_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.brightness_min = 0;
Static.brightness_max = 100;
Static.brightness = 100;
Static.colors_min = 1;
Static.colors_max = 1;
Static.colors.resize(1);
Static.speed = 0;
modes.push_back(Static);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = ACER_HID_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.brightness_min = 0;
Breathing.brightness_max = 100;
Breathing.brightness = 100;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.speed_min = 1;
Breathing.speed_max = 5;
Breathing.speed = 4;
Breathing.colors.resize(1);
modes.push_back(Breathing);
mode Neon;
Neon.name = "Neon";
Neon.value = ACER_HID_MODE_NEON;
Neon.flags = MODE_FLAG_REQUIRES_ENTIRE_DEVICE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Neon.color_mode = MODE_COLORS_NONE;
Neon.brightness_min = 0;
Neon.brightness_max = 100;
Neon.brightness = 100;
Neon.speed_min = 1;
Neon.speed_max = 5;
Neon.speed = 4;
modes.push_back(Neon);
SetupZones();
}
RGBController_AcerNitroHidLed::~RGBController_AcerNitroHidLed()
{
Shutdown();
}
void RGBController_AcerNitroHidLed::SetupZones()
{
zone* new_zone = new zone;
new_zone->name = "LED";
new_zone->type = ZONE_TYPE_LINEAR;
new_zone->leds_min = 1;
new_zone->leds_max = 1;
new_zone->leds_count = 1;
zones.push_back(*new_zone);
led* new_led = new led();
new_led->name = "LED";
new_led->value = (unsigned int)leds.size();
leds.push_back(*new_led);
SetupColors();
}
void RGBController_AcerNitroHidLed::DeviceUpdateLEDs()
{
mode& current_mode = modes[active_mode];
if(current_mode.value == ACER_HID_MODE_DIRECT)
{
controller->SetLEDs(colors, current_mode.brightness, device_id);
}
else
{
RGBColor color = current_mode.colors.size()==0? (RGBColor)0 : current_mode.colors[0];
unsigned char direction = 1;
if((modes[active_mode].flags & MODE_FLAG_HAS_DIRECTION_LR) && modes[active_mode].direction)
{
direction = 2;
}
controller->SetAllLEDs(color, current_mode.brightness, current_mode.speed, direction, current_mode.value, device_id);
}
}
void RGBController_AcerNitroHidLed::DeviceUpdateZoneLEDs(int)
{
DeviceUpdateLEDs();
}
void RGBController_AcerNitroHidLed::DeviceUpdateSingleLED(int)
{
DeviceUpdateLEDs();
}
void RGBController_AcerNitroHidLed::DeviceUpdateMode()
{
DeviceUpdateLEDs();
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_AcerNitroHidLed.cpp |
| |
| RGBController for Acer Nitro HID LED |
| |
| Marek Pokropiński Jul 27 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "AcerNitroHidKeyboardController.h"
#include "RGBController_AcerNitroHidKeyboard.h"
class RGBController_AcerNitroHidLed : public RGBController
{
public:
RGBController_AcerNitroHidLed(std::shared_ptr<AcerNitroHidKeyboardController> controller);
~RGBController_AcerNitroHidLed();
void SetupZones();
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
private:
std::shared_ptr<AcerNitroHidKeyboardController> controller;
unsigned int device_id;
};
@@ -1,22 +1,25 @@
/*---------------------------------------------------------*\
| ClevoKeyboardController.cpp |
| |
| Driver for Clevo laptop per-key RGB keyboard (ITE 8291) |
| Protocol based on tuxedo-drivers ite_8291 module |
| Driver for Clevo per-key RGB keyboard |
| Supports ITE 8291 (PID 0x600B) and ITE 829x (0x8910) |
| |
| Kyle Cascade ([email protected]) 16 Jan 2026 |
| Valentin Lobstein ([email protected]) 27 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <algorithm>
#include <cstring>
#include "ClevoKeyboardController.h"
#include "StringUtils.h"
ClevoKeyboardController::ClevoKeyboardController(hid_device* dev_handle, const hid_device_info& info)
ClevoKeyboardController::ClevoKeyboardController(hid_device* dev_handle, const hid_device_info& info, clevo_keyboard_type kb_type)
{
dev = dev_handle;
type = kb_type;
location = info.path;
version = info.release_number;
}
@@ -26,6 +29,31 @@ ClevoKeyboardController::~ClevoKeyboardController()
hid_close(dev);
}
clevo_keyboard_type ClevoKeyboardController::GetType()
{
return type;
}
int ClevoKeyboardController::GetNumRows()
{
return(type == CLEVO_KB_ITE829X) ? CLEVO_829X_NUM_ROWS : CLEVO_8291_NUM_ROWS;
}
int ClevoKeyboardController::GetNumCols()
{
return(type == CLEVO_KB_ITE829X) ? CLEVO_829X_NUM_COLS : CLEVO_8291_NUM_COLS;
}
int ClevoKeyboardController::GetNumLEDs()
{
return GetNumRows() * GetNumCols();
}
unsigned char ClevoKeyboardController::GetBrightnessMax()
{
return(type == CLEVO_KB_ITE829X) ? CLEVO_829X_BRIGHTNESS_MAX : CLEVO_8291_BRIGHTNESS_MAX;
}
std::string ClevoKeyboardController::GetDeviceLocation()
{
return("HID: " + location);
@@ -51,147 +79,355 @@ std::string ClevoKeyboardController::GetFirmwareVersion()
return(version_string);
}
void ClevoKeyboardController::WriteControl(unsigned char* data)
/*---------------------------------------------------------*\
| ITE 8291: 8-byte feature report, no report ID |
\*---------------------------------------------------------*/
void ClevoKeyboardController::WriteControl8291(unsigned char* data)
{
hid_send_feature_report(dev, data, CLEVO_KEYBOARD_REPORT_SIZE);
hid_send_feature_report(dev, data, CLEVO_8291_REPORT_SIZE);
}
void ClevoKeyboardController::WriteRowData(unsigned char* data)
{
hid_write(dev, data, CLEVO_KEYBOARD_ROW_DATA_SIZE);
hid_write(dev, data, CLEVO_8291_ROW_DATA_SIZE);
}
/*---------------------------------------------------------*\
| ITE 829x: 6-byte feature report with report ID 0xCC |
\*---------------------------------------------------------*/
void ClevoKeyboardController::SendCommand829x(unsigned char cmd, unsigned char d0, unsigned char d1, unsigned char d2, unsigned char d3)
{
unsigned char buf[CLEVO_829X_REPORT_SIZE];
buf[0] = CLEVO_829X_REPORT_ID;
buf[1] = cmd;
buf[2] = d0;
buf[3] = d1;
buf[4] = d2;
buf[5] = d3;
hid_send_feature_report(dev, buf, CLEVO_829X_REPORT_SIZE);
}
void ClevoKeyboardController::TurnOff()
{
/*---------------------------------------------------------*\
| Turn off: 08 01 00 00 00 00 00 00 |
\*---------------------------------------------------------*/
unsigned char buf[CLEVO_KEYBOARD_REPORT_SIZE];
if(type == CLEVO_KB_ITE829X)
{
SendCommand829x(CLEVO_829X_CMD_BRIGHTNESS);
return;
}
memset(buf, 0x00, CLEVO_KEYBOARD_REPORT_SIZE);
buf[0] = 0x08;
buf[1] = 0x01;
WriteControl(buf);
unsigned char buf[CLEVO_8291_REPORT_SIZE];
memset(buf, 0x00, CLEVO_8291_REPORT_SIZE);
buf[0] = CLEVO_8291_CMD_MODE;
buf[1] = CLEVO_8291_POWER_OFF;
WriteControl8291(buf);
}
void ClevoKeyboardController::SetMode(unsigned char mode, unsigned char brightness, unsigned char speed, unsigned char behaviour)
{
/*---------------------------------------------------------*\
| Set params: 08 [power] [mode] [speed] [brightness] 08 |
| [behaviour] 00 |
| power: 01=off, 02=on |
\*---------------------------------------------------------*/
unsigned char buf[CLEVO_KEYBOARD_REPORT_SIZE];
if(type == CLEVO_KB_ITE829X)
{
SendCommand829x(CLEVO_829X_CMD_BRIGHTNESS, brightness, speed);
return;
}
memset(buf, 0x00, CLEVO_KEYBOARD_REPORT_SIZE);
buf[0] = 0x08;
buf[1] = 0x02; // Power on
unsigned char buf[CLEVO_8291_REPORT_SIZE];
memset(buf, 0x00, CLEVO_8291_REPORT_SIZE);
buf[0] = CLEVO_8291_CMD_MODE;
buf[1] = CLEVO_8291_POWER_ON;
buf[2] = mode;
buf[3] = speed;
buf[4] = brightness;
buf[5] = 0x08;
buf[5] = CLEVO_8291_MODE_FLAG;
buf[6] = behaviour;
WriteControl(buf);
WriteControl8291(buf);
}
void ClevoKeyboardController::SetModeColor(unsigned char color_idx, unsigned char red, unsigned char green, unsigned char blue)
void ClevoKeyboardController::SetModeColor(unsigned char color_idx, RGBColor color)
{
/*---------------------------------------------------------*\
| Set color define: 14 00 [index] R G B 00 00 |
| index: 1-7 for built-in effects |
\*---------------------------------------------------------*/
unsigned char buf[CLEVO_KEYBOARD_REPORT_SIZE];
memset(buf, 0x00, CLEVO_KEYBOARD_REPORT_SIZE);
buf[0] = 0x14;
buf[1] = 0x00;
buf[2] = color_idx;
buf[3] = red;
buf[4] = green;
buf[5] = blue;
WriteControl(buf);
}
void ClevoKeyboardController::SendColors(unsigned char* color_data, unsigned char brightness)
{
/*---------------------------------------------------------*\
| Per-key RGB mode (mode 0x33) |
| 1. Set params with mode 0x33 and brightness |
| 2. For each row 0-5: |
| - Announce row: 16 00 [row] 00 00 00 00 00 |
| - Send 65 bytes row data via output report |
| |
| Row data format (65 bytes): |
| [0x00 padding][0x00 padding] |
| [B0..B20][G0..G20][R0..R20] |
\*---------------------------------------------------------*/
unsigned char ctrl_buf[CLEVO_KEYBOARD_REPORT_SIZE];
unsigned char row_buf[CLEVO_KEYBOARD_ROW_DATA_SIZE];
/*---------------------------------------------------------*\
| Clamp brightness |
\*---------------------------------------------------------*/
if(brightness > CLEVO_KEYBOARD_BRIGHTNESS_MAX)
if(type == CLEVO_KB_ITE829X)
{
brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
return;
}
unsigned char buf[CLEVO_8291_REPORT_SIZE];
memset(buf, 0x00, CLEVO_8291_REPORT_SIZE);
buf[0] = CLEVO_8291_CMD_COLOR;
buf[2] = color_idx;
buf[3] = RGBGetRValue(color);
buf[4] = RGBGetGValue(color);
buf[5] = RGBGetBValue(color);
WriteControl8291(buf);
}
void ClevoKeyboardController::SetBrightnessSpeed829x(unsigned char brightness, unsigned char speed)
{
if(brightness > CLEVO_829X_BRIGHTNESS_MAX)
{
brightness = CLEVO_829X_BRIGHTNESS_MAX;
}
if(brightness != current_brightness_829x || speed != current_speed_829x)
{
current_brightness_829x = brightness;
current_speed_829x = speed;
SendCommand829x(CLEVO_829X_CMD_BRIGHTNESS, brightness, speed);
}
}
void ClevoKeyboardController::SendColorSlot829x(unsigned char cmd, unsigned char slot, RGBColor color)
{
SendCommand829x(cmd, slot, RGBGetRValue(color), RGBGetGValue(color), RGBGetBValue(color));
}
/*---------------------------------------------------------*\
| ITE 829x: Save/Clear BIOS keyboard state |
\*---------------------------------------------------------*/
void ClevoKeyboardController::SaveToBios829x()
{
SendCommand829x(CLEVO_829X_CMD_SAVE_BIOS, 0x01);
}
void ClevoKeyboardController::ClearBiosSave829x()
{
SendCommand829x(CLEVO_829X_CMD_SAVE_BIOS, 0x00);
}
void ClevoKeyboardController::StopAnimation829x()
{
SendCommand829x(CLEVO_829X_CMD_ANIM, CLEVO_829X_CMD_STOP_ANIM);
}
void ClevoKeyboardController::SetEffect829x(unsigned char mode_value, const std::vector<RGBColor>& colors, unsigned int direction, unsigned int color_mode)
{
bool mode_changed = (mode_value != current_mode_829x);
current_mode_829x = mode_value;
bool use_custom_color = (color_mode != MODE_COLORS_RANDOM) && !colors.empty();
/*---------------------------------------------------------*\
| Animation modes: send [CC, 00, mode_id] on mode change |
\*---------------------------------------------------------*/
struct anim_entry { unsigned char mode; unsigned char anim_id; };
static const anim_entry anim_modes[] =
{
{ CLEVO_829X_MODE_WAVE, 0x04 },
{ CLEVO_829X_MODE_RANDOM, 0x09 },
{ CLEVO_829X_MODE_SCAN, 0x0A },
{ CLEVO_829X_MODE_SNAKE, 0x0B },
{ CLEVO_829X_MODE_RAINBOW_CYCLE, 0x02 },
{ CLEVO_829X_MODE_RAINBOW_GRAD, 0x04 },
};
const anim_entry* anim_it = std::find_if(std::begin(anim_modes), std::end(anim_modes),
[mode_value](const anim_entry& e) { return e.mode == mode_value; });
if(anim_it != std::end(anim_modes) && mode_changed)
{
SendCommand829x(CLEVO_829X_CMD_ANIM, anim_it->anim_id);
}
/*---------------------------------------------------------*\
| Set params for per-key mode |
| Direction-based effects (Wave/Snake) |
| Slot = base + direction_index |
| Custom color: base = 0xA1, Preset: base = 0x71 |
\*---------------------------------------------------------*/
memset(ctrl_buf, 0x00, CLEVO_KEYBOARD_REPORT_SIZE);
ctrl_buf[0] = 0x08;
ctrl_buf[1] = 0x02; // Power on
ctrl_buf[2] = CLEVO_KEYBOARD_MODE_DIRECT; // Per-key mode
ctrl_buf[3] = 0x00; // Speed (unused)
ctrl_buf[4] = brightness;
ctrl_buf[5] = 0x00;
ctrl_buf[6] = 0x00;
/*---------------------------------------------------------*\
| Map OpenRGB direction enum to firmware slot index |
\*---------------------------------------------------------*/
static const unsigned char dir_to_wave_idx[] =
{
6, /* MODE_DIRECTION_LEFT */
7, /* MODE_DIRECTION_RIGHT */
4, /* MODE_DIRECTION_UP */
5, /* MODE_DIRECTION_DOWN */
6, /* MODE_DIRECTION_HORIZONTAL */
4, /* MODE_DIRECTION_VERTICAL */
0, /* MODE_DIRECTION_UP_LEFT */
1, /* MODE_DIRECTION_UP_RIGHT */
2, /* MODE_DIRECTION_DOWN_LEFT */
3, /* MODE_DIRECTION_DOWN_RIGHT */
};
WriteControl(ctrl_buf);
static const unsigned char dir_to_snake_idx[] =
{
0, /* MODE_DIRECTION_LEFT */
0, /* MODE_DIRECTION_RIGHT */
0, /* MODE_DIRECTION_UP */
0, /* MODE_DIRECTION_DOWN */
0, /* MODE_DIRECTION_HORIZONTAL */
0, /* MODE_DIRECTION_VERTICAL */
0, /* MODE_DIRECTION_UP_LEFT */
1, /* MODE_DIRECTION_UP_RIGHT */
2, /* MODE_DIRECTION_DOWN_LEFT */
3, /* MODE_DIRECTION_DOWN_RIGHT */
};
if((mode_value == CLEVO_829X_MODE_WAVE || mode_value == CLEVO_829X_MODE_RAINBOW_GRAD) && direction < sizeof(dir_to_wave_idx))
{
unsigned char base = use_custom_color ? CLEVO_829X_COLOR_SLOT_BASE : CLEVO_829X_PRESET_SLOT_BASE;
unsigned char idx = dir_to_wave_idx[direction];
unsigned char slot = base + idx;
if(use_custom_color)
{
SendColorSlot829x(CLEVO_829X_CMD_WAVE_COLOR, slot, colors[0]);
}
if(!use_custom_color)
{
SendCommand829x(CLEVO_829X_CMD_WAVE_COLOR, slot);
}
}
if(mode_value == CLEVO_829X_MODE_SNAKE && direction < sizeof(dir_to_snake_idx))
{
unsigned char base = use_custom_color ? CLEVO_829X_COLOR_SLOT_BASE : CLEVO_829X_PRESET_SLOT_BASE;
unsigned char idx = dir_to_snake_idx[direction];
unsigned char slot = base + idx;
if(use_custom_color)
{
SendColorSlot829x(CLEVO_829X_CMD_SNAKE_COLOR, slot, colors[0]);
}
if(!use_custom_color)
{
SendCommand829x(CLEVO_829X_CMD_SNAKE_COLOR, slot);
}
}
/*---------------------------------------------------------*\
| Send each row |
| Breathing/Flashing: random or custom color |
\*---------------------------------------------------------*/
for(int row = 0; row < CLEVO_KEYBOARD_NUM_ROWS; row++)
if(mode_value == CLEVO_829X_MODE_BREATH && use_custom_color)
{
/*-----------------------------------------------------*\
| Announce row data |
\*-----------------------------------------------------*/
memset(ctrl_buf, 0x00, CLEVO_KEYBOARD_REPORT_SIZE);
ctrl_buf[0] = 0x16;
ctrl_buf[1] = 0x00;
ctrl_buf[2] = row;
SendColorSlot829x(0x0A, CLEVO_829X_COLOR_CUSTOM, colors[0]);
}
WriteControl(ctrl_buf);
if(mode_value == CLEVO_829X_MODE_BREATH && !use_custom_color)
{
SendCommand829x(0x0A);
}
/*-----------------------------------------------------*\
| Build row data buffer |
| Format: [pad][pad][B0..B20][G0..G20][R0..R20] |
\*-----------------------------------------------------*/
memset(row_buf, 0x00, CLEVO_KEYBOARD_ROW_DATA_SIZE);
if(mode_value == CLEVO_829X_MODE_BLINK && use_custom_color)
{
SendColorSlot829x(0x0B, CLEVO_829X_COLOR_CUSTOM, colors[0]);
}
for(int col = 0; col < CLEVO_KEYBOARD_NUM_COLS; col++)
if(mode_value == CLEVO_829X_MODE_BLINK && !use_custom_color)
{
SendCommand829x(0x0B);
}
/*---------------------------------------------------------*\
| Random: custom color only (multicolor is the default) |
\*---------------------------------------------------------*/
if(mode_value == CLEVO_829X_MODE_RANDOM && use_custom_color)
{
SendColorSlot829x(CLEVO_829X_CMD_RANDOM_COLOR, CLEVO_829X_COLOR_SLOT_BASE, colors[0]);
}
/*---------------------------------------------------------*\
| Scan: 2 custom color bands |
\*---------------------------------------------------------*/
if(mode_value == CLEVO_829X_MODE_SCAN)
{
for(size_t i = 0; i < colors.size() && i < 2; i++)
{
int led_idx = (row * CLEVO_KEYBOARD_NUM_COLS) + col;
int color_offset = led_idx * 3;
SendColorSlot829x(CLEVO_829X_CMD_SCAN_COLOR, CLEVO_829X_COLOR_SLOT_BASE + i, colors[i]);
}
}
unsigned char red = color_data[color_offset + 0];
unsigned char green = color_data[color_offset + 1];
unsigned char blue = color_data[color_offset + 2];
/*---------------------------------------------------------*\
| Fn Highlight: per-key color on function keys |
\*---------------------------------------------------------*/
if(mode_value == CLEVO_829X_MODE_FN_COLOR && !colors.empty())
{
static const unsigned char fn_keys[] =
{
0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0A, 0x0B, 0x0C, 0x21, 0x23, 0x31, 0x32, 0x33,
0x53, 0x73, 0xA2
};
/*-------------------------------------------------*\
| Row data layout (after 2-byte padding): |
| Bytes 2-22: Blue values for columns 0-20 |
| Bytes 23-43: Green values for columns 0-20 |
| Bytes 44-64: Red values for columns 0-20 |
\*-------------------------------------------------*/
row_buf[2 + col] = blue;
row_buf[2 + CLEVO_KEYBOARD_NUM_COLS + col] = green;
row_buf[2 + CLEVO_KEYBOARD_NUM_COLS*2 + col] = red;
SendCommand829x(CLEVO_829X_CMD_ANIM, CLEVO_829X_CMD_CLEAR);
for(unsigned char key : fn_keys)
{
SendCommand829x(CLEVO_829X_CMD_SET_LED, key,
RGBGetRValue(colors[0]),
RGBGetGValue(colors[0]),
RGBGetBValue(colors[0]));
}
}
}
void ClevoKeyboardController::SendColors(const unsigned char* color_data, unsigned char brightness, unsigned char speed)
{
int num_rows = GetNumRows();
int num_cols = GetNumCols();
if(type == CLEVO_KB_ITE829X)
{
if(brightness > CLEVO_829X_BRIGHTNESS_MAX)
{
brightness = CLEVO_829X_BRIGHTNESS_MAX;
}
SendCommand829x(CLEVO_829X_CMD_BRIGHTNESS, brightness, speed);
for(int row = 0; row < num_rows; row++)
{
for(int col = 0; col < num_cols; col++)
{
int idx = (row * num_cols + col) * 3;
SendCommand829x(CLEVO_829X_CMD_SET_LED,
CLEVO_829X_LED_ID(row, col),
color_data[idx + 0],
color_data[idx + 1],
color_data[idx + 2]);
}
}
return;
}
/*---------------------------------------------------------*\
| ITE 8291: row-based color transfer |
\*---------------------------------------------------------*/
unsigned char ctrl_buf[CLEVO_8291_REPORT_SIZE];
unsigned char row_buf[CLEVO_8291_ROW_DATA_SIZE];
if(brightness > CLEVO_8291_BRIGHTNESS_MAX)
{
brightness = CLEVO_8291_BRIGHTNESS_MAX;
}
memset(ctrl_buf, 0x00, CLEVO_8291_REPORT_SIZE);
ctrl_buf[0] = CLEVO_8291_CMD_MODE;
ctrl_buf[1] = CLEVO_8291_POWER_ON;
ctrl_buf[2] = CLEVO_KEYBOARD_MODE_DIRECT;
ctrl_buf[4] = brightness;
WriteControl8291(ctrl_buf);
for(int row = 0; row < num_rows; row++)
{
memset(ctrl_buf, 0x00, CLEVO_8291_REPORT_SIZE);
ctrl_buf[0] = CLEVO_8291_CMD_ROW;
ctrl_buf[2] = row;
WriteControl8291(ctrl_buf);
memset(row_buf, 0x00, CLEVO_8291_ROW_DATA_SIZE);
for(int col = 0; col < num_cols; col++)
{
int idx = (row * num_cols + col) * 3;
row_buf[2 + col] = color_data[idx + 2];
row_buf[2 + num_cols + col] = color_data[idx + 1];
row_buf[2 + num_cols * 2 + col] = color_data[idx + 0];
}
WriteRowData(row_buf);
@@ -1,9 +1,11 @@
/*---------------------------------------------------------*\
| ClevoKeyboardController.h |
| |
| Driver for Clevo laptop per-key RGB keyboard (ITE 8291) |
| Driver for Clevo per-key RGB keyboard |
| Supports ITE 8291 (PID 0x600B) and ITE 829x (0x8910) |
| |
| Kyle Cascade ([email protected]) 16 Jan 2026 |
| Valentin Lobstein ([email protected]) 27 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -12,27 +14,75 @@
#pragma once
#include <string>
#include <vector>
#include <hidapi.h>
#include "RGBController.h"
/*-----------------------------------------------------*\
| ITE 8291 keyboard defines |
\*-----------------------------------------------------*/
#define CLEVO_KEYBOARD_REPORT_SIZE 8
#define CLEVO_KEYBOARD_ROW_DATA_SIZE 65
/*---------------------------------------------------------*\
| Device type enumeration |
\*---------------------------------------------------------*/
enum clevo_keyboard_type
{
CLEVO_KB_ITE8291,
CLEVO_KB_ITE829X,
};
#define CLEVO_KEYBOARD_NUM_ROWS 6
#define CLEVO_KEYBOARD_NUM_COLS 21
#define CLEVO_KEYBOARD_NUM_LEDS (CLEVO_KEYBOARD_NUM_ROWS * CLEVO_KEYBOARD_NUM_COLS)
/*---------------------------------------------------------*\
| ITE 8291 keyboard defines (PID 0x600B) |
| 8 bytes, no report ID, row-based color data |
\*---------------------------------------------------------*/
#define CLEVO_8291_REPORT_SIZE 8
#define CLEVO_8291_ROW_DATA_SIZE 65
#define CLEVO_8291_NUM_ROWS 6
#define CLEVO_8291_NUM_COLS 21
#define CLEVO_8291_NUM_LEDS (CLEVO_8291_NUM_ROWS * CLEVO_8291_NUM_COLS)
#define CLEVO_8291_BRIGHTNESS_MAX 0x32
#define CLEVO_8291_CMD_MODE 0x08
#define CLEVO_8291_CMD_COLOR 0x14
#define CLEVO_8291_CMD_ROW 0x16
#define CLEVO_8291_POWER_OFF 0x01
#define CLEVO_8291_POWER_ON 0x02
#define CLEVO_8291_MODE_FLAG 0x08
/*---------------------------------------------------------*\
| ITE 829x keyboard defines (PID 0x8910) |
| 6 bytes with report ID 0xCC, per-key color commands |
| Protocol: [0xCC, cmd, d0, d1, d2, d3] |
| LED ID: ((row & 0x07) << 5) | (col & 0x1F) |
\*---------------------------------------------------------*/
#define CLEVO_829X_REPORT_ID 0xCC
#define CLEVO_829X_REPORT_SIZE 6
#define CLEVO_829X_NUM_ROWS 6
#define CLEVO_829X_NUM_COLS 20
#define CLEVO_829X_NUM_LEDS (CLEVO_829X_NUM_ROWS * CLEVO_829X_NUM_COLS)
#define CLEVO_829X_BRIGHTNESS_MAX 0x0A
#define CLEVO_829X_LED_ID(row, col) (unsigned char)((((row) & 0x07) << 5) | ((col) & 0x1F))
#define CLEVO_829X_CMD_SET_LED 0x01
#define CLEVO_829X_CMD_BRIGHTNESS 0x09
#define CLEVO_829X_CMD_ANIM 0x00
#define CLEVO_829X_CMD_CLEAR 0x0C
#define CLEVO_829X_CMD_WAVE_COLOR 0x15
#define CLEVO_829X_CMD_SNAKE_COLOR 0x16
#define CLEVO_829X_CMD_SCAN_COLOR 0x17
#define CLEVO_829X_CMD_RANDOM_COLOR 0x18
#define CLEVO_829X_COLOR_CUSTOM 0xAA
#define CLEVO_829X_COLOR_SLOT_BASE 0xA1
#define CLEVO_829X_PRESET_SLOT_BASE 0x71
#define CLEVO_829X_CMD_SAVE_BIOS 0x20
#define CLEVO_829X_CMD_STOP_ANIM 0x03
/*---------------------------------------------------------*\
| Shared defines |
\*---------------------------------------------------------*/
#define CLEVO_KEYBOARD_BRIGHTNESS_MIN 0x00
#define CLEVO_KEYBOARD_BRIGHTNESS_MAX 0x32
#define CLEVO_KEYBOARD_SPEED_MIN 0x01
#define CLEVO_KEYBOARD_SPEED_MIN 0x00
#define CLEVO_KEYBOARD_SPEED_MAX 0x0A
#define CLEVO_KEYBOARD_SPEED_DEFAULT 0x00
/*-----------------------------------------------------*\
| ITE 8291 modes |
\*-----------------------------------------------------*/
/*---------------------------------------------------------*\
| ITE 8291 modes |
\*---------------------------------------------------------*/
enum
{
CLEVO_KEYBOARD_MODE_DIRECT = 0x33,
@@ -47,9 +97,36 @@ enum
CLEVO_KEYBOARD_MODE_SPARK = 0x11,
};
/*-----------------------------------------------------*\
| Wave/reactive behaviour |
\*-----------------------------------------------------*/
/*---------------------------------------------------------*\
| ITE 829x firmware modes |
| Two command styles: |
| cmd=0x00, d0=mode_id (animation select) |
| cmd=mode_id, d0=0x00 (standalone effects) |
\*---------------------------------------------------------*/
enum
{
CLEVO_829X_MODE_DIRECT = 0xFD,
CLEVO_829X_MODE_WAVE = 0xA0,
CLEVO_829X_MODE_WAVE_COLOR = 0xB0,
CLEVO_829X_MODE_BREATH = 0xA1,
CLEVO_829X_MODE_BREATH_COLOR = 0xB1,
CLEVO_829X_MODE_BLINK = 0xA2,
CLEVO_829X_MODE_BLINK_COLOR = 0xB2,
CLEVO_829X_MODE_RANDOM = 0xA3,
CLEVO_829X_MODE_RANDOM_COLOR = 0xA9,
CLEVO_829X_MODE_SCAN = 0xA4,
/* Ripple (0x07) requires Insyde BIOS EC config, not supported */
CLEVO_829X_MODE_SNAKE = 0xA6,
CLEVO_829X_MODE_SNAKE_COLOR = 0xB6,
CLEVO_829X_MODE_RAINBOW_CYCLE = 0xA7,
CLEVO_829X_MODE_RAINBOW_GRAD = 0xA8,
CLEVO_829X_MODE_FN_COLOR = 0xAF,
CLEVO_829X_MODE_OFF = 0xFF,
};
/*---------------------------------------------------------*\
| Wave/reactive behaviour |
\*---------------------------------------------------------*/
enum
{
CLEVO_KEYBOARD_DIRECTION_LEFT = 0x01,
@@ -67,23 +144,40 @@ enum
class ClevoKeyboardController
{
public:
ClevoKeyboardController(hid_device* dev_handle, const hid_device_info& info);
ClevoKeyboardController(hid_device* dev_handle, const hid_device_info& info, clevo_keyboard_type kb_type);
~ClevoKeyboardController();
clevo_keyboard_type GetType();
int GetNumRows();
int GetNumCols();
int GetNumLEDs();
unsigned char GetBrightnessMax();
std::string GetDeviceLocation();
std::string GetSerialString();
std::string GetFirmwareVersion();
void TurnOff();
void SetMode(unsigned char mode, unsigned char brightness, unsigned char speed, unsigned char behaviour);
void SetModeColor(unsigned char color_idx, unsigned char red, unsigned char green, unsigned char blue);
void SendColors(unsigned char* color_data, unsigned char brightness);
void SetModeColor(unsigned char color_idx, RGBColor color);
void SendColors(const unsigned char* color_data, unsigned char brightness, unsigned char speed);
void SetEffect829x(unsigned char mode_value, const std::vector<RGBColor>& colors, unsigned int direction = 0, unsigned int color_mode = 0);
void SetBrightnessSpeed829x(unsigned char brightness, unsigned char speed);
void SaveToBios829x();
void ClearBiosSave829x();
void StopAnimation829x();
private:
hid_device* dev;
std::string location;
unsigned short version;
hid_device* dev;
clevo_keyboard_type type;
std::string location;
unsigned short version;
unsigned char current_mode_829x = 0xFF;
unsigned char current_brightness_829x = 0xFF;
unsigned char current_speed_829x = 0xFF;
void WriteControl(unsigned char* data);
void WriteControl8291(unsigned char* data);
void WriteRowData(unsigned char* data);
void SendCommand829x(unsigned char cmd, unsigned char d0 = 0, unsigned char d1 = 0, unsigned char d2 = 0, unsigned char d3 = 0);
void SendColorSlot829x(unsigned char cmd, unsigned char slot, RGBColor color);
};
@@ -1,9 +1,11 @@
/*---------------------------------------------------------*\
| ClevoKeyboardControllerDetect.cpp |
| |
| Detector for Clevo per-key RGB keyboard (ITE 8291) |
| Detector for Clevo per-key RGB keyboard |
| Supports ITE 8291 (PID 0x600B) and ITE 829x (0x8910) |
| |
| Kyle Cascade ([email protected]) 16 Jan 2026 |
| Valentin Lobstein ([email protected]) 27 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -22,11 +24,12 @@
/*---------------------------------------------------------*\
| Clevo Keyboard product IDs |
| These are ITE 8291 per-key RGB keyboard controllers |
| ITE 8291 and ITE 829x per-key RGB keyboard controllers |
\*---------------------------------------------------------*/
#define CLEVO_KEYBOARD_PID_600B 0x600B
#define CLEVO_KEYBOARD_PID_8291 0x600B
#define CLEVO_KEYBOARD_PID_829X 0x8910
DetectedControllers DetectClevoKeyboardControllers(hid_device_info* info, const std::string& /*name*/)
static DetectedControllers DetectClevoKeyboard(hid_device_info* info, clevo_keyboard_type kb_type)
{
DetectedControllers detected_controllers;
hid_device* dev;
@@ -35,7 +38,7 @@ DetectedControllers DetectClevoKeyboardControllers(hid_device_info* info, const
if(dev)
{
ClevoKeyboardController* controller = new ClevoKeyboardController(dev, *info);
ClevoKeyboardController* controller = new ClevoKeyboardController(dev, *info, kb_type);
RGBController_ClevoKeyboard* rgb_controller = new RGBController_ClevoKeyboard(controller);
detected_controllers.push_back(rgb_controller);
@@ -44,4 +47,15 @@ DetectedControllers DetectClevoKeyboardControllers(hid_device_info* info, const
return(detected_controllers);
}
REGISTER_HID_DETECTOR_PU("CLEVO Keyboard", DetectClevoKeyboardControllers, 0x048D, 0x600B, 0xFF03, 0x01);
DetectedControllers DetectClevoKeyboard8291(hid_device_info* info, const std::string& /*name*/)
{
return(DetectClevoKeyboard(info, CLEVO_KB_ITE8291));
}
DetectedControllers DetectClevoKeyboard829x(hid_device_info* info, const std::string& /*name*/)
{
return(DetectClevoKeyboard(info, CLEVO_KB_ITE829X));
}
REGISTER_HID_DETECTOR_PU("Clevo Keyboard (ITE 8291)", DetectClevoKeyboard8291, ITE_VID, CLEVO_KEYBOARD_PID_8291, 0xFF03, 0x01);
REGISTER_HID_DETECTOR_PU("Clevo Keyboard (ITE 829x)", DetectClevoKeyboard829x, ITE_VID, CLEVO_KEYBOARD_PID_829X, 0xFF89, 0xCC);
@@ -1,9 +1,11 @@
/*---------------------------------------------------------*\
| ClevoKeyboardDevices.cpp |
| |
| Device list for Clevo per-key RGB keyboards (ITE 8291) |
| Device list for Clevo per-key RGB keyboards |
| Supports ITE 8291 (PID 0x600B) and ITE 829x (0x8910) |
| |
| Kyle Cascade ([email protected]) 21 Jan 2026 |
| Valentin Lobstein ([email protected]) 27 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -112,6 +114,48 @@ static const std::vector<unsigned int> clevo_tkl_values =
15, // Right
};
/*---------------------------------------------------------*\
| ITE 829x LED value mapping |
| LED ID = ((row & 7) << 5) | col |
| Row 5 (F-keys) = 0xA0+, Row 4 (numbers) = 0x80+ |
| Row 3 (QWERTY) = 0x60+, Row 2 (home) = 0x40+ |
| Row 1 (Z row) = 0x20+, Row 0 (mods) = 0x00+ |
\*---------------------------------------------------------*/
static const std::vector<unsigned int> clevo_829x_full_values =
{
/*---------------------------------------------------------*\
| Values in FULL layout visual order (same as CM keyboard) |
| LED ID = ((row & 7) << 5) | col |
| Row counts: 16, 21, 21, 17, 18, 13 |
\*---------------------------------------------------------*/
/* ESC F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 PRSC SCLK PSBK */
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0, 0,
/* BKTK 1 2 3 4 5 6 7 8 9 0 - = BSPC INS HOME PGUP NMLK NMDV NMTM NMMI */
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2D, 0x2E, 0x0E, 0x10, 0x12, 0x30, 0x31, 0x32, 0x33,
/* TAB Q W E R T Y U I O P [ ] ANSI\ DEL END PGDN NM7 NM8 NM9 NMPL */
0x40, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x0F, 0x11, 0x13, 0x50, 0x51, 0x52, 0x53,
/* CPLK A S D F G H J K L ; ' # ENTR NM4 NM5 NM6 */
0x60, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0, 0x6E, 0x70, 0x71, 0x72,
/* LSFT ISO\ Z X C V B N M , . / RSFT ARWU NM1 NM2 NM3 NMER */
0x80, 0, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8F, 0x90, 0x91, 0x92, 0x93,
/* LCTL LFNC LWIN LALT SPACE RALT RMNU RCTL ARWL ARDN ARWR NM0 NMPD */
0xA0, 0xA2, 0xA3, 0xA4, 0xA5, 0xAA, 0xAB, 0xAC, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
};
keyboard_keymap_overlay_values clevo_829x_keyboard_layout
{
KEYBOARD_SIZE_FULL,
{
clevo_829x_full_values,
{
/* No regional overlays needed */
}
},
{
/* No edit keys needed - all keys defined in full layout */
}
};
keyboard_keymap_overlay_values clevo_keyboard_layout
{
KEYBOARD_SIZE_TKL,
@@ -122,10 +166,9 @@ keyboard_keymap_overlay_values clevo_keyboard_layout
}
},
{
/*-------------------------------------------------------------------------------------------------------------------------------------*\
| Edit Keys |
| Zone, Row, Column, Value, Key, Alternate Name, OpCode, |
\*-------------------------------------------------------------------------------------------------------------------------------------*/
/*---------------------------------------------------------*\
| Edit Keys |
\*---------------------------------------------------------*/
/*---------------------------------------------------------*\
| Remove keys not present on Clevo keyboard |
@@ -1,9 +1,11 @@
/*---------------------------------------------------------*\
| ClevoKeyboardDevices.h |
| |
| Device list for Clevo per-key RGB keyboards (ITE 8291) |
| Device list for Clevo per-key RGB keyboards |
| Supports ITE 8291 (PID 0x600B) and ITE 829x (0x8910) |
| |
| Kyle Cascade ([email protected]) 21 Jan 2026 |
| Valentin Lobstein ([email protected]) 27 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -14,7 +16,8 @@
#include <string>
#include "KeyboardLayoutManager.h"
/*-----------------------------------------------------*\
| Clevo keyboard layout definitions |
\*-----------------------------------------------------*/
/*---------------------------------------------------------*\
| Clevo keyboard layout definitions |
\*---------------------------------------------------------*/
extern keyboard_keymap_overlay_values clevo_keyboard_layout;
extern keyboard_keymap_overlay_values clevo_829x_keyboard_layout;
@@ -1,14 +1,17 @@
/*---------------------------------------------------------*\
| RGBController_ClevoKeyboard.cpp |
| |
| RGBController for Clevo per-key RGB keyboard (ITE 8291) |
| RGBController for Clevo per-key RGB keyboard |
| Supports ITE 8291 (PID 0x600B) and ITE 829x (0x8910) |
| |
| Kyle Cascade ([email protected]) 16 Jan 2026 |
| Valentin Lobstein ([email protected]) 27 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <vector>
#include "RGBController_ClevoKeyboard.h"
#include "KeyboardLayoutManager.h"
@@ -40,152 +43,306 @@ RGBController_ClevoKeyboard::RGBController_ClevoKeyboard(ClevoKeyboardController
Direct.value = CLEVO_KEYBOARD_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Direct.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Direct.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Direct.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Direct.brightness_max = controller->GetBrightnessMax();
Direct.brightness = controller->GetBrightnessMax();
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = CLEVO_KEYBOARD_MODE_RAINBOW;
Rainbow.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Rainbow.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Rainbow.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Rainbow.speed = 0x05;
Rainbow.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Rainbow.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Rainbow.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Rainbow.color_mode = MODE_COLORS_NONE;
modes.push_back(Rainbow);
/*---------------------------------------------------------*\
| ITE 8291 built-in effects (not available on ITE 829x) |
\*---------------------------------------------------------*/
if(controller->GetType() == CLEVO_KB_ITE8291)
{
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = CLEVO_KEYBOARD_MODE_RAINBOW;
Rainbow.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Rainbow.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Rainbow.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Rainbow.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Rainbow.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Rainbow.brightness_max = controller->GetBrightnessMax();
Rainbow.brightness = controller->GetBrightnessMax();
Rainbow.color_mode = MODE_COLORS_NONE;
modes.push_back(Rainbow);
mode Wave;
Wave.name = "Wave";
Wave.value = CLEVO_KEYBOARD_MODE_WAVE;
Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Wave.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Wave.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Wave.speed = 0x05;
Wave.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Wave.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Wave.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Wave.direction = MODE_DIRECTION_LEFT;
Wave.colors_min = 1;
Wave.colors_max = 1;
Wave.colors.resize(1);
Wave.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Wave);
mode Wave;
Wave.name = "Wave";
Wave.value = CLEVO_KEYBOARD_MODE_WAVE;
Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Wave.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Wave.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Wave.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Wave.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Wave.brightness_max = controller->GetBrightnessMax();
Wave.brightness = controller->GetBrightnessMax();
Wave.direction = MODE_DIRECTION_LEFT;
Wave.colors_min = 1;
Wave.colors_max = 1;
Wave.colors.resize(1);
Wave.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Wave);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = CLEVO_KEYBOARD_MODE_BREATH;
Breathing.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Breathing.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Breathing.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Breathing.speed = 0x05;
Breathing.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Breathing.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Breathing.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.colors.resize(1);
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Breathing);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = CLEVO_KEYBOARD_MODE_BREATH;
Breathing.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Breathing.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Breathing.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Breathing.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Breathing.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Breathing.brightness_max = controller->GetBrightnessMax();
Breathing.brightness = controller->GetBrightnessMax();
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.colors.resize(1);
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Breathing);
mode Reactive;
Reactive.name = "Reactive";
Reactive.value = CLEVO_KEYBOARD_MODE_REACTIVE;
Reactive.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Reactive.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Reactive.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Reactive.speed = 0x05;
Reactive.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Reactive.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Reactive.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Reactive.colors_min = 1;
Reactive.colors_max = 1;
Reactive.colors.resize(1);
Reactive.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Reactive);
mode Reactive;
Reactive.name = "Reactive";
Reactive.value = CLEVO_KEYBOARD_MODE_REACTIVE;
Reactive.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Reactive.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Reactive.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Reactive.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Reactive.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Reactive.brightness_max = controller->GetBrightnessMax();
Reactive.brightness = controller->GetBrightnessMax();
Reactive.colors_min = 1;
Reactive.colors_max = 1;
Reactive.colors.resize(1);
Reactive.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Reactive);
mode Ripple;
Ripple.name = "Ripple";
Ripple.value = CLEVO_KEYBOARD_MODE_RIPPLE;
Ripple.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Ripple.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Ripple.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Ripple.speed = 0x05;
Ripple.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Ripple.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Ripple.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Ripple.colors_min = 1;
Ripple.colors_max = 1;
Ripple.colors.resize(1);
Ripple.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Ripple);
mode Ripple;
Ripple.name = "Ripple";
Ripple.value = CLEVO_KEYBOARD_MODE_RIPPLE;
Ripple.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Ripple.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Ripple.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Ripple.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Ripple.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Ripple.brightness_max = controller->GetBrightnessMax();
Ripple.brightness = controller->GetBrightnessMax();
Ripple.colors_min = 1;
Ripple.colors_max = 1;
Ripple.colors.resize(1);
Ripple.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Ripple);
mode Marquee;
Marquee.name = "Marquee";
Marquee.value = CLEVO_KEYBOARD_MODE_MARQUEE;
Marquee.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Marquee.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Marquee.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Marquee.speed = 0x05;
Marquee.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Marquee.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Marquee.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Marquee.colors_min = 1;
Marquee.colors_max = 1;
Marquee.colors.resize(1);
Marquee.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Marquee);
mode Marquee;
Marquee.name = "Marquee";
Marquee.value = CLEVO_KEYBOARD_MODE_MARQUEE;
Marquee.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Marquee.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Marquee.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Marquee.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Marquee.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Marquee.brightness_max = controller->GetBrightnessMax();
Marquee.brightness = controller->GetBrightnessMax();
Marquee.colors_min = 1;
Marquee.colors_max = 1;
Marquee.colors.resize(1);
Marquee.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Marquee);
mode Raindrop;
Raindrop.name = "Raindrop";
Raindrop.value = CLEVO_KEYBOARD_MODE_RAINDROP;
Raindrop.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Raindrop.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Raindrop.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Raindrop.speed = 0x05;
Raindrop.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Raindrop.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Raindrop.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Raindrop.colors_min = 1;
Raindrop.colors_max = 1;
Raindrop.colors.resize(1);
Raindrop.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Raindrop);
mode Raindrop;
Raindrop.name = "Raindrop";
Raindrop.value = CLEVO_KEYBOARD_MODE_RAINDROP;
Raindrop.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Raindrop.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Raindrop.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Raindrop.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Raindrop.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Raindrop.brightness_max = controller->GetBrightnessMax();
Raindrop.brightness = controller->GetBrightnessMax();
Raindrop.colors_min = 1;
Raindrop.colors_max = 1;
Raindrop.colors.resize(1);
Raindrop.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Raindrop);
mode Aurora;
Aurora.name = "Aurora";
Aurora.value = CLEVO_KEYBOARD_MODE_AURORA;
Aurora.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Aurora.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Aurora.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Aurora.speed = 0x05;
Aurora.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Aurora.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Aurora.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Aurora.colors_min = 1;
Aurora.colors_max = 1;
Aurora.colors.resize(1);
Aurora.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Aurora);
mode Aurora;
Aurora.name = "Aurora";
Aurora.value = CLEVO_KEYBOARD_MODE_AURORA;
Aurora.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Aurora.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Aurora.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Aurora.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Aurora.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Aurora.brightness_max = controller->GetBrightnessMax();
Aurora.brightness = controller->GetBrightnessMax();
Aurora.colors_min = 1;
Aurora.colors_max = 1;
Aurora.colors.resize(1);
Aurora.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Aurora);
mode Spark;
Spark.name = "Spark";
Spark.value = CLEVO_KEYBOARD_MODE_SPARK;
Spark.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Spark.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Spark.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Spark.speed = 0x05;
Spark.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Spark.brightness_max = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Spark.brightness = CLEVO_KEYBOARD_BRIGHTNESS_MAX;
Spark.colors_min = 1;
Spark.colors_max = 1;
Spark.colors.resize(1);
Spark.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Spark);
mode Spark;
Spark.name = "Spark";
Spark.value = CLEVO_KEYBOARD_MODE_SPARK;
Spark.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Spark.speed_min = CLEVO_KEYBOARD_SPEED_MAX;
Spark.speed_max = CLEVO_KEYBOARD_SPEED_MIN;
Spark.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Spark.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Spark.brightness_max = controller->GetBrightnessMax();
Spark.brightness = controller->GetBrightnessMax();
Spark.colors_min = 1;
Spark.colors_max = 1;
Spark.colors.resize(1);
Spark.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Spark);
}
/*---------------------------------------------------------*\
| ITE 829x firmware effects |
\*---------------------------------------------------------*/
if(controller->GetType() == CLEVO_KB_ITE829X)
{
unsigned char bmax = controller->GetBrightnessMax();
mode Wave;
Wave.name = "Wave";
Wave.value = CLEVO_829X_MODE_WAVE;
Wave.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_DIRECTION_DIAG;
Wave.speed_min = CLEVO_KEYBOARD_SPEED_MIN;
Wave.speed_max = CLEVO_KEYBOARD_SPEED_MAX;
Wave.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Wave.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Wave.brightness_max = bmax;
Wave.brightness = bmax;
Wave.direction = MODE_DIRECTION_LEFT;
Wave.colors_min = 1;
Wave.colors_max = 1;
Wave.colors.resize(1, ToRGBColor(255, 0, 0));
Wave.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Wave);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = CLEVO_829X_MODE_BREATH;
Breathing.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
Breathing.speed_min = CLEVO_KEYBOARD_SPEED_MIN;
Breathing.speed_max = CLEVO_KEYBOARD_SPEED_MAX;
Breathing.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Breathing.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Breathing.brightness_max = bmax;
Breathing.brightness = bmax;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.colors.resize(1, ToRGBColor(255, 0, 0));
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Breathing);
mode Flashing;
Flashing.name = "Flashing";
Flashing.value = CLEVO_829X_MODE_BLINK;
Flashing.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
Flashing.speed_min = CLEVO_KEYBOARD_SPEED_MIN;
Flashing.speed_max = CLEVO_KEYBOARD_SPEED_MAX;
Flashing.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Flashing.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Flashing.brightness_max = bmax;
Flashing.brightness = bmax;
Flashing.colors_min = 1;
Flashing.colors_max = 1;
Flashing.colors.resize(1, ToRGBColor(255, 0, 0));
Flashing.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Flashing);
mode Random;
Random.name = "Random";
Random.value = CLEVO_829X_MODE_RANDOM;
Random.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
Random.speed_min = CLEVO_KEYBOARD_SPEED_MIN;
Random.speed_max = CLEVO_KEYBOARD_SPEED_MAX;
Random.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Random.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Random.brightness_max = bmax;
Random.brightness = bmax;
Random.colors_min = 1;
Random.colors_max = 1;
Random.colors.resize(1, ToRGBColor(255, 0, 0));
Random.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Random);
mode Scan;
Scan.name = "Scan";
Scan.value = CLEVO_829X_MODE_SCAN;
Scan.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Scan.speed_min = CLEVO_KEYBOARD_SPEED_MIN;
Scan.speed_max = CLEVO_KEYBOARD_SPEED_MAX;
Scan.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Scan.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Scan.brightness_max = bmax;
Scan.brightness = bmax;
Scan.colors_min = 2;
Scan.colors_max = 2;
Scan.colors.resize(2);
Scan.colors[0] = ToRGBColor(255, 0, 0);
Scan.colors[1] = ToRGBColor(0, 0, 255);
Scan.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Scan);
mode Snake;
Snake.name = "Snake";
Snake.value = CLEVO_829X_MODE_SNAKE;
Snake.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_DIRECTION_DIAG;
Snake.speed_min = CLEVO_KEYBOARD_SPEED_MIN;
Snake.speed_max = CLEVO_KEYBOARD_SPEED_MAX;
Snake.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
Snake.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
Snake.brightness_max = bmax;
Snake.brightness = bmax;
Snake.direction = MODE_DIRECTION_UP_LEFT;
Snake.colors_min = 1;
Snake.colors_max = 1;
Snake.colors.resize(1, ToRGBColor(255, 0, 0));
Snake.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(Snake);
mode RainbowCycle;
RainbowCycle.name = "Spectrum Cycle";
RainbowCycle.value = CLEVO_829X_MODE_RAINBOW_CYCLE;
RainbowCycle.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
RainbowCycle.speed_min = CLEVO_KEYBOARD_SPEED_MIN;
RainbowCycle.speed_max = CLEVO_KEYBOARD_SPEED_MAX;
RainbowCycle.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
RainbowCycle.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
RainbowCycle.brightness_max = bmax;
RainbowCycle.brightness = bmax;
RainbowCycle.color_mode = MODE_COLORS_NONE;
modes.push_back(RainbowCycle);
mode RainbowGrad;
RainbowGrad.name = "Rainbow Wave";
RainbowGrad.value = CLEVO_829X_MODE_RAINBOW_GRAD;
RainbowGrad.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_DIRECTION_DIAG;
RainbowGrad.speed_min = CLEVO_KEYBOARD_SPEED_MIN;
RainbowGrad.speed_max = CLEVO_KEYBOARD_SPEED_MAX;
RainbowGrad.speed = CLEVO_KEYBOARD_SPEED_DEFAULT;
RainbowGrad.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
RainbowGrad.brightness_max = bmax;
RainbowGrad.brightness = bmax;
RainbowGrad.direction = MODE_DIRECTION_LEFT;
RainbowGrad.color_mode = MODE_COLORS_NONE;
modes.push_back(RainbowGrad);
mode FnColor;
FnColor.name = "Fn Highlight";
FnColor.value = CLEVO_829X_MODE_FN_COLOR;
FnColor.flags = MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
FnColor.brightness_min = CLEVO_KEYBOARD_BRIGHTNESS_MIN;
FnColor.brightness_max = bmax;
FnColor.brightness = bmax;
FnColor.colors_min = 1;
FnColor.colors_max = 1;
FnColor.colors.resize(1, ToRGBColor(255, 0, 0));
FnColor.color_mode = MODE_COLORS_MODE_SPECIFIC;
modes.push_back(FnColor);
}
mode Off;
Off.name = "Off";
@@ -207,16 +364,22 @@ RGBController_ClevoKeyboard::~RGBController_ClevoKeyboard()
void RGBController_ClevoKeyboard::SetupZones()
{
/*---------------------------------------------------------*\
| Create keyboard layout using KeyboardLayoutManager |
| Select layout based on device type |
\*---------------------------------------------------------*/
KeyboardLayoutManager new_kb(KEYBOARD_LAYOUT_ISO_QWERTY,
clevo_keyboard_layout.base_size,
clevo_keyboard_layout.key_values);
bool is_829x = (controller->GetType() == CLEVO_KB_ITE829X);
new_kb.ChangeKeys(clevo_keyboard_layout);
keyboard_keymap_overlay_values& layout = is_829x
? clevo_829x_keyboard_layout
: clevo_keyboard_layout;
KeyboardLayoutManager new_kb(KEYBOARD_LAYOUT_ISO_QWERTY,
layout.base_size,
layout.key_values);
new_kb.ChangeKeys(layout);
/*---------------------------------------------------------*\
| Create a matrix zone for the keyboard |
| Create keyboard zone from KLM |
\*---------------------------------------------------------*/
zone keyboard_zone;
@@ -233,9 +396,6 @@ void RGBController_ClevoKeyboard::SetupZones()
zones.push_back(keyboard_zone);
/*---------------------------------------------------------*\
| Create LEDs from the KeyboardLayoutManager data |
\*---------------------------------------------------------*/
for(unsigned int led_idx = 0; led_idx < keyboard_zone.leds_count; led_idx++)
{
led new_led;
@@ -249,36 +409,48 @@ void RGBController_ClevoKeyboard::SetupZones()
SetupColors();
/*---------------------------------------------------------*\
| Create buffer map to translate OpenRGB LED order to |
| hardware LED order. The hardware expects 126 color values |
| indexed by LED position (0-125). |
| Build buffer map from LED values to color pointers |
| 829x LED IDs have gaps (stride 32), need larger map |
\*---------------------------------------------------------*/
null_color = 0x00000000;
buffer_map.resize(CLEVO_KEYBOARD_NUM_LEDS, &null_color);
size_t map_size = is_829x
? CLEVO_829X_LED_ID(CLEVO_829X_NUM_ROWS - 1, CLEVO_829X_NUM_COLS - 1) + 1
: (size_t)controller->GetNumLEDs();
buffer_map.resize(map_size, &null_color);
for(size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
buffer_map[leds[led_idx].value] = &colors[led_idx];
if(leds[led_idx].value < buffer_map.size())
{
buffer_map[leds[led_idx].value] = &colors[led_idx];
}
}
}
void RGBController_ClevoKeyboard::DeviceUpdateLEDs()
{
/*---------------------------------------------------------*\
| Build color data array using buffer map and send to device|
| The buffer_map translates from hardware LED index to the |
| corresponding color pointer for that LED. |
\*---------------------------------------------------------*/
unsigned char color_data[CLEVO_KEYBOARD_NUM_LEDS * 3];
int num_rows = controller->GetNumRows();
int num_cols = controller->GetNumCols();
bool is_829x = (controller->GetType() == CLEVO_KB_ITE829X);
for(int i = 0; i < CLEVO_KEYBOARD_NUM_LEDS; i++)
std::vector<unsigned char> color_data(num_rows * num_cols * 3);
for(int row = 0; row < num_rows; row++)
{
color_data[i * 3 + 0] = RGBGetRValue(*buffer_map[i]);
color_data[i * 3 + 1] = RGBGetGValue(*buffer_map[i]);
color_data[i * 3 + 2] = RGBGetBValue(*buffer_map[i]);
for(int col = 0; col < num_cols; col++)
{
int seq = row * num_cols + col;
int led_id = is_829x ? CLEVO_829X_LED_ID(row, col) : seq;
color_data[seq * 3 + 0] = RGBGetRValue(*buffer_map[led_id]);
color_data[seq * 3 + 1] = RGBGetGValue(*buffer_map[led_id]);
color_data[seq * 3 + 2] = RGBGetBValue(*buffer_map[led_id]);
}
}
controller->SendColors(color_data, modes[active_mode].brightness);
controller->SendColors(color_data.data(), modes[active_mode].brightness, modes[active_mode].speed);
}
void RGBController_ClevoKeyboard::DeviceUpdateZoneLEDs(int /*zone*/)
@@ -304,6 +476,16 @@ void RGBController_ClevoKeyboard::DeviceUpdateMode()
return;
}
/*---------------------------------------------------------*\
| Handle ITE 829x firmware effects |
\*---------------------------------------------------------*/
if(controller->GetType() == CLEVO_KB_ITE829X && mode_value != CLEVO_KEYBOARD_MODE_DIRECT)
{
controller->SetEffect829x(mode_value, modes[active_mode].colors, modes[active_mode].direction, modes[active_mode].color_mode);
controller->SetBrightnessSpeed829x(modes[active_mode].brightness, modes[active_mode].speed);
return;
}
/*---------------------------------------------------------*\
| Handle Direct (per-key) mode |
\*---------------------------------------------------------*/
@@ -318,41 +500,26 @@ void RGBController_ClevoKeyboard::DeviceUpdateMode()
\*---------------------------------------------------------*/
unsigned char brightness = modes[active_mode].brightness;
unsigned char speed = modes[active_mode].speed;
unsigned char behaviour = 0x00;
/*---------------------------------------------------------*\
| Set direction for wave mode |
\*---------------------------------------------------------*/
if(mode_value == CLEVO_KEYBOARD_MODE_WAVE)
{
switch(modes[active_mode].direction)
{
case MODE_DIRECTION_LEFT:
behaviour = CLEVO_KEYBOARD_DIRECTION_LEFT;
break;
case MODE_DIRECTION_RIGHT:
behaviour = CLEVO_KEYBOARD_DIRECTION_RIGHT;
break;
case MODE_DIRECTION_UP:
behaviour = CLEVO_KEYBOARD_DIRECTION_UP;
break;
case MODE_DIRECTION_DOWN:
behaviour = CLEVO_KEYBOARD_DIRECTION_DOWN;
break;
}
}
unsigned char behaviour = (mode_value == CLEVO_KEYBOARD_MODE_WAVE)
? modes[active_mode].direction + 1
: 0x00;
/*---------------------------------------------------------*\
| Set mode color if applicable |
\*---------------------------------------------------------*/
if(modes[active_mode].colors.size() > 0)
{
unsigned char red = RGBGetRValue(modes[active_mode].colors[0]);
unsigned char green = RGBGetGValue(modes[active_mode].colors[0]);
unsigned char blue = RGBGetBValue(modes[active_mode].colors[0]);
controller->SetModeColor(1, red, green, blue);
controller->SetModeColor(1, modes[active_mode].colors[0]);
}
controller->SetMode(mode_value, brightness, speed, behaviour);
}
void RGBController_ClevoKeyboard::DeviceSaveMode()
{
if(controller->GetType() == CLEVO_KB_ITE829X)
{
DeviceUpdateMode();
controller->SaveToBios829x();
}
}
@@ -1,9 +1,11 @@
/*---------------------------------------------------------*\
| RGBController_ClevoKeyboard.h |
| |
| RGBController for Clevo per-key RGB keyboard (ITE 8291) |
| RGBController for Clevo per-key RGB keyboard |
| Supports ITE 8291 (PID 0x600B) and ITE 829x (0x8910) |
| |
| Kyle Cascade ([email protected]) 16 Jan 2026 |
| Valentin Lobstein ([email protected]) 27 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -18,7 +20,7 @@
class RGBController_ClevoKeyboard : public RGBController
{
public:
RGBController_ClevoKeyboard(ClevoKeyboardController* controller_ptr);
explicit RGBController_ClevoKeyboard(ClevoKeyboardController* controller_ptr);
~RGBController_ClevoKeyboard();
void SetupZones();
@@ -28,6 +30,7 @@ public:
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
void DeviceSaveMode();
private:
ClevoKeyboardController* controller;
@@ -71,6 +71,7 @@ REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4070 Ti SUPER Advanced OC-V", Detec
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4070 Ti SUPER Ultra W", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4070TIS_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4070TI_SUPER_ULTRA_W, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4070 SUPER Ultra W OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4070_SUPER_ULTRA_W_OC, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4070 Vulcan OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4070_VULCAN_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4070 Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4070_ULTRAW_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4070 SUPER Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4070S_ULTRAW_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4080 Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4080_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4080_ULTRAW_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4080 Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4080_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4080_ULTRAW_OCV2, 0x61);
@@ -411,6 +411,7 @@ REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 4090 Gaming White OC",
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 4090 Gaming White OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_4090_O24G_GAMING_WHITE_2, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG MATRIX PLATINUM GeForce RTX 4090", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, ASUS_SUB_VEN, ASUS_ROG_MATRIX_PLATINUM_RTX_4090_24G, 0x67);
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 5060 Ti Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5060TI_O16G_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);
@@ -0,0 +1,205 @@
/*---------------------------------------------------------*\
| EdifierHaloController.cpp |
| |
| Driver for Edifier Halo series USB/HID devices |
| |
| Bartholomew Ho <[email protected]> 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogManager.h"
#include "EdifierHaloController.h"
#include "StringUtils.h"
EdifierHaloController::EdifierHaloController(hid_device* dev_handle, const char* path)
{
dev = dev_handle;
location = path;
wchar_t name_string[256];
int result;
result = hid_get_manufacturer_string(dev, name_string, 256);
if(result == 0)
{
device_name = StringUtils::wstring_to_string(name_string);
}
else
{
device_name = "Edifier";
LOG_WARNING("[EdifierHaloController] Failed to read manufacturer string for %s", path);
}
result = hid_get_product_string(dev, name_string, 256);
if(result == 0)
{
device_name.append(" ").append(StringUtils::wstring_to_string(name_string));
}
else
{
device_name.append(" Halo PixelBar");
LOG_WARNING("[EdifierHaloController] Failed to read product string for %s", path);
}
wchar_t serial_string[128];
result = hid_get_serial_number_string(dev, serial_string, 128);
if(result == 0)
{
serial = StringUtils::wstring_to_string(serial_string);
}
else
{
serial = "";
LOG_DEBUG("[EdifierHaloController] No serial number available for %s", path);
}
}
EdifierHaloController::~EdifierHaloController()
{
if(dev)
{
hid_close(dev);
}
}
std::string EdifierHaloController::GetDeviceName() const
{
return(device_name);
}
std::string EdifierHaloController::GetSerial() const
{
return(serial);
}
std::string EdifierHaloController::GetLocation() const
{
return("HID: " + location);
}
void EdifierHaloController::SetAmbientLightPower(bool enabled)
{
unsigned char mode = enabled ? 0x01 : 0x00;
unsigned char payload[] =
{
0x00,
0x00,
0x00,
0x00,
mode,
0xFF,
0xFF
};
SendEdifierPacket(0x6b, payload, sizeof(payload));
}
void EdifierHaloController::SetAmbientLight(unsigned char effect, unsigned char r, unsigned char g, unsigned char b, unsigned char brightness, unsigned char speed)
{
unsigned char raw_brightness = 0x3c;
if (brightness == 1) raw_brightness = 0x14;
else if (brightness == 2) raw_brightness = 0x28;
else if (brightness == 3) raw_brightness = 0x3c;
if (speed < 1) speed = 1;
if (speed > 10) speed = 10;
unsigned char payload[] =
{
0x13,
effect,
r,
g,
b,
raw_brightness,
speed
};
SendEdifierPacket(0x6b, payload, sizeof(payload));
}
void EdifierHaloController::SetPixelScreenColor(unsigned char r, unsigned char g, unsigned char b)
{
unsigned char payload[] =
{
0x03,
r,
g,
b
};
SendEdifierPacket(0xef, payload, sizeof(payload));
}
void EdifierHaloController::SetScreenUIModel(unsigned char model_id, unsigned char r, unsigned char g, unsigned char b)
{
unsigned char payload[] =
{
0x02,
r,
g,
b,
0x00,
0x01,
model_id,
0xff,
0xff
};
SendEdifierPacket(0xef, payload, sizeof(payload));
}
void EdifierHaloController::SetScreenScene(unsigned char category, unsigned char index, unsigned char r, unsigned char g, unsigned char b)
{
unsigned char payload[] =
{
0x01, // group
r,
g,
b,
0x00,
0x01,
category,
index,
0xff // option
};
SendEdifierPacket(0xef, payload, sizeof(payload));
}
void EdifierHaloController::SendEdifierPacket(unsigned char commandIndex, const unsigned char* payload, unsigned int payload_size)
{
if(!dev)
{
return;
}
unsigned char packet[64];
memset(packet, 0x00, sizeof(packet));
packet[0] = 0x2e;
packet[1] = 0xaa;
packet[2] = 0xec;
packet[3] = commandIndex;
packet[4] = (unsigned char)((payload_size >> 8) & 0xff);
packet[5] = (unsigned char)(payload_size & 0xff);
if(payload_size > 0 && payload != nullptr)
{
memcpy(&packet[6], payload, payload_size);
}
// Calculate checksum
unsigned int sum = 0;
for(unsigned int i = 1; i < 6 + payload_size; i++)
{
sum += packet[i];
}
packet[6 + payload_size] = (unsigned char)(sum & 0xff);
std::lock_guard<std::mutex> guard(device_mutex);
int bytes_written = hid_write(dev, packet, sizeof(packet));
if(bytes_written != sizeof(packet))
{
LOG_ERROR("[EdifierHaloController] Failed to write HID packet (%d/%d bytes): %ls",
bytes_written, (int)sizeof(packet), hid_error(dev));
}
}
@@ -0,0 +1,64 @@
/*---------------------------------------------------------*\
| EdifierHaloController.h |
| |
| Driver for Edifier Halo series USB/HID devices |
| |
| Bartholomew Ho <[email protected]> 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <mutex>
#include <string>
#include <hidapi.h>
enum
{
EDIFIER_HALO_AMBIENT_MODE_BREATHING = 1, // Breathing
EDIFIER_HALO_AMBIENT_MODE_TIDE = 2, // Rainbow
EDIFIER_HALO_AMBIENT_MODE_STATIC = 3, // Static
EDIFIER_HALO_AMBIENT_MODE_RIPPLE = 4, // Glittering and colorful
EDIFIER_HALO_AMBIENT_MODE_FLOW = 5, // Light-flow
EDIFIER_HALO_AMBIENT_MODE_DYNAMIC = 6 // Dynamic Shadows
};
enum
{
EDIFIER_HALO_AMBIENT_SPEED_SLOWEST = 1,
EDIFIER_HALO_AMBIENT_SPEED_FASTEST = 10
};
enum
{
EDIFIER_HALO_AMBIENT_BRIGHTNESS_LOWEST = 1,
EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST = 3
};
class EdifierHaloController
{
public:
EdifierHaloController(hid_device* dev_handle, const char* path);
~EdifierHaloController();
std::string GetDeviceName() const;
std::string GetSerial() const;
std::string GetLocation() const;
void SetAmbientLight(unsigned char effect, unsigned char r, unsigned char g, unsigned char b, unsigned char brightness, unsigned char speed);
void SetAmbientLightPower(bool enabled);
void SetPixelScreenColor(unsigned char r, unsigned char g, unsigned char b);
void SetScreenUIModel(unsigned char model_id, unsigned char r, unsigned char g, unsigned char b);
void SetScreenScene(unsigned char category, unsigned char index, unsigned char r, unsigned char g, unsigned char b);
private:
hid_device* dev;
std::string device_name;
std::string serial;
std::string location;
std::mutex device_mutex;
void SendEdifierPacket(unsigned char commandIndex, const unsigned char* payload, unsigned int payload_size);
};
@@ -0,0 +1,56 @@
/*---------------------------------------------------------*\
| EdifierHaloControllerDetect.cpp |
| |
| Detector for Edifier Halo PixelBar USB/HID devices |
| |
| Bartholomew Ho <[email protected]> 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include <memory>
#include "DetectionManager.h"
#include "LogManager.h"
#include "RGBController_EdifierHaloPixelbarAmbient.h"
#include "RGBController_EdifierHaloPixelbarScreen.h"
#include "EdifierHaloController.h"
/*---------------------------------------------------------*\
| Edifier HID vendor and product IDs |
\*---------------------------------------------------------*/
#define EDIFIER_VID 0x2D99
#define EDIFIER_PIXELBAR_PID 0xA106
#define EDIFIER_PIXELBAR_INTERFACE 4
#define EDIFIER_PIXELBAR_USAGE_PAGE 0xFF14
#define EDIFIER_PIXELBAR_USAGE 0x01
DetectedControllers DetectEdifierHaloControllers(hid_device_info* info, const std::string&)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
EdifierHaloController* controller = new EdifierHaloController(dev, info->path);
RGBController_EdifierHaloPixelbarAmbient* ambient_controller = new RGBController_EdifierHaloPixelbarAmbient(controller);
RGBController_EdifierHaloPixelbarScreen* screen_controller = new RGBController_EdifierHaloPixelbarScreen(controller);
detected_controllers.push_back(ambient_controller);
detected_controllers.push_back(screen_controller);
LOG_INFO("[EdifierHaloPixelbarDetect] Detected Edifier Halo PixelBar at %s (Instantiated Ambient & Screen controllers)", info->path);
}
else
{
LOG_WARNING("[EdifierHaloPixelbarDetect] Failed to open Edifier Halo PixelBar at %s", info->path);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_IPU("Edifier Halo Pixelbar", DetectEdifierHaloControllers, EDIFIER_VID, EDIFIER_PIXELBAR_PID, EDIFIER_PIXELBAR_INTERFACE, EDIFIER_PIXELBAR_USAGE_PAGE, EDIFIER_PIXELBAR_USAGE);
@@ -0,0 +1,182 @@
/*---------------------------------------------------------*\
| RGBController_EdifierHaloPixelbarAmbient.cpp |
| |
| RGBController for Edifier Halo PixelBar Ambient Light |
| |
| Bartholomew Ho <[email protected]> 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_EdifierHaloPixelbarAmbient.h"
/**------------------------------------------------------------------*\
@name Edifier Halo Pixelbar
@category Speaker
@type USB
@save :robot:
@direct :x:
@effects :white_check_mark:
@detectors DetectEdifierHaloControllers
@comment
\*-------------------------------------------------------------------*/
RGBController_EdifierHaloPixelbarAmbient::RGBController_EdifierHaloPixelbarAmbient(EdifierHaloController* controller_ptr)
{
controller = controller_ptr;
first_leds_update_ignored = false;
first_mode_update_ignored = false;
name = "Edifier Halo Pixelbar - Light Effects";
vendor = "Edifier";
type = DEVICE_TYPE_SPEAKER;
description = "Edifier Halo PixelBar Light Effects";
serial = controller->GetSerial();
location = controller->GetLocation();
// Setup modes
mode breathing_mode;
breathing_mode.name = "Breathing";
breathing_mode.value = EDIFIER_HALO_AMBIENT_MODE_BREATHING;
breathing_mode.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
breathing_mode.color_mode = MODE_COLORS_PER_LED;
breathing_mode.speed_min = EDIFIER_HALO_AMBIENT_SPEED_SLOWEST;
breathing_mode.speed_max = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
breathing_mode.speed = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
breathing_mode.brightness_min = EDIFIER_HALO_AMBIENT_BRIGHTNESS_LOWEST;
breathing_mode.brightness_max = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
breathing_mode.brightness = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
modes.push_back(breathing_mode);
mode tide_mode;
tide_mode.name = "Rainbow";
tide_mode.value = EDIFIER_HALO_AMBIENT_MODE_TIDE;
tide_mode.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
tide_mode.color_mode = MODE_COLORS_NONE;
tide_mode.speed_min = EDIFIER_HALO_AMBIENT_SPEED_SLOWEST;
tide_mode.speed_max = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
tide_mode.speed = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
tide_mode.brightness_min = EDIFIER_HALO_AMBIENT_BRIGHTNESS_LOWEST;
tide_mode.brightness_max = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
tide_mode.brightness = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
modes.push_back(tide_mode);
mode static_mode;
static_mode.name = "Static";
static_mode.value = EDIFIER_HALO_AMBIENT_MODE_STATIC;
static_mode.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
static_mode.color_mode = MODE_COLORS_MODE_SPECIFIC;
static_mode.brightness_min = EDIFIER_HALO_AMBIENT_BRIGHTNESS_LOWEST;
static_mode.brightness_max = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
static_mode.brightness = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
modes.push_back(static_mode);
mode ripple_mode;
ripple_mode.name = "Glittering and colorful";
ripple_mode.value = EDIFIER_HALO_AMBIENT_MODE_RIPPLE;
ripple_mode.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
ripple_mode.color_mode = MODE_COLORS_NONE;
ripple_mode.speed_min = EDIFIER_HALO_AMBIENT_SPEED_SLOWEST;
ripple_mode.speed_max = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
ripple_mode.speed = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
ripple_mode.brightness_min = EDIFIER_HALO_AMBIENT_BRIGHTNESS_LOWEST;
ripple_mode.brightness_max = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
ripple_mode.brightness = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
modes.push_back(ripple_mode);
mode flow_mode;
flow_mode.name = "Light-flow";
flow_mode.value = EDIFIER_HALO_AMBIENT_MODE_FLOW;
flow_mode.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
flow_mode.color_mode = MODE_COLORS_NONE;
flow_mode.speed_min = EDIFIER_HALO_AMBIENT_SPEED_SLOWEST;
flow_mode.speed_max = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
flow_mode.speed = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
flow_mode.brightness_min = EDIFIER_HALO_AMBIENT_BRIGHTNESS_LOWEST;
flow_mode.brightness_max = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
flow_mode.brightness = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
modes.push_back(flow_mode);
mode dynamic_mode;
dynamic_mode.name = "Dynamic Shadows";
dynamic_mode.value = EDIFIER_HALO_AMBIENT_MODE_DYNAMIC;
dynamic_mode.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
dynamic_mode.color_mode = MODE_COLORS_NONE;
dynamic_mode.speed_min = EDIFIER_HALO_AMBIENT_SPEED_SLOWEST;
dynamic_mode.speed_max = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
dynamic_mode.speed = EDIFIER_HALO_AMBIENT_SPEED_FASTEST;
dynamic_mode.brightness_min = EDIFIER_HALO_AMBIENT_BRIGHTNESS_LOWEST;
dynamic_mode.brightness_max = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
dynamic_mode.brightness = EDIFIER_HALO_AMBIENT_BRIGHTNESS_HIGHEST;
modes.push_back(dynamic_mode);
// Default to Static mode
active_mode = 2;
SetupZones();
}
RGBController_EdifierHaloPixelbarAmbient::~RGBController_EdifierHaloPixelbarAmbient()
{
}
void RGBController_EdifierHaloPixelbarAmbient::SetupZones()
{
zone ambient_zone;
ambient_zone.name = "Light Effects";
ambient_zone.type = ZONE_TYPE_SINGLE;
ambient_zone.leds_min = 1;
ambient_zone.leds_max = 1;
ambient_zone.leds_count = 1;
zones.push_back(ambient_zone);
led ambient_led;
ambient_led.name = "Light Effects";
leds.push_back(ambient_led);
SetupColors();
}
void RGBController_EdifierHaloPixelbarAmbient::DeviceUpdateLEDs()
{
if(!first_leds_update_ignored)
{
first_leds_update_ignored = true;
return;
}
int active_idx = GetActiveMode();
RGBColor color = colors[0];
unsigned char r = RGBGetRValue(color);
unsigned char g = RGBGetGValue(color);
unsigned char b = RGBGetBValue(color);
unsigned char effect = modes[active_idx].value;
unsigned char brightness = modes[active_idx].brightness;
unsigned char speed = modes[active_idx].speed;
controller->SetAmbientLightPower(true);
controller->SetAmbientLight(effect, r, g, b, brightness, speed);
}
void RGBController_EdifierHaloPixelbarAmbient::DeviceUpdateZoneLEDs(int)
{
DeviceUpdateLEDs();
}
void RGBController_EdifierHaloPixelbarAmbient::DeviceUpdateSingleLED(int)
{
DeviceUpdateLEDs();
}
void RGBController_EdifierHaloPixelbarAmbient::DeviceUpdateMode()
{
if(!first_mode_update_ignored)
{
first_mode_update_ignored = true;
return;
}
DeviceUpdateLEDs();
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_EdifierHaloPixelbarAmbient.h |
| |
| RGBController for Edifier Halo PixelBar Ambient Light |
| |
| Bartholomew Ho <[email protected]> 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <memory>
#include "RGBController.h"
#include "EdifierHaloController.h"
class RGBController_EdifierHaloPixelbarAmbient : public RGBController
{
public:
RGBController_EdifierHaloPixelbarAmbient(EdifierHaloController* controller_ptr);
~RGBController_EdifierHaloPixelbarAmbient();
void SetupZones();
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
private:
EdifierHaloController* controller;
bool first_leds_update_ignored;
bool first_mode_update_ignored;
};
@@ -0,0 +1,173 @@
/*---------------------------------------------------------*\
| RGBController_EdifierHaloPixelbarScreen.cpp |
| |
| RGBController for Edifier Halo PixelBar Screen Color |
| |
| Bartholomew Ho <[email protected]> 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_EdifierHaloPixelbarScreen.h"
/**------------------------------------------------------------------*\
@name Edifier Halo Pixelbar - Screen
@category Speaker
@type USB
@save :robot:
@direct :x:
@effects :white_check_mark:
@detectors DetectEdifierHaloControllers
@comment
\*-------------------------------------------------------------------*/
struct CategoryDefinition
{
std::string name;
unsigned char category_id;
};
RGBController_EdifierHaloPixelbarScreen::RGBController_EdifierHaloPixelbarScreen(EdifierHaloController* controller_ptr)
{
controller = controller_ptr;
first_leds_update_ignored = false;
first_mode_update_ignored = false;
name = "Edifier Halo Pixelbar - Pixel Display";
vendor = "Edifier";
type = DEVICE_TYPE_SPEAKER;
description = "Edifier Halo PixelBar Pixel Display Color";
serial = controller->GetSerial();
location = controller->GetLocation();
// Clock 1 to Clock 11 (11 built-in clock styles)
for(unsigned char i = 0; i < 11; ++i)
{
mode m;
m.name = "Clock " + std::to_string(i + 1);
m.value = (0 << 8) | i;
m.flags = MODE_FLAG_HAS_PER_LED_COLOR;
m.color_mode = MODE_COLORS_PER_LED;
modes.push_back(m);
}
// Default built-in layouts for other categories
std::vector<CategoryDefinition> categories =
{
{ "Gaming", 1 },
{ "Work", 2 },
{ "Study", 3 },
{ "Cats", 4 },
{ "Dogs", 5 },
{ "Buzzwords", 6 },
{ "Cyberpunk", 7 },
{ "Spectrum", 8 }
};
for(const CategoryDefinition& cat : categories)
{
mode m;
m.name = cat.name;
m.value = (cat.category_id << 8) | 0;
m.flags = MODE_FLAG_HAS_PER_LED_COLOR;
m.color_mode = MODE_COLORS_PER_LED;
modes.push_back(m);
}
// Default to Clock 1 mode
active_mode = 0;
SetupZones();
}
RGBController_EdifierHaloPixelbarScreen::~RGBController_EdifierHaloPixelbarScreen()
{
delete controller;
}
void RGBController_EdifierHaloPixelbarScreen::SetupZones()
{
zone screen_zone;
screen_zone.name = "Screen Color";
screen_zone.type = ZONE_TYPE_SINGLE;
screen_zone.leds_min = 1;
screen_zone.leds_max = 1;
screen_zone.leds_count = 1;
zones.push_back(screen_zone);
led screen_led;
screen_led.name = "Screen Text";
leds.push_back(screen_led);
SetupColors();
}
void RGBController_EdifierHaloPixelbarScreen::DeviceUpdateLEDs()
{
if(!first_leds_update_ignored)
{
first_leds_update_ignored = true;
return;
}
unsigned char r = 255;
unsigned char g = 255;
unsigned char b = 255;
if(!colors.empty())
{
RGBColor color = colors[0];
r = RGBGetRValue(color);
g = RGBGetGValue(color);
b = RGBGetBValue(color);
}
// For all other modes, color updates are sent using SetPixelScreenColor
controller->SetPixelScreenColor(r, g, b);
}
void RGBController_EdifierHaloPixelbarScreen::DeviceUpdateZoneLEDs(int)
{
DeviceUpdateLEDs();
}
void RGBController_EdifierHaloPixelbarScreen::DeviceUpdateSingleLED(int)
{
DeviceUpdateLEDs();
}
void RGBController_EdifierHaloPixelbarScreen::DeviceUpdateMode()
{
if(!first_mode_update_ignored)
{
first_mode_update_ignored = true;
return;
}
int active_idx = GetActiveMode();
unsigned char r = 255;
unsigned char g = 255;
unsigned char b = 255;
if(!colors.empty())
{
RGBColor color = colors[0];
r = RGBGetRValue(color);
g = RGBGetGValue(color);
b = RGBGetBValue(color);
}
unsigned int mode_val = modes[active_idx].value;
unsigned char category = (mode_val >> 8) & 0xFF;
unsigned char index = mode_val & 0xFF;
if(category == 0) // Clock category (11 built-in styles)
{
controller->SetScreenScene(0, index, r, g, b);
}
else // Other categories (Gaming, Working, etc.) use the default built-in layouts
{
controller->SetScreenUIModel(category, r, g, b);
}
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_EdifierHaloPixelbarScreen.h |
| |
| RGBController for Edifier Halo PixelBar Screen Color |
| |
| Bartholomew Ho <[email protected]> 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <memory>
#include "RGBController.h"
#include "EdifierHaloController.h"
class RGBController_EdifierHaloPixelbarScreen : public RGBController
{
public:
RGBController_EdifierHaloPixelbarScreen(EdifierHaloController* controller_ptr);
~RGBController_EdifierHaloPixelbarScreen();
void SetupZones();
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
private:
EdifierHaloController* controller;
bool first_leds_update_ignored;
bool first_mode_update_ignored;
};
@@ -1,133 +0,0 @@
/*---------------------------------------------------------*\
| EpomakerController.cpp |
| |
| Driver for Epomaker keyboard |
| |
| Alvaro Munoz (alvaromunoz) 05 Jun 2023 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <math.h>
#include "EpomakerController.h"
#include "LogManager.h"
#include "StringUtils.h"
EpomakerController::EpomakerController(hid_device* dev_handle, char *_path)
{
dev = dev_handle;
location = _path;
/*---------------------------------------------------------*\
| Get device name from HID manufacturer and product strings |
\*---------------------------------------------------------*/
wchar_t name_string[HID_MAX_STR];
hid_get_manufacturer_string(dev, name_string, HID_MAX_STR);
device_name = StringUtils::wstring_to_string(name_string);
hid_get_product_string(dev, name_string, HID_MAX_STR);
device_name.append(" ").append(StringUtils::wstring_to_string(name_string));
current_mode = EPOMAKER_MODE_ALWAYS_ON;
current_speed = EPOMAKER_SPEED_DEFAULT;
current_brightness = EPOMAKER_BRIGHTNESS_DEFAULT;
current_dazzle = EPOMAKER_OPTION_DAZZLE_OFF;
current_option = EPOMAKER_OPTION_DEFAULT;
}
EpomakerController::~EpomakerController()
{
hid_close(dev);
}
std::string EpomakerController::GetDeviceName()
{
return(device_name);
}
std::string EpomakerController::GetSerial()
{
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 EpomakerController::GetLocation()
{
return("HID: " + location);
}
void EpomakerController::SetMode(unsigned char mode, unsigned char speed, unsigned char brightness)
{
current_mode = mode;
current_speed = speed;
current_brightness = brightness;
SendUpdate();
}
void EpomakerController::SetColor(unsigned char red, unsigned char green, unsigned char blue)
{
current_red = red;
current_green = green;
current_blue = blue;
SendUpdate();
}
void EpomakerController::SetDazzle(bool is_dazzle)
{
if(is_dazzle)
{
current_dazzle = EPOMAKER_OPTION_DAZZLE_ON;
}
else
{
current_dazzle = EPOMAKER_OPTION_DAZZLE_OFF;
}
}
void EpomakerController::SetOption(unsigned char option)
{
current_option = option;
}
void EpomakerController::SendUpdate()
{
unsigned char buffer[EPOMAKER_PACKET_LENGTH + 1] = { 0x00 };
buffer[EPOMAKER_BYTE_COMMAND] = EPOMAKER_COMMAND_RGB;
buffer[EPOMAKER_BYTE_MODE] = current_mode;
buffer[EPOMAKER_BYTE_SPEED] = current_speed;
buffer[EPOMAKER_BYTE_BRIGHTNESS] = current_brightness;
buffer[EPOMAKER_BYTE_FLAGS] = current_option | current_dazzle;
buffer[EPOMAKER_BYTE_RED] = current_red;
buffer[EPOMAKER_BYTE_GREEN] = current_green;
buffer[EPOMAKER_BYTE_BLUE] = current_blue;
int sum_bits = 0;
for(int i = EPOMAKER_BYTE_COMMAND; i <= EPOMAKER_BYTE_BLUE; i++)
{
sum_bits += buffer[i];
}
int next_pow2 = (int)(pow(2, ceil(log2((double)(sum_bits)))));
int filler = next_pow2 - sum_bits - 1;
buffer[EPOMAKER_BYTE_FILLER] = filler;
int send_buffer_result = hid_send_feature_report(dev, buffer, (sizeof(buffer) / sizeof(buffer[0])));
if(send_buffer_result<0)
{
LOG_ERROR("[EPOMAKER]: Send Buffer Error. HIDAPI Error: %ls", hid_error(dev));
}
}
@@ -1,50 +0,0 @@
/*---------------------------------------------------------*\
| EpomakerControllerDetect.cpp |
| |
| Detector for Epomaker keyboard |
| |
| Alvaro Munoz (alvaromunoz) 05 Jun 2023 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "DetectionManager.h"
#include "EpomakerController.h"
#include "RGBController_EpomakerController.h"
#define EPOMAKER_VID 0x3151
#define EPOMAKER_TH80_Pro_USB_PID 0x4010
#define ATTACKSHARK_K86_USB_PID 0x4015
#define EPOMAKER_TH80_Pro_Dongle_PID 0x4011 /* Attack shark's Dongle is the same. */
#define EPOMAKER_TH80_Pro_BT_PID 0x4013
#define ATTACKSHARK_K86_BT_PID 0x401
DetectedControllers DetectEpomakerControllers(hid_device_info* info, const std::string&)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
EpomakerController* controller = new EpomakerController(dev, info->path);
RGBController_EpomakerController* rgb_controller = new RGBController_EpomakerController(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_I("Epomaker TH80 Pro (USB Cable)", DetectEpomakerControllers, EPOMAKER_VID, EPOMAKER_TH80_Pro_USB_PID, 2);
REGISTER_HID_DETECTOR_I("Epomaker TH80 Pro (USB Dongle)", DetectEpomakerControllers, EPOMAKER_VID, EPOMAKER_TH80_Pro_Dongle_PID, 2);
REGISTER_HID_DETECTOR_I("Attack Shark K86 (USB Cable)", DetectEpomakerControllers, EPOMAKER_VID, ATTACKSHARK_K86_USB_PID, 2);
/*---------------------------------------------------------*\
| Bluetooth Not implemented |
\*---------------------------------------------------------*/
//REGISTER_HID_DETECTOR("Epomaker TH80 Pro (Bluetooth)", DetectEpomakerControllers, EPOMAKER_VID, EPOMAKER_TH80_Pro_BT_PID);
//REGISTER_HID_DETECTOR("Attack Shark K86 (Bluetooth)", DetectEpomakerControllers, EPOMAKER_VID, ATTACKSHARK_K86_BT_PID);
@@ -44,8 +44,9 @@ bool TestForGigabyteRGBFusion2BlackwellGPUController(i2c_smbus_interface* bus, u
//GeForce RTX 5070 Ti Gaming OC 16G 0x01 0x01 0x01 0x00
//GeForce RTX 5070 Gaming OC 12G 0x01 0x01 0x01 0x00
//GeForce RTX 5080 AORUS MASTER 16G 0x01 0x01 0x01 0x10
//Radeon RX 9070 XT GAMING OC 16G 0x01 0x02 0x01 0x00
if(res < 0 || data_readpkt[0] != 0x01 || data_readpkt[1] != 0x01 || data_readpkt[2] != 0x01)
if(res < 0 || data_readpkt[0] != 0x01 || (data_readpkt[1] != 0x01 && data_readpkt[1] != 0x02) || data_readpkt[2] != 0x01)
{
// Assemble C-string with respons for debugging
std::string text = "";
@@ -57,7 +58,7 @@ bool TestForGigabyteRGBFusion2BlackwellGPUController(i2c_smbus_interface* bus, u
text.append(str);
}
LOG_DEBUG("[%s] at address 0x%02X invalid. Expected 0x01 0x01 0x01 [0x*] but received:%s", GIGABYTEGPU_CONTROLLER_NAME3, address, text.c_str());
LOG_DEBUG("[%s] at address 0x%02X invalid. Expected 0x01 0x01/0x02 0x01 [0x*] but received:%s", GIGABYTEGPU_CONTROLLER_NAME3, address, text.c_str());
pass = false;
}
@@ -596,8 +596,8 @@ static const gb_fusion2_zone common_team_up_logo_11_zone =
static const gb_fusion2_zone common_wifi_ant_9_zone =
{
LED9,
1,
1,
2,
2,
"WIFI Antenna"
};
@@ -3930,6 +3930,23 @@ static const gb_fusion2_device b550_vision_dp_device =
"B550 VISION D-P",
};
static const gb_fusion2_device b550_ud_ac_device =
{
&it5702_24_device,
0x0140005F,
0,
"B550 UD AC",
};
static const gb_fusion2_device b550_ud_ac_y1_device =
{
&it5702_24_device,
0x0140005F,
0,
"B550 UD AC-Y1",
};
static const gb_fusion2_device b550m_aor_elite_device =
{
&it5702_24_device,
@@ -4542,6 +4559,14 @@ static const gb_fusion2_device z490_aor_ultra_g2_device =
"Z490 AORUS ULTRA G2",
};
static const gb_fusion2_device z490_vision_d_device =
{
&it5702_30_device,
0x0180005F,
0,
"Z490 VISION D",
};
static const gb_fusion2_device b760m_aor_pro_device =
{
&it5702_30_device,
@@ -4894,48 +4919,13 @@ static const gb_fusion2_device b760m_aor_elite_x_ax_device =
| |
| Zone "D_LED1" : Linear |
| Zone "D_LED2" : Linear |
| Zone "IO Cover" : Single |
| Zone "LED_C1" : Single |
| Zone "Chipset Accent" : Single |
| Zone "PCI-E Accent" : Single |
| Zone "LED_C2" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_36_device =
{
&common_d_led1_zone,
&common_d_led2_zone,
&common_io_cov_btm_1_zone,
&common_io_cov_btm_mid_2_zone,
&common_chip_io_cov_tm_3_zone,
&common_io_cov_top_4_zone,
&common_led_c2_5_zone,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
};
static const gb_fusion2_device z490_vision_d_device =
{
&it5702_36_device,
0x0180005F,
0,
"Z490 VISION D",
};
/*-------------------------------------------------------------*\
| Layout 37 048D:5702 |
| |
| Zone "D_LED1" : Linear |
| Zone "D_LED2" : Linear |
| Zone "IO Cover (Top)" : Single |
| Zone "LED_C1" : Single |
| Zone "LED_CPU" : Single |
| Zone "IO Cover (Bottom)" : Single |
| Zone "LED_C2" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_37_device =
static gb_fusion2_layout it5702_36_device =
{
&common_d_led1_zone,
&common_d_led2_zone,
@@ -4953,7 +4943,7 @@ static gb_fusion2_layout it5702_37_device =
static const gb_fusion2_device b650_aor_pro_ax_device =
{
&it5702_37_device,
&it5702_36_device,
0x09A001DF,
0,
"B650 AORUS PRO AX",
@@ -4961,7 +4951,7 @@ static const gb_fusion2_device b650_aor_pro_ax_device =
static const gb_fusion2_device b650e_aor_mstr_device =
{
&it5702_37_device,
&it5702_36_device,
0x09A001DF,
0,
"B650E AORUS MASTER",
@@ -4969,14 +4959,14 @@ static const gb_fusion2_device b650e_aor_mstr_device =
static const gb_fusion2_device x670e_aor_mstr_device =
{
&it5702_37_device,
&it5702_36_device,
0x024001DF,
0,
"X670E AORUS MASTER",
};
/*-------------------------------------------------------------*\
| Layout 38 048D:5702 |
| Layout 37 048D:5702 |
| |
| Zone "D_LED1" : Linear |
| Zone "D_LED2" : Linear |
@@ -4986,7 +4976,7 @@ static const gb_fusion2_device x670e_aor_mstr_device =
| Zone "Board Accent (Right Side, Top Middle)" : Single |
| Zone "LED_C2" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_38_device =
static gb_fusion2_layout it5702_37_device =
{
&common_d_led1_zone,
&common_d_led2_zone,
@@ -5004,7 +4994,7 @@ static gb_fusion2_layout it5702_38_device =
static const gb_fusion2_device b660_aor_mstr_device =
{
&it5702_38_device,
&it5702_37_device,
0x094001DF,
0,
"B660 AORUS MASTER",
@@ -5012,7 +5002,7 @@ static const gb_fusion2_device b660_aor_mstr_device =
static const gb_fusion2_device b660_aor_mstr_ddr4_device =
{
&it5702_38_device,
&it5702_37_device,
0x094001DF,
0,
"B660 AORUS MASTER DDR4",
@@ -5020,7 +5010,7 @@ static const gb_fusion2_device b660_aor_mstr_ddr4_device =
static const gb_fusion2_device b760_aor_mstr_ddr4_device =
{
&it5702_38_device,
&it5702_37_device,
0x094001DF,
0,
"B760 AORUS MASTER DDR4",
@@ -5028,7 +5018,7 @@ static const gb_fusion2_device b760_aor_mstr_ddr4_device =
static const gb_fusion2_device z690_aor_elite_device =
{
&it5702_38_device,
&it5702_37_device,
0x016001DF,
0,
"Z690 AORUS ELITE",
@@ -5036,7 +5026,7 @@ static const gb_fusion2_device z690_aor_elite_device =
static const gb_fusion2_device z690_aor_elite_ax_2_device =
{
&it5702_38_device,
&it5702_37_device,
0x016001DF,
0,
"Z690 AORUS ELITE AX",
@@ -5044,7 +5034,7 @@ static const gb_fusion2_device z690_aor_elite_ax_2_device =
static const gb_fusion2_device z690_aor_elite_ax_ddr4_device =
{
&it5702_38_device,
&it5702_37_device,
0x016001DF,
0,
"Z690 AORUS ELITE AX DDR4",
@@ -5052,7 +5042,7 @@ static const gb_fusion2_device z690_aor_elite_ax_ddr4_device =
static const gb_fusion2_device z690_aor_elite_ax_ddr4_v2_device =
{
&it5702_38_device,
&it5702_37_device,
0x016001DF,
0,
"Z690 AORUS ELITE AX DDR4 V2",
@@ -5060,7 +5050,7 @@ static const gb_fusion2_device z690_aor_elite_ax_ddr4_v2_device =
static const gb_fusion2_device z690_aor_elite_ddr4_device =
{
&it5702_38_device,
&it5702_37_device,
0x016001DF,
0,
"Z690 AORUS ELITE DDR4",
@@ -5077,7 +5067,7 @@ static const gb_fusion2_device z690_aor_elite_ddr4_device =
| Zone "PCI-E Accent" : Single |
| Zone "LED_C2" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_39_device =
static gb_fusion2_layout it5702_38_device =
{
&common_d_led1_zone,
&common_d_led2_zone,
@@ -5095,7 +5085,7 @@ static gb_fusion2_layout it5702_39_device =
static const gb_fusion2_device b550_aor_elite_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS ELITE",
@@ -5103,7 +5093,7 @@ static const gb_fusion2_device b550_aor_elite_device =
static const gb_fusion2_device b550_aor_elite_ax_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS ELITE AX",
@@ -5111,7 +5101,7 @@ static const gb_fusion2_device b550_aor_elite_ax_device =
static const gb_fusion2_device b550_aor_elite_ax_v2_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS ELITE AX V2",
@@ -5119,7 +5109,7 @@ static const gb_fusion2_device b550_aor_elite_ax_v2_device =
static const gb_fusion2_device b550_aor_elite_ax_v3_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS ELITE AX V3",
@@ -5127,7 +5117,7 @@ static const gb_fusion2_device b550_aor_elite_ax_v3_device =
static const gb_fusion2_device b550_aor_elite_v2_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS ELITE V2",
@@ -5135,7 +5125,7 @@ static const gb_fusion2_device b550_aor_elite_v2_device =
static const gb_fusion2_device b550_aor_pro_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS PRO",
@@ -5143,7 +5133,7 @@ static const gb_fusion2_device b550_aor_pro_device =
static const gb_fusion2_device b550_aor_pro_ac_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS PRO AC",
@@ -5151,7 +5141,7 @@ static const gb_fusion2_device b550_aor_pro_ac_device =
static const gb_fusion2_device b550_aor_pro_ax_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS PRO AX",
@@ -5159,7 +5149,7 @@ static const gb_fusion2_device b550_aor_pro_ax_device =
static const gb_fusion2_device b550_aor_pro_v2_device =
{
&it5702_39_device,
&it5702_38_device,
0x0170005F,
0,
"B550 AORUS PRO V2",
@@ -5167,7 +5157,7 @@ static const gb_fusion2_device b550_aor_pro_v2_device =
static const gb_fusion2_device x570s_aor_pro_ax_device =
{
&it5702_39_device,
&it5702_38_device,
0x0150015F,
0,
"X570S AORUS PRO AX",
@@ -5178,7 +5168,7 @@ static const gb_fusion2_device x570s_aor_pro_ax_device =
| |
| Zone "IO Cover" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_40_device =
static gb_fusion2_layout it5702_39_device =
{
&common_io_cov_1_zone,
nullptr,
@@ -5196,7 +5186,7 @@ static gb_fusion2_layout it5702_40_device =
static const gb_fusion2_device z790_aor_mstr_x_2_device =
{
&it5702_40_device,
&it5702_39_device,
0x123001DF,
1,
"Z790 AORUS MASTER X",
@@ -5204,7 +5194,7 @@ static const gb_fusion2_device z790_aor_mstr_x_2_device =
static const gb_fusion2_device z790_aor_mstr_2_device =
{
&it5702_40_device,
&it5702_39_device,
0x015001DF,
1,
"Z790 AORUS MASTER",
@@ -5212,7 +5202,7 @@ static const gb_fusion2_device z790_aor_mstr_2_device =
static const gb_fusion2_device x670_aor_xtrm_2_device =
{
&it5702_40_device,
&it5702_39_device,
0x023001DF,
1,
"X670E AORUS XTREME",
@@ -5223,7 +5213,7 @@ static const gb_fusion2_device x670_aor_xtrm_2_device =
| |
| Zone "Chipset Accent" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_41_device =
static gb_fusion2_layout it5702_40_device =
{
&common_chip_acc_3_zone,
nullptr,
@@ -5241,7 +5231,7 @@ static gb_fusion2_layout it5702_41_device =
static const gb_fusion2_device z790_aor_xtrm_2_device =
{
&it5702_41_device,
&it5702_40_device,
0x026001DF,
1,
"Z790 AORUS XTREME",
@@ -5249,7 +5239,7 @@ static const gb_fusion2_device z790_aor_xtrm_2_device =
static const gb_fusion2_device z790_aor_xtrm_x_2_device =
{
&it5702_41_device,
&it5702_40_device,
0x124001DF,
1,
"Z790 AORUS XTREME X",
@@ -5261,7 +5251,7 @@ static const gb_fusion2_device z790_aor_xtrm_x_2_device =
| Zone "IO Cover" : Single |
| Zone "LED_CPU" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_42_device =
static gb_fusion2_layout it5702_41_device =
{
&common_chip_acc_3_zone,
&common_led_cpu_8_zone,
@@ -5279,7 +5269,7 @@ static gb_fusion2_layout it5702_42_device =
static const gb_fusion2_device z690_aor_xtrm_waterforce_2_device =
{
&it5702_42_device,
&it5702_41_device,
0x029001DF,
1,
"Z690 AORUS XTREME WATERFORCE",
@@ -5292,7 +5282,7 @@ static const gb_fusion2_device z690_aor_xtrm_waterforce_2_device =
| Zone "Chipset Accent" : Single |
| Zone "LED_CPU" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_43_device =
static gb_fusion2_layout it5702_42_device =
{
&common_chip_acc_3_zone,
&common_chip_acc_3_zone,
@@ -5310,7 +5300,7 @@ static gb_fusion2_layout it5702_43_device =
static const gb_fusion2_device z490_aor_xtrm_2_device =
{
&it5702_43_device,
&it5702_42_device,
0x029001DF,
1,
"Z490 AORUS XTREME",
@@ -5318,7 +5308,7 @@ static const gb_fusion2_device z490_aor_xtrm_2_device =
static const gb_fusion2_device z490_aor_xtrm_waterforce_2_device =
{
&it5702_43_device,
&it5702_42_device,
0x029001DF,
1,
"Z490 AORUS XTREME WATERFORCE",
@@ -5326,7 +5316,7 @@ static const gb_fusion2_device z490_aor_xtrm_waterforce_2_device =
static const gb_fusion2_device z590_aor_xtrm_waterforce_2_device =
{
&it5702_43_device,
&it5702_42_device,
0x029001DF,
1,
"Z590 AORUS XTREME WATERFORCE",
@@ -5340,7 +5330,7 @@ static const gb_fusion2_device z590_aor_xtrm_waterforce_2_device =
| Zone "RAM Cover" : Single |
| Zone "LED_CPU" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_44_device =
static gb_fusion2_layout it5702_43_device =
{
&common_chip_acc_3_zone,
&common_chip_acc_3_zone,
@@ -5358,7 +5348,7 @@ static gb_fusion2_layout it5702_44_device =
static const gb_fusion2_device z690_aor_xtrm_2_device =
{
&it5702_44_device,
&it5702_43_device,
0x028001DF,
1,
"Z690 AORUS XTREME",
@@ -5375,7 +5365,7 @@ static const gb_fusion2_device z690_aor_xtrm_2_device =
| Zone "IO Cover (Bottom)" : Single |
| Zone "LED_C2" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_45_device =
static gb_fusion2_layout it5702_44_device =
{
&common_d_led1_zone,
&common_d_led2_zone,
@@ -5393,7 +5383,7 @@ static gb_fusion2_layout it5702_45_device =
static const gb_fusion2_device z690_aor_mstr_device =
{
&it5702_45_device,
&it5702_44_device,
0x017001DF,
0,
"Z690 AORUS MASTER",
@@ -5409,7 +5399,7 @@ static const gb_fusion2_device z690_aor_mstr_device =
| Zone "Board Accent (Right Side)" : Single |
| Zone "LED_C2" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_46_device =
static gb_fusion2_layout it5702_45_device =
{
&common_d_led1_zone,
&common_d_led2_zone,
@@ -5427,7 +5417,7 @@ static gb_fusion2_layout it5702_46_device =
static const gb_fusion2_device x570s_aor_elite_device =
{
&it5702_46_device,
&it5702_45_device,
0x0120015F,
0,
"X570S AORUS ELITE",
@@ -5435,7 +5425,7 @@ static const gb_fusion2_device x570s_aor_elite_device =
static const gb_fusion2_device x570s_aor_elite_ax_device =
{
&it5702_46_device,
&it5702_45_device,
0x0120015F,
0,
"X570S AORUS ELITE AX",
@@ -5443,7 +5433,7 @@ static const gb_fusion2_device x570s_aor_elite_ax_device =
static const gb_fusion2_device x570s_gmg_x_device =
{
&it5702_46_device,
&it5702_45_device,
0x0120015F,
0,
"X570S GAMING X",
@@ -5458,7 +5448,7 @@ static const gb_fusion2_device x570s_gmg_x_device =
| Zone "Aorus Logo" : Single |
| Zone "LED_C2" : Single |
\*-------------------------------------------------------------*/
static gb_fusion2_layout it5702_47_device =
static gb_fusion2_layout it5702_46_device =
{
&common_d_led1_zone,
&common_d_led2_zone,
@@ -5475,7 +5465,7 @@ static gb_fusion2_layout it5702_47_device =
static const gb_fusion2_device a520m_aor_elite_device =
{
&it5702_47_device,
&it5702_46_device,
0x0050005F,
0,
"A520M AORUS ELITE",
@@ -6987,7 +6977,6 @@ static const gb_fusion2_device z890_aor_mstr_ai_top_5711_device =
| Zone "ARGB_V2_1" : Linear |
| Zone "ARGB_V2_2" : Linear |
| Zone "ARGB_V2_3" : Linear |
| Zone "ARGB_V2_4" : Linear |
| Zone "LED_C" : Single |
| Zone "WIFI Antenna" : Single |
| Zone "IO Cover" : Single |
@@ -6998,7 +6987,6 @@ static gb_fusion2_layout it5711_14_device =
&common_argb_v2_1_zone,
&common_argb_v2_2_zone,
&common_argb_v2_3_zone,
&common_argb_v2_4_zone,
&common_led_c_5_zone,
&common_wifi_ant_9_zone,
&common_io_cov_10_zone,
@@ -7007,6 +6995,7 @@ static gb_fusion2_layout it5711_14_device =
nullptr,
nullptr,
nullptr,
nullptr,
};
static const gb_fusion2_device x870e_aor_xtrm_ai_top_5711_device =
@@ -7393,6 +7382,8 @@ const gb_fusion2_device* gb_fusion2_device_list_data[] =
&b550_gmg_x_v2_device,
&b550_vision_d_device,
&b550_vision_dp_device,
&b550_ud_ac_device,
&b550_ud_ac_y1_device,
&b550i_aor_pro_ax_device,
&b550m_aor_elite_device,
&b550m_aor_elite_ax_device,
@@ -575,7 +575,7 @@ void RGBController_RGBFusion2USB::SetupZones()
continue;
}
bool fixed_zone = (zone_at_idx->leds_min == zone_at_idx->leds_max);
bool fixed_zone = (zone_at_idx->leds_min == 1 && zone_at_idx->leds_max == 1);
const Gen2StripInfo* gen2_info = nullptr;
@@ -208,7 +208,8 @@ void NanoleafController::UpdateLEDs(std::vector<RGBColor>& colors)
free(message);
}
else if((model == NANOLEAF_CANVAS_MODEL)
|| (model == NANOLEAF_SHAPES_MODEL))
|| (model == NANOLEAF_SHAPES_MODEL)
|| (model == NANOLEAF_LINES_MODEL))
{
/*---------------------------------------------------------*\
| Protocol V2 - https://forum.nanoleaf.me/docs |
@@ -275,7 +276,8 @@ void NanoleafController::StartExternalControl()
}
}
else if((model == NANOLEAF_CANVAS_MODEL)
|| (model == NANOLEAF_SHAPES_MODEL))
|| (model == NANOLEAF_SHAPES_MODEL)
|| (model == NANOLEAF_LINES_MODEL))
{
/*---------------------------------------------------------*\
| Protocol v2 does not return anything, use device IP and |
@@ -18,6 +18,7 @@
#define NANOLEAF_LIGHT_PANELS_MODEL "NL22"
#define NANOLEAF_CANVAS_MODEL "NL29"
#define NANOLEAF_SHAPES_MODEL "NL42"
#define NANOLEAF_LINES_MODEL "NL59"
class NanoleafController
{
@@ -0,0 +1,184 @@
/*---------------------------------------------------------*\
| RGBController_AkkoKeyboard.cpp |
| |
| RGBController for Akko Multi-modes keyboards |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_AkkoKeyboard.h"
/**------------------------------------------------------------------*\
@name Akko 3068B Plus
@category Keyboard
@type USB
@save :white_check_mark:
@direct :x:
@effects :white_check_mark:
@detectors DetectRoyuanAkkoKeyboardControllers
@comment
Global-color hardware lighting support for Akko/RongYuan keyboards
using USB ID 3151:4003 in wired mode.
\*-------------------------------------------------------------------*/
RGBController_AkkoKeyboard::RGBController_AkkoKeyboard(RoyuanKeyboardController* controller_ptr)
{
controller = controller_ptr;
name = "Akko 3068B Plus";
vendor = "Akko";
type = DEVICE_TYPE_KEYBOARD;
description = "Akko 3068B Plus lighting controller";
serial = controller->GetSerial();
location = controller->GetLocation();
mode off;
off.name = "Off";
off.value = ROYUAN_AKKO_EFFECT_OFF;
off.flags = MODE_FLAG_AUTOMATIC_SAVE;
off.color_mode = MODE_COLORS_NONE;
modes.push_back(off);
mode static_mode;
static_mode.name = "Static";
static_mode.value = ROYUAN_AKKO_EFFECT_STATIC;
static_mode.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_PER_LED_COLOR;
static_mode.color_mode = MODE_COLORS_PER_LED;
static_mode.brightness_min = ROYUAN_AKKO_BRIGHTNESS_MIN;
static_mode.brightness_max = ROYUAN_AKKO_BRIGHTNESS_MAX;
static_mode.brightness = ROYUAN_AKKO_BRIGHTNESS_DEFAULT;
modes.push_back(static_mode);
mode breathing;
breathing.name = "Breathing";
breathing.value = ROYUAN_AKKO_EFFECT_BREATHING;
breathing.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
breathing.color_mode = MODE_COLORS_PER_LED;
breathing.brightness_min = ROYUAN_AKKO_BRIGHTNESS_MIN;
breathing.brightness_max = ROYUAN_AKKO_BRIGHTNESS_MAX;
breathing.brightness = ROYUAN_AKKO_BRIGHTNESS_DEFAULT;
breathing.speed_min = ROYUAN_AKKO_SPEED_MIN;
breathing.speed_max = ROYUAN_AKKO_SPEED_MAX;
breathing.speed = ROYUAN_AKKO_SPEED_DEFAULT;
modes.push_back(breathing);
mode wave;
wave.name = "Wave";
wave.value = ROYUAN_AKKO_EFFECT_WAVE;
wave.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
wave.color_mode = MODE_COLORS_PER_LED;
wave.brightness_min = ROYUAN_AKKO_BRIGHTNESS_MIN;
wave.brightness_max = ROYUAN_AKKO_BRIGHTNESS_MAX;
wave.brightness = ROYUAN_AKKO_BRIGHTNESS_DEFAULT;
wave.speed_min = ROYUAN_AKKO_SPEED_MIN;
wave.speed_max = ROYUAN_AKKO_SPEED_MAX;
wave.speed = ROYUAN_AKKO_SPEED_DEFAULT;
modes.push_back(wave);
mode ripple;
ripple.name = "Ripple";
ripple.value = ROYUAN_AKKO_EFFECT_RIPPLE;
ripple.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
ripple.color_mode = MODE_COLORS_PER_LED;
ripple.brightness_min = ROYUAN_AKKO_BRIGHTNESS_MIN;
ripple.brightness_max = ROYUAN_AKKO_BRIGHTNESS_MAX;
ripple.brightness = ROYUAN_AKKO_BRIGHTNESS_DEFAULT;
ripple.speed_min = ROYUAN_AKKO_SPEED_MIN;
ripple.speed_max = ROYUAN_AKKO_SPEED_MAX;
ripple.speed = ROYUAN_AKKO_SPEED_DEFAULT;
modes.push_back(ripple);
mode reactive;
reactive.name = "Reactive";
reactive.value = ROYUAN_AKKO_EFFECT_REACTIVE;
reactive.flags = MODE_FLAG_AUTOMATIC_SAVE | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_PER_LED_COLOR;
reactive.color_mode = MODE_COLORS_PER_LED;
reactive.brightness_min = ROYUAN_AKKO_BRIGHTNESS_MIN;
reactive.brightness_max = ROYUAN_AKKO_BRIGHTNESS_MAX;
reactive.brightness = ROYUAN_AKKO_BRIGHTNESS_DEFAULT;
reactive.speed_min = ROYUAN_AKKO_SPEED_MIN;
reactive.speed_max = ROYUAN_AKKO_SPEED_MAX;
reactive.speed = ROYUAN_AKKO_SPEED_DEFAULT;
modes.push_back(reactive);
for(unsigned int mode_index = 0; mode_index < modes.size(); mode_index++)
{
if(modes[mode_index].value == controller->GetMode())
{
active_mode = mode_index;
break;
}
}
if(modes[active_mode].flags & MODE_FLAG_HAS_BRIGHTNESS)
{
modes[active_mode].brightness = controller->GetBrightness();
}
if(modes[active_mode].flags & MODE_FLAG_HAS_SPEED)
{
modes[active_mode].speed = controller->GetSpeed();
}
SetupZones();
colors[0] = ToRGBColor(controller->GetRed(), controller->GetGreen(), controller->GetBlue());
}
RGBController_AkkoKeyboard::~RGBController_AkkoKeyboard()
{
delete controller;
}
void RGBController_AkkoKeyboard::SetupZones()
{
zone keyboard_zone;
keyboard_zone.name = "Keyboard";
keyboard_zone.type = ZONE_TYPE_SINGLE;
keyboard_zone.leds_min = 1;
keyboard_zone.leds_max = 1;
keyboard_zone.leds_count = 1;
keyboard_zone.matrix_map.Reset();
zones.push_back(keyboard_zone);
led keyboard_led;
keyboard_led.name = "Keyboard LEDs";
keyboard_led.value = 0x00;
leds.push_back(keyboard_led);
SetupColors();
}
void RGBController_AkkoKeyboard::ResizeZone(int /*zone*/, int /*new_size*/)
{
}
void RGBController_AkkoKeyboard::DeviceUpdateLEDs()
{
unsigned char red = RGBGetRValue(colors[0]);
unsigned char grn = RGBGetGValue(colors[0]);
unsigned char blu = RGBGetBValue(colors[0]);
controller->SetColor(red, grn, blu);
}
void RGBController_AkkoKeyboard::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_AkkoKeyboard::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_AkkoKeyboard::DeviceUpdateMode()
{
controller->SetMode(
modes[active_mode].value,
modes[active_mode].speed,
modes[active_mode].brightness
);
DeviceUpdateLEDs();
}
@@ -0,0 +1,32 @@
/*---------------------------------------------------------*\
| RGBController_AkkoKeyboard.h |
| |
| RGBController for Akko Multi-modes keyboards |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "RoyuanKeyboardController.h"
class RGBController_AkkoKeyboard : public RGBController
{
public:
RGBController_AkkoKeyboard(RoyuanKeyboardController* controller_ptr);
~RGBController_AkkoKeyboard();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
RoyuanKeyboardController* controller;
};
@@ -0,0 +1,21 @@
/*---------------------------------------------------------*\
| EpomakerController.cpp |
| |
| Compatibility wrapper for ROYUAN-based keyboards |
| |
| Alvaro Munoz (alvaromunoz) 05 Jun 2023 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "EpomakerController.h"
EpomakerController::EpomakerController(hid_device* dev_handle, char* path)
: RoyuanKeyboardController(dev_handle, path, RoyuanKeyboardProfile::EpomakerLegacy())
{
}
EpomakerController::~EpomakerController()
{
}
@@ -13,6 +13,7 @@
#include <string>
#include <hidapi.h>
#include "../RoyuanKeyboardController/RoyuanKeyboardController.h"
#define EPOMAKER_PACKET_LENGTH 0x40
#define EPOMAKER_COMMAND_RGB 0x07
@@ -90,36 +91,9 @@ enum
EPOMAKER_OPTION_PEAK_TURN_CLOCKWISE = 0x10,
};
class EpomakerController
class EpomakerController : public RoyuanKeyboardController
{
public:
EpomakerController(hid_device* dev_handle, char *_path);
EpomakerController(hid_device* dev_handle, char* path);
~EpomakerController();
std::string GetDeviceName();
std::string GetSerial();
std::string GetLocation();
void SetDazzle(bool is_dazzle);
void SetOption(unsigned char option);
void SetMode(unsigned char mode, unsigned char speed, unsigned char brightness);
void SetColor(unsigned char red, unsigned char green, unsigned char blue);
private:
std::string device_name;
std::string location;
hid_device* dev;
unsigned char current_mode;
unsigned char current_speed;
unsigned char current_brightness;
unsigned char current_dazzle;
unsigned char current_option;
unsigned char current_red;
unsigned char current_green;
unsigned char current_blue;
void SendUpdate();
};
@@ -19,7 +19,7 @@
@save :robot:
@direct :x:
@effects :white_check_mark:
@detectors DetectEpomakerControllers
@detectors DetectRoyuanLegacyKeyboardControllers
@comment
\*-------------------------------------------------------------------*/
@@ -0,0 +1,308 @@
/*---------------------------------------------------------*\
| RoyuanKeyboardController.cpp |
| |
| Driver for ROYUAN-based keyboard lighting controllers |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RoyuanKeyboardController.h"
#include "LogManager.h"
#include "StringUtils.h"
#include <algorithm>
#include <chrono>
#include <cstring>
#include <thread>
RoyuanKeyboardProfile RoyuanKeyboardProfile::EpomakerLegacy()
{
RoyuanKeyboardProfile profile;
profile.protocol = RoyuanKeyboardProtocol::Legacy;
profile.checksum_type = RoyuanChecksumType::NextPowerOfTwo;
profile.speed_max = 0x05;
profile.speed_default = 0x04;
profile.brightness_max = 0x04;
profile.brightness_default = 0x04;
profile.option_default = 0x00;
profile.dazzle_default = 0x07;
profile.reverse_speed = false;
profile.supports_readback = false;
profile.fallback_name = "ROYUAN Keyboard";
return(profile);
}
RoyuanKeyboardProfile RoyuanKeyboardProfile::AkkoBSeries()
{
RoyuanKeyboardProfile profile;
profile.protocol = RoyuanKeyboardProtocol::AkkoBSeries;
profile.checksum_type = RoyuanChecksumType::OnesComplement;
profile.speed_max = ROYUAN_AKKO_SPEED_MAX;
profile.speed_default = ROYUAN_AKKO_SPEED_DEFAULT;
profile.brightness_max = ROYUAN_AKKO_BRIGHTNESS_MAX;
profile.brightness_default = ROYUAN_AKKO_BRIGHTNESS_DEFAULT;
profile.option_default = 0x08;
profile.dazzle_default = 0x00;
profile.reverse_speed = true;
profile.supports_readback = true;
profile.fallback_name = "Akko 3068B Plus";
return(profile);
}
RoyuanKeyboardController::RoyuanKeyboardController(hid_device* dev_handle, char* path, const RoyuanKeyboardProfile& profile)
{
dev = dev_handle;
location = path;
this->profile = profile;
valid = false;
current_mode = 0x01;
current_speed = profile.speed_default;
current_brightness = profile.brightness_default;
current_dazzle = profile.dazzle_default;
current_option = profile.option_default;
current_red = 0x00;
current_green = 0x00;
current_blue = 0x00;
wchar_t name_string[ROYUAN_HID_MAX_STR] = { 0 };
if(hid_get_manufacturer_string(dev, name_string, ROYUAN_HID_MAX_STR) == 0)
{
device_name = StringUtils::wstring_to_string(name_string);
}
std::memset(name_string, 0, sizeof(name_string));
if(hid_get_product_string(dev, name_string, ROYUAN_HID_MAX_STR) == 0)
{
std::string product_name = StringUtils::wstring_to_string(name_string);
if(!product_name.empty())
{
if(!device_name.empty())
{
device_name.append(" ");
}
device_name.append(product_name);
}
}
if(device_name.empty())
{
device_name = profile.fallback_name;
}
valid = !profile.supports_readback || ReadState();
}
RoyuanKeyboardController::~RoyuanKeyboardController()
{
hid_close(dev);
}
bool RoyuanKeyboardController::IsValid() const
{
return(valid);
}
std::string RoyuanKeyboardController::GetDeviceName()
{
return(device_name);
}
std::string RoyuanKeyboardController::GetSerial()
{
wchar_t serial_string[ROYUAN_HID_MAX_STR] = { 0 };
if(hid_get_serial_number_string(dev, serial_string, ROYUAN_HID_MAX_STR) != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
std::string RoyuanKeyboardController::GetLocation()
{
return("HID: " + location);
}
RoyuanKeyboardProtocol RoyuanKeyboardController::GetProtocol() const
{
return(profile.protocol);
}
unsigned char RoyuanKeyboardController::GetMode() const
{
return(current_mode);
}
unsigned char RoyuanKeyboardController::GetSpeed() const
{
return(current_speed);
}
unsigned char RoyuanKeyboardController::GetBrightness() const
{
return(current_brightness);
}
unsigned char RoyuanKeyboardController::GetRed() const
{
return(current_red);
}
unsigned char RoyuanKeyboardController::GetGreen() const
{
return(current_green);
}
unsigned char RoyuanKeyboardController::GetBlue() const
{
return(current_blue);
}
unsigned char RoyuanKeyboardController::CalculateChecksum(const unsigned char* data, std::size_t length) const
{
unsigned int sum = 0;
for(std::size_t index = 0; index < length; index++)
{
sum += data[index];
}
if(profile.checksum_type == RoyuanChecksumType::OnesComplement)
{
return(static_cast<unsigned char>(0xFF - (sum & 0xFF)));
}
unsigned int next_power_of_two = 1;
while(next_power_of_two < sum)
{
next_power_of_two <<= 1;
}
return(static_cast<unsigned char>((next_power_of_two - sum - 1) & 0xFF));
}
bool RoyuanKeyboardController::ReadState()
{
unsigned char request[ROYUAN_REPORT_LENGTH + 1] = { 0x00 };
request[1] = ROYUAN_COMMAND_GET_LED_PARAMS;
request[8] = CalculateChecksum(&request[1], 7);
int result = hid_send_feature_report(dev, request, sizeof(request));
if(result < 0)
{
LOG_DEBUG("[ROYUAN]: GET_LEDPARAM request failed: %ls", hid_error(dev));
return(false);
}
for(unsigned int attempt = 0; attempt < 5; attempt++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(80));
unsigned char response[ROYUAN_REPORT_LENGTH + 1] = { 0x00 };
result = hid_get_feature_report(dev, response, sizeof(response));
if(result > 8 && response[1] == ROYUAN_COMMAND_GET_LED_PARAMS)
{
current_mode = response[2];
current_speed = response[3] <= profile.speed_max ? profile.speed_max - response[3] : profile.speed_default;
current_brightness = std::min<unsigned char>(response[4], profile.brightness_max);
current_option = response[5];
current_red = response[6];
current_green = response[7];
current_blue = response[8];
return(true);
}
}
LOG_DEBUG("[ROYUAN]: Device did not answer GET_LEDPARAM");
return(false);
}
bool RoyuanKeyboardController::SendUpdate()
{
unsigned char buffer[ROYUAN_REPORT_LENGTH + 1] = { 0x00 };
buffer[1] = ROYUAN_COMMAND_SET_LED_PARAMS;
buffer[2] = current_mode;
if(profile.reverse_speed)
{
buffer[3] = profile.speed_max - std::min<unsigned char>(current_speed, profile.speed_max);
}
else
{
buffer[3] = std::min<unsigned char>(current_speed, profile.speed_max);
}
buffer[4] = std::min<unsigned char>(current_brightness, profile.brightness_max);
buffer[5] = current_option | current_dazzle;
buffer[6] = current_red;
buffer[7] = current_green;
buffer[8] = current_blue;
buffer[9] = CalculateChecksum(&buffer[1], 8);
int result = hid_send_feature_report(dev, buffer, sizeof(buffer));
if(result < 0)
{
LOG_ERROR("[ROYUAN]: SET_LEDPARAM failed: %ls", hid_error(dev));
return(false);
}
return(true);
}
void RoyuanKeyboardController::SetDazzle(bool is_dazzle)
{
if(profile.protocol == RoyuanKeyboardProtocol::Legacy)
{
current_dazzle = is_dazzle ? 0x08 : 0x07;
}
}
void RoyuanKeyboardController::SetOption(unsigned char option)
{
current_option = option;
}
void RoyuanKeyboardController::SetMode(unsigned char mode, unsigned char speed, unsigned char brightness)
{
current_mode = mode;
current_speed = std::min<unsigned char>(speed, profile.speed_max);
current_brightness = std::min<unsigned char>(brightness, profile.brightness_max);
if(profile.protocol == RoyuanKeyboardProtocol::AkkoBSeries)
{
current_option = profile.option_default;
}
SendUpdate();
}
void RoyuanKeyboardController::SetColor(unsigned char red, unsigned char green, unsigned char blue)
{
current_red = red;
current_green = green;
current_blue = blue;
if(profile.protocol == RoyuanKeyboardProtocol::AkkoBSeries)
{
current_option = profile.option_default;
}
SendUpdate();
}
@@ -0,0 +1,112 @@
/*---------------------------------------------------------*\
| RoyuanKeyboardController.h |
| |
| Driver for ROYUAN-based keyboard lighting controllers |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <hidapi.h>
#define ROYUAN_REPORT_LENGTH 64
#define ROYUAN_HID_MAX_STR 255
#define ROYUAN_COMMAND_SET_LED_PARAMS 0x07
#define ROYUAN_COMMAND_GET_LED_PARAMS 0x87
#define ROYUAN_AKKO_EFFECT_OFF 0x00
#define ROYUAN_AKKO_EFFECT_STATIC 0x01
#define ROYUAN_AKKO_EFFECT_BREATHING 0x02
#define ROYUAN_AKKO_EFFECT_WAVE 0x04
#define ROYUAN_AKKO_EFFECT_RIPPLE 0x05
#define ROYUAN_AKKO_EFFECT_REACTIVE 0x08
#define ROYUAN_AKKO_SPEED_MIN 0x00
#define ROYUAN_AKKO_SPEED_MAX 0x04
#define ROYUAN_AKKO_SPEED_DEFAULT 0x00
#define ROYUAN_AKKO_BRIGHTNESS_MIN 0x00
#define ROYUAN_AKKO_BRIGHTNESS_MAX 0x04
#define ROYUAN_AKKO_BRIGHTNESS_DEFAULT 0x03
enum class RoyuanKeyboardProtocol
{
Legacy,
AkkoBSeries
};
enum class RoyuanChecksumType
{
NextPowerOfTwo,
OnesComplement
};
struct RoyuanKeyboardProfile
{
RoyuanKeyboardProtocol protocol;
RoyuanChecksumType checksum_type;
unsigned char speed_max;
unsigned char speed_default;
unsigned char brightness_max;
unsigned char brightness_default;
unsigned char option_default;
unsigned char dazzle_default;
bool reverse_speed;
bool supports_readback;
std::string fallback_name;
static RoyuanKeyboardProfile EpomakerLegacy();
static RoyuanKeyboardProfile AkkoBSeries();
};
class RoyuanKeyboardController
{
public:
RoyuanKeyboardController(hid_device* dev_handle, char* path, const RoyuanKeyboardProfile& profile);
virtual ~RoyuanKeyboardController();
bool IsValid() const;
std::string GetDeviceName();
std::string GetSerial();
std::string GetLocation();
RoyuanKeyboardProtocol GetProtocol() const;
unsigned char GetMode() const;
unsigned char GetSpeed() const;
unsigned char GetBrightness() const;
unsigned char GetRed() const;
unsigned char GetGreen() const;
unsigned char GetBlue() const;
void SetDazzle(bool is_dazzle);
void SetOption(unsigned char option);
void SetMode(unsigned char mode, unsigned char speed, unsigned char brightness);
void SetColor(unsigned char red, unsigned char green, unsigned char blue);
private:
std::string device_name;
std::string location;
hid_device* dev;
RoyuanKeyboardProfile profile;
bool valid;
unsigned char current_mode;
unsigned char current_speed;
unsigned char current_brightness;
unsigned char current_dazzle;
unsigned char current_option;
unsigned char current_red;
unsigned char current_green;
unsigned char current_blue;
unsigned char CalculateChecksum(const unsigned char* data, std::size_t length) const;
bool ReadState();
bool SendUpdate();
};
@@ -0,0 +1,69 @@
/*---------------------------------------------------------*\
| RoyuanKeyboardControllerDetect.cpp |
| |
| Detector for ROYUAN-based keyboard lighting controllers |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "DetectionManager.h"
#include "EpomakerController.h"
#include "RGBController_AkkoKeyboard.h"
#include "RGBController_EpomakerController.h"
#include "RoyuanKeyboardController.h"
#define ROYUAN_KEYBOARD_VID 0x3151
#define ROYUAN_AKKO_B_SERIES_PID 0x4003
#define ROYUAN_EPOMAKER_TH80_PRO_USB_PID 0x4010
#define ROYUAN_EPOMAKER_TH80_PRO_DONGLE_PID 0x4011
#define ROYUAN_ATTACKSHARK_K86_USB_PID 0x4015
DetectedControllers DetectRoyuanAkkoKeyboardControllers(hid_device_info* info, const std::string&)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
RoyuanKeyboardController* controller = new RoyuanKeyboardController(dev, info->path, RoyuanKeyboardProfile::AkkoBSeries());
if(controller->IsValid())
{
detected_controllers.push_back(new RGBController_AkkoKeyboard(controller));
}
else
{
delete controller;
}
}
return(detected_controllers);
}
DetectedControllers DetectRoyuanLegacyKeyboardControllers(hid_device_info* info, const std::string&)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
EpomakerController* controller = new EpomakerController(dev, info->path);
RGBController_EpomakerController* rgb_controller = new RGBController_EpomakerController(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_I("Akko 3068B Plus", DetectRoyuanAkkoKeyboardControllers, ROYUAN_KEYBOARD_VID, ROYUAN_AKKO_B_SERIES_PID, 0);
REGISTER_HID_DETECTOR_I("Epomaker TH80 Pro (USB Cable)", DetectRoyuanLegacyKeyboardControllers, ROYUAN_KEYBOARD_VID, ROYUAN_EPOMAKER_TH80_PRO_USB_PID, 2);
REGISTER_HID_DETECTOR_I("Epomaker TH80 Pro (USB Dongle)", DetectRoyuanLegacyKeyboardControllers, ROYUAN_KEYBOARD_VID, ROYUAN_EPOMAKER_TH80_PRO_DONGLE_PID, 2);
REGISTER_HID_DETECTOR_I("Attack Shark K86 (USB Cable)", DetectRoyuanLegacyKeyboardControllers, ROYUAN_KEYBOARD_VID, ROYUAN_ATTACKSHARK_K86_USB_PID, 2);
@@ -22,6 +22,9 @@ SteelSeriesAeroxWirelessController::SteelSeriesAeroxWirelessController(hid_devic
case AEROX_3_WIRELESS_WIRED:
name = STEELSERIES_AEROX3_WIRELESS_WIRED_NAME;
break;
case AEROX_3_GEN2_WIRELESS:
name = STEELSERIES_AEROX3_GEN2_WIRELESS_NAME;
break;
case AEROX_5_WIRELESS:
name = STEELSERIES_AEROX5_WIRELESS_NAME;
break;
@@ -75,6 +78,7 @@ bool SteelSeriesAeroxWirelessController::IsWireless()
switch(proto)
{
case AEROX_3_WIRELESS:
case AEROX_3_GEN2_WIRELESS:
case AEROX_5_WIRELESS:
case AEROX_5_DESTINY_WIRELESS:
case AEROX_5_DIABLO_WIRELESS:
@@ -20,6 +20,7 @@
#define STEELSERIES_AEROX3_WIRELESS_NAME "SteelSeries Aerox 3 Wireless (2.4 GHz wireless mode)"
#define STEELSERIES_AEROX3_WIRELESS_WIRED_NAME "SteelSeries Aerox 3 Wireless (wired mode)"
#define STEELSERIES_AEROX3_GEN2_WIRELESS_NAME "SteelSeries Aerox 3 Wireless Gen 2 (2.4 GHz wireless mode)"
#define STEELSERIES_AEROX5_WIRELESS_NAME "SteelSeries Aerox 5 Wireless (2.4 GHz wireless mode)"
#define STEELSERIES_AEROX5_WIRELESS_WIRED_NAME "SteelSeries Aerox 5 Wireless (wired mode)"
#define STEELSERIES_AEROX5_DESTINY_WIRELESS_NAME "SteelSeries Aerox 5 Wireless Destiny 2 Edition (2.4 GHz wireless mode)"
@@ -150,3 +150,18 @@ std::string SteelSeriesApexBaseController::GetVersion()
return(return_string);
}
bool SteelSeriesApexBaseController::SupportsBrightness()
{
return(false);
}
unsigned char SteelSeriesApexBaseController::GetBrightness()
{
return(APEX_BRIGHTNESS_MAX);
}
void SteelSeriesApexBaseController::SetBrightness(unsigned char /*value*/)
{
}
@@ -19,6 +19,7 @@
#define STEELSERIES_PACKET_IN_SIZE 64
#define STEELSERIES_PACKET_OUT_SIZE STEELSERIES_PACKET_IN_SIZE + 1
#define STEELSERIES_APEX_HID_TIMEOUT 100
/*-------------------------------------------------*\
| Protocol quirk applicability |
@@ -58,8 +59,17 @@ enum
APEX_2023_PACKET_ID_INIT = 0x4B, /* New Initialization */
APEX_2023_PACKET_ID_ONBOARD = 0x41, /* New Wired onboard mode */
APEX_2023_PACKET_ID_REGION = 0xF5, /* Region byte */
APEX_PACKET_ID_SET_BRIGHTNESS = 0x23, /* Illumination brightness */
APEX_PACKET_ID_GET_BRIGHTNESS = 0xA3, /* Brightness read back */
};
/*---------------------------------------------------------*\
| Range of the illumination brightness setting, the same |
| range the brightness keys on the keyboard step through |
\*---------------------------------------------------------*/
#define APEX_BRIGHTNESS_MIN 0
#define APEX_BRIGHTNESS_MAX 10
class SteelSeriesApexBaseController
{
public:
@@ -75,6 +85,14 @@ public:
virtual void SetLEDsDirect(std::vector<RGBColor> colors) = 0;
/*-----------------------------------------------------*\
| Illumination brightness, offered only by the models |
| that answer the read back request |
\*-----------------------------------------------------*/
virtual bool SupportsBrightness();
virtual unsigned char GetBrightness();
virtual void SetBrightness(unsigned char /*value*/);
steelseries_type proto_type;
protected:
@@ -65,6 +65,20 @@ RGBController_SteelSeriesApex::RGBController_SteelSeriesApex(SteelSeriesApexBase
Onboard.value = APEX_MODE_ONBOARD;
Onboard.flags = 0;
Onboard.color_mode = MODE_COLORS_NONE;
/*---------------------------------------------------------*\
| The brightness setting applies to the on-board lighting. |
| Direct mode sends absolute colors and is not affected by |
| it, so it is offered on this mode only. |
\*---------------------------------------------------------*/
if(controller->SupportsBrightness())
{
Onboard.flags |= MODE_FLAG_HAS_BRIGHTNESS;
Onboard.brightness_min = APEX_BRIGHTNESS_MIN;
Onboard.brightness_max = APEX_BRIGHTNESS_MAX;
Onboard.brightness = controller->GetBrightness();
}
modes.push_back(Onboard);
SetupZones();
@@ -143,5 +157,11 @@ void RGBController_SteelSeriesApex::DeviceUpdateSingleLED(int /*led*/)
void RGBController_SteelSeriesApex::DeviceUpdateMode()
{
std::vector<RGBColor> temp_colors;
if(modes[active_mode].flags & MODE_FLAG_HAS_BRIGHTNESS)
{
controller->SetBrightness((unsigned char)modes[active_mode].brightness);
}
controller->SetMode(modes[active_mode].value, temp_colors);
}
@@ -76,6 +76,15 @@ SteelSeriesApexController::SteelSeriesApexController(hid_device* dev_handle, ste
direct_packet_length = direct_packet_length_map.at(device_pid);
}
SendInitialization();
/*-----------------------------------------------------*\
| Illumination brightness is offered only when the |
| keyboard answers the read back request, so models |
| that do not implement it are left alone. Asked after |
| initialization, so the keyboard is in a known state |
\*-----------------------------------------------------*/
current_brightness = APEX_BRIGHTNESS_MAX;
brightness_supported = ReadBrightness(current_brightness);
}
SteelSeriesApexController::~SteelSeriesApexController()
@@ -265,6 +274,80 @@ void SteelSeriesApexController::SendInitialization()
}
}
/*---------------------------------------------------------*\
| Illumination brightness, the same setting the brightness |
| keys on the keyboard step through. It applies to the |
| on-board lighting; direct mode carries absolute colors |
| and is not affected. |
\*---------------------------------------------------------*/
bool SteelSeriesApexController::ReadBrightness(unsigned char& brightness)
{
unsigned char obuf[STEELSERIES_PACKET_OUT_SIZE];
unsigned char ibuf[STEELSERIES_PACKET_IN_SIZE];
memset(obuf, 0x00, sizeof(obuf));
memset(ibuf, 0x00, sizeof(ibuf));
obuf[0x00] = 0x00;
obuf[0x01] = APEX_PACKET_ID_GET_BRIGHTNESS;
hid_write(dev, obuf, STEELSERIES_PACKET_OUT_SIZE);
int result = hid_read_timeout(dev, ibuf, STEELSERIES_PACKET_IN_SIZE, STEELSERIES_APEX_HID_TIMEOUT);
/*-----------------------------------------------------*\
| An unsupported request is answered with 0xFF in the |
| status byte |
\*-----------------------------------------------------*/
if(result < 3 || ibuf[0x00] != APEX_PACKET_ID_GET_BRIGHTNESS || ibuf[0x01] != 0x00)
{
return false;
}
if(ibuf[0x02] > APEX_BRIGHTNESS_MAX)
{
return false;
}
brightness = ibuf[0x02];
return true;
}
bool SteelSeriesApexController::SupportsBrightness()
{
return brightness_supported;
}
unsigned char SteelSeriesApexController::GetBrightness()
{
return current_brightness;
}
void SteelSeriesApexController::SetBrightness(unsigned char brightness)
{
if(!brightness_supported)
{
return;
}
if(brightness > APEX_BRIGHTNESS_MAX)
{
brightness = APEX_BRIGHTNESS_MAX;
}
unsigned char obuf[STEELSERIES_PACKET_OUT_SIZE];
memset(obuf, 0x00, sizeof(obuf));
obuf[0x00] = 0x00;
obuf[0x01] = APEX_PACKET_ID_SET_BRIGHTNESS;
obuf[0x02] = brightness;
hid_write(dev, obuf, STEELSERIES_PACKET_OUT_SIZE);
current_brightness = brightness;
}
std::string SteelSeriesApexController::GetSerial()
{
if(kbd_quirk == APEX_GEN1)
@@ -30,10 +30,17 @@ public:
void SetLEDsDirect(std::vector<RGBColor> colors) override;
std::string GetSerial() override;
bool SupportsBrightness() override;
unsigned char GetBrightness() override;
void SetBrightness(unsigned char brightness) override;
private:
void SelectProfile(unsigned char profile);
void SendInitialization();
void SendDeinitialization();
bool ReadBrightness(unsigned char& brightness);
unsigned int direct_packet_length;
unsigned int direct_packet_length;
bool brightness_supported;
unsigned char current_brightness;
};
@@ -22,6 +22,7 @@
#include "SteelSeriesQCKMatController.h"
#include "SteelSeriesRivalController.h"
#include "SteelSeriesRival3Controller.h"
#include "SteelSeriesRival5Controller.h"
#include "SteelSeriesSenseiController.h"
#include "SteelSeriesSiberiaController.h"
#include "RGBController_SteelSeriesArctisNova3.h"
@@ -32,6 +33,7 @@
#include "RGBController_SteelSeriesQCKMat.h"
#include "RGBController_SteelSeriesRival.h"
#include "RGBController_SteelSeriesRival3.h"
#include "RGBController_SteelSeriesRival5.h"
#include "RGBController_SteelSeriesSensei.h"
#include "RGBController_SteelSeriesSiberia.h"
@@ -83,6 +85,11 @@ DetectedControllers DetectSteelSeriesAerox3WirelessWired(hid_device_info* info,
return(DetectSteelSeriesAeroxWireless(info, name, AEROX_3_WIRELESS_WIRED));
}
DetectedControllers DetectSteelSeriesAerox3Gen2Wireless(hid_device_info* info, const std::string& name)
{
return(DetectSteelSeriesAeroxWireless(info, name, AEROX_3_GEN2_WIRELESS));
}
DetectedControllers DetectSteelSeriesAerox5(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
@@ -429,6 +436,32 @@ DetectedControllers DetectSteelSeriesArctis5(hid_device_info* info, const std::s
return(detected_controllers);
}
DetectedControllers DetectSteelSeriesRival5(
hid_device_info* info,
const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev = hid_open_path(info->path);
if(dev)
{
SteelSeriesRival5Controller* controller =
new SteelSeriesRival5Controller(
dev,
RIVAL_5,
info->path,
name
);
RGBController_SteelSeriesRival5* rgb_controller =
new RGBController_SteelSeriesRival5(controller);
detected_controllers.push_back(rgb_controller);
}
return detected_controllers;
}
/*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------*\
| Mice |
@@ -437,7 +470,8 @@ REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 3 Wireless",
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 3 Wireless Wired", DetectSteelSeriesAerox3WirelessWired, STEELSERIES_VID, STEELSERIES_AEROX_3_WIRELESS_WIRED_PID, 3, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 3 CS2 Dragon Lore Edition Wireless", DetectSteelSeriesAerox3Wireless, STEELSERIES_VID, STEELSERIES_AEROX_3_CS2_WIRELESS_PID, 3, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 3 CS2 Dragon Lore Edition Wireless Wired", DetectSteelSeriesAerox3WirelessWired, STEELSERIES_VID, STEELSERIES_AEROX_3_CS2_WIRELESS_WIRED_PID, 3, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 3 Wired", DetectSteelSeriesAerox3, STEELSERIES_VID, STEELSERIES_AEROX_3_PID, 3, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 3 Wireless Gen 2", DetectSteelSeriesAerox3Gen2Wireless, STEELSERIES_VID, STEELSERIES_AEROX_3_GEN2_WIRELESS_PID, 3, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 3 Wired", DetectSteelSeriesAerox3, STEELSERIES_VID, STEELSERIES_AEROX_3_PID, 3, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 5 Wireless", DetectSteelSeriesAerox5Wireless, STEELSERIES_VID, STEELSERIES_AEROX_5_WIRELESS_PID, 3, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 5 Wireless Wired", DetectSteelSeriesAerox5WirelessWired, STEELSERIES_VID, STEELSERIES_AEROX_5_WIRELESS_WIRED_PID, 3, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Aerox 5 Destiny 2 Edition Wireless", DetectSteelSeriesAerox5DestinyWireless, STEELSERIES_VID, STEELSERIES_AEROX_5_DESTINY_WIRELESS_PID, 3, 0xFFC0, 1 );
@@ -471,6 +505,7 @@ REGISTER_HID_DETECTOR_I("SteelSeries Rival 700",
REGISTER_HID_DETECTOR_I("SteelSeries Rival 710", DetectSteelSeriesRival700, STEELSERIES_VID, STEELSERIES_RIVAL_710_PID, 0);
REGISTER_HID_DETECTOR_I("SteelSeries Rival 3 (Old Firmware)", DetectSteelSeriesRival3, STEELSERIES_VID, STEELSERIES_RIVAL_3_OLD_PID, 3);
REGISTER_HID_DETECTOR_I("SteelSeries Rival 3", DetectSteelSeriesRival3, STEELSERIES_VID, STEELSERIES_RIVAL_3_PID, 3);
REGISTER_HID_DETECTOR_IPU("SteelSeries Rival 5", DetectSteelSeriesRival5, STEELSERIES_VID, STEELSERIES_RIVAL_5_PID, 0, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_I("SteelSeries Sensei TEN", DetectSteelSeriesSensei, STEELSERIES_VID, STEELSERIES_SENSEI_TEN_PID, 0);
REGISTER_HID_DETECTOR_I("SteelSeries Sensei TEN CS:GO Neon Rider Edition", DetectSteelSeriesSensei, STEELSERIES_VID, STEELSERIES_SENSEI_TEN_CSGO_NEON_RIDER_PID, 0);
REGISTER_HID_DETECTOR_I("SteelSeries Sensei 310", DetectSteelSeriesSensei, STEELSERIES_VID, STEELSERIES_SENSEI_310_PID, 0);
@@ -22,6 +22,7 @@
#define STEELSERIES_AEROX_3_WIRELESS_WIRED_PID 0x183A
#define STEELSERIES_AEROX_3_CS2_WIRELESS_PID 0x1878
#define STEELSERIES_AEROX_3_CS2_WIRELESS_WIRED_PID 0x187A
#define STEELSERIES_AEROX_3_GEN2_WIRELESS_PID 0x1890
#define STEELSERIES_AEROX_5_WIRELESS_PID 0x1852
#define STEELSERIES_AEROX_5_WIRELESS_WIRED_PID 0x1854
#define STEELSERIES_AEROX_5_DESTINY_WIRELESS_PID 0x185C
@@ -48,6 +49,7 @@
#define STEELSERIES_RIVAL_310_PID 0x1720
#define STEELSERIES_RIVAL_310_CSGO_HOWL_PID 0x171E
#define STEELSERIES_RIVAL_310_PUBG_PID 0x1736
#define STEELSERIES_RIVAL_5_PID 0x183C
#define STEELSERIES_RIVAL_600_PID 0x1724
#define STEELSERIES_RIVAL_600_DOTA_2_PID 0x172E
#define STEELSERIES_RIVAL_650_PID 0x172B
@@ -135,4 +137,6 @@ typedef enum
AEROX_5_DIABLO_WIRELESS_WIRED = 0x15,
AEROX_9_WIRELESS = 0x16,
AEROX_9_WIRELESS_WIRED = 0x17,
AEROX_3_GEN2_WIRELESS = 0x18,
RIVAL_5 = 0x19
} steelseries_type;
@@ -30,7 +30,9 @@ enum
STEELSERIES_MOUSE_EFFECT_BREATHING_MIN = 0x03,
STEELSERIES_MOUSE_EFFECT_DIRECT = 0x04,
STEELSERIES_MOUSE_EFFECT_RAINBOW_BREATHING = 0x05,
STEELSERIES_MOUSE_EFFECT_DISCO = 0x06
STEELSERIES_MOUSE_EFFECT_DISCO = 0x06,
STEELSERIES_MOUSE_EFFECT_REACTIVE = 0x07,
STEELSERIES_MOUSE_EFFECT_RAINBOW = 0x08
};
typedef struct
@@ -0,0 +1,159 @@
/*---------------------------------------------------------*\
| RGBController_SteelSeriesRival5.cpp |
| |
| RGBController for SteelSeries Rival 5 |
| |
| A Ingram (Woodsy900) 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_SteelSeriesRival5.h"
RGBController_SteelSeriesRival5::RGBController_SteelSeriesRival5(SteelSeriesMouseController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "SteelSeries";
type = DEVICE_TYPE_MOUSE;
description = "SteelSeries Mouse Device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
version = controller->GetFirmwareVersion();
mode Direct;
Direct.name = "Direct";
Direct.value = STEELSERIES_MOUSE_EFFECT_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Direct.color_mode = MODE_COLORS_PER_LED;
Direct.brightness_min = 0x00;
Direct.brightness_max = STEELSERIES_MOUSE_BRIGHTNESS_MAX;
Direct.brightness = STEELSERIES_MOUSE_BRIGHTNESS_MAX;
mode Reactive;
Reactive.name = "Reactive";
Reactive.value = STEELSERIES_MOUSE_EFFECT_REACTIVE; // any unique value; define e.g. 0x26
Reactive.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Reactive.color_mode = MODE_COLORS_MODE_SPECIFIC;
Reactive.colors_min = 1;
Reactive.colors_max = 1;
Reactive.colors.resize(1);
Reactive.colors[0] = ToRGBColor(255, 255, 255);
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = STEELSERIES_MOUSE_EFFECT_RAINBOW;
Rainbow.color_mode = MODE_COLORS_NONE;
Rainbow.flags = MODE_FLAG_HAS_BRIGHTNESS;
Rainbow.brightness_min = 0x00;
Rainbow.brightness_max = STEELSERIES_MOUSE_BRIGHTNESS_MAX;
Rainbow.brightness = STEELSERIES_MOUSE_BRIGHTNESS_MAX;
steelseries_mouse mouse = controller->GetMouse();
for(const uint8_t i: mouse.modes)
{
switch(i)
{
case STEELSERIES_MOUSE_EFFECT_DIRECT:
modes.push_back(Direct);
break;
case STEELSERIES_MOUSE_EFFECT_REACTIVE:
modes.push_back(Reactive);
break;
case STEELSERIES_MOUSE_EFFECT_RAINBOW:
modes.push_back(Rainbow);
break;
}
}
SetupZones();
}
RGBController_SteelSeriesRival5::~RGBController_SteelSeriesRival5()
{
Shutdown();
delete controller;
}
void RGBController_SteelSeriesRival5::SetupZones()
{
steelseries_mouse mouse = controller->GetMouse();
zone lighting_zone;
lighting_zone.name = "Whole Mouse";
lighting_zone.type = ZONE_TYPE_LINEAR;
lighting_zone.leds_min = static_cast<unsigned int>(mouse.leds.size());
lighting_zone.leds_max = static_cast<unsigned int>(mouse.leds.size());
lighting_zone.leds_count = static_cast<unsigned int>(mouse.leds.size());
zones.push_back(lighting_zone);
for(const led_info& info : mouse.leds)
{
led mouse_led;
mouse_led.name = info.name;
mouse_led.value = info.value;
leds.push_back(mouse_led);
}
SetupColors();
}
void RGBController_SteelSeriesRival5::DeviceUpdateLEDs()
{
SteelSeriesRival5Controller* ctrl = static_cast<SteelSeriesRival5Controller*>(controller);
ctrl->SetBrightness((uint8_t)modes[active_mode].brightness); // guarded, so ~free under effects
ctrl->UpdateLEDs(colors);
}
void RGBController_SteelSeriesRival5::DeviceUpdateZoneLEDs(int)
{
DeviceUpdateLEDs();
}
void RGBController_SteelSeriesRival5::DeviceUpdateSingleLED(int)
{
DeviceUpdateLEDs();
}
void RGBController_SteelSeriesRival5::DeviceUpdateMode()
{
SteelSeriesRival5Controller* ctrl = static_cast<SteelSeriesRival5Controller*>(controller);
if(modes[active_mode].name == "Reactive")
{
RGBColor c = modes[active_mode].colors[0];
ctrl->SetReactiveColor(
true,
RGBGetRValue(c),
RGBGetGValue(c),
RGBGetBValue(c)
);
}
else if(modes[active_mode].name == "Rainbow")
{
ctrl->SetRainbowEffect(true);
}
else
{
ctrl->SetReactiveColor(false,0,0,0); // leaving Reactive must disable it (it's persistent in HW)
DeviceUpdateLEDs();
}
}
void RGBController_SteelSeriesRival5::DeviceSaveMode()
{
/*--------------------------------------------------------*\
| Note: Rival 5 does not have a "save" command |
| The Rival 5 saves the current state automatically |
\*--------------------------------------------------------*/
}
@@ -0,0 +1,32 @@
/*---------------------------------------------------------*\
| RGBController_SteelSeriesRival5.h |
| |
| RGBController for SteelSeries Rival 5 |
| |
| A Ingram (Woodsy900) 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "SteelSeriesRival5Controller.h"
class RGBController_SteelSeriesRival5 : public RGBController
{
public:
RGBController_SteelSeriesRival5(SteelSeriesMouseController* controller_ptr);
~RGBController_SteelSeriesRival5();
void SetupZones();
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
void DeviceSaveMode();
private:
SteelSeriesMouseController* controller;
};
@@ -0,0 +1,174 @@
/*---------------------------------------------------------*\
| SteelSeriesRival5Controller.cpp |
| |
| Controller for SteelSeries Rival 5 |
| |
| A Ingram (Woodsy900) 20 Jul 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "SteelSeriesRival5Controller.h"
#include "LogManager.h"
#include <cstring>
SteelSeriesRival5Controller::SteelSeriesRival5Controller(
hid_device* dev_handle,
steelseries_type proto_type,
const char* path,
std::string dev_name
)
: SteelSeriesMouseController(
dev_handle,
proto_type,
path,
dev_name
)
{
memset(zone_colors, 0, sizeof(zone_colors));
}
SteelSeriesRival5Controller::~SteelSeriesRival5Controller()
{
hid_close(dev);
}
steelseries_mouse SteelSeriesRival5Controller::GetMouse()
{
return rival_5;
}
std::string SteelSeriesRival5Controller::GetFirmwareVersion()
{
return "";
/*--------------------------------------------------------*\
| Note: Rival 5 does not have a "Get firmware" command |
\*--------------------------------------------------------*/
}
void SteelSeriesRival5Controller::SetLightEffectAll(uint8_t)
{
/*--------------------------------------------------------*\
| Note: Not Used |
\*--------------------------------------------------------*/
}
void SteelSeriesRival5Controller::SetRainbowEffect(bool enable)
{
uint8_t report[64] = {0};
if(enable)
{
report[1] = 0x22;
report[2] = 0xFF;
report[3] = 0x03;
}
hid_send_feature_report(
dev,
report,
sizeof(report)
);
}
void SteelSeriesRival5Controller::Save()
{
/*--------------------------------------------------------*\
| Note: Rival 5 does not have a "save" command |
| The Rival 5 saves the current state automatically |
\*--------------------------------------------------------*/
}
void SteelSeriesRival5Controller::SetColor(
unsigned char zone_id,
unsigned char red,
unsigned char green,
unsigned char blue,
unsigned char /*brightness*/
)
{
if(zone_id >= 10)
{
return;
}
zone_colors[zone_id][0] = red;
zone_colors[zone_id][1] = green;
zone_colors[zone_id][2] = blue;
}
void SteelSeriesRival5Controller::UpdateLEDs(const std::vector<RGBColor>& colors)
{
uint8_t report[64] = {0};
report[0] = 0x00;
report[1] = 0x21;
report[2] = 0xFF;
report[3] = 0x03;
for(size_t i = 0; i < 10 && i < colors.size(); i++)
{
report[4 + (i * 3) + 0] = RGBGetRValue(colors[i]);
report[4 + (i * 3) + 1] = RGBGetGValue(colors[i]);
report[4 + (i * 3) + 2] = RGBGetBValue(colors[i]);
}
int result =
hid_send_feature_report(
dev,
report,
sizeof(report)
);
}
void SteelSeriesRival5Controller::SetBrightness(uint8_t brightness)
{
if(brightness == current_brightness) return; // only on change
current_brightness = brightness;
uint8_t report[64] = {0};
report[1] = 0x23;
//report[2] = brightness;
if(brightness >= 88)
{
report[2] = 0x64;
}
else if(brightness >= 63)
{
report[2] = 0x32;
}
else if(brightness >= 38)
{
report[2] = 0x19;
}
else if(brightness >= 13)
{
report[2] = 0x0C;
}
else
{
report[2] = 0x00;
} // 0x00–0x64
hid_send_feature_report(dev, report, sizeof(report));
}
void SteelSeriesRival5Controller::SetReactiveColor(bool on, uint8_t r, uint8_t g, uint8_t b)
{
uint8_t report[64] = {0};
report[1] = 0x26;
if(on)
{
report[2] = 0x01;
report[3] = 0x00;
report[4] = r;
report[5] = g;
report[6] = b;
}
// off → bytes stay 0x00 → [00 26 00 00 00 00 00]
hid_send_feature_report(dev, report, sizeof(report));
}
@@ -0,0 +1,84 @@
/*---------------------------------------------------------*\
| SteelSeriesRival5Controller.h |
| |
| SteelSeriesRival5Controller for SteelSeries Rival 5 |
| |
| A Ingram (Woodsy900) 20 Jul 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 <cstring>
#include "RGBController.h"
#include "SteelSeriesDevices.h"
#include "SteelSeriesMouseController.h"
#define STEELSERIES_RIVAL5_NAME "SteelSeries Rival 5"
static const steelseries_mouse rival_5 =
{
{
0x04, // Direct
0x07, // Reactive
0x08 // Rainbow (for Rival 5, this is a static rainbow effect that cannot be changed)
},
{
{ "Scroll Wheel", 0 },
{ "Body Left 1", 1 },
{ "Body Right 1", 2 },
{ "Body Left 2", 3 },
{ "Body Right 2", 4 },
{ "Body Left 3", 5 },
{ "Body Right 3", 6 },
{ "Body Left 4", 7 },
{ "Body Right 4", 8 },
{ "Logo", 9 }
}
};
class SteelSeriesRival5Controller : public SteelSeriesMouseController
{
public:
SteelSeriesRival5Controller(
hid_device* dev_handle,
steelseries_type proto_type,
const char* path,
std::string dev_name
);
~SteelSeriesRival5Controller();
std::string GetFirmwareVersion() override;
steelseries_mouse GetMouse() override;
void Save() override;
void SetLightEffectAll(uint8_t effect) override;
void SetRainbowEffect(bool enable);
void SetBrightness(uint8_t brightness);
void SetColor(
unsigned char zone_id,
unsigned char red,
unsigned char green,
unsigned char blue,
unsigned char brightness
) override;
void SetReactiveColor(
bool on,
uint8_t r,
uint8_t g,
uint8_t b
);
void UpdateLEDs(const std::vector<RGBColor>& colors);
private:
uint8_t zone_colors[10][3];
uint8_t current_brightness = 0xFF;
};
+6 -6
View File
@@ -228,8 +228,8 @@ The Zone Data block represents one entry in the `RGBController::zones` vector.
| 2 | unsigned short | num_segments | 4 | Number of segments in zone |
| Variable | Segment Data[num_segments] | segments | 4 | See [Segment Data](#segment-data) block format table. Repeat num_segments times |
| 4 | unsigned int | zone_flags | 5 | Zone flags value |
| 2 | unsigned short | zone_num_modes | 6 | Number of modes in zone |
| 4 | int | zone_active_mode | 6 | Zone active_mode field value |
| 2 | unsigned short | zone_num_modes | 6 | Number of modes in zone |
| Variable | Mode Data[zone_num_modes] | zone_modes | 6 | See [Mode Data](#mode-data) block format table. Repeat zone_num_modes times |
## Segment Data
@@ -393,7 +393,7 @@ The server responds with a data block containing I2C bus information.
| Size | Format | Name | Protocol Version | Description |
| -------- | -------------------------- | ----------- | ---------------- | ---------------------------------------------------------------------------- |
| 4 | unsigned int | data_size | 6 | Size of all data in packet |
| 4 | unsigned int | bus_count | 6 | Number of I2C bus entries |
| 2 | unsigned short | bus_count | 6 | Number of I2C bus entries |
| Variable | i2c_smbus_info[bus_count] | bus_info | 6 | I2C bus info data, each entry is the fixed-size `i2c_smbus_info` struct |
## NET_PACKET_ID_GET_HID_DEVICE_INFO
@@ -409,7 +409,7 @@ The server responds with a data block containing HID device information.
| Size | Format | Name | Protocol Version | Description |
| -------- | ------------------------------ | -------------- | ---------------- | -------------------------------------------------------------------------------------- |
| 4 | unsigned int | data_size | 6 | Size of all data in packet |
| 4 | unsigned int | device_count | 6 | Number of HID device entries |
| 2 | unsigned short | device_count | 6 | Number of HID device entries |
| Variable | HID Device Data[device_count] | device_data | 6 | See [HID Device Data](#hid-device-data) block format table. Repeat device_count times |
### HID Device Data
@@ -444,7 +444,7 @@ The server responds with a data block containing USB device information.
| Size | Format | Name | Protocol Version | Description |
| -------- | ------------------------------- | -------------- | ---------------- | -------------------------------------------------------------------------------------- |
| 4 | unsigned int | data_size | 6 | Size of all data in packet |
| 4 | unsigned int | device_count | 6 | Number of USB device entries |
| 2 | unsigned short | device_count | 6 | Number of USB device entries |
| Variable | USB Device Data[device_count] | device_data | 6 | See [USB Device Data](#usb-device-data) block format table. Repeat device_count times |
### USB Device Data
@@ -473,7 +473,7 @@ The server responds with a data block containing serial port information.
| Size | Format | Name | Protocol Version | Description |
| -------- | ------------------------------- | -------------- | ---------------- | ------------------------------------------------------------------------------------------ |
| 4 | unsigned int | data_size | 6 | Size of all data in packet |
| 4 | unsigned int | port_count | 6 | Number of serial port entries |
| 2 | unsigned short | port_count | 6 | Number of serial port entries |
| Variable | Serial Port Data[port_count] | port_data | 6 | See [Serial Port Data](#serial-port-data) block format table. Repeat port_count times |
### Serial Port Data
@@ -496,7 +496,7 @@ The server responds with a data block containing USB serial port information.
| Size | Format | Name | Protocol Version | Description |
| -------- | ------------------------------- | -------------- | ---------------- | ------------------------------------------------------------------------------------------ |
| 4 | unsigned int | data_size | 6 | Size of all data in packet |
| 4 | unsigned int | port_count | 6 | Number of USB serial port entries |
| 2 | unsigned short | port_count | 6 | Number of USB serial port entries |
| Variable | Serial Port Data[port_count] | port_data | 6 | See [Serial Port Data](#usb-serial-port-data) block format table. Repeat port_count times |
### USB Serial Port Data
+471 -180
View File
@@ -96,13 +96,195 @@ NetworkClient::NetworkClient()
server_reinitialize = false;
change_in_progress = false;
client_active = false;
profilemanager_thread = NULL;
ListenThread = NULL;
ConnectionThread = NULL;
ReceiveQueueThread = NULL;
receive_generation = 0;
receive_queue_thread_running = false;
}
/*---------------------------------------------------------*\
| Packets the receive queue thread claims. Everything else |
| in the queue is a synchronous reply for WaitForResponse. |
| Must match the queueing cases in ListenThreadFunction. |
\*---------------------------------------------------------*/
static bool IsCallbackPacket(unsigned int pkt_id)
{
switch(pkt_id)
{
case NET_PACKET_ID_REQUEST_CONTROLLER_COUNT:
case NET_PACKET_ID_REQUEST_CONTROLLER_DATA:
case NET_PACKET_ID_SET_SERVER_NAME:
case NET_PACKET_ID_DEVICE_LIST_UPDATED:
case NET_PACKET_ID_DETECTION_STARTED:
case NET_PACKET_ID_DETECTION_PROGRESS_CHANGED:
case NET_PACKET_ID_DETECTION_COMPLETE:
return(true);
default:
return(false);
}
}
/*---------------------------------------------------------*\
| Receive queue thread (see NetworkClient.h) |
\*---------------------------------------------------------*/
void NetworkClient::ReceiveQueueThreadFunction()
{
std::unique_lock<std::mutex> lock(receive_queue_mutex);
while(receive_queue_thread_running)
{
/*-------------------------------------------------*\
| Wait for a callback-emitting packet. Replies are |
| left in the queue for WaitForResponse to claim. |
\*-------------------------------------------------*/
receive_queue_cv.wait(lock, [this]
{
if(!receive_queue_thread_running)
{
return true;
}
for(std::list<NetworkClientListenerThreadQueueEntry>::iterator it = receive_queue.begin(); it != receive_queue.end(); it++)
{
if(it->generation != receive_generation || IsCallbackPacket(it->header.pkt_id))
{
return true;
}
}
return false;
});
if(!receive_queue_thread_running)
{
return;
}
/*-------------------------------------------------*\
| Drop what a previous connection queued |
\*-------------------------------------------------*/
for(std::list<NetworkClientListenerThreadQueueEntry>::iterator it = receive_queue.begin(); it != receive_queue.end(); )
{
if(it->generation != receive_generation)
{
delete[] it->data;
it = receive_queue.erase(it);
}
else
{
it++;
}
}
/*-------------------------------------------------*\
| Take the oldest callback-emitting packet |
\*-------------------------------------------------*/
NetworkClientListenerThreadQueueEntry entry;
bool claimed = false;
for(std::list<NetworkClientListenerThreadQueueEntry>::iterator it = receive_queue.begin(); it != receive_queue.end(); it++)
{
if(IsCallbackPacket(it->header.pkt_id))
{
entry = *it;
claimed = true;
receive_queue.erase(it);
break;
}
}
if(!claimed)
{
continue;
}
lock.unlock();
switch(entry.header.pkt_id)
{
case NET_PACKET_ID_REQUEST_CONTROLLER_COUNT:
ProcessReply_ControllerIDs(entry.header.pkt_size, entry.data);
break;
case NET_PACKET_ID_REQUEST_CONTROLLER_DATA:
ProcessReply_ControllerData(entry.header.pkt_size, entry.data, entry.header.pkt_dev_id);
break;
case NET_PACKET_ID_SET_SERVER_NAME:
ProcessRequest_ServerString(entry.header.pkt_size, entry.data);
break;
case NET_PACKET_ID_DETECTION_STARTED:
SignalNetworkClientUpdate(NETWORKCLIENT_UPDATE_REASON_DETECTION_STARTED);
break;
case NET_PACKET_ID_DETECTION_PROGRESS_CHANGED:
ProcessRequest_DetectionProgressChanged(entry.header.pkt_size, entry.data);
break;
case NET_PACKET_ID_DETECTION_COMPLETE:
SignalNetworkClientUpdate(NETWORKCLIENT_UPDATE_REASON_DETECTION_COMPLETE);
break;
case NET_PACKET_ID_DEVICE_LIST_UPDATED:
ProcessRequest_DeviceListChanged();
break;
}
delete[] entry.data;
lock.lock();
}
}
void NetworkClient::StopReceiveQueueThread()
{
{
std::lock_guard<std::mutex> lock(receive_queue_mutex);
receive_queue_thread_running = false;
for(std::list<NetworkClientListenerThreadQueueEntry>::iterator it = receive_queue.begin(); it != receive_queue.end(); it++)
{
delete[] it->data;
}
receive_queue.clear();
}
receive_queue_cv.notify_all();
if(ReceiveQueueThread)
{
ReceiveQueueThread->join();
delete ReceiveQueueThread;
ReceiveQueueThread = NULL;
}
}
NetworkClient::~NetworkClient()
{
StopClient();
StopReceiveQueueThread();
/*-----------------------------------------------------*\
| Free the controllers set aside by StopClient, plus |
| any left behind by a listener that never ran |
\*-----------------------------------------------------*/
ControllerListMutex.lock();
orphaned_controllers.insert(orphaned_controllers.end(), server_controllers.begin(), server_controllers.end());
server_controllers.clear();
ControllerListMutex.unlock();
DeleteOrphanedControllers();
}
/*---------------------------------------------------------*\
@@ -247,6 +429,16 @@ void NetworkClient::StartClient()
\*-----------------------------------------------------*/
ConnectionThread = new std::thread(&NetworkClient::ConnectionThreadFunction, this);
/*-----------------------------------------------------*\
| Start the receive queue thread. It spans |
| reconnects; entries age out by generation. |
\*-----------------------------------------------------*/
if(!ReceiveQueueThread)
{
receive_queue_thread_running = true;
ReceiveQueueThread = new std::thread(&NetworkClient::ReceiveQueueThreadFunction, this);
}
/*-----------------------------------------------------*\
| Start the ProfileManager listen thread |
\*-----------------------------------------------------*/
@@ -271,6 +463,17 @@ void NetworkClient::StopClient()
server_connected = false;
client_active = false;
/*-----------------------------------------------------*\
| Retire this connection's generation so requests still |
| waiting on a reply that is never coming wake up |
\*-----------------------------------------------------*/
{
std::lock_guard<std::mutex> lock(receive_queue_mutex);
receive_generation++;
}
receive_queue_cv.notify_all();
/*-----------------------------------------------------*\
| Shut down and close the client socket |
\*-----------------------------------------------------*/
@@ -323,6 +526,36 @@ void NetworkClient::StopClient()
SignalNetworkClientUpdate(NETWORKCLIENT_UPDATE_REASON_CLIENT_STOPPED);
}
/*---------------------------------------------------------*\
| Controller teardown functions |
\*---------------------------------------------------------*/
void NetworkClient::TakeOrphanedControllers(std::vector<RGBController*>& orphaned)
{
ControllerListMutex.lock();
orphaned.insert(orphaned.end(), orphaned_controllers.begin(), orphaned_controllers.end());
orphaned_controllers.clear();
ControllerListMutex.unlock();
}
void NetworkClient::DeleteOrphanedControllers()
{
/*-----------------------------------------------------*\
| Delete outside the mutex; the controller destructor |
| waits for callbacks that are already running |
\*-----------------------------------------------------*/
std::vector<RGBController*> orphaned;
TakeOrphanedControllers(orphaned);
for(std::size_t orphaned_idx = 0; orphaned_idx < orphaned.size(); orphaned_idx++)
{
delete orphaned[orphaned_idx];
}
}
void NetworkClient::SendRequest_ControllerData(unsigned int dev_id)
{
NetPacketHeader request_hdr;
@@ -409,61 +642,59 @@ std::vector<HIDDeviceInfo> NetworkClient::GetHIDDeviceInfo()
send_in_progress.unlock();
/*-------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_GET_HID_DEVICE_INFO);
/*-------------------------------------------------*\
| Parse response into device list |
\*-------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_GET_HID_DEVICE_INFO && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int device_count = 0;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_header.pkt_size;
unsigned short device_count = 0;
unsigned char* data_ptr = response_entry.data;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned int data_size_pkt;
COPY_DATA_FIELD(data_ptr, response_data_ptr, data_size_pkt);
COPY_DATA_FIELD(data_ptr, response_entry.data, data_size_pkt);
if(data_size_pkt == data_size)
{
COPY_DATA_FIELD(data_ptr, response_data_ptr, device_count);
COPY_DATA_FIELD(data_ptr, response_entry.data, device_count);
for(unsigned int device_idx = 0; device_idx < device_count; device_idx++)
for(unsigned short device_idx = 0; device_idx < device_count; device_idx++)
{
HIDDeviceInfo device;
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.vendor_id);
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.product_id);
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.release_number);
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.usage_page);
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.usage);
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.interface_number);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.vendor_id);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.product_id);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.release_number);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.usage_page);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.usage);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.interface_number);
unsigned short serial_number_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, serial_number_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, serial_number_size, device.serial_number);
COPY_DATA_FIELD(data_ptr, response_entry.data, serial_number_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, serial_number_size, device.serial_number);
unsigned short manufacturer_string_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, manufacturer_string_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, manufacturer_string_size, device.manufacturer_string);
COPY_DATA_FIELD(data_ptr, response_entry.data, manufacturer_string_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, manufacturer_string_size, device.manufacturer_string);
unsigned short product_string_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, product_string_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, product_string_size, device.product_string);
COPY_DATA_FIELD(data_ptr, response_entry.data, product_string_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, product_string_size, device.product_string);
unsigned short path_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, path_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, path_size, device.path);
COPY_DATA_FIELD(data_ptr, response_entry.data, path_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, path_size, device.path);
device_info.push_back(device);
}
}
COPY_DATA_ERROR:
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
}
@@ -487,40 +718,38 @@ std::vector<i2c_smbus_info> NetworkClient::GetI2CBusInfo()
send_in_progress.unlock();
/*-------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_GET_I2C_BUS_INFO);
/*-------------------------------------------------*\
| Parse response into bus list |
\*-------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_GET_I2C_BUS_INFO && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int bus_count = 0;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_header.pkt_size;
unsigned short bus_count = 0;
unsigned char* data_ptr = response_entry.data;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned int data_size_pkt;
COPY_DATA_FIELD(data_ptr, response_data_ptr, data_size_pkt);
COPY_DATA_FIELD(data_ptr, response_entry.data, data_size_pkt);
if(data_size_pkt == data_size)
{
COPY_DATA_FIELD(data_ptr, response_data_ptr, bus_count);
COPY_DATA_FIELD(data_ptr, response_entry.data, bus_count);
for(unsigned int bus_idx = 0; bus_idx < bus_count; bus_idx++)
for(unsigned short bus_idx = 0; bus_idx < bus_count; bus_idx++)
{
i2c_smbus_info bus;
COPY_DATA_FIELD(data_ptr, response_data_ptr, bus);
COPY_DATA_FIELD(data_ptr, response_entry.data, bus);
bus_info.push_back(bus);
}
}
COPY_DATA_ERROR:
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
}
@@ -544,41 +773,39 @@ std::vector<std::string> NetworkClient::GetSerialPorts()
send_in_progress.unlock();
/*-------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_GET_SERIAL_PORTS);
/*-------------------------------------------------*\
| Parse response into device list |
\*-------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_GET_SERIAL_PORTS && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int port_count = 0;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_header.pkt_size;
unsigned short port_count = 0;
unsigned char* data_ptr = response_entry.data;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned int data_size_pkt;
COPY_DATA_FIELD(data_ptr, response_data_ptr, data_size_pkt);
COPY_DATA_FIELD(data_ptr, response_entry.data, data_size_pkt);
if(data_size_pkt == data_size)
{
COPY_DATA_FIELD(data_ptr, response_data_ptr, port_count);
COPY_DATA_FIELD(data_ptr, response_entry.data, port_count);
for(unsigned int port_idx = 0; port_idx < port_count; port_idx++)
for(unsigned short port_idx = 0; port_idx < port_count; port_idx++)
{
std::string port_string;
unsigned short port_string_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, port_string_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, port_string_size, port_string);
COPY_DATA_FIELD(data_ptr, response_entry.data, port_string_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, port_string_size, port_string);
serial_ports.push_back(port_string);
}
}
COPY_DATA_ERROR:
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
}
@@ -602,53 +829,51 @@ std::vector<USBDeviceInfo> NetworkClient::GetUSBDeviceInfo()
send_in_progress.unlock();
/*-------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_GET_USB_DEVICE_INFO);
/*-------------------------------------------------*\
| Parse response into device list |
\*-------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_GET_USB_DEVICE_INFO && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int device_count = 0;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_header.pkt_size;
unsigned short device_count = 0;
unsigned char* data_ptr = response_entry.data;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned int data_size_pkt;
COPY_DATA_FIELD(data_ptr, response_data_ptr, data_size_pkt);
COPY_DATA_FIELD(data_ptr, response_entry.data, data_size_pkt);
if(data_size_pkt == data_size)
{
COPY_DATA_FIELD(data_ptr, response_data_ptr, device_count);
COPY_DATA_FIELD(data_ptr, response_entry.data, device_count);
for(unsigned int device_idx = 0; device_idx < device_count; device_idx++)
for(unsigned short device_idx = 0; device_idx < device_count; device_idx++)
{
USBDeviceInfo device;
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.vendor_id);
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.product_id);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.vendor_id);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.product_id);
unsigned short serial_number_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, serial_number_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, serial_number_size, device.serial_number);
COPY_DATA_FIELD(data_ptr, response_entry.data, serial_number_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, serial_number_size, device.serial_number);
unsigned short manufacturer_string_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, manufacturer_string_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, manufacturer_string_size, device.manufacturer_string);
COPY_DATA_FIELD(data_ptr, response_entry.data, manufacturer_string_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, manufacturer_string_size, device.manufacturer_string);
unsigned short product_string_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, product_string_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, product_string_size, device.product_string);
COPY_DATA_FIELD(data_ptr, response_entry.data, product_string_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, product_string_size, device.product_string);
device_info.push_back(device);
}
}
COPY_DATA_ERROR:
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
}
@@ -672,49 +897,47 @@ std::vector<SerialDeviceInfo> NetworkClient::GetUSBSerialPorts()
send_in_progress.unlock();
/*-------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_GET_USB_SERIAL_PORTS);
/*-------------------------------------------------*\
| Parse response into device list |
\*-------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_GET_USB_SERIAL_PORTS && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int device_count = 0;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_header.pkt_size;
unsigned short device_count = 0;
unsigned char* data_ptr = response_entry.data;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned int data_size_pkt;
COPY_DATA_FIELD(data_ptr, response_data_ptr, data_size_pkt);
COPY_DATA_FIELD(data_ptr, response_entry.data, data_size_pkt);
if(data_size_pkt == data_size)
{
COPY_DATA_FIELD(data_ptr, response_data_ptr, device_count);
COPY_DATA_FIELD(data_ptr, response_entry.data, device_count);
for(unsigned int device_idx = 0; device_idx < device_count; device_idx++)
for(unsigned short device_idx = 0; device_idx < device_count; device_idx++)
{
SerialDeviceInfo device;
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.vendor_id);
COPY_DATA_FIELD(data_ptr, response_data_ptr, device.product_id);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.vendor_id);
COPY_DATA_FIELD(data_ptr, response_entry.data, device.product_id);
unsigned short port_path_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, port_path_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, port_path_size, device.port_path);
COPY_DATA_FIELD(data_ptr, response_entry.data, port_path_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, port_path_size, device.port_path);
unsigned short usb_path_size;
COPY_DATA_FIELD(data_ptr, response_data_ptr, usb_path_size);
COPY_STRING_FIELD(data_ptr, response_data_ptr, usb_path_size, device.usb_path);
COPY_DATA_FIELD(data_ptr, response_entry.data, usb_path_size);
COPY_STRING_FIELD(data_ptr, response_entry.data, usb_path_size, device.usb_path);
device_info.push_back(device);
}
}
COPY_DATA_ERROR:
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
}
@@ -774,23 +997,21 @@ unsigned int NetworkClient::LogManager_GetLogLevel()
send_in_progress.unlock();
/*-----------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-----------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_LOGMANAGER_GET_LOG_LEVEL);
/*-----------------------------------------------------*\
| Parse response into bus list |
\*-----------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_LOGMANAGER_GET_LOG_LEVEL && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
if(response_header.pkt_size >= sizeof(log_level))
if(response_entry.header.pkt_size >= sizeof(log_level))
{
memcpy(&log_level, response_data_ptr, sizeof(log_level));
memcpy(&log_level, response_entry.data, sizeof(log_level));
}
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
return(log_level);
@@ -886,24 +1107,22 @@ std::string NetworkClient::ProfileManager_DownloadProfile(std::string profile_na
send_in_progress.unlock();
/*-----------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-----------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_PROFILEMANAGER_DOWNLOAD_PROFILE);
/*-----------------------------------------------------*\
| Parse response into bus list |
\*-----------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_PROFILEMANAGER_DOWNLOAD_PROFILE && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int& data_size = response_header.pkt_size;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned char* data_ptr = response_entry.data;
COPY_STRING_FIELD(data_ptr, response_data_ptr, data_size, response_string);
COPY_STRING_FIELD(data_ptr, response_entry.data, data_size, response_string);
COPY_DATA_ERROR:
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
return(response_string);
@@ -924,24 +1143,22 @@ std::string NetworkClient::ProfileManager_GetActiveProfile()
send_in_progress.unlock();
/*-----------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-----------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_PROFILEMANAGER_GET_ACTIVE_PROFILE);
/*-----------------------------------------------------*\
| Parse response into bus list |
\*-----------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_PROFILEMANAGER_GET_ACTIVE_PROFILE && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int& data_size = response_header.pkt_size;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned char* data_ptr = response_entry.data;
COPY_STRING_FIELD(data_ptr, response_data_ptr, data_size, response_string);
COPY_STRING_FIELD(data_ptr, response_entry.data, data_size, response_string);
COPY_DATA_ERROR:
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
return(response_string);
@@ -977,23 +1194,22 @@ std::string NetworkClient::SettingsManager_GetSettings(std::string settings_key)
send_in_progress.unlock();
/*-----------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-----------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_SETTINGSMANAGER_GET_SETTINGS);
/*-----------------------------------------------------*\
| Parse response into bus list |
\*-----------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_SETTINGSMANAGER_GET_SETTINGS && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int& data_size = response_header.pkt_size;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned char* data_ptr = response_entry.data;
COPY_STRING_FIELD(data_ptr, response_data_ptr, data_size, response_string);
COPY_STRING_FIELD(data_ptr, response_entry.data, data_size, response_string);
COPY_DATA_ERROR:
response_data_ptr = NULL;
delete[] response_entry.data;
}
return(response_string);
@@ -1015,24 +1231,22 @@ std::string NetworkClient::SettingsManager_GetSettingsSchema(std::string setting
send_in_progress.unlock();
/*-----------------------------------------------------*\
| Wait for response |
| Wait for response using the response queue |
\*-----------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(waiting_on_response_mutex);
waiting_on_response_cv.wait(wait_lock);
NetworkClientListenerThreadQueueEntry response_entry = WaitForResponse(NET_PACKET_ID_SETTINGSMANAGER_GET_SETTINGS_SCHEMA);
/*-----------------------------------------------------*\
| Parse response into bus list |
\*-----------------------------------------------------*/
if(response_header.pkt_id == NET_PACKET_ID_SETTINGSMANAGER_GET_SETTINGS_SCHEMA && response_data_ptr != NULL)
if(response_entry.data != NULL)
{
unsigned int& data_size = response_header.pkt_size;
unsigned char* data_ptr = response_data_ptr;
unsigned int& data_size = response_entry.header.pkt_size;
unsigned char* data_ptr = response_entry.data;
COPY_STRING_FIELD(data_ptr, response_data_ptr, data_size, response_string);
COPY_STRING_FIELD(data_ptr, response_entry.data, data_size, response_string);
COPY_DATA_ERROR:
delete[] response_data_ptr;
response_data_ptr = NULL;
delete[] response_entry.data;
}
return(response_string);
@@ -1723,6 +1937,16 @@ void NetworkClient::ListenThreadFunction()
{
LOG_INFO("[%s] Listener thread started", NETWORKCLIENT);
/*-----------------------------------------------------*\
| Claim this connection's receive generation |
\*-----------------------------------------------------*/
unsigned int listen_generation;
{
std::lock_guard<std::mutex> gen_lock(receive_queue_mutex);
listen_generation = ++receive_generation;
}
/*-----------------------------------------------------*\
| This thread handles messages received from the server |
\*-----------------------------------------------------*/
@@ -1806,46 +2030,30 @@ void NetworkClient::ListenThreadFunction()
\*-------------------------------------------------*/
switch(header.pkt_id)
{
case NET_PACKET_ID_REQUEST_CONTROLLER_COUNT:
ProcessReply_ControllerIDs(header.pkt_size, data);
break;
case NET_PACKET_ID_REQUEST_CONTROLLER_DATA:
ProcessReply_ControllerData(header.pkt_size, data, header.pkt_dev_id);
break;
case NET_PACKET_ID_REQUEST_PROTOCOL_VERSION:
ProcessReply_ProtocolVersion(header.pkt_size, data);
break;
case NET_PACKET_ID_SET_SERVER_NAME:
ProcessRequest_ServerString(header.pkt_size, data);
break;
case NET_PACKET_ID_SET_SERVER_FLAGS:
ProcessRequest_ServerFlags(header.pkt_size, data);
break;
case NET_PACKET_ID_DEVICE_LIST_UPDATED:
ProcessRequest_DeviceListChanged();
break;
case NET_PACKET_ID_DETECTION_STARTED:
SignalNetworkClientUpdate(NETWORKCLIENT_UPDATE_REASON_DETECTION_STARTED);
break;
case NET_PACKET_ID_DETECTION_PROGRESS_CHANGED:
ProcessRequest_DetectionProgressChanged(header.pkt_size, data);
break;
case NET_PACKET_ID_DETECTION_COMPLETE:
SignalNetworkClientUpdate(NETWORKCLIENT_UPDATE_REASON_DETECTION_COMPLETE);
break;
case NET_PACKET_ID_LOGMANAGER_LOGGED_ENTRY:
ProcessRequest_LogManager_LoggedEntry(header.pkt_size, data);
break;
/*---------------------------------------------*\
| Queue what is not handled inline; the queue |
| owns the data. Running callback packets here |
| would block the listener on the GUI. |
\*---------------------------------------------*/
case NET_PACKET_ID_REQUEST_CONTROLLER_COUNT:
case NET_PACKET_ID_REQUEST_CONTROLLER_DATA:
case NET_PACKET_ID_SET_SERVER_NAME:
case NET_PACKET_ID_DEVICE_LIST_UPDATED:
case NET_PACKET_ID_DETECTION_STARTED:
case NET_PACKET_ID_DETECTION_PROGRESS_CHANGED:
case NET_PACKET_ID_DETECTION_COMPLETE:
case NET_PACKET_ID_GET_I2C_BUS_INFO:
case NET_PACKET_ID_GET_HID_DEVICE_INFO:
case NET_PACKET_ID_GET_USB_DEVICE_INFO:
@@ -1855,15 +2063,19 @@ void NetworkClient::ListenThreadFunction()
case NET_PACKET_ID_PROFILEMANAGER_DOWNLOAD_PROFILE:
case NET_PACKET_ID_PROFILEMANAGER_GET_ACTIVE_PROFILE:
case NET_PACKET_ID_SETTINGSMANAGER_GET_SETTINGS:
case NET_PACKET_ID_SETTINGSMANAGER_GET_SETTINGS_SCHEMA:
{
std::unique_lock<std::mutex> lock(waiting_on_response_mutex);
std::lock_guard<std::mutex> lock(receive_queue_mutex);
response_header = header;
response_data_ptr = data;
delete_data = false;
NetworkClientListenerThreadQueueEntry new_entry;
new_entry.header = header;
new_entry.data = data;
new_entry.generation = listen_generation;
lock.unlock();
waiting_on_response_cv.notify_all();
receive_queue.push_back(new_entry);
receive_queue_cv.notify_all();
delete_data = false;
}
break;
@@ -1881,8 +2093,9 @@ void NetworkClient::ListenThreadFunction()
profilemanager_thread->queue_mutex.lock();
NetworkClientListenerThreadQueueEntry new_entry;
new_entry.data = data;
new_entry.header = header;
new_entry.data = data;
new_entry.generation = listen_generation;
profilemanager_thread->queue.push(new_entry);
profilemanager_thread->queue_mutex.unlock();
@@ -1901,6 +2114,7 @@ void NetworkClient::ListenThreadFunction()
listen_done:
LOG_INFO("[%s] Client socket has been closed", NETWORKCLIENT);
client_flags = NET_CLIENT_FLAG_SUPPORTS_RGBCONTROLLER
| NET_CLIENT_FLAG_SUPPORTS_PROFILEMANAGER
| NET_CLIENT_FLAG_SUPPORTS_SETTINGSMANAGER;
@@ -1918,23 +2132,39 @@ listen_done:
server_initialized = false;
server_connected = false;
/*-----------------------------------------------------*\
| Retire this connection's generation. Blocked |
| requests wake with a cleared response and the |
| packets this connection queued age out. |
\*-----------------------------------------------------*/
{
std::lock_guard<std::mutex> lock(receive_queue_mutex);
receive_generation++;
}
receive_queue_cv.notify_all();
/*-----------------------------------------------------*\
| Do not delete the controllers here; the front end |
| still holds pointers to them. Move them to |
| orphaned_controllers for the teardown thread to free |
\*-----------------------------------------------------*/
ControllerListMutex.lock();
std::vector<RGBController *> server_controllers_copy = server_controllers;
orphaned_controllers.insert(orphaned_controllers.end(), server_controllers.begin(), server_controllers.end());
server_controllers.clear();
for(size_t server_controller_idx = 0; server_controller_idx < server_controllers_copy.size(); server_controller_idx++)
{
delete server_controllers_copy[server_controller_idx];
}
ControllerListMutex.unlock();
/*-----------------------------------------------------*\
| Client info has changed, call the callbacks |
| Signal only an unplanned connection loss; on a |
| requested stop the caller runs the teardown |
\*-----------------------------------------------------*/
SignalNetworkClientUpdate(NETWORKCLIENT_UPDATE_REASON_CLIENT_DISCONNECTED);
if(client_active)
{
SignalNetworkClientUpdate(NETWORKCLIENT_UPDATE_REASON_CLIENT_DISCONNECTED);
}
}
/*---------------------------------------------------------*\
@@ -1958,7 +2188,11 @@ void NetworkClient::ProcessReply_ControllerData(unsigned int data_size, unsigned
| Create a new controller from the received data |
\*-------------------------------------------------*/
new_controller = new RGBController_Network(this, dev_id);
RGBController::SetDeviceDescription(data_ptr, data_size - (unsigned int)(data_ptr - data_start), new_controller, GetProtocolVersion());
if(RGBController::SetDeviceDescription(data_ptr, data_size - (unsigned int)(data_ptr - data_start), new_controller, GetProtocolVersion()) == NULL)
{
goto COPY_DATA_ERROR;
}
/*-------------------------------------------------*\
| Mark this controller as remote owned |
@@ -2255,7 +2489,10 @@ void NetworkClient::ProcessRequest_RGBController_SignalUpdate(unsigned int data_
| UpdateLEDs() sends color description |
\*-------------------------------------------------*/
case RGBCONTROLLER_UPDATE_REASON_UPDATELEDS:
RGBController::SetColorDescription(data_ptr, data_size - (unsigned int)(data_ptr - data_start), controller, GetProtocolVersion());
if(RGBController::SetColorDescription(data_ptr, data_size - (unsigned int)(data_ptr - data_start), controller, GetProtocolVersion()) == NULL)
{
goto COPY_DATA_ERROR;
}
break;
/*-------------------------------------------------*\
@@ -2272,7 +2509,10 @@ void NetworkClient::ProcessRequest_RGBController_SignalUpdate(unsigned int data_
case RGBCONTROLLER_UPDATE_REASON_SETDEVICESPECIFICCONFIGURATION:
case RGBCONTROLLER_UPDATE_REASON_SETDEVICESPECIFICZONECONFIGURATION:
default:
RGBController::SetDeviceDescription(data_ptr, data_size - (unsigned int)(data_ptr - data_start), controller, GetProtocolVersion());
if(RGBController::SetDeviceDescription(data_ptr, data_size - (unsigned int)(data_ptr - data_start), controller, GetProtocolVersion()) == NULL)
{
goto COPY_DATA_ERROR;
}
/*---------------------------------------------*\
| Mark this controller as remote owned |
@@ -2500,6 +2740,57 @@ std::vector<std::string>* NetworkClient::ProcessReply_ProfileList(unsigned int d
return(profile_list);
}
/*---------------------------------------------------------*\
| Response queue helper |
\*---------------------------------------------------------*/
NetworkClientListenerThreadQueueEntry NetworkClient::WaitForResponse(unsigned int expected_pkt_id)
{
NetworkClientListenerThreadQueueEntry result;
result.data = NULL;
result.generation = 0;
std::unique_lock<std::mutex> lock(receive_queue_mutex);
/*-----------------------------------------------------*\
| Wait for a reply on this connection, or for the |
| connection to drop. Without the connection test a |
| request whose reply is lost waits forever. |
\*-----------------------------------------------------*/
receive_queue_cv.wait(lock, [this, expected_pkt_id]()
{
if(!server_connected)
{
return true;
}
for(std::list<NetworkClientListenerThreadQueueEntry>::iterator it = receive_queue.begin(); it != receive_queue.end(); it++)
{
if(it->header.pkt_id == expected_pkt_id && it->generation == receive_generation)
{
return true;
}
}
return false;
});
/*-----------------------------------------------------*\
| Find and remove the matching entry from the queue. |
| The caller owns the data pointer from here. |
\*-----------------------------------------------------*/
for(std::list<NetworkClientListenerThreadQueueEntry>::iterator it = receive_queue.begin(); it != receive_queue.end(); it++)
{
if(it->header.pkt_id == expected_pkt_id && it->generation == receive_generation)
{
result = *it;
receive_queue.erase(it);
break;
}
}
return result;
}
/*---------------------------------------------------------*\
| Private helper functions |
\*---------------------------------------------------------*/
+38 -4
View File
@@ -12,6 +12,7 @@
#pragma once
#include <condition_variable>
#include <list>
#include <mutex>
#include <queue>
#include <thread>
@@ -50,6 +51,7 @@ typedef struct
{
NetPacketHeader header;
unsigned char* data;
unsigned int generation;
} NetworkClientListenerThreadQueueEntry;
typedef struct
@@ -99,6 +101,12 @@ public:
void StartClient();
void StopClient();
/*-----------------------------------------------------*\
| Controller teardown functions |
\*-----------------------------------------------------*/
void DeleteOrphanedControllers();
void TakeOrphanedControllers(std::vector<RGBController*>& orphaned);
void SendRequest_ControllerData(unsigned int dev_id);
void SendRequest_RescanDevices();
@@ -195,10 +203,17 @@ private:
unsigned int requested_controller_index;
std::mutex send_in_progress;
NetPacketHeader response_header;
unsigned char* response_data_ptr;
std::mutex waiting_on_response_mutex;
std::condition_variable waiting_on_response_cv;
/*-----------------------------------------------------*\
| Every packet the listener does not handle inline. |
| Replies are claimed by WaitForResponse, callback |
| packets by the receive queue thread. Whoever |
| claims an entry frees its data. |
\*-----------------------------------------------------*/
std::list<NetworkClientListenerThreadQueueEntry>
receive_queue;
std::mutex receive_queue_mutex;
std::condition_variable receive_queue_cv;
unsigned int receive_generation;
/*-----------------------------------------------------*\
| Client information |
@@ -233,6 +248,18 @@ private:
std::thread * ListenThread;
NetworkClientListenerThread* profilemanager_thread;
/*-----------------------------------------------------*\
| Receive queue thread. Runs the callback-emitting |
| packets, which block on the GUI thread while the |
| GUI may itself be blocked on a reply. Queue order |
| keeps plugin callbacks single threaded. |
\*-----------------------------------------------------*/
std::thread* ReceiveQueueThread;
bool receive_queue_thread_running;
void ReceiveQueueThreadFunction();
void StopReceiveQueueThread();
/*-----------------------------------------------------*\
| Callbacks |
\*-----------------------------------------------------*/
@@ -245,6 +272,7 @@ private:
\*-----------------------------------------------------*/
std::mutex ControllerListMutex;
std::vector<RGBController*> server_controllers;
std::vector<RGBController*> orphaned_controllers;
std::vector<unsigned int> server_controller_ids;
/*-----------------------------------------------------*\
@@ -296,6 +324,12 @@ private:
\*-----------------------------------------------------*/
std::vector<std::string>* ProcessReply_ProfileList(unsigned int data_size, unsigned char* data_ptr);
/*-----------------------------------------------------*\
| Response queue helper |
\*-----------------------------------------------------*/
NetworkClientListenerThreadQueueEntry
WaitForResponse(unsigned int expected_pkt_id);
/*-----------------------------------------------------*\
| Private helper functions |
\*-----------------------------------------------------*/
+177 -29
View File
@@ -97,6 +97,8 @@ NetworkClientInfo::NetworkClientInfo()
client_protocol_version = 0;
client_is_local = false;
client_is_local_client = false;
client_send_thread = nullptr;
client_send_running = false;
}
NetworkClientInfo::~NetworkClientInfo()
@@ -104,8 +106,39 @@ NetworkClientInfo::~NetworkClientInfo()
if(client_sock != INVALID_SOCKET)
{
LOG_INFO("[%s] Closing server connection: %s", NETWORKSERVER, client_ip.c_str());
/*-------------------------------------------------*\
| Stop the send thread before closing the socket. |
| SD_BOTH (not just SD_RECEIVE) unblocks a send |
| stuck on a slow client, so the join cannot hang. |
\*-------------------------------------------------*/
if(client_send_thread)
{
client_send_running = false;
shutdown(client_sock, SD_BOTH);
client_send_cv.notify_all();
client_send_thread->join();
delete client_send_thread;
client_send_thread = nullptr;
/*---------------------------------------------*\
| Free any packets still queued; the entries |
| own their data pointers. |
\*---------------------------------------------*/
for(std::list<NetworkServerClientSendQueueEntry>::iterator entry = client_send_queue.begin(); entry != client_send_queue.end(); entry++)
{
delete[] entry->data;
}
client_send_queue.clear();
client_send_coalesce.clear();
}
else
{
shutdown(client_sock, SD_RECEIVE);
}
delete client_listen_thread;
shutdown(client_sock, SD_RECEIVE);
closesocket(client_sock);
}
}
@@ -141,6 +174,7 @@ NetworkServer::NetworkServer()
plugin_manager = nullptr;
profile_manager = nullptr;
profilemanager_thread = nullptr;
settings_manager = nullptr;
}
@@ -997,6 +1031,14 @@ void NetworkServer::ConnectionThreadFunction(int socket_idx)
client_info->client_listen_thread = new std::thread(&NetworkServer::ListenThreadFunction, this, client_info);
client_info->client_listen_thread->detach();
/*-------------------------------------------------*\
| Start the per-client send thread. It is joinable |
| (not detached) so the client destructor can stop |
| and join it cleanly. |
\*-------------------------------------------------*/
client_info->client_send_running = true;
client_info->client_send_thread = new std::thread(&NetworkServer::ClientSendThreadFunction, this, client_info);
ServerClients.push_back(client_info);
ServerClientsMutex.unlock();
@@ -1068,7 +1110,7 @@ void NetworkServer::ControllerListenThread(NetworkServerControllerThread* this_t
delete[] queue_entry.data;
SendAck(queue_entry.client_info, queue_entry.header.pkt_id, queue_entry.header.pkt_id, status);
SendAck(queue_entry.client_info, queue_entry.header.pkt_dev_id, queue_entry.header.pkt_id, status);
}
}
else
@@ -1136,7 +1178,7 @@ void NetworkServer::ProfileManagerListenThread(NetworkServerControllerThread* th
delete[] queue_entry.data;
SendAck(queue_entry.client_info, queue_entry.header.pkt_id, queue_entry.header.pkt_id, status);
SendAck(queue_entry.client_info, queue_entry.header.pkt_dev_id, queue_entry.header.pkt_id, status);
}
}
}
@@ -1646,7 +1688,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetHIDDeviceInfo(NetworkClientInfo
std::vector<HIDDeviceInfo> device_info = ResourceManager::get()->GetHIDDeviceInfo();
unsigned int data_size = 0;
unsigned int device_count = (unsigned int)device_info.size();
unsigned short device_count = (unsigned short)device_info.size();
/*-----------------------------------------------------*\
| Calculate data size |
@@ -1654,7 +1696,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetHIDDeviceInfo(NetworkClientInfo
data_size += sizeof(data_size);
data_size += sizeof(device_count);
for(unsigned int device_idx = 0; device_idx < device_count; device_idx++)
for(unsigned short device_idx = 0; device_idx < device_count; device_idx++)
{
data_size += sizeof(device_info[device_idx].vendor_id);
data_size += sizeof(device_info[device_idx].product_id);
@@ -1754,7 +1796,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetI2CBusInfo(NetworkClientInfo* c
std::vector<i2c_smbus_info> bus_info = ResourceManager::get()->GetI2CBusInfo();
unsigned int data_size = 0;
unsigned int bus_count = (unsigned int)bus_info.size();
unsigned short bus_count = (unsigned short)bus_info.size();
data_size += sizeof(data_size);
data_size += sizeof(bus_count);
data_size += bus_count * sizeof(i2c_smbus_info);
@@ -1768,7 +1810,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetI2CBusInfo(NetworkClientInfo* c
memcpy(data_ptr, &bus_count, sizeof(bus_count));
data_ptr += sizeof(bus_count);
for(std::size_t bus_idx = 0; bus_idx < bus_info.size(); bus_idx++)
for(unsigned short bus_idx = 0; bus_idx < bus_count; bus_idx++)
{
memcpy(data_ptr, &bus_info[bus_idx], sizeof(bus_info[bus_idx]));
data_ptr += sizeof(bus_info[bus_idx]);
@@ -1799,7 +1841,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetSerialPorts(NetworkClientInfo*
std::vector<std::string> serial_ports = ResourceManager::get()->GetSerialPorts();
unsigned int data_size = 0;
unsigned int port_count = (unsigned int)serial_ports.size();
unsigned short port_count = (unsigned short)serial_ports.size();
/*-----------------------------------------------------*\
| Calculate data size |
@@ -1807,7 +1849,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetSerialPorts(NetworkClientInfo*
data_size += sizeof(data_size);
data_size += sizeof(port_count);
for(unsigned int port_idx = 0; port_idx < port_count; port_idx++)
for(unsigned short port_idx = 0; port_idx < port_count; port_idx++)
{
data_size += sizeof(unsigned short);
data_size += (unsigned int)strlen(serial_ports[port_idx].c_str()) + 1;
@@ -1825,7 +1867,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetSerialPorts(NetworkClientInfo*
memcpy(data_ptr, &port_count, sizeof(port_count));
data_ptr += sizeof(port_count);
for(unsigned int port_idx = 0; port_idx < port_count; port_idx++)
for(unsigned short port_idx = 0; port_idx < port_count; port_idx++)
{
unsigned short port_string_size = (unsigned short)strlen(serial_ports[port_idx].c_str()) + 1;
memcpy(data_ptr, &port_string_size, sizeof(port_string_size));
@@ -1859,7 +1901,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetUSBDeviceInfo(NetworkClientInfo
std::vector<USBDeviceInfo> device_info = ResourceManager::get()->GetUSBDeviceInfo();
unsigned int data_size = 0;
unsigned int device_count = (unsigned int)device_info.size();
unsigned short device_count = (unsigned short)device_info.size();
/*-----------------------------------------------------*\
| Calculate data size |
@@ -1867,7 +1909,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetUSBDeviceInfo(NetworkClientInfo
data_size += sizeof(data_size);
data_size += sizeof(device_count);
for(unsigned int device_idx = 0; device_idx < device_count; device_idx++)
for(unsigned short device_idx = 0; device_idx < device_count; device_idx++)
{
data_size += sizeof(device_info[device_idx].vendor_id);
data_size += sizeof(device_info[device_idx].product_id);
@@ -1943,7 +1985,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetUSBSerialPorts(NetworkClientInf
std::vector<SerialDeviceInfo> device_info = ResourceManager::get()->GetUSBSerialPorts();
unsigned int data_size = 0;
unsigned int device_count = (unsigned int)device_info.size();
unsigned short device_count = (unsigned short)device_info.size();
/*-----------------------------------------------------*\
| Calculate data size |
@@ -1951,7 +1993,7 @@ NetPacketStatus NetworkServer::ProcessRequest_GetUSBSerialPorts(NetworkClientInf
data_size += sizeof(data_size);
data_size += sizeof(device_count);
for(unsigned int device_idx = 0; device_idx < device_count; device_idx++)
for(unsigned short device_idx = 0; device_idx < device_count; device_idx++)
{
data_size += sizeof(device_info[device_idx].vendor_id);
data_size += sizeof(device_info[device_idx].product_id);
@@ -2986,7 +3028,7 @@ NetPacketStatus NetworkServer::ProcessRequest_RGBController_SetHidden(NetworkCli
return(NET_PACKET_STATUS_ERROR_INVALID_ID);
}
controllers[controller_idx]->SetHidden((bool*)data_ptr);
controllers[controller_idx]->SetHidden(*(bool*)data_ptr);
return(NET_PACKET_STATUS_OK);
}
@@ -4043,15 +4085,19 @@ void NetworkServer::SendRequest_RGBController_SignalUpdate(RGBController * contr
if(found)
{
/*-------------------------------------------------*\
| Send to each connected client with protocol 6 or |
| higher |
| Queue to each connected client with protocol 6+. |
| ServerClientsMutex is held across the loop so a |
| client cannot be deleted while we enqueue to it. |
\*-------------------------------------------------*/
ServerClientsMutex.lock();
for(std::size_t client_idx = 0; client_idx < ServerClients.size(); client_idx++)
{
if(ServerClients[client_idx]->client_protocol_version >= 6)
NetworkClientInfo* client_info = ServerClients[client_idx];
if(client_info->client_protocol_version >= 6)
{
SOCKET client_sock = ServerClients[client_idx]->client_sock;
unsigned int protocol_version = ServerClients[client_idx]->client_protocol_version;
unsigned int protocol_version = client_info->client_protocol_version;
NetPacketHeader reply_hdr;
unsigned char* reply_data;
unsigned int reply_size;
@@ -4154,19 +4200,121 @@ void NetworkServer::SendRequest_RGBController_SignalUpdate(RGBController * contr
InitNetPacketHeader(&reply_hdr, controller_id, NET_PACKET_ID_RGBCONTROLLER_SIGNALUPDATE, reply_size);
/*-----------------------------------------*\
| Send packet |
| Queue the packet for the send thread. |
| This runs inside SignalUpdate, so a |
| blocking send would stall the callback |
| drain and deadlock a rescan. The entry |
| takes ownership of reply_data; the send |
| thread frees it after the send, so it is |
| safe to send after the controller is |
| gone. |
\*-----------------------------------------*/
send_in_progress.lock();
send(client_sock, (const char *)&reply_hdr, sizeof(NetPacketHeader), MSG_NOSIGNAL);
send(client_sock, (const char *)reply_data, reply_size, MSG_NOSIGNAL);
send_in_progress.unlock();
{
std::lock_guard<std::mutex> queue_lock(client_info->client_send_mutex);
/*-----------------------------------------*\
| Delete data |
\*-----------------------------------------*/
delete[] reply_data;
if(update_reason == RGBCONTROLLER_UPDATE_REASON_UPDATELEDS)
{
/*---------------------------------*\
| Coalesce: a newer frame for |
| this controller replaces the |
| pending one, keeping the queue |
| bounded under a stalled client. |
\*---------------------------------*/
std::map<unsigned int, std::list<NetworkServerClientSendQueueEntry>::iterator>::iterator existing = client_info->client_send_coalesce.find(controller_id);
if(existing != client_info->client_send_coalesce.end())
{
delete[] existing->second->data;
existing->second->header = reply_hdr;
existing->second->data = reply_data;
}
else
{
client_info->client_send_queue.push_back(NetworkServerClientSendQueueEntry{controller_id, true, reply_hdr, reply_data});
client_info->client_send_coalesce[controller_id] = std::prev(client_info->client_send_queue.end());
}
}
else
{
/*---------------------------------*\
| Non-coalescable event. Backstop |
| a stalled client by dropping |
| the oldest at the cap. |
\*---------------------------------*/
if(client_info->client_send_queue.size() >= NETWORKSERVER_SIGNAL_QUEUE_MAX)
{
NetworkServerClientSendQueueEntry& oldest = client_info->client_send_queue.front();
if(oldest.coalescable)
{
client_info->client_send_coalesce.erase(oldest.controller_id);
}
delete[] oldest.data;
client_info->client_send_queue.pop_front();
LOG_WARNING("[%s] SignalUpdate queue full for client %s, dropping oldest packet", NETWORKSERVER, client_info->client_ip.c_str());
}
client_info->client_send_queue.push_back(NetworkServerClientSendQueueEntry{controller_id, false, reply_hdr, reply_data});
}
}
client_info->client_send_cv.notify_one();
}
}
ServerClientsMutex.unlock();
}
}
/*---------------------------------------------------------*\
| Per-client SignalUpdate send thread. The blocking send |
| happens here, not inside SignalUpdate, so a slow client |
| blocks only its own queue and thread. The callback drain |
| is never blocked, so a rescan cannot deadlock on a send. |
\*---------------------------------------------------------*/
void NetworkServer::ClientSendThreadFunction(NetworkClientInfo* client_info)
{
while(true)
{
NetworkServerClientSendQueueEntry entry;
{
std::unique_lock<std::mutex> queue_lock(client_info->client_send_mutex);
client_info->client_send_cv.wait(queue_lock, [client_info]{ return !client_info->client_send_queue.empty() || !client_info->client_send_running; });
if(!client_info->client_send_running)
{
break;
}
entry = client_info->client_send_queue.front();
if(entry.coalescable)
{
client_info->client_send_coalesce.erase(entry.controller_id);
}
client_info->client_send_queue.pop_front();
}
/*-------------------------------------------------*\
| send_in_progress still serializes against |
| response sends to this client. Routing those |
| through a per-client mutex is the follow-up. |
\*-------------------------------------------------*/
send_in_progress.lock();
send(client_info->client_sock, (const char *)&entry.header, sizeof(NetPacketHeader), MSG_NOSIGNAL);
send(client_info->client_sock, (const char *)entry.data, entry.header.pkt_size, MSG_NOSIGNAL);
send_in_progress.unlock();
/*-------------------------------------------------*\
| The entry owns the data pointer; free it now that |
| the send is done. |
\*-------------------------------------------------*/
delete[] entry.data;
}
}
+34
View File
@@ -14,9 +14,12 @@
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <list>
#include <map>
#include <mutex>
#include <thread>
#include <queue>
#include <vector>
#include "LogManager.h"
#include "RGBController.h"
#include "NetworkProtocol.h"
@@ -29,8 +32,24 @@
#define MAXSOCK 32
#define TCP_TIMEOUT_SECONDS 5
/*---------------------------------------------------------*\
| Backstop cap on a client's outbound queue. UPDATELEDS |
| coalesces per controller, so this is only hit by a flood |
| of non-coalescable events to a stalled client; the oldest |
| are dropped rather than grow without bound. |
\*---------------------------------------------------------*/
#define NETWORKSERVER_SIGNAL_QUEUE_MAX 1024
typedef void (*NetServerCallback)(void*);
typedef struct
{
unsigned int controller_id;
bool coalescable;
NetPacketHeader header;
unsigned char* data;
} NetworkServerClientSendQueueEntry;
class NetworkClientInfo
{
public:
@@ -45,6 +64,20 @@ public:
std::string client_ip;
bool client_is_local;
bool client_is_local_client;
/*-----------------------------------------------------*\
| Per-client outbound queue for SignalUpdate packets, |
| drained by a dedicated send thread so a slow client |
| never blocks SignalUpdate (which would deadlock a |
| rescan). UPDATELEDS packets coalesce per controller, |
| keeping the queue bounded to one per controller. |
\*-----------------------------------------------------*/
std::list<NetworkServerClientSendQueueEntry> client_send_queue;
std::map<unsigned int, std::list<NetworkServerClientSendQueueEntry>::iterator> client_send_coalesce;
std::mutex client_send_mutex;
std::condition_variable client_send_cv;
std::thread* client_send_thread;
std::atomic<bool> client_send_running;
};
typedef struct
@@ -220,6 +253,7 @@ private:
void ConnectionThreadFunction(int socket_idx);
void ControllerListenThread(NetworkServerControllerThread* this_thread);
void ListenThreadFunction(NetworkClientInfo* client_info);
void ClientSendThreadFunction(NetworkClientInfo* client_info);
void ProfileManagerListenThread(NetworkServerControllerThread* this_thread);
/*-----------------------------------------------------*\
+87
View File
@@ -10,6 +10,7 @@
\*---------------------------------------------------------*/
#include "OpenRGBPluginAPI.h"
#include "RGBController_Dummy.h"
#include "RGBController_Virtual.h"
OpenRGBPluginAPI::OpenRGBPluginAPI()
@@ -185,6 +186,11 @@ bool OpenRGBPluginAPI::LoadProfile(std::string profile_name)
return(profile_manager->LoadProfile(profile_name));
}
bool OpenRGBPluginAPI::SaveProfileFromPlugin(std::string profile_name, std::string plugin_name, nlohmann::json plugin_data)
{
return(profile_manager->SaveProfileFromPlugin(profile_name, plugin_name, plugin_data));
}
/*---------------------------------------------------------*\
| ResourceManager APIs |
\*---------------------------------------------------------*/
@@ -223,6 +229,87 @@ std::vector<RGBControllerInterface*> OpenRGBPluginAPI::GetRGBControllers()
return(resource_manager->GetRGBControllerInterfaces());
}
/*---------------------------------------------------------*\
| RGBController APIs |
\*---------------------------------------------------------*/
nlohmann::json OpenRGBPluginAPI::GetDeviceDescriptionJSON(RGBControllerInterface* controller)
{
return(RGBController::GetDeviceDescriptionJSON((RGBController*)controller));
}
nlohmann::json OpenRGBPluginAPI::GetLEDDescriptionJSON(led led)
{
return(RGBController::GetLEDDescriptionJSON(led));
}
nlohmann::json OpenRGBPluginAPI::GetMatrixMapDescriptionJSON(matrix_map_type matrix_map)
{
return(RGBController::GetMatrixMapDescriptionJSON(matrix_map));
}
nlohmann::json OpenRGBPluginAPI::GetModeDescriptionJSON(mode mode)
{
return(RGBController::GetModeDescriptionJSON(mode));
}
nlohmann::json OpenRGBPluginAPI::GetSegmentDescriptionJSON(segment segment)
{
return(RGBController::GetSegmentDescriptionJSON(segment));
}
nlohmann::json OpenRGBPluginAPI::GetZoneDescriptionJSON(zone zone)
{
return(RGBController::GetZoneDescriptionJSON(zone));
}
RGBControllerInterface* OpenRGBPluginAPI::SetDeviceDescriptionJSON(nlohmann::json controller_json)
{
RGBController_Dummy* new_controller = new RGBController_Dummy();
RGBController::SetDeviceDescriptionJSON(controller_json, (RGBController*)new_controller);
return(new_controller);
}
led OpenRGBPluginAPI::SetLEDDescriptionJSON(nlohmann::json led_json)
{
return(RGBController::SetLEDDescriptionJSON(led_json));
}
matrix_map_type OpenRGBPluginAPI::SetMatrixMapDescriptionJSON(nlohmann::json matrix_map_json)
{
return(RGBController::SetMatrixMapDescriptionJSON(matrix_map_json));
}
mode OpenRGBPluginAPI::SetModeDescriptionJSON(nlohmann::json mode_json)
{
return(RGBController::SetModeDescriptionJSON(mode_json));
}
segment OpenRGBPluginAPI::SetSegmentDescriptionJSON(nlohmann::json segment_json)
{
return(RGBController::SetSegmentDescriptionJSON(segment_json));
}
zone OpenRGBPluginAPI::SetZoneDescriptionJSON(nlohmann::json zone_json)
{
return(RGBController::SetZoneDescriptionJSON(zone_json));
}
bool OpenRGBPluginAPI::CompareControllers(RGBControllerInterface* controller_1, RGBControllerInterface* controller_2)
{
return(RGBController::CompareControllers((RGBController*)controller_1, (RGBController*)controller_2));
}
std::string OpenRGBPluginAPI::DeviceTypeToString(device_type type)
{
return(RGBController::DeviceTypeToString(type));
}
bool OpenRGBPluginAPI::SetModeValuesFromMode(mode& destination, mode& source)
{
return(RGBController::SetModeValuesFromMode(destination, source));
}
/*---------------------------------------------------------*\
| SettingsManager APIs |
\*---------------------------------------------------------*/
+22
View File
@@ -45,6 +45,7 @@ public:
void ClearActiveProfile();
std::vector<std::string> GetProfileList();
bool LoadProfile(std::string profile_name);
bool SaveProfileFromPlugin(std::string profile_name, std::string plugin_name, nlohmann::json plugin_data);
/*-----------------------------------------------------*\
| ResourceManager APIs |
@@ -57,6 +58,27 @@ public:
void WaitForDetection();
std::vector<RGBControllerInterface*> GetRGBControllers();
/*-----------------------------------------------------*\
| RGBController APIs |
\*-----------------------------------------------------*/
nlohmann::json GetDeviceDescriptionJSON(RGBControllerInterface* controller);
nlohmann::json GetLEDDescriptionJSON(led led);
nlohmann::json GetMatrixMapDescriptionJSON(matrix_map_type matrix_map);
nlohmann::json GetModeDescriptionJSON(mode mode);
nlohmann::json GetSegmentDescriptionJSON(segment segment);
nlohmann::json GetZoneDescriptionJSON(zone zone);
RGBControllerInterface* SetDeviceDescriptionJSON(nlohmann::json controller_json);
led SetLEDDescriptionJSON(nlohmann::json led_json);
matrix_map_type SetMatrixMapDescriptionJSON(nlohmann::json matrix_map_json);
mode SetModeDescriptionJSON(nlohmann::json mode_json);
segment SetSegmentDescriptionJSON(nlohmann::json segment_json);
zone SetZoneDescriptionJSON(nlohmann::json zone_json);
bool CompareControllers(RGBControllerInterface* controller_1, RGBControllerInterface* controller_2);
std::string DeviceTypeToString(device_type type);
bool SetModeValuesFromMode(mode& destination, mode& source);
/*-----------------------------------------------------*\
| SettingsManager APIs |
\*-----------------------------------------------------*/
+43 -21
View File
@@ -77,43 +77,65 @@ public:
/*-----------------------------------------------------*\
| LogManager APIs |
\*-----------------------------------------------------*/
virtual void LogEntry(const char* filename, int line, unsigned int level, const char* fmt, ...) = 0;
virtual void LogEntry(const char* filename, int line, unsigned int level, const char* fmt, ...) = 0;
/*-----------------------------------------------------*\
| PluginManager APIs |
\*-----------------------------------------------------*/
virtual RGBControllerInterface* CreateVirtualRGBController(RGBController_Setup* setup) = 0;
virtual void DeleteVirtualRGBController(RGBControllerInterface* rgb_controller) = 0;
virtual void RegisterVirtualRGBController(RGBControllerInterface* rgb_controller) = 0;
virtual void RegisterVirtualRGBControllerInThread(RGBControllerInterface* rgb_controller) = 0;
virtual void UnregisterVirtualRGBController(RGBControllerInterface* rgb_controller) = 0;
virtual void UpdateVirtualRGBController(RGBControllerInterface* rgb_controller, RGBController_Setup* setup) = 0;
virtual void UnregisterVirtualRGBControllerInThread(RGBControllerInterface* rgb_controller) = 0;
virtual RGBControllerInterface* CreateVirtualRGBController(RGBController_Setup* setup) = 0;
virtual void DeleteVirtualRGBController(RGBControllerInterface* rgb_controller) = 0;
virtual void RegisterVirtualRGBController(RGBControllerInterface* rgb_controller) = 0;
virtual void RegisterVirtualRGBControllerInThread(RGBControllerInterface* rgb_controller) = 0;
virtual void UnregisterVirtualRGBController(RGBControllerInterface* rgb_controller) = 0;
virtual void UpdateVirtualRGBController(RGBControllerInterface* rgb_controller, RGBController_Setup* setup) = 0;
virtual void UnregisterVirtualRGBControllerInThread(RGBControllerInterface* rgb_controller) = 0;
/*-----------------------------------------------------*\
| ProfileManager APIs |
\*-----------------------------------------------------*/
virtual void ClearActiveProfile() = 0;
virtual std::vector<std::string> GetProfileList() = 0;
virtual bool LoadProfile(std::string profile_name) = 0;
virtual void ClearActiveProfile() = 0;
virtual std::vector<std::string> GetProfileList() = 0;
virtual bool LoadProfile(std::string profile_name) = 0;
virtual bool SaveProfileFromPlugin(std::string profile_name, std::string plugin_name, nlohmann::json plugin_data) = 0;
/*-----------------------------------------------------*\
| ResourceManager APIs |
\*-----------------------------------------------------*/
virtual filesystem::path GetConfigurationDirectory() = 0;
virtual bool GetDetectionEnabled() = 0;
virtual unsigned int GetDetectionPercent() = 0;
virtual std::string GetDetectionString() = 0;
virtual void RescanDevices() = 0;
virtual void WaitForDetection() = 0;
virtual std::vector<RGBControllerInterface*> GetRGBControllers() = 0;
virtual filesystem::path GetConfigurationDirectory() = 0;
virtual bool GetDetectionEnabled() = 0;
virtual unsigned int GetDetectionPercent() = 0;
virtual std::string GetDetectionString() = 0;
virtual void RescanDevices() = 0;
virtual void WaitForDetection() = 0;
virtual std::vector<RGBControllerInterface*> GetRGBControllers() = 0;
/*-----------------------------------------------------*\
| RGBController APIs |
\*-----------------------------------------------------*/
virtual nlohmann::json GetDeviceDescriptionJSON(RGBControllerInterface* controller) = 0;
virtual nlohmann::json GetLEDDescriptionJSON(led led) = 0;
virtual nlohmann::json GetMatrixMapDescriptionJSON(matrix_map_type matrix_map) = 0;
virtual nlohmann::json GetModeDescriptionJSON(mode mode) = 0;
virtual nlohmann::json GetSegmentDescriptionJSON(segment segment) = 0;
virtual nlohmann::json GetZoneDescriptionJSON(zone zone) = 0;
virtual RGBControllerInterface* SetDeviceDescriptionJSON(nlohmann::json controller_json) = 0;
virtual led SetLEDDescriptionJSON(nlohmann::json led_json) = 0;
virtual matrix_map_type SetMatrixMapDescriptionJSON(nlohmann::json matrix_map_json) = 0;
virtual mode SetModeDescriptionJSON(nlohmann::json mode_json) = 0;
virtual segment SetSegmentDescriptionJSON(nlohmann::json segment_json) = 0;
virtual zone SetZoneDescriptionJSON(nlohmann::json zone_json) = 0;
virtual bool CompareControllers(RGBControllerInterface* controller_1, RGBControllerInterface* controller_2) = 0;
virtual std::string DeviceTypeToString(device_type type) = 0;
virtual bool SetModeValuesFromMode(mode& destination, mode& source) = 0;
/*-----------------------------------------------------*\
| SettingsManager APIs |
\*-----------------------------------------------------*/
virtual nlohmann::json GetSettings(std::string settings_key) = 0;
virtual void SaveSettings() = 0;
virtual void SetSettings(std::string settings_key, nlohmann::json new_settings) = 0;
virtual nlohmann::json GetSettings(std::string settings_key) = 0;
virtual void SaveSettings() = 0;
virtual void SetSettings(std::string settings_key, nlohmann::json new_settings) = 0;
};
class OpenRGBPluginInterface
+25 -20
View File
@@ -170,6 +170,7 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
/*-----------------------------------------------------*\
| Prepare variables and open plugin settings |
\*-----------------------------------------------------*/
std::string path_string = path.generic_u8string();
OpenRGBPluginInterface* plugin = nullptr;
unsigned int plugin_idx;
json plugin_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("Plugins");
@@ -183,7 +184,7 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
{
LOG_WARNING("[%s] Checking remove %d, %s", PLUGINMANAGER, plugin_remove_idx, to_string(plugin_settings["plugins_remove"][plugin_remove_idx]).c_str());
if(plugin_settings["plugins_remove"][plugin_remove_idx] == path.generic_u8string())
if(plugin_settings["plugins_remove"][plugin_remove_idx] == path_string)
{
/*-----------------------------------------*\
| Delete the plugin file |
@@ -209,7 +210,7 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
{
if(path == ActivePlugins[plugin_idx].path)
{
LOG_WARNING("[%s] Plugin file %s already in list, skipping.", PLUGINMANAGER, path.c_str());
LOG_WARNING("[%s] Plugin file %s already in list, skipping.", PLUGINMANAGER, path_string.c_str());
return;
}
}
@@ -217,7 +218,6 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
/*-----------------------------------------------------*\
| Create a QPluginLoader and extract the metadata |
\*-----------------------------------------------------*/
std::string path_string = path.generic_u8string();
QPluginLoader* loader = new QPluginLoader(QString::fromStdString(path_string));
QJsonObject metadata = loader->metaData();
unsigned int plugin_api_version = 0;
@@ -233,7 +233,7 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
}
else
{
LOG_WARNING("[%s] Plugin %s does not have a MetaData field, skipping.", PLUGINMANAGER, path.c_str());
LOG_WARNING("[%s] Plugin %s does not have a MetaData field, skipping.", PLUGINMANAGER, path_string.c_str());
return;
}
@@ -243,7 +243,7 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
}
else
{
LOG_WARNING("[%s] Plugin %s does not have an OpenRGBPluginAPIVersion field, skipping.", PLUGINMANAGER, path.c_str());
LOG_WARNING("[%s] Plugin %s does not have an OpenRGBPluginAPIVersion field, skipping.", PLUGINMANAGER, path_string.c_str());
return;
}
@@ -253,7 +253,7 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
}
else
{
LOG_WARNING("[%s] Plugin %s does not have an Id field, skipping.", PLUGINMANAGER, path.c_str());
LOG_WARNING("[%s] Plugin %s does not have an Id field, skipping.", PLUGINMANAGER, path_string.c_str());
return;
}
@@ -291,19 +291,29 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
entry.info.URL = plugin_url.toStdString();
entry.info.Version = plugin_version.toStdString();
entry.id = plugin_id;
entry.incompatible = (plugin_api_version == OPENRGB_PLUGIN_API_VERSION);
entry.incompatible = true;
entry.is_system = is_system;
entry.loader = loader;
entry.path = path_string;
entry.plugin = plugin;
entry.widget = nullptr;
/*-----------------------------------------------------*\
| If metadata indicates a mismatching API version, |
| skip loading |
\*-----------------------------------------------------*/
if(plugin_api_version != OPENRGB_PLUGIN_API_VERSION)
{
LOG_WARNING("[%s] Plugin %s has an incompatible API version", PLUGINMANAGER, path_string.c_str());
goto add_plugin_entry;
}
/*-----------------------------------------------------*\
| Check to see if this plugin's ID already exists |
\*-----------------------------------------------------*/
for(plugin_idx = 0; plugin_idx < ActivePlugins.size(); plugin_idx++)
{
if(plugin_id == ActivePlugins[plugin_idx].id)
if(plugin_id == ActivePlugins[plugin_idx].id && !ActivePlugins[plugin_idx].incompatible)
{
entry.info.Description = "This plugin has a duplicate ID as an already loaded plugin and cannot be loaded.";
LOG_WARNING("[%s] Plugin ID %s already in list.", PLUGINMANAGER, plugin_id.toStdString().c_str());
@@ -311,23 +321,18 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
}
}
/*-----------------------------------------------------*\
| If metadata indicates a matching API version, |
| continue loading |
\*-----------------------------------------------------*/
if(plugin_api_version == OPENRGB_PLUGIN_API_VERSION)
{
QObject* instance = loader->instance();
if(!loader->isLoaded())
{
LOG_WARNING("[%s] Plugin %s cannot be loaded: %s", PLUGINMANAGER, path.c_str(), loader->errorString().toStdString().c_str());
LOG_WARNING("[%s] Plugin %s cannot be loaded: %s", PLUGINMANAGER, path_string.c_str(), loader->errorString().toStdString().c_str());
goto add_plugin_entry;
}
if(!instance)
{
LOG_WARNING("[%s] Plugin %s cannot be instantiated.", PLUGINMANAGER, path.c_str());
LOG_WARNING("[%s] Plugin %s cannot be instantiated.", PLUGINMANAGER, path_string.c_str());
goto add_plugin_entry;
}
@@ -338,13 +343,13 @@ void PluginManager::AddPlugin(const filesystem::path& path, bool is_system)
if(!plugin)
{
LOG_WARNING("[%s] Plugin %s cannot be casted to OpenRGBPluginInterface", PLUGINMANAGER, path.c_str());
LOG_WARNING("[%s] Plugin %s cannot be casted to OpenRGBPluginInterface", PLUGINMANAGER, path_string.c_str());
goto add_plugin_entry;
}
if(plugin->GetPluginAPIVersion() != OPENRGB_PLUGIN_API_VERSION)
{
LOG_WARNING("[%s] Plugin %s has a compatible API version", PLUGINMANAGER, path.c_str());
LOG_WARNING("[%s] Plugin %s has an incompatible API version", PLUGINMANAGER, path_string.c_str());
goto add_plugin_entry;
}
@@ -433,7 +438,7 @@ void PluginManager::RemovePlugin(const filesystem::path& path)
{
unsigned int plugin_idx;
LOG_TRACE("[%s] Attempting to remove plugin %s", PLUGINMANAGER, path.c_str());
LOG_TRACE("[%s] Attempting to remove plugin %s", PLUGINMANAGER, path.generic_u8string().c_str());
/*-----------------------------------------------------*\
| Search active plugins to see if this path already |
@@ -453,7 +458,7 @@ void PluginManager::RemovePlugin(const filesystem::path& path)
\*-----------------------------------------------------*/
if(plugin_idx == ActivePlugins.size())
{
LOG_TRACE("[%s] Plugin %s not active", PLUGINMANAGER, path.c_str());
LOG_TRACE("[%s] Plugin %s not active", PLUGINMANAGER, path.generic_u8string().c_str());
return;
}
@@ -463,7 +468,7 @@ void PluginManager::RemovePlugin(const filesystem::path& path)
\*-----------------------------------------------------*/
if(ActivePlugins[plugin_idx].loader->isLoaded())
{
LOG_TRACE("[%s] Plugin %s is active, unloading", PLUGINMANAGER, path.c_str());
LOG_TRACE("[%s] Plugin %s is active, unloading", PLUGINMANAGER, path.generic_u8string().c_str());
UnloadPlugin(&ActivePlugins[plugin_idx]);
}
+224 -1
View File
@@ -176,6 +176,106 @@ std::string ProfileManager::GetActiveProfile()
return(active_profile);
}
std::vector<RGBController*> ProfileManager::GetControllerListFromLegacyProfile(std::string profile_name, bool sizes)
{
unsigned int controller_size;
unsigned int profile_offset = 0;
std::vector<RGBController*> temp_controllers;
filesystem::path filename = configuration_directory / StringUtils::make_filename(profile_name);
/*-----------------------------------------------------*\
| Determine file extension |
\*-----------------------------------------------------*/
if(sizes)
{
filename.concat(".ors");
}
else
{
if(filename.extension() != ".orp")
{
filename.concat(".orp");
}
}
/*-----------------------------------------------------*\
| Open input file in binary mode |
\*-----------------------------------------------------*/
std::ifstream profile_file(filename, std::ios::in | std::ios::binary);
if(!profile_file.is_open())
{
LOG_WARNING("[%s] Unable to open legacy profile: %s", PROFILEMANAGER, filename.string().c_str());
return(temp_controllers);
}
/*-----------------------------------------------------*\
| Read and verify file header |
\*-----------------------------------------------------*/
char profile_string[16] = "";
unsigned int profile_version = 0;
profile_file.read(profile_string, 16);
profile_file.read((char *)&profile_version, sizeof(unsigned int));
/*-----------------------------------------------------*\
| Profile version started at 1 and protocol version |
| started at 0. Version 1 profiles should use protocol |
| 0, but 2 or greater should be synchronized |
\*-----------------------------------------------------*/
if(profile_version == 1)
{
profile_version = 0;
}
profile_offset += 16 + sizeof(unsigned int);
profile_file.seekg(profile_offset);
if(strcmp(profile_string, OPENRGB_PROFILE_HEADER) == 0)
{
if(profile_version <= OPENRGB_PROFILE_VERSION)
{
/*---------------------------------------------*\
| Read controller data from file until EOF |
\*---------------------------------------------*/
while(!(profile_file.peek() == EOF))
{
profile_file.read((char *)&controller_size, sizeof(controller_size));
unsigned char *controller_data = new unsigned char[controller_size];
profile_file.seekg(profile_offset);
profile_file.read((char *)controller_data, controller_size);
RGBController_Dummy *temp_controller = new RGBController_Dummy();
RGBController::SetDeviceDescription(controller_data + sizeof(unsigned int), controller_size - sizeof(unsigned int), temp_controller, profile_version);
temp_controllers.push_back(temp_controller);
delete[] controller_data;
profile_offset += controller_size;
profile_file.seekg(profile_offset);
}
}
else
{
LOG_WARNING("[%s] Legacy profile has unsupported version %u: %s", PROFILEMANAGER, profile_version, filename.string().c_str());
return(temp_controllers);
}
}
else
{
LOG_WARNING("[%s] Unable to read legacy profile: %s", PROFILEMANAGER, filename.string().c_str());
return(temp_controllers);
}
return(temp_controllers);
}
std::vector<RGBController*> ProfileManager::GetControllerListFromProfileJson(nlohmann::json profile_json)
{
std::vector<RGBController*> profile_controllers;
@@ -681,7 +781,44 @@ bool ProfileManager::SaveProfileFromJSON(nlohmann::json profile_json)
\*-------------------------------------------------*/
UpdateProfileList();
SetActiveProfile(profile_json["profile_name"]);
return(true);
}
else
{
return(false);
}
}
bool ProfileManager::SaveProfileFromPlugin(std::string profile_name, std::string plugin_name, nlohmann::json plugin_data)
{
/*-----------------------------------------------------*\
| If a name was entered, save the profile file |
\*-----------------------------------------------------*/
if(profile_name != "")
{
/*-------------------------------------------------*\
| Start filling in profile json data |
\*-------------------------------------------------*/
nlohmann::json profile_json;
profile_json["profile_version"] = OPENRGB_PROFILE_VERSION;
profile_json["profile_name"] = profile_name;
profile_json["plugins"][plugin_name] = plugin_data;
if(ResourceManager::get()->IsLocalClient() && (ResourceManager::get()->GetLocalClient()->GetSupportsProfileManagerAPI()))
{
/*---------------------------------------------*\
| Upload the profile to the server |
\*---------------------------------------------*/
ResourceManager::get()->GetLocalClient()->ProfileManager_UploadProfile(profile_json.dump());
}
else
{
/*---------------------------------------------*\
| Save the profile to file from the JSON |
\*---------------------------------------------*/
SaveProfileFromJSON(profile_json);
}
return(true);
}
@@ -832,6 +969,92 @@ void ProfileManager::SetConfigurationDirectory(const filesystem::path& directory
manually_configured_rgb_controllers = GetControllerListFromSavedConfiguration();
}
void ProfileManager::MigrateLegacyProfiles()
{
/*-----------------------------------------------------*\
| Migrate any controller configuration in sizes.ors |
| that doesn't already exist in the manual |
| configuration list |
\*-----------------------------------------------------*/
std::vector<RGBController*> sizes_controllers;
sizes_controllers = GetControllerListFromLegacyProfile("sizes", true);
if(sizes_controllers.size() > 0)
{
for(std::size_t controller_idx = 0; controller_idx < sizes_controllers.size(); controller_idx++)
{
bool found = false;
for(std::size_t manually_configured_idx = 0; manually_configured_idx < manually_configured_rgb_controllers.size(); manually_configured_idx++)
{
if(RGBController::CompareControllers(sizes_controllers[controller_idx], manually_configured_rgb_controllers[manually_configured_idx]))
{
found = true;
break;
}
}
if(!found)
{
manually_configured_rgb_controllers.push_back(sizes_controllers[controller_idx]);
}
}
SaveConfiguration();
}
/*-----------------------------------------------------*\
| Look at each file in the configuration directory for |
| files with .orp extension |
\*-----------------------------------------------------*/
for(const filesystem::directory_entry &entry : filesystem::directory_iterator(configuration_directory))
{
std::string filename = entry.path().filename().string();
if(filename.size() <= 4 || filename.substr(filename.size() - 4) != ".orp")
{
continue;
}
/*-------------------------------------------------*\
| Determine the profile name based on the filename |
\*-------------------------------------------------*/
std::string profile_name = StringUtils::make_filename(filename.substr(0, filename.size() - 4));
std::vector<RGBController*> profile_controllers;
std::vector<std::string> profile_plugin_data;
/*-------------------------------------------------*\
| If this profile name already exists, skip it |
\*-------------------------------------------------*/
bool found = false;
for(std::size_t profile_idx = 0; profile_idx < profile_list.size(); profile_idx++)
{
if(profile_name == profile_list[profile_idx])
{
found = true;
break;
}
}
if(found)
{
continue;
}
/*-------------------------------------------------*\
| Read the controller data from the profile |
\*-------------------------------------------------*/
profile_controllers = GetControllerListFromLegacyProfile(profile_name, false);
/*-------------------------------------------------*\
| Save the profile in JSON format |
\*-------------------------------------------------*/
SaveProfileCustom(profile_name, profile_controllers, 0, false, profile_plugin_data);
}
}
void ProfileManager::SetProfileListFromDescription(unsigned int /*data_size*/, char * data_buf)
{
unsigned int data_ptr = sizeof(unsigned int);
+4
View File
@@ -83,6 +83,7 @@ public:
void DeleteProfile(std::string profile_name);
std::string GetActiveProfile();
std::vector<RGBController*> GetControllerListFromLegacyProfile(std::string profile_name, bool sizes);
std::vector<RGBController*> GetControllerListFromProfileJson(nlohmann::json profile_json);
std::vector<RGBController*> GetControllerListFromProfileName(std::string profile_name);
std::vector<RGBController*> GetControllerListFromSavedConfiguration();
@@ -110,6 +111,8 @@ public:
void OnProfileAboutToLoad();
void OnProfileLoaded(std::string profile_json_string);
void MigrateLegacyProfiles();
/*-----------------------------------------------------*\
| Callback Registration Functions |
\*-----------------------------------------------------*/
@@ -122,6 +125,7 @@ public:
bool SaveProfile(std::string profile_name);
bool SaveProfileCustom(std::string profile_name, std::vector<RGBController*> controllers, RGBColor base_color, bool base_color_enabled, std::vector<std::string> enabled_plugins);
bool SaveProfileFromJSON(nlohmann::json profile_json);
bool SaveProfileFromPlugin(std::string profile_name, std::string plugin_name, nlohmann::json plugin_data);
bool SaveConfiguration();
void SetActiveProfile(std::string profile_name);
+61 -46
View File
@@ -1075,7 +1075,8 @@ void RGBController::SetZoneModeDirection(unsigned int zone, unsigned int mode, u
&& (mode < zones[zone].modes.size())
&& (((zones[zone].modes[mode].flags & MODE_FLAG_HAS_DIRECTION_HV) && ((direction == MODE_DIRECTION_HORIZONTAL) || (direction == MODE_DIRECTION_VERTICAL)))
|| ((zones[zone].modes[mode].flags & MODE_FLAG_HAS_DIRECTION_LR) && ((direction == MODE_DIRECTION_LEFT) || (direction == MODE_DIRECTION_RIGHT)))
|| ((zones[zone].modes[mode].flags & MODE_FLAG_HAS_DIRECTION_UD) && ((direction == MODE_DIRECTION_UP) || (direction == MODE_DIRECTION_DOWN)))))
|| ((zones[zone].modes[mode].flags & MODE_FLAG_HAS_DIRECTION_UD) && ((direction == MODE_DIRECTION_UP) || (direction == MODE_DIRECTION_DOWN)))
|| ((zones[zone].modes[mode].flags & MODE_FLAG_HAS_DIRECTION_DIAG) && (direction >= MODE_DIRECTION_UP_LEFT) && (direction <= MODE_DIRECTION_DOWN_RIGHT))))
{
zones[zone].modes[mode].direction = direction;
}
@@ -1408,7 +1409,8 @@ void RGBController::SetModeDirection(unsigned int mode, unsigned int direction)
if((mode < modes.size())
&& (((modes[mode].flags & MODE_FLAG_HAS_DIRECTION_HV) && ((direction == MODE_DIRECTION_HORIZONTAL) || (direction == MODE_DIRECTION_VERTICAL)))
|| ((modes[mode].flags & MODE_FLAG_HAS_DIRECTION_LR) && ((direction == MODE_DIRECTION_LEFT) || (direction == MODE_DIRECTION_RIGHT)))
|| ((modes[mode].flags & MODE_FLAG_HAS_DIRECTION_UD) && ((direction == MODE_DIRECTION_UP) || (direction == MODE_DIRECTION_DOWN)))))
|| ((modes[mode].flags & MODE_FLAG_HAS_DIRECTION_UD) && ((direction == MODE_DIRECTION_UP) || (direction == MODE_DIRECTION_DOWN)))
|| ((modes[mode].flags & MODE_FLAG_HAS_DIRECTION_DIAG) && (direction >= MODE_DIRECTION_UP_LEFT) && (direction <= MODE_DIRECTION_DOWN_RIGHT))))
{
modes[mode].direction = direction;
}
@@ -1857,17 +1859,17 @@ void RGBController::UnregisterUpdateCallback(void * callback_arg)
| callback, return immediately rather than waiting for |
| callback completion to avoid deadlock. |
\*-----------------------------------------------------*/
if(SignalCalls != 0)
{
return;
}
//if(SignalCalls != 0)
//{
// return;
//}
/*-----------------------------------------------------*\
| Otherwise, wait for all currently active callbacks to |
| complete before returning |
\*-----------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(SignalMutex);
SignalCallsDone.wait(wait_lock, [this]{ return SignalCalls == 0; });
//std::unique_lock<std::mutex> wait_lock(SignalMutex);
//SignalCallsDone.wait(wait_lock, [this]{ return SignalCalls == 0; });
}
void RGBController::ClearCallbacks()
@@ -1882,17 +1884,17 @@ void RGBController::ClearCallbacks()
| callback, return immediately rather than waiting for |
| callback completion to avoid deadlock. |
\*-----------------------------------------------------*/
if(SignalCalls != 0)
{
return;
}
//if(SignalCalls != 0)
//{
// return;
//}
/*-----------------------------------------------------*\
| Otherwise, wait for all currently active callbacks to |
| complete before returning |
\*-----------------------------------------------------*/
std::unique_lock<std::mutex> wait_lock(SignalMutex);
SignalCallsDone.wait(wait_lock, [this]{ return SignalCalls == 0; });
//std::unique_lock<std::mutex> wait_lock(SignalMutex);
//SignalCallsDone.wait(wait_lock, [this]{ return SignalCalls == 0; });
}
void RGBController::SignalUpdate(unsigned int update_reason)
@@ -1913,14 +1915,14 @@ void RGBController::SignalUpdate(unsigned int update_reason)
| count. Return immediately if the controller has been |
| shut down. |
\*-----------------------------------------------------*/
SignalMutex.lock();
if(SignalShutdown)
{
UpdateMutex.unlock();
return;
}
SignalCalls++;
SignalMutex.unlock();
//SignalMutex.lock();
//if(SignalShutdown)
//{
// UpdateMutex.unlock();
// return;
//}
//SignalCalls++;
//SignalMutex.unlock();
/*-----------------------------------------------------*\
| Copy the list of callbacks and unlock the update |
@@ -1929,7 +1931,7 @@ void RGBController::SignalUpdate(unsigned int update_reason)
callbacks = UpdateCallbacks;
callback_args = UpdateCallbackArgs;
UpdateMutex.unlock();
//UpdateMutex.unlock();
/*-----------------------------------------------------*\
| Invoke the copied callbacks |
@@ -1939,20 +1941,21 @@ void RGBController::SignalUpdate(unsigned int update_reason)
callbacks[callback_idx](callback_args[callback_idx], update_reason, this);
}
UpdateMutex.unlock();
/*-----------------------------------------------------*\
| Decrement the signal call count once callbacks have |
| been called. Notify anyone waiting on signal calls |
| to be done. |
\*-----------------------------------------------------*/
SignalMutex.lock();
SignalCalls--;
//SignalMutex.lock();
//SignalCalls--;
if(SignalCalls == 0)
{
SignalCallsDone.notify_all();
}
//if(SignalCalls == 0)
//{
// SignalCallsDone.notify_all();
//}
SignalMutex.unlock();
//SignalMutex.unlock();
}
/*---------------------------------------------------------*\
@@ -2882,9 +2885,15 @@ unsigned char * RGBController::GetModeDescriptionData(unsigned char* data_ptr, m
/*-----------------------------------------------------*\
| Copy in mode flags (data) |
| Strip diagonal flag for SDK v5 and earlier clients |
\*-----------------------------------------------------*/
memcpy(data_ptr, &mode.flags, sizeof(mode.flags));
data_ptr += sizeof(mode.flags);
unsigned int flags = mode.flags;
if(protocol_version < 6)
{
flags &= ~MODE_FLAG_HAS_DIRECTION_DIAG;
}
memcpy(data_ptr, &flags, sizeof(flags));
data_ptr += sizeof(flags);
/*-----------------------------------------------------*\
| Copy in mode speed_min (data) |
@@ -2941,9 +2950,15 @@ unsigned char * RGBController::GetModeDescriptionData(unsigned char* data_ptr, m
/*-----------------------------------------------------*\
| Copy in mode direction (data) |
| Clamp diagonal directions for SDK v5 and earlier |
\*-----------------------------------------------------*/
memcpy(data_ptr, &mode.direction, sizeof(mode.direction));
data_ptr += sizeof(mode.direction);
unsigned int dir = mode.direction;
if(protocol_version < 6 && dir > MODE_DIRECTION_VERTICAL)
{
dir = MODE_DIRECTION_LEFT;
}
memcpy(data_ptr, &dir, sizeof(dir));
data_ptr += sizeof(dir);
/*-----------------------------------------------------*\
| Copy in mode color_mode (data) |
@@ -3174,6 +3189,12 @@ unsigned char * RGBController::GetZoneDescriptionData(unsigned char* data_ptr, z
\*-----------------------------------------------------*/
if(protocol_version >= 6)
{
/*-------------------------------------------------*\
| Copy in active mode |
\*-------------------------------------------------*/
memcpy(data_ptr, &zone.active_mode, sizeof(zone.active_mode));
data_ptr += sizeof(zone.active_mode);
/*-------------------------------------------------*\
| Copy in number of modes |
\*-------------------------------------------------*/
@@ -3182,12 +3203,6 @@ unsigned char * RGBController::GetZoneDescriptionData(unsigned char* data_ptr, z
memcpy(data_ptr, &num_modes, sizeof(num_modes));
data_ptr += sizeof(num_modes);
/*-------------------------------------------------*\
| Copy in active mode |
\*-------------------------------------------------*/
memcpy(data_ptr, &zone.active_mode, sizeof(zone.active_mode));
data_ptr += sizeof(zone.active_mode);
/*-------------------------------------------------*\
| Copy in modes |
\*-------------------------------------------------*/
@@ -3261,8 +3276,8 @@ unsigned int RGBController::GetZoneDescriptionSize(zone zone, unsigned int proto
{
unsigned short num_modes = (unsigned short)zone.modes.size();
data_size += sizeof(num_modes);
data_size += sizeof(zone.active_mode);
data_size += sizeof(num_modes);
for(int mode_index = 0; mode_index < num_modes; mode_index++)
{
@@ -3889,17 +3904,17 @@ unsigned char* RGBController::SetZoneDescription(unsigned char* data_ptr, unsign
\*-----------------------------------------------------*/
if(protocol_version >= 6)
{
/*-------------------------------------------------*\
| Copy in active mode |
\*-------------------------------------------------*/
COPY_DATA_FIELD(data_ptr, data_start, zone->active_mode);
/*-------------------------------------------------*\
| Copy in number of modes |
\*-------------------------------------------------*/
unsigned short num_modes;
COPY_DATA_FIELD(data_ptr, data_start, num_modes);
/*-------------------------------------------------*\
| Copy in active mode |
\*-------------------------------------------------*/
COPY_DATA_FIELD(data_ptr, data_start, zone->active_mode);
/*-------------------------------------------------*\
| Copy in modes |
\*-------------------------------------------------*/
+5
View File
@@ -73,6 +73,7 @@ enum
MODE_FLAG_AUTOMATIC_SAVE = (1 << 9), /* Mode automatically saves */
MODE_FLAG_REQUIRES_ENTIRE_DEVICE = (1 << 10),/* Mode always applies to entire */
/* device, overrides per-zone modes */
MODE_FLAG_HAS_DIRECTION_DIAG = (1 << 11),/* Mode has diagonal direction */
};
/*---------------------------------------------------------*\
@@ -86,6 +87,10 @@ enum
MODE_DIRECTION_DOWN = 3, /* Mode direction down */
MODE_DIRECTION_HORIZONTAL = 4, /* Mode direction horizontal */
MODE_DIRECTION_VERTICAL = 5, /* Mode direction vertical */
MODE_DIRECTION_UP_LEFT = 6, /* Mode direction up-left */
MODE_DIRECTION_UP_RIGHT = 7, /* Mode direction up-right */
MODE_DIRECTION_DOWN_LEFT = 8, /* Mode direction down-left */
MODE_DIRECTION_DOWN_RIGHT = 9, /* Mode direction down-right */
};
/*---------------------------------------------------------*\
+18
View File
@@ -99,7 +99,9 @@ void RGBController_Virtual::DeviceConfigureZone(int zone_idx)
{
if(DeviceConfigureZone_ptr)
{
AccessMutex.unlock();
DeviceConfigureZone_ptr(object_ptr, zone_idx);
AccessMutex.lock();
}
}
@@ -107,7 +109,9 @@ void RGBController_Virtual::DeviceUpdateLEDs()
{
if(DeviceUpdateLEDs_ptr)
{
AccessMutex.unlock_shared();
DeviceUpdateLEDs_ptr(object_ptr);
AccessMutex.lock_shared();
}
}
@@ -115,7 +119,9 @@ void RGBController_Virtual::DeviceUpdateZoneLEDs(int zone)
{
if(DeviceUpdateZoneLEDs_ptr)
{
AccessMutex.unlock_shared();
DeviceUpdateZoneLEDs_ptr(object_ptr, zone);
AccessMutex.lock_shared();
}
}
@@ -123,7 +129,9 @@ void RGBController_Virtual::DeviceUpdateSingleLED(int led)
{
if(DeviceUpdateSingleLED_ptr)
{
AccessMutex.unlock_shared();
DeviceUpdateSingleLED_ptr(object_ptr, led);
AccessMutex.lock_shared();
}
}
@@ -131,7 +139,9 @@ void RGBController_Virtual::DeviceUpdateMode()
{
if(DeviceUpdateMode_ptr)
{
AccessMutex.unlock_shared();
DeviceUpdateMode_ptr(object_ptr);
AccessMutex.lock_shared();
}
}
@@ -139,7 +149,9 @@ void RGBController_Virtual::DeviceUpdateZoneMode(int zone)
{
if(DeviceUpdateZoneMode_ptr)
{
AccessMutex.unlock_shared();
DeviceUpdateZoneMode_ptr(object_ptr, zone);
AccessMutex.lock_shared();
}
}
@@ -147,7 +159,9 @@ void RGBController_Virtual::DeviceSaveMode()
{
if(DeviceSaveMode_ptr)
{
AccessMutex.unlock_shared();
DeviceSaveMode_ptr(object_ptr);
AccessMutex.lock_shared();
}
}
@@ -155,7 +169,9 @@ void RGBController_Virtual::DeviceUpdateDeviceSpecificConfiguration()
{
if(DeviceUpdateDeviceSpecificConfiguration_ptr)
{
AccessMutex.unlock();
DeviceUpdateDeviceSpecificConfiguration_ptr(object_ptr);
AccessMutex.lock();
}
}
@@ -163,6 +179,8 @@ void RGBController_Virtual::DeviceUpdateDeviceSpecificZoneConfiguration(int zone
{
if(DeviceUpdateDeviceSpecificZoneConfiguration_ptr)
{
AccessMutex.unlock();
DeviceUpdateDeviceSpecificZoneConfiguration_ptr(object_ptr, zone);
AccessMutex.lock();
}
}
+184 -22
View File
@@ -86,6 +86,19 @@ static void ResourceManagerNetworkClientCallback(void* this_ptr, unsigned int up
this_obj->UpdateDeviceList();
break;
case NETWORKCLIENT_UPDATE_REASON_CLIENT_CONNECTED:
this_obj->SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_CLIENT_INFO_UPDATED);
break;
/*-------------------------------------------------*\
| Unplanned connection loss: free the client's |
| orphaned controllers on the teardown thread |
\*-------------------------------------------------*/
case NETWORKCLIENT_UPDATE_REASON_CLIENT_DISCONNECTED:
this_obj->SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_CLIENT_INFO_UPDATED);
this_obj->QueueClientTeardown();
break;
case NETWORKCLIENT_UPDATE_REASON_DETECTION_STARTED:
this_obj->SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_DETECTION_STARTED);
break;
@@ -151,6 +164,13 @@ ResourceManager::ResourceManager()
plugin_manager = NULL;
server = NULL;
/*-----------------------------------------------------*\
| Start the client teardown thread |
\*-----------------------------------------------------*/
ClientTeardownPending = false;
ClientTeardownRunning = true;
ClientTeardownThread = new std::thread(&ResourceManager::ClientTeardownThreadFunction, this);
SetupConfigurationDirectory();
/*-----------------------------------------------------*\
@@ -274,7 +294,21 @@ ResourceManager::ResourceManager()
ResourceManager::~ResourceManager()
{
/*-----------------------------------------------------*\
| Stop the client teardown thread |
\*-----------------------------------------------------*/
ClientTeardownMutex.lock();
ClientTeardownRunning = false;
ClientTeardownMutex.unlock();
ClientTeardownCv.notify_all();
if(ClientTeardownThread)
{
ClientTeardownThread->join();
delete ClientTeardownThread;
ClientTeardownThread = nullptr;
}
}
/*---------------------------------------------------------*\
@@ -535,38 +569,155 @@ void ResourceManager::RegisterNetworkClient(NetworkClient* new_client)
{
new_client->RegisterNetworkClientCallback(ResourceManagerNetworkClientCallback, this);
ClientListMutex.lock();
clients.push_back(new_client);
ClientListMutex.unlock();
}
void ResourceManager::UnregisterNetworkClient(NetworkClient* network_client)
{
/*-----------------------------------------------------*\
| Stop the disconnecting client |
| Queue the removal for the teardown thread. This is |
| called on the GUI thread, and the teardown rebuilds |
| the device list, whose DEVICE_LIST_UPDATED handler |
| is a BlockingQueuedConnection to the GUI thread. It |
| also keeps StopClient's join off the GUI thread |
\*-----------------------------------------------------*/
network_client->StopClient();
ClientTeardownMutex.lock();
/*-----------------------------------------------------*\
| Clear callbacks from the client before removal |
\*-----------------------------------------------------*/
network_client->ClearCallbacks();
/*-----------------------------------------------------*\
| Find the client to remove and remove it from the |
| clients list |
\*-----------------------------------------------------*/
std::vector<NetworkClient*>::iterator client_it = std::find(clients.begin(), clients.end(), network_client);
if(client_it != clients.end())
if(std::find(clients_to_remove.begin(), clients_to_remove.end(), network_client) == clients_to_remove.end())
{
clients.erase(client_it);
clients_to_remove.push_back(network_client);
}
/*-----------------------------------------------------*\
| Delete the client |
\*-----------------------------------------------------*/
delete network_client;
ClientTeardownPending = true;
UpdateDeviceList();
ClientTeardownMutex.unlock();
ClientTeardownCv.notify_all();
}
void ResourceManager::QueueClientTeardown()
{
ClientTeardownMutex.lock();
ClientTeardownPending = true;
ClientTeardownMutex.unlock();
ClientTeardownCv.notify_all();
}
void ResourceManager::ClientTeardownThreadFunction()
{
std::unique_lock<std::mutex> lock(ClientTeardownMutex);
while(ClientTeardownRunning)
{
if(!ClientTeardownPending)
{
ClientTeardownCv.wait(lock);
continue;
}
ClientTeardownPending = false;
/*-------------------------------------------------*\
| Take the clients queued for removal off the |
| pending list |
\*-------------------------------------------------*/
std::vector<NetworkClient*> remove_list;
remove_list.swap(clients_to_remove);
lock.unlock();
/*-------------------------------------------------*\
| Stop the clients being removed and take them out |
| of the clients list. StopClient joins the client |
| threads and moves the client's controllers to |
| orphaned_controllers; the delete below frees them |
\*-------------------------------------------------*/
for(std::size_t remove_idx = 0; remove_idx < remove_list.size(); remove_idx++)
{
remove_list[remove_idx]->StopClient();
remove_list[remove_idx]->ClearCallbacks();
/*---------------------------------------------*\
| If the removed client is the auto local |
| connection, leave client mode. Rescan and |
| GetDetection* use auto_connection_client, |
| which the delete below would leave pointing |
| at freed memory |
\*---------------------------------------------*/
if(remove_list[remove_idx] == auto_connection_client)
{
auto_connection_active = false;
auto_connection_client = NULL;
}
ClientListMutex.lock();
std::vector<NetworkClient*>::iterator client_it = std::find(clients.begin(), clients.end(), remove_list[remove_idx]);
if(client_it != clients.end())
{
clients.erase(client_it);
}
ClientListMutex.unlock();
}
/*-------------------------------------------------*\
| Collect the orphaned controllers from the clients |
| that remain. Ownership moves here, so a client |
| deleted later cannot free them a second time |
\*-------------------------------------------------*/
std::vector<RGBController*> orphaned;
ClientListMutex.lock();
for(std::size_t client_idx = 0; client_idx < clients.size(); client_idx++)
{
clients[client_idx]->TakeOrphanedControllers(orphaned);
}
ClientListMutex.unlock();
/*-------------------------------------------------*\
| Run the same sequence a rescan does while the |
| controllers are still allocated. Consumers |
| release them on DETECTION_STARTED, the GUI |
| deletes the device pages (whose destructors use |
| the controller) on UpdateDeviceList, and plugins |
| rebuild on DETECTION_COMPLETE. Free them last, |
| once nothing references them |
\*-------------------------------------------------*/
if(!remove_list.empty() || !orphaned.empty())
{
SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_DETECTION_STARTED);
UpdateDeviceList();
SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_DETECTION_COMPLETE);
/*---------------------------------------------*\
| Deleting a removed client frees the |
| controllers StopClient left on it |
\*---------------------------------------------*/
for(std::size_t remove_idx = 0; remove_idx < remove_list.size(); remove_idx++)
{
delete remove_list[remove_idx];
}
for(std::size_t orphaned_idx = 0; orphaned_idx < orphaned.size(); orphaned_idx++)
{
delete orphaned[orphaned_idx];
}
}
lock.lock();
}
}
/*---------------------------------------------------------*\
@@ -742,6 +893,8 @@ void ResourceManager::UpdateDeviceList()
/*-----------------------------------------------------*\
| Insert client controllers into controller list |
\*-----------------------------------------------------*/
ClientListMutex.lock();
for(std::size_t client_idx = 0; client_idx < clients.size(); client_idx++)
{
std::vector<RGBController*> rgb_controllers_client = clients[client_idx]->GetRGBControllers();
@@ -752,6 +905,8 @@ void ResourceManager::UpdateDeviceList()
}
}
ClientListMutex.unlock();
/*-----------------------------------------------------*\
| Update server list |
\*-----------------------------------------------------*/
@@ -955,9 +1110,9 @@ void ResourceManager::Initialize(bool tryConnect, bool detectDevices, bool start
{
/*-----------------------------------------------------*\
| Cache the parameters |
| TODO: Possibly cache them in the CLI file somewhere |
| Don't try auto connect if starting server |
\*-----------------------------------------------------*/
tryAutoConnect = tryConnect;
tryAutoConnect = tryConnect && !startServer;
detection_enabled = detectDevices;
start_server = startServer;
apply_post_options = applyPostOptions;
@@ -988,6 +1143,13 @@ void ResourceManager::Initialize(bool tryConnect, bool detectDevices, bool start
tryAutoConnect = false;
}
/*-----------------------------------------------------*\
| Migrate legacy profiles after attempting local |
| connection so that migrated profiles get uploaded to |
| the server if connected |
\*-----------------------------------------------------*/
profile_manager->MigrateLegacyProfiles();
/*-----------------------------------------------------*\
| Initialize Saved Client Connections |
\*-----------------------------------------------------*/
+21
View File
@@ -13,7 +13,9 @@
#pragma once
#include <condition_variable>
#include <memory>
#include <mutex>
#include <vector>
#include <functional>
#include <thread>
@@ -102,6 +104,7 @@ public:
\*-----------------------------------------------------*/
void RegisterNetworkClient(NetworkClient* new_client);
void UnregisterNetworkClient(NetworkClient* network_client);
void QueueClientTeardown();
/*-----------------------------------------------------*\
| Local Client Accessors |
@@ -139,6 +142,7 @@ public:
private:
bool AttemptLocalConnection();
void ClientTeardownThreadFunction();
void SetupConfigurationDirectory();
/*-----------------------------------------------------*\
@@ -214,6 +218,23 @@ private:
| Network Clients |
\*-----------------------------------------------------*/
std::vector<NetworkClient*> clients;
std::mutex ClientListMutex;
/*-----------------------------------------------------*\
| Client teardown thread |
| |
| Frees controllers a lost connection left behind, |
| inside a DETECTION_STARTED / DETECTION_COMPLETE |
| sequence. Runs on its own thread: StopClient joins |
| the client's listener thread, and the sequence's |
| callbacks need the GUI thread free |
\*-----------------------------------------------------*/
std::thread* ClientTeardownThread;
std::mutex ClientTeardownMutex;
std::condition_variable ClientTeardownCv;
bool ClientTeardownPending;
bool ClientTeardownRunning;
std::vector<NetworkClient*> clients_to_remove;
/*-----------------------------------------------------*\
| Device List Mutex |
+1
View File
@@ -41,6 +41,7 @@
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define SD_RECEIVE SHUT_RD
#define SD_BOTH SHUT_RDWR
#endif
//Network Port Class
+2
View File
@@ -359,6 +359,7 @@
#define ASUS_ROG_STRIX_RTX_4090_O24G_GAMING_WHITE_2 0x88F1
#define ASUS_ROG_MATRIX_PLATINUM_RTX_4090_24G 0x8934
#define ASUS_TUF_RTX_5060_O8G_GAMING 0x8A1A
#define ASUS_TUF_RTX_5060TI_O16G_GAMING 0x8A08
#define ASUS_TUF_RTX_5070_O12G_GAMING 0x89F2
#define ASUS_TUF_RTX_5070TI_O16G_GAMING 0x89F4
#define ASUS_TUF_RTX_5070TI_O16G_GAMING_BTF_WHITE 0x8A37
@@ -439,6 +440,7 @@
#define COLORFUL_IGAME_RTX_3080TI_ADVANCED_OCV2 0x1401
#define COLORFUL_IGAME_RTX_3080_ULTRAW_OC_10G 0x1501
#define COLORFUL_IGAME_RTX_3080_ULTRAW_OC_10G_2 0x150A
#define COLORFUL_IGAME_RTX_4070_ULTRAW_OCV 0x1500
#define COLORFUL_IGAME_RTX_4070_VULCAN_OCV 0x1200
#define COLORFUL_IGAME_RTX_4070_SUPER_ULTRA_W_OC 0x1500
#define COLORFUL_IGAME_RTX_4070S_ULTRAW_OCV 0x1500
+44 -28
View File
@@ -1094,18 +1094,21 @@ void OpenRGBDevicePage::UpdateMode()
bool supports_dir_lr = 0;
bool supports_dir_ud = 0;
bool supports_dir_hv = 0;
bool supports_dir_diag = 0;
if(selected_zone_mode)
{
supports_dir_lr = ( device->GetZoneModeFlags(selected_zone, (unsigned int)selected_mode) & MODE_FLAG_HAS_DIRECTION_LR );
supports_dir_ud = ( device->GetZoneModeFlags(selected_zone, (unsigned int)selected_mode) & MODE_FLAG_HAS_DIRECTION_UD );
supports_dir_hv = ( device->GetZoneModeFlags(selected_zone, (unsigned int)selected_mode) & MODE_FLAG_HAS_DIRECTION_HV );
supports_dir_diag = ( device->GetZoneModeFlags(selected_zone, (unsigned int)selected_mode) & MODE_FLAG_HAS_DIRECTION_DIAG );
}
else
{
supports_dir_lr = ( device->GetModeFlags((unsigned int)selected_mode) & MODE_FLAG_HAS_DIRECTION_LR );
supports_dir_ud = ( device->GetModeFlags((unsigned int)selected_mode) & MODE_FLAG_HAS_DIRECTION_UD );
supports_dir_hv = ( device->GetModeFlags((unsigned int)selected_mode) & MODE_FLAG_HAS_DIRECTION_HV );
supports_dir_diag = ( device->GetModeFlags((unsigned int)selected_mode) & MODE_FLAG_HAS_DIRECTION_DIAG );
}
/*-----------------------------------------------------*\
@@ -1113,38 +1116,29 @@ void OpenRGBDevicePage::UpdateMode()
\*-----------------------------------------------------*/
if(ui->DirectionBox->isEnabled())
{
if(supports_dir_hv)
{
if(supports_dir_lr && supports_dir_ud)
{
current_direction = current_dir_idx;
}
else if(supports_dir_lr || supports_dir_ud)
{
current_direction = current_dir_idx + 2;
}
else
{
current_direction = current_dir_idx + 4;
}
}
int offset = 0;
if(supports_dir_ud)
if(supports_dir_lr && current_dir_idx < offset + 2)
{
if(supports_dir_lr)
{
current_direction = current_dir_idx;
}
else
{
current_direction = current_dir_idx + 2;
}
current_direction = current_dir_idx - offset;
}
if(supports_dir_lr) offset += 2;
if((supports_dir_lr)
&&(current_dir_idx < 2))
if(supports_dir_ud && current_dir_idx >= offset && current_dir_idx < offset + 2)
{
current_direction = current_dir_idx;
current_direction = MODE_DIRECTION_UP + (current_dir_idx - offset);
}
if(supports_dir_ud) offset += 2;
if(supports_dir_hv && current_dir_idx >= offset && current_dir_idx < offset + 2)
{
current_direction = MODE_DIRECTION_HORIZONTAL + (current_dir_idx - offset);
}
if(supports_dir_hv) offset += 2;
if(supports_dir_diag && current_dir_idx >= offset && current_dir_idx < offset + 4)
{
current_direction = MODE_DIRECTION_UP_LEFT + (current_dir_idx - offset);
}
if(selected_zone_mode)
@@ -1446,6 +1440,7 @@ void OpenRGBDevicePage::UpdateModeUi()
bool supports_dir_lr = flags & MODE_FLAG_HAS_DIRECTION_LR;
bool supports_dir_ud = flags & MODE_FLAG_HAS_DIRECTION_UD;
bool supports_dir_hv = flags & MODE_FLAG_HAS_DIRECTION_HV;
bool supports_dir_diag = flags & MODE_FLAG_HAS_DIRECTION_DIAG;
bool per_led = color_mode == MODE_COLORS_PER_LED;
bool mode_specific = color_mode == MODE_COLORS_MODE_SPECIFIC;
bool random = color_mode == MODE_COLORS_RANDOM;
@@ -1539,7 +1534,15 @@ void OpenRGBDevicePage::UpdateModeUi()
ui->DirectionBox->addItem(tr("Vertical"));
}
if(supports_dir_lr || supports_dir_ud || supports_dir_hv)
if(supports_dir_diag)
{
ui->DirectionBox->addItem(tr("Up-Left"));
ui->DirectionBox->addItem(tr("Up-Right"));
ui->DirectionBox->addItem(tr("Down-Left"));
ui->DirectionBox->addItem(tr("Down-Right"));
}
if(supports_dir_lr || supports_dir_ud || supports_dir_hv || supports_dir_diag)
{
if((supports_dir_lr)
&&((direction == MODE_DIRECTION_LEFT)
@@ -1580,6 +1583,19 @@ void OpenRGBDevicePage::UpdateModeUi()
}
}
if((supports_dir_diag)
&&((direction == MODE_DIRECTION_UP_LEFT)
||(direction == MODE_DIRECTION_UP_RIGHT)
||(direction == MODE_DIRECTION_DOWN_LEFT)
||(direction == MODE_DIRECTION_DOWN_RIGHT)))
{
int offset = 0;
if(supports_dir_lr) offset += 2;
if(supports_dir_ud) offset += 2;
if(supports_dir_hv) offset += 2;
ui->DirectionBox->setCurrentIndex(direction - MODE_DIRECTION_UP_LEFT + offset);
}
ui->DirectionBox->setEnabled(enable_controls);
}
else
+103 -3
View File
@@ -38,6 +38,7 @@
#include <QCheckBox>
#include <QScreen>
#include <QSpinBox>
#include <QThread>
#include <string>
#include <functional>
@@ -106,7 +107,14 @@ static void OpenRGBDialogResourceManagerCallback(void * this_ptr, unsigned int u
break;
case RESOURCEMANAGER_UPDATE_REASON_DEVICE_LIST_UPDATED:
QMetaObject::invokeMethod(this_obj, "onDeviceListUpdated", Qt::BlockingQueuedConnection);
if(QThread::currentThread() != qApp->thread())
{
QMetaObject::invokeMethod(this_obj, "onDeviceListUpdated", Qt::BlockingQueuedConnection);
}
else
{
QMetaObject::invokeMethod(this_obj, "onDeviceListUpdated", Qt::DirectConnection);
}
break;
}
}
@@ -960,6 +968,84 @@ void OpenRGBDialog::ClearDevicesList()
}
}
void OpenRGBDialog::ShowHideDevicesList()
{
/*-----------------------------------------------------*\
| Loop through all controller tabs and move any hidden |
| controller's page to the hidden pages vector |
\*-----------------------------------------------------*/
for(int tab_idx = 0; tab_idx < ui->DevicesTabBar->count(); tab_idx++)
{
/*-------------------------------------------------*\
| Get a pointer to the page at this index in the |
| tab bar |
\*-------------------------------------------------*/
QWidget* page = ui->DevicesTabBar->widget(tab_idx);
/*-------------------------------------------------*\
| Verify this page is an OpenRGBDevicePage |
\*-------------------------------------------------*/
if(dynamic_cast<OpenRGBDevicePage*>(page) != nullptr)
{
/*---------------------------------------------*\
| If the controller for this page is hidden, |
| remove the page from the tab bar and store it |
| in the hidden pages vector |
\*---------------------------------------------*/
if(((OpenRGBDevicePage*)page)->GetController()->GetHidden())
{
hidden_pages.push_back((OpenRGBDevicePage*)page);
ui->DevicesTabBar->removeTab(tab_idx);
/*-----------------------------------------*\
| Decrement tab index to account for |
| removing tab |
\*-----------------------------------------*/
tab_idx--;
}
}
}
/*-----------------------------------------------------*\
| Loop through all pages in the hidden pages vector and |
| move any no-longer-hidden controller's page back to |
| the tab bar |
\*-----------------------------------------------------*/
for(std::size_t page_idx = 0; page_idx < hidden_pages.size(); page_idx++)
{
/*-------------------------------------------------*\
| Get a pointer to the page at this index in the |
| hidden pages vector |
\*-------------------------------------------------*/
OpenRGBDevicePage* page = hidden_pages[page_idx];
/*-------------------------------------------------*\
| If the controller for this page is not hidden, |
| remove the page from the hidden pages vector and |
| add it to the tab bar |
\*-------------------------------------------------*/
if(!(page->GetController()->GetHidden()))
{
ui->DevicesTabBar->addTab(page, "");
/*---------------------------------------------*\
| Create the tab label |
\*---------------------------------------------*/
TabLabel* NewTabLabel = new TabLabel(OpenRGBFont::GetIconIDFromDeviceType(page->GetController()->GetDeviceType()), (char *)page->GetController()->GetDisplayName().c_str(), (char *)context, false);
ui->DevicesTabBar->tabBar()->setTabButton(ui->DevicesTabBar->count() - 1, QTabBar::LeftSide, NewTabLabel);
hidden_pages.erase(hidden_pages.begin() + page_idx);
/*---------------------------------------------*\
| Decrement page index to account for removing |
| page |
\*---------------------------------------------*/
page_idx--;
}
}
}
void OpenRGBDialog::UpdateDevicesList()
{
std::vector<RGBController *> controllers = ResourceManager::get()->GetRGBControllers();
@@ -1038,8 +1124,8 @@ void OpenRGBDialog::UpdateDevicesList()
\*-------------------------------------*/
else
{
ui->DevicesTabBar->tabBar()->moveTab(tab_idx, controller_tab_count);
controller_tab_count++;
ui->DevicesTabBar->tabBar()->moveTab(tab_idx, controller_idx);
}
break;
}
@@ -1140,7 +1226,7 @@ void OpenRGBDialog::UpdateDevicesList()
connect(NewPage,
SIGNAL(RefreshList()),
this,
SLOT(onDeviceListUpdated()));
SLOT(onDeviceListShowHide()));
if(controllers[controller_idx]->GetHidden())
{
@@ -1278,6 +1364,7 @@ void OpenRGBDialog::UpdateDevicesList()
}
bool found = true;
base_tab = controllers.size();
while(found)
{
@@ -1470,6 +1557,11 @@ void OpenRGBDialog::on_QuickWhite()
on_SetAllDevices(0xFF, 0xFF, 0xFF);
}
void OpenRGBDialog::onDeviceListShowHide()
{
ShowHideDevicesList();
}
void OpenRGBDialog::onDeviceListUpdated()
{
UpdateDevicesList();
@@ -1512,6 +1604,14 @@ void OpenRGBDialog::onDetectionStarted()
void OpenRGBDialog::onDetectionEnded()
{
/*-----------------------------------------------------*\
| Always hide the detection view on |
| DETECTION_COMPLETE. The progress handler only hides |
| it at 100%, and a client teardown raises the |
| detection signals without setting the percent |
\*-----------------------------------------------------*/
SetDetectionViewState(false);
/*-----------------------------------------------------*\
| Detect unconfigured zones and prompt for resizing |
\*-----------------------------------------------------*/
+2
View File
@@ -148,6 +148,7 @@ private:
void ClearDevicesList();
void UpdateDevicesList();
void ShowHideDevicesList();
void closeEvent(QCloseEvent *event) override;
bool SelectConfigProfile(const std::string name);
@@ -176,6 +177,7 @@ private slots:
void on_QuickBlue();
void on_QuickMagenta();
void on_QuickWhite();
void onDeviceListShowHide();
void onDeviceListUpdated();
void onDetectionProgressUpdated();
void onDetectionStarted();
+10
View File
@@ -8,5 +8,15 @@ Restart=always
RuntimeDirectory=openrgb
WorkingDirectory=/run/openrgb
ProtectSystem=strict
ProtectHome=true
PrivateDevices=false
ProtectKernelTunables=false
ProtectControlGroups=false
ReadWritePaths=/etc/openrgb
ReadWritePaths=/run/openrgb
[Install]
WantedBy=multi-user.target