Compare commits

...
390 Commits
Author SHA1 Message Date
SalastilandClaude Opus 5 f888b1ddbc Fix Corsair K70 RGB PRO V2 direct lighting
The K70 RGB PRO V2 (1B1C:1BB3) was detected but never lit: it blanked
into software render mode and then fell back to its onboard effect.
Getting it working turned up five faults, three of which affect every
Corsair V2 device rather than just this keyboard.

Packet size is now read from the HID report descriptor before the first
write instead of being inferred from a reply. These devices come in a 64
byte and a 1024 byte flavour, and sending a short packet to a 1024 byte
endpoint stalls it until the device is power cycled, so the size has to
be known up front rather than after two short queries have gone out.

CORSAIR_V2_PACKET_SIZE was 1024 while pkt_sze reached 1025, so every
read using pkt_sze overran its stack buffer by a byte. The command
helpers now use pkt_sze against buffers of the corrected size.

The lighting resource probe ran before the device was placed in software
render mode, where the answer is meaningless, and treated any error as
"use resource 1". Devices that answer invalid or unsupported for
resource 1 want the alternate lighting resource, which takes RGB
triplets; this keyboard is one of them. The probe now runs after the
render mode switch and distinguishes a stale open handle from an
unsupported resource.

Direct lighting writes were sized from the keymap, which covers only the
keys that exist. The hardware expects its full slot count and ignores a
short write, so corsair_v2_device carries an optional hw_led_count and
the buffer map is padded to it. Set to 193 for this keyboard.

The keepalive thread wrote LEDs on its first pass, because
last_update_time was left at the clock epoch and so always read as older
than the update period. That raced detection on the same HID handle with
no locking, which both corrupted the transaction and could abort the
process. The clock is now started before the thread runs, device
transactions are serialised behind a mutex, and the thread is stopped
before Shutdown rather than after.

Also fixes an uninitialised stack buffer sent to the device during
setup, and widens the K70 RGB PRO matrix to 22 columns so the numpad
period key has a position in the layout instead of being dropped.

Verified on hardware: repeated colour changes apply and the device stays
responsive across runs.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-21 21:08:51 -04:00
Alzeroff-The-Allfather f03b3472ea Add Corsair K60 RGB PRO SE support 2026-09-21 12:01:20 -05:00
Kamil Rojewski 3e53302613 Apply active profile plugin data when the GUI loads its plugins 2026-09-21 07:20:47 -05:00
Kamil Rojewski 30043e3835 Do not clear the active profile when a profile cannot be read 2026-09-21 07:20:14 +02:00
André Schwarz ce3ee27d8e Renaming PAGE_SIZE in LogitechHIDPP20Controller.cpp 2026-09-20 23:20:08 -05:00
Dmitry Kychanov 0873907793 Fix potential race condition in log message 2026-09-21 03:26:28 +04:00
rom4ster 5303e29628 Remove Debug and Extra Line 2026-09-20 15:33:55 -05:00
rom4ster ffcf003c37 Add Support for Razer Naga V3 Pro 2026-09-20 15:33:54 -05:00
ubelhj f7e0e3d77b Add MSI PRO B850-S WIFI6E to supported devices 2026-09-20 12:45:15 -05:00
Adam Honse cb0052c761 Add a mutex to protect against concurrent saving of configuration file that could result in corruption of the file 2026-09-19 15:18:47 -05:00
Jan Dvořák e075870853 Improve Intel GPU I2C bus detection 2026-09-19 14:36:31 -05:00
Victor Ferreira 4cf5fe9346 Correct the swapped brightness and speed comments
Measured on an ASRock Intel Arc B580 Steel Legend, which uses this same
packet: holding everything else fixed and stepping data[5] through 0xFF,
0x40 and 0x00 dims the zone progressively and turns it off at zero, while
data[4] does not change brightness.  So data[4] is the effect speed and
data[5] is the brightness, the other way around from what the comments
said.  The bytes written are unchanged, this only fixes the labels.
2026-09-19 14:35:25 -05:00
Victor Ferreira eb5d88537d Call Shutdown() in the ASRock GPU controller destructor
Every other ASRock controller shuts the device thread down before deleting
the controller.  This one did not, so tearing it down logs

  Device thread still active in base class destructor, ensure Shutdown()
  was called from inherited destructor

on every exit.  Seen on an ASRock Intel Arc B580 Steel Legend, but the file
is shared with the Radeon Steel Legend and Taichi cards.
2026-09-19 14:35:25 -05:00
Adam Honse d5e0c94fdc Fix LED view hiding if a device supports per-zone modes and a zone is selected that is following device mode while the device mode is per-LED 2026-09-19 14:23:37 -05:00
Petre cd44e41d46 [New Device] Gigabyte GeForce RTX 4070 SUPER Eagle OC (non-ICE) 2026-09-18 14:02:43 -05:00
epicchillman 0129e58f53 Add support for MSI PRO B850M-A WIFI motherboard 2026-09-18 01:04:25 -07:00
Adam Honse 0adfde971b Update PalitGPUControllerDetect to filter for port ID 1 (the NVIDIA OEM bus) and re-add the PALIT string check in addition to the Gainward i2c_smbus_write_quick check for V1 controllers 2026-09-18 00:47:41 -05:00
Adam Honse 27a93edec5 Add second ID for PNY RTX 5070TI ARGB Epic-X OC 2026-09-18 00:38:50 -05:00
Adam Honse 65e04a5332 Fix crash in MSI 185 controller when board config is null, fixes #5869 2026-09-18 00:24:19 -05:00
Eder Sánchez ec5ff26eda Migrate pre-1.0 AutoStart and UserInterface settings 2026-09-18 00:08:10 -05:00
Prasad Patole 51398fd6c5 Added Support for ASRock RX 9070 XT Steel Legend Dark (0x5414) 2026-09-17 23:45:02 -05:00
Adam Honse 9a6cfaf021 Add visible field to schema for hiding entries from the UI. Use it to hide the no show dialog hashes 2026-09-17 20:39:33 -05:00
Adam Honse 0a201edbc1 Fix a null pointer dereference in SendReply_PluginList 2026-09-17 16:48:41 -05:00
Adam Honse 83ea112603 Update drivers settings defaults 2026-09-16 12:04:43 -05:00
DJMalachite 2c37d4eebb [Quadcast 2 S] Edit RGBController_HyperXMicrophoneV2.cpp to fix reversed matrix map 2026-09-16 07:43:17 -05:00
Matthew Uniack 08a9f83ff4 Add PID for Corsair Sabre RGB Optical mouse (0x1B32) 2026-09-15 12:07:28 -05:00
Nicolas Wendling 1cee73e125 Add alternate subsystem device ID for Sapphire Radeon RX 9070 XT Nitro+ 2026-09-15 12:02:24 -05:00
Yoann Laissus 6f027de52b Fix layout on Gigabyte RTX 5090 XTREME WATERFORCE non WB version 2026-09-15 11:59:50 -05:00
Daniel Clark 94e7b03a50 update the led10 and led 11 zone count routine to handle new cases. 2026-09-15 11:41:11 -05:00
Adam Honse ec13cda548 Correct rc names and mark as development in AppStream manifest 2026-09-15 11:33:31 -05:00
Adam Honse de580d736c Clean up unnecessary and broken Debian dependencies 2026-09-15 11:31:50 -05:00
Adam Honse d42ed48ae1 Always reply to plugin list even if plugin manager is null 2026-09-15 11:29:32 -05:00
Adam Honse 384e1faa56 Change suffix back to git after 1.0 release 2026-09-14 17:28:04 -05:00
Adam Honse 81bbe18a84 OpenRGB version 1.0 2026-09-11 18:06:58 -05:00
Adam Honse 8eb30ba840 Fix warnings in RGBController_PalitGPUv2 2026-09-11 18:02:20 -05:00
Adam Honse f01ab516df Filter out i2c entries that are non-numeric, my PC has a /sys/bus/i2c/devices/i2c-ITE8800:00 that was failing 2026-09-11 17:57:42 -05:00
Adam Honse 0440478acb Protect against out of bounds memory type, replaces !3598 2026-09-11 17:21:29 -05:00
Denilson Martins 6bb57b8822 MSI 7E76 (MSI B840M GAMING WIFI6E) 2026-09-11 15:02:29 -05:00
Adam Honse 954027f087 Update WIX configuration to ensure default INSTALLLEVEL is 2 (service installation) and fix cut off text 2026-09-11 11:50:57 -05:00
OfotViking 243889d818 Add support for Gainward GeForce RTX 4090 Phantom 2026-09-11 10:07:00 -05:00
Adam Honse f69213db02 Palit GPU Cleanup - Merge Palit, Gainward, and PNY - V1 and V2 2026-09-11 10:07:00 -05:00
Jonathan Arellano f4173ed72f Gigabyte RGB Fusion 2 USB: apply once per zone update, not per LED 2026-09-11 09:05:09 -05:00
Jonathan Arellano 69226884cb Device page: coalesce rapid colour-wheel updates instead of queueing 2026-09-11 09:04:17 -05:00
Adam Honse 728846f668 Update screenshot 2026-09-10 20:34:04 -05:00
Adam Honse f6cfac8eef Update Debian dependencies 2026-09-10 20:23:02 -05:00
Adam Honse 9a4857ac33 Update Debian and Fedora package dependencies 2026-09-10 19:09:47 -05:00
Adam Honse 45a6c93bb3 Use 1 second TCP connection timeout for local client autoconnect 2026-09-10 13:11:01 -05:00
RedBlackAka 77285e308a Update German and Norwegian translations for 1.0 final 2026-09-10 07:25:56 -05:00
Adam Honse 84fb7d18d0 Fix warning in RGBController_Alienware.cpp 2026-09-09 21:14:08 -05:00
brenmous e161314090 Add support for MSI B850MPOWER 2026-09-09 23:26:10 +10:00
Adam Honse 0ff7f0fcd6 Fix missing handling of new zone types and fall back to legacy types for older protocols 2026-09-09 01:04:52 -05:00
Adam Honse e6ea038ac8 Update prepare-artifacts.py 2026-09-09 00:25:48 -05:00
Adam Honse 6be0193788 Add an AutoStart implementation that uses the FreeDesktop Background portal for Flatpak builds 2026-09-09 00:02:22 -05:00
Adam Honse 8963586ead Fix removing settings tab plugin, fixes #5844 2026-09-08 21:51:13 -05:00
Adam Honse 9ac50bd646 Add firmware read timeout in Corsair Lighting Node controller to prevent lockup, fixes #5838 2026-09-08 21:22:38 -05:00
Adam Honse fde019d920 Skip DPMST i2c interfaces 2026-09-08 20:13:22 -05:00
Adam Honse 2b24d0a2e9 Add firmware version check to MSIMotherboard185Controller 2026-09-08 19:59:04 -05:00
Vangel Dior afb654aa55 Added support for Gainward GeForce RTX 5080 Phoenix 2026-09-08 16:44:07 -05:00
Aman Ali 82ae818712 Add PNY GeForce RTX 3060 XLR8 Revel EPIC-X LHR (196E:138F) 2026-09-08 04:01:52 -07:00
Adam Yearout 6ac47cc4f3 HID LampArray: fix keyboard usage mappings 2026-09-08 00:43:17 -05:00
vectorseven 1e1b26e062 macOS: initialize macUSPCIO in SMBus detectors
Ensure macOS SMBus detectors initialize the macUSPCIO connection before attempting detection instead of relying on the cached connection status.

InitMacUSPCIODriver() is idempotent, so this preserves the normal startup path while allowing the detectors to establish the required connection when needed.

Also update the failure messages to report initialization failure rather than incorrectly assuming macUSPCIO is not loaded.
2026-09-08 00:42:35 -05:00
Adam Honse aaa7cb0886 Clean up MSI flags handling and add comments 2026-09-08 00:41:38 -05:00
JAO1988 a42352c867 Added MSI MAG X870E GAMING MAX WIFI motherboard 2026-09-07 22:14:01 -05:00
Michael Updike 1da6a652fd Fix Clevo ITE8291 keyboard LED value/key positional mismatch 2026-09-06 15:45:52 -05:00
jpage77 4f63756e15 RazerController: Add support for Huntsman V3 HE Magnetic Mini 2026-09-06 13:25:31 -05:00
Vangel Dior f72e99225d Fixd hid_error dlsym typo in libusb HID wrapper 2026-09-06 13:23:21 -05:00
Venatrix “Joe” Ritz 93b374ad9f TurtleBeachKP7: name the LEDs and split into three zones 2026-09-06 02:00:50 -05:00
Noocktall Klown S. (NKlownS) 2319f5c20d AlienwareController: add support, zone mapping, direction and EEPROM save for Dell G15 5520/5530 (AW-ELC) 2026-09-06 01:59:31 -05:00
JAO1988 7523e11eb5 Added ASRock Radeon RX 9070 XT Taichi 16GB OC 2026-09-06 01:55:18 -05:00
Adam Honse 523bdb96f8 Fix a crash from Apply All Devices 2026-09-05 18:38:34 -05:00
J-Man 75302d7219 Add ASUS ROG STRIX XG27ACDNG RGB detection 2026-09-05 16:51:15 -05:00
Venatrix “Joe” Ritz 56256a8054 NetworkServer: fix reversed format arguments in invalid-size log calls 2026-09-05 13:48:55 -05:00
Adam Honse ed3d7894c8 Update Realtek ARGB controller to the new configuration system 2026-09-04 21:48:39 -05:00
Daniel Clark dc14dff646 Fix reset to default in device specific settings for gigabyte motherboard usb controllers. 2026-09-04 21:11:28 -05:00
Adam Honse 706e2ab30f Make device editor the same size as zone editor 2026-09-04 19:50:09 -05:00
Ken Sanislo 3e494c884a Keep saved device configuration when controllers are remote 2026-09-04 19:40:03 -05:00
Daniel Clark 31a09bc728 Move Gigabyte USB Controller settings to Device Specific Settings. 2026-09-04 19:29:47 -05:00
Adam Honse 87b0b904c5 Add ordering support to device-specific configuration user interface 2026-09-04 19:28:27 -05:00
Ken Sanislo 07e1f2bb2a Logitech HID++ 2.0: drop the per-frame wire logs 2026-09-04 15:21:36 -05:00
Daniel Clark fd1bc449ae Update Gigabyte Motherboard USB RGB Controller 2026-09-04 09:31:50 -05:00
Adam Honse 1103a567da Keep plugin data from existing profile when saving over an existing profile, fixes #5837 2026-09-04 00:15:32 -05:00
Delnegend b420af5c42 ASRock GPU: Add Radeon RX 9060 XT Steel Legend and fix Windows ADL detection 2026-09-03 23:44:02 -05:00
Adam Honse d092f3b159 Remove colors_min/max from per-LED mode in NVIDIA Illumination controller, fixes #5836 2026-09-03 20:30:53 -05:00
Adam Honse 87af7c8dc2 Fix issues in mode set validity check - correct duplicate HAS_PERE_LED_COLOR, fix with MODE_SPECIFIC_COLOR, only check colors_min/max if mode specific color, fixes #5836 2026-09-03 20:30:07 -05:00
John Sideserf 1335a9b06c Add Gigabyte AORUS RTX 5080 MASTER ICE (0x418C) at 0x71, reject echo-bus probe responses 2026-09-03 18:46:28 -05:00
Ken Sanislo bf2a0fb8f1 HID LampArray: stop on a failed report descriptor read
hid_get_report_descriptor returns -1 on failure and the length was
stored unsigned, so the descriptor walk ran past the end of the stack
buffer. Windows reconstructs the descriptor from preparsed data and can
fail where Linux returns the kernel's copy. A failed read now leaves the
device with no lamps, the same as a descriptor with missing reports.
2026-09-03 11:11:36 -05:00
Piotr Kochan 657cc0d05d MSI 761-byte: add PRO B850M-A WIFI PZ 2026-09-03 07:13:35 -05:00
Valtekken 47eba889c2 Minor fixes for the Italian translation 2026-09-03 08:02:10 +00:00
Adam Honse 7fed68ccf1 Try notifying all to prevent lockups 2026-09-02 19:29:50 -05:00
Adam Honse 3e290907d5 Implement fair_mutex in i2c_smbus so that i2c controllers are given access to the bus in a more fair manner, which should help with high frame rates on slow buses 2026-09-02 19:12:26 -05:00
Ken Sanislo 999cbd2578 Logitech HID++ 2.0: place the G915 X top strip and G-keys 2026-09-02 14:45:04 -05:00
danielminter cf048edb6c Narrow the 0b05:19af Aura motherboard detector to its real usage page 2026-09-02 09:28:07 -05:00
Ken Sanislo a44f525078 Logitech HID++ 2.0: key the detector to the Logitech VID and the bus 2026-09-02 09:25:21 -05:00
Dmitry Kychanov 88af06182a Fix russian translations 2026-09-01 22:59:44 -05:00
Adam Honse dd35cb8cbf Update Keychron QMK controller to pull keyboard's real firmware version when connected over wireless dongle, display both dongle and keyboard version 2026-09-01 22:58:22 -05:00
Adam Honse cfc6081f9d Update Keychron QMK controller to pull keyboard's real VID when connected over wireless dongle (at least for ZMK keyboards that support full control over the wireless link) 2026-09-01 22:56:23 -05:00
Dmitry Kychanov 8f701ee147 Fix hotplugging in Logitech HID++ by using a lambda callback to un-list used IDs 2026-09-01 22:30:23 -05:00
Gent Semaj eb535f43c8 Fix AW3423DWF E_PIPE on init for newer firmware 2026-09-01 21:58:40 -05:00
JAO1988 864ea220da Added Gigabyte GTX 1080 8G Rev 2 and RX9060XT GPUs 2026-09-01 20:12:56 -04:00
Adam Honse a152927ec6 Fix warning in OpenRGBDialog.cpp 2026-09-01 11:42:11 -05:00
Adam Honse bc9c04c00b Fix warning in OpenRGBZoneEditorDialog.cpp 2026-09-01 11:41:26 -05:00
Adam Honse 648f190e22 Fix warnings in OpenRGBProfileEditorDialog.cpp 2026-09-01 11:39:09 -05:00
Adam Honse 47044213fd Fix warnings in OpenRGBDevicePage.cpp 2026-09-01 11:38:13 -05:00
Adam Honse da3f396a25 Fix warnings in ValveSteamMachineController_Windows.cpp 2026-09-01 11:36:32 -05:00
Adam Honse 0dff37baa9 Fix warnings in RGBController_EVGAX12.cpp 2026-09-01 11:32:59 -05:00
Adam Honse 44c7b9fc07 Fix warnings in RGBController_HIDLampArray.cpp 2026-09-01 11:31:28 -05:00
Adam Honse bfba7e99d0 Fix warning in RGBController_GigabyteRGBFusion2USB.cpp 2026-09-01 11:29:37 -05:00
Adam Honse cd02ba5d39 Fix warning in ClevoKeyboardController.cpp 2026-09-01 11:28:43 -05:00
Adam Honse bf03f70950 Use Fusion theme on MacOS 2026-09-01 11:24:25 -05:00
Daniel Clark c789351977 Fixes Keychron Patch Layouts (hopefully) 2026-09-01 07:43:58 -05:00
Ken Sanislo bac9cffb27 Logitech HID++ 2.0: fix effect modes, brightness and phantom LEDs on per-key keyboards 2026-09-01 07:30:40 -05:00
Adam Honse 544fd47756 Add some sanity checking around the per-LED direct mode in MSIMotherboard185Controller. Ensure the enable per-LED 185-byte packet gets sent before the large per-LED packets, skip sending packets if there are no LEDs configured 2026-09-01 00:53:59 -05:00
Adam Honse 206f211ccc More MSI installer cleanups 2026-08-31 22:57:16 -05:00
Adam Honse de7a8df31d Add the ability to register detectors that are disabled by default 2026-08-31 22:53:07 -05:00
Adam Honse 054e979fcf Fix missing text 2026-08-31 22:34:18 -05:00
Adam Honse d3e06b9124 Update CM Small ARGB name 2026-08-31 22:30:15 -05:00
Adam Honse b0c517ab9f Update CMSmallARGBController to match the changes made to CMARGBController 2026-08-31 22:17:14 -05:00
Adam Honse 7325e83e8c Clean up formatting on warning installer page 2026-08-31 21:46:18 -05:00
Adam Honse c40967989a MSI controller renames 2026-08-31 21:13:13 -05:00
Adam Honse 382b3e4d72 More MSI installer fixes 2026-08-31 21:12:42 -05:00
Adam Honse a8d46b32c2 Add Warning to installer 2026-08-31 20:36:32 -05:00
Venatrix “Joe” Ritz 72b7e5cf5b Govee: refresh Direct/Razer mode in KeepaliveThread 2026-08-31 12:31:30 -05:00
Venatrix “Joe” Ritz 5903e7e7c5 Govee: add H6046 RGBIC Gaming Light Bars support 2026-08-31 11:47:20 -05:00
Mathias Grün-Drebes 1198d3ed3c Fix use-after-free in RGBController_HIDLampArray destructor 2026-08-31 11:14:25 -05:00
homelab black d623e76d25 Fix server-host help message to show correct default IP 2026-08-31 00:52:11 -05:00
Adam Honse a1ff9c8e35 Update naming of PawnIO SMBus interfaces and add index field so that multiple buses can be distinguished 2026-08-30 21:21:09 -05:00
Adam Honse c3102bc724 Ignore interface check on Keychron QMK controller's detector as the Ultra series wireless dongles use interface 2 2026-08-30 16:40:36 -05:00
Richard van Liessum a9dea96147 Add Asus ROG Aura Monitor Light Bar and PG32UCDP support 2026-08-30 14:59:15 -05:00
Adam Honse e5823a0e10 Update udev generation to generate udev rules for PID-only and VID-only USB HID detectors 2026-08-30 02:30:45 -05:00
Adam Honse e5d3b76263 Fix issues with SetCustomMode generic implementation, it was not updating mode after setting 2026-08-30 02:04:47 -05:00
Adam Honse a757c04cf9 Fix the Keychron Q2 knob layout 2026-08-30 01:46:40 -05:00
Adam Honse 01c65c1d17 Fix the Keychron Q6 Ultra 8K layout 2026-08-30 01:43:33 -05:00
Adam Honse cfdd06e1f4 Update NZXT device list read timeout as the initial implementation was much too short for Smart Device V2 to fully detect 2026-08-30 01:02:40 -05:00
Adam Honse e2d0bee51a Update PawnIO Modules to release 0.2.11. The existing LedsValve.bin was already from this release 2026-08-30 00:12:10 -05:00
QuickMythril 280df42267 Fix EVGA X12 physical zone mapping 2026-08-30 00:01:11 -05:00
Adam Honse 1538936758 Initial driver for PawnIO-based Windows controller for Valve Steam Machine 2026-08-29 23:36:39 -05:00
Adam Honse 255cfcf422 Initialize active profile tracking variables and don't try loading active profile if no profile is active 2026-08-29 17:42:33 -05:00
Adam Honse c029bb2b1d Clean up detector entries that no longer exist when running detection 2026-08-29 15:10:23 -05:00
Devin Christensen e958e0132e Fix udev rule generation for Glorious Model O/O-/D Wireless mice 2026-08-29 01:08:50 -05:00
Adam Honse 51daeed585 Clean up REGISTER_DUMMY_DETECTOR lines that are no longer relevant after udev rework 2026-08-29 01:07:33 -05:00
Luca Cesarano 60e4309eec Improve Govee LAN mode support 2026-08-29 00:56:04 -05:00
Adam Honse 31247bacd4 Cleaner fix for plugin null check 2026-08-29 00:39:17 -05:00
Adam Honse 2c23fd6ace Revert "Fix skip_generic_detectors scope and a headless-server plugin-list crash"
This reverts commit d9a726d97f.
2026-08-29 00:34:40 -05:00
Hanjjog-ui Gamyeong 61fcdd1a1c Add legacy probe for Gigabyte GAMING OC RTX 5080 2026-08-28 19:30:31 -05:00
Adam Honse dc0e9b2fd3 SDK6 PROTOCOL CHANGE: Update missing timestamp field to Log Message packet on server side, type is actually 32-bit so update documentation, fix issue where log console was enabled based on local settings rather than remote settings 2026-08-28 19:00:10 -05:00
Adam Honse c90eafbe72 Save default server settings to configuration file if they don't exist already 2026-08-28 12:03:56 -05:00
Daniel Clark b087a65f3c Update Keychron Controller 2026-08-28 00:01:06 -05:00
Venatrix “Joe” Ritz c31c6088ac NZXTHue2Controller: Add friendly zone/LED naming for Kraken coolers and add read retry safeguard 2026-08-27 23:59:56 -05:00
Zoin Zoin b14342d9ee Add proper HiDPI rendering for color controls 2026-08-27 23:58:02 -05:00
Gökhan Jantz 849e725b11 Initial commit for Corsair Ironclaw Wireless SE 2026-08-27 18:19:24 -05:00
Rickey Bartlett b23da372ad AlienwareController: fix stack buffer overflow with >25 zones 2026-08-27 18:18:36 -05:00
Venatrix “Joe” Ritz a96b5e0f1a Add support for Turtle Beach Command Series KP7 keypad 2026-08-27 18:17:26 -05:00
Zoin Zoin 6315c79afa Improve Skydimo SK0L32 serial device support 2026-08-27 18:15:00 -05:00
Adam Honse ee8d24bac6 Rename migrated legacy profile files with .bak extension to prevent them from being re-migrated on every startup 2026-08-27 11:31:51 -05:00
Mathias Grün-Drebes d9a726d97f Fix skip_generic_detectors scope and a headless-server plugin-list crash 2026-08-27 07:41:58 -05:00
Adam Honse 1a55da8ac4 Clean up dead code in OpenRGBDevicePage 2026-08-27 00:06:47 -05:00
Adam Honse 5c8baf47f8 Add missing condition in per-zone mode sanity check to handle active mode -1 (follow device mode) 2026-08-26 20:16:33 -05:00
Adam Honse 249c22082d Fix multi-line comment warning in RGBController_AcerNitroHidLed.cpp 2026-08-26 19:25:28 -05:00
Adam Honse 02237aeb4d Fix warning in main_FreeBSD_Linux_MacOS.cpp 2026-08-26 19:18:09 -05:00
Adam Honse 5d7090b5fd Fix warnings in DetectionManager.cpp 2026-08-26 19:17:26 -05:00
Adam Honse f70c3bf8d4 Fix warning in SteelSeriesRival5Controller.cpp 2026-08-26 19:16:18 -05:00
Adam Honse dc0d2e2d2f Fix warnings in HIDLampArrayController.cpp 2026-08-26 19:15:11 -05:00
Adam Honse fa91a11964 Fix multi-line comment warning in RGBController_AcerNitroHidKeyboard.cpp 2026-08-26 19:12:55 -05:00
Adam Honse ecf0bfc847 Update MacOS and Linux AutoStart implementations to properly escape special characters in paths 2026-08-26 18:48:46 -05:00
Adam Honse 41b7087e20 Update Windows AutoStart implementation to allow non-ASCII paths 2026-08-26 18:48:46 -05:00
Alexander Skakov 046a1bae65 Add MSI RTX 4090 Suprim X Classic support 2026-08-26 18:46:36 -05:00
Adam Honse a9a1856b9c Prevent asynchronous mode update issues when setting mode 2026-08-26 16:42:27 -05:00
Adam Honse d7f452e542 Fix UI issue related to REQUIRES_ENTIRE_DEVICE flag when building per-zone modes list 2026-08-26 12:04:10 -05:00
MX1D 799113942c Add full support for Lenovo Legion K500 2026-08-26 00:00:06 -05:00
Aidan Stewart 206ef9aca5 Add support for MSI Raider 18 HX AI A2XWIG laptop 2026-08-25 23:55:29 -05:00
Jason Greathouse 603714f749 Enable Corsair header on MSI 7D70 (MPG X670E Carbon WIFI) 2026-08-25 22:21:09 -05:00
Adam Honse 49e1687141 Adjust button position in MSI 2026-08-25 18:55:30 -05:00
Adam Honse 6d07573fff Update UdevRules.md 2026-08-25 18:24:18 -05:00
JAO1988 2a0222bde1 Added AORUS 2080TI 11G 2026-08-25 18:10:22 -05:00
Jefte Puente 725d0ec1f5 Corsair M75 Wireless: add device support 2026-08-25 18:09:58 -05:00
Adam Honse f14b6cb49d Adjust spacing on installer 2026-08-25 18:08:47 -05:00
Adam Honse 2b60e9ac37 Make system service the default install on Windows 2026-08-25 17:03:53 -05:00
Jefte Puente 693332ff50 Fix SaveProfile carrying forward the wrong controller 2026-08-25 12:07:56 -05:00
Adam Honse a678f3bb25 Ignore files that are not shared libraries when scanning for plugins 2026-08-25 11:11:45 -05:00
Guilherme Santiago b80ac45d99 Kingston Fury DDR5: accept modules with unreadable signature (#4981) 2026-08-25 08:59:59 -05:00
Devar 20a8bc611b Add ASUS ROG ASTRAL RTX 5090 BTF GPU variant (SPID 0x8A5A) 2026-08-24 23:49:08 -05:00
Soldier Alexander 77a474b993 Fix MSI 761 direct mode bounds check comparing a byte offset against an LED count 2026-08-24 20:39:40 -05:00
Adam Honse 853074d80c Update metadata with 1.0rc3 Hotfix 1 2026-08-24 20:32:34 -05:00
Adam Honse 5df989c7a0 Update udev dialog message and update translations 2026-08-24 20:27:45 -05:00
Adam Honse f3b9f1c3fd Add --print-udev-rules for printing rules to stdout 2026-08-24 18:51:10 -05:00
Adam Honse 061bae9546 Remove udev rules and readme from AppImage CI 2026-08-24 18:09:28 -05:00
Adam Honse 679635afd9 Remove udev references in AppImage and artifacts scripts 2026-08-24 11:15:34 -05:00
Adam Honse 7854c491f7 Remove udev rules from the build process now that the rules are generated dynamically from the executable 2026-08-24 11:01:35 -05:00
Adam Honse 9695f9d00b Update upstream CI rules to run pipelines for tagged commits 2026-08-23 15:42:34 -05:00
Merih Bulut b26df0f67e Add support for Corsair K70 RGB PRO Optomechanical (1B1C:1BC6) 2026-08-23 12:57:48 -05:00
Adam Honse 88fa92fd8d Update Fedora packaging to use openrgb binary's generated udev rules in place of the build udev rules from build-udev-rules.sh 2026-08-23 01:24:28 -05:00
Adam Honse 430501408b Update Debian packaging to use openrgb binary's generated udev rules in place of the built udev rules from build-udev-rules.sh 2026-08-23 01:19:02 -05:00
Adam Honse 19d3f8884e Don't detect devices before and exit immediately after generating udev rules 2026-08-23 00:40:39 -05:00
Thomas Xu 3b4a66096a Added Gigabyte GeForce RTX 4060 Ti AERO OC 16G GPU 2026-08-22 12:17:49 -05:00
Raman b93fe2a058 Add Colorful iGame RTX 3060 Ultra W OC 12G 2503:1501 detector 2026-08-21 14:01:35 -05:00
Diego Ortiz 7332c6a7ea Add Gigabyte Z390 DESIGNARE-CF Ambient LED support 2026-08-21 12:50:00 -05:00
Adam Honse d91dae8f15 Update to Qt6 dependency as removing it broke build 2026-08-20 23:00:09 -05:00
Junguo Song 02979dc209 Initial commit for ZOTAC GAMING GeForce RTX 5090 D SOLID OC 2026-08-20 22:37:03 -05:00
Adam Honse c1cbbfc207 Remove outdated Qt dependency in Fedora build 2026-08-20 22:33:52 -05:00
Wiktor Max 2631b5c0b2 Add Gigabyte RX 9070 GAMING OC detection (at 0x75) 2026-08-20 22:31:59 -05:00
Adam Honse 5795280b49 Use Trixie for AppImage and Supported Devices CI targets, update Fedora target to 44 and update naming convention to match Debian targets 2026-08-20 11:43:39 -05:00
Adam Honse 2b834c9ee4 Put OpenRGB version and git commit in udev rules header 2026-08-20 11:32:43 -05:00
Adam Honse 444a748411 More updates to match the behavior of the build-udev-rules.sh script 2026-08-20 10:38:57 -05:00
Adam Honse 58238c048b Add custom udev rules everywhere a dummy detector was used 2026-08-20 10:38:57 -05:00
Adam Honse c5befc354b Add framework for registering custom udev rules 2026-08-20 10:38:57 -05:00
Adam Honse 33854f8886 Build udev from DetectionManager 2026-08-20 10:37:46 -05:00
Adam Honse 8121ee29f4 Clean up the CLI code 2026-08-19 23:53:28 -05:00
Richard Harris 15b784f6c6 Fix startup crash and other issues in VialRGB controller 2026-08-19 22:42:22 -05:00
Ken Sanislo 2df162408e Logitech HID++ 2.0: detect and drive devices over Bluetooth 2026-08-19 22:38:14 -05:00
Ken Sanislo 10d06298ce Logitech HID++ 2.0: read the whole Centurion feature set 2026-08-19 22:35:19 -05:00
QuickMythril eebe4dd0dd Initial commit for EVGA X12 Gaming Mouse 2026-08-19 13:43:54 -05:00
Adam Honse 0459df64c1 Add missing display name fields in SDK protocol documentation 2026-08-19 12:21:28 -05:00
Adam Honse b635f0c2fd Block signals when updating the mode colors spin box in UpdateLEDList 2026-08-18 22:36:00 -05:00
Ryan Black 770e40596e Corsair Strafe MK.2: add PID 0x1B48 to K70 MK2 group (fixes 4-year unresponsive-keyboard bug) 2026-08-18 22:23:24 -05:00
Adrian Tworzydlo 2e990ba3ce Add PowerColor Red Devil RX 7900 XTX support (V2 driver) 2026-08-18 22:22:27 -05:00
Ken Sanislo ea5d7c6c4c Logitech HID++ 2.0: read the power source on the device's broadcast 2026-08-18 22:09:52 -05:00
Ken Sanislo 779514a6fc Fix Logitech HID++ 2.0: key both collections of one Windows interface together 2026-08-18 22:08:17 -05:00
smbd f027b7a312 Add Palit RTX 3090 Ti GameRock support 2026-08-18 19:10:51 -05:00
Ken Sanislo da31501087 Fix LogitechHIDPP20Controller probe negative cache 2026-08-18 19:10:18 -05:00
Ken Sanislo 140aa96aff Fix/macos hid open nonexclusive 2026-08-18 19:01:53 -05:00
Adam Honse 671f0a66f5 Exit after listing profiles 2026-08-18 18:46:00 -05:00
Adam Honse 5b1e782b9b Update CLI description to match about in UI 2026-08-18 18:35:30 -05:00
Adam Honse b222dfeceb Clean up help text some more 2026-08-18 18:30:22 -05:00
Adam Honse 5db5678c1d Add -lp/--list-profiles command line options, clean up help text, and clear active profile on CLI device changes 2026-08-18 18:22:43 -05:00
Adam Honse 433cbc1837 Rework -l/--list-devices to just print a simple list, add -ld/--list-detailed for the full detailed list 2026-08-18 17:37:47 -05:00
Adam Honse bf99c32e72 Remove warning that triggered during normal operation 2026-08-18 17:25:52 -05:00
Adam Honse c2ec44c0b7 Ignore empty matrix maps when getting zone/segment descriptions 2026-08-18 11:01:56 -05:00
Adam Honse 550fa1046e Fix logic around denying setting or modifying local settings from the server 2026-08-17 19:37:55 -05:00
Adam Honse cf910d2616 Implement profile loading on service shutdown 2026-08-17 19:36:05 -05:00
Adam Honse 2d32510008 Create Startup directory if it doesn't exist on Windows 2026-08-17 16:47:33 -05:00
RomanG1337 4d8fd55206 Add support for GameSir Nova 2 Lite controller 2026-08-17 11:38:57 -05:00
Ken Sanislo c852af2a2c Logitech HID++ 2.0: VID-generic detection, protocol fixes and device support 2026-08-16 23:27:46 -05:00
Daniel Clark ae22e1e8ea Add Asus PG32UCDMR 2026-08-16 23:20:45 -05:00
Adam Honse 02620313f6 Adjust DetectionManager to allow specific VID, any PID detectors but still separate generic detectors into their own category 2026-08-16 19:34:42 -05:00
Adam Honse bbccdc2de0 Fix typo in Windows startup file 2026-08-16 18:43:21 -05:00
Adam Honse bc54e18e48 Clean up Creative Sound Blaster controllers 2026-08-16 18:41:23 -05:00
Adam Honse 8e4859688d Make zones in DDP, E1.31, and Serial LED strip manually configurable except for size, as this is defined in the manual configuration 2026-08-16 18:36:41 -05:00
Adam Honse 967bb51313 More settings schema filter fixes, allow registering a schema key that ignores filtering as Detectors needs this 2026-08-16 18:05:08 -05:00
Adam Honse 51d46849fb Pass from_server flags down from JsonString functions into Set/ModifySettings 2026-08-16 17:36:34 -05:00
Adam Honse 74bb119a05 Add an option to automatically load a profile on background service startup 2026-08-16 17:33:55 -05:00
Daniel Clark ae038bc8e9 Update DetectionManger to handle generic wildcards in safe mode. 2026-08-16 16:33:11 -05:00
Adam Honse 433788cc49 Add PNY RTX 5070Ti Epic-X ARGB non-OC 2026-08-16 14:42:37 -05:00
Buu342 5e6d627f51 Fix for LEDs without an assigned key not being properly represented in QMK Firmware 2026-08-15 19:51:28 -05:00
Adam Honse f9897ef99a Implement settings schema filtering
* Filter settings that have a registered schema so that only keys matching the schema get saved
  * Allow settings that do not have a registered schema (manually added devices, device-specific, plugins, client)
  * Update schema registration to allow marking a settings group as local only, which means it modifies the local instance's settings, not the remote
  * Deny setting local only settings when the update comes from the network server
2026-08-15 17:41:29 -05:00
Adam Honse 5c1a2c4d13 Attempt to build against mbedtls3 path by default and fall back to mbedtls path if it doesn't exist, fixes build on distros that have moved mbedtls 3.x to the /usr/include/mbedtls3 path 2026-08-14 11:33:40 -05:00
John Monteiro f987bc3199 Add support for Gigabyte B360 AORUS GAMING 3 WIFI-CF 2026-08-14 01:02:04 -05:00
Irturijus 0c1a73accc Add ASUS ROG Strix G18 G814JIR support 2026-08-12 18:23:40 -05:00
Adam Honse 1fb75b66dd Limit maximum SDK packet size to 8MB 2026-08-12 18:17:51 -05:00
Adam Honse 790e1485f6 Remove minimum width check when determining compact tab mode as it doesn't handle scaling above 100% properly and the manual configuration exists now 2026-08-11 20:00:22 -05:00
Adam Honse 400faabb21 Improve scroll wheel behavior in Plugins settings page 2026-08-11 19:58:57 -05:00
Rogue Turtle 684b74ca9e Added support for 75% Sinowealth10c-based keyboards with indices bigger than the keyboard size 2026-08-10 12:31:02 -05:00
Tony McDowell 6069a3c0cb RazerController: update HID detection for Razer Blackwidow V4 standard. 2026-08-10 12:16:03 -05:00
Adam Honse 606f5f6c86 Updates Flapak plugin support and building Flatpak in GitLab CI 2026-08-09 16:13:10 -05:00
Adam Honse bfccc1a95d Check /run/host paths when looking for udev rules, as this is the only way to mount these host-side paths on Flatpak 2026-08-08 15:45:39 -05:00
Chalenged e33a56001a Fix TUF Gaming K3 GEN II Miku Edition layout 2026-08-07 15:17:07 -05:00
Titan 0afc4c752f Skyloong GK68HE Pro Support 2026-08-07 11:27:18 -05:00
Adam Honse 834fc6a8f7 Use device type to set zone name in HID LampArray controller 2026-08-07 11:17:36 -05:00
Adam Honse da824ccfa8 Update hidapi-hotplug Windows dependencies to hidapi-hotplug-0.15.0 tag 2026-08-06 23:17:43 -05:00
Adam Honse 2c397c5110 Call DeviceUpdateLEDs from Zone/Single functions 2026-08-06 22:00:22 -05:00
Jannik Vogel 4a4f04825c Fix crash due to bad lamp count in DeviceUpdateLEDs 2026-08-06 21:59:36 -05:00
Adam Honse 2ac41813fa Add autonomous mode as a selectable mode 2026-08-06 21:57:25 -05:00
saad-script 422414737b rework report id parsing to be more consistent 2026-08-06 21:57:25 -05:00
Adam Honse d45d9a31be Units are in micrometers not millimeters 2026-08-06 21:57:25 -05:00
Adam Honse 5f297a62ba Get vendor and device names from HID descriptors 2026-08-06 21:57:25 -05:00
Adam Honse 7605c14d89 Switch to PU ONLY detector 2026-08-06 21:57:25 -05:00
Adam Honse bad23f7574 Use HID serial number 2026-08-06 21:57:25 -05:00
Adam Honse ee4886512b Determine device type from LampArray kind 2026-08-06 21:57:25 -05:00
Werner Sembach 8a87befeda Dynamically parse report IDs 2026-08-06 21:57:25 -05:00
Werner Sembach 44b37c57c1 Prepare LampArray report IDs being parsed dynamically 2026-08-06 21:57:25 -05:00
Werner Sembach 0b59ccfb42 Properly setup key names 2026-08-06 21:57:25 -05:00
Werner Sembach 3c8f05982e Automatically create matrix 2026-08-06 21:57:25 -05:00
Werner Sembach a7a6b241e0 Add meta information for udev rule to be generated automatically 2026-08-06 21:57:25 -05:00
Adam Honse d6de563178 Rework to use only a single hid_device and DetectionManager API 2026-08-06 21:57:25 -05:00
Adam Honse ee62b14384 Multi update packet working for proof of concept direct mode 2026-08-06 21:57:25 -05:00
Adam Honse 712df22b95 Implement getting lamp attributes for each lamp and disabling autonomous mode, work on multi update 2026-08-06 21:57:25 -05:00
Adam Honse a0d063e78f Begin implementing the HID LampArray protocol for Arduino test device 2026-08-06 21:57:25 -05:00
Adam Honse 5a2712750b Initial commit for HID LampArray Controller 2026-08-06 21:57:25 -05:00
Adam Honse 320f755e5e Update super_io_pawnio.cpp to add logging and update ioctls 2026-08-06 20:47:05 -05:00
Adam Honse f1d4a7c045 Add logging to MSI-RGB motherboard detection 2026-08-06 19:38:48 -05:00
Adam Honse 710f97bb62 Fix LpcIO.bin name in superio_pawnio so that Super IO access initializes in Windows 2026-08-06 18:51:14 -05:00
Adam Honse 454344185c MSI Motherboard Cleanup - Rename Mystic Light to Motherboard, move MSI Mystic Light 64 to keyboard as it is a keyboard, do some code cleanup 2026-08-06 17:20:17 -05:00
Kyle Finter b25250db7b New Keyboard layout for Corsair K70 LUX RGB 2026-08-06 08:35:47 -05:00
Yousif Ragab 009156994e Add support for Gigabyte AORUS RX 6900 XT MASTER rev 2.0 2026-08-06 08:31:13 -05:00
Aiden Corbin 6f21dd884c Add GALAX GeForce RTX 3070 1-Click OC 2026-08-06 07:18:49 -05:00
Adam Honse 5dfa33320e Split detector file for Asus Aura Core 2026-08-06 00:05:10 -05:00
Adam Honse 2147291c52 Split detector file for Alienware monitors and organize into folders 2026-08-06 00:01:52 -05:00
Rui Guilherme e971c619b1 Add support for Redragon M711-FPS-1 Cobra FPS 2026-08-05 20:32:02 -03:00
LittleQuartZ 6e832b337f Add PowerColor Red Devil RX 6700 XT support 2026-08-05 20:02:48 +07:00
Raulkong898 6ec5a955df Add Keychron J2 HE 8K support to QMKKeychronController 2026-08-05 01:59:28 -05:00
Adam Honse 71cd411ffc Fix active profile initialization during initial local client connection as well as local client reconnections 2026-08-05 01:20:06 -05:00
Adam Honse 1be9097852 Do not reinitialize client flags upon disconnect, this prevented local client from being re-requested upon local server restart 2026-08-05 00:25:38 -05:00
Adam Honse a8a848a259 More local client cleanup in the UI - don't show the save or rescan buttons for local client 2026-08-05 00:02:27 -05:00
Ken Sanislo 15d008a169 OpenRGBClientInfoPage: disable the disconnect button for the local client
Commit amended to adjust comment and tooltip by Adam Honse <[email protected]>
2026-08-04 23:40:41 -05:00
Adam Honse 2e09228568 SDK6 PROTOCOL CHANGE: Add client and server hostname set packets 2026-08-04 11:52:54 -05:00
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
Alessandro Di Ronza 069eeccb60 CLI: return a device list again, and print the version 2026-07-27 17:27:54 -05:00
Alessandro Di Ronza da77b63bea Fix SteelSeries Apex 9 lighting by using the correct direct report size 2026-07-27 17:23:28 -05:00
Adam Honse 791d8820d6 Get active effect and active settings during Valve Steam Machine initialization 2026-07-26 23:18:15 -05:00
Ken Sanislo 05fbe381dd MSIMonitorController: idle the control pipe between feature reports 2026-07-26 22:07:33 -05:00
isJuhn 3b2466ba10 Fix use after free in NetworkClient and NetworkServer 2026-07-26 22:03:43 -05:00
Ken Sanislo acc7fa6754 Invoke resource manager callbacks outside the callback mutex 2026-07-26 17:47:18 -05:00
Adam Honse 394a5c8eb0 Replace thread-local SignalUpdateDepth with SignalCalls per controller. Fixes an issue where callbacks that performed an unregister or clear operation on a separate thread would still deadlock, as is the case in Visual Map Plugin. Cleaned up comments 2026-07-26 01:29:34 -05:00
Adam Honse ac6d89b611 Bundle Qt6OpenGL and Qt6Core5Compat libraries and adjust LD_LIBRARY_PATH in AppImage to fix plugins using the system qt6 libraries 2026-07-25 21:37:03 -05:00
Ilya Miller 46cdd6fbe1 OpenRGBDevicePage: keep the color selection when applying a color 2026-07-25 13:28:38 -05:00
Adam Honse 14d543b9e4 Initial commit for Linux-only controller for Valve Steam Machine using
the leds-valve kernel module and sysfs interface
2026-07-25 01:16:11 -05:00
Ilya Miller 8a831cef04 Add Dark Project KD104A support to the Keyrox Classic driver 2026-07-24 13:53:06 -05:00
Ken Sanislo 9fd79b1ad2 RGBController_Virtual: signal update when description is replaced 2026-07-24 13:22:00 -05:00
Daniel Clark c9c37a4576 Add GC-USB dongle, per zone effects, Gen2 Zones, and Updated MB Layouts 2026-07-22 22:47:25 -05:00
Ken Sanislo fe53d90664 RGBController: fix callback list race and dangling device-page callback on rescan 2026-07-22 21:59:30 -05:00
Adam Honse 8e8a9c8e05 Split Alienware keyboard controller detect file into individual controllers 2026-07-22 19:08:23 -05:00
Adam Honse dafc6e9cbc Split Bloody/A4Tech detector file into individual files per controller 2026-07-22 11:37:15 -05:00
Bernd Loecke 68b113bf86 Add MSI MAG X870E TOMAHAWK MAX WIFI PZ support 2026-07-22 07:42:41 -05:00
Ken Sanislo eb5d8b579c MSIMysticLight761: restore resizable JAF/JARGB zones 2026-07-22 07:41:30 -05:00
Adam Honse 56df76dbfc Split HyperX keyboard detector file into individual detector files 2026-07-22 00:42:16 -05:00
Adam Honse 878f32fccf Split Holtek detector file into individual detector files for A1FA Mousemat and A070 Mouse 2026-07-21 23:50:13 -05:00
Adam Honse 63828313bd Split EVGAUSBControllerDetect into individual keyboard and mouse files 2026-07-21 23:34:06 -05:00
homelab black 31992acad1 Fix layout regression for KEY_EN_ESCAPE and KEY_JP_EJ 2026-07-21 22:44:15 +09:00
HNParda bd41ba3b5c Added Support for Gigabyte RX 6700 XT Eagle 12G 2026-07-21 00:15:58 -05:00
Adam Honse b9309c61e1 Split EVision keyboard files into folders and split the shared detector file 2026-07-21 00:14:12 -05:00
Ken Sanislo 7fc8aea930 MSIMonitorController: 0x72 layout support (MPG 322URX QD-OLED + siblings) 2026-07-20 23:22:02 -05:00
Ken Sanislo b35e119325 DetectionManager: controller lifecycle and thread-safe registration 2026-07-19 02:04:46 -05:00
eldiablo 8fb790358f Add support for SteelSeries Arctis Nova 3 headset 2026-07-18 18:54:38 -05:00
Ken Sanislo 39c6dbf361 DeviceView: add F13-F15 and Number Pad = device-view labels 2026-07-18 13:01:25 -05:00
John Hamlin 9d189d5b9b Add support for MSI B850 GAMING PLUS WIFI PZ (MS-7E75) motherboard 2026-07-17 19:07:39 -05:00
Adam Honse 9e5c63cc64 Add second sub-dev for MSI RTX4070Ti Super Gaming White X Slim 2026-07-17 18:54:54 -05:00
Adam Honse b664f35e17 Fix potential out of bounds reference in ASRockPolychromeUSB controller when zone size is 0 2026-07-17 17:17:39 -05:00
Filip Machałowski 381e3ac70c Add support for PowerColor Red Devil RX 9070 2026-07-17 13:19:35 +02:00
Stella e3225d93c4 Add support for Colorful iGAME RTX 5070Ti Ultra W OC 16GB 2026-07-17 00:14:57 -05:00
Andrey 1b3480bf8a Replace ADL2_ADAPTER_PRIMARY_GET function usage with adapter_index in i2c_smbus_amdadl 2026-07-16 08:17:16 -05:00
RedBlackAka a6bea17df2 Add Nvidia RTX 2080 Ti Cyperpunk 2077 Edition 2026-07-16 04:10:41 +02:00
Adam Honse 28d73d74bc Delete hidden pages if their controller no longer exists 2026-07-14 21:45:18 -05:00
Adam Honse cf383cacdd Use a blocking connection when updating the device list to prevent accessing deleted controllers 2026-07-14 21:45:18 -05:00
Adam Honse d9b0995b44 Run RescanDevices in a background thread to avoid locking the UI thread and causing a deadlock in the plugins 2026-07-14 21:45:18 -05:00
Ken Sanislo 79eb758a52 Logitech HID++ 2.0: restore COUNT fill for the keyboard matrix map 2026-07-14 21:45:18 -05:00
Daniel Clark 75463b8772 Fix K5 V2 layout and add ANSI Enter Expansion 2026-07-14 21:45:18 -05:00
Barry H 5da92fca4e Added all Skydimo Serial and HID devices 2026-07-14 21:42:42 -05:00
Darren You d11be2aa0b Initial commit for Colorful iGame GeForce RTX 4090 Neptune OC-V 2026-07-13 00:17:06 -05:00
Adam Honse 285a7bce4e Implement a matrix patching system in the Keychron QMK controller to apply keyboard-specific patches to correct for misplaced LEDs 2026-07-12 16:25:07 -05:00
Adam Honse 21009cdc45 Don't send RGBController packets that are too new for the current protocol in NetworkClient 2026-07-11 14:20:43 -05:00
Adam Honse a0cee2ce2b Maintain a list of created virtual controllers in the OpenRGBPluginAPI object and only allow registering virtual controllers that exist in this list 2026-07-11 14:20:38 -05:00
Joseph E ba33ff0c35 Steelseries Apex 9 merge into Apex controller + enable onboard mode 2026-07-11 00:11:45 -05:00
compguy284 795616e62d Fix Corsair keyboard cold-boot detection by retrying firmware info read 2026-07-10 17:55:23 -05:00
Adam Honse baa033ff7f Add Keychron Q0 HE 2026-07-10 15:40:35 -05:00
Eder Sanchez 33ec03d129 NetworkServer: reject SDK index == element count (off-by-one)
The SDK LED/zone update handlers checked the client-supplied index with '>'
instead of '>=', so an index equal to the element count passed the check and
reached an out-of-bounds access:

- UpdateSingleLED: out-of-bounds write to colors[led_idx]
- UpdateZoneLEDs / UpdateZoneMode: out-of-bounds read/write on zones[zone_idx]

UpdateMode had the same off-by-one (currently harmless due to a later check),
fixed for consistency. The num_colors check stays '>' since num_colors ==
leds_count is a valid "all LEDs" request.
2026-07-10 14:44:44 -05:00
Eder Sanchez a0d5a985f0 ZOTAC Blackwell GPU: fix firmware-version read
ReadVersion() issued a register-less block read, so the device returned a
status byte (0x01) instead of the version string. Read register 0xF0 (a
32-byte ASCII board string), as the vendor tool does, and validate it.

- fixes the empty/garbage firmware version in the log and GUI
- closes an out-of-bounds read (unbounded std::string over a 32-byte buffer)
- tested on RTX 5080 AMP Extreme INFINITY: now reads "N762A-2008e"
2026-07-10 13:42:01 -05:00
Adam Honse 1275fe816b Make tray icon menus translatable 2026-07-10 13:00:12 -05:00
homelab black d4e1ed5245 Controllers: RoccatVulcan: Add Japanese (JIS) layout support for Vulcan II Max 2026-07-10 00:14:02 -05:00
Marius Huecker b833a43490 Add Gainward RTX 4080 Phantom GS 2026-07-08 20:16:38 -05:00
Adam Honse 94295c52ba Dynamically set DeviceView minimum height based on height of contents to avoid devices with a lot of segments/zones getting squished 2026-07-08 20:05:21 -05:00
Adam Honse f32aa29ecd Put Device View in a QScrollArea so it can be scrolled if it is too large to fit in the window 2026-07-08 11:03:31 -05:00
499 changed files with 51872 additions and 32300 deletions
+55 -19
View File
@@ -14,9 +14,9 @@
# run only if manually started to save CI minutes if #
# GitLab hosted runners #
# * For commits to the CalcProgrammer1/OpenRGB repository, #
# automatically run all jobs on the default branch, #
# otherwise run them if the source is part of a merge #
# request #
# automatically run all jobs on the default branch or #
# tag builds, otherwise run them if the source is part #
# of a merge request #
#-----------------------------------------------------------#
.downstream_rules:
rules:
@@ -26,7 +26,7 @@
.upstream_rules:
rules:
- if: '$CI_PROJECT_PATH == "CalcProgrammer1/OpenRGB" && ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event")'
- if: '$CI_PROJECT_PATH == "CalcProgrammer1/OpenRGB" && ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG || $CI_PIPELINE_SOURCE == "merge_request_event")'
when: on_success
- !reference [.downstream_rules, rules]
@@ -51,7 +51,7 @@ before_script:
# Supported Devices Build Target #
#-----------------------------------------------------------#
"Supported Devices":
image: registry.gitlab.com/openrgbdevelopers/openrgb-linux-ci-deb-builder:bookworm-amd64
image: registry.gitlab.com/openrgbdevelopers/openrgb-linux-ci-deb-builder:trixie-amd64
tags:
- linux
- amd64
@@ -71,10 +71,10 @@ before_script:
- !reference [.upstream_rules, rules]
#-----------------------------------------------------------#
# OpenRGB Common Appimage Build Steps #
# OpenRGB Common AppImage Build Steps #
#-----------------------------------------------------------#
.hidden_appimage_script: &appimage_script_steps
image: registry.gitlab.com/openrgbdevelopers/openrgb-linux-ci-deb-builder:bookworm-${TGT_ARCH}
image: registry.gitlab.com/openrgbdevelopers/openrgb-linux-ci-deb-builder:trixie-${TGT_ARCH}
tags:
- linux
- ${TGT_ARCH}
@@ -87,8 +87,6 @@ before_script:
name: "${CI_PROJECT_NAME}_Linux_${TGT_NAME}_${CI_COMMIT_SHORT_SHA}"
paths:
- OpenRGB-${TGT_PATH}.AppImage
- 60-openrgb.rules
- README.md
expire_in: 30 days
rules:
@@ -290,13 +288,13 @@ before_script:
<<: *debian_script_steps
#-----------------------------------------------------------#
# Linux (.rpm, F43) 64-bit Build Target #
# Linux (.rpm) Fedora 44 amd64 Build Target #
#-----------------------------------------------------------#
"Linux 64 F43 rpm":
image: fedora:43
"Linux amd64 .rpm (Fedora 44)":
image: fedora:44
stage: build
script:
- dnf install rpmdevtools dnf-plugins-core libcurl-devel qt5-qtbase-devel git wget -y
- dnf install rpmdevtools dnf-plugins-core libcurl-devel qt6-qtbase-devel git wget -y
- 'wget -O hidapi-hotplug.zip https://gitlab.com/OpenRGBDevelopers/hidapi-hotplug/-/jobs/artifacts/master/download?job=Linux+64+F43+rpm'
- unzip hidapi-hotplug.zip
- dnf install ./*.rpm -y
@@ -312,7 +310,7 @@ before_script:
- cd ${CI_PROJECT_DIR}
artifacts:
name: "${CI_PROJECT_NAME}_Linux_64_rpm_${CI_COMMIT_SHORT_SHA}"
name: "${CI_PROJECT_NAME}_Linux_amd64_rpm_${CI_COMMIT_SHORT_SHA}"
paths:
- openrgb*.rpm
expire_in: 30 days
@@ -320,6 +318,44 @@ before_script:
rules:
- !reference [.upstream_rules, rules]
#-----------------------------------------------------------#
# OpenRGB Common Flatpak Build Steps #
#-----------------------------------------------------------#
.hidden_flatpak_script: &flatpak_script_steps
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10
stage: build
tags:
- linux
- ${TGT_ARCH}
script:
- flatpak-builder --user --disable-rofiles-fuse --repo=build/repo build/flatpak-build flatpak/org.openrgb.OpenRGB.yaml
- flatpak build-bundle build/repo org.openrgb.OpenRGB.flatpak org.openrgb.OpenRGB
artifacts:
name: "${CI_PROJECT_NAME}_Linux_${TGT_ARCH}_Flatpak_${CI_COMMIT_SHORT_SHA}"
paths:
- org.openrgb.OpenRGB.flatpak
expire_in: 30 days
rules:
- !reference [.upstream_rules, rules]
#-----------------------------------------------------------#
# Linux Flatpak amd64 Build Target #
#-----------------------------------------------------------#
"Linux amd64 Flatpak":
variables:
TGT_ARCH: "amd64"
<<: *flatpak_script_steps
#-----------------------------------------------------------#
# Linux Flatpak arm64 Build Target #
#-----------------------------------------------------------#
"Linux arm64 Flatpak":
variables:
TGT_ARCH: "arm64"
<<: *flatpak_script_steps
#-----------------------------------------------------------#
# Debian i386 Bookworm test #
#-----------------------------------------------------------#
@@ -387,10 +423,10 @@ before_script:
- !reference [.upstream_rules, rules]
#-----------------------------------------------------------#
# Fedora 64 v43 test #
# Fedora amd64 44 test #
#-----------------------------------------------------------#
"Fedora 64 v43":
image: fedora:43
"Fedora 64 44":
image: fedora:44
stage: test
script:
- dnf install unzip wget -y
@@ -401,9 +437,9 @@ before_script:
- openrgb --version
- dnf5 -y remove openrgb
dependencies:
- "Linux 64 F43 rpm"
- "Linux amd64 .rpm (Fedora 44)"
needs:
- "Linux 64 F43 rpm"
- "Linux amd64 .rpm (Fedora 44)"
rules:
- !reference [.upstream_rules, rules]
+158
View File
@@ -0,0 +1,158 @@
/*---------------------------------------------------------*\
| AutoStart-Flatpak.cpp |
| |
| Autostart implementation for Linux via Flatpak Portal |
| |
| Adam Honse <[email protected]> 04 Sep 2026 |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "AutoStart-Flatpak.h"
#include <gio/gio.h>
AutoStart::AutoStart(std::string name)
{
InitAutoStart(name);
}
void AutoStart::InitAutoStart(std::string name)
{
app_name = name;
}
bool AutoStart::CallBackgroundPortal(bool enable, const AutoStartInfo& autostart_info)
{
GError* error = nullptr;
GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error);
if(!connection)
{
if(error)
{
g_error_free(error);
}
return false;
}
/*-----------------------------------------------------*\
| FreeDesktop Background Portal parameters |
\*-----------------------------------------------------*/
const char* bus_name = "org.freedesktop.portal.Desktop";
const char* object_path = "/org/freedesktop/portal/desktop";
const char* interface_name = "org.freedesktop.portal.Background";
const char* method_name = "RequestBackground";
/*-----------------------------------------------------*\
| Build the options dictionary (vardict) required by |
| the Portal |
\*-----------------------------------------------------*/
GVariantBuilder options_builder;
g_variant_builder_init(&options_builder, G_VARIANT_TYPE_VARDICT);
/*-----------------------------------------------------*\
| Reason shown to the user in the system settings / |
| permission prompt |
\*-----------------------------------------------------*/
std::string reason_str = "Allow " + app_name + " to launch automatically at startup.";
g_variant_builder_add(&options_builder, "{sv}", "reason", g_variant_new_string(reason_str.c_str()));
/*-----------------------------------------------------*\
| Toggle the autostart feature state |
\*-----------------------------------------------------*/
g_variant_builder_add(&options_builder, "{sv}", "autostart", g_variant_new_boolean(enable));
if(enable)
{
/*-------------------------------------------------*\
| Pass command-line arguments to the background |
| request |
\*-------------------------------------------------*/
GVariantBuilder arg_builder;
g_variant_builder_init(&arg_builder, G_VARIANT_TYPE("as"));
/*-------------------------------------------------*\
| Add the primary executable path inside the |
| Flatpak sandbox |
\*-------------------------------------------------*/
g_variant_builder_add(&arg_builder, "s", GetExePath().c_str());
/*-------------------------------------------------*\
| Append any additional arguments provided by |
| OpenRGB's autostart config |
\*-------------------------------------------------*/
if(!autostart_info.args.empty())
{
g_variant_builder_add(&arg_builder, "s", autostart_info.args.c_str());
}
GVariant* commandline_args = g_variant_builder_end(&arg_builder);
g_variant_builder_add(&options_builder, "{sv}", "commandline", commandline_args);
}
/*-----------------------------------------------------*\
| An empty string window handle is used since this is a |
| background configuration request |
\*-----------------------------------------------------*/
const char* parent_window = "";
GVariant* options = g_variant_builder_end(&options_builder);
/*-----------------------------------------------------*\
| Bundle parameters: (s a{sv}) -> Parent window handle, |
| Options dictionary |
\*-----------------------------------------------------*/
GVariant* parameters = g_variant_new("(s@a{sv})", parent_window, options);
GVariant* reply;
reply = g_dbus_connection_call_sync(connection, bus_name, object_path, interface_name, method_name, parameters, G_VARIANT_TYPE("(o)"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
g_object_unref(connection);
if(error)
{
g_error_free(error);
return false;
}
if(reply)
{
g_variant_unref(reply);
return true;
}
return false;
}
bool AutoStart::EnableAutoStart(AutoStartInfo autostart_info)
{
return CallBackgroundPortal(true, autostart_info);
}
bool AutoStart::DisableAutoStart()
{
return CallBackgroundPortal(false);
}
bool AutoStart::IsAutoStartEnabled()
{
/*-----------------------------------------------------*\
| The FreeDesktop Background Portal does not provide a |
| stateless status getter. Sandbox permissions are |
| securely managed and persisted internally by the |
| host's portal service. To implement a true state |
| track here, OpenRGB will need to manage a local |
| boolean flag inside its own configuration files (e.g. |
| OpenRGB.json). |
\*-----------------------------------------------------*/
return false;
}
std::string AutoStart::GetExePath()
{
/*-----------------------------------------------------*\
| Flatpak applications always run from this specific |
| exported path hierarchy |
\*-----------------------------------------------------*/
return "/app/bin/openrgb";
}
+31
View File
@@ -0,0 +1,31 @@
/*---------------------------------------------------------*\
| AutoStart-Flatpak.h |
| |
| Autostart implementation for Linux via Flatpak Portal |
| |
| Adam Honse <[email protected]> 04 Sep 2026 |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include "AutoStart.h"
class AutoStart : public AutoStartInterface
{
public:
AutoStart(std::string name);
bool DisableAutoStart();
bool EnableAutoStart(AutoStartInfo autostart_info);
bool IsAutoStartEnabled();
std::string GetExePath();
private:
void InitAutoStart(std::string name);
bool CallBackgroundPortal(bool enable, const AutoStartInfo& autostart_info = {});
std::string app_name;
};
+81 -2
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 |
\*-----------------------------------------------------*/
@@ -134,6 +149,64 @@ std::string AutoStart::GetExePath()
| Private Methods |
\*---------------------------------------------------------*/
/*---------------------------------------------------------*\
| Escape a single argument for use in a .desktop Exec= |
| field, per the Desktop Entry Specification. |
| |
| Reserved characters must be escaped by wrapping the whole |
| argument in double quotes and backslash-escaping the |
| quote/backquote/backslash/dollar characters, and any |
| literal '%' must be doubled to '%%'. |
\*---------------------------------------------------------*/
static std::string DesktopExecEscape(const std::string& arg)
{
static const std::string reserved = " \t\n\"'`\\><~|&;$*?#()%";
bool needs_quotes = false;
for(char c : arg)
{
if(reserved.find(c) != std::string::npos)
{
needs_quotes = true;
break;
}
}
std::string escaped;
if(needs_quotes)
{
escaped += '"';
}
for(char c : arg)
{
switch(c)
{
case '"':
case '`':
case '\\':
case '$':
escaped += '\\';
escaped += c;
break;
case '%':
escaped += "%%";
break;
default:
escaped += c;
break;
}
}
if(needs_quotes)
{
escaped += '"';
}
return(escaped);
}
std::string AutoStart::GenerateDesktopFile(AutoStartInfo autostart_info)
{
/*-----------------------------------------------------*\
@@ -154,11 +227,17 @@ std::string AutoStart::GenerateDesktopFile(AutoStartInfo autostart_info)
/*-----------------------------------------------------*\
| Add the executable path and arguments |
\*-----------------------------------------------------*/
fileContents << "Exec=" << autostart_info.path;
fileContents << "Exec=" << DesktopExecEscape(autostart_info.path);
if (autostart_info.args != "")
{
fileContents << " " << autostart_info.args;
std::istringstream arg_parser(autostart_info.args);
std::string arg;
while(arg_parser >> arg)
{
fileContents << " " << DesktopExecEscape(arg);
}
}
fileContents << std::endl;
+41 -2
View File
@@ -137,6 +137,43 @@ std::string AutoStart::GetExePath()
| Private Methods |
\*---------------------------------------------------------*/
/*---------------------------------------------------------*\
| Escape a string for embedding inside an XML element |
| (used by the .plist generator) to avoid producing |
| malformed property list files. |
\*---------------------------------------------------------*/
static std::string XmlEscape(const std::string& input)
{
std::string escaped;
for(char c : input)
{
switch(c)
{
case '&':
escaped += "&amp;";
break;
case '<':
escaped += "&lt;";
break;
case '>':
escaped += "&gt;";
break;
case '"':
escaped += "&quot;";
break;
case '\'':
escaped += "&apos;";
break;
default:
escaped += c;
break;
}
}
return(escaped);
}
std::string AutoStart::GenerateLaunchAgentFile(AutoStartInfo autostart_info)
{
/*-----------------------------------------------------*\
@@ -153,7 +190,8 @@ std::string AutoStart::GenerateLaunchAgentFile(AutoStartInfo autostart_info)
fileContents << " <string>org.openrgb</string>" << std::endl;
fileContents << " <key>ProgramArguments</key>" << std::endl;
fileContents << " <array>" << std::endl;
fileContents << " <string>" << autostart_info.path << "</string>" << std::endl;
fileContents << " <string>" << XmlEscape(autostart_info.path) << "</string>"
<< std::endl;
if(autostart_info.args != "")
{
@@ -162,7 +200,8 @@ std::string AutoStart::GenerateLaunchAgentFile(AutoStartInfo autostart_info)
while(arg_parser >> arg)
{
fileContents << " <string>" << arg << "</string>" << std::endl;
fileContents << " <string>" << XmlEscape(arg) << "</string>"
<< std::endl;
}
}
+34 -32
View File
@@ -12,6 +12,7 @@
#include <shlobj.h>
#include "AutoStart-Windows.h"
#include "LogManager.h"
#include "StringUtils.h"
#include "filesystem.h"
#include "windows.h"
@@ -71,11 +72,11 @@ bool AutoStart::EnableAutoStart(AutoStartInfo autostart_info)
HRESULT result;
IShellLinkW* shellLink = NULL;
std::wstring exepathw = utf8_decode(autostart_info.path);
std::wstring argumentsw = utf8_decode(autostart_info.args);
std::wstring startupfilepathw = utf8_decode(autostart_file);
std::wstring descriptionw = utf8_decode(autostart_info.desc);
std::wstring iconw = utf8_decode(autostart_info.path);
std::wstring exepathw = StringUtils::string_to_wstring(autostart_info.path);
std::wstring argumentsw = StringUtils::string_to_wstring(autostart_info.args);
std::wstring startupfilepathw = StringUtils::string_to_wstring(autostart_file);
std::wstring descriptionw = StringUtils::string_to_wstring(autostart_info.desc);
std::wstring iconw = StringUtils::string_to_wstring(autostart_info.path);
result = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_ALL, IID_IShellLinkW, (void**)&shellLink);
@@ -150,11 +151,16 @@ std::string AutoStart::GetExePath()
/*-----------------------------------------------------*\
| Create the OpenRGB executable path |
\*-----------------------------------------------------*/
char exepath[MAX_PATH] = "";
wchar_t exepath[MAX_PATH] = L"";
DWORD count = GetModuleFileNameA(NULL, exepath, MAX_PATH);
DWORD count = GetModuleFileNameW(NULL, exepath, MAX_PATH);
return(std::string(exepath, (count > 0) ? count : 0));
if(count == 0)
{
return(std::string());
}
return(StringUtils::wstring_to_string(std::wstring(exepath, count)));
}
/*---------------------------------------------------------*\
@@ -164,43 +170,39 @@ std::string AutoStart::GetExePath()
void AutoStart::InitAutoStart(std::string name)
{
char startMenuPath[MAX_PATH];
wchar_t startMenuPath[MAX_PATH];
autostart_name = name;
/*-----------------------------------------------------*\
| Get startup applications path |
\*-----------------------------------------------------*/
HRESULT result = SHGetFolderPathA(NULL, CSIDL_PROGRAMS, NULL, 0, startMenuPath);
HRESULT result = SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, 0, startMenuPath);
if(SUCCEEDED(result))
{
autostart_file = std::string(startMenuPath);
std::string autostart_dir = StringUtils::wstring_to_string(startMenuPath);
autostart_dir += "\\Startup\\";
autostart_file += "\\Startup\\" + autostart_name + ".lnk";
/*-------------------------------------------------*\
| Create the directory if it doesn't exist |
\*-------------------------------------------------*/
std::error_code ec;
bool success = true;
if(!filesystem::exists(autostart_dir))
{
success = filesystem::create_directories(autostart_dir, ec);
}
if(success)
{
autostart_file = autostart_dir + autostart_name + ".lnk";
}
}
else
{
autostart_file.clear();
}
}
/*---------------------------------------------------------*\
| Convert an UTF8 string to a wide Unicode String |
| (from wmi.cpp) |
\*---------------------------------------------------------*/
std::wstring AutoStart::utf8_decode(const std::string& str)
{
if(str.empty())
{
return std::wstring();
}
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int) str.size(), nullptr, 0);
std::wstring wstrTo(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int) str.size(), &wstrTo[0], size_needed);
return(wstrTo);
}
-1
View File
@@ -24,5 +24,4 @@ public:
private:
void InitAutoStart(std::string name);
std::wstring utf8_decode(const std::string& str);
};
+6
View File
@@ -40,9 +40,15 @@ protected:
#include "AutoStart-Windows.h"
#endif
#ifdef FLATPAK_BUILD
#include "AutoStart-Flatpak.h"
#endif
#ifdef __linux__
#ifndef FLATPAK_BUILD
#include "AutoStart-Linux.h"
#endif
#endif
#ifdef __APPLE__
#include "AutoStart-MacOS.h"
@@ -1,66 +0,0 @@
/*---------------------------------------------------------*\
| A4TechDetector.cpp |
| |
| Detector for A4Tech Devices |
| |
| Chris M (Dr_No) 30 Jun 2022 |
| Mohammed Julfikar Ali Mahbub (o-julfikar) 01 Apr 2024 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "BloodyB820RController.h"
#include "BloodyMouseController.h"
#include "DetectionManager.h"
#include "RGBController_BloodyB820R.h"
#include "RGBController_BloodyMouse.h"
/*---------------------------------------------------------*\
| A4 Tech USB vendor ID |
\*---------------------------------------------------------*/
#define A4_TECH_VID 0x09DA
DetectedControllers DetectA4TechMouseControllers(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
BloodyMouseController* controller = new BloodyMouseController(dev, info->path, info->product_id, name);
RGBController_BloodyMouse* rgb_controller = new RGBController_BloodyMouse(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
DetectedControllers DetectBloodyB820R(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
BloodyB820RController* controller = new BloodyB820RController(dev, info->path, name);
RGBController_BloodyB820R* rgb_controller = new RGBController_BloodyB820R(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_IPU("Bloody W60 Pro", DetectA4TechMouseControllers, A4_TECH_VID, BLOODY_W60_PRO_PID, 2, 0xFF33, 0x0529);
REGISTER_HID_DETECTOR_IPU("Bloody W70 Max", DetectA4TechMouseControllers, A4_TECH_VID, BLOODY_W70_MAX_PID, 2, 0xFF33, 0x0518);
REGISTER_HID_DETECTOR_IPU("Bloody W90 Max", DetectA4TechMouseControllers, A4_TECH_VID, BLOODY_W90_MAX_PID, 2, 0xFF33, 0x053D);
REGISTER_HID_DETECTOR_IPU("Bloody W90 Pro", DetectA4TechMouseControllers, A4_TECH_VID, BLOODY_W90_PRO_PID, 2, 0xFF33, 0x054D);
REGISTER_HID_DETECTOR_IPU("Bloody MP 50RS", DetectA4TechMouseControllers, A4_TECH_VID, BLOODY_MP_50RS_PID, 2, 0xFFF2, 0x6009);
REGISTER_HID_DETECTOR_IPU("Bloody B820R", DetectBloodyB820R, A4_TECH_VID, BLOODY_B820R_PID, 2, 0xFF52, 0x0210);
@@ -0,0 +1,40 @@
/*---------------------------------------------------------*\
| BloodyB820RControllerDetect.cpp |
| |
| Detector for Bloody B820R Devices |
| |
| Mohammed Julfikar Ali Mahbub (o-julfikar) 01 Apr 2024 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "BloodyB820RController.h"
#include "DetectionManager.h"
#include "RGBController_BloodyB820R.h"
/*---------------------------------------------------------*\
| A4 Tech USB vendor ID |
\*---------------------------------------------------------*/
#define A4_TECH_VID 0x09DA
DetectedControllers DetectBloodyB820R(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
BloodyB820RController* controller = new BloodyB820RController(dev, info->path, name);
RGBController_BloodyB820R* rgb_controller = new RGBController_BloodyB820R(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_IPU("Bloody B820R", DetectBloodyB820R, A4_TECH_VID, BLOODY_B820R_PID, 2, 0xFF52, 0x0210);
@@ -0,0 +1,44 @@
/*---------------------------------------------------------*\
| BloodyMouseControllerDetect.cpp |
| |
| Detector for Bloody Mouse Devices |
| |
| Chris M (Dr_No) 30 Jun 2022 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "BloodyMouseController.h"
#include "DetectionManager.h"
#include "RGBController_BloodyMouse.h"
/*---------------------------------------------------------*\
| A4 Tech USB vendor ID |
\*---------------------------------------------------------*/
#define A4_TECH_VID 0x09DA
DetectedControllers DetectBloodyMouseControllers(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
BloodyMouseController* controller = new BloodyMouseController(dev, info->path, info->product_id, name);
RGBController_BloodyMouse* rgb_controller = new RGBController_BloodyMouse(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_IPU("Bloody W60 Pro", DetectBloodyMouseControllers, A4_TECH_VID, BLOODY_W60_PRO_PID, 2, 0xFF33, 0x0529);
REGISTER_HID_DETECTOR_IPU("Bloody W70 Max", DetectBloodyMouseControllers, A4_TECH_VID, BLOODY_W70_MAX_PID, 2, 0xFF33, 0x0518);
REGISTER_HID_DETECTOR_IPU("Bloody W90 Max", DetectBloodyMouseControllers, A4_TECH_VID, BLOODY_W90_MAX_PID, 2, 0xFF33, 0x053D);
REGISTER_HID_DETECTOR_IPU("Bloody W90 Pro", DetectBloodyMouseControllers, A4_TECH_VID, BLOODY_W90_PRO_PID, 2, 0xFF33, 0x054D);
REGISTER_HID_DETECTOR_IPU("Bloody MP 50RS", DetectBloodyMouseControllers, A4_TECH_VID, BLOODY_MP_50RS_PID, 2, 0xFFF2, 0x6009);
@@ -334,9 +334,14 @@ void RGBController_PolychromeUSB::DeviceUpdateLEDs()
}
for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
if(zones[zone_idx].leds_count == 0)
{
continue;
}
unsigned char set_mode = zones_info[zone_idx].mode;
if (set_mode>modes.size())
if(set_mode>modes.size())
{
set_mode = active_mode;
}
@@ -347,6 +352,11 @@ void RGBController_PolychromeUSB::DeviceUpdateLEDs()
void RGBController_PolychromeUSB::DeviceUpdateZoneLEDs(int zone)
{
if(zones[zone].leds_count == 0)
{
return;
}
unsigned char set_mode=zones_info[zone].mode;
if(set_mode > modes.size())
@@ -354,7 +364,7 @@ void RGBController_PolychromeUSB::DeviceUpdateZoneLEDs(int zone)
set_mode = active_mode;
}
controller->WriteZone(zone, set_mode, zones_info[zone].speed, zones[zone].colors[0], false);
controller->WriteZone((unsigned char)zone, set_mode, zones_info[zone].speed, zones[zone].colors[0], false);
}
void RGBController_PolychromeUSB::DeviceUpdateSingleLED(int led)
@@ -362,12 +372,17 @@ void RGBController_PolychromeUSB::DeviceUpdateSingleLED(int led)
unsigned int channel = leds[led].value;
unsigned char set_mode = zones_info[channel].mode;
if(zones[channel].leds_count == 0)
{
return;
}
if(set_mode > modes.size())
{
set_mode = active_mode;
}
controller->WriteZone(channel, set_mode, zones_info[channel].speed, zones[channel].colors[0], false);
controller->WriteZone((unsigned char)channel, set_mode, zones_info[channel].speed, zones[channel].colors[0], false);
}
unsigned char RGBController_PolychromeUSB::GetDeviceMode(unsigned char zone)
@@ -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, // Effect speed
0xFF, // Brightness
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,47 @@
/*---------------------------------------------------------*\
| 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_amd_gpu.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;
if(bus->info.pci_vendor == AMD_GPU_VEN && !is_amd_gpu_i2c_bus(bus))
{
return(detected_controllers);
}
LOG_DEBUG("[ASRock GPU] Checking GPU I2C bus for RGB controller at 0x%02X", 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);
REGISTER_I2C_PCI_DETECTOR("ASRock Radeon RX 9070 XT Steel Legend Dark", DetectASRockGPUSMBusControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, ASROCK_SUB_VEN, 0x5414, ASROCK_GPU_SMBUS_ADDRESS);
REGISTER_I2C_PCI_DETECTOR("ASRock Radeon RX 9070 XT Taichi 16GB OC", DetectASRockGPUSMBusControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, ASROCK_SUB_VEN, 0x5401, ASROCK_GPU_SMBUS_ADDRESS);
REGISTER_I2C_PCI_DETECTOR("ASRock Radeon RX 9060 XT Steel Legend", DetectASRockGPUSMBusControllers, AMD_GPU_VEN, AMD_NAVI44_DEV, ASROCK_SUB_VEN, 0x5407, ASROCK_GPU_SMBUS_ADDRESS);
@@ -0,0 +1,140 @@
/*---------------------------------------------------------*\
| 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"
/**------------------------------------------------------------------*\
@name ASRock GPU SMBus
@category GPU
@type I2C
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectASRockGPUSMBusControllers
@comment
\*-------------------------------------------------------------------*/
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()
{
Shutdown();
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();
}
@@ -1,9 +1,9 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGPro.h |
| RGBController_ASRockGPUSMBus.h |
| |
| RGBController for Logitech G Pro keyboard |
| RGBController for ASRock GPU RGB controllers |
| |
| sanchezzzs 20 Oct 2021 |
| Based on SignalRGB ASRock GPU.js plugin |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -12,22 +12,23 @@
#pragma once
#include "RGBController.h"
#include "LogitechGProKeyboardController.h"
#include "ASRockGPUSMBusController.h"
class RGBController_LogitechGProKeyboard : public RGBController
class RGBController_ASRockGPUSMBus : public RGBController
{
public:
RGBController_LogitechGProKeyboard(LogitechGProKeyboardController* controller_ptr);
~RGBController_LogitechGProKeyboard();
RGBController_ASRockGPUSMBus(ASRockGPUSMBusController* controller_ptr);
~RGBController_ASRockGPUSMBus();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechGProKeyboardController* controller;
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;
};
@@ -32,6 +32,7 @@ static const std::map<alienware_platform_id, uint8_t> zone_quirks_table =
{ 0x0C01, 4 }, // Dell G5 SE 5505
{ 0x0A01, 16 }, // Dell G7 15 7500
{ 0x0E03, 4 }, // Dell G15 5511
{ 0x0E07, 4 }, // Dell G15 5520
{ 0x0E0A, 4 } // Dell G15 5530
};
@@ -49,9 +50,21 @@ static const std::map<alienware_platform_id, std::vector<const char*>> zone_name
"Light Bar 7", "Light Bar 8", "Light Bar 9",
"Light Bar 10", "Light Bar 11", "Light Bar 12" } },
{ 0x0E03, { "Left", "Middle", "Right", "Numpad" } },
{ 0x0E07, { "Left", "Middle", "Right", "Numpad" } },
{ 0x0E0A, { "Left", "Middle", "Right", "Numpad" } }
};
/*---------------------------------------------------------*\
| Mapping the platform ID to the actual hardware zone IDs |
\*---------------------------------------------------------*/
static const std::map<alienware_platform_id, std::vector<uint8_t>> zone_ids_table =
{
{ 0x0C01, { 0x00, 0x01, 0x02, 0x03 } }, // Dell G5 SE 5505
{ 0x0E03, { 0x00, 0x01, 0x02, 0x03 } }, // Dell G15 5511
{ 0x0E07, { 0x10, 0x11, 0x12, 0x13 } }, // Dell G15 5520
{ 0x0E0A, { 0x10, 0x11, 0x12, 0x13 } } // Dell G15 5530
};
static void SendHIDReport(hid_device *dev, const unsigned char* usb_buf, size_t usb_buf_size)
{
using namespace std::chrono_literals;
@@ -138,6 +151,21 @@ AlienwareController::AlienwareController(hid_device* dev_handle, const hid_devic
}
}
/*-----------------------------------------------------*\
| Initialize hardware zone IDs |
\*-----------------------------------------------------*/
if(zone_ids_table.count(platform_id))
{
zone_ids = zone_ids_table.at(platform_id);
}
else
{
for(size_t i = 0; i < number_of_zones; i++)
{
zone_ids.push_back(static_cast<uint8_t>(i));
}
}
/*-----------------------------------------------------*\
| Set defaults for all zones |
| It doesn't seem possible to read the controller's |
@@ -155,6 +183,7 @@ AlienwareController::AlienwareController(hid_device* dev_handle, const hid_devic
zones[zone_idx].period = 2000;
zones[zone_idx].tempo = ALIENWARE_TEMPO_MAX;
zones[zone_idx].dim = 0;
zones[zone_idx].direction = 0;
}
/*-----------------------------------------------------*\
@@ -266,42 +295,53 @@ bool AlienwareController::Dim(std::vector<uint8_t> zones, double percent)
return(true);
}
unsigned char usb_buf[HIDAPI_ALIENWARE_REPORT_SIZE];
bool result = true;
/*-----------------------------------------------------*\
| Zero out buffer |
| Split the zone list into packet-sized chunks so it |
| never overflows usb_buf |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up message packet with leading 00, per hidapi |
\*-----------------------------------------------------*/
uint16_t num_zones = (uint16_t)zones.size();
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x03;
usb_buf[0x02] = ALIENWARE_COMMAND_DIM;
usb_buf[0x03] = static_cast<uint8_t>(percent);
usb_buf[0x04] = num_zones >> 8;
usb_buf[0x05] = num_zones & 0xFF;
for(size_t i = 0; i < num_zones; i++)
for(size_t offset = 0; offset < zones.size(); offset += ALIENWARE_MAX_ZONES_PER_PACKET)
{
usb_buf[0x06+i] = zones[i];
unsigned char usb_buf[HIDAPI_ALIENWARE_REPORT_SIZE];
/*-------------------------------------------------*\
| Zero out buffer |
\*-------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-------------------------------------------------*\
| Set up message packet with leading 00, per hidapi |
\*-------------------------------------------------*/
uint16_t num_zones = (uint16_t)std::min<size_t>(ALIENWARE_MAX_ZONES_PER_PACKET, zones.size() - offset);
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x03;
usb_buf[0x02] = ALIENWARE_COMMAND_DIM;
usb_buf[0x03] = static_cast<uint8_t>(percent);
usb_buf[0x04] = num_zones >> 8;
usb_buf[0x05] = num_zones & 0xFF;
for(size_t i = 0; i < num_zones; i++)
{
usb_buf[0x06+i] = zones[offset + i];
}
/*-------------------------------------------------*\
| Send packet |
\*-------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-------------------------------------------------*\
| For this command, error is if the output equals |
| the input |
\*-------------------------------------------------*/
result &= (response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE);
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-----------------------------------------------------*\
| For this command, error is if the output equals the |
| input |
\*-----------------------------------------------------*/
return((response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE));
return(result);
}
bool AlienwareController::UserAnimation(uint16_t subcommand, uint16_t animation, uint16_t duration)
@@ -370,42 +410,53 @@ bool AlienwareController::SelectZones(const std::vector<uint8_t>& zones)
return(false);
}
unsigned char usb_buf[HIDAPI_ALIENWARE_REPORT_SIZE];
bool result = true;
/*-----------------------------------------------------*\
| Zero out buffer |
| Split the zone list into packet-sized chunks so it |
| never overflows usb_buf |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up message packet with leading 00, per hidapi |
\*-----------------------------------------------------*/
uint16_t num_zones = (uint16_t)zones.size();
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x03;
usb_buf[0x02] = ALIENWARE_COMMAND_SELECT_ZONES;
usb_buf[0x03] = 1; // loop?
usb_buf[0x04] = num_zones >> 8;
usb_buf[0x05] = num_zones & 0xFF;
for(size_t i = 0; i < num_zones; i++)
for(size_t offset = 0; offset < zones.size(); offset += ALIENWARE_MAX_ZONES_PER_PACKET)
{
usb_buf[0x06+i] = zones[i];
unsigned char usb_buf[HIDAPI_ALIENWARE_REPORT_SIZE];
/*-------------------------------------------------*\
| Zero out buffer |
\*-------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-------------------------------------------------*\
| Set up message packet with leading 00, per hidapi |
\*-------------------------------------------------*/
uint16_t num_zones = (uint16_t)std::min<size_t>(ALIENWARE_MAX_ZONES_PER_PACKET, zones.size() - offset);
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x03;
usb_buf[0x02] = ALIENWARE_COMMAND_SELECT_ZONES;
usb_buf[0x03] = 1; // loop?
usb_buf[0x04] = num_zones >> 8;
usb_buf[0x05] = num_zones & 0xFF;
for(size_t i = 0; i < num_zones; i++)
{
usb_buf[0x06+i] = zones[offset + i];
}
/*-------------------------------------------------*\
| Send packet |
\*-------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-------------------------------------------------*\
| For this command, error is if the output equals |
| the input |
\*-------------------------------------------------*/
result &= (response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE);
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-----------------------------------------------------*\
| For this command, error is if the output equals the |
| input |
\*-----------------------------------------------------*/
return((response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE));
return(result);
}
bool AlienwareController::ModeAction(uint8_t mode, uint16_t duration, uint16_t tempo, RGBColor color)
@@ -509,44 +560,55 @@ bool AlienwareController::SetColorDirect(RGBColor color, std::vector<uint8_t> zo
return(true);
}
unsigned char usb_buf[HIDAPI_ALIENWARE_REPORT_SIZE];
bool result = true;
/*-----------------------------------------------------*\
| Zero out buffer |
| Split the zone list into packet-sized chunks so it |
| never overflows usb_buf |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up message packet with leading 00, per hidapi |
\*-----------------------------------------------------*/
uint16_t num_zones = (uint16_t)zones.size();
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x03;
usb_buf[0x02] = ALIENWARE_COMMAND_SET_COLOR;
usb_buf[0x03] = RGBGetRValue(color);
usb_buf[0x04] = RGBGetGValue(color);
usb_buf[0x05] = RGBGetBValue(color);
usb_buf[0x06] = num_zones >> 8;
usb_buf[0x07] = num_zones & 0xFF;
for(size_t i = 0; i < num_zones; i++)
for(size_t offset = 0; offset < zones.size(); offset += ALIENWARE_MAX_ZONES_PER_PACKET)
{
usb_buf[0x08 + i] = zones[i];
unsigned char usb_buf[HIDAPI_ALIENWARE_REPORT_SIZE];
/*-------------------------------------------------*\
| Zero out buffer |
\*-------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-------------------------------------------------*\
| Set up message packet with leading 00, per hidapi |
\*-------------------------------------------------*/
uint16_t num_zones = (uint16_t)std::min<size_t>(ALIENWARE_MAX_ZONES_PER_PACKET, zones.size() - offset);
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x03;
usb_buf[0x02] = ALIENWARE_COMMAND_SET_COLOR;
usb_buf[0x03] = RGBGetRValue(color);
usb_buf[0x04] = RGBGetGValue(color);
usb_buf[0x05] = RGBGetBValue(color);
usb_buf[0x06] = num_zones >> 8;
usb_buf[0x07] = num_zones & 0xFF;
for(size_t i = 0; i < num_zones; i++)
{
usb_buf[0x08 + i] = zones[offset + i];
}
/*-------------------------------------------------*\
| Send packet |
\*-------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-------------------------------------------------*\
| For this command, error is if the output equals |
| the input |
\*-------------------------------------------------*/
result &= (response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE);
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-----------------------------------------------------*\
| For this command, error is if the output equals the |
| input |
\*-----------------------------------------------------*/
return((response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE));
return(result);
}
bool AlienwareController::Reset()
@@ -640,6 +702,15 @@ void AlienwareController::SetDim(uint8_t zone, uint8_t dim)
}
}
void AlienwareController::SetDirection(uint8_t zone, uint8_t direction)
{
if(zone < zones.size())
{
zones[zone].direction = direction;
dirty = true;
}
}
void AlienwareController::UpdateDim()
{
if(!dirty_dim)
@@ -655,7 +726,8 @@ void AlienwareController::UpdateDim()
for(size_t i = 0; i < zones.size(); i++)
{
dim_zone_map[zones[i].dim].emplace_back((uint8_t)i);
uint8_t hw_zone = (i < zone_ids.size()) ? zone_ids[i] : static_cast<uint8_t>(i);
dim_zone_map[zones[i].dim].emplace_back(hw_zone);
}
for(std::pair<const uint8_t, std::vector<uint8_t>> &pair : dim_zone_map)
@@ -682,7 +754,8 @@ bool AlienwareController::UpdateDirect()
for(size_t i = 0; i < zones.size(); i++)
{
color_zone_map[zones[i].color[0]].emplace_back((uint8_t)i);
uint8_t hw_zone = (i < zone_ids.size()) ? zone_ids[i] : static_cast<uint8_t>(i);
color_zone_map[zones[i].color[0]].emplace_back(hw_zone);
}
for(std::pair<const RGBColor, std::vector<uint8_t>> &pair : color_zone_map)
@@ -726,8 +799,9 @@ void AlienwareController::UpdateMode()
for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
alienware_zone zone = zones[zone_idx];
uint8_t hw_zone = (zone_idx < zone_ids.size()) ? zone_ids[zone_idx] : static_cast<uint8_t>(zone_idx);
result = SelectZones({static_cast<uint8_t>(zone_idx)});
result = SelectZones({hw_zone});
if(!result)
{
@@ -750,18 +824,18 @@ void AlienwareController::UpdateMode()
case ALIENWARE_MODE_MORPH:
{
uint8_t zones[2] = { zone.mode, zone.mode };
uint8_t modes[2] = { zone.mode, zone.mode };
uint16_t periods[2] = { zone.period, zone.period };
uint16_t tempos[2] = { zone.tempo, zone.tempo };
RGBColor colors[2] = { zone.color[0], zone.color[1] };
result = MultiModeAction(zones, periods, tempos, colors, 2);
result = MultiModeAction(modes, periods, tempos, colors, 2);
}
break;
case ALIENWARE_MODE_SPECTRUM:
{
uint8_t zones[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
uint8_t modes[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH };
@@ -774,13 +848,13 @@ void AlienwareController::UpdateMode()
zone.tempo, zone.tempo,
zone.tempo };
result = MultiModeAction(zones, periods, tempos, rainbow_colors[0], 7);
result = MultiModeAction(modes, periods, tempos, rainbow_colors[0], 7);
}
break;
case ALIENWARE_MODE_RAINBOW:
{
uint8_t zones[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
uint8_t modes[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH };
@@ -793,18 +867,19 @@ void AlienwareController::UpdateMode()
zone.tempo, zone.tempo,
zone.tempo };
result = MultiModeAction(zones, periods, tempos, rainbow_colors[zone_idx], 7);
unsigned int color_idx = (zone.direction == MODE_DIRECTION_LEFT) ? (4 - 1 - (zone_idx % 4)) : (zone_idx % 4);
result = MultiModeAction(modes, periods, tempos, rainbow_colors[color_idx], 7);
}
break;
case ALIENWARE_MODE_BREATHING:
{
uint8_t zones[2] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH };
uint8_t modes[2] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH };
uint16_t periods[2] = { zone.period, zone.period };
uint16_t tempos[2] = { zone.tempo, zone.tempo };
RGBColor colors[2] = { zone.color[0], 0x0 };
result = MultiModeAction(zones, periods, tempos, colors, 2);
result = MultiModeAction(modes, periods, tempos, colors, 2);
}
break;
@@ -831,6 +906,117 @@ void AlienwareController::UpdateMode()
dirty = false;
}
void AlienwareController::SaveController()
{
/*-----------------------------------------------------*\
| Save current mode configuration to EEPROM slot 0x0061 |
\*-----------------------------------------------------*/
UserAnimation(ALIENWARE_COMMAND_USER_ANIM_REMOVE, ALIENWARE_ANIM_DEFAULT, 0);
bool result = UserAnimation(ALIENWARE_COMMAND_USER_ANIM_NEW, ALIENWARE_ANIM_DEFAULT, 0);
if(!result)
{
return;
}
for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
alienware_zone zone = zones[zone_idx];
uint8_t hw_zone = (zone_idx < zone_ids.size()) ? zone_ids[zone_idx] : static_cast<uint8_t>(zone_idx);
result = SelectZones({hw_zone});
if(!result)
{
return;
}
switch (zone.mode)
{
case ALIENWARE_MODE_COLOR:
result = ModeAction(zone.mode, 2000, ALIENWARE_TEMPO_MAX, zone.color[0]);
break;
case ALIENWARE_MODE_PULSE:
result = ModeAction(zone.mode, zone.period, zone.tempo, zone.color[0]);
break;
case ALIENWARE_MODE_MORPH:
{
uint8_t modes[2] = { zone.mode, zone.mode };
uint16_t periods[2] = { zone.period, zone.period };
uint16_t tempos[2] = { zone.tempo, zone.tempo };
RGBColor colors[2] = { zone.color[0], zone.color[1] };
result = MultiModeAction(modes, periods, tempos, colors, 2);
}
break;
case ALIENWARE_MODE_SPECTRUM:
{
uint8_t modes[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH };
uint16_t periods[7] = { zone.period, zone.period,
zone.period, zone.period,
zone.period, zone.period,
zone.period };
uint16_t tempos[7] = { zone.tempo, zone.tempo,
zone.tempo, zone.tempo,
zone.tempo, zone.tempo,
zone.tempo };
result = MultiModeAction(modes, periods, tempos, rainbow_colors[0], 7);
}
break;
case ALIENWARE_MODE_RAINBOW:
{
uint8_t modes[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH,
ALIENWARE_MODE_MORPH };
uint16_t periods[7] = { zone.period, zone.period,
zone.period, zone.period,
zone.period, zone.period,
zone.period };
uint16_t tempos[7] = { zone.tempo, zone.tempo,
zone.tempo, zone.tempo,
zone.tempo, zone.tempo,
zone.tempo };
unsigned int color_idx = (zone.direction == MODE_DIRECTION_LEFT) ? (4 - 1 - (zone_idx % 4)) : (zone_idx % 4);
result = MultiModeAction(modes, periods, tempos, rainbow_colors[color_idx], 7);
}
break;
case ALIENWARE_MODE_BREATHING:
{
uint8_t modes[2] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH };
uint16_t periods[2] = { zone.period, zone.period };
uint16_t tempos[2] = { zone.tempo, zone.tempo };
RGBColor colors[2] = { zone.color[0], 0x0 };
result = MultiModeAction(modes, periods, tempos, colors, 2);
}
break;
default:
result = false;
}
if(!result)
{
return;
}
}
UserAnimation(ALIENWARE_COMMAND_USER_ANIM_FINISH_SAVE, ALIENWARE_ANIM_DEFAULT, 0);
UserAnimation(ALIENWARE_COMMAND_USER_ANIM_DEFAULT, ALIENWARE_ANIM_DEFAULT, 0);
UserAnimation(ALIENWARE_COMMAND_USER_ANIM_STARTUP, ALIENWARE_ANIM_DEFAULT, 0);
}
void AlienwareController::UpdateController()
{
UpdateMode();
@@ -22,6 +22,14 @@
#define ALIENWARE_REPORT_SIZE 33
#define HIDAPI_ALIENWARE_REPORT_SIZE (ALIENWARE_REPORT_SIZE + 1)
#define ALIENWARE_CONTROLLER_NAME "AlienWare Controller"
/*---------------------------------------------------------*\
| Max zone IDs that fit in one report after the command |
| header. Selections larger than this must be split across |
| multiple packets; writing them into a single report |
| overflows usb_buf (e.g. desktops with ~100 zones). |
\*---------------------------------------------------------*/
#define ALIENWARE_MAX_ZONES_PER_PACKET 25
enum
{
ALIENWARE_COMMAND_REPORT = 0x20, /* Set report type to get */
@@ -115,11 +123,13 @@ public:
void SetPeriod(uint8_t zone, uint16_t period);
void SetTempo(uint8_t zone, uint16_t tempo);
void SetDim(uint8_t zone, uint8_t dim);
void SetDirection(uint8_t zone, uint8_t direction);
AlienwareReport GetStatus(uint8_t subcommand);
void UpdateDim();
void UpdateMode();
void UpdateController();
void SaveController();
protected:
hid_device* dev;
@@ -132,6 +142,7 @@ private:
uint16_t period;
uint16_t tempo;
uint8_t dim;
uint8_t direction;
} alienware_zone;
typedef struct
@@ -145,6 +156,7 @@ private:
std::string serial_number;
std::string version;
std::vector<const char*> zone_names;
std::vector<uint8_t> zone_ids;
bool dirty;
bool dirty_dim;
@@ -39,7 +39,7 @@ RGBController_Alienware::RGBController_Alienware(AlienwareController* controller
mode Color;
Color.name = "Static";
Color.value = ALIENWARE_MODE_COLOR;
Color.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Color.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE;
Color.color_mode = MODE_COLORS_PER_LED;
Color.colors_min = 1;
Color.colors_max = 1;
@@ -51,13 +51,13 @@ RGBController_Alienware::RGBController_Alienware(AlienwareController* controller
mode Pulse;
Pulse.name = "Flashing";
Pulse.value = ALIENWARE_MODE_PULSE;
Pulse.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Pulse.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE;
Pulse.color_mode = MODE_COLORS_PER_LED;
Pulse.colors_min = 1;
Pulse.colors_max = 1;
Pulse.speed_min = ALIENWARE_TEMPO_MIN;
Pulse.speed_max = ALIENWARE_TEMPO_MAX;
Pulse.speed = ALIENWARE_TEMPO_MIN;
Pulse.speed_min = ALIENWARE_TEMPO_MAX;
Pulse.speed_max = 50;
Pulse.speed = 150;
Pulse.brightness_min = 100;
Pulse.brightness_max = 0;
Pulse.brightness = 0;
@@ -66,14 +66,16 @@ RGBController_Alienware::RGBController_Alienware(AlienwareController* controller
mode Morph;
Morph.name = "Morph";
Morph.value = ALIENWARE_MODE_MORPH;
Morph.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Morph.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE;
Morph.color_mode = MODE_COLORS_MODE_SPECIFIC;
Morph.colors_min = 2 * controller->GetZoneCount();
Morph.colors_max = Morph.colors_min;
Morph.colors.resize(Morph.colors_max);
Morph.speed_min = ALIENWARE_TEMPO_MIN;
Morph.speed_max = ALIENWARE_TEMPO_MAX;
Morph.speed = ALIENWARE_TEMPO_MIN;
Morph.colors_min = 2;
Morph.colors_max = 2 * controller->GetZoneCount();
Morph.colors.resize(2);
Morph.colors[0] = 0x0000FF;
Morph.colors[1] = 0xFF0000;
Morph.speed_min = ALIENWARE_TEMPO_MAX;
Morph.speed_max = 20;
Morph.speed = 100;
Morph.brightness_min = 100;
Morph.brightness_max = 0;
Morph.brightness = 0;
@@ -82,11 +84,11 @@ RGBController_Alienware::RGBController_Alienware(AlienwareController* controller
mode Spectrum;
Spectrum.name = "Spectrum Cycle";
Spectrum.value = ALIENWARE_MODE_SPECTRUM;
Spectrum.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Spectrum.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE;
Spectrum.color_mode = MODE_COLORS_NONE;
Spectrum.speed_min = ALIENWARE_TEMPO_SPECTRUM;
Spectrum.speed_max = ALIENWARE_TEMPO_MAX;
Spectrum.speed = ALIENWARE_TEMPO_SPECTRUM;
Spectrum.speed_min = ALIENWARE_TEMPO_MAX;
Spectrum.speed_max = ALIENWARE_TEMPO_SPECTRUM;
Spectrum.speed = 40;
Spectrum.brightness_min = 100;
Spectrum.brightness_max = 0;
Spectrum.brightness = 0;
@@ -95,11 +97,12 @@ RGBController_Alienware::RGBController_Alienware(AlienwareController* controller
mode Rainbow;
Rainbow.name = "Rainbow Wave";
Rainbow.value = ALIENWARE_MODE_RAINBOW;
Rainbow.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Rainbow.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_MANUAL_SAVE;
Rainbow.color_mode = MODE_COLORS_NONE;
Rainbow.speed_min = ALIENWARE_TEMPO_SPECTRUM;
Rainbow.speed_max = ALIENWARE_TEMPO_MAX;
Rainbow.speed = ALIENWARE_TEMPO_SPECTRUM;
Rainbow.speed_min = ALIENWARE_TEMPO_MAX;
Rainbow.speed_max = ALIENWARE_TEMPO_SPECTRUM;
Rainbow.speed = 40;
Rainbow.direction = MODE_DIRECTION_RIGHT;
Rainbow.brightness_min = 100;
Rainbow.brightness_max = 0;
Rainbow.brightness = 0;
@@ -108,13 +111,13 @@ RGBController_Alienware::RGBController_Alienware(AlienwareController* controller
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = ALIENWARE_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE;
Breathing.color_mode = MODE_COLORS_PER_LED;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.speed_min = ALIENWARE_TEMPO_MIN;
Breathing.speed_max = ALIENWARE_TEMPO_MAX;
Breathing.speed = ALIENWARE_TEMPO_MIN;
Breathing.speed_min = ALIENWARE_TEMPO_MAX;
Breathing.speed_max = 50;
Breathing.speed = 150;
Breathing.brightness_min = 100;
Breathing.brightness_max = 0;
Breathing.brightness = 0;
@@ -224,6 +227,7 @@ void RGBController_Alienware::DeviceUpdateMode()
uint16_t period = 0x07d0;
controller->SetMode(zone_idx, current_mode.value);
controller->SetDirection(zone_idx, current_mode.direction);
switch(current_mode_idx)
{
@@ -235,6 +239,7 @@ void RGBController_Alienware::DeviceUpdateMode()
break;
case ALIENWARE_MODE_PULSE:
period = std::max<uint16_t>(500, (uint16_t)(current_mode.speed * 10));
controller->SetPeriod(zone_idx, period);
controller->SetColor( zone_idx, colors[current_zone.start_idx]);
controller->SetTempo( zone_idx, current_mode.speed);
@@ -242,10 +247,22 @@ void RGBController_Alienware::DeviceUpdateMode()
break;
case ALIENWARE_MODE_MORPH:
controller->SetPeriod(zone_idx, period);
controller->SetColor( zone_idx, current_mode.colors[zone_idx * 2], current_mode.colors[(zone_idx * 2) + 1]);
controller->SetTempo( zone_idx, current_mode.speed);
controller->SetDim( zone_idx, modes[current_mode_idx].brightness);
{
RGBColor c1 = (current_mode.colors.size() >= 1) ? current_mode.colors[0] : 0x0000FF;
RGBColor c2 = (current_mode.colors.size() >= 2) ? current_mode.colors[1] : 0xFF0000;
if(current_mode.colors.size() >= (std::size_t)((zone_idx + 1) * 2))
{
c1 = current_mode.colors[zone_idx * 2];
c2 = current_mode.colors[(zone_idx * 2) + 1];
}
period = std::max<uint16_t>(400, (uint16_t)(current_mode.speed * 10));
controller->SetPeriod(zone_idx, period);
controller->SetColor( zone_idx, c1, c2);
controller->SetTempo( zone_idx, current_mode.speed);
controller->SetDim( zone_idx, modes[current_mode_idx].brightness);
}
break;
case ALIENWARE_MODE_SPECTRUM:
@@ -256,6 +273,7 @@ void RGBController_Alienware::DeviceUpdateMode()
break;
case ALIENWARE_MODE_BREATHING:
period = std::max<uint16_t>(500, (uint16_t)(current_mode.speed * 10));
controller->SetPeriod(zone_idx, period);
controller->SetColor( zone_idx, colors[current_zone.start_idx], 0x0);
controller->SetTempo( zone_idx, current_mode.speed);
@@ -282,3 +300,9 @@ void RGBController_Alienware::DeviceUpdateMode()
current_mode = new_current_mode;
}
}
void RGBController_Alienware::DeviceSaveMode()
{
controller->SaveController();
}
@@ -28,6 +28,7 @@ public:
void DeviceUpdateSingleLED(int led);
void DeviceUpdateMode();
void DeviceSaveMode();
private:
AlienwareController* controller;
@@ -1,7 +1,7 @@
/*---------------------------------------------------------*\
| AlienwareKeyboardControllerDetect.cpp |
| AlienwareAW410KControllerDetect.cpp |
| |
| Detector for Alienware Keyboard |
| Detector for Alienware AW410K Keyboard |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -10,38 +10,17 @@
#include <hidapi.h>
#include "DetectionManager.h"
#include "AlienwareAW410KController.h"
#include "AlienwareAW510KController.h"
#include "RGBController_AlienwareAW410K.h"
#include "RGBController_AlienwareAW510K.h"
/*---------------------------------------------------------*\
| Alienware vendor ID |
\*---------------------------------------------------------*/
#define ALIENWARE_VID 0x04F2
#define ALIENWARE_VID 0x04F2
/*---------------------------------------------------------*\
| Keyboard product IDs |
\*---------------------------------------------------------*/
#define ALIENWARE_AW510K_PID 0x1830
#define ALIENWARE_AW410K_PID 0x1968
DetectedControllers DetectAlienwareAW510KControllers(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
AlienwareAW510KController* controller = new AlienwareAW510KController(dev, info->path, name);
RGBController_AlienwareAW510K* rgb_controller = new RGBController_AlienwareAW510K(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
#define ALIENWARE_AW410K_PID 0x1968
DetectedControllers DetectAlienwareAW410KControllers(hid_device_info* info, const std::string& name)
{
@@ -61,5 +40,4 @@ DetectedControllers DetectAlienwareAW410KControllers(hid_device_info* info, cons
return(detected_controllers);
}
REGISTER_HID_DETECTOR_IPU("Alienware AW510K", DetectAlienwareAW510KControllers, ALIENWARE_VID, ALIENWARE_AW510K_PID, 0x02, 0xFF00, 0x01);
REGISTER_HID_DETECTOR_IPU("Alienware AW410K", DetectAlienwareAW410KControllers, ALIENWARE_VID, ALIENWARE_AW410K_PID, 0x02, 0xFF00, 0x01);
@@ -0,0 +1,43 @@
/*---------------------------------------------------------*\
| AlienwareAW510KControllerDetect.cpp |
| |
| Detector for Alienware AW510K Keyboard |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "DetectionManager.h"
#include "AlienwareAW510KController.h"
#include "RGBController_AlienwareAW510K.h"
/*---------------------------------------------------------*\
| Alienware vendor ID |
\*---------------------------------------------------------*/
#define ALIENWARE_VID 0x04F2
/*---------------------------------------------------------*\
| Keyboard product IDs |
\*---------------------------------------------------------*/
#define ALIENWARE_AW510K_PID 0x1830
DetectedControllers DetectAlienwareAW510KControllers(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
AlienwareAW510KController* controller = new AlienwareAW510KController(dev, info->path, name);
RGBController_AlienwareAW510K* rgb_controller = new RGBController_AlienwareAW510K(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_IPU("Alienware AW510K", DetectAlienwareAW510KControllers, ALIENWARE_VID, ALIENWARE_AW510K_PID, 0x02, 0xFF00, 0x01);
@@ -40,11 +40,11 @@ std::vector<unsigned char> AlienwareAW3423DWFController::GetReportResponse()
void AlienwareAW3423DWFController::PerformLogin()
{
unsigned char init_packet[64] =
{
0x40, 0xE1, 0x01
};
SendControlPacket(init_packet, 4);
unsigned char init_packet[192] = {0x00};
init_packet[0] = 0x40;
init_packet[1] = 0xE1;
init_packet[2] = 0x01;
SendControlPacket(init_packet, 192);
std::vector<unsigned char> response = GetReportResponse();
@@ -0,0 +1,47 @@
/*---------------------------------------------------------*\
| AlienwareAW3423DWFControllerDetect.cpp |
| |
| Detector for Alienware AW3423DWF monitor |
| |
| Ferréol DUBOIS COLI (Fefe_du_973) 23 Jan 2025 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "AlienwareAW3423DWFController.h"
#include "DetectionManager.h"
#include "RGBController_AlienwareAW3423DWF.h"
/*---------------------------------------------------------*\
| Alienware Vendor ID |
\*---------------------------------------------------------*/
#define ALIENWARE_VID 0x187C
/*---------------------------------------------------------*\
| Alienware Vendor ID |
\*---------------------------------------------------------*/
#define ALIENWARE_AW3423DWF_PID 0x100E
#define ALIENWARE_USAGE_PAGE 0xFFDA
#define ALIENWARE_USAGE 0x00DA
DetectedControllers DetectAlienwareAW3423DWFControllers(hid_device_info* info, const std::string&)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
AlienwareAW3423DWFController* controller = new AlienwareAW3423DWFController(dev, info->path);
RGBController_AlienwareAW3423DWF* rgb_controller = new RGBController_AlienwareAW3423DWF(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR("Alienware AW3423DWF", DetectAlienwareAW3423DWFControllers, ALIENWARE_VID, ALIENWARE_AW3423DWF_PID);
@@ -10,10 +10,8 @@
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "AlienwareAW3423DWFController.h"
#include "AlienwareMonitorController.h"
#include "DetectionManager.h"
#include "RGBController_AlienwareAW3423DWF.h"
#include "RGBController_AlienwareMonitor.h"
/*---------------------------------------------------------*\
@@ -24,29 +22,10 @@
/*---------------------------------------------------------*\
| Alienware Vendor ID |
\*---------------------------------------------------------*/
#define ALIENWARE_AW3423DWF_PID 0x100E
#define ALIENWARE_AW3225QF_PID 0x1013
#define ALIENWARE_USAGE_PAGE 0xFFDA
#define ALIENWARE_USAGE 0x00DA
DetectedControllers DetectAlienwareAW3423DWFControllers(hid_device_info* info, const std::string&)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
AlienwareAW3423DWFController* controller = new AlienwareAW3423DWFController(dev, info->path);
RGBController_AlienwareAW3423DWF* rgb_controller = new RGBController_AlienwareAW3423DWF(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
DetectedControllers DetectAlienwareMonitorControllers(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
@@ -65,5 +44,4 @@ DetectedControllers DetectAlienwareMonitorControllers(hid_device_info* info, con
return(detected_controllers);
}
REGISTER_HID_DETECTOR("Alienware AW3423DWF", DetectAlienwareAW3423DWFControllers, ALIENWARE_VID, ALIENWARE_AW3423DWF_PID);
REGISTER_HID_DETECTOR("Alienware AW3225QF", DetectAlienwareMonitorControllers, ALIENWARE_VID, ALIENWARE_AW3225QF_PID);
@@ -44,8 +44,4 @@ DetectedControllers DetectArcticControllers()
}
REGISTER_DETECTOR("Arctic RGB controller", DetectArcticControllers);
/*---------------------------------------------------------------------------------------------------------*\
| Entries for dynamic UDEV rules |
| |
| DUMMY_DEVICE_DETECTOR("Arctic RGB controller", DetectArcticControllers, 0x1A86, 0x7523 ) |
\*---------------------------------------------------------------------------------------------------------*/
REGISTER_CUSTOM_UDEV_RULE(arctic, "Arctic RGB controller", "SUBSYSTEMS==\"serial|hidraw\", ATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"7523\", TAG+=\"uaccess\", TAG+=\"Arctic_RGB_controller\"");
@@ -4,18 +4,15 @@
| Detector for ASUS ROG Aura Core |
| |
| Adam Honse (CalcProgrammer1) 13 Apr 2020 |
| Chris M (Dr_No) 28 Jul 2022 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "DetectionManager.h"
#include "AsusAuraCoreController.h"
#include "RGBController.h"
#include "RGBController_AsusAuraCore.h"
#include "RGBController_AsusAuraCoreLaptop.h"
#include <hidapi.h>
#include "AsusAuraCoreController.h"
#include "DetectionManager.h"
#include "RGBController_AsusAuraCore.h"
#define AURA_CORE_VID 0x0B05
@@ -44,27 +41,6 @@ DetectedControllers DetectAsusAuraCoreControllers(hid_device_info* info, const s
return(detected_controllers);
}
DetectedControllers DetectAsusAuraCoreLaptopControllers(hid_device_info* info, const std::string& /*name*/)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
AsusAuraCoreLaptopController* controller = new AsusAuraCoreLaptopController(dev, info->path);
RGBController_AsusAuraCoreLaptop* rgb_controller = new RGBController_AsusAuraCoreLaptop(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR ("ASUS Aura Core", DetectAsusAuraCoreControllers, AURA_CORE_VID, 0x1854);
REGISTER_HID_DETECTOR ("ASUS Aura Core", DetectAsusAuraCoreControllers, AURA_CORE_VID, 0x1866);
REGISTER_HID_DETECTOR ("ASUS Aura Core", DetectAsusAuraCoreControllers, AURA_CORE_VID, 0x1869);
REGISTER_HID_DETECTOR_PU("ASUS ROG Strix SCAR 15", DetectAsusAuraCoreLaptopControllers, AURA_CORE_VID, AURA_STRIX_SCAR_15_PID, 0xFF31, 0x79);
REGISTER_HID_DETECTOR_PU("ASUS ROG Strix SCAR 17", DetectAsusAuraCoreLaptopControllers, AURA_CORE_VID, 0x1866, 0xFF31, 0x79);
@@ -0,0 +1,38 @@
/*---------------------------------------------------------*\
| AsusAuraCoreLaptopControllerDetect.cpp |
| |
| Detector for ASUS ROG Aura Core Laptop |
| |
| Chris M (Dr_No) 28 Jul 2022 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "AsusAuraCoreLaptopController.h"
#include "DetectionManager.h"
#include "RGBController_AsusAuraCoreLaptop.h"
#define AURA_CORE_VID 0x0B05
DetectedControllers DetectAsusAuraCoreLaptopControllers(hid_device_info* info, const std::string& /*name*/)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
AsusAuraCoreLaptopController* controller = new AsusAuraCoreLaptopController(dev, info->path);
RGBController_AsusAuraCoreLaptop* rgb_controller = new RGBController_AsusAuraCoreLaptop(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_PU("ASUS ROG Strix SCAR 15", DetectAsusAuraCoreLaptopControllers, AURA_CORE_VID, AURA_STRIX_SCAR_15_PID, 0xFF31, 0x79);
REGISTER_HID_DETECTOR_PU("ASUS ROG Strix SCAR 17", DetectAsusAuraCoreLaptopControllers, AURA_CORE_VID, 0x1866, 0xFF31, 0x79);
@@ -940,6 +940,25 @@ static const aura_core_laptop_device g814jv_device =
}
};
/*-------------------------------------------------------------*\
| ASUS ROG Strix G18 (G814JIR) |
| |
| G814JIR uses the same Aura Core keyboard and lightbar |
| layout as the G814JV G18 profile. |
\*-------------------------------------------------------------*/
static const aura_core_laptop_device g814jir_device =
{
"G814JIR",
{
&g814rw_keyboard_zone,
&g814jv_lightbar_zone,
nullptr,
nullptr,
nullptr,
nullptr
}
};
/*-------------------------------------------------------------------------*\
| DEVICE MASTER LIST |
\*-------------------------------------------------------------------------*/
@@ -954,6 +973,7 @@ const aura_core_laptop_device* aura_core_laptop_device_list_data[] =
&g713rw_device,
&g733qr_device,
&g814jv_device,
&g814jir_device,
};
const unsigned int AURA_CORE_LAPTOP_DEVICE_COUNT = (sizeof(aura_core_laptop_device_list_data) / sizeof(aura_core_laptop_device_list_data[ 0 ]));
@@ -428,7 +428,6 @@ void RGBController_AuraTUFKeyboard::SetupZones()
break;
case AURA_TUF_K3_GAMING_PID:
case AURA_TUF_K7_GAMING_PID:
case AURA_TUF_K3_GENII_MIKU_EDITION_PID:
keyboard_ptr = &AsusTUFK7Layouts;
break;
case AURA_ROG_STRIX_SCOPE_PID:
@@ -485,6 +484,7 @@ void RGBController_AuraTUFKeyboard::SetupZones()
keyboard_ptr = &AsusTufK1Layouts;
break;
case AURA_TUF_K3_GAMING_GEN_II_PID:
case AURA_TUF_K3_GENII_MIKU_EDITION_PID:
keyboard_ptr = &AsusTUFK3GamingGen2Layouts;
break;
default:
@@ -417,7 +417,7 @@ REGISTER_HID_DETECTOR ("ASUS Aura Addressable", DetectAs
REGISTER_HID_DETECTOR ("ASUS Aura Addressable", DetectAsusAuraUSBAddressable, AURA_USB_VID, AURA_ADDRESSABLE_4_PID);
REGISTER_HID_DETECTOR ("ASUS Aura Motherboard", DetectAsusAuraUSBMotherboards, AURA_USB_VID, AURA_MOTHERBOARD_1_PID);
REGISTER_HID_DETECTOR ("ASUS Aura Motherboard", DetectAsusAuraUSBMotherboards, AURA_USB_VID, AURA_MOTHERBOARD_2_PID);
REGISTER_HID_DETECTOR ("ASUS Aura Motherboard", DetectAsusAuraUSBMotherboards, AURA_USB_VID, AURA_MOTHERBOARD_3_PID);
REGISTER_HID_DETECTOR_PU("ASUS Aura Motherboard", DetectAsusAuraUSBMotherboards, AURA_USB_VID, AURA_MOTHERBOARD_3_PID, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR ("ASUS Aura Motherboard", DetectAsusAuraUSBMotherboards, AURA_USB_VID, AURA_MOTHERBOARD_4_PID);
REGISTER_HID_DETECTOR ("ASUS Aura Motherboard", DetectAsusAuraUSBMotherboards, AURA_USB_VID, AURA_MOTHERBOARD_5_PID);
@@ -22,8 +22,12 @@
| Product ID |
\*---------------------------------------------------------*/
#define ASUS_ROG_STRIX_XG27AQDMG_PID 0x1BA3
#define ASUS_ROG_SWIFT_XG27UCG_PID 0x1BB4
#define ASUS_ROG_STRIX_XG27ACDNG_PID 0x1BC9
#define ASUS_ROG_STRIX_XG27UCG_PID 0x1BB4
#define ASUS_ROG_SWIFT_PG32UCDM_PID 0x1B2B
#define ASUS_ROG_SWIFT_PG32UCDMR_PID 0x1C9B
#define ASUS_ROG_SWIFT_PG32UCDP_PID 0x1BCA
#define ASUS_ROG_AURA_MONITOR_LIGHT_BAR_PID 0x1AC8
DetectedControllers DetectAsusMonitorControllers(hid_device_info* info, const std::string& name)
{
@@ -44,5 +48,13 @@ DetectedControllers DetectAsusMonitorControllers(hid_device_info* info, const st
}
REGISTER_HID_DETECTOR_IPU("Asus ROG STRIX XG27AQDMG", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_STRIX_XG27AQDMG_PID, 1, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_IPU("Asus ROG STRIX XG27UCG", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_SWIFT_XG27UCG_PID, 1, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_IPU("Asus ROG STRIX PG32UCDM", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_SWIFT_PG32UCDM_PID, 1, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_IPU("Asus ROG STRIX XG27ACDNG", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_STRIX_XG27ACDNG_PID, 1, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_IPU("Asus ROG STRIX XG27UCG", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_STRIX_XG27UCG_PID, 1, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_IPU("Asus ROG SWIFT PG32UCDM", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_SWIFT_PG32UCDM_PID, 1, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_IPU("Asus ROG SWIFT PG32UCDMR", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_SWIFT_PG32UCDMR_PID, 1, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_IPU("Asus ROG SWIFT PG32UCDP", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_SWIFT_PG32UCDP_PID, 1, 0xFF72, 0x00A1);
/*---------------------------------------------------------*\
| ALB01 exposes the Aura HID collection on interface 2. |
\*---------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Asus ROG Aura Monitor Light Bar", DetectAsusMonitorControllers, ASUS_VID, ASUS_ROG_AURA_MONITOR_LIGHT_BAR_PID, 2, 0xFF72, 0x00A1);
@@ -36,3 +36,5 @@ DetectedControllers DetectAsusTUFLaptopLinuxControllers()
}
REGISTER_DETECTOR("ASUS TUF Laptop", DetectAsusTUFLaptopLinuxControllers);
REGISTER_CUSTOM_UDEV_RULE(asus_wmi, "ASUS TUF Laptops (asus-wmi)", "ACTION==\"add\", SUBSYSTEM==\"leds\", KERNEL==\"asus::kbd_backlight\", RUN+=\"/usr/bin/env chmod a+w /sys%p/kbd_rgb_mode\"\nACTION==\"add\", SUBSYSTEM==\"leds\", KERNEL==\"asus::kbd_backlight\", RUN+=\"/usr/bin/env chmod a+w /sys%p/brightness\"");
@@ -40,8 +40,4 @@ DetectedControllers DetectBlinkyTapeControllers()
}
REGISTER_DETECTOR("BlinkyTape", DetectBlinkyTapeControllers);
/*---------------------------------------------------------------------------------------------------------*\
| Entries for dynamic UDEV rules |
| |
| DUMMY_DEVICE_DETECTOR("BlinkyTape", DetectBlinkyTapeControllers, 0x1D50, 0x605E ) |
\*---------------------------------------------------------------------------------------------------------*/
REGISTER_CUSTOM_UDEV_RULE(blinky_tape, "BlinkyTape", "SUBSYSTEMS==\"serial|hidraw\", ATTRS{idVendor}==\"1d50\", ATTRS{idProduct}==\"605e\", TAG+=\"uaccess\", TAG+=\"BlinkyTape\"");
@@ -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 (kyle@cascade.family) 16 Jan 2026 |
| Valentin Lobstein (balgogan@protonmail.com) 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 + (unsigned char)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 (kyle@cascade.family) 16 Jan 2026 |
| Valentin Lobstein (balgogan@protonmail.com) 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 (kyle@cascade.family) 16 Jan 2026 |
| Valentin Lobstein (balgogan@protonmail.com) 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 (kyle@cascade.family) 21 Jan 2026 |
| Valentin Lobstein (balgogan@protonmail.com) 27 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -16,71 +18,79 @@
| |
| Based on KEYBOARD_SIZE_TKL with numpad added and |
| navigation cluster adjusted to match Clevo's layout. |
| |
| Hardware LED indices (value field): |
| - Row 5 (F-keys): 105-124 |
| - Row 4 (numbers): 84-102 |
| - Row 3 (QWERTY): 63-81 |
| - Row 2 (home): 42-59 |
| - Row 1 (Z row): 22-39 |
| - Row 0 (modifiers): 0-18 |
\*---------------------------------------------------------*/
/*---------------------------------------------------------*\
| LED values in TKL order (fn_row + main + extras) |
| LED values, in physical (row, then column) order. |
| |
| This must match the order KeyboardLayoutManager assigns |
| default_values against: the structural keymap sorted by |
| (row, col) across main+fn_row+extras combined, NOT the |
| fn_row-then-main-then-extras order the zone tables below |
| are declared in. Getting this wrong silently misassigns |
| almost every key from Print Screen onward, not just a few.|
| |
| Values follow the key order in KeyboardLayoutManager.cpp. |
| For ANSI-only keys (not present on this ISO keyboard), |
| use 0 as a placeholder - they won't be displayed. |
| Numpad values are added via edit_keys. |
\*---------------------------------------------------------*/
static const std::vector<unsigned int> clevo_tkl_values =
{
/*---------------------------------------------------------*\
| Function row (keyboard_zone_fn_row) |
\*---------------------------------------------------------*/
/*-----------------------------------------------------*\
| Row 0: fn_row + Print Screen/Scroll Lock/Pause |
\*-----------------------------------------------------*/
105, // Escape
106, 107, 108, 109, // F1-F4
110, 111, 112, 113, // F5-F8
114, 115, 116, 117, // F9-F12
118, // Print Screen
0, // Scroll Lock (removed via edit_keys)
0, // Pause (removed via edit_keys)
/*---------------------------------------------------------*\
| Main block - Row 1 (keyboard_zone_main) |
\*---------------------------------------------------------*/
/*-----------------------------------------------------*\
| Row 1: numbers row + Insert/Home/Page Up |
\*-----------------------------------------------------*/
84, // Back tick
85, 86, 87, 88, 89, 90, 91, 92, 93, 94, // 1-0
95, 96, // Minus, Equals
98, // Backspace
119, // Insert
121, // Home
122, // Page Up
/*---------------------------------------------------------*\
| Main block - Row 2 |
\*---------------------------------------------------------*/
/*-----------------------------------------------------*\
| Row 2: Tab/QWERTY row + Delete/End/PageDown |
\*-----------------------------------------------------*/
63, // Tab
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, // Q-P
75, 76, // [ ]
0, // ANSI backslash (not on ISO)
120, // Delete
124, // End
123, // Page Down
/*---------------------------------------------------------*\
| Main block - Row 3 |
\*---------------------------------------------------------*/
/*-----------------------------------------------------*\
| Row 3: Caps Lock/home row/Enter |
\*-----------------------------------------------------*/
42, // Caps Lock
44, 45, 46, 47, 48, 49, 50, 51, 52, // A-L
53, 54, // ; '
55, // ISO # (POUND)
77, // Enter (ANSI/ISO share same LED)
56, // Enter
/*---------------------------------------------------------*\
| Main block - Row 4 |
\*---------------------------------------------------------*/
22, // Left Shift
23, // ISO backslash
/*-----------------------------------------------------*\
| Row 4: Left Shift/ISO backslash/Z row + Up |
\*-----------------------------------------------------*/
21, // Left Shift
77, // ISO backslash
24, 25, 26, 27, 28, 29, 30, 31, 32, // Z-. (9 keys)
33, // /
35, // Right Shift
14, // Up
/*---------------------------------------------------------*\
| Main block - Row 5 |
\*---------------------------------------------------------*/
/*-----------------------------------------------------*\
| Row 5: bottom modifier row + Left/Down/Right |
\*-----------------------------------------------------*/
0, // Left Ctrl
3, // Left Win
4, // Left Alt
@@ -89,29 +99,53 @@ static const std::vector<unsigned int> clevo_tkl_values =
0, // Right Fn (removed via edit_keys)
0, // Menu (removed via edit_keys)
12, // Right Ctrl
/*---------------------------------------------------------*\
| Extras - Navigation cluster (keyboard_zone_extras) |
\*---------------------------------------------------------*/
118, // Print Screen
0, // Scroll Lock (removed via edit_keys)
0, // Pause (removed via edit_keys)
119, // Insert
121, // Home
123, // Page Up
120, // Delete
122, // End
124, // Page Down
/*---------------------------------------------------------*\
| Extras - Arrow keys |
\*---------------------------------------------------------*/
14, // Up
13, // Left
18, // Down
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 +156,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 (kyle@cascade.family) 21 Jan 2026 |
| Valentin Lobstein (balgogan@protonmail.com) 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 (kyle@cascade.family) 16 Jan 2026 |
| Valentin Lobstein (balgogan@protonmail.com) 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 (kyle@cascade.family) 16 Jan 2026 |
| Valentin Lobstein (balgogan@protonmail.com) 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;
@@ -45,7 +45,8 @@ void ColorfulGPUController::SetDirect(RGBColor color)
uint8_t g = RGBGetGValue(color);
uint8_t b = RGBGetBValue(color);
if(this->bus->info.pci_subsystem_device == COLORFUL_IGAME_RTX_4070_VULCAN_OCV)
if(this->bus->info.pci_subsystem_device == COLORFUL_IGAME_RTX_4070_VULCAN_OCV
|| this->bus->info.pci_subsystem_device == COLORFUL_IGAME_RTX_4090_NEPTUNE_OCV)
{
uint8_t data_pkt[COLORFUL_PACKET_LENGTH_V2] = { 0xAA, 0xEF, 0x01, 0x04, 0x88, 0x26 };
for(int i=6; i < COLORFUL_PACKET_LENGTH_V2 -2; i = i + 3)
@@ -51,6 +51,7 @@ DetectedControllers DetectColorfulGPUControllers(i2c_smbus_interface* bus, uint8
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Advanced OC 12G L-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060_LHR_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3060_ADVANCED_OC_12G_LV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Ultra W OC 12G L-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060_LHR_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3060_ULTRAW_OC_12G, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Ultra W OC 12G L-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060_GA106_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3060_ULTRAW_OC_12G, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Ultra W OC 12G L-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060_GA106_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3060_ULTRAW_OC_12G_2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Ultra W OC 12G L-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060_LHR_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3060_ULTRAW_OC_12G_2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Ti Ultra W OC LHR-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060TI_LHR_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3060_ULTRAW_OC_12G, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Ti Ultra W OC LHR-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060TI_LHR_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3060_ULTRAW_OC_12G_2, 0x61);
@@ -71,9 +72,11 @@ 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);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4090 Neptune OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4090_NEPTUNE_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4090 Advanced OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4090_ADVANCED_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4090 Advanced OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4090_ADVANCED_OCV2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ultra W OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060_ULTRAW_OC, 0x61);
@@ -81,4 +84,5 @@ REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ultra W OC", Detec
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ti Ultra W DUO OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060TI_ULTRAW_DUO_OC, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ti Ultra W DUO OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060TI_ULTRAW_DUO_OC_2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5070 Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5070_ULTRAW_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5070 Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5070_ULTRAW_OCV2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5070 Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5070_ULTRAW_OCV2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5070 Ti Ultra W OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5070TI_ULTRAW_OC, 0x61);
@@ -13,11 +13,6 @@
#include "CMSmallARGBController.h"
#include "StringUtils.h"
cm_small_argb_headers cm_small_argb_header_data[1] =
{
{ "CM Small ARGB", 0x01, true, 12 }
};
CMSmallARGBController::CMSmallARGBController(hid_device* dev_handle, char *_path)
{
dev = dev_handle;
@@ -92,6 +87,24 @@ std::string CMSmallARGBController::GetSerial()
return(StringUtils::wstring_to_string(serial_string));
}
std::string CMSmallARGBController::GetVersion()
{
/*-----------------------------------------------------*\
| This device uses the serial value to determine the |
| version. It does not report a proper unique serial. |
\*-----------------------------------------------------*/
std::string serial_string = GetSerial();
if(serial_string == CM_SMALL_ARGB_FW0012)
{
return("0012");
}
else
{
return("Unsupported");
}
}
std::string CMSmallARGBController::GetLocation()
{
return("HID: " + location);
@@ -122,6 +135,11 @@ unsigned char CMSmallARGBController::GetLedSpeed()
return(current_speed);
}
unsigned char CMSmallARGBController::GetBrightness()
{
return(current_brightness);
}
bool CMSmallARGBController::GetRandomColours()
{
return(bool_random);
@@ -217,9 +235,9 @@ void CMSmallARGBController::SendUpdate()
hid_write(dev, buffer, buffer_size);
buffer[CM_SMALL_ARGB_COMMAND_BYTE] = 0x0b;
buffer[CM_SMALL_ARGB_FUNCTION_BYTE] = (false) ? 0x01 : 0x02; //This controls custom mode TODO
buffer[CM_SMALL_ARGB_ZONE_BYTE] = cm_small_argb_header_data[0].header;
buffer[CM_SMALL_ARGB_COMMAND_BYTE] = 0x0B;
buffer[CM_SMALL_ARGB_FUNCTION_BYTE] = 0x02;
buffer[CM_SMALL_ARGB_ZONE_BYTE] = 0x01;
buffer[CM_SMALL_ARGB_MODE_BYTE] = current_mode;
buffer[CM_SMALL_ARGB_SPEED_BYTE] = current_speed;
buffer[CM_SMALL_ARGB_COLOUR_INDEX_BYTE] = (bool_random) ? 0x00 : 0x10; //This looks to still be the colour index and controls random colours
@@ -39,16 +39,6 @@ enum
CM_SMALL_ARGB_BLUE_BYTE = 11,
};
struct cm_small_argb_headers
{
const char* name;
unsigned char header;
bool digital;
unsigned int count;
};
extern cm_small_argb_headers cm_small_argb_header_data[1];
enum
{
CM_SMALL_ARGB_MODE_SPECTRUM = 0x01, //Spectrum Mode
@@ -71,6 +61,8 @@ enum
CM_SMALL_ARGB_SPEED_FASTEST = 0x04, // Fastest speed
};
#define CM_SMALL_ARGB_FW0012 std::string("A202104052336")
class CMSmallARGBController
{
public:
@@ -80,12 +72,14 @@ public:
std::string GetDeviceName();
std::string GetSerial();
std::string GetLocation();
std::string GetVersion();
unsigned char GetMode();
unsigned char GetLedRed();
unsigned char GetLedGreen();
unsigned char GetLedBlue();
unsigned char GetLedSpeed();
unsigned char GetBrightness();
bool GetRandomColours();
void SetLedCount(int zone, int led_count);
@@ -27,22 +27,68 @@
RGBController_CMSmallARGBController::RGBController_CMSmallARGBController(CMSmallARGBController* controller_ptr)
{
controller = controller_ptr;
unsigned char speed = controller->GetLedSpeed();
name = cm_small_argb_header_data[0].name;
name = controller->GetDeviceName();
vendor = "Cooler Master";
type = DEVICE_TYPE_LEDSTRIP;
description = controller->GetDeviceName();
version = "2.0 for FW0012";
description = "Cooler Master Small ARGB Controller Device";
version = controller->GetVersion();
serial = controller->GetSerial();
location = controller->GetLocation();
if(serial >= CM_SMALL_ARGB_FW0012)
SetupModes();
SetupZones();
/*-----------------------------------------------------*\
| Initialize the active mode to the device's current |
| mode |
\*-----------------------------------------------------*/
int device_mode = controller->GetMode();
for(std::size_t mode_idx = 0; mode_idx < modes.size(); mode_idx++)
{
if(modes[mode_idx].value == device_mode)
{
active_mode = (int)mode_idx;
if((modes[mode_idx].flags & MODE_FLAG_HAS_MODE_SPECIFIC_COLOR) && (modes[mode_idx].colors.size() > 0))
{
modes[mode_idx].colors[0] = ToRGBColor(controller->GetLedRed(), controller->GetLedGreen(), controller->GetLedBlue());
}
if(modes[mode_idx].flags & MODE_FLAG_HAS_SPEED)
{
modes[mode_idx].speed = controller->GetLedSpeed();
}
if(modes[mode_idx].flags & MODE_FLAG_HAS_BRIGHTNESS)
{
modes[mode_idx].brightness = controller->GetBrightness();
}
if(modes[mode_idx].flags & MODE_FLAG_HAS_RANDOM_COLOR)
{
modes[mode_idx].color_mode = controller->GetRandomColours() ? MODE_COLORS_RANDOM : MODE_COLORS_MODE_SPECIFIC;
}
break;
}
}
}
void RGBController_CMSmallARGBController::SetupModes()
{
/*-----------------------------------------------------*\
| The Small ARGB only supports Direct mode from FW0012 |
| onwards, older firmware does not accept direct mode |
| packets |
\*-----------------------------------------------------*/
//if(serial >= CM_SMALL_ARGB_FW0012)
{
mode Direct;
Direct.name = "Direct";
Direct.value = CM_SMALL_ARGB_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_REQUIRES_ENTIRE_DEVICE;
Direct.brightness_min = 0;
Direct.brightness_max = CM_SMALL_ARGB_BRIGHTNESS_MAX;
Direct.brightness = CM_SMALL_ARGB_BRIGHTNESS_MAX;
@@ -69,7 +115,7 @@ RGBController_CMSmallARGBController::RGBController_CMSmallARGBController(CMSmall
Reload.speed_min = CM_SMALL_ARGB_SPEED_SLOWEST;
Reload.speed_max = CM_SMALL_ARGB_SPEED_FASTEST;
Reload.color_mode = MODE_COLORS_RANDOM;
Reload.speed = speed;
Reload.speed = CM_SMALL_ARGB_SPEED_NORMAL;
modes.push_back(Reload);
mode Recoil;
@@ -85,7 +131,7 @@ RGBController_CMSmallARGBController::RGBController_CMSmallARGBController(CMSmall
Recoil.speed_min = CM_SMALL_ARGB_SPEED_SLOWEST;
Recoil.speed_max = CM_SMALL_ARGB_SPEED_FASTEST;
Recoil.color_mode = MODE_COLORS_RANDOM;
Recoil.speed = speed;
Recoil.speed = CM_SMALL_ARGB_SPEED_NORMAL;
modes.push_back(Recoil);
mode Breathing;
@@ -101,7 +147,7 @@ RGBController_CMSmallARGBController::RGBController_CMSmallARGBController(CMSmall
Breathing.speed_min = CM_SMALL_ARGB_SPEED_SLOWEST;
Breathing.speed_max = CM_SMALL_ARGB_SPEED_FASTEST;
Breathing.color_mode = MODE_COLORS_RANDOM;
Breathing.speed = speed;
Breathing.speed = CM_SMALL_ARGB_SPEED_NORMAL;
modes.push_back(Breathing);
mode Refill;
@@ -117,7 +163,7 @@ RGBController_CMSmallARGBController::RGBController_CMSmallARGBController(CMSmall
Refill.speed_min = CM_SMALL_ARGB_SPEED_SLOWEST;
Refill.speed_max = CM_SMALL_ARGB_SPEED_FASTEST;
Refill.color_mode = MODE_COLORS_RANDOM;
Refill.speed = speed;
Refill.speed = CM_SMALL_ARGB_SPEED_NORMAL;
modes.push_back(Refill);
mode Demo;
@@ -130,7 +176,7 @@ RGBController_CMSmallARGBController::RGBController_CMSmallARGBController(CMSmall
Demo.speed_min = CM_SMALL_ARGB_SPEED_SLOWEST;
Demo.speed_max = CM_SMALL_ARGB_SPEED_FASTEST;
Demo.color_mode = MODE_COLORS_NONE;
Demo.speed = speed;
Demo.speed = CM_SMALL_ARGB_SPEED_NORMAL;
modes.push_back(Demo);
mode Spectrum;
@@ -143,39 +189,19 @@ RGBController_CMSmallARGBController::RGBController_CMSmallARGBController(CMSmall
Spectrum.speed_min = CM_SMALL_ARGB_SPEED_SLOWEST;
Spectrum.speed_max = CM_SMALL_ARGB_SPEED_FASTEST;
Spectrum.color_mode = MODE_COLORS_NONE;
Spectrum.speed = speed;
Spectrum.speed = CM_SMALL_ARGB_SPEED_NORMAL;
modes.push_back(Spectrum);
/*-----------------------------------------------------*\
| Define Passthrough mode as requiring the entire |
| device, it hands control over to the motherboard |
\*-----------------------------------------------------*/
mode PassThru;
PassThru.name = "Pass Thru";
PassThru.value = CM_SMALL_ARGB_MODE_PASSTHRU;
PassThru.flags = MODE_FLAG_REQUIRES_ENTIRE_DEVICE;
PassThru.color_mode = MODE_COLORS_NONE;
modes.push_back(PassThru);
SetupZones();
int temp_mode = controller->GetMode();
for(int mode_idx = 0; mode_idx < (int)modes.size() ; mode_idx++)
{
if(temp_mode == modes[mode_idx].value)
{
active_mode = mode_idx;
break;
}
}
if (modes[active_mode].flags & MODE_FLAG_HAS_MODE_SPECIFIC_COLOR)
{
modes[active_mode].colors[0] = ToRGBColor(controller->GetLedRed(), controller->GetLedGreen(), controller->GetLedBlue());
}
modes[active_mode].color_mode = (controller->GetRandomColours()) ? MODE_COLORS_RANDOM : MODE_COLORS_MODE_SPECIFIC;
if (modes[active_mode].flags & MODE_FLAG_HAS_SPEED)
{
modes[active_mode].speed = controller->GetLedSpeed();
}
}
RGBController_CMSmallARGBController::~RGBController_CMSmallARGBController()
@@ -248,7 +274,9 @@ void RGBController_CMSmallARGBController::SetupZones()
{
led new_led;
new_led.name = zones[zone_idx].name;
new_led.name.append(", LED " + std::to_string(lp_idx));
new_led.name.append(", LED ");
new_led.name.append(std::to_string(lp_idx + 1));
new_led.value = (unsigned int)zone_idx;
leds.push_back(new_led);
}
@@ -261,7 +289,7 @@ void RGBController_CMSmallARGBController::DeviceConfigureZone(int zone_idx)
{
if((size_t)zone_idx < zones.size())
{
controller->SetLedCount(cm_small_argb_header_data[zone_idx].header, zones[zone_idx].leds_count);
controller->SetLedCount(1, zones[zone_idx].leds_count);
SetupZones();
}
@@ -277,37 +305,79 @@ void RGBController_CMSmallARGBController::DeviceUpdateLEDs()
void RGBController_CMSmallARGBController::DeviceUpdateZoneLEDs(int zone)
{
if(serial >= CM_SMALL_ARGB_FW0012)
/*-----------------------------------------------------*\
| Only send direct packets while Direct mode is active. |
| Direct mode is only offered on FW0012 and newer, so |
| older firmware will never send direct packets here |
\*-----------------------------------------------------*/
if(modes[active_mode].value != CM_SMALL_ARGB_MODE_DIRECT)
{
controller->SetLedsDirect( zones[zone].colors, zones[zone].leds_count );
return;
}
controller->SetLedsDirect(zones[zone].colors, zones[zone].leds_count);
}
void RGBController_CMSmallARGBController::DeviceUpdateSingleLED(int led)
{
DeviceUpdateZoneLEDs(led);
/*-----------------------------------------------------*\
| Map the LED back to its zone and update that zone |
\*-----------------------------------------------------*/
unsigned int zone_idx = leds[led].value;
UpdateZoneLEDs(zone_idx);
}
void RGBController_CMSmallARGBController::SetCustomMode()
{
/*-------------------------------------------------*\
| The small ARGB may not support "Direct" mode |
| in which case this will select "Pass Thru" |
\*-------------------------------------------------*/
/*-----------------------------------------------------*\
| Direct mode is only supported on FW0012 and newer |
\*-----------------------------------------------------*/
if(serial >= CM_SMALL_ARGB_FW0012)
{
active_mode = 0;
/*-------------------------------------------------*\
| Search the mode list for Direct mode |
\*-------------------------------------------------*/
for(std::size_t mode_idx = 0; mode_idx < modes.size(); mode_idx++)
{
if(modes[mode_idx].name == "Direct")
{
SetActiveMode((int)mode_idx);
break;
}
}
}
else
{
active_mode = 7;
/*-------------------------------------------------*\
| Direct mode is unsupported by the firmware, |
| fall back to Pass Thru |
\*-------------------------------------------------*/
for(std::size_t mode_idx = 0; mode_idx < modes.size(); mode_idx++)
{
if(modes[mode_idx].value == CM_SMALL_ARGB_MODE_PASSTHRU)
{
SetActiveMode((int)mode_idx);
break;
}
}
}
}
void RGBController_CMSmallARGBController::DeviceUpdateMode()
{
bool random_colours = (modes[active_mode].color_mode == MODE_COLORS_RANDOM);
RGBColor colour = (modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC) ? modes[active_mode].colors[0] : 0;
/*-----------------------------------------------------*\
| Set up mode parameters |
\*-----------------------------------------------------*/
bool random_colours = (modes[active_mode].color_mode == MODE_COLORS_RANDOM);
RGBColor colour = 0;
unsigned int speed = modes[active_mode].speed;
unsigned int brightness = modes[active_mode].brightness;
controller->SetMode( modes[active_mode].value, modes[active_mode].speed, modes[active_mode].brightness, colour, random_colours);
if(modes[active_mode].colors.size() > 0)
{
colour = modes[active_mode].colors[0];
}
controller->SetMode(modes[active_mode].value, speed, brightness, colour, random_colours);
}
@@ -18,7 +18,6 @@
#define CM_SMALL_ARGB_MIN_LEDS 4
#define CM_SMALL_ARGB_MAX_LEDS 48
#define CM_SMALL_ARGB_BRIGHTNESS_MAX 0xFF
#define CM_SMALL_ARGB_FW0012 "A202104052336"
class RGBController_CMSmallARGBController : public RGBController
{
@@ -26,7 +25,9 @@ public:
RGBController_CMSmallARGBController(CMSmallARGBController* controller_ptr);
~RGBController_CMSmallARGBController();
void SetupModes();
void SetupZones();
void DeviceConfigureZone(int zone_idx);
void DeviceUpdateLEDs();
@@ -35,9 +36,7 @@ public:
void SetCustomMode();
void DeviceUpdateMode();
private:
void Init_Controller();
int GetDeviceMode();
private:
CMSmallARGBController* controller;
};
@@ -327,9 +327,18 @@ DetectedControllers DetectCoolerMasterSmallARGB(hid_device_info* info, const std
if(dev)
{
CMSmallARGBController* controller = new CMSmallARGBController(dev, info->path);
RGBController_CMSmallARGBController* rgb_controller = new RGBController_CMSmallARGBController(controller);
detected_controllers.push_back(rgb_controller);
if(controller->GetVersion() != "Unsupported")
{
RGBController_CMSmallARGBController* rgb_controller = new RGBController_CMSmallARGBController(controller);
detected_controllers.push_back(rgb_controller);
}
else
{
LOG_ERROR("[CMSmallARGBController] Unsupported firmware version");
delete controller;
}
}
return(detected_controllers);
@@ -44,8 +44,4 @@ DetectedControllers DetectCorsairHydro2Controllers()
}
REGISTER_DETECTOR("Corsair H100i v2", DetectCorsairHydro2Controllers);
/*---------------------------------------------------------------------------------------------------------*\
| Entries for dynamic UDEV rules |
| |
| DUMMY_DEVICE_DETECTOR("Corsair H100i v2", DetectCorsairHydro2Controllers, 0x1B1C, 0x0C09 ) |
\*---------------------------------------------------------------------------------------------------------*/
REGISTER_CUSTOM_UDEV_RULE(corsair_h100i_v2, "Corsair H100i v2", "SUBSYSTEMS==\"usb|hidraw\", ATTRS{idVendor}==\"1b1c\", ATTRS{idProduct}==\"0c09\", TAG+=\"uaccess\", TAG+=\"Corsair_H100i_v2\"");
@@ -77,10 +77,6 @@ DetectedControllers DetectCorsairHydroControllers()
}
REGISTER_DETECTOR("Corsair Hydro Series", DetectCorsairHydroControllers);
/*---------------------------------------------------------------------------------------------------------*\
| Entries for dynamic UDEV rules |
| |
| DUMMY_DEVICE_DETECTOR("Corsair Hydro Series", DetectCorsairHydroControllers, 0x1B1C, 0x0C12 ) |
| DUMMY_DEVICE_DETECTOR("Corsair Hydro Series", DetectCorsairHydroControllers, 0x1B1C, 0x0C13 ) |
| DUMMY_DEVICE_DETECTOR("Corsair Hydro Series", DetectCorsairHydroControllers, 0x1B1C, 0x0C15 ) |
\*---------------------------------------------------------------------------------------------------------*/
REGISTER_CUSTOM_UDEV_RULE(corsair_hydro_1, "Corsair Hydro Series", "SUBSYSTEMS==\"usb|hidraw\", ATTRS{idVendor}==\"1b1c\", ATTRS{idProduct}==\"0c12\", TAG+=\"uaccess\", TAG+=\"Corsair_Hydro_Series\"");
REGISTER_CUSTOM_UDEV_RULE(corsair_hydro_2, "Corsair Hydro Series", "SUBSYSTEMS==\"usb|hidraw\", ATTRS{idVendor}==\"1b1c\", ATTRS{idProduct}==\"0c13\", TAG+=\"uaccess\", TAG+=\"Corsair_Hydro_Series\"");
REGISTER_CUSTOM_UDEV_RULE(corsair_hydro_3, "Corsair Hydro Series", "SUBSYSTEMS==\"usb|hidraw\", ATTRS{idVendor}==\"1b1c\", ATTRS{idProduct}==\"0c15\", TAG+=\"uaccess\", TAG+=\"Corsair_Hydro_Series\"");
@@ -232,7 +232,7 @@ void CorsairLightingNodeController::SendFirmwareRequest()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
actual = WriteAndRead(usb_buf);
actual = WriteAndRead(usb_buf, CORSAIR_LIGHTING_NODE_READ_TIMEOUT);
if(actual > 0)
{
@@ -217,7 +217,7 @@ void CorsairPeripheralController::SetLEDsKeyboardFull(std::vector<RGBColor> colo
blu_val[keys_k95[color_idx]] = RGBGetBValue(color);
data_sz = 48; //untested
}
else if (logical_layout == CORSAIR_TYPE_K70_MK2)
else if (logical_layout == CORSAIR_TYPE_K70_MK2 || logical_layout == CORSAIR_TYPE_K70_LUX_RGB)
{
red_val[keys_k70_mk2[color_idx]] = RGBGetRValue(color);
grn_val[keys_k70_mk2[color_idx]] = RGBGetGValue(color);
@@ -447,7 +447,7 @@ void CorsairPeripheralController::SetupK55AndK95LightingControl()
if (physical_layout == CORSAIR_LAYOUT_ISO)
{
if(logical_layout == CORSAIR_TYPE_K70_MK2)
if(logical_layout == CORSAIR_TYPE_K70_MK2 || logical_layout == CORSAIR_TYPE_K70_LUX_RGB)
{
skipped_identifiers = key_mapping_k70_mk2_plat_iso;
skipped_identifiers_count = sizeof(key_mapping_k70_mk2_plat_iso) / sizeof(key_mapping_k70_mk2_plat_iso[0]);
@@ -526,27 +526,26 @@ void CorsairPeripheralController::ReadFirmwareInfo()
char offset = 0;
/*-----------------------------------------------------*\
| Zero out buffer |
| Some Corsair keyboards (e.g. K70 RGB MK.2) do not |
| answer the firmware-info query on the control |
| interface until several seconds after the host first |
| talks to them following a cold boot. A single read |
| attempt returns all zeros, the device type comes back |
| as 0x00, and the device is discarded as UNKNOWN. |
| Retry the query until a valid device type byte is |
| returned or the attempt cap is reached. Each failed |
| attempt is paced by the 1s hid_read_timeout below, so |
| the cap also bounds the detection stall for a device |
| that opens but never answers. Observed need |
| on a K70 RGB MK.2 cold boot is 2 attempts; the cap |
| leaves generous headroom. |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, CORSAIR_PERIPHERAL_PACKET_LENGTH);
int attempt = 0;
/*-----------------------------------------------------*\
| Set up Read Firmware Info packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = CORSAIR_COMMAND_READ;
usb_buf[0x02] = CORSAIR_PROPERTY_FIRMWARE_INFO;
/*-----------------------------------------------------*\
| Send packet and try reading it using an HID read |
| If that fails, repeat the send and read the reply as |
| a feature report. |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, CORSAIR_PERIPHERAL_PACKET_LENGTH);
actual = hid_read_timeout(dev, usb_buf, CORSAIR_PERIPHERAL_PACKET_LENGTH, 1000);
if(actual == 0)
do
{
offset = 0;
/*-------------------------------------------------*\
| Zero out buffer |
\*-------------------------------------------------*/
@@ -559,10 +558,48 @@ void CorsairPeripheralController::ReadFirmwareInfo()
usb_buf[0x01] = CORSAIR_COMMAND_READ;
usb_buf[0x02] = CORSAIR_PROPERTY_FIRMWARE_INFO;
hid_send_feature_report(dev, usb_buf, CORSAIR_PERIPHERAL_PACKET_LENGTH);
actual = hid_get_feature_report(dev, usb_buf, CORSAIR_PERIPHERAL_PACKET_LENGTH);
offset = 1;
}
/*-------------------------------------------------*\
| Send packet and try reading it using an HID read |
| If that fails, repeat the send and read the reply |
| as a feature report. |
\*-------------------------------------------------*/
hid_write(dev, usb_buf, CORSAIR_PERIPHERAL_PACKET_LENGTH);
actual = hid_read_timeout(dev, usb_buf, CORSAIR_PERIPHERAL_PACKET_LENGTH, 1000);
if(actual == 0)
{
/*---------------------------------------------*\
| Zero out buffer |
\*---------------------------------------------*/
memset(usb_buf, 0x00, CORSAIR_PERIPHERAL_PACKET_LENGTH);
/*---------------------------------------------*\
| Set up Read Firmware Info packet |
\*---------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = CORSAIR_COMMAND_READ;
usb_buf[0x02] = CORSAIR_PROPERTY_FIRMWARE_INFO;
hid_send_feature_report(dev, usb_buf, CORSAIR_PERIPHERAL_PACKET_LENGTH);
actual = hid_get_feature_report(dev, usb_buf, CORSAIR_PERIPHERAL_PACKET_LENGTH);
offset = 1;
}
attempt++;
LOG_DEBUG("[%s] Firmware info attempt %d/%d: device type %02X", CORSAIR_PERIPHERAL_CONTROLLER_NAME, attempt, CORSAIR_FW_INFO_MAX_ATTEMPTS, usb_buf[0x14 + offset]);
/*-------------------------------------------------*\
| Stop as soon as a valid device type is returned |
| 0xC0 keyboard / 0xC1 mouse / 0xC2 pad or stand |
\*-------------------------------------------------*/
if(usb_buf[0x14 + offset] == 0xC0
|| usb_buf[0x14 + offset] == 0xC1
|| usb_buf[0x14 + offset] == 0xC2)
{
break;
}
} while(attempt < CORSAIR_FW_INFO_MAX_ATTEMPTS);
/*-----------------------------------------------------*\
| Get device type |
@@ -570,7 +607,7 @@ void CorsairPeripheralController::ReadFirmwareInfo()
| 0xC1 Device is a mouse |
| 0xC2 Device is a mousepad or headset stand |
\*-----------------------------------------------------*/
LOG_DEBUG("[%s] Device type %02X", CORSAIR_PERIPHERAL_CONTROLLER_NAME, usb_buf[0x14 + offset]);
LOG_DEBUG("[%s] Device type %02X after %d attempt(s)", CORSAIR_PERIPHERAL_CONTROLLER_NAME, usb_buf[0x14 + offset], attempt);
switch(usb_buf[0x14 + offset])
{
@@ -595,10 +632,15 @@ void CorsairPeripheralController::ReadFirmwareInfo()
logical_layout = CORSAIR_TYPE_K55;
break;
case 0x1B33:
logical_layout = CORSAIR_TYPE_K70_LUX_RGB;
break;
case 0x1B38:
case 0x1B49:
case 0x1B6B:
case 0x1B55:
case 0x1B48:
logical_layout = CORSAIR_TYPE_K70_MK2;
break;
@@ -16,6 +16,7 @@
#include "RGBController.h"
#define CORSAIR_PERIPHERAL_PACKET_LENGTH 65
#define CORSAIR_FW_INFO_MAX_ATTEMPTS 10
enum
{
@@ -66,7 +67,8 @@ enum
CORSAIR_TYPE_K95 = 2,
CORSAIR_TYPE_K55 = 3,
CORSAIR_TYPE_K70_MK2 = 4,
CORSAIR_TYPE_K68 = 5
CORSAIR_TYPE_K68 = 5,
CORSAIR_TYPE_K70_LUX_RGB = 6
};
enum
@@ -48,7 +48,7 @@
#define CORSAIR_K70_RGB_MK2_PID 0x1B49
#define CORSAIR_K70_RGB_MK2_SE_PID 0x1B6B
#define CORSAIR_K70_RGB_MK2_LP_PID 0x1B55
#define CORSAIR_K70_RGB_PRO_OPTO_PID 0x1BC6
#define CORSAIR_K95_RGB_PID 0x1B11
#define CORSAIR_K95_PLATINUM_PID 0x1B2D
#define CORSAIR_K95_PLATINUM_SE_PID 0x1B82
@@ -81,6 +81,7 @@
#define CORSAIR_SCIMITAR_PRO_RGB_PID 0x1B3E
#define CORSAIR_SCIMITAR_ELITE_RGB_PID 0x1B8B
#define CORSAIR_SABRE_RGB_PID 0x1B2F
#define CORSAIR_SABRE_RGB_OPTICAL_PID 0x1B32
/*---------------------------------------------------------*\
| Mousepad product IDs |
@@ -192,6 +193,7 @@ REGISTER_HID_DETECTOR_IP("Corsair K70 RGB RAPIDFIRE", DetectCorsairPeriphe
REGISTER_HID_DETECTOR_IP("Corsair K70 RGB MK.2", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_K70_RGB_MK2_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair K70 RGB MK.2 SE", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_K70_RGB_MK2_SE_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair K70 RGB MK.2 Low Profile",DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_K70_RGB_MK2_LP_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair K70 RGB PRO Optomechanical", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_K70_RGB_PRO_OPTO_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair K95 RGB", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_K95_RGB_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair K95 RGB PLATINUM", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_K95_PLATINUM_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair K95 RGB PLATINUM SE", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_K95_PLATINUM_SE_PID, 1, 0xFFC2);
@@ -214,6 +216,7 @@ REGISTER_HID_DETECTOR_IP("Corsair Scimitar RGB", DetectCorsairPeriphe
REGISTER_HID_DETECTOR_IP("Corsair Scimitar PRO RGB", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_SCIMITAR_PRO_RGB_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair Scimitar Elite RGB", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_SCIMITAR_ELITE_RGB_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair Sabre RGB", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_SABRE_RGB_PID, 1, 0xFFC2);
REGISTER_HID_DETECTOR_IP("Corsair Sabre RGB Optical", DetectCorsairPeripheralControllers, CORSAIR_VID, CORSAIR_SABRE_RGB_OPTICAL_PID, 1, 0xFFC2);
/*---------------------------------------------------------*\
| Mousemats |
@@ -23,6 +23,15 @@ static unsigned int matrix_map[6][23] =
{ 4, 111, 22, 32, 40, 50, NA, 59, NA, 68, 78, 88, 97, 106, 61, NA, NA, 81, NA, 73, 83, 92, 109 },
{ 5, 14, 23, NA, NA, NA, NA, 41, NA, NA, NA, NA, 69, 79, 89, 71, 90, 99, 108, 101, NA, 110, NA } };
static unsigned int matrix_map_k70_lux_rgb[7][23] =
{ { NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 107, 8, NA, NA, 16, NA, NA,},
{ 0, NA, 10, 18, 28, 36, NA, 46, 55, 64, 74, NA, 84, 93, 102, 6, 15, 24, 33, 26, 35, 44, 53 },
{ 1, 11, 19, 29, 37, 47, NA, 56, 65, 75, 85, 94, 103, 7, 25, NA, 42, 51, 60, 62, 72, 82, 91 },
{ 2, NA, 12, 20, 30, 38, 48, 57, 66, 76, 86, NA, 95, 104, 70, 80, 34, 43, 52, 9, 17, 27, 100 },
{ 3, NA, 13, 21, 31, 39, 49, 58, 67, 77, 87, NA, 96, 105, 98, 112, NA, NA, NA, 45, 54, 63, NA },
{ 4, 111, NA, 22, 32, 40, 50, 59, 68, 78, 88, 97, NA, 106, 61, NA, NA, 81, NA, 73, 83, 92, 109 },
{ 5, NA, 14, 23, NA, NA, NA, 41, NA, NA, NA, 69, NA, 79, 89, 71, 90, 99, 108, 101, NA, 110, NA } };
static unsigned int matrix_map_k70_mk2[7][23] =
{ { NA, NA, NA, 115, 107, 8, NA, NA, NA, NA, NA, 113, 114, NA, NA, NA, NA, NA, NA, 16, NA, NA, NA,},
{ 0, NA, 10, 18, 28, 36, NA, 46, 55, 64, 74, NA, 84, 93, 102, 6, 15, 24, 33, 26, 35, 44, 53 },
@@ -68,6 +77,24 @@ static const zone_type zone_types[] =
ZONE_TYPE_MATRIX,
};
/*---------------------------------------------------------*\
| K70 LUX RGB Corsair Layout |
\*---------------------------------------------------------*/
static const char* zone_names_k70_lux_rgb[] =
{
ZONE_EN_KEYBOARD,
};
static const unsigned int zone_sizes_k70_lux_rgb[] =
{
116
};
static const zone_type zone_types_k70_lux_rgb[] =
{
ZONE_TYPE_MATRIX,
};
/*---------------------------------------------------------*\
| K70 MK2 Corsair Layout |
\*---------------------------------------------------------*/
@@ -159,7 +186,7 @@ static const char* led_names[] =
KEY_EN_LEFT_CONTROL, //5
KEY_EN_F12, //6
KEY_EN_EQUALS, //7
"Key: Lock", //8
KEY_EN_WINDOWS_LOCK, //8
KEY_EN_NUMPAD_7, //9
KEY_EN_F1, //12
KEY_EN_1, //13
@@ -258,7 +285,7 @@ static const char* led_names[] =
KEY_EN_LEFT_BRACKET, //134
KEY_EN_QUOTE, //135
KEY_EN_FORWARD_SLASH, //136
"Key: Brightness", //137
KEY_EN_BRIGHTNESS, //137
KEY_EN_RIGHT_ARROW, //139
KEY_EN_NUMPAD_ENTER, //140
KEY_EN_NUMPAD_PERIOD, //141
@@ -266,7 +293,7 @@ static const char* led_names[] =
KEY_EN_ISO_BACK_SLASH,
};
static const char* led_names_k70_mk2[] =
static const char* led_names_k70_lux_rgb[] =
{
KEY_EN_ESCAPE, //0
KEY_EN_BACK_TICK, //1
@@ -276,7 +303,7 @@ static const char* led_names_k70_mk2[] =
KEY_EN_LEFT_CONTROL, //5
KEY_EN_F12, //6
KEY_EN_EQUALS, //7
"Key: Lock", //8
KEY_EN_WINDOWS_LOCK, //8
KEY_EN_NUMPAD_7, //9
KEY_EN_F1, //12
KEY_EN_1, //13
@@ -380,7 +407,132 @@ static const char* led_names_k70_mk2[] =
KEY_EN_LEFT_BRACKET, //134
KEY_EN_QUOTE, //135
KEY_EN_FORWARD_SLASH, //136
"Key: Brightness", //137
KEY_EN_BRIGHTNESS, //137
KEY_EN_RIGHT_ARROW, //139
KEY_EN_NUMPAD_ENTER, //140
KEY_EN_NUMPAD_PERIOD, //141
"Key: / (ISO)", //16
KEY_EN_ISO_BACK_SLASH, //114
"Key: Logo Left", //047
"Key: Logo Right", //059
"Key: Profile", //125
};
static const char* led_names_k70_mk2[] =
{
KEY_EN_ESCAPE, //0
KEY_EN_BACK_TICK, //1
KEY_EN_TAB, //2
KEY_EN_CAPS_LOCK, //3
KEY_EN_LEFT_SHIFT, //4
KEY_EN_LEFT_CONTROL, //5
KEY_EN_F12, //6
KEY_EN_EQUALS, //7
KEY_EN_WINDOWS_LOCK, //8
KEY_EN_NUMPAD_7, //9
KEY_EN_F1, //12
KEY_EN_1, //13
KEY_EN_Q, //14
KEY_EN_A, //15
//"Key: / (ISO)", //16
KEY_EN_LEFT_WINDOWS, //17
KEY_EN_PRINT_SCREEN, //18
KEY_EN_MEDIA_MUTE, //20
KEY_EN_NUMPAD_8, //21
KEY_EN_F2, //24
KEY_EN_2, //25
KEY_EN_W, //26
KEY_EN_S, //27
KEY_EN_Z, //28
KEY_EN_LEFT_ALT, //29
KEY_EN_SCROLL_LOCK, //30
KEY_EN_BACKSPACE, //31
KEY_EN_MEDIA_STOP, //32
KEY_EN_NUMPAD_9, //33
KEY_EN_F3, //36
KEY_EN_3, //37
KEY_EN_E, //38
KEY_EN_D, //39
KEY_EN_X, //40
KEY_EN_PAUSE_BREAK, //42
KEY_EN_DELETE, //43
KEY_EN_MEDIA_PREVIOUS, //44
//"Key: Logo Left", //047
KEY_EN_F4, //48
KEY_EN_4, //49
KEY_EN_R, //50
KEY_EN_F, //51
KEY_EN_C, //52
KEY_EN_SPACE, //53
KEY_EN_INSERT, //54
KEY_EN_END, //55
KEY_EN_MEDIA_PLAY_PAUSE, //56
KEY_EN_NUMPAD_4, //57
//"Key: Logo Right", //059
KEY_EN_F5, //60
KEY_EN_5, //61
KEY_EN_T, //62
KEY_EN_G, //63
KEY_EN_V, //64
KEY_EN_HOME, //66
KEY_EN_PAGE_DOWN, //67
KEY_EN_MEDIA_NEXT, //68
KEY_EN_NUMPAD_5, //69
KEY_EN_F6, //72
KEY_EN_6, //73
KEY_EN_Y, //74
KEY_EN_H, //75
KEY_EN_B, //76
KEY_EN_PAGE_UP, //78
KEY_EN_RIGHT_SHIFT, //79
KEY_EN_NUMPAD_LOCK, //80
KEY_EN_NUMPAD_6, //81
KEY_EN_F7, //84
KEY_EN_7, //85
KEY_EN_U, //86
KEY_EN_J, //87
KEY_EN_N, //88
KEY_EN_RIGHT_ALT, //89
KEY_EN_RIGHT_BRACKET, //90
KEY_EN_RIGHT_CONTROL, //91
KEY_EN_NUMPAD_DIVIDE, //92
KEY_EN_NUMPAD_1, //93
KEY_EN_F8, //96
KEY_EN_8, //97
KEY_EN_I, //98
KEY_EN_K, //99
KEY_EN_M, //100
KEY_EN_RIGHT_WINDOWS, //101
KEY_EN_ANSI_BACK_SLASH, //102
KEY_EN_UP_ARROW, //103
KEY_EN_NUMPAD_TIMES, //104
KEY_EN_NUMPAD_2, //105
KEY_EN_F9, //108
KEY_EN_9, //109
KEY_EN_O, //110
KEY_EN_L, //111
KEY_EN_COMMA, //112
KEY_EN_MENU, //113
//KEY_EN_ISO_BACK_SLASH, //114
KEY_EN_LEFT_ARROW, //115
KEY_EN_NUMPAD_MINUS, //116
KEY_EN_NUMPAD_3, //117
KEY_EN_F10, //120
KEY_EN_0, //121
KEY_EN_P, //122
KEY_EN_SEMICOLON, //123
//"Key: Profile", //125
KEY_EN_PERIOD, //124
KEY_EN_ANSI_ENTER, //126
KEY_EN_DOWN_ARROW, //127
KEY_EN_NUMPAD_PLUS, //128
KEY_EN_NUMPAD_0, //129
KEY_EN_F11, //132
KEY_EN_MINUS, //133
KEY_EN_LEFT_BRACKET, //134
KEY_EN_QUOTE, //135
KEY_EN_FORWARD_SLASH, //136
KEY_EN_BRIGHTNESS, //137
KEY_EN_RIGHT_ARROW, //139
KEY_EN_NUMPAD_ENTER, //140
KEY_EN_NUMPAD_PERIOD, //141
@@ -401,7 +553,7 @@ static const char* led_names_k95_plat[] =
KEY_EN_LEFT_CONTROL, //5
KEY_EN_F12, //6
KEY_EN_EQUALS, //7
"Key: Lock", //8
KEY_EN_WINDOWS_LOCK, //8
KEY_EN_NUMPAD_7, //9
KEY_EN_F1, //12
KEY_EN_1, //13
@@ -500,7 +652,7 @@ static const char* led_names_k95_plat[] =
KEY_EN_LEFT_BRACKET, //134
KEY_EN_QUOTE, //135
KEY_EN_FORWARD_SLASH, //136
"Key: Brightness", //137
KEY_EN_BRIGHTNESS, //137
KEY_EN_RIGHT_ARROW, //139
KEY_EN_NUMPAD_ENTER, //140
KEY_EN_NUMPAD_PERIOD, //141
@@ -544,7 +696,7 @@ static const char* led_names_k95[] =
KEY_EN_LEFT_CONTROL, //5
KEY_EN_F12, //6
KEY_EN_EQUALS, //7
"Key: Lock", //8
KEY_EN_WINDOWS_LOCK, //8
KEY_EN_NUMPAD_7, //9
KEY_EN_F1, //12
KEY_EN_1, //13
@@ -643,7 +795,7 @@ static const char* led_names_k95[] =
KEY_EN_LEFT_BRACKET, //134
KEY_EN_QUOTE, //135
KEY_EN_FORWARD_SLASH, //136
"Key: Brightness", //137
KEY_EN_BRIGHTNESS, //137
KEY_EN_RIGHT_ARROW, //139
KEY_EN_NUMPAD_ENTER, //140
KEY_EN_NUMPAD_PERIOD, //141
@@ -1012,6 +1164,19 @@ void RGBController_CorsairPeripheral::SetupZones()
new_zone.leds_max = zone_sizes_k55[zone_idx];
new_zone.leds_count = zone_sizes_k55[zone_idx];
}
else if (logical_layout == CORSAIR_TYPE_K70_LUX_RGB)
{
new_zone.name = zone_names_k70_lux_rgb[zone_idx];
new_zone.type = zone_types_k70_lux_rgb[zone_idx];
new_zone.leds_min = zone_sizes_k70_lux_rgb[zone_idx];
new_zone.leds_max = zone_sizes_k70_lux_rgb[zone_idx];
new_zone.leds_count = zone_sizes_k70_lux_rgb[zone_idx];
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map.Set(7, 23, (unsigned int *)&matrix_map_k70_lux_rgb);
}
}
else if (logical_layout == CORSAIR_TYPE_K70_MK2)
{
new_zone.name = zone_names_k70_mk2[zone_idx];
@@ -1101,6 +1266,10 @@ void RGBController_CorsairPeripheral::SetupZones()
{
new_led.name = led_names_k55[led_idx];
}
else if(logical_layout == CORSAIR_TYPE_K70_LUX_RGB)
{
new_led.name = led_names_k70_lux_rgb[led_idx];
}
else if(logical_layout == CORSAIR_TYPE_K70_MK2)
{
new_led.name = led_names_k70_mk2[led_idx];
@@ -9,6 +9,9 @@
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <chrono>
#include <thread>
#include "CorsairPeripheralV2Controller.h"
#include "StringUtils.h"
@@ -19,21 +22,36 @@ CorsairPeripheralV2Controller::CorsairPeripheralV2Controller(hid_device* dev_han
dev = dev_handle;
location = path;
device_name = name;
device_index = CORSAIR_V2_DEVICE_NOT_FOUND;
/*---------------------------------------------------------*\
| Get PID |
| If the PID is in the know wireless receivers list |
| switch the write_cmd to talk to the device and retry |
\*---------------------------------------------------------*/
unsigned int pid = GetAddress(0x12);
/*---------------------------------------------------------*\
| Establish the packet size before talking to the device. |
\*---------------------------------------------------------*/
uint16_t report_size = GetOutputReportSize();
if(report_size >= CORSAIR_V2_WRITE_SIZE && report_size <= CORSAIR_V2_PACKET_SIZE)
{
pkt_sze = report_size;
}
LOG_DEBUG("[%s] Packet length set to %d", device_name.c_str(), pkt_sze);
unsigned int pid = GetAddressRetry(0x12);
switch(pid)
{
case CORSAIR_SLIPSTREAM_WIRELESS_PID1:
case CORSAIR_SLIPSTREAM_WIRELESS_V2_PID1:
case CORSAIR_SLIPSTREAM_WIRELESS_PID2:
case CORSAIR_SLIPSTREAM_M75_PID:
case CORSAIR_SLIPSTREAM_IRONCLAW_SE_PID:
write_cmd = CORSAIR_V2_WRITE_WIRELESS_ID;
pid = GetAddress(0x12);
pid = GetAddressRetry(0x12);
break;
case CORSAIR_K57_RGB_WIRED_PID:
@@ -58,14 +76,12 @@ CorsairPeripheralV2Controller::CorsairPeripheralV2Controller(hid_device* dev_han
| the device being set up. |
\*---------------------------------------------------------*/
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
memset(buffer, 0, pkt_sze);
buffer[1] = write_cmd;
buffer[2] = CORSAIR_V2_CMD_GET;
buffer[3] = 0x11;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
uint16_t result = hid_read_timeout(dev, buffer, CORSAIR_V2_PACKET_SIZE, CORSAIR_V2_TIMEOUT);
result++;
pkt_sze = std::max(result, (uint16_t)CORSAIR_V2_WRITE_SIZE);
LOG_DEBUG("[%s] Packet length set to %d", device_name.c_str(), pkt_sze);
hid_write(dev, buffer, pkt_sze);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT);
/*---------------------------------------------------------*\
| NB: If the device is not found in the device list |
@@ -88,28 +104,129 @@ CorsairPeripheralV2Controller::CorsairPeripheralV2Controller(hid_device* dev_han
}
}
/*---------------------------------------------------------*\
| Unknown PID |
| Leave device_index at CORSAIR_V2_DEVICE_NOT_FOUND so |
| GetDeviceData() returns nullptr and the detector drops |
| the device, rather than building an RGBController on |
| top of an out of range index. |
\*---------------------------------------------------------*/
if(not_found)
{
LOG_ERROR("[%s] device capabilities not found. Please creata a new device request.",
device_name.c_str());
return;
}
}
/*---------------------------------------------------------*\
| Check lighting control endpoints |
| If lighting control endpoint 2 is unavailable |
| then use endpoint 1. |
\*---------------------------------------------------------*/
uint16_t CorsairPeripheralV2Controller::GetOutputReportSize()
{
unsigned char descriptor[HID_API_MAX_REPORT_DESCRIPTOR_SIZE];
int length = hid_get_report_descriptor(dev, descriptor, sizeof(descriptor));
if(length <= 0)
{
return 0;
}
unsigned int report_size = 0;
unsigned int report_count = 0;
int index = 0;
while(index < length)
{
unsigned char item = descriptor[index];
unsigned char tag = item & 0xFC;
unsigned char item_size = item & 0x03;
/*---------------------------------------------------------*\
| A size field of 3 means four data bytes follow. |
\*---------------------------------------------------------*/
if(item_size == 3)
{
item_size = 4;
}
if(index + 1 + item_size > length)
{
break;
}
unsigned int value = 0;
for(unsigned char byte = 0; byte < item_size; byte++)
{
value |= (unsigned int)descriptor[index + 1 + byte] << (8 * byte);
}
switch(tag)
{
case 0x74: /* Report Size */
report_size = value;
break;
case 0x94: /* Report Count */
report_count = value;
break;
case 0x90: /* Output */
if(report_size > 0 && report_count > 0)
{
return (uint16_t)(((report_size * report_count) / 8) + 1);
}
break;
}
index += 1 + item_size;
}
return 0;
}
void CorsairPeripheralV2Controller::DetectLightingEndpoint()
{
/*---------------------------------------------------------*\
| A device that cannot be read from cannot be probed; those |
| carry the endpoint they need already set. |
\*---------------------------------------------------------*/
if(skip_reads)
{
return;
}
/*---------------------------------------------------------*\
| Check lighting control endpoints |
| If lighting control endpoint 2 is unavailable |
| then use endpoint 1. |
| Lighting control 1 is the plain lighting resource and |
| takes colour block data. A device that answers |
| invalid or unsupported for it wants the alternate |
| lighting resource, which takes RGB triplets. |
\*---------------------------------------------------------*/
if(light_ctrl == CORSAIR_V2_LIGHT_CTRL2)
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
unsigned char result = StartTransaction(0);
/*---------------------------------------------------------*\
| A handle left open by an earlier session answers failed; |
| close it and open it again. |
\*---------------------------------------------------------*/
if(result == CORSAIR_V2_ERR_FAILED)
{
result = StartTransaction(0);
if(result > 0)
{
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
StartTransaction(0);
}
StopTransaction(0);
LOG_DEBUG("[%s] Lighting Endpoint set to %02X", device_name.c_str(), light_ctrl);
result = StartTransaction(0);
}
if(result == CORSAIR_V2_ERR_INVALID || result == CORSAIR_V2_ERR_NOT_SUPPORTED)
{
light_ctrl = CORSAIR_V2_LIGHT_CTRL2;
StartTransaction(0);
}
StopTransaction(0);
LOG_DEBUG("[%s] Lighting Endpoint set to %02X", device_name.c_str(), light_ctrl);
}
CorsairPeripheralV2Controller::~CorsairPeripheralV2Controller()
@@ -119,9 +236,19 @@ CorsairPeripheralV2Controller::~CorsairPeripheralV2Controller()
const corsair_v2_device* CorsairPeripheralV2Controller::GetDeviceData()
{
if(!IsDeviceSupported())
{
return nullptr;
}
return corsair_v2_device_list[device_index];
}
bool CorsairPeripheralV2Controller::IsDeviceSupported()
{
return device_index < CORSAIR_V2_DEVICE_COUNT;
}
std::string CorsairPeripheralV2Controller::GetDeviceLocation()
{
return("HID: " + location);
@@ -167,9 +294,9 @@ std::string CorsairPeripheralV2Controller::GetSerialString()
void CorsairPeripheralV2Controller::SetRenderMode(corsair_v2_device_mode mode)
{
uint8_t buffer[CORSAIR_V2_WRITE_SIZE];
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
memset(buffer, 0, CORSAIR_V2_WRITE_SIZE);
memset(buffer, 0, pkt_sze);
/*---------------------------------------------------------*\
| Set Mode |
@@ -179,19 +306,19 @@ void CorsairPeripheralV2Controller::SetRenderMode(corsair_v2_device_mode mode)
buffer[3] = CORSAIR_V2_VALUE_MODE;
buffer[5] = mode;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_write(dev, buffer, pkt_sze);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT);
}
}
void CorsairPeripheralV2Controller::LightingControl(uint8_t opt1)
{
uint8_t buffer[CORSAIR_V2_WRITE_SIZE];
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
memset(buffer, 0, CORSAIR_V2_WRITE_SIZE);
memset(buffer, 0, pkt_sze);
/*---------------------------------------------------------*\
| The Corsair command is the same for each initialisation |
@@ -203,11 +330,11 @@ void CorsairPeripheralV2Controller::LightingControl(uint8_t opt1)
buffer[3] = opt1;
buffer[5] = 0x00;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_write(dev, buffer, pkt_sze);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT);
}
}
@@ -216,20 +343,48 @@ unsigned int CorsairPeripheralV2Controller::GetKeyboardLayout()
return GetAddress(0x41);
}
/*---------------------------------------------------------*\
| GetAddress returns NA when the device answers with a |
| protocol error, and a receiver whose device has not |
| woken answers zero. Straight after enumeration both |
| are normal, so poll before treating either as final. |
\*---------------------------------------------------------*/
unsigned int CorsairPeripheralV2Controller::GetAddressRetry(uint8_t address)
{
unsigned int value = NA;
for(unsigned int attempt = 0; attempt < CORSAIR_V2_PID_RETRIES; attempt++)
{
value = GetAddress(address);
if(value != NA && value != 0)
{
return value;
}
if(attempt + 1 < CORSAIR_V2_PID_RETRIES)
{
std::this_thread::sleep_for(std::chrono::milliseconds(CORSAIR_V2_PID_RETRY_DELAY));
}
}
return value;
}
unsigned int CorsairPeripheralV2Controller::GetAddress(uint8_t address)
{
uint8_t buffer[CORSAIR_V2_WRITE_SIZE];
uint8_t read[CORSAIR_V2_WRITE_SIZE];
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
uint8_t read[CORSAIR_V2_PACKET_SIZE];
memset(buffer, 0, CORSAIR_V2_WRITE_SIZE);
memset(read, 0, CORSAIR_V2_WRITE_SIZE);
memset(buffer, 0, pkt_sze);
memset(read, 0, pkt_sze);
buffer[1] = write_cmd;
buffer[2] = CORSAIR_V2_CMD_GET;
buffer[3] = address;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_read_timeout(dev, read, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
hid_write(dev, buffer, pkt_sze);
hid_read_timeout(dev, read, pkt_sze, CORSAIR_V2_TIMEOUT);
unsigned int temp = (unsigned int)(read[6] << 24 | read[5] << 16 | read[4] << 8 | read[3]);
LOG_DEBUG("[%s] GetAddress %02X - %02X %02X - %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(),
@@ -247,20 +402,20 @@ unsigned int CorsairPeripheralV2Controller::GetAddress(uint8_t address)
unsigned char CorsairPeripheralV2Controller::StartTransaction(uint8_t opt1)
{
uint8_t buffer[CORSAIR_V2_WRITE_SIZE];
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
memset(buffer, 0, CORSAIR_V2_WRITE_SIZE);
memset(buffer, 0, pkt_sze);
buffer[1] = write_cmd;
buffer[2] = CORSAIR_V2_CMD_START_TX;
buffer[3] = opt1;
buffer[4] = light_ctrl;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_write(dev, buffer, pkt_sze);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT);
}
return buffer[2];
@@ -268,20 +423,20 @@ unsigned char CorsairPeripheralV2Controller::StartTransaction(uint8_t opt1)
void CorsairPeripheralV2Controller::StopTransaction(uint8_t opt1)
{
uint8_t buffer[CORSAIR_V2_WRITE_SIZE];
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
memset(buffer, 0, CORSAIR_V2_WRITE_SIZE);
memset(buffer, 0, pkt_sze);
buffer[1] = write_cmd;
buffer[2] = CORSAIR_V2_CMD_STOP_TX;
buffer[3] = 0x01;
buffer[4] = opt1;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_write(dev, buffer, pkt_sze);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT);
}
}
@@ -295,6 +450,11 @@ void CorsairPeripheralV2Controller::ClearPacketBuffer()
uint8_t result = 0;
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
/*---------------------------------------------------------*\
| Draining expects to find nothing, so the last read here |
| always waits out the timeout. Keep it short: this runs |
| before every LED update. |
\*---------------------------------------------------------*/
do
{
result = hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
@@ -304,6 +464,8 @@ void CorsairPeripheralV2Controller::ClearPacketBuffer()
void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
{
std::lock_guard<std::mutex> lock(device_mutex);
const uint8_t offset1 = 8;
const uint8_t offset2 = 4;
uint16_t remaining = data_size;
@@ -337,7 +499,7 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
if(!skip_reads)
{
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT);
}
remaining -= copy_bytes;
@@ -362,7 +524,7 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
if(!skip_reads)
{
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT);
}
remaining -= copy_bytes;
@@ -374,6 +536,8 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
void CorsairPeripheralV2Controller::UpdateHWMode(uint16_t mode, corsair_v2_color /*color_mode*/, uint8_t /*speed*/,
uint8_t /*direction*/, uint8_t /*brightness*/, std::vector<RGBColor> /*colors*/)
{
std::lock_guard<std::mutex> lock(device_mutex);
/*---------------------------------------------------------*\
| If we are switching to `Direct` mode |
| set device in software mode |
@@ -12,6 +12,7 @@
#pragma once
#include <string>
#include <mutex>
#include <vector>
#include <hidapi.h>
#include "LogManager.h"
@@ -21,13 +22,33 @@
#define NA 0xFFFFFFFF
#define HID_MAX_STR 255
#define CORSAIR_V2_TIMEOUT 50
/*---------------------------------------------------------*\
| A jumbo packet device answers a 1KB command in well over |
| 50ms. Missing the reply leaves it queued and the next |
| write to the device then fails outright, so this has to |
| be generous. A reply that does arrive is returned as |
| soon as it lands, so this only costs time on failure. |
\*---------------------------------------------------------*/
#define CORSAIR_V2_TIMEOUT 500
#define CORSAIR_V2_TIMEOUT_SHORT 3
#define CORSAIR_V2_VALUE_MODE 3
#define CORSAIR_V2_WRITE_WIRED_ID 8
#define CORSAIR_V2_WRITE_WIRELESS_ID 9
#define CORSAIR_V2_WRITE_SIZE 65
#define CORSAIR_V2_PACKET_SIZE 1024
/*---------------------------------------------------------*\
| Largest packet the protocol uses: 1024 payload bytes |
| plus the leading report ID. pkt_sze is measured |
| against this, and every stack buffer handed to hidapi |
| with pkt_sze is this size, so the two must agree. |
\*---------------------------------------------------------*/
#define CORSAIR_V2_PACKET_SIZE 1025
/*---------------------------------------------------------*\
| Error codes the device returns in byte 2 of a reply. |
\*---------------------------------------------------------*/
#define CORSAIR_V2_ERR_INVALID 0x01
#define CORSAIR_V2_ERR_FAILED 0x03
#define CORSAIR_V2_ERR_NOT_SUPPORTED 0x06
#define CORSAIR_V2_LIGHT_CTRL1 1
#define CORSAIR_V2_LIGHT_CTRL2 34 /* 0x22 */
@@ -37,6 +58,19 @@
#define CORSAIR_V2_BRIGHTNESS_MIN 0
#define CORSAIR_V2_BRIGHTNESS_MAX 0xFF
/*---------------------------------------------------------*\
| A device that has just enumerated does not answer the PID |
| query straight away, so retry before giving up on it. |
\*---------------------------------------------------------*/
#define CORSAIR_V2_PID_RETRIES 8
#define CORSAIR_V2_PID_RETRY_DELAY 120
/*---------------------------------------------------------*\
| device_index when the PID is not in the device list. |
| Never index the device list with it. |
\*---------------------------------------------------------*/
#define CORSAIR_V2_DEVICE_NOT_FOUND 0xFFFF
enum corsair_v2_cmd
{
CORSAIR_V2_CMD_SET = 0x01, /* Command for setting values */
@@ -84,6 +118,7 @@ public:
std::string GetName();
std::string GetSerialString();
const corsair_v2_device* GetDeviceData();
bool IsDeviceSupported();
unsigned int GetKeyboardLayout();
void SetRenderMode(corsair_v2_device_mode mode);
@@ -99,14 +134,46 @@ protected:
std::string device_name;
uint8_t light_ctrl = CORSAIR_V2_LIGHT_CTRL2;
/*---------------------------------------------------------*\
| Works out which lighting control endpoint the device |
| accepts. Must be called only once the device has been |
| put into software render mode: endpoint 2 rejects the |
| transaction while the device is still driving its own |
| lighting, which would pick endpoint 1 for a device that |
| does not support it. |
\*---------------------------------------------------------*/
void DetectLightingEndpoint();
/*---------------------------------------------------------*\
| Size of one output report, plus the leading report ID. |
| Read from the HID report descriptor before anything is |
| sent: these devices come in a 64 byte and a 1024 byte |
| flavour, and sending a short packet to a 1024 byte |
| endpoint wedges it until the device is reset, so the |
| size has to be known before the first write rather than |
| inferred from a reply. Returns 0 if it cannot be read. |
\*---------------------------------------------------------*/
uint16_t GetOutputReportSize();
private:
void ClearPacketBuffer();
unsigned int GetAddress(uint8_t address);
unsigned int GetAddressRetry(uint8_t address);
unsigned char StartTransaction(uint8_t opt1);
void StopTransaction(uint8_t opt1);
hid_device* dev;
/*---------------------------------------------------------*\
| Serialises device transactions. |
| The RGB controllers run a keepalive thread that writes |
| LEDs independently of the caller, and a Corsair block |
| write spans several packets. Letting two of those |
| interleave on one HID handle corrupts the transaction |
| and leaves the device unresponsive until it is reset. |
\*---------------------------------------------------------*/
std::mutex device_mutex;
uint8_t write_cmd = CORSAIR_V2_WRITE_WIRED_ID;
uint16_t pkt_sze = CORSAIR_V2_WRITE_SIZE;
bool skip_reads = false;
@@ -12,6 +12,7 @@
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "DetectionManager.h"
#include "LogManager.h"
/*---------------------------------------------------------*\
| Corsair Peripheral specific includes |
@@ -37,6 +38,20 @@ DetectedControllers DetectCorsairV2HardwareControllers(hid_device_info* info, co
if(dev)
{
CorsairPeripheralV2HWController* controller = new CorsairPeripheralV2HWController(dev, info->path, name);
/*---------------------------------------------------------*\
| A device whose PID is not in the device list has no |
| zone or LED data to build an RGBController from. |
| Drop it here: building one anyway indexes the device |
| list with an index that was never set. |
\*---------------------------------------------------------*/
if(!controller->IsDeviceSupported())
{
LOG_WARNING("[%s] not registering device, capabilities unknown.", name.c_str());
delete controller;
return(detected_controllers);
}
RGBController_CorsairV2HW* rgb_controller = new RGBController_CorsairV2HW(controller);
detected_controllers.push_back(rgb_controller);
@@ -55,6 +70,14 @@ DetectedControllers DetectCorsairV2SoftwareControllers(hid_device_info* info, co
if(dev)
{
CorsairPeripheralV2SWController* controller = new CorsairPeripheralV2SWController(dev, info->path, name);
if(!controller->IsDeviceSupported())
{
LOG_WARNING("[%s] not registering device, capabilities unknown.", name.c_str());
delete controller;
return(detected_controllers);
}
RGBController_CorsairV2SW* rgb_controller = new RGBController_CorsairV2SW(controller);
detected_controllers.push_back(rgb_controller);
@@ -69,6 +92,7 @@ DetectedControllers DetectCorsairV2SoftwareControllers(hid_device_info* info, co
REGISTER_HID_DETECTOR_IP("Corsair K55 RGB PRO", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K55_RGB_PRO_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K57 RGB (Wired)", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K57_RGB_WIRED_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO SE", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_SE_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO Low Profile", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_LP_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO TKL Black", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_TKL_B_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO TKL White", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_TKL_W_PID, 1, 0xFF42);
@@ -91,6 +115,8 @@ REGISTER_HID_DETECTOR_IP("Corsair Dark Core RGB SE (Wired)", DetectCorsai
REGISTER_HID_DETECTOR_IP("Corsair Dark Core RGB Pro SE (Wired)", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_DARK_CORE_RGB_PRO_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Harpoon Wireless (Wired)", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_HARPOON_WIRELESS_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Ironclaw Wireless (Wired)", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_IRONCLAW_WIRELESS_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Ironclaw Wireless SE (Wired)", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_IRONCLAW_WIRELESS_SE_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Ironclaw Wireless SE", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_SLIPSTREAM_IRONCLAW_SE_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Katar Pro", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_KATAR_PRO_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Katar Pro V2", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_KATAR_PRO_V2_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Katar Pro XT", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_KATAR_PRO_XT_PID, 1, 0xFF42);
@@ -98,6 +124,8 @@ REGISTER_HID_DETECTOR_IP("Corsair M55 RGB PRO", DetectCorsai
REGISTER_HID_DETECTOR_IP("Corsair M65 RGB Ultra Wired", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_M65_RGB_ULTRA_WIRED_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair M65 RGB Ultra Wireless (Wired)", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_M65_RGB_ULTRA_WIRELESS_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair M75 Gaming Mouse", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_M75_GAMING_MOUSE_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair M75 Wireless", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_SLIPSTREAM_M75_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair M75 Wireless (Wired)", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_M75_WIRELESS_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Slipstream Wireless Receiver HW", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_SLIPSTREAM_WIRELESS_PID1, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Slipstream Wireless Receiver SW", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_SLIPSTREAM_WIRELESS_PID2, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair Slipstream Wireless Receiver HW", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_SLIPSTREAM_WIRELESS_V2_PID1, 1, 0xFF42);
@@ -638,6 +638,71 @@ static const corsair_v2_device ironclaw_wireless_device =
nullptr
};
/*-------------------------------------------------------------*\
| Corsair Ironclaw Wireless SE 1B1C:2B32 |
| |
| Zone "Logo" |
| Single |
| |
| Zone "Scroll Wheel" |
| Single |
| |
| Zone "Buttons" |
| Single |
| |
| Zone "Side" |
| Linear |
| 1 Row, 3 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_zone ironclaw_se_logo_zone =
{
"Logo",
ZONE_TYPE_SINGLE,
1,
1
};
static const corsair_v2_zone ironclaw_se_scroll_zone =
{
"Scroll Wheel",
ZONE_TYPE_SINGLE,
1,
1
};
static const corsair_v2_zone ironclaw_se_button_zone =
{
"Buttons",
ZONE_TYPE_SINGLE,
1,
1
};
static const corsair_v2_zone ironclaw_se_side_zone =
{
"Side Zone",
ZONE_TYPE_LINEAR,
1,
3
};
static const corsair_v2_device ironclaw_wireless_se_device =
{
CORSAIR_IRONCLAW_WIRELESS_SE_PID,
DEVICE_TYPE_MOUSE,
1,
6,
{
&ironclaw_se_logo_zone,
&ironclaw_se_scroll_zone,
&ironclaw_se_button_zone,
&ironclaw_se_side_zone,
nullptr,
nullptr
},
nullptr
};
/*-------------------------------------------------------------*\
| Corsair Katar Pro 1B1C:1B93 |
| |
@@ -836,7 +901,7 @@ static const corsair_v2_device k57_rgb_wired_device =
| |
| Zone "Keyboard" |
| Matrix |
| 6 Rows, 21 Columns |
| 7 Rows, 22 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_zone k60_rgb_pro_zone =
{
@@ -863,12 +928,34 @@ static const corsair_v2_device k60_rgb_pro_device =
&corsair_K60_layout
};
/*-------------------------------------------------------------*\
| Corsair K60 RGB Pro SE 1B1C:1B8D |
| |
| Initial implementation reuses K60 RGB Pro zone/layout |
\*-------------------------------------------------------------*/
static const corsair_v2_device k60_rgb_pro_se_device =
{
CORSAIR_K60_RGB_PRO_SE_PID,
DEVICE_TYPE_KEYBOARD,
6,
21,
{
&k60_rgb_pro_zone,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr
},
&corsair_K60_layout
};
/*-------------------------------------------------------------*\
| Corsair K60 RGB Pro Low Profile 1B1C:1BAD |
| |
| Zone "Keyboard" |
| Matrix |
| 6 Rows, 21 Columns |
| 7 Rows, 22 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_zone k60_rgb_pro_lp_zone =
{
@@ -901,7 +988,7 @@ static const corsair_v2_device k60_rgb_pro_lp_device =
| |
| Zone "Keyboard" |
| Matrix |
| 6 Rows, 21 Columns |
| 7 Rows, 22 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_zone k60_rgb_pro_tkl_zone =
{
@@ -951,7 +1038,7 @@ static const corsair_v2_device k60_rgb_pro_tkl_device_w =
| |
| Zone "Keyboard" |
| Matrix |
| 6 Rows, 21 Columns |
| 7 Rows, 22 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_zone k70_core_rgb_zone =
{
@@ -1080,14 +1167,14 @@ static const corsair_v2_device k70_rgb_tkl_cs_device =
| |
| Zone "Keyboard" |
| Matrix |
| 6 Rows, 21 Columns |
| 7 Rows, 22 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_zone k70_rgb_pro_zone =
{
ZONE_EN_KEYBOARD,
ZONE_TYPE_MATRIX,
7,
21
22
};
static const corsair_v2_device k70_rgb_pro_device =
@@ -1095,7 +1182,7 @@ static const corsair_v2_device k70_rgb_pro_device =
CORSAIR_K70_RGB_PRO_PID,
DEVICE_TYPE_KEYBOARD,
7,
21,
22,
{
&k70_rgb_pro_zone,
nullptr,
@@ -1112,14 +1199,14 @@ static const corsair_v2_device k70_rgb_pro_device =
| |
| Zone "Keyboard" |
| Matrix |
| 6 Rows, 21 Columns |
| 7 Rows, 22 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_device k70_rgb_pro_v2_device =
{
CORSAIR_K70_RGB_PRO_V2_PID,
DEVICE_TYPE_KEYBOARD,
7,
21,
22,
{
&k70_rgb_pro_zone,
nullptr,
@@ -1128,7 +1215,8 @@ static const corsair_v2_device k70_rgb_pro_v2_device =
nullptr,
nullptr
},
&corsair_k70_pro_layout
&corsair_k70_pro_layout,
193
};
/*-------------------------------------------------------------*\
@@ -1443,6 +1531,44 @@ static const corsair_v2_device m75_device =
nullptr
};
/*-------------------------------------------------------------*\
| Corsair M75 Wireless 1B1C:1BF1 |
| |
| Reached through the Slipstream receiver 1B1C:1BDC, which |
| reports the mouse PID 0x1BF1 once the write command is |
| switched to the wireless ID. |
| |
| Zone "Logo" |
| Single |
| |
| Zone "Bottom" |
| Single |
\*-------------------------------------------------------------*/
static const corsair_v2_zone m75_wireless_bottom_zone =
{
"Bottom",
ZONE_TYPE_SINGLE,
1,
1
};
static const corsair_v2_device m75_wireless_device =
{
CORSAIR_M75_WIRELESS_PID,
DEVICE_TYPE_MOUSE,
1,
2,
{
&m75_logo_zone,
&m75_wireless_bottom_zone,
nullptr,
nullptr,
nullptr,
nullptr
},
nullptr
};
/*-------------------------------------------------------------*\
| Corsair MM700 1B1C:1B9B |
| |
@@ -1522,6 +1648,7 @@ const corsair_v2_device* corsair_v2_device_list_data[] =
&k55_rgb_pro_device,
&k57_rgb_wired_device,
&k60_rgb_pro_device,
&k60_rgb_pro_se_device,
&k60_rgb_pro_lp_device,
&k60_rgb_pro_tkl_device_b,
&k60_rgb_pro_tkl_device_w,
@@ -1543,6 +1670,7 @@ const corsair_v2_device* corsair_v2_device_list_data[] =
&dark_core_pro_se_device,
&harpoon_wireless_device,
&ironclaw_wireless_device,
&ironclaw_wireless_se_device,
&katar_pro_device,
&katar_pro_v2_device,
&katar_pro_xt_device,
@@ -1550,6 +1678,7 @@ const corsair_v2_device* corsair_v2_device_list_data[] =
&m65_rgb_ultra_wired_device,
&m65_ultra_rgb_device,
&m75_device,
&m75_wireless_device,
/*-----------------------------------------------------------------*\
| MOUSEMATS |
@@ -63,6 +63,15 @@ typedef struct
uint8_t cols;
const corsair_v2_zone* zones[CORSAIR_ZONES_MAX];
keyboard_keymap_overlay_values* layout_new;
/*-------------------------------------------------------*\
| Number of LED slots the device expects in a direct |
| lighting write. The keymap only covers the keys that |
| exist, but the device wants its full slot count and |
| ignores a short write, so the buffer map is padded up |
| to this. Left 0 where the count is not known, which |
| keeps the keymap derived size. |
\*-------------------------------------------------------*/
uint16_t hw_led_count;
} corsair_v2_device;
/*-----------------------------------------------------*\
@@ -72,6 +81,7 @@ typedef struct
#define CORSAIR_K57_RGB_WIRED_PID 0x1B6E
#define CORSAIR_K57_RGB_WIRELESS_PID 0x1B62
#define CORSAIR_K60_RGB_PRO_PID 0x1BA0
#define CORSAIR_K60_RGB_PRO_SE_PID 0x1B8D
#define CORSAIR_K60_RGB_PRO_LP_PID 0x1BAD
#define CORSAIR_K60_RGB_PRO_TKL_B_PID 0x1BC7
#define CORSAIR_K60_RGB_PRO_TKL_W_PID 0x1BED
@@ -93,6 +103,8 @@ typedef struct
#define CORSAIR_DARK_CORE_RGB_PRO_PID 0x1B7E
#define CORSAIR_HARPOON_WIRELESS_PID 0x1B5E
#define CORSAIR_IRONCLAW_WIRELESS_PID 0x1B4C
#define CORSAIR_IRONCLAW_WIRELESS_SE_PID 0x2B32
#define CORSAIR_SLIPSTREAM_IRONCLAW_SE_PID 0x2B00
#define CORSAIR_KATAR_PRO_PID 0x1B93
#define CORSAIR_KATAR_PRO_V2_PID 0x1BBA
#define CORSAIR_KATAR_PRO_XT_PID 0x1BAC
@@ -100,6 +112,8 @@ typedef struct
#define CORSAIR_M65_RGB_ULTRA_WIRED_PID 0x1B9E
#define CORSAIR_M65_RGB_ULTRA_WIRELESS_PID 0x1BB5
#define CORSAIR_M75_GAMING_MOUSE_PID 0x1BF0
#define CORSAIR_M75_WIRELESS_PID 0x1BF1
#define CORSAIR_SLIPSTREAM_M75_PID 0x1BDC
#define CORSAIR_SLIPSTREAM_WIRELESS_PID1 0x1BA6
#define CORSAIR_SLIPSTREAM_WIRELESS_V2_PID1 0x1B66
#define CORSAIR_SLIPSTREAM_WIRELESS_PID2 0x1B65
@@ -16,6 +16,12 @@ CorsairPeripheralV2HWController::CorsairPeripheralV2HWController(hid_device* dev
{
SetRenderMode(CORSAIR_V2_MODE_SW);
LightingControl(0x5F);
/*---------------------------------------------------------*\
| Only now that the device is in software render mode can |
| its lighting control endpoint be probed reliably. |
\*---------------------------------------------------------*/
DetectLightingEndpoint();
}
CorsairPeripheralV2HWController::~CorsairPeripheralV2HWController()
@@ -16,6 +16,12 @@ CorsairPeripheralV2SWController::CorsairPeripheralV2SWController(hid_device* dev
{
SetRenderMode(CORSAIR_V2_MODE_SW);
LightingControl(0x5F);
/*---------------------------------------------------------*\
| Only now that the device is in software render mode can |
| its lighting control endpoint be probed reliably. |
\*---------------------------------------------------------*/
DetectLightingEndpoint();
}
CorsairPeripheralV2SWController::~CorsairPeripheralV2SWController()
@@ -66,21 +66,32 @@ RGBController_CorsairV2HW::RGBController_CorsairV2HW(CorsairPeripheralV2Controll
| to not revert back into rainbow mode. Start a thread |
| to continuously send a keepalive packet every 50 sec |
\*-----------------------------------------------------*/
/*-----------------------------------------------------*\
| Start the keepalive clock before the thread runs. |
| A default constructed time_point sits at the clock |
| epoch, which is always older than the update |
| period, so the thread would otherwise fire a write |
| on its first pass - during detection, racing |
| whatever the caller is doing. |
\*-----------------------------------------------------*/
last_update_time = std::chrono::steady_clock::now();
keepalive_thread_run = true;
keepalive_thread = new std::thread(&RGBController_CorsairV2HW::KeepaliveThread, this);
}
RGBController_CorsairV2HW::~RGBController_CorsairV2HW()
{
Shutdown();
/*-----------------------------------------------------*\
| Close keepalive thread |
| Close keepalive thread first, so that it cannot issue |
| a write while the device is being shut down. |
\*-----------------------------------------------------*/
keepalive_thread_run = false;
keepalive_thread->join();
delete keepalive_thread;
Shutdown();
delete controller;
}
@@ -198,6 +209,15 @@ void RGBController_CorsairV2HW::SetupZones()
SetupColors();
/*---------------------------------------------------------*\
| Pad out to the slot count the device expects. A write |
| that is short of it is ignored by the hardware. |
\*---------------------------------------------------------*/
if(corsair->hw_led_count > max_led_value)
{
max_led_value = corsair->hw_led_count;
}
/*---------------------------------------------------------*\
| Create a buffer map of pointers which contains the |
| layout order of colors the device expects. |
@@ -53,21 +53,32 @@ RGBController_CorsairV2SW::RGBController_CorsairV2SW(CorsairPeripheralV2Controll
| to not revert back into rainbow mode. Start a thread |
| to continuously send a keepalive packet every 50 sec |
\*-----------------------------------------------------*/
/*-----------------------------------------------------*\
| Start the keepalive clock before the thread runs. |
| A default constructed time_point sits at the clock |
| epoch, which is always older than the update |
| period, so the thread would otherwise fire a write |
| on its first pass - during detection, racing |
| whatever the caller is doing. |
\*-----------------------------------------------------*/
last_update_time = std::chrono::steady_clock::now();
keepalive_thread_run = true;
keepalive_thread = new std::thread(&RGBController_CorsairV2SW::KeepaliveThread, this);
}
RGBController_CorsairV2SW::~RGBController_CorsairV2SW()
{
Shutdown();
/*-----------------------------------------------------*\
| Close keepalive thread |
| Close keepalive thread first, so that it cannot issue |
| a write while the device is being shut down. |
\*-----------------------------------------------------*/
keepalive_thread_run = false;
keepalive_thread->join();
delete keepalive_thread;
Shutdown();
delete controller;
}
@@ -185,6 +196,15 @@ void RGBController_CorsairV2SW::SetupZones()
SetupColors();
/*---------------------------------------------------------*\
| Pad out to the slot count the device expects. A write |
| that is short of it is ignored by the hardware. |
\*---------------------------------------------------------*/
if(corsair->hw_led_count > max_led_value)
{
max_led_value = corsair->hw_led_count;
}
/*---------------------------------------------------------*\
| Create a buffer map of pointers which contains the |
| layout order of colors the device expects. |
@@ -16,13 +16,10 @@ DetectedControllers DetectCreativeAE5Device()
{
DetectedControllers detected_controllers;
LOG_INFO("[Creative SoundBlaster AE-5] Windows detection function called");
CreativeSoundBlasterAE5Controller_Windows* controller = new CreativeSoundBlasterAE5Controller_Windows();
if(controller->Initialize())
{
LOG_INFO("[Creative SoundBlaster AE-5] Device initialized successfully, registering controller");
RGBController_CreativeSoundBlasterAE5* rgb_controller = new RGBController_CreativeSoundBlasterAE5(controller);
detected_controllers.push_back(rgb_controller);
@@ -1,7 +1,7 @@
/*---------------------------------------------------------*\
| CreativeControllerDetect.cpp |
| CreativeSoundBlasterXG6ControllerDetect.cpp |
| |
| Detector for Creative devices |
| Detector for Creative Sound Blaster XG6 devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -15,14 +15,14 @@
/*---------------------------------------------------------*\
| Creative vendor ID |
\*---------------------------------------------------------*/
#define CREATIVE_VID 0x041E
#define CREATIVE_VID 0x041E
/*---------------------------------------------------------*\
| SoundCards |
| Creative Sound Blaster XG6 product ID |
\*---------------------------------------------------------*/
#define CREATIVE_SOUNDBLASTERX_G6_PID 0x3256
#define CREATIVE_SOUNDBLASTERX_G6_PID 0x3256
DetectedControllers DetectCreativeDevice(hid_device_info* info, const std::string& name)
DetectedControllers DetectCreativeSoundBlasterXG6Device(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
@@ -40,7 +40,7 @@ DetectedControllers DetectCreativeDevice(hid_device_info* info, const std::strin
return(detected_controllers);
}
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Sound Cards |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_I("Creative SoundBlasterX G6", DetectCreativeDevice, CREATIVE_VID, CREATIVE_SOUNDBLASTERX_G6_PID, 4);
/*---------------------------------------------------------*\
| Sound Cards |
\*---------------------------------------------------------*/
REGISTER_HID_DETECTOR_I("Creative SoundBlasterX G6", DetectCreativeSoundBlasterXG6Device, CREATIVE_VID, CREATIVE_SOUNDBLASTERX_G6_PID, 4);
+60 -10
View File
@@ -59,15 +59,57 @@ RGBController_DDP::~RGBController_DDP()
void RGBController_DDP::SetupZones()
{
for(unsigned int zone_idx = 0; zone_idx < devices.size(); zone_idx++)
/*-----------------------------------------------------*\
| Only set LED count on the first run |
\------------------------------------------------------*/
bool first_run = false;
if(zones.size() == 0)
{
zone led_zone;
led_zone.name = devices[zone_idx].name;
led_zone.type = ZONE_TYPE_LINEAR;
led_zone.leds_min = devices[zone_idx].num_leds;
led_zone.leds_max = devices[zone_idx].num_leds;
led_zone.leds_count = devices[zone_idx].num_leds;
zones.push_back(led_zone);
first_run = true;
}
/*-----------------------------------------------------*\
| Clear any existing color/LED configuration |
\------------------------------------------------------*/
leds.clear();
colors.clear();
zones.resize(devices.size());
/*-----------------------------------------------------*\
| Set zones and leds |
\------------------------------------------------------*/
for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
zones[zone_idx].leds_min = devices[zone_idx].num_leds;
zones[zone_idx].leds_max = devices[zone_idx].num_leds;
if(first_run)
{
zones[zone_idx].flags = ZONE_FLAG_MANUALLY_CONFIGURABLE_NAME
| ZONE_FLAG_MANUALLY_CONFIGURABLE_TYPE
| ZONE_FLAG_MANUALLY_CONFIGURABLE_MATRIX_MAP
| ZONE_FLAG_MANUALLY_CONFIGURABLE_SEGMENTS;
}
if(!(zones[zone_idx].flags & ZONE_FLAG_MANUALLY_CONFIGURED_NAME))
{
zones[zone_idx].name = devices[zone_idx].name;
}
zones[zone_idx].leds_count = devices[zone_idx].num_leds;
if(!(zones[zone_idx].flags & ZONE_FLAG_MANUALLY_CONFIGURED_TYPE))
{
zones[zone_idx].type = ZONE_TYPE_LINEAR;
}
if(!(zones[zone_idx].flags & ZONE_FLAG_MANUALLY_CONFIGURED_MATRIX_MAP))
{
zones[zone_idx].matrix_map.width = 0;
zones[zone_idx].matrix_map.height = 0;
zones[zone_idx].matrix_map.map.resize(0);
}
}
for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++)
@@ -75,14 +117,22 @@ void RGBController_DDP::SetupZones()
for(unsigned int led_idx = 0; led_idx < zones[zone_idx].leds_count; led_idx++)
{
led new_led;
new_led.name = zones[zone_idx].name + " LED " + std::to_string(led_idx + 1);
new_led.value = 0;
new_led.name = zones[zone_idx].name + " LED " + std::to_string(led_idx + 1);
leds.push_back(new_led);
}
}
SetupColors();
}
void RGBController_DDP::DeviceConfigureZone(int zone_idx)
{
if((size_t)zone_idx < zones.size())
{
SetupZones();
}
}
void RGBController_DDP::DeviceUpdateLEDs()
{
std::vector<unsigned int> brightness_adjusted_colors;
@@ -21,6 +21,8 @@ public:
void SetupZones();
void DeviceConfigureZone(int zone_idx);
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
@@ -40,8 +40,4 @@ DetectedControllers DetectDygmaRaiseControllers()
}
REGISTER_DETECTOR("Dygma Raise", DetectDygmaRaiseControllers);
/*---------------------------------------------------------------------------------------------------------*\
| Entries for dynamic UDEV rules |
| |
| DUMMY_DEVICE_DETECTOR("Dygma Raise", DetectDygmaRaiseControllers, 0x1209, 0x2201 ) |
\*---------------------------------------------------------------------------------------------------------*/
REGISTER_CUSTOM_UDEV_RULE(dygma_raise, "Dygma Raise", "SUBSYSTEMS==\"serial|hidraw\", ATTRS{idVendor}==\"1209\", ATTRS{idProduct}==\"2201\", TAG+=\"uaccess\", TAG+=\"Dygma_Raise\"");
@@ -223,33 +223,68 @@ RGBController_E131::~RGBController_E131()
void RGBController_E131::SetupZones()
{
/*-----------------------------------------------------*\
| Add Zones |
| Only set LED count on the first run |
\*-----------------------------------------------------*/
for(std::size_t zone_idx = 0; zone_idx < devices.size(); zone_idx++)
{
zone led_zone;
led_zone.name = devices[zone_idx].name;
led_zone.type = ZONE_TYPE_LINEAR;
led_zone.leds_min = devices[zone_idx].num_leds;
led_zone.leds_max = devices[zone_idx].num_leds;
led_zone.leds_count = devices[zone_idx].num_leds;
led_zone.flags = ZONE_FLAG_MANUALLY_CONFIGURABLE_TYPE
| ZONE_FLAG_MANUALLY_CONFIGURABLE_MATRIX_MAP
| ZONE_FLAG_MANUALLY_CONFIGURABLE_SEGMENTS;
bool first_run = false;
zones.push_back(led_zone);
if(zones.size() == 0)
{
first_run = true;
}
/*-----------------------------------------------------*\
| Add LEDs |
| Clear any existing color/LED configuration |
\*-----------------------------------------------------*/
leds.clear();
colors.clear();
zones.resize(devices.size());
/*-----------------------------------------------------*\
| Set zones and leds |
\*-----------------------------------------------------*/
for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
zones[zone_idx].leds_min = devices[zone_idx].num_leds;
zones[zone_idx].leds_max = devices[zone_idx].num_leds;
if(first_run)
{
zones[zone_idx].flags = ZONE_FLAG_MANUALLY_CONFIGURABLE_NAME
| ZONE_FLAG_MANUALLY_CONFIGURABLE_TYPE
| ZONE_FLAG_MANUALLY_CONFIGURABLE_MATRIX_MAP
| ZONE_FLAG_MANUALLY_CONFIGURABLE_SEGMENTS;
}
if(!(zones[zone_idx].flags & ZONE_FLAG_MANUALLY_CONFIGURED_NAME))
{
zones[zone_idx].name = devices[zone_idx].name;
}
zones[zone_idx].leds_count = devices[zone_idx].num_leds;
if(!(zones[zone_idx].flags & ZONE_FLAG_MANUALLY_CONFIGURED_TYPE))
{
zones[zone_idx].type = ZONE_TYPE_LINEAR;
}
if(!(zones[zone_idx].flags & ZONE_FLAG_MANUALLY_CONFIGURED_MATRIX_MAP))
{
zones[zone_idx].matrix_map.width = 0;
zones[zone_idx].matrix_map.height = 0;
zones[zone_idx].matrix_map.map.resize(0);
}
}
/*-----------------------------------------------------*\
| Add LEDs |
\*-----------------------------------------------------*/
for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
for(std::size_t led_idx = 0; led_idx < zones[zone_idx].leds_count; led_idx++)
{
led new_led;
new_led.name = zones[zone_idx].name + " LED ";
new_led.name = zones[zone_idx].name + " LED ";
new_led.name.append(std::to_string(led_idx));
leds.push_back(new_led);
@@ -259,6 +294,14 @@ void RGBController_E131::SetupZones()
SetupColors();
}
void RGBController_E131::DeviceConfigureZone(int zone_idx)
{
if((size_t)zone_idx < zones.size())
{
SetupZones();
}
}
void RGBController_E131::DeviceUpdateLEDs()
{
int color_idx = 0;
@@ -35,6 +35,8 @@ public:
void SetupZones();
void DeviceConfigureZone(int zone_idx);
void DeviceUpdateLEDs();
void DeviceUpdateZoneLEDs(int zone);
void DeviceUpdateSingleLED(int led);
@@ -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);
@@ -426,6 +427,7 @@ REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5080 WHITE",
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_2, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC BTF", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_BTF, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC BTF", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_BTF_2, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC WHITE", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL LC GeForce RTX 5090 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_LC_RTX_5090_O32G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL LC OC GeForce RTX 5090 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_LC_OC_RTX_5090_O32G_GAMING, 0x67);
@@ -0,0 +1,49 @@
/*---------------------------------------------------------*\
| EVGAKeyboardControllerDetect.cpp |
| |
| Detector for EVGA keyboard devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "DetectionManager.h"
#include "RGBController_EVGAKeyboard.h"
/*---------------------------------------------------------*\
| EVGA USB vendor ID |
\*---------------------------------------------------------*/
#define EVGA_USB_VID 0x3842
/*---------------------------------------------------------*\
| Keyboard product IDs |
\*---------------------------------------------------------*/
#define Z15_ISO_PID 0x260E
#define Z15_ANSI_PID 0x2608
#define Z20_ANSI_PID 0x260A
#define Z20_UK_PID 0x2610
DetectedControllers DetectEVGAKeyboardControllers(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
EVGAKeyboardController* controller = new EVGAKeyboardController(dev, info->path, info->product_id, name);
RGBController_EVGAKeyboard* rgb_controller = new RGBController_EVGAKeyboard(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
REGISTER_HID_DETECTOR_IPU("EVGA Z15 Keyboard", DetectEVGAKeyboardControllers, EVGA_USB_VID, Z15_ISO_PID, 1, 0x08, 0x4B);
REGISTER_HID_DETECTOR_IPU("EVGA Z15 Keyboard", DetectEVGAKeyboardControllers, EVGA_USB_VID, Z15_ANSI_PID, 1, 0x08, 0x4B);
REGISTER_HID_DETECTOR_IPU("EVGA Z20 Keyboard", DetectEVGAKeyboardControllers, EVGA_USB_VID, Z20_ANSI_PID, 1, 0x08, 0x4B);
REGISTER_HID_DETECTOR_IPU("EVGA Z20 Keyboard", DetectEVGAKeyboardControllers, EVGA_USB_VID, Z20_UK_PID, 1, 0x08, 0x4B);
@@ -1,7 +1,7 @@
/*---------------------------------------------------------*\
| EVGAUSBControllerDetect.cpp |
| EVGAMouseControllerDetect.cpp |
| |
| Detector for EVGA USB devices |
| Detector for EVGA mouse devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
@@ -9,45 +9,18 @@
#include <hidapi.h>
#include "DetectionManager.h"
#include "RGBController_EVGAKeyboard.h"
#include "RGBController_EVGAMouse.h"
/*---------------------------------------------------------*\
| EVGA USB vendor ID |
\*---------------------------------------------------------*/
#define EVGA_USB_VID 0x3842
/*---------------------------------------------------------*\
| Keyboard product IDs |
\*---------------------------------------------------------*/
#define Z15_ISO_PID 0x260E
#define Z15_ANSI_PID 0x2608
#define Z20_ANSI_PID 0x260A
#define Z20_UK_PID 0x2610
#define EVGA_USB_VID 0x3842
/*---------------------------------------------------------*\
| Mouse product IDs |
\*---------------------------------------------------------*/
#define X20_WIRED_PID 0x2420
#define X20_WIRELESS_ADAPTER_PID 0x2402
DetectedControllers DetectEVGAKeyboardControllers(hid_device_info* info, const std::string& name)
{
DetectedControllers detected_controllers;
hid_device* dev;
dev = hid_open_path(info->path);
if(dev)
{
EVGAKeyboardController* controller = new EVGAKeyboardController(dev, info->path, info->product_id, name);
RGBController_EVGAKeyboard* rgb_controller = new RGBController_EVGAKeyboard(controller);
detected_controllers.push_back(rgb_controller);
}
return(detected_controllers);
}
#define X20_WIRED_PID 0x2420
#define X20_WIRELESS_ADAPTER_PID 0x2402
DetectedControllers DetectEVGAMouse(hid_device_info* info, const std::string &name, int connection_type)
{
@@ -77,10 +50,5 @@ DetectedControllers DetectWirelessEVGAMouse(hid_device_info* info, const std::st
return(DetectEVGAMouse(info, name, EVGA_PERIPHERAL_CONNECTION_TYPE_WIRELESS));
}
REGISTER_HID_DETECTOR_IPU("EVGA Z15 Keyboard", DetectEVGAKeyboardControllers, EVGA_USB_VID, Z15_ISO_PID, 1, 0x08, 0x4B);
REGISTER_HID_DETECTOR_IPU("EVGA Z15 Keyboard", DetectEVGAKeyboardControllers, EVGA_USB_VID, Z15_ANSI_PID, 1, 0x08, 0x4B);
REGISTER_HID_DETECTOR_IPU("EVGA Z20 Keyboard", DetectEVGAKeyboardControllers, EVGA_USB_VID, Z20_ANSI_PID, 1, 0x08, 0x4B);
REGISTER_HID_DETECTOR_IPU("EVGA Z20 Keyboard", DetectEVGAKeyboardControllers, EVGA_USB_VID, Z20_UK_PID, 1, 0x08, 0x4B);
REGISTER_HID_DETECTOR_IPU("EVGA X20 Gaming Mouse", DetectWiredEVGAMouse, EVGA_USB_VID, X20_WIRED_PID, 2, 0xFFFF, 0);
REGISTER_HID_DETECTOR_IPU("EVGA X20 USB Receiver", DetectWirelessEVGAMouse, EVGA_USB_VID, X20_WIRELESS_ADAPTER_PID, 2, 0xFFFF, 0);
@@ -0,0 +1,268 @@
/*---------------------------------------------------------*\
| EVGAX12Controller.cpp |
| |
| Driver for the EVGA X12 gaming mouse |
| |
| QuickMythril 08 Aug 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <chrono>
#include <cstring>
#include <thread>
#include "EVGAX12Controller.h"
#include "LogManager.h"
#include "StringUtils.h"
#define EVGA_X12_HEADER 0xEA
#define EVGA_X12_DIRECTION_GET 0x01
#define EVGA_X12_DIRECTION_SET 0x02
#define EVGA_X12_COMMAND_MODE 0x01
#define EVGA_X12_COMMAND_STATIC 0x02
#define EVGA_X12_START_WITH_PROFILE 0x01
#define EVGA_X12_READ_ATTEMPTS 5
#define EVGA_X12_READ_DELAY_MS 5
#define EVGA_X12_WRITE_ATTEMPTS 3
static const unsigned int EVGA_X12_LED_TO_FIRMWARE_SLOT[EVGA_X12_LED_COUNT] =
{
EVGA_X12_FIRMWARE_SLOT_HEAD_LEFT,
EVGA_X12_FIRMWARE_SLOT_WHEEL,
EVGA_X12_FIRMWARE_SLOT_LOGO,
};
EVGAX12Controller::EVGAX12Controller(hid_device* dev_handle, const hid_device_info& info, const std::string& dev_name)
{
dev = dev_handle;
location = info.path;
name = dev_name;
}
EVGAX12Controller::~EVGAX12Controller()
{
hid_close(dev);
}
std::string EVGAX12Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string EVGAX12Controller::GetNameString()
{
return(name);
}
std::string EVGAX12Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
EVGAX12LightingState EVGAX12Controller::GetLightingState()
{
std::lock_guard<std::mutex> lock(device_mutex);
EVGAX12LightingState state = {};
unsigned char packet[EVGA_X12_REPORT_SIZE];
state.modes_valid = GetReport(EVGA_X12_COMMAND_MODE, packet);
if(state.modes_valid)
{
for(unsigned int led_idx = 0; led_idx < EVGA_X12_LED_COUNT; led_idx++)
{
unsigned int firmware_slot = EVGA_X12_LED_TO_FIRMWARE_SLOT[led_idx];
state.modes[led_idx] = packet[4 + firmware_slot];
}
}
state.static_valid = GetReport(EVGA_X12_COMMAND_STATIC, packet);
if(state.static_valid)
{
for(unsigned int led_idx = 0; led_idx < EVGA_X12_LED_COUNT; led_idx++)
{
unsigned int firmware_slot = EVGA_X12_LED_TO_FIRMWARE_SLOT[led_idx];
unsigned int offset = 4 + (firmware_slot * 4);
state.brightness[led_idx] = packet[offset];
state.colors[led_idx] = ToRGBColor(packet[offset + 1], packet[offset + 2], packet[offset + 3]);
}
}
return(state);
}
bool EVGAX12Controller::SetMode(unsigned char mode)
{
std::lock_guard<std::mutex> lock(device_mutex);
return(SetModeReport(mode));
}
bool EVGAX12Controller::SetModeReport(unsigned char mode)
{
unsigned char packet[EVGA_X12_REPORT_SIZE] = {};
packet[0] = EVGA_X12_REPORT_ID;
packet[1] = EVGA_X12_HEADER;
packet[2] = EVGA_X12_DIRECTION_SET;
packet[3] = EVGA_X12_COMMAND_MODE;
for(unsigned int led_idx = 0; led_idx < EVGA_X12_LED_COUNT; led_idx++)
{
unsigned int firmware_slot = EVGA_X12_LED_TO_FIRMWARE_SLOT[led_idx];
packet[4 + firmware_slot] = mode;
}
return(SendReport(packet));
}
bool EVGAX12Controller::SetStatic(const std::vector<RGBColor>& colors, unsigned char brightness)
{
if(colors.size() < EVGA_X12_LED_COUNT)
{
LOG_ERROR("[%s] Cannot set static lighting with only %u colors", name.c_str(), (unsigned int)colors.size());
return(false);
}
std::lock_guard<std::mutex> lock(device_mutex);
return(SetStaticReport(colors, brightness));
}
bool EVGAX12Controller::SetStaticAndActivate(const std::vector<RGBColor>& colors, unsigned char brightness)
{
if(colors.size() < EVGA_X12_LED_COUNT)
{
LOG_ERROR("[%s] Cannot set static lighting with only %u colors", name.c_str(), (unsigned int)colors.size());
return(false);
}
std::lock_guard<std::mutex> lock(device_mutex);
if(!SetStaticReport(colors, brightness))
{
return(false);
}
std::this_thread::sleep_for(std::chrono::milliseconds(EVGA_X12_READ_DELAY_MS));
return(SetModeReport(EVGA_X12_MODE_STATIC));
}
bool EVGAX12Controller::SetStaticReport(const std::vector<RGBColor>& colors, unsigned char brightness)
{
unsigned char packet[EVGA_X12_REPORT_SIZE] = {};
packet[0] = EVGA_X12_REPORT_ID;
packet[1] = EVGA_X12_HEADER;
packet[2] = EVGA_X12_DIRECTION_SET;
packet[3] = EVGA_X12_COMMAND_STATIC;
for(unsigned int firmware_slot = 0; firmware_slot < EVGA_X12_FIRMWARE_SLOT_COUNT; firmware_slot++)
{
packet[20 + firmware_slot] = EVGA_X12_START_WITH_PROFILE;
}
for(unsigned int led_idx = 0; led_idx < EVGA_X12_LED_COUNT; led_idx++)
{
unsigned int firmware_slot = EVGA_X12_LED_TO_FIRMWARE_SLOT[led_idx];
unsigned int offset = 4 + (firmware_slot * 4);
packet[offset] = brightness;
packet[offset + 1] = RGBGetRValue(colors[led_idx]);
packet[offset + 2] = RGBGetGValue(colors[led_idx]);
packet[offset + 3] = RGBGetBValue(colors[led_idx]);
}
for(unsigned int attempt = 0; attempt < EVGA_X12_WRITE_ATTEMPTS; attempt++)
{
if(!SendReport(packet))
{
return(false);
}
std::this_thread::sleep_for(std::chrono::milliseconds(EVGA_X12_READ_DELAY_MS));
unsigned char reply[EVGA_X12_REPORT_SIZE];
if(GetReport(EVGA_X12_COMMAND_STATIC, reply))
{
bool matches = true;
for(unsigned int firmware_slot = 0; firmware_slot < EVGA_X12_FIRMWARE_SLOT_COUNT; firmware_slot++)
{
unsigned int offset = 4 + (firmware_slot * 4);
if(std::memcmp(&packet[offset], &reply[offset], 4) != 0)
{
matches = false;
break;
}
}
if(matches)
{
return(true);
}
}
}
LOG_VERBOSE("[%s] Retries exhausted setting static lighting", name.c_str());
return(false);
}
bool EVGAX12Controller::GetReport(unsigned char command, unsigned char* packet)
{
std::memset(packet, 0, EVGA_X12_REPORT_SIZE);
packet[0] = EVGA_X12_REPORT_ID;
packet[1] = EVGA_X12_HEADER;
packet[2] = EVGA_X12_DIRECTION_GET;
packet[3] = command;
if(!SendReport(packet))
{
return(false);
}
for(unsigned int attempt = 0; attempt < EVGA_X12_READ_ATTEMPTS; attempt++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(EVGA_X12_READ_DELAY_MS));
std::memset(packet, 0, EVGA_X12_REPORT_SIZE);
packet[0] = EVGA_X12_REPORT_ID;
int bytes_read = hid_get_feature_report(dev, packet, EVGA_X12_REPORT_SIZE);
if(bytes_read == EVGA_X12_REPORT_SIZE && packet[1] == EVGA_X12_HEADER && packet[3] == command)
{
return(true);
}
if(bytes_read < 0)
{
LOG_DEBUG("[%s] Error reading feature report: %ls", name.c_str(), hid_error(dev));
return(false);
}
}
LOG_VERBOSE("[%s] Retries exhausted reading command 0x%02X", name.c_str(), command);
return(false);
}
bool EVGAX12Controller::SendReport(const unsigned char* packet)
{
int bytes_written = hid_send_feature_report(dev, packet, EVGA_X12_REPORT_SIZE);
if(bytes_written != EVGA_X12_REPORT_SIZE)
{
LOG_DEBUG("[%s] Error writing feature report: %ls", name.c_str(), hid_error(dev));
return(false);
}
return(true);
}
@@ -0,0 +1,80 @@
/*---------------------------------------------------------*\
| EVGAX12Controller.h |
| |
| Driver for the EVGA X12 gaming mouse |
| |
| QuickMythril 08 Aug 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <array>
#include <mutex>
#include <string>
#include <vector>
#include <hidapi.h>
#include "RGBController.h"
#define EVGA_X12_REPORT_ID 0x07
#define EVGA_X12_REPORT_SIZE 40
#define EVGA_X12_LED_COUNT 3
#define EVGA_X12_FIRMWARE_SLOT_COUNT 4
#define EVGA_X12_BRIGHTNESS_MIN 0
#define EVGA_X12_BRIGHTNESS_MAX 255
enum
{
EVGA_X12_MODE_OFF = 0,
EVGA_X12_MODE_STATIC = 1,
};
/*---------------------------------------------------------*\
| HEAD_LEFT drives both physical side lights. HEAD_RIGHT |
| is writable and readable, but has no physical emitter. |
\*---------------------------------------------------------*/
enum
{
EVGA_X12_FIRMWARE_SLOT_HEAD_LEFT = 0,
EVGA_X12_FIRMWARE_SLOT_HEAD_RIGHT = 1,
EVGA_X12_FIRMWARE_SLOT_WHEEL = 2,
EVGA_X12_FIRMWARE_SLOT_LOGO = 3,
};
struct EVGAX12LightingState
{
std::array<unsigned char, EVGA_X12_LED_COUNT> modes;
std::array<unsigned char, EVGA_X12_LED_COUNT> brightness;
std::array<RGBColor, EVGA_X12_LED_COUNT> colors;
bool modes_valid;
bool static_valid;
};
class EVGAX12Controller
{
public:
EVGAX12Controller(hid_device* dev_handle, const hid_device_info& info, const std::string& dev_name);
~EVGAX12Controller();
std::string GetDeviceLocation();
std::string GetNameString();
std::string GetSerialString();
EVGAX12LightingState GetLightingState();
bool SetMode(unsigned char mode);
bool SetStatic(const std::vector<RGBColor>& colors, unsigned char brightness);
bool SetStaticAndActivate(const std::vector<RGBColor>& colors, unsigned char brightness);
private:
bool GetReport(unsigned char command, unsigned char* packet);
bool SetModeReport(unsigned char mode);
bool SetStaticReport(const std::vector<RGBColor>& colors, unsigned char brightness);
bool SendReport(const unsigned char* packet);
hid_device* dev;
std::string location;
std::string name;
std::mutex device_mutex;
};

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