Compare commits

...
Author SHA1 Message Date
Adam Honse 5e81e26fcc OpenRGB Release Candidate 1.0rc3.1 2026-08-23 15:59:48 -05:00
Adam Honse b97b429684 Update upstream CI rules to run pipelines for tagged commits 2026-08-23 15:40:01 -05:00
Adam Honse 9f06fc63a1 Update metainfo xml 2026-08-13 01:00:10 -05:00
Adam Honse d2dd9dcc73 Address security issues in release_candidate_1.0rc3
- CVE-2026-59682: OpenRGB: arbitrary file overwrite and deletion local and remote
- CVE-2026-59683: OpenRGB: local and remote system compromise via arbitrary file write using attacker controlled strings
- CVE-2026-18794: OpenRGB: insufficient input data checks lead to Denial-of-Service, memory overread and overwrite
2026-08-13 00:59:02 -05:00
Adam Honse 6fbcf62d76 OpenRGB Release Candidate 1.0rc3 2026-06-28 16:25:51 -05:00
Vedant Bhandare 1a6943fe17 Add support for MSI PRO Z890-P WIFI (MS-7E34) 2026-06-28 15:55:58 -05:00
Dante M cde2343d09 MSI Mystic Light: Add support for MSI PRO B760M-VC WIFI (MS-7D37) 2026-06-28 15:50:09 -05:00
Adam Honse c746b30287 Add Keychron K-series V2 keyboards from wls_2025q1 branch 2026-06-28 15:45:48 -05:00
Ace 3582b96dd9 Fix Logitech G915 TKL: correct PID mapping and HID packet sizes 2026-06-28 03:21:57 -05:00
Adam Honse d258065e36 Convert static class variables to defines to fix linker error in debug builds 2026-06-28 01:30:40 -05:00
Adam Honse 7627466891 Get Keychron firmware version 2026-06-28 01:30:32 -05:00
Adam Honse c476ee46f8 Flatpak is now considered official 2026-06-28 00:51:43 -05:00
Adam Honse 6fc68f4e62 Add the rest of the Keychron keyboards in the 2025q3 branch 2026-06-27 20:03:50 -05:00
Adam Honse 75bac1fd4e Add all the Keychron K series keyboards from the 2025q3 firmware branch 2026-06-26 10:17:06 -05:00
Richard Harris 94042375a7 Add support for SayoDevice E1 2026-06-25 20:16:18 -05:00
Adam Honse 103306c6c8 Add all the Keychron C series keyboards from the 2025q3 firmware branch 2026-06-25 16:04:22 -05:00
Adam Honse 6d37d262ea Add all the Keychron Q series keyboards from the 2025q3 firmware branch 2026-06-25 12:55:39 -05:00
Adam Honse 473331dd3a Add option to skip reads for most packets as even short reads slow K57 effects 2026-06-25 01:27:22 -05:00
Adam Honse bc9d03ef48 Use short wait for StartTransaction if CTRL1 is selected, as K57 does not appear to respond and we only use the response for determining if switching to CTRL1 is needed 2026-06-25 01:15:10 -05:00
Sam Van maele 740c88fe0c Corsair V2: Fix K70 RGB PRO crash & add spacebar side lights 2026-06-25 00:42:56 -05:00
D W 684ef09168 Razer BlackWidow v4 Pro: new USB IPU values after upgrading firmware 2026-06-25 00:41:36 -05:00
Adam Honse e3b08fc15d Move Corsair K57 (Wired) to CorsairPeripheralV2Controller as it uses the same protocol 2026-06-25 00:35:01 -05:00
Adam Honse 5c931a61b4 Add support for Keychron K10 V2 in QMKKeychronController 2026-06-24 23:33:23 -05:00
Wolfieee Wolf ad7534d00d fix(razer): apply Tartarus V2 keypad LED layout in OpenRGB 2026-06-24 22:01:20 -05:00
Richard Harris c81f8699f1 Add support for Govee H6022 Smart Table Lamp 2 2026-06-24 21:52:47 -05:00
Adam Honse 2af03eb0dc Return false if no PawnIO bus was detected so that the warning dialog actually displays 2026-06-24 18:30:37 -05:00
Adam Honse 5064ba3f1d Update warning messages and remove references to help.openrgb.org. 2026-06-24 15:02:20 -05:00
Adam Honse c5abe47859 Create a common type for storing QMK LED info and use it in both VialRGB and Keychron controllers 2026-06-24 11:55:26 -05:00
Nikhil Jeikar P M 8fdd0e40f8 Adding support for MSI PRO X870E-S EVO WIFI 2026-06-24 16:14:26 +05:30
HorrorTroll a6a07ba0d7 Added support for Colorful iGame GeForce RTX 5060 Ultra W OC when using OC mode 2026-06-23 23:50:18 -05:00
Ken Sanislo 614fef4189 G515 LS TKL and G502 X PLUS support, via Logitech HID++ 2.0 controller with feature discovery 2026-06-23 19:33:18 -05:00
Adam Honse d3e7dba2b7 Add supported feature check in QMKKeychronController 2026-06-23 19:22:50 -05:00
Stefanie Jane 39343c3e9e Fix buffer overflow in KeyboardLayoutManager::GetKeyMap() 2026-06-22 20:49:53 -05:00
Adam Honse 73865fec59 Improve matrix generation for QMK Keychron controller, clean up modes, implement manual save 2026-06-22 18:42:03 -05:00
Adam Honse b508818f23 Update PawnIO Modules to Release 0.2.9 and re-add Intel Skylake-X IMC SMBus support 2026-06-21 22:17:49 -05:00
lin bef8ae0a2f Add support for Gigabyte AORUS GeForce RTX 5090 D V2 MASTER ICE 2026-06-22 10:33:29 +08:00
Adam Honse cce2d41500 Cleanups to QMKKeychronController and create common QMK VIA protocol header 2026-06-21 21:14:46 -05:00
Amadej Kastelic 175f3ed338 feat: add Keychron Q1 HE keyboard support and restructure Keychron controllers 2026-06-21 21:14:46 -05:00
Adam Honse c2cc7c6163 Move priority setup to ResourceManager and make it configurable 2026-06-21 21:14:46 -05:00
RedBlackAka 8fee041fad Lower process priority 2026-06-21 21:14:46 -05:00
Wolfieee Wolf dfc1f8524a fix(razer): add BlackWidow X Chroma keyboard layout 2026-06-21 18:30:31 -05:00
Wolfieee Wolf 752bd2e35a fix(intel): number Arc A770 LE per-LED dropdown labels 2026-06-21 18:26:31 -05:00
I-Kerosin-I 01fe0b4630 Add support for Gigabyte GeForce RTX 4070 GAMING OC V2 with AD103 2026-06-21 18:05:32 -05:00
Daniel Clark e7f8bc0136 Fix X870 Gaming WIFI6 and X870 Aorus Tachyon ICE layouts 2026-06-17 06:45:01 -07:00
Rodrigo Henrique 370918e07f Add Gigabyte GeForce RTX 5070 Eagle OC ICE 2026-06-17 07:35:31 -05:00
trjohnson19 133966677b Add support for ASUS TUF RTX5070 Ti OC BTF White 2026-06-16 07:52:28 -05:00
kStor2poche 44f84e4781 Add Gigabyte Radeon RX 6800 XT GAMING OC. Closes #5657 2026-06-16 07:41:56 -05:00
Roy Biggins a8354ee046 Add Gigabyte Aorus Waterforce X II 360 AIO controller (Castor3) 2026-06-16 00:02:41 -05:00
Luca Cesarano 4550e3073a Fix NZXT Kraken Elite V2 effects 2026-06-15 23:58:57 -05:00
Adam Honse abe7a49092 Updates to DRAM controllers to avoid use of SMBus Quick Write and add fallback paths if SMBus Block Write is unavailable, in preparation for X299 SMBus support 2026-06-15 23:54:33 -05:00
Adam Honse 82a486bfb6 Update SMBus tools page to add more SMBus operation commands 2026-06-15 23:54:33 -05:00
Victor Hugo Garzo Prado ad06e733f8 Add missing push_back to insert Corsair AIO leds into leds vector 2026-06-15 23:40:05 -05:00
Adam Honse 4306603a28 Remove Skylake-X IMC SMBus PawnIO module for now as it does not support the necessary features yet 2026-06-09 22:58:41 -05:00
Jerry Fan 554f834a90 Fix typo of custled in RealtekARGBController 2026-06-09 08:16:39 -05:00
manuelulreich 7236bec61f [New Device] Add razer blackwidow v4 low profile tenkeyless hyperspeed 2026-06-09 07:29:23 -05:00
XPRAMT c799f1bfe9 Improve ColorWheel drag behavior 2026-06-09 07:19:14 -05:00
Adam Honse f15c3c1420 Add Redragon M921 2026-06-07 14:08:34 -05:00
Adam Honse aa67045330 Fix duplicate entries in SteelSeriesControllerDetect 2026-06-07 13:28:46 -05:00
Alexey Kopytko 736640f38d Add support for Zotac RTX 5090 SOLID OC 2026-06-07 13:21:29 -05:00
David Glushkov e9caf663bc Fix MSILaptopController ALC zone mapping and packet initialization 2026-06-07 13:09:19 -05:00
Daniel Clark 8afbea53de Update RGBFusion2USB Driver. 2026-06-07 01:13:03 -05:00
metroite c3d496ad5b Initial commit for SteelSeries Apex Pro TKL Gen 3 2026-06-07 01:09:30 -05:00
ilyaKhalidov88 06501f528d Add support for ASUS ROG Astral 5090 2026-06-07 00:46:27 -05:00
Jerry Fan f6a678c1a4 Add support for RealtekARGBController 2026-06-05 01:03:29 -05:00
Andy Herbert 30690d0ebd New device- MSI MAG272CQR monitor 2026-06-04 11:54:51 -05:00
Kyle Finter 7a830cddf9 Added new Govee devices H612F and H607C 2026-06-04 07:27:02 +00:00
Adam Honse 3dc1365d39 Add PowerShell implementation of build-msi for building MSI on Windows, add additional information about PawnIO and the OpenRGB system service to the installer. 2026-06-02 22:07:17 -05:00
Adam Honse b03cd94ae8 Fix incorrect firmware packet format and add timeouts to HID reads in ThermaltakeRiingController 2026-06-02 01:07:33 -05:00
Jefferson González bbf348d273 Add HyperX Eve 1800 keyboard support 2026-06-02 00:15:19 -05:00
David Glushkov dce48eb415 Add MSI Raider A18 HX A9WJG SteelSeries RGB 2026-06-02 00:05:18 -05:00
Ox HaK decf14635d Add Fnatic Streak65 support 2026-06-01 08:27:17 -05:00
Richard Harris db4f49bbba Implement Direct mode support for Roccat Vulcan TKL (non-Pro) 2026-05-31 12:38:57 -05:00
HorrorTroll 26824fcb6e Added support for Colorful iGame GeForce RTX 5060 Ultra W OC 2026-05-31 15:12:36 +07:00
Jerry Fan 8afad91b33 Add support for RealtekBridgeController 2026-05-29 07:46:22 -05:00
Adam Honse 69de0b13c4 Update PawnIO modules to 0.2.6 and enable Intel Skylake IMC SMBus module 2026-05-28 23:48:42 -05:00
alstruit 967153bc10 Add support for ASUS ROG Gladius III Core 2026-05-25 12:08:43 -05:00
JAO1988 971d9be1bd Added Colorful iGame RTX 5060TI 16GB Ultra W DUO Variant 2026-05-23 23:10:36 -05:00
Mark Willis 7a3faef886 Initial commit for ASUS TUF GeForce RTX 5070 Ti Gaming White OC 2026-05-22 12:25:59 -05:00
Purrple Kitten f67030fcc7 Add support for the Gigabyte AORUS RTX 5080 MASTER ICE 2026-05-18 07:30:37 -05:00
Alex Jawhari 7353e99449 Fix AMD GPU i2c bus PCI ID detection on Linux (sysfs path traversal) 2026-05-17 23:58:21 -05:00
JAO1988 91d9597c89 Added Support for ASUS ROG ASTRAL GeForce RTX 5080 WHITE 2026-05-17 23:47:55 -05:00
Adam Honse 2eb569912b XPGSummonerKeyboardController - Remove fake modes 2026-05-13 11:49:05 -05:00
Ken Sanislo 9410d9c066 Add support for Thrustmaster Sol series joystick RGB LEDs 2026-05-09 00:21:40 -05:00
Adam Honse 29d8788e25 Allow value 0x27 at address 0x00 for Galax GPU controllers at address 0x23 2026-05-08 20:05:40 -05:00
Elvis Pranskevichus 0304c50bc6 Add support for Gigabyte AORUS RX 7900 XTX ELITE 24G 2026-05-08 00:12:43 -05:00
Adam Honse dd22150cfe Add vertical spacer to OpenRGBDeviceInfoPage 2026-05-07 16:17:46 -05:00
Barry H 07e7feca56 Nollie 2.1 firmware for Master 2026-05-04 14:16:29 -05:00
Sha_425 a93eec8f50 Add ASUS ROG STRIX RTX 3060 Ti V2 LHR (subsystem 1043:8835) 2026-05-03 12:15:13 -05:00
w43322 88bad762e5 add MSI MEG Z890 UNIFY-X (MS-7E20) support 2026-05-02 19:19:10 -05:00
w43322 18dfe6ca8b add ASUS TUF GeForce RTX 5060 Gaming OC support 2026-05-02 19:15:16 -05:00
mangobiche 96dfa7009f Add support for Corsair Vengeance RGB DDR5 CMH32GX5M2E6000C36 2026-05-02 15:06:09 -05:00
NaifuKishi 92a9d8ad11 Initial commit for ASUS TUF Radeon RX 7900 GRE Gaming OC 2026-05-01 11:09:31 -05:00
Francesco P 2ab9094b41 Add support for MSI MAG B850 TOMAHAWK WiFi7 2026-04-29 21:54:51 -05:00
M1dDler Official f6154ccbc4 Add msi rtx 5070 gaming trio 2026-04-28 20:04:53 -05:00
Illustrious-Net-8110 0be01dde19 Add Asus Ryujin AIO Gen1 support 2026-04-24 07:35:53 -05:00
Adam Honse e113e91423 Deploy QOpenGL and QtCore5Compat frameworks used by the OpenRGB Effects Plugin 2026-04-23 23:42:36 -05:00
Adam Honse 28c63b8b9d Call macdeployqt a second time on Qt6 builds to ensure frameworks depended on by default plugins are deployed 2026-04-23 12:37:49 -05:00
Adam Honse c5aac4c44f Fix libsharpyuv macdeployqt error for Qt5 builds 2026-04-23 10:52:54 -05:00
Adam Honse 42e7f2a5c3 Ignore SDK version in Mac build 2026-04-23 03:56:07 -05:00
Adam Honse ae9ae18d31 Add build-macos.sh script for easier MacOS builds 2026-04-23 00:37:45 -05:00
ricarim f89cdac198 Initial commit for HyperX Origins 2 65 2026-04-20 23:32:49 -05:00
acehighdan e8f345ff77 Sync autostart system configuration with OpenRGB settings on startup
If the autostart system configuration is deleted (e.g. by a reinstall or
package manager) but the JSON config still has enabled=true, OpenRGB
would silently fail to start at login. Fix by calling ConfigureAutoStart()
in the settings page constructor so the system configuration is always
reconciled with the stored JSON state every time OpenRGB starts.

Commit amended to make wording more generic by Adam Honse <[email protected]>
2026-04-20 23:32:49 -05:00
Dmitry Kychanov aff0610df9 Fix default language load on start 2026-04-20 23:32:49 -05:00
JAO1988 9199d062d1 Add Support for MSI GeForce RTX 5080 SUPRIM LIQUID SOC 2026-04-20 23:32:49 -05:00
Dmitry Kychanov 0ec4aaa629 Fix losing software info upon changing language 2026-04-20 23:32:49 -05:00
Adam Honse 9fca99b4ef Reduce i2c_smbus_linux PCI log messages from warning to info, they are expected in many cases 2026-04-20 23:32:31 -05:00
Vasily Galkin 14853895f3 Add support for Gigabyte GTX 1050 G1 Gaming 2GB (1458:372B) 2026-04-20 23:32:31 -05:00
Adam Honse d79a400c8c Fix corrupted text in TabLabels when translation occurs after the constructor 2026-04-20 23:32:31 -05:00
Adam Honse f741385e43 Fix bug introduced in MSI controller merge 2026-04-20 23:32:31 -05:00
Adam Honse 840bd92afb Add AI guidelines section to CONTRIBUTING.md 2026-04-20 23:32:31 -05:00
Nameless Monarch 96e1fb0b10 Add support for MSI PRO B850M-VC WIFI6E 2026-04-20 23:32:31 -05:00
Adam Honse 388557e5f9 Ensure device view is initialized if there are no LEDs to avoid accessing out of bounds 2026-04-20 23:32:17 -05:00
Slim Amamou 3f32817272 Added support for PNY GeForce RTX 5060Ti ARGB Epic-X OC 2026-04-20 23:32:10 -05:00
Adam Honse 056b383536 Add SMBus Sleep Mode settings page 2026-04-15 00:29:54 -05:00
Adam Honse 0691f40588 Update PawnIO modules to release 0.2.4 and implement SMBus sleep mode setting 2026-04-15 00:03:38 -05:00
Adam Honse 800ca0aa7c Fix same heap buffer overflow in RGBController_MSIMysticLight761.cpp 2026-04-13 11:42:03 -05:00
kuroki-kael a91679609b Fix heap-buffer-overflow in RGBController_MSIMysticLight185::GetDeviceConfig() 2026-04-13 11:40:46 -05:00
Adam Honse 19f9408990 Fix remaining warning 2026-04-13 11:34:28 -05:00
Adam Honse 0eea0c9aca Fix warnings 2026-04-13 10:54:34 -05:00
Stella 9f95aa8271 Add support for colorful igame rtx 5070 ultraw ocv 2 2026-04-11 02:24:15 -05:00
Adam Honse 0ee04c497b Revert "Change test in ENE SMBus controller to fix a conflict where it locks up Corsair DDR5 Light Enhancement Kit sticks"
This reverts commit c4e0164479.
2026-04-11 01:29:05 -05:00
Adam Honse 2847270c97 Remove out-of-range addresses from ENE RAM address list 2026-04-10 11:52:50 -05:00
Adam Honse b98174afba Fix off-by-one error in CorsairDRAMController 2026-04-10 11:03:34 -05:00
MissingDLL 3416980849 Add MSI MPG B850 EDGE TI WIFI (MS-7E62) to 761 controller 2026-04-09 20:49:35 -05:00
Joseph E c171bfbf3b Apex Pro Gen 3 TKL wired, add media light and regional layout support 2026-04-09 18:11:52 -05:00
Eder Sánchez 583a737cb5 Fix Windows build in ZOTAC Blackwell GPU controller 2026-04-09 15:03:21 -05:00
rjc862003 d2af6289dd Roccat vulcan ii max support 2026-04-09 13:28:33 -05:00
Moh'd Khier Abualruz 44e6f455e9 Fix Apex Pro TKL 2023 Wireless (PIDs 0x1630/0x1632) RGB control 2026-04-09 13:18:38 -05:00
Eder Sánchez b3a50d669e Add support for ZOTAC GAMING GeForce RTX 5080 AMP Extreme INFINITY 2026-04-09 12:21:52 -05:00
ricarim 0092ea22e2 Fix name assignment for HyperXAlloyOrigins60and65 2026-04-09 10:14:06 -05:00
Jérôme Bauer 58fb35a9bb Fix MSI Mystic Light 185 for Common PID boards (0x0DB0:0x0076) 2026-04-09 10:09:55 -05:00
Adam Honse 9a6ff77545 Combine Corsair RAM controllers, Corsair RAM identification packet testing 2026-04-09 00:47:06 -05:00
Adam Honse c4e0164479 Change test in ENE SMBus controller to fix a conflict where it locks up Corsair DDR5 Light Enhancement Kit sticks 2026-04-08 22:24:56 -05:00
Adam Honse f872f1c591 Ignore modes out of range in VialRGB controller 2026-04-08 18:50:47 -05:00
kuroki-kael 03e64da845 Fix heap corruption in i2c_smbus_linux when sysfs files are empty 2026-04-08 15:54:38 -05:00
Frans Meulenbroeks 3150f64895 Mountain Everest 60: fix led index 2026-04-08 09:33:56 -05:00
Hugo Chabert 8b36e9c317 Add razer blackwidow v4 tenkeyless hyperspeed 2026-04-07 07:30:59 -05:00
Stella c5c5fc6c02 Add support for Colorful iGAME RTX 5060Ti 16GB Ultra W DUO V 2026-04-05 03:46:51 -05:00
Artemis Tomaras c7d9992a3d Fix SL V2 SendStartAction/SendCommitAction packet size 2026-04-05 01:52:56 -05:00
InterStella0 0d8f2c6b14 add colorful rtx 5070 ultra w ocv support 2026-04-03 18:22:49 -05:00
vitutu1 c1800f2a6f Add support for MSI RTX 4070 SUPER Gaming X Slim MLG 2026-04-03 16:46:23 +00:00
Micael Pereira d41a115a07 Add support for MSI PRO A620M-B (MS-7E28) 2026-04-03 01:31:28 -05:00
Elijah Mock 2640a3b93f Update docstring detector to match registered detector 2026-03-26 21:50:02 -05:00
Adam Honse 984c18cb11 Add Gigabyte 9070XT GAMING OC ICE 2026-03-23 16:44:30 -05:00
Adam Honse 23db15dfb9 Add support for E6K5-1114 ENE device 2026-03-23 11:46:04 -05:00
Org van Rensburg 9426f954b0 Add support for MSI MAG Z890 Tomahawk WIFI (MS-7E32) - Resolves #5438 2026-03-23 07:24:22 -05:00
asheriif 133b64778b Add support for Apex Pro TKL Gen 3 Wireless 2026-03-22 22:19:37 +01:00
Aakrisht Sharma Paudel a8a97fa38d Legion 5 gen10 2026-03-17 07:50:23 -05:00
Roy Biggins da6061bbde Add X570 AORUS XTREME zone mapping for IT8297BX 2026-03-16 10:29:38 -05:00
208 changed files with 31479 additions and 7355 deletions
+8 -20
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]
@@ -516,10 +516,7 @@ before_script:
- macos
stage: build
script:
- eval $(/opt/homebrew/bin/brew shellenv)
- /opt/homebrew/opt/qt@5/bin/qmake OpenRGB.pro
- make -j16
- /opt/homebrew/opt/qt@5/bin/macdeployqt OpenRGB.app -codesign=OpenRGB
- ./scripts/build-macos.sh qt5 arm
artifacts:
name: "${CI_PROJECT_NAME}_MacOS_ARM64_${CI_COMMIT_SHORT_SHA}"
@@ -535,10 +532,7 @@ before_script:
- macos
stage: build
script:
- eval $(/opt/homebrew/bin/brew shellenv)
- qmake OpenRGB.pro
- make -j16
- macdeployqt OpenRGB.app -codesign=OpenRGB
- ./scripts/build-macos.sh qt6 arm
artifacts:
name: "${CI_PROJECT_NAME}_MacOS_ARM64_Qt6_${CI_COMMIT_SHORT_SHA}"
@@ -554,10 +548,7 @@ before_script:
- macos
stage: build
script:
- eval $(/usr/local/bin/brew shellenv)
- arch -x86_64 /usr/local/opt/qt@5/bin/qmake OpenRGB.pro
- arch -x86_64 make -j16
- arch -x86_64 /usr/local/opt/qt@5/bin/macdeployqt OpenRGB.app -codesign=OpenRGB
- ./scripts/build-macos.sh qt5 intel
artifacts:
name: "${CI_PROJECT_NAME}_MacOS_Intel_${CI_COMMIT_SHORT_SHA}"
@@ -573,10 +564,7 @@ before_script:
- macos
stage: build
script:
- eval $(/usr/local/bin/brew shellenv)
- arch -x86_64 /usr/local/bin/qmake OpenRGB.pro
- arch -x86_64 make -j16
- arch -x86_64 /usr/local/bin/macdeployqt OpenRGB.app -codesign=OpenRGB
- ./scripts/build-macos.sh qt6 intel
artifacts:
name: "${CI_PROJECT_NAME}_MacOS_Intel_Qt6_${CI_COMMIT_SHORT_SHA}"
+1 -2
View File
@@ -22,13 +22,12 @@ CODEOWNERS @Calcprogrammer1
/Controllers/BlinkyTapeController/
/Controllers/CoolerMasterController/ @Dr_No
/Controllers/CorsairCommanderCoreController/
/Controllers/CorsairDominatorPlatinumController/
/Controllers/CorsairDRAMController/
/Controllers/CorsairHydroController/
/Controllers/CorsairHydroPlatinumController/
/Controllers/CorsairLightingNodeController/
/Controllers/CorsairPeripheralController/
/Controllers/CorsairVengeanceController/
/Controllers/CorsairVengeanceProController/
/Controllers/CorsairWirelessController/
/Controllers/CreativeController/
/Controllers/CrucialController/
+4
View File
@@ -69,3 +69,7 @@ OpenRGB is written in C++, uses the Qt framework for UI, and uses the QMake buil
Translation files are located in [`OpenRGB/qt/i18n/`](https://gitlab.com/CalcProgrammer1/OpenRGB/-/tree/master/qt/i18n), where languages are formatted using ISO 639-1 format: `OpenRGB_xx_XX.ts` — `xx_XX` representing the language code.
In order to translate a file, you need to [fork](https://gitlab.com/CalcProgrammer1/OpenRGB/-/forks/new) the project, create a new file for your language with `lupdate` (or edit an exisiting one), edit the file with `qtlinguist`, commit, push, and create a merge request.
## AI Guidelines
OpenRGB is an open source project developed by humans for humans. As a general rule, AI generated submissions are not permitted. The licensing behind AI generated code is problematic. If you choose to use AI for assistance in your development process, we can't stop you, but do not credit the AI in commits (no AI authorship/co-authorship). Ultimately, you as a human developer are responsible for the code you submit and it is expected that any code you submit you fully understand and have manually vetted before submission. Merge requests that appear to be straight from an AI output, commits with AI tool authorship or co-authorship tags, or otherwise AI generated submissions are subject to closure.
@@ -22,6 +22,7 @@
#define AURA_ROG_GLADIUS_II_WIRELESS_1_PID 0x189E
#define AURA_ROG_GLADIUS_II_WIRELESS_2_PID 0x18A0
#define AURA_ROG_GLADIUS_III_PID 0x197B
#define AURA_ROG_GLADIUS_III_CORE_PID 0x1C8D
#define AURA_ROG_GLADIUS_III_WIRELESS_USB_PID 0x197D
#define AURA_ROG_GLADIUS_III_WIRELESS_2_4_PID 0x197F
#define AURA_ROG_GLADIUS_III_WIRELESS_BT_PID 0x1981
@@ -217,6 +218,20 @@ static std::map<int,mouse_type> aura_mouse_devices =
{ AURA_MOUSE_MODE_STATIC, AURA_MOUSE_MODE_BREATHING, AURA_MOUSE_MODE_SPECTRUM, AURA_MOUSE_MODE_WAVE, AURA_MOUSE_MODE_REACTIVE, AURA_MOUSE_MODE_COMET, AURA_MOUSE_MODE_BATTERY }
}
},
{
AURA_ROG_GLADIUS_III_CORE_PID, // ROG Gladius III Core
{
0,
0,
0,
100,
false,
1,
false,
{ AURA_MOUSE_ZONE_LOGO, AURA_MOUSE_ZONE_SCROLL },
{ AURA_MOUSE_MODE_STATIC, AURA_MOUSE_MODE_BREATHING, AURA_MOUSE_MODE_SPECTRUM, AURA_MOUSE_MODE_NONE, AURA_MOUSE_MODE_REACTIVE }
}
},
{
AURA_ROG_GLADIUS_III_WIRELESS_USB_PID, // ROG Gladius III Wireless USB
{
@@ -19,7 +19,10 @@ AsusAuraRyuoAIOController::AsusAuraRyuoAIOController(hid_device* dev_handle, con
| Manually adding device info for now |
| TODO: Implement config table accurately |
\*-----------------------------------------------------*/
device_info.push_back({0x00, 0x00, 12, 0, AuraDeviceType::FIXED});
uint8_t leds = (dev_name.find("Ryujin") != std::string::npos) ? 5 : 12;
device_info.push_back({0x00, 0x00, leds, 0, AuraDeviceType::FIXED});
}
AsusAuraRyuoAIOController::~AsusAuraRyuoAIOController()
@@ -120,6 +120,7 @@
#define AURA_TERMINAL_PID 0x1889
#define ROG_STRIX_LC120_PID 0x879E
#define AURA_RYUO_AIO_PID 0x1887
#define AURA_RYUJIN_AIO_PID 0x18AE
#define ASUS_ROG_ALLY_PID 0x1ABE
#define ASUS_ROG_ALLY_X_PID 0x1B4C
@@ -393,6 +394,7 @@ REGISTER_HID_DETECTOR_IP("ASUS ROG Gladius II Origin COD",
REGISTER_HID_DETECTOR_IP("ASUS ROG Gladius II Wireless", DetectAsusAuraUSBMice, AURA_USB_VID, AURA_ROG_GLADIUS_II_WIRELESS_1_PID, 1, 0xFF13);
REGISTER_HID_DETECTOR_IP("ASUS ROG Gladius II Wireless", DetectAsusAuraUSBMice, AURA_USB_VID, AURA_ROG_GLADIUS_II_WIRELESS_2_PID, 2, 0xFF01);
REGISTER_HID_DETECTOR_IP("ASUS ROG Gladius III", DetectAsusAuraUSBMice, AURA_USB_VID, AURA_ROG_GLADIUS_III_PID, 0, 0xFF01);
REGISTER_HID_DETECTOR_IP("ASUS ROG Gladius III Core", DetectAsusAuraUSBMice, AURA_USB_VID, AURA_ROG_GLADIUS_III_CORE_PID, 0, 0xFF01);
REGISTER_HID_DETECTOR_IP("ASUS ROG Gladius III Wireless USB", DetectAsusAuraUSBMice, AURA_USB_VID, AURA_ROG_GLADIUS_III_WIRELESS_USB_PID, 0, 0xFF01);
REGISTER_HID_DETECTOR_IP("ASUS ROG Gladius III Wireless 2.4Ghz", DetectAsusAuraUSBMice, AURA_USB_VID, AURA_ROG_GLADIUS_III_WIRELESS_2_4_PID, 0, 0xFF01);
REGISTER_HID_DETECTOR_IP("ASUS ROG Gladius III Wireless Bluetooth", DetectAsusAuraUSBMice, AURA_USB_VID, AURA_ROG_GLADIUS_III_WIRELESS_BT_PID, 0, 0xFF01);
@@ -453,6 +455,7 @@ REGISTER_HID_DETECTOR_PU("ASUS ROG PG32UQ", DetectAs
REGISTER_HID_DETECTOR ("ASUS ROG AURA Terminal", DetectAsusAuraUSBTerminal, AURA_USB_VID, AURA_TERMINAL_PID);
REGISTER_HID_DETECTOR_PU ("ASUS ROG Strix LC", DetectAsusAuraUSBROGStrixLC, AURA_USB_VID, ROG_STRIX_LC120_PID, 0x00FF, 1);
REGISTER_HID_DETECTOR_PU ("ASUS ROG Ryuo AIO", DetectAsusAuraUSBRyuoAIO, AURA_USB_VID, AURA_RYUO_AIO_PID, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_PU ("ASUS ROG Ryujin AIO", DetectAsusAuraUSBRyuoAIO, AURA_USB_VID, AURA_RYUJIN_AIO_PID, 0xFF72, 0x00A1);
REGISTER_HID_DETECTOR_I ("ASUS ROG Throne", DetectAsusAuraUSBHeadsetStand, AURA_USB_VID, AURA_ROG_THRONE_PID, 0);
REGISTER_HID_DETECTOR_I ("ASUS ROG Throne QI", DetectAsusAuraUSBHeadsetStand, AURA_USB_VID, AURA_ROG_THRONE_QI_PID, 0);
REGISTER_HID_DETECTOR_I ("ASUS ROG Throne QI GUNDAM", DetectAsusAuraUSBHeadsetStand, AURA_USB_VID, AURA_ROG_THRONE_QI_GUNDAM_PID, 0);
@@ -1,4 +1,4 @@
/*---------------------------------------------------------*\
/*---------------------------------------------------------*\
| ColorfulGPUControllerDetect.cpp |
| |
| Detector for Colorful GPU |
@@ -48,7 +48,7 @@ REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Advanced OC 12G L-V", Detec
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_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, 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);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Ti Advanced OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060TI_LHR_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3070_ADVANCED_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 3060 Ti Advanced OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060TI_LHR_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_3060TI_ADVANCED_OC, 0x61);
@@ -72,3 +72,9 @@ REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4080 Ultra W OC-V", Detec
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 Advanced OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4090_ADVANCED_OCV, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 4090 Advanced OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_4090_ADVANCED_OCV2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ultra W OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060_ULTRAW_OC, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ultra W OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060_ULTRAW_OC_2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ti Ultra W DUO OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060TI_ULTRAW_DUO_OC, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5060 Ti Ultra W DUO OC", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5060TI_ULTRAW_DUO_OC_2, 0x61);
REGISTER_I2C_PCI_DETECTOR("iGame GeForce RTX 5070 Ultra W OC-V", DetectColorfulGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, COLORFUL_SUB_VEN, COLORFUL_IGAME_RTX_5070_ULTRAW_OCV, 0x61);
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);
@@ -0,0 +1,489 @@
/*---------------------------------------------------------*\
| CorsairDRAMController.cpp |
| |
| Driver for Corsair DRAM RGB controllers |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "CRC.h"
#include "CorsairDRAMController.h"
#include "LogManager.h"
#define CORSAIR_DRAM_NAME "Corsair DRAM"
using namespace std::chrono_literals;
CorsairDRAMController::CorsairDRAMController(i2c_smbus_interface *bus, corsair_dev_id dev)
{
/*-----------------------------------------------------*\
| Initialize class variables |
\*-----------------------------------------------------*/
this->bus = bus;
this->dev = dev;
device_index = 0;
direct_mode = true;
pid = 0;
vid = 0;
protocol_version = 0;
/*-----------------------------------------------------*\
| Read device information |
\*-----------------------------------------------------*/
ReadDeviceInfo();
}
CorsairDRAMController::~CorsairDRAMController()
{
}
unsigned int CorsairDRAMController::GetLEDCount()
{
return(corsair_dram_device_list[device_index]->led_count);
}
unsigned char CorsairDRAMController::GetProtocolVersion()
{
return(protocol_version);
}
std::string CorsairDRAMController::GetDeviceLocation()
{
std::string return_string(bus->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 CorsairDRAMController::GetDeviceName()
{
return(corsair_dram_device_list[device_index]->name);
}
std::string CorsairDRAMController::GetDeviceVersion()
{
return(firmware_version);
}
void CorsairDRAMController::SetColorsPerLED(RGBColor* colors)
{
/*-----------------------------------------------------*\
| Get LED count from device list |
\*-----------------------------------------------------*/
unsigned int led_count = corsair_dram_device_list[device_index]->led_count;
if(direct_mode)
{
/*-------------------------------------------------*\
| Sanity check - Direct mode can only be used on |
| protocol 4+ |
\*-------------------------------------------------*/
if(protocol_version < 4)
{
LOG_ERROR("[%s] Protocol version %d tried to use direct mode, ignoring", CORSAIR_DRAM_NAME, protocol_version);
return;
}
/*-------------------------------------------------*\
| Packet format: |
| Size n is (LED count * 3) + 2 |
| 0: Command byte (0x0A or 0x0C) |
| 1 to (n-2): LED color data in R/G/B order |
| (n-1): CRC8 of bytes 0 to (n-2) |
\*-------------------------------------------------*/
unsigned int direct_packet_size = (led_count * 3) + 2;
unsigned char* direct_packet = new unsigned char[direct_packet_size];
/*-------------------------------------------------*\
| First byte in packet is LED count |
\*-------------------------------------------------*/
direct_packet[0] = led_count;
/*-------------------------------------------------*\
| Fill in LED data |
\*-------------------------------------------------*/
for(unsigned int led_idx = 0; led_idx < led_count; led_idx++)
{
unsigned int color_index = led_idx;
unsigned int offset = (led_idx * 3) + 1;
if(corsair_dram_device_list[device_index]->reverse)
{
color_index = (led_count -1) - led_idx;
}
direct_packet[offset + 0] = RGBGetRValue(colors[color_index]);
direct_packet[offset + 1] = RGBGetGValue(colors[color_index]);
direct_packet[offset + 2] = RGBGetBValue(colors[color_index]);
}
/*-------------------------------------------------*\
| Last byte in packet is CRC of all data up to it |
\*-------------------------------------------------*/
direct_packet[direct_packet_size - 1] = CRCPP::CRC::Calculate(direct_packet, (direct_packet_size - 1), CRCPP::CRC::CRC_8());
/*-------------------------------------------------*\
| Write using block writes, if packet exceeds 32 |
| bytes, use a second block write to the second |
| block write address for the remaining data |
\*-------------------------------------------------*/
s32 ret = bus->i2c_smbus_write_block_data(dev, CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_1, 32, direct_packet);
if((ret >= 0) && (direct_packet_size > 32))
{
bus->i2c_smbus_write_block_data(dev, CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_2, direct_packet_size - 32, direct_packet + 32);
}
/*-------------------------------------------------*\
| Corsair DRAM supports an alternate means of |
| writing block data without using SMBus block |
| operations. If block operations are not |
| available, fall back to this scheme. |
| |
| Blocks are split up into word data writes. |
| Some data bytes are packed into the lower nibble |
| of the register address byte. |
\*-------------------------------------------------*/
if(ret < 0)
{
unsigned int block_index = 0;
bool even_frame = true;
bool first_frame = true;
while(block_index < direct_packet_size)
{
unsigned char reg_value_0 = 0xA0;
unsigned char reg_value_1 = 0x00;
unsigned short word_value_0 = 0;
unsigned short word_value_1 = 0;
if(block_index == 0)
{
reg_value_0 = 0x90;
}
word_value_0 = (direct_packet[block_index]);
block_index++;
if(block_index < direct_packet_size)
{
word_value_0 |= (direct_packet[block_index] << 8);
block_index++;
}
if(block_index < direct_packet_size)
{
reg_value_1 = 0xA0;
reg_value_0 |= (direct_packet[block_index] & 0x0F);
reg_value_1 |= (direct_packet[block_index] & 0xF0) >> 4;
block_index++;
}
if(block_index < direct_packet_size)
{
word_value_1 = (direct_packet[block_index]);
block_index++;
}
if(block_index < direct_packet_size)
{
word_value_1 |= (direct_packet[block_index] << 8);
block_index++;
}
bus->i2c_smbus_write_word_data(dev, reg_value_0, word_value_0);
if(reg_value_1 > 0)
{
bus->i2c_smbus_write_word_data(dev, reg_value_1, word_value_1);
}
}
}
/*-------------------------------------------------*\
| Remember to delete the data buffer |
\*-------------------------------------------------*/
delete[] direct_packet;
}
else
{
/*-------------------------------------------------*\
| Local variables |
\*-------------------------------------------------*/
unsigned char device_crc;
unsigned char calc_crc;
/*-------------------------------------------------*\
| Packet format: |
| Size n is (LED count * 4) |
| Format is 0xRR, 0xGG, 0xBB, 0xFF for each LED |
\*-------------------------------------------------*/
unsigned int color_data_size = (led_count * 4);
unsigned char* color_data_packet = new unsigned char[color_data_size];
for(unsigned int led_idx = 0; led_idx < led_count; led_idx++)
{
unsigned int color_index = led_idx;
if(corsair_dram_device_list[device_index]->reverse)
{
color_index = (led_count -1) - led_idx;
}
color_data_packet[(led_idx * 4) + 0] = RGBGetRValue(colors[color_index]);
color_data_packet[(led_idx * 4) + 1] = RGBGetGValue(colors[color_index]);
color_data_packet[(led_idx * 4) + 2] = RGBGetBValue(colors[color_index]);
color_data_packet[(led_idx * 4) + 3] = 0xFF;
}
/*-------------------------------------------------*\
| Write LED color data packet |
\*-------------------------------------------------*/
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_RESET_BUFFER, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_BINARY_START, 0x00);
for(unsigned int i = 0; i < color_data_size; i++)
{
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_SET_BINARY_DATA, color_data_packet[i]);
}
/*-------------------------------------------------*\
| Calculate CRC and read CRC reported by device |
\*-------------------------------------------------*/
calc_crc = CRCPP::CRC::Calculate(color_data_packet, color_data_size, CRCPP::CRC::CRC_8());
device_crc = bus->i2c_smbus_read_byte_data(dev, CORSAIR_DRAM_REG_GET_CHECKSUM);
/*-------------------------------------------------*\
| Write effect configuration only if CRCs match |
\*-------------------------------------------------*/
if(calc_crc == device_crc)
{
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_WRITE_CONFIGURATION, CORSAIR_DRAM_ID_COLOR_DATA);
WaitReady();
}
/*-------------------------------------------------*\
| Remember to delete the data buffer |
\*-------------------------------------------------*/
delete[] color_data_packet;
}
}
void CorsairDRAMController::SetDirect(bool direct)
{
direct_mode = direct;
}
void CorsairDRAMController::SetEffect
(
unsigned char mode,
unsigned char speed,
unsigned char direction,
bool random,
unsigned char brightness,
unsigned char red1,
unsigned char grn1,
unsigned char blu1,
unsigned char red2,
unsigned char grn2,
unsigned char blu2
)
{
/*-----------------------------------------------------*\
| Local variables |
\*-----------------------------------------------------*/
unsigned char effect_data[20];
unsigned char device_crc;
unsigned char calc_crc;
unsigned char random_byte;
/*-----------------------------------------------------*\
| If mode is direct (which is a dummy value not |
| understood by the hardware), return. Direct mode is |
| not set in the effect configuration. |
\*-----------------------------------------------------*/
direct_mode = (mode == CORSAIR_DRAM_MODE_DIRECT);
if(direct_mode)
{
return;
}
/*-----------------------------------------------------*\
| Determine random byte |
\*-----------------------------------------------------*/
if(random)
{
random_byte = CORSAIR_DRAM_EFFECT_RANDOM_COLORS;
}
else
{
random_byte = CORSAIR_DRAM_EFFECT_CUSTOM_COLORS;
}
/*-----------------------------------------------------*\
| Fill in effect packet |
\*-----------------------------------------------------*/
effect_data[0] = mode; // Mode
effect_data[1] = speed; // Speed
effect_data[2] = random_byte; // Custom color
effect_data[3] = direction; // Direction
effect_data[4] = red1; // Custom color 1 red
effect_data[5] = grn1; // Custom color 1 green
effect_data[6] = blu1; // Custom color 1 blue
effect_data[7] = brightness;
effect_data[8] = red2; // Custom color 2 red
effect_data[9] = grn2; // Custom color 2 green
effect_data[10] = blu2; // Custom color 2 blue
effect_data[11] = brightness;
effect_data[12] = 0x00;
effect_data[13] = 0x00;
effect_data[14] = 0x00;
effect_data[15] = 0x00;
effect_data[16] = 0x00;
effect_data[17] = 0x00;
effect_data[18] = 0x00;
effect_data[19] = 0x00;
/*-----------------------------------------------------*\
| Write effect packet |
\*-----------------------------------------------------*/
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_RESET_BUFFER, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_BINARY_START, 0x00);
for(unsigned int i = 0; i < 20; i++)
{
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_SET_BINARY_DATA, effect_data[i]);
}
/*-----------------------------------------------------*\
| Calculate CRC and read CRC reported by device |
\*-----------------------------------------------------*/
calc_crc = CRCPP::CRC::Calculate(effect_data, sizeof(effect_data), CRCPP::CRC::CRC_8());
device_crc = bus->i2c_smbus_read_byte_data(dev, CORSAIR_DRAM_REG_GET_CHECKSUM);
/*-----------------------------------------------------*\
| Write effect configuration only if CRCs match |
\*-----------------------------------------------------*/
if(calc_crc == device_crc)
{
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_WRITE_CONFIGURATION, CORSAIR_DRAM_ID_EFFECT_CONFIGURATION);
WaitReady();
}
}
bool CorsairDRAMController::WaitReady()
{
/*-----------------------------------------------------*\
| Poll status register 0x30; bit 3 (0x08) = busy. |
| Device is ready when bit 3 is clear. |
\*-----------------------------------------------------*/
for(int retry = 0; retry < 5; retry++)
{
int status = bus->i2c_smbus_read_byte_data(dev, CORSAIR_DRAM_REG_STATUS);
if(status >= 0 && (status & 0x08) == 0)
{
return true;
}
std::this_thread::sleep_for(10ms);
}
return false;
}
void CorsairDRAMController::ReadDeviceInfo()
{
unsigned char device_information_data[32];
unsigned char device_crc;
unsigned char calc_crc;
/*-----------------------------------------------------*\
| Request Device Information Data |
\*-----------------------------------------------------*/
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_GET_DEVICE_INFO, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_DRAM_REG_BINARY_START, 0x00);
/*-----------------------------------------------------*\
| Read Device Information Data |
\*-----------------------------------------------------*/
for(unsigned int i = 0; i < 32; i++)
{
device_information_data[i] = bus->i2c_smbus_read_byte_data(dev, CORSAIR_DRAM_REG_GET_BINARY_DATA);
}
/*-----------------------------------------------------*\
| Compare CRC |
\*-----------------------------------------------------*/
calc_crc = CRCPP::CRC::Calculate(device_information_data, sizeof(device_information_data), CRCPP::CRC::CRC_8());
device_crc = bus->i2c_smbus_read_byte_data(dev, CORSAIR_DRAM_REG_GET_CHECKSUM);
if(calc_crc != device_crc)
{
LOG_ERROR("[%s] ReadDeviceInfo CRC Mismatch", CORSAIR_DRAM_NAME);
}
/*-----------------------------------------------------*\
| Log Device Information Data |
\*-----------------------------------------------------*/
if(LogManager::get()->getLoglevel() >= LL_TRACE)
{
char device_info_buf[256];
unsigned int pos;
pos = snprintf(device_info_buf, sizeof(device_info_buf), "%02X: ", dev);
for(unsigned int i = 0; i < 32; i++)
{
pos += snprintf(&device_info_buf[pos], sizeof(device_info_buf) - pos, "%02X ", device_information_data[i]);
}
LOG_TRACE("[%s] Device Info: %s", CORSAIR_DRAM_NAME, device_info_buf);
}
/*-----------------------------------------------------*\
| Read VID |
\*-----------------------------------------------------*/
vid = (device_information_data[1] << 8) | device_information_data[0];
/*-----------------------------------------------------*\
| Read PID |
\*-----------------------------------------------------*/
pid = (device_information_data[3] << 8) | device_information_data[2];
/*-----------------------------------------------------*\
| Format Firwmare Version |
\*-----------------------------------------------------*/
firmware_version = std::to_string(device_information_data[9]) + "." + std::to_string(device_information_data[8]) + "." + std::to_string((device_information_data[11] << 8) | device_information_data[10]);
/*-----------------------------------------------------*\
| Read Protocol Version |
\*-----------------------------------------------------*/
protocol_version = device_information_data[28];
/*-----------------------------------------------------*\
| Loop through all known devices to look for a PID |
| match |
\*-----------------------------------------------------*/
for(unsigned int i = 0; i < CORSAIR_DRAM_NUM_DEVICES; i++)
{
for(unsigned int j = 0; j < CORSAIR_DRAM_MAX_PIDS; j++)
{
if(corsair_dram_device_list[i]->pids[j] == pid)
{
/*-----------------------------------------*\
| Set device ID |
\*-----------------------------------------*/
device_index = i;
break;
}
}
}
}
@@ -0,0 +1,149 @@
/*---------------------------------------------------------*\
| CorsairDRAMController.h |
| |
| Driver for Corsair DRAM RGB controllers |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include "i2c_smbus.h"
#include "CorsairDRAMDevices.h"
#include "RGBController.h"
typedef unsigned char corsair_dev_id;
enum
{ /* (*) indicates deprecated registers no longer used by iCue */
CORSAIR_DRAM_REG_RESET_BUFFER = 0x0B, /* Reset buffer by writing 0x00 */
CORSAIR_DRAM_REG_SET_BINARY_DATA = 0x20, /* Write byte to active binary data buffer */
CORSAIR_DRAM_REG_BINARY_START = 0x21, /* Start binary data transfer by writing 0x00 */
CORSAIR_DRAM_REG_SWITCH_MODE = 0x23, /* Switch between Bootloader(0x00) and Normal(0x01) */
CORSAIR_DRAM_REG_SET_BUFFER = 0x26, /* Select configuration buffer to write by ID (*) */
CORSAIR_DRAM_REG_STATUS = 0x30, /* Status register */
CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_1 = 0x31, /* Direct color buffer block register 1 */
CORSAIR_DRAM_REG_COLOR_BUFFER_BLOCK_2 = 0x32, /* Direct color buffer block register 2 */
CORSAIR_DRAM_REG_GET_BINARY_DATA = 0x40, /* Read byte from active binary data buffer */
CORSAIR_DRAM_REG_BUSY_STATUS = 0x41, /* Reads nonzero while busy, zero when ready(*) */
CORSAIR_DRAM_REG_GET_CHECKSUM = 0x42, /* Get checksum (CRC8) of active binary data buffer */
CORSAIR_DRAM_REG_GET_DEVICE_INFO = 0x61, /* Select device info buffer */
CORSAIR_DRAM_REG_GET_CONFIGURATION = 0x63, /* Select configuration buffer to read by ID */
CORSAIR_DRAM_REG_WRITE_CONFIGURATION = 0x82, /* Write/Apply configuration by writing buffer ID */
};
enum
{
CORSAIR_DRAM_ID_COMMAND_LIST = 0, /* Command list */
CORSAIR_DRAM_ID_EFFECT_CONFIGURATION = 1, /* Effect configuration */
CORSAIR_DRAM_ID_COLOR_DATA = 2, /* Color data */
};
enum
{
CORSAIR_DRAM_MODE_DIRECT = 0xDD, /* Arbitrary value to compare against later. Not the actual packet */
CORSAIR_DRAM_MODE_COLOR_SHIFT = 0x00, /* Color Shift mode */
CORSAIR_DRAM_MODE_COLOR_PULSE = 0x01, /* Color Pulse mode */
CORSAIR_DRAM_MODE_RAINBOW_WAVE = 0x03, /* Rainbow Wave mode */
CORSAIR_DRAM_MODE_COLOR_WAVE = 0x04, /* Color Wave mode */
CORSAIR_DRAM_MODE_VISOR = 0x05, /* Visor mode */
CORSAIR_DRAM_MODE_RAIN = 0x06, /* Rain mode */
CORSAIR_DRAM_MODE_MARQUEE = 0x07, /* Marquee mode */
CORSAIR_DRAM_MODE_RAINBOW = 0x08, /* Rainbow mode */
CORSAIR_DRAM_MODE_SEQUENTIAL = 0x09, /* Sequential mode */
CORSAIR_DRAM_MODE_STATIC = 0x10, /* Static mode */
CORSAIR_DRAM_NUMBER_MODES = 10, /* Number of Corsair Pro modes */
};
enum
{
CORSAIR_DRAM_SPEED_SLOW = 0x00, /* Slow speed */
CORSAIR_DRAM_SPEED_MEDIUM = 0x01, /* Medium speed */
CORSAIR_DRAM_SPEED_FAST = 0x02, /* Fast speed */
};
enum
{
CORSAIR_DRAM_EFFECT_RANDOM_COLORS = 0x00, /* Random colors */
CORSAIR_DRAM_EFFECT_CUSTOM_COLORS = 0x01, /* Custom colors */
};
enum
{
CORSAIR_DRAM_DIRECTION_UP = 0x00, /* Up direction */
CORSAIR_DRAM_DIRECTION_DOWN = 0x01, /* Down direction */
CORSAIR_DRAM_DIRECTION_LEFT = 0x02, /* Left direction */
CORSAIR_DRAM_DIRECTION_RIGHT = 0x03, /* Right direction */
CORSAIR_DRAM_DIRECTION_VERTICAL = 0x01, /* Vertical direction */
CORSAIR_DRAM_DIRECTION_HORIZONTAL = 0x03, /* Horizontal direction */
};
enum
{
CORSAIR_DRAM_BRIGHTNESS_MIN = 0, /* Minimum brightness */
CORSAIR_DRAM_BRIGHTNESS_MAX = 255, /* Maximum brightness */
CORSAIR_DRAM_BRIGHTNESS_DEFAULT = 255, /* Default brightness */
};
class CorsairDRAMController
{
public:
CorsairDRAMController(i2c_smbus_interface *bus, corsair_dev_id dev);
~CorsairDRAMController();
std::string GetDeviceLocation();
std::string GetDeviceName();
std::string GetDeviceVersion();
unsigned int GetLEDCount();
unsigned char GetProtocolVersion();
void SetColorsPerLED(RGBColor* colors);
void SetDirect(bool direct);
void SetEffect(unsigned char mode,
unsigned char speed,
unsigned char direction,
bool random,
unsigned char brightness,
unsigned char red1,
unsigned char grn1,
unsigned char blu1,
unsigned char red2,
unsigned char grn2,
unsigned char blu2);
bool WaitReady();
private:
/*-----------------------------------------------------*\
| I2C |
\*-----------------------------------------------------*/
i2c_smbus_interface* bus;
corsair_dev_id dev;
/*-----------------------------------------------------*\
| State tracking |
\*-----------------------------------------------------*/
bool direct_mode;
/*-----------------------------------------------------*\
| Corsair DRAM information |
\*-----------------------------------------------------*/
unsigned short vid;
unsigned short pid;
std::string firmware_version;
unsigned char protocol_version;
unsigned int device_index;
/*-----------------------------------------------------*\
| Private functions |
\*-----------------------------------------------------*/
void ReadDeviceInfo();
};
@@ -0,0 +1,89 @@
/*---------------------------------------------------------*\
| CorsairDRAMControllerDetect.cpp |
| |
| Detector for Corsair DRAM RGB controllers |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <vector>
#include "Detector.h"
#include "CorsairDRAMController.h"
#include "RGBController_CorsairDRAM.h"
#include "LogManager.h"
#include "i2c_smbus.h"
#include "pci_ids.h"
using namespace std::chrono_literals;
#define CORSAIR_DRAM_NAME "Corsair DRAM"
bool TestForCorsairDRAMController(i2c_smbus_interface *bus, unsigned char address)
{
LOG_DEBUG("[%s] Trying address %02X", CORSAIR_DRAM_NAME, address);
int res = bus->i2c_smbus_read_byte_data(address, 0x43);
if(res < 0)
{
return false;
}
if(!(res == 0x1A || res == 0x1B || res == 0x1C))
{
LOG_DEBUG("[%s] Failed: expected 0x1A, 0x1B, or 0x1C, got %04X", CORSAIR_DRAM_NAME, res);
return false;
}
res = bus->i2c_smbus_read_byte_data(address, 0x44);
if(!(res == 0x01 || res == 0x03 || res == 0x04))
{
LOG_DEBUG("[%s] Failed: expected 0x01, 0x03, or 0x04, got %04X", CORSAIR_DRAM_NAME, res);
return false;
}
return true;
}
void DetectCorsairDRAMControllers(std::vector<i2c_smbus_interface *> &busses)
{
for(unsigned int bus = 0; bus < busses.size(); bus++)
{
IF_DRAM_SMBUS(busses[bus]->pci_vendor, busses[bus]->pci_device)
{
LOG_DEBUG("[%s] Testing bus %d", CORSAIR_DRAM_NAME, bus);
std::vector<unsigned char> addresses;
for(unsigned char addr = 0x58; addr <= 0x5F; addr++)
{
addresses.push_back(addr);
}
for(unsigned char addr = 0x18; addr <= 0x1F; addr++)
{
addresses.push_back(addr);
}
for(unsigned char addr : addresses)
{
if(TestForCorsairDRAMController(busses[bus], addr))
{
CorsairDRAMController* controller = new CorsairDRAMController(busses[bus], addr);
RGBController_CorsairDRAM* rgb_controller = new RGBController_CorsairDRAM(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
std::this_thread::sleep_for(10ms);
}
}
}
}
REGISTER_I2C_DETECTOR(CORSAIR_DRAM_NAME, DetectCorsairDRAMControllers);
@@ -0,0 +1,163 @@
/*---------------------------------------------------------*\
| CorsairDRAMDevices.cpp |
| |
| Device list for Corsair DRAM RGB controllers |
| |
| Adam Honse (CalcProgrammer1) 07 Apr 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "CorsairDRAMDevices.h"
static const corsair_dram_device corsair_vengeance_pro_ddr4_device =
{
"Corsair Vengeance RGB Pro DDR4",
{
CORSAIR_VENGEANCE_PRO_DDR4_PID_1,
CORSAIR_VENGEANCE_PRO_DDR4_PID_2,
0,
0,
0,
0,
},
10,
false
};
static const corsair_dram_device corsair_dominator_platinum_ddr4_device =
{
"Corsair Dominator Platinum RGB DDR4",
{
CORSAIR_DOMINATOR_PLATINUM_DDR4_PID_1,
CORSAIR_DOMINATOR_PLATINUM_DDR4_PID_2,
0,
0,
0,
0,
},
12,
true
};
static const corsair_dram_device corsair_vengeance_pro_sl_ddr4_device =
{
"Corsair Vengeance RGB Pro SL DDR4",
{
CORSAIR_VENGEANCE_PRO_SL_DDR4_PID_1,
CORSAIR_VENGEANCE_PRO_SL_DDR4_PID_2,
0,
0,
0,
0,
},
10,
false
};
static const corsair_dram_device corsair_vengeance_rs_ddr4_device =
{
"Corsair Vengeance RGB RS DDR4",
{
CORSAIR_VENGEANCE_RS_DDR4_PID_1,
CORSAIR_VENGEANCE_RS_DDR4_PID_2,
0,
0,
0,
0,
},
6,
false
};
static const corsair_dram_device corsair_dominator_platinum_ddr5_device =
{
"Corsair Dominator Platinum RGB DDR5",
{
CORSAIR_DOMINATOR_PLATINUM_DDR5_PID_1,
CORSAIR_DOMINATOR_PLATINUM_DDR5_PID_2,
0,
0,
0,
0,
},
12,
true
};
static const corsair_dram_device corsair_dominator_titanium_ddr5_device =
{
"Corsair Dominator Titanium RGB DDR5",
{
CORSAIR_DOMINATOR_TITANIUM_DDR5_PID_1,
CORSAIR_DOMINATOR_TITANIUM_DDR5_PID_2,
CORSAIR_DOMINATOR_TITANIUM_DDR5_PID_3,
CORSAIR_DOMINATOR_TITANIUM_DDR5_PID_4,
0,
0,
},
12,
true
};
static const corsair_dram_device corsair_vengeance_ddr5_device =
{
"Corsair Vengeance RGB DDR5",
{
CORSAIR_VENGEANCE_DDR5_PID_1,
CORSAIR_VENGEANCE_DDR5_PID_2,
CORSAIR_VENGEANCE_DDR5_PID_3,
CORSAIR_VENGEANCE_DDR5_PID_4,
CORSAIR_VENGEANCE_DDR5_PID_5,
CORSAIR_VENGEANCE_DDR5_PID_6,
},
10,
false
};
static const corsair_dram_device corsair_vengeance_shugo_series_ddr5_device =
{
"Corsair Vengeance Shugo Series DDR5",
{
CORSAIR_VENGEANCE_SHUGO_SERIES_DDR5_PID_1,
CORSAIR_VENGEANCE_SHUGO_SERIES_DDR5_PID_2,
CORSAIR_VENGEANCE_SHUGO_SERIES_DDR5_PID_3,
CORSAIR_VENGEANCE_SHUGO_SERIES_DDR5_PID_4,
0,
0,
},
10,
false
};
static const corsair_dram_device corsair_vengeance_rs_ddr5_device =
{
"Corsair Vengeance RGB RS DDR5",
{
CORSAIR_VENGEANCE_RS_DDR5_PID_1,
CORSAIR_VENGEANCE_RS_DDR5_PID_2,
0,
0,
0,
0,
},
6,
false
};
static const corsair_dram_device* device_list[] =
{
&corsair_vengeance_pro_ddr4_device,
&corsair_dominator_platinum_ddr4_device,
&corsair_vengeance_pro_sl_ddr4_device,
&corsair_vengeance_rs_ddr4_device,
&corsair_dominator_platinum_ddr5_device,
&corsair_dominator_titanium_ddr5_device,
&corsair_vengeance_ddr5_device,
&corsair_vengeance_shugo_series_ddr5_device,
&corsair_vengeance_rs_ddr5_device,
};
const unsigned int CORSAIR_DRAM_NUM_DEVICES = (sizeof(device_list) / sizeof(device_list[ 0 ]));
const corsair_dram_device** corsair_dram_device_list = device_list;
@@ -0,0 +1,68 @@
/*---------------------------------------------------------*\
| CorsairDRAMDevices.h |
| |
| Device list for Corsair DRAM RGB controllers |
| |
| Adam Honse (CalcProgrammer1) 07 Apr 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
/*---------------------------------------------------------*\
| Maximum number of PIDs for a given DRAM model |
\*---------------------------------------------------------*/
#define CORSAIR_DRAM_MAX_PIDS 6
/*---------------------------------------------------------*\
| Corsair DRAM vendor ID |
\*---------------------------------------------------------*/
#define CORSAIR_DRAM_VID 0x1B1C
/*---------------------------------------------------------*\
| Corsair DRAM product IDs |
\*---------------------------------------------------------*/
#define CORSAIR_VENGEANCE_PRO_DDR4_PID_1 0x0100
#define CORSAIR_VENGEANCE_PRO_DDR4_PID_2 0x0101
#define CORSAIR_DOMINATOR_PLATINUM_DDR4_PID_1 0x0200
#define CORSAIR_DOMINATOR_PLATINUM_DDR4_PID_2 0x0201
#define CORSAIR_VENGEANCE_PRO_SL_DDR4_PID_1 0x0300
#define CORSAIR_VENGEANCE_PRO_SL_DDR4_PID_2 0x0301
#define CORSAIR_VENGEANCE_RS_DDR4_PID_1 0x0400
#define CORSAIR_VENGEANCE_RS_DDR4_PID_2 0x0401
#define CORSAIR_DOMINATOR_PLATINUM_DDR5_PID_1 0x0600
#define CORSAIR_DOMINATOR_PLATINUM_DDR5_PID_2 0x0601
#define CORSAIR_DOMINATOR_TITANIUM_DDR5_PID_1 0x0800
#define CORSAIR_DOMINATOR_TITANIUM_DDR5_PID_2 0x0801
#define CORSAIR_DOMINATOR_TITANIUM_DDR5_PID_3 0x0810
#define CORSAIR_DOMINATOR_TITANIUM_DDR5_PID_4 0x0811
#define CORSAIR_VENGEANCE_DDR5_PID_1 0x0700
#define CORSAIR_VENGEANCE_DDR5_PID_2 0x0701
#define CORSAIR_VENGEANCE_DDR5_PID_3 0x0900
#define CORSAIR_VENGEANCE_DDR5_PID_4 0x0901
#define CORSAIR_VENGEANCE_DDR5_PID_5 0x0910
#define CORSAIR_VENGEANCE_DDR5_PID_6 0x0911
#define CORSAIR_VENGEANCE_SHUGO_SERIES_DDR5_PID_1 0x0A00
#define CORSAIR_VENGEANCE_SHUGO_SERIES_DDR5_PID_2 0x0A01
#define CORSAIR_VENGEANCE_SHUGO_SERIES_DDR5_PID_3 0x0A10
#define CORSAIR_VENGEANCE_SHUGO_SERIES_DDR5_PID_4 0x0A11
#define CORSAIR_VENGEANCE_RS_DDR5_PID_1 0x0B00
#define CORSAIR_VENGEANCE_RS_DDR5_PID_2 0x0B01
typedef struct
{
std::string name;
unsigned short pids[CORSAIR_DRAM_MAX_PIDS];
unsigned int led_count;
bool reverse;
} corsair_dram_device;
/*-----------------------------------------------------*\
| These constant values are defined in RazerDevices.cpp |
\*-----------------------------------------------------*/
extern const unsigned int CORSAIR_DRAM_NUM_DEVICES;
extern const corsair_dram_device** corsair_dram_device_list;
@@ -0,0 +1,330 @@
/*---------------------------------------------------------*\
| RGBController_CorsairDRAM.cpp |
| |
| RGBController for Corsair DRAM RGB controllers |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_CorsairDRAM.h"
/**------------------------------------------------------------------*\
@name Corsair DRAM
@category RAM
@type SMBus
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectCorsairDRAMControllers
@comment
The Corsair DRAM RGB controller chip can be found on several
Corsair memory sticks which have different LED counts. This can be controlled
by editing the Part Number in OpenRGB.json with values in the below table.
| Part Number | LED Count |
| :---------: | --------: |
| CMG | 6 |
| CMH | 10 |
| CMN | 10 |
| CMT | 12 |
\*-------------------------------------------------------------------*/
RGBController_CorsairDRAM::RGBController_CorsairDRAM(CorsairDRAMController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "Corsair";
type = DEVICE_TYPE_DRAM;
description = "Corsair DRAM RGB Device";
location = controller->GetDeviceLocation();
version = controller->GetDeviceVersion();
if(controller->GetProtocolVersion() >= 4)
{
mode Direct;
Direct.name = "Direct";
Direct.value = CORSAIR_DRAM_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
}
mode Custom;
Custom.name = "Custom";
Custom.value = CORSAIR_DRAM_MODE_STATIC;
Custom.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_AUTOMATIC_SAVE;
Custom.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Custom);
mode ColorShift;
ColorShift.name = "Color Shift";
ColorShift.value = CORSAIR_DRAM_MODE_COLOR_SHIFT;
ColorShift.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
ColorShift.color_mode = MODE_COLORS_MODE_SPECIFIC;
ColorShift.speed_min = CORSAIR_DRAM_SPEED_SLOW;
ColorShift.speed_max = CORSAIR_DRAM_SPEED_FAST;
ColorShift.speed = CORSAIR_DRAM_SPEED_SLOW;
ColorShift.brightness_min = CORSAIR_DRAM_BRIGHTNESS_MIN;
ColorShift.brightness_max = CORSAIR_DRAM_BRIGHTNESS_MAX;
ColorShift.brightness = CORSAIR_DRAM_BRIGHTNESS_DEFAULT;
ColorShift.colors_min = 2;
ColorShift.colors_max = 2;
ColorShift.colors.resize(2);
modes.push_back(ColorShift);
mode ColorPulse;
ColorPulse.name = "Color Pulse";
ColorPulse.value = CORSAIR_DRAM_MODE_COLOR_PULSE;
ColorPulse.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
ColorPulse.color_mode = MODE_COLORS_MODE_SPECIFIC;
ColorPulse.speed_min = CORSAIR_DRAM_SPEED_SLOW;
ColorPulse.speed_max = CORSAIR_DRAM_SPEED_FAST;
ColorPulse.speed = CORSAIR_DRAM_SPEED_SLOW;
ColorPulse.brightness_min = CORSAIR_DRAM_BRIGHTNESS_MIN;
ColorPulse.brightness_max = CORSAIR_DRAM_BRIGHTNESS_MAX;
ColorPulse.brightness = CORSAIR_DRAM_BRIGHTNESS_DEFAULT;
ColorPulse.colors_min = 2;
ColorPulse.colors_max = 2;
ColorPulse.colors.resize(2);
modes.push_back(ColorPulse);
mode RainbowWave;
RainbowWave.name = "Rainbow Wave";
RainbowWave.value = CORSAIR_DRAM_MODE_RAINBOW_WAVE;
RainbowWave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_AUTOMATIC_SAVE;
RainbowWave.color_mode = MODE_COLORS_NONE;
RainbowWave.speed_min = CORSAIR_DRAM_SPEED_SLOW;
RainbowWave.speed_max = CORSAIR_DRAM_SPEED_FAST;
RainbowWave.speed = CORSAIR_DRAM_SPEED_SLOW;
RainbowWave.direction = MODE_DIRECTION_DOWN;
modes.push_back(RainbowWave);
mode ColorWave;
ColorWave.name = "Color Wave";
ColorWave.value = CORSAIR_DRAM_MODE_COLOR_WAVE;
ColorWave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
ColorWave.color_mode = MODE_COLORS_MODE_SPECIFIC;
ColorWave.speed_min = CORSAIR_DRAM_SPEED_SLOW;
ColorWave.speed_max = CORSAIR_DRAM_SPEED_FAST;
ColorWave.speed = CORSAIR_DRAM_SPEED_SLOW;
ColorWave.direction = MODE_DIRECTION_DOWN;
ColorWave.brightness_min = CORSAIR_DRAM_BRIGHTNESS_MIN;
ColorWave.brightness_max = CORSAIR_DRAM_BRIGHTNESS_MAX;
ColorWave.brightness = CORSAIR_DRAM_BRIGHTNESS_DEFAULT;
ColorWave.colors_min = 2;
ColorWave.colors_max = 2;
ColorWave.colors.resize(2);
modes.push_back(ColorWave);
mode Visor;
Visor.name = "Visor";
Visor.value = CORSAIR_DRAM_MODE_VISOR;
Visor.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_HV | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
Visor.color_mode = MODE_COLORS_MODE_SPECIFIC;
Visor.speed_min = CORSAIR_DRAM_SPEED_SLOW;
Visor.speed_max = CORSAIR_DRAM_SPEED_FAST;
Visor.speed = CORSAIR_DRAM_SPEED_SLOW;
Visor.direction = MODE_DIRECTION_VERTICAL;
Visor.brightness_min = CORSAIR_DRAM_BRIGHTNESS_MIN;
Visor.brightness_max = CORSAIR_DRAM_BRIGHTNESS_MAX;
Visor.brightness = CORSAIR_DRAM_BRIGHTNESS_DEFAULT;
Visor.colors_min = 2;
Visor.colors_max = 2;
Visor.colors.resize(2);
modes.push_back(Visor);
mode Rain;
Rain.name = "Rain";
Rain.value = CORSAIR_DRAM_MODE_RAIN;
Rain.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
Rain.color_mode = MODE_COLORS_MODE_SPECIFIC;
Rain.speed_min = CORSAIR_DRAM_SPEED_SLOW;
Rain.speed_max = CORSAIR_DRAM_SPEED_FAST;
Rain.speed = CORSAIR_DRAM_SPEED_SLOW;
Rain.direction = MODE_DIRECTION_DOWN;
Rain.brightness_min = CORSAIR_DRAM_BRIGHTNESS_MIN;
Rain.brightness_max = CORSAIR_DRAM_BRIGHTNESS_MAX;
Rain.brightness = CORSAIR_DRAM_BRIGHTNESS_DEFAULT;
Rain.colors_min = 2;
Rain.colors_max = 2;
Rain.colors.resize(2);
modes.push_back(Rain);
mode Marquee;
Marquee.name = "Marquee";
Marquee.value = CORSAIR_DRAM_MODE_MARQUEE;
Marquee.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
Marquee.color_mode = MODE_COLORS_MODE_SPECIFIC;
Marquee.speed_min = CORSAIR_DRAM_SPEED_SLOW;
Marquee.speed_max = CORSAIR_DRAM_SPEED_FAST;
Marquee.speed = CORSAIR_DRAM_SPEED_SLOW;
Marquee.brightness_min = CORSAIR_DRAM_BRIGHTNESS_MIN;
Marquee.brightness_max = CORSAIR_DRAM_BRIGHTNESS_MAX;
Marquee.brightness = CORSAIR_DRAM_BRIGHTNESS_DEFAULT;
Marquee.colors_min = 1;
Marquee.colors_max = 1;
Marquee.colors.resize(1);
modes.push_back(Marquee);
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = CORSAIR_DRAM_MODE_RAINBOW;
Rainbow.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_AUTOMATIC_SAVE;
Rainbow.color_mode = MODE_COLORS_NONE;
Rainbow.speed_min = CORSAIR_DRAM_SPEED_SLOW;
Rainbow.speed_max = CORSAIR_DRAM_SPEED_FAST;
Rainbow.speed = CORSAIR_DRAM_SPEED_SLOW;
modes.push_back(Rainbow);
mode Sequential;
Sequential.name = "Sequential";
Sequential.value = CORSAIR_DRAM_MODE_SEQUENTIAL;
Sequential.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
Sequential.color_mode = MODE_COLORS_MODE_SPECIFIC;
Sequential.speed_min = CORSAIR_DRAM_SPEED_SLOW;
Sequential.speed_max = CORSAIR_DRAM_SPEED_FAST;
Sequential.speed = CORSAIR_DRAM_SPEED_SLOW;
Sequential.direction = MODE_DIRECTION_DOWN;
Sequential.brightness_min = CORSAIR_DRAM_BRIGHTNESS_MIN;
Sequential.brightness_max = CORSAIR_DRAM_BRIGHTNESS_MAX;
Sequential.brightness = CORSAIR_DRAM_BRIGHTNESS_DEFAULT;
Sequential.colors_min = 1;
Sequential.colors_max = 1;
Sequential.colors.resize(1);
modes.push_back(Sequential);
SetupZones();
}
RGBController_CorsairDRAM::~RGBController_CorsairDRAM()
{
delete controller;
}
void RGBController_CorsairDRAM::SetupZones()
{
/*-----------------------------------------------------*\
| Set up zone |
\*-----------------------------------------------------*/
zone new_zone;
new_zone.name = "Corsair DRAM";
new_zone.type = ZONE_TYPE_LINEAR;
new_zone.leds_min = controller->GetLEDCount();
new_zone.leds_max = controller->GetLEDCount();
new_zone.leds_count = controller->GetLEDCount();
new_zone.matrix_map = NULL;
zones.push_back(new_zone);
/*-----------------------------------------------------*\
| Set up LEDs |
\*-----------------------------------------------------*/
for(std::size_t led_idx = 0; led_idx < zones[0].leds_count; led_idx++)
{
led new_led;
new_led.name = "Corsair DRAM LED ";
new_led.name.append(std::to_string(led_idx));
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_CorsairDRAM::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*-----------------------------------------------------*\
| This device does not support resizing zones |
\*-----------------------------------------------------*/
}
void RGBController_CorsairDRAM::DeviceUpdateLEDs()
{
controller->SetColorsPerLED(colors.data());
}
void RGBController_CorsairDRAM::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_CorsairDRAM::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_CorsairDRAM::DeviceUpdateMode()
{
unsigned int corsair_direction = 0;
bool random = (modes[active_mode].color_mode == MODE_COLORS_RANDOM);
unsigned char mode_colors[6];
switch(modes[active_mode].direction)
{
case MODE_DIRECTION_LEFT:
corsair_direction = CORSAIR_DRAM_DIRECTION_LEFT;
break;
case MODE_DIRECTION_RIGHT:
corsair_direction = CORSAIR_DRAM_DIRECTION_RIGHT;
break;
case MODE_DIRECTION_UP:
corsair_direction = CORSAIR_DRAM_DIRECTION_UP;
break;
case MODE_DIRECTION_DOWN:
corsair_direction = CORSAIR_DRAM_DIRECTION_DOWN;
break;
case MODE_DIRECTION_HORIZONTAL:
corsair_direction = CORSAIR_DRAM_DIRECTION_HORIZONTAL;
break;
case MODE_DIRECTION_VERTICAL:
corsair_direction = CORSAIR_DRAM_DIRECTION_VERTICAL;
break;
}
mode_colors[0] = 0;
mode_colors[1] = 0;
mode_colors[2] = 0;
mode_colors[3] = 0;
mode_colors[4] = 0;
mode_colors[5] = 0;
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
mode_colors[0] = RGBGetRValue(modes[active_mode].colors[0]);
mode_colors[1] = RGBGetGValue(modes[active_mode].colors[0]);
mode_colors[2] = RGBGetBValue(modes[active_mode].colors[0]);
if(modes[active_mode].colors.size() == 2)
{
mode_colors[3] = RGBGetRValue(modes[active_mode].colors[1]);
mode_colors[4] = RGBGetGValue(modes[active_mode].colors[1]);
mode_colors[5] = RGBGetBValue(modes[active_mode].colors[1]);
}
}
if(modes[active_mode].name == "Direct")
{
controller->SetDirect(true);
}
else
{
controller->SetDirect(false);
}
controller->SetEffect(modes[active_mode].value,
modes[active_mode].speed,
corsair_direction,
random,
(unsigned char)modes[active_mode].brightness,
mode_colors[0],
mode_colors[1],
mode_colors[2],
mode_colors[3],
mode_colors[4],
mode_colors[5]);
std::this_thread::sleep_for(std::chrono::milliseconds(15));
}
@@ -1,8 +1,9 @@
/*---------------------------------------------------------*\
| RGBController_CorsairDominatorPlatinum.h |
| RGBController_CorsairDRAM.h |
| |
| RGBController for Corsair Dominator Platinum RAM |
| RGBController for Corsair DRAM RGB controllers |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
@@ -12,13 +13,13 @@
#pragma once
#include "RGBController.h"
#include "CorsairDominatorPlatinumController.h"
#include "CorsairDRAMController.h"
class RGBController_CorsairDominatorPlatinum : public RGBController
class RGBController_CorsairDRAM : public RGBController
{
public:
RGBController_CorsairDominatorPlatinum(CorsairDominatorPlatinumController* controller_ptr);
~RGBController_CorsairDominatorPlatinum();
RGBController_CorsairDRAM(CorsairDRAMController* controller_ptr);
~RGBController_CorsairDRAM();
void SetupZones();
@@ -31,5 +32,5 @@ public:
void DeviceUpdateMode();
private:
CorsairDominatorPlatinumController* controller;
CorsairDRAMController* controller;
};
@@ -1,134 +0,0 @@
/*---------------------------------------------------------*\
| CorsairDominatorPlatinumController.cpp |
| |
| Driver for Corsair Dominator Platinum RAM |
| |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <chrono>
#include "CorsairDominatorPlatinumController.h"
using namespace std::chrono_literals;
CorsairDominatorPlatinumController::CorsairDominatorPlatinumController(i2c_smbus_interface *bus, corsair_dev_id dev, unsigned int leds_count, std::string dev_name)
{
this->bus = bus;
this->dev = dev;
this->leds_count = leds_count;
this->name = dev_name;
led_data[0] = 0xc;
}
CorsairDominatorPlatinumController::~CorsairDominatorPlatinumController()
{
}
unsigned int CorsairDominatorPlatinumController::GetLEDCount()
{
return leds_count;
}
std::string CorsairDominatorPlatinumController::GetDeviceLocation()
{
std::string return_string(bus->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 CorsairDominatorPlatinumController::GetDeviceName()
{
return(name);
}
void CorsairDominatorPlatinumController::SetAllColors
(
unsigned char red,
unsigned char green,
unsigned char blue
)
{
for(unsigned int led = 0; led < leds_count; led++)
{
SetLEDColor(led, red, green, blue);
}
}
void CorsairDominatorPlatinumController::SetLEDColor
(
unsigned int led,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
if(led >= leds_count)
{
return;
}
unsigned int offset = (led * 3) + 1;
led_data[offset] = red;
led_data[offset + 1] = green;
led_data[offset + 2] = blue;
}
unsigned char CorsairDominatorPlatinumController::crc8
(
unsigned char init,
unsigned char poly,
unsigned char* data,
unsigned char len
)
{
unsigned char crc = init;
for(unsigned int i = 0; i < len; i++)
{
unsigned char val = data[i];
for(unsigned char mask = 0x80; mask != 0; mask >>= 1)
{
unsigned char bit;
if ((val & mask) != 0)
{
bit = (crc & 0x80) ^ 0x80;
}
else
{
bit = crc & 0x80;
}
if (bit == 0)
{
crc <<= 1;
}
else
{
crc = (crc << 1) ^ poly;
}
}
}
return crc;
}
void CorsairDominatorPlatinumController::ApplyColors()
{
unsigned char data[sizeof(led_data)];
memcpy(data, led_data, sizeof(led_data));
unsigned char crc = crc8(0x0, 0x7, data, sizeof(data) - 1);
data[sizeof(data) - 1] = crc;
bus->i2c_smbus_write_block_data(dev, 0x31, 0x20, data);
std::this_thread::sleep_for(800us);
bus->i2c_smbus_write_block_data(dev, 0x32, sizeof(data) - 0x20, data + 0x20);
std::this_thread::sleep_for(200us);
}
@@ -1,44 +0,0 @@
/*---------------------------------------------------------*\
| CorsairDominatorPlatinumController.h |
| |
| Driver for Corsair Dominator Platinum RAM |
| |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include "i2c_smbus.h"
#define CORSAIR_DOMINATOR_PLATINUM_DATA_SIZE 38
typedef unsigned char corsair_dev_id;
class CorsairDominatorPlatinumController
{
public:
CorsairDominatorPlatinumController(i2c_smbus_interface *bus, corsair_dev_id dev, unsigned int leds_count, std::string dev_name);
~CorsairDominatorPlatinumController();
std::string GetDeviceLocation();
std::string GetDeviceName();
unsigned int GetLEDCount();
void SetAllColors(unsigned char red, unsigned char green, unsigned char blue);
void SetLEDColor(unsigned int led, unsigned char red, unsigned char green, unsigned char blue);
void ApplyColors();
bool WaitReady();
private:
unsigned char led_data[CORSAIR_DOMINATOR_PLATINUM_DATA_SIZE];
i2c_smbus_interface* bus;
corsair_dev_id dev;
unsigned int leds_count;
std::string name;
static unsigned char crc8(unsigned char init, unsigned char poly, unsigned char *data, unsigned char len);
};
@@ -1,176 +0,0 @@
/*---------------------------------------------------------*\
| CorsairDominatorPlatinumControllerDetect.cpp |
| |
| Detector for Corsair Dominator Platinum RAM |
| |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <vector>
#include "Detector.h"
#include "CorsairDominatorPlatinumController.h"
#include "RGBController_CorsairDominatorPlatinum.h"
#include "SettingsManager.h"
#include "LogManager.h"
#include "i2c_smbus.h"
#include "pci_ids.h"
using namespace std::chrono_literals;
json corsair_dominator_models =
{
{
"CMT",
{
{"name", "Corsair Dominator Platinum"},
{"leds", 12}
}
},
{
"CMH",
{
{"name", "Corsair Vengeance Pro SL"},
{"leds", 10}
}
},
{
"CMN",
{
{"name", "Corsair Vengeance RGB RT"},
{"leds", 10}
}
},
{
"CMG",
{
{"name", "Corsair Vengeance RGB RS"},
{"leds", 6}
}
},
{
"CMP",
{
{"name", "Corsair Dominator Titanium"},
{"leds", 11}
}
}
};
#define CORSAIR_DOMINATOR_PLATINUM_NAME "Corsair Dominator Platinum"
bool TestForCorsairDominatorPlatinumController(i2c_smbus_interface *bus, unsigned char address)
{
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
LOG_DEBUG("[%s] Trying address %02X", CORSAIR_DOMINATOR_PLATINUM_NAME, address);
if(res < 0)
{
LOG_DEBUG("[%s] Failed: res was %04X", CORSAIR_DOMINATOR_PLATINUM_NAME, res);
return false;
}
res = bus->i2c_smbus_read_byte_data(address, 0x43);
if(!(res == 0x1A || res == 0x1B))
{
LOG_DEBUG("[%s] Failed: expected 0x1a or 0x1b, got %04X", CORSAIR_DOMINATOR_PLATINUM_NAME, res);
return false;
}
res = bus->i2c_smbus_read_byte_data(address, 0x44);
if(res != 0x04)
{
LOG_DEBUG("[%s] Failed: expected 0x04, got %04X", CORSAIR_DOMINATOR_PLATINUM_NAME, res);
return false;
}
return true;
}
/******************************************************************************************\
* *
* DetectCorsairDominatorPlatinumControllers *
* *
* Detect Corsair Dominator Platinum controllers on the enumerated I2C busses. *
* *
* bus - pointer to i2c_smbus_interface where Aura device is connected *
* dev - I2C address of Aura device *
* *
\******************************************************************************************/
void DetectCorsairDominatorPlatinumControllers(std::vector<i2c_smbus_interface *> &busses)
{
SettingsManager* settings_manager = ResourceManager::get()->GetSettingsManager();
json corsair_dominator_settings = settings_manager->GetSettings("CorsairDominatorSettings");
if(!corsair_dominator_settings.contains("model"))
{
// Set default value
corsair_dominator_settings["model"] = "CMT";
settings_manager->SetSettings("CorsairDominatorSettings", corsair_dominator_settings);
settings_manager->SaveSettings();
}
std::string model = corsair_dominator_settings["model"];
for(unsigned int bus = 0; bus < busses.size(); bus++)
{
IF_DRAM_SMBUS(busses[bus]->pci_vendor, busses[bus]->pci_device)
{
LOG_DEBUG("[%s] Testing bus %d", CORSAIR_DOMINATOR_PLATINUM_NAME, bus);
std::vector<unsigned char> addresses;
for(unsigned char addr = 0x58; addr <= 0x5F; addr++)
{
addresses.push_back(addr);
}
for(unsigned char addr = 0x18; addr <= 0x1F; addr++)
{
addresses.push_back(addr);
}
for(unsigned char addr : addresses)
{
if(TestForCorsairDominatorPlatinumController(busses[bus], addr))
{
unsigned int leds;
std::string name;
if(corsair_dominator_models.contains(model))
{
leds = corsair_dominator_models[model]["leds"];
name = corsair_dominator_models[model]["name"];
}
else
{
leds = corsair_dominator_models["CMT"]["leds"];
name = corsair_dominator_models["CMT"]["name"];
}
LOG_DEBUG("[%s] Model: %s, Leds: %d", CORSAIR_DOMINATOR_PLATINUM_NAME, name.c_str(), leds);
CorsairDominatorPlatinumController* controller = new CorsairDominatorPlatinumController(busses[bus], addr, leds, name);
RGBController_CorsairDominatorPlatinum* rgbcontroller = new RGBController_CorsairDominatorPlatinum(controller);
ResourceManager::get()->RegisterRGBController(rgbcontroller);
}
std::this_thread::sleep_for(10ms);
}
}
else
{
LOG_DEBUG("[%s] Bus %d is not a DRAM bus", CORSAIR_DOMINATOR_PLATINUM_NAME, bus);
}
}
} /* DetectCorsairDominatorPlatinumControllers() */
REGISTER_I2C_DETECTOR(CORSAIR_DOMINATOR_PLATINUM_NAME, DetectCorsairDominatorPlatinumControllers);
@@ -1,134 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_CorsairDominatorPlatinum.cpp |
| |
| RGBController for Corsair Dominator Platinum RAM |
| |
| Erik Gilling (konkers) 25 Sep 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_CorsairDominatorPlatinum.h"
/**------------------------------------------------------------------*\
@name Corsair Dominator Platinum
@category RAM
@type SMBus
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectCorsairDominatorPlatinumControllers
@comment
The Corsair Dominator controller chip can be found on several
Corsair memory sticks which have different LED counts. This can be controlled
by editing the Part Number in OpenRGB.json with values in the below table.
| Part Number | LED Count |
| :---------: | --------: |
| CMG | 6 |
| CMH | 10 |
| CMN | 10 |
| CMT | 12 |
\*-------------------------------------------------------------------*/
RGBController_CorsairDominatorPlatinum::RGBController_CorsairDominatorPlatinum(CorsairDominatorPlatinumController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "Corsair";
type = DEVICE_TYPE_DRAM;
description = "Corsair RAM RGB Device";
location = controller->GetDeviceLocation();
mode Direct;
Direct.name = "Direct";
Direct.value = 0;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.speed_min = 0;
Direct.speed_max = 0;
Direct.speed = 0;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
active_mode = 0;
}
RGBController_CorsairDominatorPlatinum::~RGBController_CorsairDominatorPlatinum()
{
delete controller;
}
void RGBController_CorsairDominatorPlatinum::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zone |
\*---------------------------------------------------------*/
zone new_zone;
new_zone.name = "Corsair RAM Zone";
new_zone.type = ZONE_TYPE_LINEAR;
new_zone.leds_min = controller->GetLEDCount();
new_zone.leds_max = controller->GetLEDCount();
new_zone.leds_count = controller->GetLEDCount();
new_zone.matrix_map = NULL;
zones.push_back(new_zone);
/*---------------------------------------------------------*\
| Set up LEDs |
\*---------------------------------------------------------*/
for(std::size_t led_idx = 0; led_idx < zones[0].leds_count; led_idx++)
{
led new_led;
new_led.name = "Corsair RAM LED ";
new_led.name.append(std::to_string(led_idx));
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_CorsairDominatorPlatinum::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_CorsairDominatorPlatinum::DeviceUpdateLEDs()
{
for(unsigned int led = 0; led < (unsigned int)colors.size(); led++)
{
RGBColor color = colors[led];
unsigned char red = RGBGetRValue(color);
unsigned char grn = RGBGetGValue(color);
unsigned char blu = RGBGetBValue(color);
controller->SetLEDColor(led, red, grn, blu);
}
controller->ApplyColors();
}
void RGBController_CorsairDominatorPlatinum::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_CorsairDominatorPlatinum::UpdateSingleLED(int led)
{
RGBColor color = colors[led];
unsigned char red = RGBGetRValue(color);
unsigned char grn = RGBGetGValue(color);
unsigned char blu = RGBGetBValue(color);
controller->SetLEDColor(led, red, grn, blu);
controller->ApplyColors();
}
void RGBController_CorsairDominatorPlatinum::DeviceUpdateMode()
{
}
@@ -96,7 +96,16 @@ void RGBController_CorsairICueLink::SetupZones()
lcd_zone.leds_min = controller->GetEndpoints()[lcd_idx]->led_channels;
lcd_zone.leds_max = lcd_zone.leds_min;
lcd_zone.leds_count = lcd_zone.leds_min;
zones.push_back(lcd_zone);
for(unsigned int led_idx = 0; led_idx < lcd_zone.leds_count; led_idx++)
{
led new_led;
new_led.name = "LED " + std::to_string(led_idx + 1);
leds.push_back(new_led);
}
}
}
}
@@ -35,6 +35,12 @@ CorsairPeripheralV2Controller::CorsairPeripheralV2Controller(hid_device* dev_han
write_cmd = CORSAIR_V2_WRITE_WIRELESS_ID;
pid = GetAddress(0x12);
break;
case CORSAIR_K57_RGB_WIRED_PID:
write_cmd = 0x80;
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
skip_reads = true;
break;
}
/*---------------------------------------------------------*\
@@ -93,14 +99,17 @@ CorsairPeripheralV2Controller::CorsairPeripheralV2Controller(hid_device* dev_han
| If lighting control endpoint 2 is unavailable |
| then use endpoint 1. |
\*---------------------------------------------------------*/
result = StartTransaction(0);
if(result > 0)
if(light_ctrl == CORSAIR_V2_LIGHT_CTRL2)
{
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
StartTransaction(0);
result = StartTransaction(0);
if(result > 0)
{
light_ctrl = CORSAIR_V2_LIGHT_CTRL1;
StartTransaction(0);
}
StopTransaction(0);
LOG_DEBUG("[%s] Lighting Endpoint set to %02X", device_name.c_str(), light_ctrl);
}
StopTransaction(0);
LOG_DEBUG("[%s] Lighting Endpoint set to %02X", device_name.c_str(), light_ctrl);
}
CorsairPeripheralV2Controller::~CorsairPeripheralV2Controller()
@@ -171,7 +180,11 @@ void CorsairPeripheralV2Controller::SetRenderMode(corsair_v2_device_mode mode)
buffer[5] = mode;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
}
void CorsairPeripheralV2Controller::LightingControl(uint8_t opt1)
@@ -191,7 +204,11 @@ void CorsairPeripheralV2Controller::LightingControl(uint8_t opt1)
buffer[5] = 0x00;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
}
unsigned int CorsairPeripheralV2Controller::GetKeyboardLayout()
@@ -240,7 +257,11 @@ unsigned char CorsairPeripheralV2Controller::StartTransaction(uint8_t opt1)
buffer[4] = light_ctrl;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
return buffer[2];
}
@@ -257,11 +278,20 @@ void CorsairPeripheralV2Controller::StopTransaction(uint8_t opt1)
buffer[4] = opt1;
hid_write(dev, buffer, CORSAIR_V2_WRITE_SIZE);
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, CORSAIR_V2_WRITE_SIZE, CORSAIR_V2_TIMEOUT);
}
}
void CorsairPeripheralV2Controller::ClearPacketBuffer()
{
if(skip_reads)
{
return;
}
uint8_t result = 0;
uint8_t buffer[CORSAIR_V2_PACKET_SIZE];
@@ -304,7 +334,11 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
memcpy(&buffer[offset1], &data[0], copy_bytes);
hid_write(dev, buffer, pkt_sze);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
}
remaining -= copy_bytes;
buffer[2] = CORSAIR_V2_CMD_BLK_WN;
@@ -325,7 +359,11 @@ void CorsairPeripheralV2Controller::SetLEDs(uint8_t *data, uint16_t data_size)
memcpy(&buffer[offset2], &data[index], copy_bytes);
hid_write(dev, buffer, pkt_sze);
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
if(!skip_reads)
{
hid_read_timeout(dev, buffer, pkt_sze, CORSAIR_V2_TIMEOUT_SHORT);
}
remaining -= copy_bytes;
}
@@ -109,6 +109,7 @@ private:
uint8_t write_cmd = CORSAIR_V2_WRITE_WIRED_ID;
uint16_t pkt_sze = CORSAIR_V2_WRITE_SIZE;
bool skip_reads = false;
std::string firmware_version;
std::string location;
};
@@ -57,6 +57,7 @@ void DetectCorsairV2SoftwareControllers(hid_device_info* info, const std::string
| Keyboards |
\*-----------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IP("Corsair K55 RGB PRO", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K55_RGB_PRO_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K57 RGB (Wired)", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K57_RGB_WIRED_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO Low Profile", DetectCorsairV2SoftwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_LP_PID, 1, 0xFF42);
REGISTER_HID_DETECTOR_IP("Corsair K60 RGB PRO TKL Black", DetectCorsairV2HardwareControllers, CORSAIR_VID, CORSAIR_K60_RGB_PRO_TKL_B_PID, 1, 0xFF42);
@@ -48,6 +48,31 @@ std::vector<unsigned int> corsair_full_size_values =
/*-------------------------------------------------------------------------*\
| KEYMAPS |
\*-------------------------------------------------------------------------*/
keyboard_keymap_overlay_values corsair_K57_layout
{
KEYBOARD_SIZE::KEYBOARD_SIZE_FULL,
{
corsair_full_size_values,
{
/* Add more regional layout fixes here */
}
},
{
/*-------------------------------------------------------------------------------------------------------------------------------------*\
| Edit Keys |
| Zone, Row, Column, Value, Name, Alternate Name, OpCode |
\*-------------------------------------------------------------------------------------------------------------------------------------*/
{ 0, 0, 17, 0, "Power/Wireless Indicator", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_ROW, }, // Insert Profile into new row
{ 0, 0, 18, 1, "Lock/Macro Indicator", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Light key
{ 0, 1, 0, 131, "Key: G1", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G1 key
{ 0, 2, 0, 132, "Key: G2", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G2 key
{ 0, 3, 0, 133, "Key: G3", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G3 key
{ 0, 4, 0, 134, "Key: G4", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G4 key
{ 0, 5, 0, 135, "Key: G5", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G5 key
{ 0, 6, 0, 136, "Key: G6", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert G6 key
}
};
keyboard_keymap_overlay_values corsair_K60_layout
{
KEYBOARD_SIZE::KEYBOARD_SIZE_FULL,
@@ -116,12 +141,14 @@ keyboard_keymap_overlay_values corsair_k70_pro_layout
{ 0, 0, 0, 128, "Profile", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_ROW, }, // Insert Profile into new row
{ 0, 0, 1, 113, "Light", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Light key
{ 0, 0, 2, 114, "Lock", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Lock Key
{ 0, 0, 10, 191, "Logo", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Logo
{ 0, 0, 10, 138, "Logo", KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Logo
{ 0, 0, 18, 102, KEY_EN_MEDIA_MUTE, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Mute Key
{ 0, 1, 17, 123, KEY_EN_MEDIA_STOP, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Stop Key
{ 0, 1, 18, 126, KEY_EN_MEDIA_PREVIOUS, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Previous Track Key
{ 0, 1, 19, 124, KEY_EN_MEDIA_PLAY_PAUSE, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Play Pause Key
{ 0, 1, 20, 125, KEY_EN_MEDIA_NEXT, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert Next Tack Key
{ 0, 6, 5, 140, KEY_EN_SPACE, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert spacebar L
{ 0, 6, 7, 141, KEY_EN_SPACE, KEY_EN_UNUSED, KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT, }, // Insert spacebar R
}
};
@@ -167,10 +194,10 @@ keyboard_keymap_overlay_values corsair_K70_CORE_TKL_layout
| Edit Keys |
| Zone, Row, Column, Value, Name, Alternate Name, OpCode, |
\*-------------------------------------------------------------------------------------------------------------------------------------*/
{ 0, 0, 14, 70, KEY_EN_MEDIA_MUTE, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Swap PRSC with Mute
{ 0, 0, 14, 70, KEY_EN_MEDIA_MUTE, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Swap PRSC with Mute
{ 0, 0, 15, 71, KEY_EN_UNUSED, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Remove SCLK
{ 0, 0, 16, 72, KEY_EN_MEDIA_VOLUME_UP, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Swap PSBK with Volume Potion Up
{ 0, 0, 16, 72, KEY_EN_MEDIA_VOLUME_UP, KEY_EN_UNUSED, KEYBOARD_OPCODE_SWAP_ONLY, }, // Swap PSBK with Volume Potion Up
}
};
@@ -772,6 +799,38 @@ static const corsair_v2_device k55_rgb_pro_device =
nullptr
};
/*-------------------------------------------------------------*\
| Corsair K57 RGB (Wired) 1B1C:1B6E |
| |
| Zone "Keyboard" |
| Matrix |
| 7 Rows, 22 Columns |
\*-------------------------------------------------------------*/
static const corsair_v2_zone k57_rgb_wired_zone =
{
ZONE_EN_KEYBOARD,
ZONE_TYPE_MATRIX,
7,
22
};
static const corsair_v2_device k57_rgb_wired_device =
{
CORSAIR_K57_RGB_WIRED_PID,
DEVICE_TYPE_KEYBOARD,
7,
22,
{
&k57_rgb_wired_zone,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr
},
&corsair_K57_layout
};
/*-------------------------------------------------------------*\
| Corsair K60 RGB Pro 1B1C:1BA0 |
| |
@@ -1461,6 +1520,7 @@ const corsair_v2_device* corsair_v2_device_list_data[] =
| KEYBOARDS |
\*-----------------------------------------------------------------*/
&k55_rgb_pro_device,
&k57_rgb_wired_device,
&k60_rgb_pro_device,
&k60_rgb_pro_lp_device,
&k60_rgb_pro_tkl_device_b,
@@ -69,6 +69,8 @@ typedef struct
| Corsair V2 Protocol Keyboards |
\*-----------------------------------------------------*/
#define CORSAIR_K55_RGB_PRO_PID 0x1BA4
#define CORSAIR_K57_RGB_WIRED_PID 0x1B6E
#define CORSAIR_K57_RGB_WIRELESS_PID 0x1B62
#define CORSAIR_K60_RGB_PRO_PID 0x1BA0
#define CORSAIR_K60_RGB_PRO_LP_PID 0x1BAD
#define CORSAIR_K60_RGB_PRO_TKL_B_PID 0x1BC7
@@ -1,213 +0,0 @@
/*---------------------------------------------------------*\
| CorsairVengeanceProController.cpp |
| |
| Driver for Corsair Vengeance Pro RGB RAM |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <chrono>
#include "CorsairVengeanceProController.h"
using namespace std::chrono_literals;
CorsairVengeanceProController::CorsairVengeanceProController(i2c_smbus_interface* bus, corsair_dev_id dev)
{
this->bus = bus;
this->dev = dev;
strcpy(device_name, "Corsair Vengeance Pro RGB");
led_count = CORSAIR_PRO_LED_COUNT;
direct_mode = false;
effect_mode = CORSAIR_PRO_MODE_STATIC;
for (unsigned int i = 0; i < led_count; i++)
{
led_red[i] = 0;
led_green[i] = 0;
led_blue[i] = 0;
}
}
CorsairVengeanceProController::~CorsairVengeanceProController()
{
}
std::string CorsairVengeanceProController::GetDeviceName()
{
return(device_name);
}
std::string CorsairVengeanceProController::GetDeviceLocation()
{
std::string return_string(bus->device_name);
char addr[5];
snprintf(addr, 5, "0x%02X", dev);
return_string.append(", address ");
return_string.append(addr);
return("I2C: " + return_string);
}
unsigned int CorsairVengeanceProController::GetLEDCount()
{
return(led_count);
}
unsigned char CorsairVengeanceProController::GetEffect()
{
return(effect_mode);
}
void CorsairVengeanceProController::SetAllColors(unsigned char red, unsigned char green, unsigned char blue)
{
for (unsigned int i = 0; i < led_count; i++)
{
led_red[i] = red;
led_green[i] = green;
led_blue[i] = blue;
}
}
void CorsairVengeanceProController::SetLEDColor(unsigned int led, unsigned char red, unsigned char green, unsigned char blue)
{
led_red[led] = red;
led_green[led] = green;
led_blue[led] = blue;
}
void CorsairVengeanceProController::ApplyColors()
{
if(direct_mode)
{
unsigned char full_packet[32];
unsigned char crc_packet[31];
full_packet[0] = 0x0A;
crc_packet[0] = 0x0A;
for (int i = 0; i < 10; i++)
{
crc_packet[(i * 3) + 1] = led_red[i];
full_packet[(i * 3) + 1] = led_red[i];
crc_packet[(i * 3) + 2] = led_green[i];
full_packet[(i * 3) + 2] = led_green[i];
crc_packet[(i * 3) + 3] = led_blue[i];
full_packet[(i * 3) + 3] = led_blue[i];
}
uint8_t footer = CRCPP::CRC::Calculate(crc_packet, 31, CRCPP::CRC::CRC_8());
full_packet[31] = footer;
bus->i2c_smbus_write_block_data(dev, CORSAIR_PRO_DIRECT_COMMAND, 32, full_packet);
}
else
{
bus->i2c_smbus_write_byte_data(dev, 0x26, 0x02);
std::this_thread::sleep_for(1ms);
bus->i2c_smbus_write_byte_data(dev, 0x21, 0x00);
std::this_thread::sleep_for(1ms);
for (int i = 0; i < 10; i++)
{
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, led_red[i]);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, led_green[i]);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, led_blue[i]);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0xFF);
}
bus->i2c_smbus_write_byte_data(dev, 0x82, 0x02);
}
}
void CorsairVengeanceProController::SetEffect(unsigned char mode,
unsigned char speed,
unsigned char direction,
bool random,
unsigned char red1,
unsigned char grn1,
unsigned char blu1,
unsigned char red2,
unsigned char grn2,
unsigned char blu2
)
{
if(mode == effect_mode)
{
return;
}
effect_mode = mode;
direct_mode = (effect_mode == CORSAIR_PRO_MODE_DIRECT);
if(direct_mode)
{
return;
}
bus->i2c_smbus_write_byte_data(dev, 0x26, 0x01);
std::this_thread::sleep_for(1ms);
bus->i2c_smbus_write_byte_data(dev, 0x21, 0x00);
std::this_thread::sleep_for(1ms);
unsigned char random_byte;
if(random)
{
random_byte = CORSAIR_PRO_EFFECT_RANDOM_COLORS;
}
else
{
random_byte = CORSAIR_PRO_EFFECT_CUSTOM_COLORS;
}
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, effect_mode); // Mode
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, speed); // Speed
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, random_byte); // Custom color
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, direction); // Direction
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, red1); // Custom color 1 red
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, grn1); // Custom color 1 green
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, blu1); // Custom color 1 blue
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0xFF);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, red2); // Custom color 2 red
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, grn2); // Custom color 2 green
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, blu2); // Custom color 2 blue
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0xFF);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0x00);
bus->i2c_smbus_write_byte_data(dev, CORSAIR_PRO_REG_COMMAND, 0x00);
bus->i2c_smbus_write_byte_data(dev, 0x82, 0x01);
WaitReady();
}
bool CorsairVengeanceProController::WaitReady()
{
int i = 0;
while (bus->i2c_smbus_read_byte_data(dev, 0x41) != 0x00)
{
i++;
std::this_thread::sleep_for(1ms);
}
return false;
}
void CorsairVengeanceProController::SetDirect(bool direct)
{
direct_mode = direct;
}
@@ -1,109 +0,0 @@
/*---------------------------------------------------------*\
| CorsairVengeanceProController.h |
| |
| Driver for Corsair Vengeance Pro RGB RAM |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include "i2c_smbus.h"
#include "CRC.h"
typedef unsigned char corsair_dev_id;
#define CORSAIR_PRO_LED_COUNT ( 10 )
#define CORSAIR_VENGEANCE_RGB_PRO_NAME "Corsair Vengeance RGB PRO"
enum
{
CORSAIR_PRO_REG_COMMAND = 0x20, /* Command write register */
CORSAIR_PRO_DIRECT_COMMAND = 0x31, /* Where it writes direct mode colors */
};
enum
{
CORSAIR_PRO_MODE_DIRECT = 0xDD, /* Arbitrary value to compare against later. Not the actual packet */
CORSAIR_PRO_MODE_COLOR_SHIFT = 0x00, /* Color Shift mode */
CORSAIR_PRO_MODE_COLOR_PULSE = 0x01, /* Color Pulse mode */
CORSAIR_PRO_MODE_RAINBOW_WAVE = 0x03, /* Rainbow Wave mode */
CORSAIR_PRO_MODE_COLOR_WAVE = 0x04, /* Color Wave mode */
CORSAIR_PRO_MODE_VISOR = 0x05, /* Visor mode */
CORSAIR_PRO_MODE_RAIN = 0x06, /* Rain mode */
CORSAIR_PRO_MODE_MARQUEE = 0x07, /* Marquee mode */
CORSAIR_PRO_MODE_RAINBOW = 0x08, /* Rainbow mode */
CORSAIR_PRO_MODE_SEQUENTIAL = 0x09, /* Sequential mode */
CORSAIR_PRO_MODE_STATIC = 0x10, /* Static mode */
CORSAIR_PRO_NUMBER_MODES = 10, /* Number of Corsair Pro modes */
};
enum
{
CORSAIR_PRO_SPEED_SLOW = 0x00, /* Slow speed */
CORSAIR_PRO_SPEED_MEDIUM = 0x01, /* Medium speed */
CORSAIR_PRO_SPEED_FAST = 0x02, /* Fast speed */
};
enum
{
CORSAIR_PRO_EFFECT_RANDOM_COLORS = 0x00, /* Random colors */
CORSAIR_PRO_EFFECT_CUSTOM_COLORS = 0x01, /* Custom colors */
};
enum
{
CORSAIR_PRO_DIRECTION_UP = 0x00, /* Up direction */
CORSAIR_PRO_DIRECTION_DOWN = 0x01, /* Down direction */
CORSAIR_PRO_DIRECTION_LEFT = 0x02, /* Left direction */
CORSAIR_PRO_DIRECTION_RIGHT = 0x03, /* Right direction */
CORSAIR_PRO_DIRECTION_VERTICAL = 0x01, /* Vertical direction */
CORSAIR_PRO_DIRECTION_HORIZONTAL = 0x03, /* Horizontal direction */
};
class CorsairVengeanceProController
{
public:
CorsairVengeanceProController(i2c_smbus_interface* bus, corsair_dev_id dev);
~CorsairVengeanceProController();
std::string GetDeviceName();
std::string GetDeviceLocation();
unsigned int GetLEDCount();
unsigned char GetEffect();
void SetEffect(unsigned char mode,
unsigned char speed,
unsigned char direction,
bool random,
unsigned char red1,
unsigned char grn1,
unsigned char blu1,
unsigned char red2,
unsigned char grn2,
unsigned char blu2
);
void SetAllColors(unsigned char red, unsigned char green, unsigned char blue);
void SetLEDColor(unsigned int led, unsigned char red, unsigned char green, unsigned char blue);
void ApplyColors();
bool WaitReady();
void SetDirect(bool direct);
private:
char device_name[32];
unsigned int led_count;
bool direct_mode;
unsigned char effect_mode;
unsigned char led_red[CORSAIR_PRO_LED_COUNT];
unsigned char led_green[CORSAIR_PRO_LED_COUNT];
unsigned char led_blue[CORSAIR_PRO_LED_COUNT];
i2c_smbus_interface* bus;
corsair_dev_id dev;
};
@@ -1,107 +0,0 @@
/*---------------------------------------------------------*\
| CorsairVengeanceProControllerDetect.cpp |
| |
| Detector for Corsair Vengeance Pro RGB RAM |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <vector>
#include "Detector.h"
#include "CorsairVengeanceProController.h"
#include "RGBController_CorsairVengeancePro.h"
#include "i2c_smbus.h"
#include "pci_ids.h"
#include "LogManager.h"
using namespace std::chrono_literals;
/******************************************************************************************\
* *
* TestForCorsairVengeanceProController *
* *
* Tests the given address to see if a Corsair Pro controller exists there. *
* *
\******************************************************************************************/
bool TestForCorsairVengeanceProController(i2c_smbus_interface* bus, unsigned char address)
{
bool pass = false;
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
LOG_DEBUG("[%s] Trying address %02X", CORSAIR_VENGEANCE_RGB_PRO_NAME, address);
if(res >= 0)
{
pass = true;
res = bus->i2c_smbus_read_byte_data(address, 0x43);
if (res != 0x1C)
{
pass = false;
LOG_DEBUG("[%s] Failed: was expecting 0x1C got %02X", CORSAIR_VENGEANCE_RGB_PRO_NAME, res);
}
res = bus->i2c_smbus_read_byte_data(address, 0x44);
if(!((res == 0x03) || (res == 0x04)))
{
pass = false;
LOG_DEBUG("[%s] Failed: was expecting 0x03 or 0x04 got %02X", CORSAIR_VENGEANCE_RGB_PRO_NAME, res);
}
}
else
{
LOG_DEBUG("[%s] Failed: res was %04X", CORSAIR_VENGEANCE_RGB_PRO_NAME, res);
}
std::this_thread::sleep_for(10ms);
return(pass);
} /* TestForCorsairVengeanceProController() */
/******************************************************************************************\
* *
* DetectCorsairVengeanceProControllers *
* *
* Detect Corsair Vengeance Pro controllers on the enumerated I2C busses. *
* *
* bus - pointer to i2c_smbus_interface where Aura device is connected *
* dev - I2C address of Aura device *
* *
\******************************************************************************************/
void DetectCorsairVengeanceProControllers(std::vector<i2c_smbus_interface*> &busses)
{
for(unsigned int bus = 0; bus < busses.size(); bus++)
{
LOG_DEBUG("[%s] Testing bus %d", CORSAIR_VENGEANCE_RGB_PRO_NAME, bus);
IF_DRAM_SMBUS(busses[bus]->pci_vendor, busses[bus]->pci_device)
{
for(unsigned char addr = 0x58; addr <= 0x5F; addr++)
{
if(TestForCorsairVengeanceProController(busses[bus], addr))
{
CorsairVengeanceProController* new_controller = new CorsairVengeanceProController(busses[bus], addr);
RGBController_CorsairVengeancePro* new_rgbcontroller = new RGBController_CorsairVengeancePro(new_controller);
ResourceManager::get()->RegisterRGBController(new_rgbcontroller);
}
}
}
else
{
LOG_DEBUG("[%s] Bus %d is not a DRAM bus", CORSAIR_VENGEANCE_RGB_PRO_NAME, bus);
}
}
} /* DetectCorsairVengeanceProControllers() */
REGISTER_I2C_DETECTOR("Corsair Vengeance Pro", DetectCorsairVengeanceProControllers);
@@ -1,316 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_CorsairVengeancePro.cpp |
| |
| RGBController for Corsair Vengeance Pro RGB RAM |
| |
| Adam Honse (CalcProgrammer1) 30 Jun 2019 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_CorsairVengeancePro.h"
/**------------------------------------------------------------------*\
@name Corsair Vengeance Pro
@category RAM
@type SMBus
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectCorsairVengeanceProControllers
@comment
\*-------------------------------------------------------------------*/
RGBController_CorsairVengeancePro::RGBController_CorsairVengeancePro(CorsairVengeanceProController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "Corsair";
type = DEVICE_TYPE_DRAM;
description = "Corsair Vengeance Pro RGB Device";
location = controller->GetDeviceLocation();
mode Direct;
Direct.name = "Direct";
Direct.value = CORSAIR_PRO_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.speed_min = 0;
Direct.speed_max = 0;
Direct.speed = 0;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = CORSAIR_PRO_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Static.speed_min = 0;
Static.speed_max = 0;
Static.speed = 0;
Static.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Static);
mode ColorShift;
ColorShift.name = "Color Shift";
ColorShift.value = CORSAIR_PRO_MODE_COLOR_SHIFT;
ColorShift.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
ColorShift.speed_min = CORSAIR_PRO_SPEED_SLOW;
ColorShift.speed_max = CORSAIR_PRO_SPEED_FAST;
ColorShift.colors_min = 2;
ColorShift.colors_max = 2;
ColorShift.speed = CORSAIR_PRO_SPEED_SLOW;
ColorShift.color_mode = MODE_COLORS_MODE_SPECIFIC;
ColorShift.colors.resize(2);
modes.push_back(ColorShift);
mode ColorPulse;
ColorPulse.name = "Color Pulse";
ColorPulse.value = CORSAIR_PRO_MODE_COLOR_PULSE;
ColorPulse.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
ColorPulse.speed_min = CORSAIR_PRO_SPEED_SLOW;
ColorPulse.speed_max = CORSAIR_PRO_SPEED_FAST;
ColorPulse.colors_min = 2;
ColorPulse.colors_max = 2;
ColorPulse.speed = CORSAIR_PRO_SPEED_SLOW;
ColorPulse.color_mode = MODE_COLORS_MODE_SPECIFIC;
ColorPulse.colors.resize(2);
modes.push_back(ColorPulse);
mode RainbowWave;
RainbowWave.name = "Rainbow Wave";
RainbowWave.value = CORSAIR_PRO_MODE_RAINBOW_WAVE;
RainbowWave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD;
RainbowWave.speed_min = CORSAIR_PRO_SPEED_SLOW;
RainbowWave.speed_max = CORSAIR_PRO_SPEED_FAST;
RainbowWave.speed = CORSAIR_PRO_SPEED_SLOW;
RainbowWave.direction = MODE_DIRECTION_DOWN;
RainbowWave.color_mode = MODE_COLORS_NONE;
modes.push_back(RainbowWave);
mode ColorWave;
ColorWave.name = "Color Wave";
ColorWave.value = CORSAIR_PRO_MODE_COLOR_WAVE;
ColorWave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
ColorWave.speed_min = CORSAIR_PRO_SPEED_SLOW;
ColorWave.speed_max = CORSAIR_PRO_SPEED_FAST;
ColorWave.colors_min = 2;
ColorWave.colors_max = 2;
ColorWave.speed = CORSAIR_PRO_SPEED_SLOW;
ColorWave.direction = MODE_DIRECTION_DOWN;
ColorWave.color_mode = MODE_COLORS_MODE_SPECIFIC;
ColorWave.colors.resize(2);
modes.push_back(ColorWave);
mode Visor;
Visor.name = "Visor";
Visor.value = CORSAIR_PRO_MODE_VISOR;
Visor.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_HV | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
Visor.speed_min = CORSAIR_PRO_SPEED_SLOW;
Visor.speed_max = CORSAIR_PRO_SPEED_FAST;
Visor.colors_min = 2;
Visor.colors_max = 2;
Visor.speed = CORSAIR_PRO_SPEED_SLOW;
Visor.direction = MODE_DIRECTION_VERTICAL;
Visor.color_mode = MODE_COLORS_MODE_SPECIFIC;
Visor.colors.resize(2);
modes.push_back(Visor);
mode Rain;
Rain.name = "Rain";
Rain.value = CORSAIR_PRO_MODE_RAIN;
Rain.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
Rain.speed_min = CORSAIR_PRO_SPEED_SLOW;
Rain.speed_max = CORSAIR_PRO_SPEED_FAST;
Rain.colors_min = 2;
Rain.colors_max = 2;
Rain.speed = CORSAIR_PRO_SPEED_SLOW;
Rain.direction = MODE_DIRECTION_DOWN;
Rain.color_mode = MODE_COLORS_MODE_SPECIFIC;
Rain.colors.resize(2);
modes.push_back(Rain);
mode Marquee;
Marquee.name = "Marquee";
Marquee.value = CORSAIR_PRO_MODE_MARQUEE;
Marquee.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Marquee.speed_min = CORSAIR_PRO_SPEED_SLOW;
Marquee.speed_max = CORSAIR_PRO_SPEED_FAST;
Marquee.colors_min = 1;
Marquee.colors_max = 1;
Marquee.speed = CORSAIR_PRO_SPEED_SLOW;
Marquee.color_mode = MODE_COLORS_MODE_SPECIFIC;
Marquee.colors.resize(1);
modes.push_back(Marquee);
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = CORSAIR_PRO_MODE_RAINBOW;
Rainbow.flags = MODE_FLAG_HAS_SPEED;
Rainbow.speed_min = CORSAIR_PRO_SPEED_SLOW;
Rainbow.speed_max = CORSAIR_PRO_SPEED_FAST;
Rainbow.speed = CORSAIR_PRO_SPEED_SLOW;
Rainbow.color_mode = MODE_COLORS_NONE;
modes.push_back(Rainbow);
mode Sequential;
Sequential.name = "Sequential";
Sequential.value = CORSAIR_PRO_MODE_SEQUENTIAL;
Sequential.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR;
Sequential.speed_min = CORSAIR_PRO_SPEED_SLOW;
Sequential.speed_max = CORSAIR_PRO_SPEED_FAST;
Sequential.colors_min = 1;
Sequential.colors_max = 1;
Sequential.speed = CORSAIR_PRO_SPEED_SLOW;
Sequential.direction = MODE_DIRECTION_DOWN;
Sequential.color_mode = MODE_COLORS_MODE_SPECIFIC;
Sequential.colors.resize(1);
modes.push_back(Sequential);
SetupZones();
active_mode = 9;
}
RGBController_CorsairVengeancePro::~RGBController_CorsairVengeancePro()
{
delete controller;
}
void RGBController_CorsairVengeancePro::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zone |
\*---------------------------------------------------------*/
zone new_zone;
new_zone.name = "Corsair Pro Zone";
new_zone.type = ZONE_TYPE_LINEAR;
new_zone.leds_min = controller->GetLEDCount();
new_zone.leds_max = controller->GetLEDCount();
new_zone.leds_count = controller->GetLEDCount();
new_zone.matrix_map = NULL;
zones.push_back(new_zone);
/*---------------------------------------------------------*\
| Set up LEDs |
\*---------------------------------------------------------*/
for(std::size_t led_idx = 0; led_idx < zones[0].leds_count; led_idx++)
{
led* new_led = new led();
new_led->name = "Corsair Pro LED ";
new_led->name.append(std::to_string(led_idx));
leds.push_back(*new_led);
}
SetupColors();
}
void RGBController_CorsairVengeancePro::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_CorsairVengeancePro::DeviceUpdateLEDs()
{
for(unsigned int led = 0; led < (unsigned int)colors.size(); led++)
{
RGBColor color = colors[led];
unsigned char red = RGBGetRValue(color);
unsigned char grn = RGBGetGValue(color);
unsigned char blu = RGBGetBValue(color);
controller->SetLEDColor(led, red, grn, blu);
}
controller->ApplyColors();
}
void RGBController_CorsairVengeancePro::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_CorsairVengeancePro::UpdateSingleLED(int led)
{
RGBColor color = colors[led];
unsigned char red = RGBGetRValue(color);
unsigned char grn = RGBGetGValue(color);
unsigned char blu = RGBGetBValue(color);
controller->SetLEDColor(led, red, grn, blu);
controller->ApplyColors();
}
void RGBController_CorsairVengeancePro::DeviceUpdateMode()
{
unsigned int corsair_direction = 0;
bool random = (modes[active_mode].color_mode == MODE_COLORS_RANDOM);
unsigned char mode_colors[6];
switch(modes[active_mode].direction)
{
case MODE_DIRECTION_LEFT:
corsair_direction = CORSAIR_PRO_DIRECTION_LEFT;
break;
case MODE_DIRECTION_RIGHT:
corsair_direction = CORSAIR_PRO_DIRECTION_RIGHT;
break;
case MODE_DIRECTION_UP:
corsair_direction = CORSAIR_PRO_DIRECTION_UP;
break;
case MODE_DIRECTION_DOWN:
corsair_direction = CORSAIR_PRO_DIRECTION_DOWN;
break;
case MODE_DIRECTION_HORIZONTAL:
corsair_direction = CORSAIR_PRO_DIRECTION_HORIZONTAL;
break;
case MODE_DIRECTION_VERTICAL:
corsair_direction = CORSAIR_PRO_DIRECTION_VERTICAL;
break;
}
mode_colors[0] = 0;
mode_colors[1] = 0;
mode_colors[2] = 0;
mode_colors[3] = 0;
mode_colors[4] = 0;
mode_colors[5] = 0;
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
mode_colors[0] = RGBGetRValue(modes[active_mode].colors[0]);
mode_colors[1] = RGBGetGValue(modes[active_mode].colors[0]);
mode_colors[2] = RGBGetBValue(modes[active_mode].colors[0]);
if(modes[active_mode].colors.size() == 2)
{
mode_colors[3] = RGBGetRValue(modes[active_mode].colors[1]);
mode_colors[4] = RGBGetGValue(modes[active_mode].colors[1]);
mode_colors[5] = RGBGetBValue(modes[active_mode].colors[1]);
}
}
if (modes[active_mode].name == "Direct")
{
controller->SetDirect(true);
}
else
{
controller->SetDirect(false);
}
controller->SetEffect(modes[active_mode].value,
modes[active_mode].speed,
corsair_direction,
random,
mode_colors[0],
mode_colors[1],
mode_colors[2],
mode_colors[3],
mode_colors[4],
mode_colors[5]);
std::this_thread::sleep_for(std::chrono::milliseconds(15));
}
@@ -1,217 +0,0 @@
/*---------------------------------------------------------*\
| CorsairWirelessController.cpp |
| |
| Driver for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "CorsairWirelessController.h"
#include "StringUtils.h"
using namespace std::chrono_literals;
CorsairWirelessController::CorsairWirelessController(hid_device* dev_handle, const char* path)
{
dev = dev_handle;
location = path;
type = DEVICE_TYPE_KEYBOARD;
EnterDirectMode();
}
CorsairWirelessController::~CorsairWirelessController()
{
hid_close(dev);
}
device_type CorsairWirelessController::GetDeviceType()
{
return type;
}
std::string CorsairWirelessController::GetDeviceLocation()
{
return("HID: " + location);
}
std::string CorsairWirelessController::GetFirmwareString()
{
return firmware_version;
}
std::string CorsairWirelessController::GetName()
{
return name;
}
std::string CorsairWirelessController::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void CorsairWirelessController::SetLEDs(std::vector<RGBColor>colors)
{
unsigned char buf[3 * 137];
for(int color = 0; color < 137; color++)
{
buf[0 + color] = RGBGetRValue(colors[color]);
buf[137 + color] = RGBGetGValue(colors[color]);
buf[274+color] = RGBGetBValue(colors[color]);
}
StartDirectMode();
SendDirectFrame(true, &buf[0]);
SendDirectFrame(false, &buf[57]);
SendDirectFrame(false, &buf[118]);
SendDirectFrame(false, &buf[179]);
SendDirectFrame(false, &buf[240]);
SendDirectFrame(false, &buf[301]);
SendDirectFrame(false, &buf[362]);
}
void CorsairWirelessController::SetName(std::string device_name)
{
name = device_name;
}
/*-------------------------------------------------------------------------------------------------*\
| Private packet sending functions. |
\*-------------------------------------------------------------------------------------------------*/
void CorsairWirelessController::EnterDirectMode()
{
unsigned char usb_buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Submit Mouse Colors packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x80;
usb_buf[0x02] = 0x01;
usb_buf[0x03] = 0x03;
usb_buf[0x04] = 0x00;
usb_buf[0x05] = 0x02;
/*-----------------------------------------------------*\
| Send packet using feature reports, as headset stand |
| seems to not update completely using HID writes |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 65);
}
void CorsairWirelessController::StartDirectMode()
{
unsigned char usb_buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Submit Mouse Colors packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x80;
usb_buf[0x02] = 0x0D;
usb_buf[0x03] = 0x00;
usb_buf[0x04] = 0x01;
/*-----------------------------------------------------*\
| Send packet using feature reports, as headset stand |
| seems to not update completely using HID writes |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 65);
}
void CorsairWirelessController::ExitDirectMode()
{
unsigned char usb_buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Submit Mouse Colors packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x80;
usb_buf[0x02] = 0x01;
usb_buf[0x03] = 0x03;
usb_buf[0x04] = 0x00;
usb_buf[0x05] = 0x01;
/*-----------------------------------------------------*\
| Send packet using feature reports, as headset stand |
| seems to not update completely using HID writes |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 65);
}
void CorsairWirelessController::SendDirectFrame(bool first_frame, unsigned char* data)
{
unsigned char usb_buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Submit Mouse Colors packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x80;
usb_buf[0x02] = first_frame ? 0x06 : 0x07;
usb_buf[0x03] = 0x00;
if(first_frame)
{
usb_buf[0x04] = 0x9B;
usb_buf[0x05] = 0x01;
}
/*-----------------------------------------------------*\
| Copy in colors in <RED> <GREEN> <BLUE> order |
\*-----------------------------------------------------*/
if(first_frame)
{
memcpy(&usb_buf[0x08], data, 57);
}
else
{
memcpy(&usb_buf[0x04], data, 61);
}
/*-----------------------------------------------------*\
| Send packet using feature reports, as headset stand |
| seems to not update completely using HID writes |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 65);
}
@@ -1,45 +0,0 @@
/*---------------------------------------------------------*\
| CorsairWirelessController.h |
| |
| Driver for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
class CorsairWirelessController
{
public:
CorsairWirelessController(hid_device* dev_handle, const char* path);
~CorsairWirelessController();
device_type GetDeviceType();
std::string GetDeviceLocation();
std::string GetFirmwareString();
std::string GetName();
std::string GetSerialString();
void SetLEDs(std::vector<RGBColor> colors);
void SetName(std::string device_name);
private:
hid_device* dev;
std::string firmware_version;
std::string location;
std::string name;
device_type type;
void EnterDirectMode();
void ExitDirectMode();
void StartDirectMode();
void SendDirectFrame(bool first_frame, unsigned char* data);
};
@@ -1,62 +0,0 @@
/*---------------------------------------------------------*\
| CorsairWirelessControllerDetect.cpp |
| |
| Detector for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "Detector.h"
#include "CorsairWirelessController.h"
#include "RGBController.h"
#include "RGBController_CorsairWireless.h"
/*-----------------------------------------------------*\
| Corsair vendor ID |
\*-----------------------------------------------------*/
#define CORSAIR_VID 0x1B1C
/*-----------------------------------------------------*\
| Keyboard product IDs |
\*-----------------------------------------------------*/
#define CORSAIR_K57_RGB_WIRED_PID 0x1B6E
#define CORSAIR_K57_RGB_WIRELESS_PID 0x1B62
/******************************************************************************************\
* *
* DetectCorsairWirelessControllers *
* *
* Tests the USB address to see if a Corsair RGB Keyboard controller exists there. *
* *
\******************************************************************************************/
void DetectCorsairWirelessControllers(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
CorsairWirelessController* controller = new CorsairWirelessController(dev, info->path);
controller->SetName(name);
if(controller->GetDeviceType() != DEVICE_TYPE_UNKNOWN)
{
RGBController_CorsairWireless* rgb_controller = new RGBController_CorsairWireless(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else
{
delete controller;
}
}
} /* DetectCorsairWirelessControllers() */
/*-----------------------------------------------------------------------------------------------------*\
| Keyboards |
\*-----------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Corsair K57 RGB (Wired)", DetectCorsairWirelessControllers, CORSAIR_VID, CORSAIR_K57_RGB_WIRED_PID, 1, 0xFF42, 1);
//REGISTER_HID_DETECTOR_IPU("Corsair K57 RGB (Wireless)", DetectCorsairWirelessControllers, CORSAIR_VID, CORSAIR_K57_RGB_WIRELESS_PID, 1, 0xFF42, 1);
@@ -1,342 +0,0 @@
/*---------------------------------------------------------*\
| RGBController_CorsairWireless.cpp |
| |
| RGBController for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBControllerKeyNames.h"
#include "RGBController_CorsairWireless.h"
using namespace std::chrono_literals;
//0xFFFFFFFF indicates an unused entry in matrix
#define NA 0xFFFFFFFF
static unsigned int matrix_map[7][24] =
{ { NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 0, 1, NA, NA, NA },
{ 131, 41, NA, 58, 59, 60, 61, NA, 62, 63, 64, 65, NA, 66, 67, 68, 69, 70, 71, 72, NA, NA, NA, NA },
{ 132, 53, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, NA, 45, 46, 42, NA, 73, 74, 75, 83, 84, 85, 86 },
{ 133, 43, NA, 20, 26, 8, 21, NA, 23, 28, 24, 12, 18, 19, 47, 48, 49, 76, 77, 78, 95, 96, 97, 87 },
{ 134, 57, NA, 4, 22, 7, 9, NA, 10, 11, 13, 14, 15, 51, 52, NA, 40, NA, NA, NA, 92, 93, 94, NA },
{ 135, 106, NA, 29, 27, 6, 25, NA, 5, NA, 17, 16, 54, 55, 56, 110, NA, NA, 82, NA, 89, 90, 91, 88 },
{ 136, 105, 108, 107, NA, NA, NA, NA, 44, NA, NA, NA, NA, 111, 122, 101, 109, 80, 81, 79, 98, NA, 99, NA } };
static const char* zone_names[] =
{
ZONE_EN_KEYBOARD,
};
static const unsigned int zone_sizes[] =
{
137
};
static const zone_type zone_types[] =
{
ZONE_TYPE_MATRIX,
};
static const char* led_names[] =
{
"Power/Wireless Indicator",
"Lock/Macro Indicator",
"N/A",
"N/A",
KEY_EN_A,
KEY_EN_B,
KEY_EN_C,
KEY_EN_D,
KEY_EN_E,
KEY_EN_F,
KEY_EN_G,
KEY_EN_H,
KEY_EN_I,
KEY_EN_J,
KEY_EN_K,
KEY_EN_L,
KEY_EN_M,
KEY_EN_N,
KEY_EN_O,
KEY_EN_P,
KEY_EN_Q,
KEY_EN_R,
KEY_EN_S,
KEY_EN_T,
KEY_EN_U,
KEY_EN_V,
KEY_EN_W,
KEY_EN_X,
KEY_EN_Y,
KEY_EN_Z,
KEY_EN_1,
KEY_EN_2,
KEY_EN_3,
KEY_EN_4,
KEY_EN_5,
KEY_EN_6,
KEY_EN_7,
KEY_EN_8,
KEY_EN_9,
KEY_EN_0,
KEY_EN_ANSI_ENTER,
KEY_EN_ESCAPE,
KEY_EN_BACKSPACE,
KEY_EN_TAB,
KEY_EN_SPACE,
KEY_EN_MINUS,
KEY_EN_EQUALS,
KEY_EN_LEFT_BRACKET,
KEY_EN_RIGHT_BRACKET,
KEY_EN_ANSI_BACK_SLASH,
KEY_EN_UNUSED,
KEY_EN_SEMICOLON,
KEY_EN_QUOTE,
KEY_EN_BACK_TICK,
KEY_EN_COMMA,
KEY_EN_PERIOD,
KEY_EN_FORWARD_SLASH,
KEY_EN_CAPS_LOCK,
KEY_EN_F1,
KEY_EN_F2,
KEY_EN_F3,
KEY_EN_F4,
KEY_EN_F5,
KEY_EN_F6,
KEY_EN_F7,
KEY_EN_F8,
KEY_EN_F9,
KEY_EN_F10,
KEY_EN_F11,
KEY_EN_F12,
KEY_EN_PRINT_SCREEN,
KEY_EN_SCROLL_LOCK,
KEY_EN_PAUSE_BREAK,
KEY_EN_INSERT,
KEY_EN_HOME,
KEY_EN_PAGE_UP,
KEY_EN_DELETE,
KEY_EN_END,
KEY_EN_PAGE_DOWN,
KEY_EN_RIGHT_ARROW,
KEY_EN_LEFT_ARROW,
KEY_EN_DOWN_ARROW,
KEY_EN_UP_ARROW,
KEY_EN_NUMPAD_LOCK,
KEY_EN_NUMPAD_DIVIDE,
KEY_EN_NUMPAD_TIMES,
KEY_EN_NUMPAD_MINUS,
KEY_EN_NUMPAD_PLUS,
KEY_EN_NUMPAD_ENTER,
KEY_EN_NUMPAD_1,
KEY_EN_NUMPAD_2,
KEY_EN_NUMPAD_3,
KEY_EN_NUMPAD_4,
KEY_EN_NUMPAD_5,
KEY_EN_NUMPAD_6,
KEY_EN_NUMPAD_7,
KEY_EN_NUMPAD_8,
KEY_EN_NUMPAD_9,
KEY_EN_NUMPAD_0,
KEY_EN_NUMPAD_PERIOD,
KEY_EN_UNUSED,
KEY_EN_MENU,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_LEFT_CONTROL,
KEY_EN_LEFT_SHIFT,
KEY_EN_LEFT_ALT,
KEY_EN_LEFT_WINDOWS,
KEY_EN_RIGHT_CONTROL,
KEY_EN_RIGHT_SHIFT,
KEY_EN_RIGHT_ALT,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_RIGHT_FUNCTION,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
KEY_EN_UNUSED,
"Key: G1",
"Key: G2",
"Key: G3",
"Key: G4",
"Key: G5",
"Key: G6",
};
/**------------------------------------------------------------------*\
@name Corsair Wireless Peripheral
@category Keyboard
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectCorsairWirelessControllers
@comment
\*-------------------------------------------------------------------*/
RGBController_CorsairWireless::RGBController_CorsairWireless(CorsairWirelessController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetName();
vendor = "Corsair";
description = "Corsair RGB Peripheral Device";
type = controller->GetDeviceType();
version = controller->GetFirmwareString();
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = 0;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
/*-----------------------------------------------------*\
| The Corsair Lighting Node Pro requires a packet within|
| 20 seconds of sending the lighting change in order |
| to not revert back into rainbow mode. Start a thread |
| to continuously send a keepalive packet every 5s |
\*-----------------------------------------------------*/
keepalive_thread_run = true;
keepalive_thread = new std::thread(&RGBController_CorsairWireless::KeepaliveThread, this);
}
RGBController_CorsairWireless::~RGBController_CorsairWireless()
{
/*-----------------------------------------------------*\
| Close keepalive thread |
\*-----------------------------------------------------*/
keepalive_thread_run = false;
keepalive_thread->join();
delete keepalive_thread;
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
delete zones[zone_index].matrix_map;
}
}
delete controller;
}
void RGBController_CorsairWireless::SetupZones()
{
/*---------------------------------------------------------*\
| Determine number of zones |
| For now, keyboard has 2 zones and mousemat has 1 |
\*---------------------------------------------------------*/
unsigned int num_zones = 1;
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
unsigned int total_led_count = 0;
for(unsigned int zone_idx = 0; zone_idx < num_zones; zone_idx++)
{
zone new_zone;
new_zone.name = zone_names[zone_idx];
new_zone.type = zone_types[zone_idx];
new_zone.leds_min = zone_sizes[zone_idx];
new_zone.leds_max = zone_sizes[zone_idx];
new_zone.leds_count = zone_sizes[zone_idx];
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map = new matrix_map_type;
new_zone.matrix_map->height = 7;
new_zone.matrix_map->width = 24;
new_zone.matrix_map->map = (unsigned int *)&matrix_map;
}
else
{
new_zone.matrix_map = NULL;
}
zones.push_back(new_zone);
total_led_count += new_zone.leds_count;
}
for(unsigned int led_idx = 0; led_idx < total_led_count; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx];
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_CorsairWireless::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_CorsairWireless::DeviceUpdateLEDs()
{
last_update_time = std::chrono::steady_clock::now();
controller->SetLEDs(colors);
}
void RGBController_CorsairWireless::UpdateZoneLEDs(int /*zone*/)
{
controller->SetLEDs(colors);
}
void RGBController_CorsairWireless::UpdateSingleLED(int /*led*/)
{
controller->SetLEDs(colors);
}
void RGBController_CorsairWireless::DeviceUpdateMode()
{
}
void RGBController_CorsairWireless::KeepaliveThread()
{
while(keepalive_thread_run.load())
{
if(active_mode == 0)
{
if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(5000))
{
DeviceUpdateLEDs();
}
}
std::this_thread::sleep_for(1000ms);
}
}
@@ -134,7 +134,14 @@ void CrucialController::CrucialRegisterWriteBlock(crucial_register reg, unsigned
bus->i2c_smbus_write_word_data(dev, 0x00, ((reg << 8) & 0xFF00) | ((reg >> 8) & 0x00FF));
//Write Crucial block data
bus->i2c_smbus_write_block_data(dev, 0x03, sz, data);
if(bus->i2c_smbus_write_block_data(dev, 0x03, sz, data) == -1)
{
//Fall back to individual byte operations if the block operation fails
for(unsigned int block_byte = 0; block_byte < sz; block_byte++)
{
bus->i2c_smbus_write_byte_data(dev, 0x01, data[block_byte]);
}
}
}
void CrucialController::SendEffectColor
@@ -27,12 +27,6 @@ using namespace std::chrono_literals;
static const unsigned char crucial_addresses[] =
{
/*-----------------------------------------------------*\
| These addresses have been disabled due to conflict |
| with ASUS Aura DRAM. Since the detection scheme is |
| the same, Aura RAM will be detected as Crucial. |
| We need to improve the Crucial detection scheme. |
\*-----------------------------------------------------*/
0x39,
0x3A,
0x3B,
@@ -81,7 +75,7 @@ unsigned char CrucialRegisterRead(i2c_smbus_interface* bus, crucial_dev_id dev,
* TestForCrucialController *
* *
* Tests the given address to see if an Crucial controller exists there. First does a*
* quick write to test for a response, and if so does a simple read at 0xA0 to test *
* byte read to test for a response, and if so does a simple read at 0xA0 to test *
* for incrementing values 0...F which was observed at this location during data dump *
* *
\******************************************************************************************/
@@ -90,19 +84,19 @@ bool TestForCrucialController(i2c_smbus_interface* bus, unsigned char address)
{
bool pass = false;
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
int res = bus->i2c_smbus_read_byte(address);
if (res >= 0)
if(res >= 0)
{
pass = true;
LOG_DEBUG("[%s] Detected an I2C device at address %02X", CRUCIAL_CONTROLLER_NAME, address);
for (int i = 0xA0; i < 0xB0; i++)
for(int i = 0xA0; i < 0xB0; i++)
{
res = bus->i2c_smbus_read_byte_data(address, i);
if (res != (i - 0xA0))
if(res != (i - 0xA0))
{
LOG_VERBOSE("[%s] Detection failed testing register %02X. Expected %02X, got %02X.", CRUCIAL_CONTROLLER_NAME, i, (i - 0xA0), res);
@@ -178,7 +172,7 @@ void DetectCrucialControllers(std::vector<i2c_smbus_interface*> &busses)
{
for(unsigned int slot = 0; slot < 4; slot++)
{
int res = busses[bus]->i2c_smbus_write_quick(0x27, I2C_SMBUS_WRITE);
int res = busses[bus]->i2c_smbus_read_byte(0x27);
if(res < 0)
{
@@ -192,7 +186,7 @@ void DetectCrucialControllers(std::vector<i2c_smbus_interface*> &busses)
if(address_list_idx < CRUCIAL_ADDRESS_COUNT)
{
res = busses[bus]->i2c_smbus_write_quick(crucial_addresses[address_list_idx], I2C_SMBUS_WRITE);
res = busses[bus]->i2c_smbus_read_byte(crucial_addresses[address_list_idx]);
}
else
{
@@ -266,6 +266,21 @@ ENESMBusController::ENESMBusController(ENESMBusInterface* interface, ene_dev_id
led_count = config_table[ENE_CONFIG_LED_COUNT_0107];
}
/*-----------------------------------------------------*\
| AUMA0-E6K5-1114 |
| Found on ASUS ROG MATRIX PLATINUM 5090 |
\*-----------------------------------------------------*/
else if(strcmp(device_version, "AUMA0-E6K5-1114") == 0)
{
direct_reg = ENE_REG_COLORS_DIRECT_V2;
effect_reg = ENE_REG_COLORS_EFFECT_V2;
channel_cfg = ENE_CONFIG_CHANNEL_V2;
/*-------------------------------------------------*\
| Read LED count from configuration table |
\*-------------------------------------------------*/
led_count = config_table[ENE_CONFIG_LED_COUNT_0107];
}
/*-----------------------------------------------------*\
| ROG ARION - ASUS ROG Arion external SSD enclosure |
| This device does not support ENE read, so we fake the |
| device name string if the interface is ROG Arion type.|
@@ -30,10 +30,11 @@ using namespace std::chrono_literals;
#undef interface
#endif
/*----------------------------------------------------------------------*\
| This list contains the available SMBus addresses for mapping ENE RAM |
\*----------------------------------------------------------------------*/
#define ENE_RAM_ADDRESS_COUNT 23
/*---------------------------------------------------------*\
| This list contains the available SMBus addresses for |
| mapping ENE RAM |
\*---------------------------------------------------------*/
#define ENE_RAM_ADDRESS_COUNT (sizeof(ene_ram_addresses) / sizeof(ene_ram_addresses[0]))
static const unsigned char ene_ram_addresses[] =
{
@@ -44,14 +45,7 @@ static const unsigned char ene_ram_addresses[] =
0x74,
0x75,
0x76,
0x78,
0x79,
0x7A,
0x7B,
0x7C,
0x7D,
0x7E,
0x7F,
0x77,
0x4F,
0x66,
0x67,
@@ -62,10 +56,11 @@ static const unsigned char ene_ram_addresses[] =
0x3D
};
/*---------------------------------------------------------------------------------*\
| This list contains the available SMBus addresses for mapping Aura motherboards |
\*---------------------------------------------------------------------------------*/
#define AURA_MOBO_ADDRESS_COUNT 3
/*---------------------------------------------------------*\
| This list contains the available SMBus addresses for |
| mapping Aura motherboards |
\*---------------------------------------------------------*/
#define AURA_MOBO_ADDRESS_COUNT (sizeof(aura_mobo_addresses) / sizeof(aura_mobo_addresses[0]))
static const unsigned char aura_mobo_addresses[] =
{
@@ -117,7 +112,7 @@ static void ENERegisterWrite(i2c_smbus_interface* bus, ene_dev_id dev, ene_regis
* TestForENESMBusController *
* *
* Tests the given address to see if an ENE controller exists there. First does a *
* quick write to test for a response, and if so does a simple read at 0xA0 to test *
* byte read to test for a response, and if so does a simple read at 0xA0 to test *
* for incrementing values 0...F which was observed at this location during data dump *
* *
* Also tests for the string "Micron" in the ENE register space. Crucial (Micron) *
@@ -207,7 +202,7 @@ void DetectENESMBusDRAMControllers(std::vector<i2c_smbus_interface*> &busses)
for (unsigned int slot = 0; slot < 8; slot++)
{
int res = busses[bus]->i2c_smbus_write_quick(0x77, I2C_SMBUS_WRITE);
int res = busses[bus]->i2c_smbus_read_byte(0x77);
if(res < 0)
{
@@ -220,11 +215,11 @@ void DetectENESMBusDRAMControllers(std::vector<i2c_smbus_interface*> &busses)
{
address_list_idx++;
if(address_list_idx < ENE_RAM_ADDRESS_COUNT)
if(address_list_idx < (int)ENE_RAM_ADDRESS_COUNT)
{
LOG_DEBUG("[ENE SMBus DRAM] Testing address %02X to see if there is a device there", ene_ram_addresses[address_list_idx]);
res = busses[bus]->i2c_smbus_write_quick(ene_ram_addresses[address_list_idx], I2C_SMBUS_WRITE);
res = busses[bus]->i2c_smbus_read_byte(ene_ram_addresses[address_list_idx]);
}
else
{
@@ -232,7 +227,7 @@ void DetectENESMBusDRAMControllers(std::vector<i2c_smbus_interface*> &busses)
}
} while (res >= 0);
if(address_list_idx < ENE_RAM_ADDRESS_COUNT)
if(address_list_idx < (int)ENE_RAM_ADDRESS_COUNT)
{
LOG_DEBUG("[ENE SMBus DRAM] Remapping slot %d to address %02X", slot, ene_ram_addresses[address_list_idx]);
@@ -355,6 +350,7 @@ REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 3060 Ti Gaming OC",
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 3060 Ti OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060TI_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_3060TI_O8G_OC, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 3060 Ti OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060TI_LHR_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_3060TI_O8G_OC_V2, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 3060 Ti V2 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060TI_LHR_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_3060TI_O8G_V2_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 3060 Ti V2 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX3060TI_LHR_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_3060TI_O8G_V2_2_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 3070 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX3070_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_3070_OC, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 3070 Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX3070_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_3070_O8G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 3070 White OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX3070_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_3070_O8G_WHITE, 0x67);
@@ -462,8 +458,11 @@ REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 4090 Gaming White",
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, 0x67);
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 5070 Gaming", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070_O12G_GAMING, 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 5070 Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070_O12G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5070 Ti Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070TI_O16G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5070 Ti Gaming BTF White OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070TI_O16G_GAMING_BTF_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5070 Ti Gaming White OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5070TI_O16G_GAMING_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG STRIX GeForce RTX 5070 Ti Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, ASUS_SUB_VEN, ASUS_ROG_STRIX_RTX_5070TI_O16G_GAMING_OC, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5080 Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5080_O16G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5090 Gaming OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_TUF_RTX_5090_O32G_GAMING, 0x67);
@@ -471,7 +470,9 @@ REGISTER_I2C_PCI_DETECTOR("ASUS TUF GeForce RTX 5090 Gaming",
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5080 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5080_O16G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5080 OC WHITE", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5080_O16G_GAMING_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5080", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5080_16G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5080 WHITE", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5080_16G_GAMING_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_2, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC BTF", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_BTF, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL GeForce RTX 5090 OC WHITE", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_RTX_5090_O32G_GAMING_WHITE, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS ROG ASTRAL LC GeForce RTX 5090 OC", DetectENESMBusGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, ASUS_SUB_VEN, ASUS_ROG_ASTRAL_LC_RTX_5090_O32G_GAMING, 0x67);
@@ -502,6 +503,7 @@ REGISTER_I2C_PCI_DETECTOR("ASUS TUF Radeon RX 7700 XT Gaming OC",
REGISTER_I2C_PCI_DETECTOR("ASUS TUF Radeon RX 7800 XT Gaming OC", DetectENESMBusGPUControllers, AMD_GPU_VEN, AMD_NAVI32_DEV, ASUS_SUB_VEN, ASUS_TUF_RX_7800XT_O16G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF Radeon RX 7800 XT Gaming OC", DetectENESMBusGPUControllers, AMD_GPU_VEN, AMD_NAVI32_DEV, ASUS_SUB_VEN, ASUS_TUF_RX_7800XT_O16G_GAMING_0606, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF Radeon RX 7800 XT Gaming White OC", DetectENESMBusGPUControllers, AMD_GPU_VEN, AMD_NAVI32_DEV, ASUS_SUB_VEN, ASUS_TUF_RX_7800XT_O16G_WHITE_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF Radeon RX 7900 GRE Gaming OC", DetectENESMBusGPUControllers, AMD_GPU_VEN, AMD_NAVI31_DEV, ASUS_SUB_VEN, ASUS_TUF_RX_7900GRE_O16G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF Radeon RX 7900 XT Gaming OC", DetectENESMBusGPUControllers, AMD_GPU_VEN, AMD_NAVI31_DEV, ASUS_SUB_VEN, ASUS_TUF_RX_7900XT_020G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF Radeon RX 7900 XTX Gaming OC", DetectENESMBusGPUControllers, AMD_GPU_VEN, AMD_NAVI31_DEV, ASUS_SUB_VEN, ASUS_TUF_RX_7900XTX_O24G_GAMING, 0x67);
REGISTER_I2C_PCI_DETECTOR("ASUS TUF Radeon RX 9070 Gaming OC", DetectENESMBusGPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, ASUS_SUB_VEN, ASUS_TUF_RX_9070_016G_GAMING, 0x67);
@@ -61,5 +61,12 @@ void ENESMBusInterface_i2c_smbus::ENERegisterWriteBlock(ene_dev_id dev, ene_regi
bus->i2c_smbus_write_word_data(dev, 0x00, ((reg << 8) & 0xFF00) | ((reg >> 8) & 0x00FF));
//Write ENE block data
bus->i2c_smbus_write_block_data(dev, 0x03, sz, data);
if(bus->i2c_smbus_write_block_data(dev, 0x03, sz, data) == -1)
{
//Fall back to individual byte operations if the block operation fails
for(unsigned int block_byte = 0; block_byte < sz; block_byte++)
{
bus->i2c_smbus_write_byte_data(dev, 0x01, data[block_byte]);
}
}
}
@@ -44,7 +44,7 @@ FnaticStreakController::~FnaticStreakController()
std::string FnaticStreakController::GetDeviceLocation()
{
return("HID " + location);
return("HID: " + location);
}
std::string FnaticStreakController::GetNameString()
@@ -230,16 +230,41 @@ void FnaticStreakController::SetLEDsDirect(std::vector<led> leds, std::vector<RG
void FnaticStreakController::SendRGBToDevice()
{
unsigned int total_leds = GetLEDCount();
unsigned int data_size = total_leds * 3;
if(keyboard_type == FNATIC_STREAK_TYPE_65)
{
unsigned char data[64];
memset(data, 0x00, sizeof(data));
/*-----------------------------------------------------*\
| For direct/software control, use command 0x0f |
| This bypasses the profile and allows immediate update |
| The 0x03 subcommand indicates per-key color data |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x0f, 0x03 };
SendRequest(prefix, sizeof(prefix), color_buf, data_size);
data[0] = 0x0F;
data[1] = 0x15;
data[7] = 0x0F;
data[8] = 0x03;
for(unsigned int i = 9; i < 25; i++)
{
data[i] = 0xFF;
}
data[25] = color_buf[0];
data[26] = color_buf[1];
data[27] = color_buf[2];
hid_write(dev, data, sizeof(data));
}
else
{
unsigned int total_leds = GetLEDCount();
unsigned int data_size = total_leds * 3;
/*-----------------------------------------------------*\
| For direct/software control, use command 0x0f |
| This bypasses the profile and allows immediate update |
| The 0x03 subcommand indicates per-key color data |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x0f, 0x03 };
SendRequest(prefix, sizeof(prefix), color_buf, data_size);
}
}
void FnaticStreakController::SetPulse(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed)
@@ -51,7 +51,8 @@
enum FnaticStreakType
{
FNATIC_STREAK_TYPE_FULL,
FNATIC_STREAK_TYPE_MINI
FNATIC_STREAK_TYPE_MINI,
FNATIC_STREAK_TYPE_65
};
class FnaticStreakController
@@ -26,6 +26,7 @@
#define FNATIC_STREAK_PID 0x0101
#define FNATIC_MINISTREAK_PID 0x0102
#define FNATIC_STREAK65_PID 0x0105
void DetectFnaticStreakKeyboard(hid_device_info* info, const std::string& name)
{
@@ -39,6 +40,10 @@ void DetectFnaticStreakKeyboard(hid_device_info* info, const std::string& name)
{
kb_type = FNATIC_STREAK_TYPE_MINI;
}
else if(info->product_id == FNATIC_STREAK65_PID)
{
kb_type = FNATIC_STREAK_TYPE_65;
}
else
{
kb_type = FNATIC_STREAK_TYPE_FULL;
@@ -53,3 +58,4 @@ void DetectFnaticStreakKeyboard(hid_device_info* info, const std::string& name)
REGISTER_HID_DETECTOR_I("Fnatic Streak", DetectFnaticStreakKeyboard, FNATIC_VID, FNATIC_STREAK_PID, 1);
REGISTER_HID_DETECTOR_I("Fnatic miniStreak", DetectFnaticStreakKeyboard, FNATIC_VID, FNATIC_MINISTREAK_PID, 1);
REGISTER_HID_DETECTOR_I("Fnatic Streak65", DetectFnaticStreakKeyboard, FNATIC_VID, FNATIC_STREAK65_PID, 1);
@@ -37,19 +37,22 @@ bool TestForGalaxGPUController(i2c_smbus_interface* bus, unsigned char address)
\*-----------------------------------------------------------------*/
case 0x32:
res = bus->i2c_smbus_read_byte_data(address, 0x00);
if(res == 0x27 || res == 0x26) {
if(res == 0x27 || res == 0x26)
{
res = bus->i2c_smbus_read_byte_data(address, 0x01);
if (res == 0x10 || res == 0x20)
if(res == 0x10 || res == 0x20)
{
pass = true;
}
}
break;
/*-----------------------------------------------------------------*\
| V1 Controller - RTX 3080 |
| V1 Controller |
\*-----------------------------------------------------------------*/
case 0x23:
res = bus->i2c_smbus_read_byte_data(address, 0x00);
if(res == 0x30)
if(res == 0x27 || res == 0x30)
{
pass = true;
}
@@ -60,8 +63,10 @@ bool TestForGalaxGPUController(i2c_smbus_interface* bus, unsigned char address)
\*-----------------------------------------------------------------*/
case 0x51:
res = bus->i2c_smbus_read_byte_data(address, 0x00);
if (res == 0x80)
if(res == 0x80)
{
pass = true;
}
break;
}
@@ -0,0 +1,238 @@
/*---------------------------------------------------------*\
| GigabyteCastor3Controller.cpp |
| |
| Driver for Gigabyte Aorus Waterforce X II 360 AIO |
| (Castor3 USB HID controller) |
| |
| RGB ring control only — LCD not implemented |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "GigabyteCastor3Controller.h"
#include "StringUtils.h"
GigabyteCastor3Controller::GigabyteCastor3Controller(hid_device* dev_handle, const char* path)
{
dev = dev_handle;
location = path;
/*---------------------------------------------------------*\
| Query firmware version via de 00 |
| Response: de [version_byte] |
| Observed: de 02 -> version 2 |
\*---------------------------------------------------------*/
unsigned char de_payload[] = { 0xDE, 0x00 };
SendPacket(de_payload, 2);
unsigned char response[CASTOR3_IN_SIZE];
int bytes_read = hid_read_timeout(dev, response, CASTOR3_IN_SIZE, CASTOR3_IN_TIMEOUT_MS);
if(bytes_read > 1)
{
/*-----------------------------------------------------*\
| Strip report ID 0x99 if present on IN packets |
\*-----------------------------------------------------*/
int offset = 0;
if(response[0] == CASTOR3_REPORT_ID)
{
offset = 1;
}
if(response[offset] == 0xDE && bytes_read > offset + 1)
{
firmware_version = std::to_string(response[offset + 1]);
}
}
}
GigabyteCastor3Controller::~GigabyteCastor3Controller()
{
hid_close(dev);
}
std::string GigabyteCastor3Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string GigabyteCastor3Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
std::string GigabyteCastor3Controller::GetFirmwareVersion()
{
return firmware_version;
}
void GigabyteCastor3Controller::SetEffect
(
unsigned char style,
unsigned char speed,
unsigned char brightness,
unsigned char b4,
unsigned char b5,
castor3_color_type color_type,
std::vector<RGBColor> colors
)
{
/*---------------------------------------------------------*\
| Wire protocol for LED effect apply |
| (from castor3.py led_set_effect, confirmed from |
| led_profile.pcapng + .cled.dat cross-reference) |
| |
| Step 1: c9 [Style] [Speed*20] [Bright] [b4] [b5] |
| Step 2: cd [R] [G] [B] (single-color only) |
| -OR- b0..b3 palette registers (palette effects) |
| Step 3: b6 (commit) |
\*---------------------------------------------------------*/
/*---------------------------------------------------------*\
| Step 1: c9 — effect select + parameters |
\*---------------------------------------------------------*/
unsigned char c9_payload[] =
{
0xC9,
style,
(unsigned char)(speed * 20),
brightness,
b4,
b5
};
SendPacket(c9_payload, sizeof(c9_payload));
/*---------------------------------------------------------*\
| Step 2a: cd — primary color for single-color effects |
| Effects: static, pulse, flash, dflash, gradient, off |
\*---------------------------------------------------------*/
if(color_type == CASTOR3_COLORS_SINGLE)
{
unsigned char r = 0xFF, g = 0x66, b = 0x00;
if(colors.size() > 0)
{
r = RGBGetRValue(colors[0]);
g = RGBGetGValue(colors[0]);
b = RGBGetBValue(colors[0]);
}
unsigned char cd_payload[] = { 0xCD, r, g, b };
SendPacket(cd_payload, sizeof(cd_payload));
}
/*---------------------------------------------------------*\
| Step 2b: b0..b3 — palette registers for multi-color |
| Each register carries 2 colors (6 bytes RGB + 1 pad): |
| bN [Style] [R1 G1 B1] [R2 G2 B2] 0x00 |
| b0=colors 1+2, b1=colors 3+4, b2=colors 5+6, b3=7+8 |
| |
| Effects: colorshift(8), tricolor(3), spin(3), switch(2) |
\*---------------------------------------------------------*/
else if(color_type == CASTOR3_COLORS_PALETTE)
{
/*-----------------------------------------------------*\
| Build palette: pad to 8 colors by repeating last |
\*-----------------------------------------------------*/
RGBColor palette[8];
for(int i = 0; i < 8; i++)
{
if(i < (int)colors.size())
{
palette[i] = colors[i];
}
else if(colors.size() > 0)
{
palette[i] = colors[colors.size() - 1];
}
else
{
palette[i] = ToRGBColor(0xFF, 0x00, 0x00);
}
}
unsigned char regs[] = { 0xB0, 0xB1, 0xB2, 0xB3 };
for(int i = 0; i < 4; i++)
{
RGBColor c1 = palette[i * 2];
RGBColor c2 = palette[i * 2 + 1];
unsigned char bN_payload[] =
{
regs[i],
style,
(unsigned char)RGBGetRValue(c1), (unsigned char)RGBGetGValue(c1), (unsigned char)RGBGetBValue(c1),
(unsigned char)RGBGetRValue(c2), (unsigned char)RGBGetGValue(c2), (unsigned char)RGBGetBValue(c2),
0x00
};
SendPacket(bN_payload, sizeof(bN_payload));
}
}
/*---------------------------------------------------------*\
| Step 3: b6 — commit/apply |
\*---------------------------------------------------------*/
unsigned char b6_payload[] = { 0xB6 };
SendPacket(b6_payload, sizeof(b6_payload));
}
void GigabyteCastor3Controller::SetOff()
{
/*---------------------------------------------------------*\
| Off = Style 0x01 (Static) with Speed=0 |
| From castor3.py: off uses style 0x01, speed_wire=0 |
\*---------------------------------------------------------*/
std::vector<RGBColor> black;
black.push_back(ToRGBColor(0x00, 0x00, 0x00));
unsigned char c9_payload[] =
{
0xC9,
CASTOR3_STYLE_STATIC,
0x00, /* speed_wire = 0 for off */
CASTOR3_BRIGHTNESS_DEFAULT,
0x02, /* b4 */
0x01 /* b5 */
};
SendPacket(c9_payload, sizeof(c9_payload));
unsigned char cd_payload[] = { 0xCD, 0x00, 0x00, 0x00 };
SendPacket(cd_payload, sizeof(cd_payload));
unsigned char b6_payload[] = { 0xB6 };
SendPacket(b6_payload, sizeof(b6_payload));
}
void GigabyteCastor3Controller::SendPacket(const unsigned char* payload, unsigned int payload_len)
{
/*---------------------------------------------------------*\
| Castor3 HID OUT transport: |
| - Byte 0: Report ID (0x99) |
| - Bytes 1..6143: payload, zero-padded |
| Total write size: 6144 bytes |
\*---------------------------------------------------------*/
unsigned char buf[CASTOR3_OUT_TOTAL];
memset(buf, 0x00, CASTOR3_OUT_TOTAL);
buf[0] = CASTOR3_REPORT_ID;
if(payload_len > CASTOR3_OUT_PAYLOAD)
{
payload_len = CASTOR3_OUT_PAYLOAD;
}
memcpy(&buf[1], payload, payload_len);
hid_write(dev, buf, CASTOR3_OUT_TOTAL);
}
@@ -0,0 +1,123 @@
/*---------------------------------------------------------*\
| GigabyteCastor3Controller.h |
| |
| Driver for Gigabyte Aorus Waterforce X II 360 AIO |
| (Castor3 USB HID controller) |
| |
| RGB ring control only — LCD not implemented |
| |
| Protocol reference: castor3.py reverse engineering |
| VID=0x0414 PID=0x7A5E Report ID=0x99 |
| OUT size: 6144 (report ID + 6143 payload) |
| IN size: 255 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <hidapi.h>
#include "RGBController.h"
/*---------------------------------------------------------*\
| Castor3 USB IDs |
\*---------------------------------------------------------*/
#define CASTOR3_VID 0x0414
#define CASTOR3_PID 0x7A5E
/*---------------------------------------------------------*\
| Castor3 HID transport |
\*---------------------------------------------------------*/
#define CASTOR3_REPORT_ID 0x99
#define CASTOR3_OUT_PAYLOAD 6143 /* payload after report ID */
#define CASTOR3_OUT_TOTAL 6144 /* report ID + payload */
#define CASTOR3_IN_SIZE 255
#define CASTOR3_IN_TIMEOUT_MS 2000
/*---------------------------------------------------------*\
| LED effect style IDs (c9 byte[1]) |
| From castor3.py LED_EFFECTS, cross-referenced with |
| .cled.dat profile files and led_profile.pcapng |
\*---------------------------------------------------------*/
#define CASTOR3_STYLE_STATIC 0x01
#define CASTOR3_STYLE_PULSE 0x02
#define CASTOR3_STYLE_CYCLE 0x03
#define CASTOR3_STYLE_FLASH 0x04
#define CASTOR3_STYLE_DFLASH 0x05
#define CASTOR3_STYLE_GRADIENT 0x06
#define CASTOR3_STYLE_COLORSHIFT 0x07
#define CASTOR3_STYLE_WAVE 0x08
#define CASTOR3_STYLE_RAINBOW 0x0A
#define CASTOR3_STYLE_TRICOLOR 0x0B
#define CASTOR3_STYLE_SPIN 0x0C
#define CASTOR3_STYLE_SWITCH 0x0D
/*---------------------------------------------------------*\
| LED speed range: 1-5 (wire value = speed * 20) |
| LED brightness range: 1-10 (direct on wire) |
\*---------------------------------------------------------*/
#define CASTOR3_SPEED_MIN 1
#define CASTOR3_SPEED_MAX 5
#define CASTOR3_SPEED_DEFAULT 5
#define CASTOR3_BRIGHTNESS_MIN 1
#define CASTOR3_BRIGHTNESS_MAX 10
#define CASTOR3_BRIGHTNESS_DEFAULT 10
/*---------------------------------------------------------*\
| Color type for effects |
\*---------------------------------------------------------*/
enum castor3_color_type
{
CASTOR3_COLORS_NONE = 0, /* cycle, wave, rainbow */
CASTOR3_COLORS_SINGLE = 1, /* static, pulse, flash... */
CASTOR3_COLORS_PALETTE = 2, /* colorshift, tricolor... */
};
class GigabyteCastor3Controller
{
public:
GigabyteCastor3Controller(hid_device* dev_handle, const char* path);
~GigabyteCastor3Controller();
std::string GetDeviceLocation();
std::string GetSerialString();
std::string GetFirmwareVersion();
/*---------------------------------------------------------*\
| LED effect application |
| |
| Wire sequence per effect apply: |
| c9 [Style] [Speed*20] [Bright] [b4] [b5] |
| cd [R] [G] [B] — single-color only |
| b0 [Style] [R1G1B1] [R2G2B2] 00 — palette colors 1+2 |
| b1 [Style] [R3G3B3] [R4G4B4] 00 — colors 3+4 |
| b2 [Style] [R5G5B5] [R6G6B6] 00 — colors 5+6 |
| b3 [Style] [R7G7B7] [R8G8B8] 00 — colors 7+8 |
| b6 — commit/apply |
\*---------------------------------------------------------*/
void SetEffect(unsigned char style,
unsigned char speed,
unsigned char brightness,
unsigned char b4,
unsigned char b5,
castor3_color_type color_type,
std::vector<RGBColor> colors);
void SetOff();
private:
hid_device* dev;
std::string location;
std::string firmware_version;
void SendPacket(const unsigned char* payload, unsigned int payload_len);
};
@@ -0,0 +1,39 @@
/*---------------------------------------------------------*\
| GigabyteCastor3ControllerDetect.cpp |
| |
| Detector for Gigabyte Aorus Waterforce X II 360 AIO |
| (Castor3 USB HID controller) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <hidapi.h>
#include "Detector.h"
#include "GigabyteCastor3Controller.h"
#include "RGBController_GigabyteCastor3.h"
#define GIGABYTE_CASTOR3_VID 0x0414
#define GIGABYTE_CASTOR3_PID 0x7A5E
void DetectGigabyteCastor3Controllers(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
GigabyteCastor3Controller* controller = new GigabyteCastor3Controller(dev, info->path);
RGBController_GigabyteCastor3* rgb_controller = new RGBController_GigabyteCastor3(controller);
rgb_controller->name = name;
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
REGISTER_HID_DETECTOR_IPU("Gigabyte Aorus Waterforce X II 360",
DetectGigabyteCastor3Controllers,
GIGABYTE_CASTOR3_VID,
GIGABYTE_CASTOR3_PID,
0, /* interface 0 */
0x0000, /* usage_page */
0x0002); /* usage */
@@ -0,0 +1,446 @@
/*---------------------------------------------------------*\
| RGBController_GigabyteCastor3.cpp |
| |
| RGBController for Gigabyte Aorus Waterforce X II 360 |
| AIO Cooler (Castor3 controller) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_GigabyteCastor3.h"
/**------------------------------------------------------------------*\
@name Gigabyte Aorus Waterforce X II 360
@category Cooler
@type USB
@save :o:
@direct :o:
@effects :white_check_mark:
@detectors DetectGigabyteCastor3Controllers
@comment Controls the LED ring on the pump head of the
Gigabyte Aorus Waterforce X II 360 AIO cooler.
Uses the Castor3 USB HID controller (VID 0x0414, PID 0x7A5E).
The LED ring and fans share the same controller — they are
not independently addressable and follow the same effect.
LCD display control is not implemented.
No direct/per-LED mode available — all modes are hardware
effects with a single color or palette.
\*-------------------------------------------------------------------*/
RGBController_GigabyteCastor3::RGBController_GigabyteCastor3(GigabyteCastor3Controller* controller_ptr)
{
controller = controller_ptr;
name = "Gigabyte Aorus Waterforce X II 360";
vendor = "Gigabyte";
type = DEVICE_TYPE_COOLER;
description = "Gigabyte Aorus Waterforce X II 360 AIO Cooler";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
version = controller->GetFirmwareVersion();
/*---------------------------------------------------------*\
| All modes from castor3.py LED_EFFECTS (12 effects + off) |
| |
| The Castor3 has no direct/per-LED mode. All effects are |
| hardware-driven, either single-color, palette, or |
| firmware-color (no user color). |
| |
| Speed: 1-5 mapped to MODE_FLAG_HAS_SPEED |
| Brightness: 1-10 mapped to MODE_FLAG_HAS_BRIGHTNESS |
| |
| mode.value stores the style byte for the c9 command. |
| mode.speed stores 1-5 (scaled to speed*20 on wire). |
| mode.brightness stores 1-10 (direct on wire). |
\*---------------------------------------------------------*/
/*---------------------------------------------------------*\
| Off |
| Wire: Style=0x01 (Static) with speed_wire=0 |
\*---------------------------------------------------------*/
mode Off;
Off.name = "Off";
Off.value = 0xFF; /* sentinel — handled specially */
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
/*---------------------------------------------------------*\
| Static — single color, no speed |
\*---------------------------------------------------------*/
mode Static;
Static.name = "Static";
Static.value = CASTOR3_STYLE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Static.colors_min = 1;
Static.colors_max = 1;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Static.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Static.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Static.colors.resize(1);
modes.push_back(Static);
/*---------------------------------------------------------*\
| Pulse — single color, speed + brightness |
\*---------------------------------------------------------*/
mode Pulse;
Pulse.name = "Pulse";
Pulse.value = CASTOR3_STYLE_PULSE;
Pulse.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Pulse.speed_min = CASTOR3_SPEED_MIN;
Pulse.speed_max = CASTOR3_SPEED_MAX;
Pulse.speed = CASTOR3_SPEED_DEFAULT;
Pulse.colors_min = 1;
Pulse.colors_max = 1;
Pulse.color_mode = MODE_COLORS_MODE_SPECIFIC;
Pulse.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Pulse.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Pulse.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Pulse.colors.resize(1);
modes.push_back(Pulse);
/*---------------------------------------------------------*\
| Flash — single color, speed + brightness |
\*---------------------------------------------------------*/
mode Flash;
Flash.name = "Flash";
Flash.value = CASTOR3_STYLE_FLASH;
Flash.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Flash.speed_min = CASTOR3_SPEED_MIN;
Flash.speed_max = CASTOR3_SPEED_MAX;
Flash.speed = CASTOR3_SPEED_DEFAULT;
Flash.colors_min = 1;
Flash.colors_max = 1;
Flash.color_mode = MODE_COLORS_MODE_SPECIFIC;
Flash.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Flash.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Flash.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Flash.colors.resize(1);
modes.push_back(Flash);
/*---------------------------------------------------------*\
| Double Flash — single color, speed + brightness |
\*---------------------------------------------------------*/
mode DFlash;
DFlash.name = "Double Flash";
DFlash.value = CASTOR3_STYLE_DFLASH;
DFlash.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
DFlash.speed_min = CASTOR3_SPEED_MIN;
DFlash.speed_max = CASTOR3_SPEED_MAX;
DFlash.speed = CASTOR3_SPEED_DEFAULT;
DFlash.colors_min = 1;
DFlash.colors_max = 1;
DFlash.color_mode = MODE_COLORS_MODE_SPECIFIC;
DFlash.brightness_min = CASTOR3_BRIGHTNESS_MIN;
DFlash.brightness_max = CASTOR3_BRIGHTNESS_MAX;
DFlash.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
DFlash.colors.resize(1);
modes.push_back(DFlash);
/*---------------------------------------------------------*\
| Cycle — firmware colors, speed only |
\*---------------------------------------------------------*/
mode Cycle;
Cycle.name = "Spectrum Cycle";
Cycle.value = CASTOR3_STYLE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Cycle.speed_min = CASTOR3_SPEED_MIN;
Cycle.speed_max = CASTOR3_SPEED_MAX;
Cycle.speed = CASTOR3_SPEED_DEFAULT;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Cycle.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Cycle.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
modes.push_back(Cycle);
/*---------------------------------------------------------*\
| Gradient — single color, speed + brightness |
| Note: b4 = brightness on wire (special case) |
\*---------------------------------------------------------*/
mode Gradient;
Gradient.name = "Gradient";
Gradient.value = CASTOR3_STYLE_GRADIENT;
Gradient.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Gradient.speed_min = CASTOR3_SPEED_MIN;
Gradient.speed_max = CASTOR3_SPEED_MAX;
Gradient.speed = CASTOR3_SPEED_DEFAULT;
Gradient.colors_min = 1;
Gradient.colors_max = 1;
Gradient.color_mode = MODE_COLORS_MODE_SPECIFIC;
Gradient.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Gradient.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Gradient.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Gradient.colors.resize(1);
modes.push_back(Gradient);
/*---------------------------------------------------------*\
| Color Shift — 8-color palette, speed + brightness |
\*---------------------------------------------------------*/
mode ColorShift;
ColorShift.name = "Color Shift";
ColorShift.value = CASTOR3_STYLE_COLORSHIFT;
ColorShift.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
ColorShift.speed_min = CASTOR3_SPEED_MIN;
ColorShift.speed_max = CASTOR3_SPEED_MAX;
ColorShift.speed = CASTOR3_SPEED_DEFAULT;
ColorShift.colors_min = 1;
ColorShift.colors_max = 8;
ColorShift.color_mode = MODE_COLORS_MODE_SPECIFIC;
ColorShift.brightness_min = CASTOR3_BRIGHTNESS_MIN;
ColorShift.brightness_max = CASTOR3_BRIGHTNESS_MAX;
ColorShift.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
ColorShift.colors.resize(8);
ColorShift.colors[0] = ToRGBColor(0xFF, 0x00, 0x00);
ColorShift.colors[1] = ToRGBColor(0xFF, 0x72, 0x00);
ColorShift.colors[2] = ToRGBColor(0xFF, 0xFF, 0x00);
ColorShift.colors[3] = ToRGBColor(0x00, 0xFF, 0x00);
ColorShift.colors[4] = ToRGBColor(0x00, 0xFF, 0xFF);
ColorShift.colors[5] = ToRGBColor(0x00, 0x00, 0xFF);
ColorShift.colors[6] = ToRGBColor(0xFF, 0x00, 0xFF);
ColorShift.colors[7] = ToRGBColor(0xFF, 0x80, 0x80);
modes.push_back(ColorShift);
/*---------------------------------------------------------*\
| Wave — firmware colors, speed only |
\*---------------------------------------------------------*/
mode Wave;
Wave.name = "Wave";
Wave.value = CASTOR3_STYLE_WAVE;
Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Wave.speed_min = CASTOR3_SPEED_MIN;
Wave.speed_max = CASTOR3_SPEED_MAX;
Wave.speed = CASTOR3_SPEED_DEFAULT;
Wave.color_mode = MODE_COLORS_NONE;
Wave.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Wave.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Wave.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
modes.push_back(Wave);
/*---------------------------------------------------------*\
| Rainbow — firmware colors, speed only |
\*---------------------------------------------------------*/
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = CASTOR3_STYLE_RAINBOW;
Rainbow.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Rainbow.speed_min = CASTOR3_SPEED_MIN;
Rainbow.speed_max = CASTOR3_SPEED_MAX;
Rainbow.speed = CASTOR3_SPEED_DEFAULT;
Rainbow.color_mode = MODE_COLORS_NONE;
Rainbow.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Rainbow.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Rainbow.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
modes.push_back(Rainbow);
/*---------------------------------------------------------*\
| Tri-Color — 3-color palette, speed + brightness |
\*---------------------------------------------------------*/
mode TriColor;
TriColor.name = "Tri-Color";
TriColor.value = CASTOR3_STYLE_TRICOLOR;
TriColor.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
TriColor.speed_min = CASTOR3_SPEED_MIN;
TriColor.speed_max = CASTOR3_SPEED_MAX;
TriColor.speed = CASTOR3_SPEED_DEFAULT;
TriColor.colors_min = 1;
TriColor.colors_max = 3;
TriColor.color_mode = MODE_COLORS_MODE_SPECIFIC;
TriColor.brightness_min = CASTOR3_BRIGHTNESS_MIN;
TriColor.brightness_max = CASTOR3_BRIGHTNESS_MAX;
TriColor.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
TriColor.colors.resize(3);
TriColor.colors[0] = ToRGBColor(0x00, 0x00, 0xFF);
TriColor.colors[1] = ToRGBColor(0x7D, 0x00, 0xFF);
TriColor.colors[2] = ToRGBColor(0xFF, 0x00, 0xFF);
modes.push_back(TriColor);
/*---------------------------------------------------------*\
| Spin — 3-color palette, speed + brightness |
\*---------------------------------------------------------*/
mode Spin;
Spin.name = "Spin";
Spin.value = CASTOR3_STYLE_SPIN;
Spin.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Spin.speed_min = CASTOR3_SPEED_MIN;
Spin.speed_max = CASTOR3_SPEED_MAX;
Spin.speed = CASTOR3_SPEED_DEFAULT;
Spin.colors_min = 1;
Spin.colors_max = 3;
Spin.color_mode = MODE_COLORS_MODE_SPECIFIC;
Spin.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Spin.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Spin.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Spin.colors.resize(3);
Spin.colors[0] = ToRGBColor(0xFF, 0x00, 0xFE);
Spin.colors[1] = ToRGBColor(0x00, 0xFF, 0xFB);
Spin.colors[2] = ToRGBColor(0xFF, 0xFF, 0x00);
modes.push_back(Spin);
/*---------------------------------------------------------*\
| Switch — 2-color palette, speed + brightness |
\*---------------------------------------------------------*/
mode Switch;
Switch.name = "Switch";
Switch.value = CASTOR3_STYLE_SWITCH;
Switch.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Switch.speed_min = CASTOR3_SPEED_MIN;
Switch.speed_max = CASTOR3_SPEED_MAX;
Switch.speed = CASTOR3_SPEED_DEFAULT;
Switch.colors_min = 1;
Switch.colors_max = 2;
Switch.color_mode = MODE_COLORS_MODE_SPECIFIC;
Switch.brightness_min = CASTOR3_BRIGHTNESS_MIN;
Switch.brightness_max = CASTOR3_BRIGHTNESS_MAX;
Switch.brightness = CASTOR3_BRIGHTNESS_DEFAULT;
Switch.colors.resize(2);
Switch.colors[0] = ToRGBColor(0xFF, 0x00, 0xFE);
Switch.colors[1] = ToRGBColor(0x00, 0xFF, 0xFB);
modes.push_back(Switch);
SetupZones();
}
RGBController_GigabyteCastor3::~RGBController_GigabyteCastor3()
{
delete controller;
}
void RGBController_GigabyteCastor3::SetupZones()
{
/*---------------------------------------------------------*\
| Single zone: LED ring + fans (not independently |
| addressable — fans follow the pump ring effect) |
| |
| This is a SINGLE zone because the protocol does not |
| expose per-LED addressing. All LEDs display the same |
| hardware effect simultaneously. |
\*---------------------------------------------------------*/
zone led_ring;
led_ring.name = "LED Ring";
led_ring.type = ZONE_TYPE_SINGLE;
led_ring.leds_min = 1;
led_ring.leds_max = 1;
led_ring.leds_count = 1;
led_ring.matrix_map = NULL;
zones.push_back(led_ring);
led new_led;
new_led.name = "LED Ring";
leds.push_back(new_led);
SetupColors();
}
void RGBController_GigabyteCastor3::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| Fixed zone size — not resizable |
\*---------------------------------------------------------*/
}
void RGBController_GigabyteCastor3::DeviceUpdateLEDs()
{
DeviceUpdateMode();
}
void RGBController_GigabyteCastor3::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_GigabyteCastor3::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_GigabyteCastor3::DeviceUpdateMode()
{
unsigned char style = (unsigned char)modes[active_mode].value;
/*---------------------------------------------------------*\
| Handle Off mode (sentinel value 0xFF) |
\*---------------------------------------------------------*/
if(style == 0xFF)
{
controller->SetOff();
return;
}
/*---------------------------------------------------------*\
| Determine speed, brightness, b4, b5, and color type |
| from the effect lookup table matching castor3.py |
\*---------------------------------------------------------*/
unsigned char speed = CASTOR3_SPEED_DEFAULT;
unsigned char brightness = CASTOR3_BRIGHTNESS_DEFAULT;
if(modes[active_mode].flags & MODE_FLAG_HAS_SPEED)
{
speed = (unsigned char)modes[active_mode].speed;
}
if(modes[active_mode].flags & MODE_FLAG_HAS_BRIGHTNESS)
{
brightness = (unsigned char)modes[active_mode].brightness;
}
/*---------------------------------------------------------*\
| Look up b4, b5, and color_type per effect |
| These match the LED_EFFECTS dict in castor3.py |
\*---------------------------------------------------------*/
unsigned char b4 = 0x02;
unsigned char b5 = 0x01;
castor3_color_type color_type = CASTOR3_COLORS_NONE;
switch(style)
{
case CASTOR3_STYLE_STATIC:
case CASTOR3_STYLE_PULSE:
case CASTOR3_STYLE_FLASH:
case CASTOR3_STYLE_DFLASH:
b4 = 0x02;
b5 = 0x01;
color_type = CASTOR3_COLORS_SINGLE;
break;
case CASTOR3_STYLE_GRADIENT:
/*-------------------------------------------------*\
| Gradient: b4 = brightness on wire (special case) |
\*-------------------------------------------------*/
b4 = brightness;
b5 = 0x01;
color_type = CASTOR3_COLORS_SINGLE;
break;
case CASTOR3_STYLE_CYCLE:
case CASTOR3_STYLE_WAVE:
case CASTOR3_STYLE_RAINBOW:
b4 = 0x02;
b5 = 0x01;
color_type = CASTOR3_COLORS_NONE;
break;
case CASTOR3_STYLE_COLORSHIFT:
b4 = 0x08; /* ClrCount=8 */
b5 = 0x02; /* CmbIndex+1=2 */
color_type = CASTOR3_COLORS_PALETTE;
break;
case CASTOR3_STYLE_TRICOLOR:
case CASTOR3_STYLE_SPIN:
b4 = 0x02;
b5 = 0x01;
color_type = CASTOR3_COLORS_PALETTE;
break;
case CASTOR3_STYLE_SWITCH:
b4 = 0x02;
b5 = 0x01;
color_type = CASTOR3_COLORS_PALETTE;
break;
}
controller->SetEffect(style, speed, brightness, b4, b5,
color_type, modes[active_mode].colors);
}
@@ -0,0 +1,33 @@
/*---------------------------------------------------------*\
| RGBController_GigabyteCastor3.h |
| |
| RGBController for Gigabyte Aorus Waterforce X II 360 |
| AIO Cooler (Castor3 controller) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "GigabyteCastor3Controller.h"
class RGBController_GigabyteCastor3 : public RGBController
{
public:
RGBController_GigabyteCastor3(GigabyteCastor3Controller* controller_ptr);
~RGBController_GigabyteCastor3();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
GigabyteCastor3Controller* controller;
};
@@ -68,9 +68,14 @@ void RGBFusion2BlackwellGPUController::SetMode(uint8_t type, uint8_t zone, uint8
if(zone_config.numberOfColors > 0)
{
int currentPos = 12;
if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT)
switch(gpu_layout)
{
currentPos = 11;
case RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT:
case RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT:
currentPos = 11;
break;
default:
break;
}
for(uint8_t i = 0; i < zone_config.numberOfColors; i++)
@@ -90,6 +95,16 @@ void RGBFusion2BlackwellGPUController::SetZone(uint8_t zone, uint8_t mode, fusio
if(mode == RGB_FUSION2_BLACKWELL_GPU_MODE_BREATHING)
zone_config.brightness = RGB_FUSION2_BLACKWELL_GPU_BRIGHTNESS_MAX;
switch(gpu_layout)
{
case RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT:
if(mode == RGB_FUSION2_BLACKWELL_GPU_MODE_DIRECT)
mode = RGB_FUSION2_BLACKWELL_GPU_MODE_STATIC;
break;
default:
break;
}
uint8_t type = RGB_FUSION2_BLACKWELL_GPU_REG_COLOR;
if(mode != RGB_FUSION2_BLACKWELL_GPU_MODE_DIRECT)
type = RGB_FUSION2_BLACKWELL_GPU_REG_MODE;
@@ -62,11 +62,12 @@ enum
enum
{
RGB_FUSION2_BLACKWELL_GPU_SINGLE_ZONE = 0,
RGB_FUSION2_BLACKWELL_GPU_GAMING_LAYOUT = 1,
RGB_FUSION2_BLACKWELL_GPU_WATERFORCE_LAYOUT = 2,
RGB_FUSION2_BLACKWELL_GPU_AORUS_WATERFORCE_LAYOUT = 3,
RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT = 4,
RGB_FUSION2_BLACKWELL_GPU_SINGLE_ZONE = 0,
RGB_FUSION2_BLACKWELL_GPU_GAMING_LAYOUT = 1,
RGB_FUSION2_BLACKWELL_GPU_WATERFORCE_LAYOUT = 2,
RGB_FUSION2_BLACKWELL_GPU_AORUS_WATERFORCE_LAYOUT = 3,
RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT = 4,
RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT = 5,
};
class RGBFusion2BlackwellGPUController
@@ -1,4 +1,4 @@
/*---------------------------------------------------------*\
/*---------------------------------------------------------*\
| GigabyteRGBFusion2BlackwellGPUControllerDetect.cpp |
| |
| Detector for Gigabyte RGB Fusion 2 Blackwell GPU |
@@ -181,29 +181,48 @@ void DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers(i2c_sm
DetectGigabyteRGBFusion2BlackwellGPUControllers(bus, i2c_addr, name, RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT);
} /* DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers() */
/*******************************************************************************************\
* *
* DetectGigabyteRGBFusion2BlackwellAorusMaster5090DV2IceLayoutGPUControllers *
* *
* Detect GigabyteRGB Fusion2 controllers with AORUS master 5090 D V2 ICE layout on *
* enumerated I2C busses. *
* *
* bus - pointer to i2c_smbus_interface where RGB Fusion2 device is connected *
* dev - I2C address of RGB Fusion2 device *
* *
\*******************************************************************************************/
void DetectGigabyteRGBFusion2BlackwellAorusMaster5090DV2IceLayoutGPUControllers(i2c_smbus_interface* bus, uint8_t i2c_addr, const std::string& name)
{
DetectGigabyteRGBFusion2BlackwellGPUControllers(bus, i2c_addr, name, RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT);
} /* DetectGigabyteRGBFusion2BlackwellAorusMaster5090DV2IceLayoutGPUControllers() */
/*-----------------------------------------*\
| Nvidia GPUs |
\*-----------------------------------------*/
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5060 Ti Gaming OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5060TI_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_AERO_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Eagle OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_EAGLE_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_GAMING_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Eagle OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_EAGLE_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Eagle OC ICE", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_EAGLE_OC_ICE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_AERO_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_AERO_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 MASTER", DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_MASTER_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellAorusWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_XTREME_WATERFORCE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_XTREME_WATERFORCE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_GAMING_OC_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_XTREME_WATERFORCE_32G_SUB_DEV1, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_XTREME_WATERFORCE_32G_SUB_DEV2, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 MASTER", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090_MASTER_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 MASTER ICE", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090_MASTER_ICE_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5060 Ti Gaming OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5060TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5060TI_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_AERO_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Eagle OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_EAGLE_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Eagle OC ICE", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_EAGLE_OC_ICE_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070_GAMING_OC_12G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Eagle OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_EAGLE_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Eagle OC ICE", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_EAGLE_OC_ICE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_AERO_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5070 Ti Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5070TI_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 Aero OC", DetectGigabyteRGBFusion2BlackwellSingleZoneGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_AERO_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 MASTER", DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_MASTER_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellAorusWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_XTREME_WATERFORCE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5080 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5080_XTREME_WATERFORCE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5080 MASTER ICE", DetectGigabyteRGBFusion2BlackwellAorusMaster5080LayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5080_MASTER_ICE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 Gaming OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_GAMING_OC_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_XTREME_WATERFORCE_32G_SUB_DEV1, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 5090 XTREME WATERFORCE", DetectGigabyteRGBFusion2BlackwellWaterforceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX5090_XTREME_WATERFORCE_32G_SUB_DEV2, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 MASTER", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090_MASTER_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 MASTER ICE", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090_MASTER_ICE_32G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 5090 D V2 MASTER ICE", DetectGigabyteRGBFusion2BlackwellAorusMaster5090DV2IceLayoutGPUControllers, NVIDIA_VEN, NVIDIA_RTX5090D_V2_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RTX5090D_V2_MASTER_ICE_24G_SUB_DEV, 0x75);
/*-----------------------------------------*\
| AMD GPUs |
@@ -213,6 +232,7 @@ REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 9060 XT GAMING",
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 9060 XT GAMING OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, AMD_GPU_VEN, AMD_NAVI44_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX9060XT_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS Radeon RX 9070 XT Elite", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RX9070XT_ELITE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 9070 XT GAMING OC", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX9070XT_GAMING_OC_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 9070 XT GAMING OC ICE", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX9070XT_GAMING_OC_ICE_16G_SUB_DEV, 0x75);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 9070 XT GAMING", DetectGigabyteRGBFusion2BlackwellGamingLayoutGPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX9070XT_GAMING_16G_SUB_DEV, 0x75);
@@ -400,6 +400,52 @@ void RGBController_RGBFusion2BlackwellGPU::SetupZones()
leds.push_back(top_led);
}
else if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT)
{
const char * fan_names[] = { "Right Fan", "Left Fan", "Middle Fan" };
for(int i = 0; i < 3; i++)
{
zone fan_zone;
fan_zone.name = fan_names[i];
fan_zone.type = ZONE_TYPE_SINGLE;
fan_zone.leds_min = 1;
fan_zone.leds_max = 1;
fan_zone.leds_count = 1;
fan_zone.matrix_map = NULL;
zones.push_back(fan_zone);
led new_led;
new_led.name = fan_names[i];
leds.push_back(new_led);
}
zone backplate;
backplate.name = "Backplate";
backplate.type = ZONE_TYPE_SINGLE;
backplate.leds_min = 1;
backplate.leds_max = 1;
backplate.leds_count = 1;
backplate.matrix_map = NULL;
zones.push_back(backplate);
led bp_led;
bp_led.name = "Backplate";
leds.push_back(bp_led);
zone side_logo;
side_logo.name = "Side Logo";
side_logo.type = ZONE_TYPE_SINGLE;
side_logo.leds_min = 1;
side_logo.leds_max = 1;
side_logo.leds_count = 1;
side_logo.matrix_map = NULL;
zones.push_back(side_logo);
led sl_led;
sl_led.name = "Side Logo";
leds.push_back(sl_led);
}
SetupColors();
}
@@ -446,6 +492,10 @@ void RGBController_RGBFusion2BlackwellGPU::DeviceUpdateLEDs()
gpu_zones = 6; // Zones 4 and 5 refer to ui zone 4
break;
case RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT:
gpu_zones = 6;
break;
default:
LOG_TRACE("[%s] Invalid GPU layout (%d) when updating LEDs.", name.c_str(), gpu_layout);
return; // should not happen
@@ -472,7 +522,8 @@ void RGBController_RGBFusion2BlackwellGPU::DeviceUpdateLEDs()
}
ui_zone_idx = zone_idx - 1; // Map: HW zone 1->UI zone 0, HW zone 2->UI zone 1, HW zone 3->UI zone 2, HW zone 4->UI zone 3
}
else if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT)
else if(gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5080_LAYOUT ||
gpu_layout == RGB_FUSION2_BLACKWELL_GPU_AORUS_MASTER_5090D_V2_ICE_LAYOUT)
{
if(zone_idx == 5)
{
@@ -152,6 +152,7 @@ REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 3090 XTREME WATERFORCE",
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS GeForce RTX 3090 XTREME WATERFORCE WB", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX3090_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX3090_XTREME_WATERFORCE_WB_SUB_DEV, 0x64);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 GAMING OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070_GAMING_OC_12G, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 GAMING OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070_GAMING_OC_12G_V2, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 GAMING OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_AD103_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070_GAMING_OC_12G_V2, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Geforce RTX 4070 Aero OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070_AERO_OC_12G_SUB_DEV, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 SUPER GAMING OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070S_GAMING_OC_12G, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce RTX 4070 SUPER Aero OC", DetectGigabyteRGBFusion2GPUControllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RTX4070S_AERO_OC_12G, 0x71);
@@ -188,14 +189,17 @@ REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7600 GAMING OC 8G",
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7600 GAMING OC 8G", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI33_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7600_GAMING_OC_8G_SUB_DEV2, 0x55);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7600 XT GAMING OC 16G", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI33_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7600XT_GAMING_OC_16G_SUB_DEV, 0x55);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 6700 XT GAMING OC 12G", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI22_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX6700XT_GAMING_OC_12G_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 6800 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI21_DEV1, GIGABYTE_SUB_VEN, GIGABYTE_RX6800XT_GAMING_OC_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 6900 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI21_DEV1, GIGABYTE_SUB_VEN, GIGABYTE_RX6900XT_GAMING_OC_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7700 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI32_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7700XT_GAMING_OC_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7800 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI32_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7800XT_GAMING_OC_16G_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7900 GRE GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI31_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7900GRE_GAMING_OC_16G_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7900 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI31_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7900XT_GAMING_OC_20G_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 7900 XTX GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI31_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX7900XTX_GAMING_OC_24G_SUB_DEV, 0x62);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS Radeon RX 7900 XTX ELITE 24G", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI31_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RX7900XTX_ELITE_24G_SUB_DEV, 0x71);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS RX 6750 XT ELITE 12G", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI22_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RX_6750_XT_ELITE_12G_SUB_DEV, 0x70);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS RX 6900 XT EXTREME WATERFORCE WB", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI21_DEV2, GIGABYTE_SUB_VEN, GIGABYTE_RX6900XT_XTREME_WATERFORCE_WB_SUB_DEV, 0x70);
REGISTER_I2C_PCI_DETECTOR("Gigabyte AORUS Radeon RX 9070 XT Elite", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, GIGABYTE_SUB_VEN, GIGABYTE_AORUS_RX9070XT_ELITE_16G_SUB_DEV, 0x73);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 9070 XT GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX9070XT_GAMING_OC_16G_SUB_DEV, 0x73);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 9070 XT GAMING OC ICE", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX9070XT_GAMING_OC_ICE_16G_SUB_DEV, 0x73);
REGISTER_I2C_PCI_DETECTOR("Gigabyte Radeon RX 9070 GAMING OC", DetectGigabyteRGBFusion2GPUControllers, AMD_GPU_VEN, AMD_NAVI48_DEV, GIGABYTE_SUB_VEN, GIGABYTE_RX9070_GAMING_OC_16G_SUB_DEV, 0x73);
File diff suppressed because it is too large Load Diff
@@ -16,7 +16,7 @@
#include <map>
#include "RGBController.h"
#define GB_FUSION2_ZONES_MAX 8
#define GB_FUSION2_ZONES_MAX 12
/*--------------------------------------------------------*\
| Base LED mappings found on all controllers. |
@@ -89,6 +89,23 @@ const FwdLedHeaders LedLookup =
\*-------------------------------------------------*/
};
/*--------------------------------------------------------*\
| The layout_id masks for supported effects. |
\*--------------------------------------------------------*/
enum GB_LID_EFFECTS_MASKS : uint32_t
{
GB_EFF_BREATH = 0x001,
GB_EFF_BEAT = 0x002,
GB_EFF_CYCLE = 0x004,
GB_EFF_FLASH = 0x008,
GB_EFF_RANDOM = 0x010,
GB_EFF_WAVE = 0x020,
GB_EFF_DFLASH = 0x040,
GB_EFF_WAVE1 = 0x080,
GB_EFF_WAVE2 = 0x100,
GB_EFF_CORE_MASK = 0x1FF
};
typedef struct
{
GB_FUSION2_LED_IDX idx;
@@ -95,10 +95,9 @@ bool RGBFusion2USBController::RefreshHardwareInfo()
return false;
}
IT8297Report report;
std::memcpy(&report, buffer, sizeof(IT8297Report));
report_loaded = true;
device_num = report.device_num;
description = std::string(report.str_product, 28);
if(std::string::iterator nul = std::find(description.begin(), description.end(), '\0');
nul != description.end())
@@ -318,7 +317,7 @@ bool RGBFusion2USBController::SetCalibration(const EncodedCalibration& cal, bool
cal_data.mainboard = desired.c.rgb_cali;
cal_data.spare[0] = desired.c.c_spare0;
cal_data.spare[1] = desired.c.c_spare1;
if(product_id == 0x5711)
{
cal_data.dled[2] = desired.c.d_strip_c2;
@@ -362,7 +361,6 @@ void RGBFusion2USBController::SetLedCount(unsigned int c0, unsigned int c1, unsi
\*---------------------------------------------------------*/
bool RGBFusion2USBController::SetStripBuiltinEffectState(int hdr, bool enable)
{
static bool first_call = true;
int bitmask = 0;
if(hdr == -1)
@@ -392,16 +390,16 @@ bool RGBFusion2USBController::SetStripBuiltinEffectState(int hdr, bool enable)
}
}
int new_effect_disabled = enable
? (effect_disabled & ~bitmask)
: (effect_disabled | bitmask);
int base_mask = (effect_disabled < 0) ? 0 : effect_disabled;
int new_effect_disabled = enable
? (base_mask & ~bitmask)
: (base_mask | bitmask);
if(!first_call && new_effect_disabled == effect_disabled)
// Skip redundant writes only after we have synchronized at least once
if(effect_disabled >= 0 && new_effect_disabled == effect_disabled)
{
return true;
}
first_call = false;
effect_disabled = new_effect_disabled;
int res = SendCCReport(0x32, effect_disabled);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
@@ -439,7 +437,7 @@ bool RGBFusion2USBController::EnableLampArray(bool enable)
{
return SendCCReport(0x48, enable ? 1 : 0);
}
std::string RGBFusion2USBController::GetDeviceName()
{
return(name);
@@ -703,3 +701,115 @@ void RGBFusion2USBController::ResetController()
}
ApplyEffect(true);
}
/*---------------------------------------------------------*\
| Check controller for gen2 ARGB support |
| Checks for supported device number |
| Then checks for supported controllers |
| Then checks for supported feature bit (SaveLEDState) |
| Finally checks for strip detection value. |
\*---------------------------------------------------------*/
bool RGBFusion2USBController::SupportsGen2() const
{
bool supports_gen2 = false;
supports_gen2 = (device_num == 0x00)
&& (product_id == 0x5702 || product_id==0x5711 || product_id==0x8950)
&& (report.support_cmd_flag & 0x01)
&& (report.strip_detect == 0x01);
return supports_gen2;
}
std::vector<Gen2StripInfo> RGBFusion2USBController::ExportGen2Strips() const
{
size_t count = (product_id == 0x5711) ? 4u : 2u;
std::vector<Gen2StripInfo> out;
out.reserve(count);
for(size_t i = 0; i < count; ++i)
{
out.push_back(g2_strip_info[i]);
}
return out;
}
/*---------------------------------------------------------*\
| Scan Headers for Gen2 Devices |
\*---------------------------------------------------------*/
bool RGBFusion2USBController::ScanGen2Strips()
{
for(unsigned i = 0; i < 4; ++i)
{
if(g2_strip_info[i].LedsOfStrip.capacity() < 15)
{
g2_strip_info[i].LedsOfStrip.reserve(15);
}
g2_strip_info[i].numStrip = 0;
g2_strip_info[i].totalLeds = 0;
g2_strip_info[i].LedsOfStrip.resize(0);
}
const unsigned int hdr_lim = (product_id == 0x5711) ? 4u : 2u;
for(unsigned int slot = 0; slot < hdr_lim; ++slot)
{
static constexpr uint8_t delta[4] = {4, 5, 0, 1};
uint8_t scan_cmd = static_cast<uint8_t>(GEN2_LED_BASE_SCAN + delta[slot]);
uint8_t info_cmd = static_cast<uint8_t>(scan_cmd + 2);
if(!SendCCReport(scan_cmd, 0x00, 0x00))
{
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(700));
if(!SendCCReport(info_cmd, 0x00, 0x00))
{
return false;
}
unsigned char feature_buf[64] = {0};
feature_buf[0] = report_id;
int recv_len = hid_get_feature_report(dev, feature_buf, sizeof(feature_buf));
if(recv_len < 64)
{
return false;
}
int seg_count = static_cast<int>(feature_buf[1]);
if(seg_count < 0)
{
seg_count = 0;
}
if(seg_count > 15)
{
seg_count = 15;
}
Gen2StripInfo& dst = g2_strip_info[slot];
dst.numStrip = static_cast<uint8_t>(seg_count);
dst.LedsOfStrip.resize(static_cast<size_t>(seg_count));
uint32_t total_leds = 0;
const int counts_base = 2;
for(int k = 0; k < seg_count; ++k)
{
const int off = counts_base + (k * 2);
uint16_t lo = static_cast<uint16_t>(feature_buf[off + 0]);
uint16_t hi = static_cast<uint16_t>(feature_buf[off + 1]);
uint16_t cnt = static_cast<uint16_t>(lo | (hi << 8));
dst.LedsOfStrip[static_cast<size_t>(k)] = cnt;
total_leds += cnt;
}
dst.totalLeds = total_leds;
SetLedCount(0, 0, 0, 0);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
SaveLEDState(false);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
return true;
}
@@ -21,6 +21,11 @@
#define FUSION2_USB_BUFFER_SIZE 64
/*--------------------------------------------------------*\
| Gen2 scan/info opcode base |
\*--------------------------------------------------------*/
#define GEN2_LED_BASE_SCAN 0x38
/*---------------------------------------------------------*\
| Effects mode list |
\*---------------------------------------------------------*/
@@ -83,6 +88,16 @@ struct EncodedCalibration
std::string mainboard;
};
/*---------------------------------------------------------*\
| High level struct to contain Gen2 Header Data |
\*---------------------------------------------------------*/
struct Gen2StripInfo
{
uint8_t numStrip = 0;
std::vector<uint16_t> LedsOfStrip;
uint32_t totalLeds = 0;
};
#pragma pack(push, 1)
/*---------------------------------------------------------*\
@@ -321,6 +336,8 @@ public:
void SetLEDEffect(int led, int mode, unsigned int speed, unsigned char brightness, bool random, uint32_t* color);
bool SetStripBuiltinEffectState(int hdr, bool enable);
void SetStripColors(unsigned int hdr, RGBColor * colors, unsigned int num_colors, int single_led = -1);
bool SupportsGen2() const;
bool ScanGen2Strips();
EncodedCalibration GetCalibration(bool refresh_from_hw = false);
std::string GetDeviceName();
@@ -330,6 +347,8 @@ public:
std::string GetFWVersion();
uint16_t GetProductID();
std::string GetSerial();
std::vector<Gen2StripInfo> ExportGen2Strips() const;
Gen2StripInfo g2_strip_info[4];
private:
std::string DecodeCalibrationBuffer(uint32_t value) const;
@@ -345,7 +364,7 @@ private:
int SendPacket(unsigned char* packet);
hid_device* dev;
int device_num;
uint8_t device_num;
uint16_t product_id;
uint32_t effect_zone_mask = 0;
int mode;
@@ -356,7 +375,7 @@ private:
std::string location;
std::string version;
std::string chip_id;
int effect_disabled = 0;
int effect_disabled = -1;
int report_id = 0xCC;
bool report_loaded = false;
bool cali_loaded = false;
@@ -25,7 +25,7 @@
@effects :white_check_mark:
@detectors DetectGigabyteRGBFusion2USBControllers
@comment The Fusion 2 USB controller applies to most AMD and
Intel mainboards from the x570 and z390 chipsets onwards.
Intel mainboards from the X570 and z390 chipsets onwards.
\*-------------------------------------------------------------------*/
RGBController_RGBFusion2USB::RGBController_RGBFusion2USB(RGBFusion2USBController* controller_ptr, std::string detector)
@@ -39,8 +39,10 @@ RGBController_RGBFusion2USB::RGBController_RGBFusion2USB(RGBFusion2USBController
version = controller->GetFWVersion();
location = controller->GetDeviceLocation();
serial = controller->GetSerial();
product_id = controller->GetProductID();
device_num = controller->GetDeviceNum();
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFFFF;
@@ -206,6 +208,13 @@ RGBController_RGBFusion2USB::RGBController_RGBFusion2USB(RGBFusion2USBController
RGBController_RGBFusion2USB::~RGBController_RGBFusion2USB()
{
// Free any zones we allocated for the per-instance layout
for(gb_fusion2_zone* z : allocated_zones)
{
delete z;
}
allocated_zones.clear();
delete controller;
}
@@ -214,94 +223,125 @@ RGBController_RGBFusion2USB::~RGBController_RGBFusion2USB()
\*---------------------------------------------------------*/
void RGBController_RGBFusion2USB::Init_Controller()
{
const std::string SectionCustom = "CustomLayout";
const gb_fusion2_device* src_layout = gb_fusion2_device_list[device_index];
const std::string SectionGen2 = "Gigabyte-Gen2-ARGB";
const std::string SectionCustomBase = "CustomLayout";
const std::string SectionCustom = SectionCustomBase + std::to_string(device_num);
const std::string SectionCalibration = "Calibration";
RvrseLedHeaders ReverseLedLookup = reverse_map(LedLookup);
SettingsManager* settings_manager = ResourceManager::get()->GetSettingsManager();
nlohmann::json device_settings = settings_manager->GetSettings(detector_name);
/*---------------------------------------------------------*\
| Create the custom layout from the generic_device |
| Checks for Gen2 support and adds flag to json. |
\*---------------------------------------------------------*/
gb_fusion2_device* layout = const_cast<gb_fusion2_device*>(gb_fusion2_device_list[device_index]);
if(controller->SupportsGen2())
{
if(!device_settings.contains(SectionGen2))
{
device_settings[SectionGen2]["Enabled"] = false;
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
supports_gen2 = device_settings[SectionGen2]["Enabled"];
if(supports_gen2)
{
controller->ScanGen2Strips();
}
}
/*---------------------------------------------------------*\
| Create the custom layout from the generic layout |
\*---------------------------------------------------------*/
switch(product_id)
{
case 0x8950:
src_layout = gb_fusion2_device_list[device_index + 1];
break;
case 0x5711:
src_layout = gb_fusion2_device_list[device_index + 2];
break;
default:
break;
}
if(!device_settings.contains(SectionCustom))
{
device_settings[SectionCustom]["Enabled"] = false;
device_settings[SectionCustom]["Data"] = BuildCustomLayoutJson(layout, ReverseLedLookup);
device_settings[SectionCustom]["Data"] = BuildCustomLayoutJson(src_layout, ReverseLedLookup);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
bool custom_layout = device_settings[SectionCustom]["Enabled"];
if(custom_layout)
{
LoadCustomLayoutFromJson(device_settings[SectionCustom]["Data"], LedLookup, layout);
}
EncodedCalibration hw_cal = controller->GetCalibration(false);
if(!device_settings.contains(SectionCalibration))
if(device_num == 0)
{
device_settings[SectionCalibration]["Enabled"] = false;
device_settings[SectionCalibration]["Data"] = WriteCalJsonFrom(hw_cal);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
else
{
nlohmann::json& cal_sec = device_settings[SectionCalibration];
bool cal_enable = cal_sec.value("Enabled", false);
if(!cal_sec.contains("Data") || !cal_sec["Data"].is_object())
if(!device_settings.contains(SectionCalibration))
{
cal_sec["Data"] = WriteCalJsonFrom(hw_cal);
device_settings[SectionCalibration]["Enabled"] = false;
device_settings[SectionCalibration]["Data"] = WriteCalJsonFrom(hw_cal);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
else
{
nlohmann::json& cdata = cal_sec["Data"];
FillMissingWith(cdata, hw_cal);
nlohmann::json& cal_sec = device_settings[SectionCalibration];
bool cal_enable = cal_sec.value("Enabled", false);
if(!cal_enable)
if(!cal_sec.contains("Data") || !cal_sec["Data"].is_object())
{
cal_sec["Data"] = WriteCalJsonFrom(hw_cal);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
}
if(cal_enable)
{
const nlohmann::json& cdata = cal_sec["Data"];
EncodedCalibration desired;
desired.dled[0] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED1");
desired.dled[1] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED2");
desired.mainboard = GET_JSON_VAL_ELSE_OFF(cdata, "Mainboard");
desired.spare[0] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare0");
desired.spare[1] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare1");
if(controller->GetProductID() == 0x5711)
{
desired.dled[2] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED3");
desired.dled[3] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED4");
desired.spare[2] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare2");
desired.spare[3] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare3");
}
else
{
desired.dled[2] = "OFF";
desired.dled[3] = "OFF";
desired.spare[2] = "OFF";
desired.spare[3] = "OFF";
nlohmann::json& cdata = cal_sec["Data"];
FillMissingWith(cdata, hw_cal);
if(!cal_enable)
{
cal_sec["Data"] = WriteCalJsonFrom(hw_cal);
settings_manager->SetSettings(detector_name, device_settings);
settings_manager->SaveSettings();
}
}
if(cal_enable)
{
const nlohmann::json& cdata = cal_sec["Data"];
EncodedCalibration desired;
desired.dled[0] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED1");
desired.dled[1] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED2");
desired.mainboard = GET_JSON_VAL_ELSE_OFF(cdata, "Mainboard");
desired.spare[0] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare0");
desired.spare[1] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare1");
if(controller->GetProductID() == 0x5711)
{
desired.dled[2] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED3");
desired.dled[3] = GET_JSON_VAL_ELSE_OFF(cdata, "HDR_D_LED4");
desired.spare[2] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare2");
desired.spare[3] = GET_JSON_VAL_ELSE_OFF(cdata, "Spare3");
}
else
{
desired.dled[2] = "OFF";
desired.dled[3] = "OFF";
desired.spare[2] = "OFF";
desired.spare[3] = "OFF";
}
controller->SetCalibration(desired, false);
}
controller->SetCalibration(desired, false);
}
}
/*---------------------------------------------------------------------*\
| When no match found the first entry (generic_device) will be used |
| otherwise look up channel map based on device name |
@@ -315,35 +355,81 @@ void RGBController_RGBFusion2USB::Init_Controller()
\*-----------------------------------------------------------------*/
for(unsigned int i = 0; i < GB_FUSION2_DEVICE_COUNT; i++)
{
if(gb_fusion2_device_list[i]->name == name)
if(gb_fusion2_device_list[i]->name == name &&
gb_fusion2_device_list[i]->device_num == device_num)
{
/*---------------------------------------------------------*\
| Set device ID |
\*---------------------------------------------------------*/
device_index = i;
layout = const_cast<gb_fusion2_device*>(gb_fusion2_device_list[i]);
src_layout = gb_fusion2_device_list[i];
break;
}
}
}
/*---------------------------------------------------------------------*\
| Creates per instance copy of layouts. |
\*---------------------------------------------------------------------*/
instance_layout.zones = &instance_zones;
instance_layout.layout_id = src_layout->layout_id;
instance_layout.device_num = src_layout->device_num;
instance_layout.name = src_layout->name;
for(uint8_t zi = 0; zi < GB_FUSION2_ZONES_MAX; ++zi)
{
(*instance_layout.zones)[zi] = (*src_layout->zones)[zi];
}
if(custom_layout)
{
LoadCustomLayoutFromJson(device_settings[SectionCustom]["Data"], LedLookup, &instance_layout);
}
/*---------------------------------------------------------------------*\
| Culls the mode support based on layout_id. |
\*---------------------------------------------------------------------*/
const uint32_t effect_mask = instance_layout.layout_id & GB_EFF_CORE_MASK;
modes.erase(std::remove_if(modes.begin(), modes.end(),
[effect_mask](const mode& m)
{
if(m.value == 0xFFFF /* Direct */) { return false; }
if(m.value == EFFECT_STATIC) { return false; }
uint32_t bit = 0u;
switch(m.value)
{
case EFFECT_PULSE: bit = GB_EFF_BREATH; break;
case EFFECT_COLORCYCLE: bit = GB_EFF_CYCLE; break;
case EFFECT_BLINKING: bit = GB_EFF_FLASH; break;
case EFFECT_RANDOM: bit = GB_EFF_RANDOM; break;
case EFFECT_WAVE: bit = GB_EFF_WAVE; break;
case EFFECT_DFLASH: bit = GB_EFF_DFLASH; break;
case EFFECT_WAVE1: bit = GB_EFF_WAVE1; break;
case EFFECT_WAVE2: bit = GB_EFF_WAVE2; break;
case EFFECT_WAVE3: bit = GB_EFF_WAVE1; break;
case EFFECT_WAVE4: bit = GB_EFF_WAVE2; break;
default: bit = 0u; break;
}
return (bit == 0u) || ((effect_mask & bit) == 0u);
}),
modes.end());
/*---------------------------------------------------------*\
| Iterate through layout and process each zone |
\*---------------------------------------------------------*/
for(uint8_t zone_idx = 0; zone_idx < GB_FUSION2_ZONES_MAX; zone_idx++)
{
if(!layout->zones[0][zone_idx])
if(!(*instance_layout.zones)[zone_idx])
{
continue;
}
const gb_fusion2_zone* zone_at_idx = layout->zones[0][zone_idx];
const gb_fusion2_zone* zone_at_idx = (*instance_layout.zones)[zone_idx];
zone new_zone;
new_zone.name = zone_at_idx->name;
new_zone.leds_min = zone_at_idx->leds_min;
new_zone.leds_max = zone_at_idx->leds_max;
new_zone.leds_count = new_zone.leds_min;
new_zone.type = (new_zone.leds_min == new_zone.leds_max) ? ZONE_TYPE_SINGLE : ZONE_TYPE_LINEAR;
new_zone.type = ((new_zone.leds_min == 1) && (new_zone.leds_max == 1)) ? ZONE_TYPE_SINGLE : ZONE_TYPE_LINEAR;
new_zone.matrix_map = NULL;
zones.emplace_back(new_zone);
}
@@ -363,17 +449,68 @@ void RGBController_RGBFusion2USB::SetupZones()
| Set up zones (Fixed so as to not spam the controller) |
\*---------------------------------------------------------*/
std::vector<Gen2StripInfo> strips = controller->ExportGen2Strips();
for(uint8_t zone_idx = 0; zone_idx < GB_FUSION2_ZONES_MAX; zone_idx++)
{
const gb_fusion2_zone* zone_at_idx = gb_fusion2_device_list[device_index]->zones[0][zone_idx];
const gb_fusion2_zone* zone_at_idx = (*instance_layout.zones)[zone_idx];
if(!zone_at_idx)
{
continue;
}
bool single_zone = (zone_at_idx->leds_min == zone_at_idx->leds_max);
bool single_zone = ((zone_at_idx->leds_min == 1) && (zone_at_idx->leds_max == 1));
if(!single_zone)
{
if(supports_gen2)
{
int slot = 0;
switch(zone_at_idx->idx)
{
case LED4:
case HDR_D_LED2:
slot = 1;
break;
case HDR_D_LED3:
slot = 2;
break;
case HDR_D_LED4:
slot = 3;
break;
default:
break;
}
if(slot >= 0 && static_cast<size_t>(slot) < strips.size())
{
const Gen2StripInfo& info = strips[static_cast<size_t>(slot)];
if(info.totalLeds > 0u)
{
zones[zone_idx].leds_count = static_cast<unsigned int>(info.totalLeds);
zones[zone_idx].leds_min = static_cast<unsigned int>(info.totalLeds);
zones[zone_idx].leds_max = static_cast<unsigned int>(info.totalLeds);
zones[zone_idx].segments.clear();
zones[zone_idx].segments.reserve(info.LedsOfStrip.size());
unsigned int offset = 0;
for(size_t si = 0; si < info.LedsOfStrip.size(); ++si)
{
const uint16_t cnt = info.LedsOfStrip[si];
if(cnt == 0) continue;
segment seg;
seg.name = std::string("Segment ") + std::to_string(si);
seg.type = ZONE_TYPE_LINEAR;
seg.start_idx = offset;
seg.leds_count = static_cast<unsigned int>(cnt);
zones[zone_idx].segments.push_back(seg);
offset += static_cast<unsigned int>(cnt);
}
}
}
}
switch(zone_at_idx->idx)
{
case LED4:
@@ -826,6 +963,7 @@ void RGBController_RGBFusion2USB::LoadCustomLayoutFromJson(
&& new_zone->idx <= GB_FUSION2_LED_IDX::LED11)
{
layout->zones[0][zone_idx] = new_zone;
allocated_zones.push_back(new_zone);
}
else
{
@@ -62,17 +62,26 @@ private:
std::string detector_name;
RGBFusion2USBController* controller;
int device_num;
uint8_t device_num;
RGBColor null_color = 0;
bool supports_gen2 = 0;
/*---------------------------------------------------------*\
| The intial value of device_index should point to the |
| layout for the generic_device |
\*---------------------------------------------------------*/
uint32_t device_index = 0;
uint16_t product_id = 0;
uint32_t effects_mask = 0;
void Init_Controller();
int GetLED_Zone(int led_idx);
/*---------------------------------------------------------*\
| Per instance layout lookup tables. |
\*---------------------------------------------------------*/
gb_fusion2_device instance_layout{};
gb_fusion2_layout instance_zones{};
std::vector<gb_fusion2_zone*> allocated_zones;
nlohmann::json WriteCalJsonFrom(
const EncodedCalibration& src);
void FillMissingWith(
@@ -105,6 +105,7 @@ void DetectGigabyteRGBFusionGPUControllers(i2c_smbus_interface* bus, uint8_t i2c
}
} /* DetectGigabyteRGBFusionGPUControllers() */
REGISTER_I2C_PCI_DETECTOR("Gigabyte GTX 1050 G1 Gaming", DetectGigabyteRGBFusionGPUControllers, NVIDIA_VEN, NVIDIA_GTX1050_DEV, GIGABYTE_SUB_VEN, GIGABYTE_GTX1050_G1_GAMING_SUB_DEV, 0x47);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce GTX 1050 Ti G1 Gaming Rev A1", DetectGigabyteRGBFusionGPUControllers, NVIDIA_VEN, NVIDIA_GTX1050TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_GTX1050TI_G1_GAMING_SUB_DEV, 0x47);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce GTX 1050 Ti G1 Gaming", DetectGigabyteRGBFusionGPUControllers, NVIDIA_VEN, NVIDIA_GTX1050TI_DEV, GIGABYTE_SUB_VEN, GIGABYTE_GTX1050TI_G1_GAMING_SUB_DEV, 0x48);
REGISTER_I2C_PCI_DETECTOR("Gigabyte GeForce GTX 1060 G1 Gaming", DetectGigabyteRGBFusionGPUControllers, NVIDIA_VEN, NVIDIA_GTX1060_DEV, GIGABYTE_SUB_VEN, GIGABYTE_GTX1060_G1_GAMING_SUB_DEV, 0x48);
@@ -16,10 +16,21 @@
using namespace std::chrono_literals;
static std::map<std::string, unsigned int> govee_led_counts
struct GoveeHardwareInfo
{
{ "H619A", 20 },
{ "H70B1", 20 },
unsigned int led_count;
unsigned int matrix_row_len; // 0 = linear
};
const unsigned int GOVEE_FALLBACK_LED_COUNT = 20;
static std::map<std::string, GoveeHardwareInfo> govee_hardware_info
{
{ "H6022", { 132, 12 } }, // Govee Smart Table Lamp 2
{ "H612F", { 12, 0 } }, // Govee Strip Light S (3m)
{ "H619A", { 20, 0 } }, // Govee RGBIC Led Strip Lights
{ "H70B1", { 20, 0 } }, // Govee LED Curtain Lights
{ "H607C", { 174, 0 } }, // Govee Floor Lamp 2
};
RGBController_Govee::RGBController_Govee(GoveeController* controller_ptr)
@@ -67,39 +78,64 @@ RGBController_Govee::~RGBController_Govee()
void RGBController_Govee::SetupZones()
{
unsigned int led_count = 0;
std::map<std::string, unsigned int>::iterator it = govee_led_counts.find(controller->GetSku());
if(it != govee_led_counts.end())
GoveeHardwareInfo hw = { GOVEE_FALLBACK_LED_COUNT, 0 };
bool resizable = true;
std::map<std::string, GoveeHardwareInfo>::iterator it = govee_hardware_info.find(controller->GetSku());
if(it != govee_hardware_info.end())
{
led_count = it->second;
}
/*-----------------------------------------------------*\
| Fallback so Direct mode is usable even if SKU isn't |
| in the table |
\*-----------------------------------------------------*/
if(led_count == 0)
{
led_count = 20; /* safe default; user can resize in UI */
hw = it->second;
resizable = false;
}
zone strip;
strip.name = "Govee Strip";
strip.type = ZONE_TYPE_LINEAR;
strip.leds_count = led_count;
/*-----------------------------------------------------*\
| Only make resizable for unknown SKUs |
\*-----------------------------------------------------*/
if(govee_led_counts.find(controller->GetSku()) == govee_led_counts.end())
strip.leds_count = hw.led_count;
strip.leds_min = resizable ? 0 : hw.led_count;
strip.leds_max = resizable ? 255 : hw.led_count;
if(hw.matrix_row_len == 0)
{
strip.leds_min = 1;
strip.leds_max = 255;
strip.name = "Govee Strip";
strip.type = ZONE_TYPE_LINEAR;
strip.matrix_map = NULL;
}
else
{
strip.leds_min = led_count;
strip.leds_max = led_count;
strip.name = "Govee Matrix";
strip.type = ZONE_TYPE_MATRIX;
unsigned int width = hw.matrix_row_len;
unsigned int height = hw.led_count / width;
strip.matrix_map = new matrix_map_type;
strip.matrix_map->height = height;
strip.matrix_map->width = width;
strip.matrix_map->map = new unsigned int[hw.led_count];
/*-----------------------------------------------------*\
| On H6022, LEDs indexed bottom to top, alternating |
| clockwise and counterclockwise for each row. |
\*-----------------------------------------------------*/
for(unsigned int y = 0; y < height; y++)
{
/*-------------------------------------------------*\
| LEDs numbered bottom to top, opposite of matrix |
| clockwise and counterclockwise for each row. |
\*-------------------------------------------------*/
unsigned int led_y = (height - 1) - y;
for(unsigned int x = 0; x < width; x++)
{
/*---------------------------------------------*\
| LED is right-to-left for even rows, including |
| first one |
\*---------------------------------------------*/
unsigned int led_x = led_y & 1 ? x : (width - 1) - x;
strip.matrix_map->map[y * width + x] = led_y * width + led_x;
}
}
}
strip.matrix_map = NULL;
zones.push_back(strip);
for(std::size_t led_idx = 0; led_idx < strip.leds_count; led_idx++)
@@ -114,6 +150,11 @@ void RGBController_Govee::SetupZones()
void RGBController_Govee::ResizeZone(int zone, int new_size)
{
if(zones[zone].type == ZONE_TYPE_MATRIX)
{
return;
}
if(zone < 0 || zone >= (int)zones.size() || new_size <= 0)
{
return;
@@ -30,28 +30,9 @@ using namespace std::chrono_literals;
bool TestForHyperXDRAMController(i2c_smbus_interface* bus, unsigned char address)
{
bool pass = false;
int res = bus->i2c_smbus_read_byte(address);
int res = bus->i2c_smbus_write_quick(address, I2C_SMBUS_WRITE);
LOG_DEBUG("[%s] Writing at address %02X, res=%02X", HYPERX_CONTROLLER_NAME, address, res);
if (res >= 0)
{
pass = true;
for (int i = 0xA0; i < 0xB0; i++)
{
res = bus->i2c_smbus_read_byte_data(address, i);
if (res != i)
{
pass = false;
}
}
}
return(pass);
return(res >= 0);
} /* TestForHyperXDRAMController() */
@@ -17,7 +17,7 @@ HyperXAlloyOrigins60and65Controller::HyperXAlloyOrigins60and65Controller(hid_dev
{
dev = dev_handle;
location = path;
dev_name = name;
name = dev_name;
}
HyperXAlloyOrigins60and65Controller::~HyperXAlloyOrigins60and65Controller()
@@ -0,0 +1,121 @@
/*---------------------------------------------------------*\
| HyperXEve1800Controller.cpp |
| |
| Driver for HyperX Eve 1800 keyboard |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "HyperXEve1800Controller.h"
#include "StringUtils.h"
HyperXEve1800Controller::HyperXEve1800Controller(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
}
HyperXEve1800Controller::~HyperXEve1800Controller()
{
hid_close(dev);
}
std::string HyperXEve1800Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string HyperXEve1800Controller::GetNameString()
{
return(name);
}
std::string HyperXEve1800Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void HyperXEve1800Controller::SetBrightness(unsigned int brightness)
{
unsigned char buf[65];
memset(buf, 0x00, sizeof(buf));
buf[0x00] = 0x40;
buf[0x01] = 0x01;
buf[0x02] = 0x00;
buf[0x03] = 0x00;
buf[0x04] = brightness & 0xFF;
hid_write(dev, buf, 65);
}
void HyperXEve1800Controller::SetLEDsDirect(std::vector<RGBColor> colors)
{
SendDirectInitialization();
if(colors.empty())
{
RGBColor temp = 0x00000000;
SendDirectColorPacket(&temp, 1);
}
else
{
SendDirectColorPacket(&colors[0], (unsigned int)colors.size());
}
}
void HyperXEve1800Controller::SendDirectInitialization()
{
unsigned char buf[65];
memset(buf, 0x00, sizeof(buf));
buf[0x00] = 0x44;
buf[0x01] = 0x01;
buf[0x02] = 0x04;
buf[0x03] = 0x00;
hid_write(dev, buf, 65);
}
void HyperXEve1800Controller::SendDirectColorPacket(RGBColor* color_data, unsigned int color_count)
{
unsigned char buf[65];
memset(buf, 0x00, sizeof(buf));
buf[0x00] = 0x44;
buf[0x01] = 0x02;
buf[0x02] = 0x00;
buf[0x03] = 0x00;
/*-----------------------------------------------------*\
| The Eve 1800 exposes 10 lighting zones, but this |
| report format can carry up to 20 RGB triplets. |
\*-----------------------------------------------------*/
if(color_count > 20)
{
color_count = 20;
}
for(unsigned int color_idx = 0; color_idx < color_count; color_idx++)
{
buf[4 + (color_idx * 3)] = RGBGetRValue(color_data[color_idx]);
buf[4 + (color_idx * 3) + 1] = RGBGetGValue(color_data[color_idx]);
buf[4 + (color_idx * 3) + 2] = RGBGetBValue(color_data[color_idx]);
}
hid_write(dev, buf, 65);
}
@@ -0,0 +1,36 @@
/*---------------------------------------------------------*\
| HyperXEve1800Controller.h |
| |
| Driver for HyperX Eve 1800 keyboard |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
class HyperXEve1800Controller
{
public:
HyperXEve1800Controller(hid_device* dev_handle, const char* path, std::string dev_name);
~HyperXEve1800Controller();
std::string GetDeviceLocation();
std::string GetNameString();
std::string GetSerialString();
void SetBrightness(unsigned int brightness);
void SetLEDsDirect(std::vector<RGBColor> colors);
private:
hid_device* dev;
std::string location;
std::string name;
void SendDirectInitialization();
void SendDirectColorPacket(RGBColor* color_data, unsigned int color_count);
};
@@ -0,0 +1,127 @@
/*---------------------------------------------------------*\
| RGBController_HyperXEve1800.cpp |
| |
| RGBController for HyperX Eve 1800 keyboard |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_HyperXEve1800.h"
using namespace std::chrono_literals;
#define HYPERX_EVE_1800_BRIGHTNESS_MIN 0x00
#define HYPERX_EVE_1800_BRIGHTNESS_MAX 0xFF
#define HYPERX_EVE_1800_ZONE_COUNT 10
/**------------------------------------------------------------------*\
@name HyperX Eve 1800
@category Keyboard
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectHyperXEve1800
@comment
\*-------------------------------------------------------------------*/
RGBController_HyperXEve1800::RGBController_HyperXEve1800(HyperXEve1800Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "HyperX";
type = DEVICE_TYPE_KEYBOARD;
description = "HyperX Eve 1800 Keyboard Device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFFFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Direct.brightness_min = HYPERX_EVE_1800_BRIGHTNESS_MIN;
Direct.brightness_max = HYPERX_EVE_1800_BRIGHTNESS_MAX;
Direct.brightness = HYPERX_EVE_1800_BRIGHTNESS_MAX;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
keepalive_thread_run = 1;
keepalive_thread = new std::thread(&RGBController_HyperXEve1800::KeepaliveThread, this);
}
RGBController_HyperXEve1800::~RGBController_HyperXEve1800()
{
keepalive_thread_run = 0;
keepalive_thread->join();
delete keepalive_thread;
delete controller;
}
void RGBController_HyperXEve1800::SetupZones()
{
zone new_zone;
new_zone.name = "Keyboard";
new_zone.type = ZONE_TYPE_LINEAR;
new_zone.leds_min = HYPERX_EVE_1800_ZONE_COUNT;
new_zone.leds_max = HYPERX_EVE_1800_ZONE_COUNT;
new_zone.leds_count = HYPERX_EVE_1800_ZONE_COUNT;
new_zone.matrix_map = NULL;
zones.push_back(new_zone);
for(unsigned int led_idx = 0; led_idx < zones[0].leds_count; led_idx++)
{
led new_led;
new_led.name = "Zone ";
new_led.name.append(std::to_string(led_idx + 1));
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_HyperXEve1800::ResizeZone(int /*zone*/, int /*new_size*/)
{
}
void RGBController_HyperXEve1800::DeviceUpdateLEDs()
{
controller->SetLEDsDirect(colors);
last_update_time = std::chrono::steady_clock::now();
}
void RGBController_HyperXEve1800::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_HyperXEve1800::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_HyperXEve1800::DeviceUpdateMode()
{
controller->SetBrightness(modes[active_mode].brightness);
DeviceUpdateLEDs();
}
void RGBController_HyperXEve1800::KeepaliveThread()
{
while(keepalive_thread_run.load())
{
if(active_mode == 0)
{
if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(50))
{
UpdateLEDs();
}
}
std::this_thread::sleep_for(10ms);
}
}
@@ -0,0 +1,39 @@
/*---------------------------------------------------------*\
| RGBController_HyperXEve1800.h |
| |
| RGBController for HyperX Eve 1800 keyboard |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <chrono>
#include "RGBController.h"
#include "HyperXEve1800Controller.h"
class RGBController_HyperXEve1800 : public RGBController
{
public:
RGBController_HyperXEve1800(HyperXEve1800Controller* controller_ptr);
~RGBController_HyperXEve1800();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void KeepaliveThread();
private:
HyperXEve1800Controller* controller;
std::thread* keepalive_thread;
std::atomic<bool> keepalive_thread_run;
std::chrono::time_point<std::chrono::steady_clock> last_update_time;
};
@@ -15,12 +15,16 @@
#include "HyperXAlloyOriginsController.h"
#include "HyperXAlloyOriginsCoreController.h"
#include "HyperXAlloyOrigins60and65Controller.h"
#include "HyperXEve1800Controller.h"
#include "HyperXOrigins2_65Controller.h"
#include "RGBController_HyperXAlloyElite.h"
#include "RGBController_HyperXAlloyElite2.h"
#include "RGBController_HyperXAlloyFPS.h"
#include "RGBController_HyperXAlloyOrigins.h"
#include "RGBController_HyperXAlloyOriginsCore.h"
#include "RGBController_HyperXAlloyOrigins60and65.h"
#include "RGBController_HyperXEve1800.h"
#include "RGBController_HyperXOrigins2_65.h"
/*-----------------------------------------------------*\
| HyperX keyboard vendor and product IDs |
@@ -44,6 +48,8 @@
#define HYPERX_ALLOY_ORIGINS_65_HP_PID 0x038F
#define HYPERX_ALLOY_ORIGINS_CORE_HP_PID 0x098F
#define HYPERX_ALLOY_ORIGINS_HP_PID 0x0591
#define HYPERX_EVE_1800_HP_PID 0x08C2
#define HYPERX_ORIGINS_2_65_HP_PID 0x0CC2
AlloyOrigins60and65MappingLayoutType GetAlloyOrigins60and65MappingLayoutType(int pid)
{
@@ -140,12 +146,41 @@ void DetectHyperXAlloyOrigins60and65(hid_device_info* info, const std::string& n
}
}
void DetectHyperXOrigins2_65(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
HyperXOrigins2_65Controller* controller = new HyperXOrigins2_65Controller(dev, info->path, name);
RGBController_HyperXOrigins2_65* rgb_controller = new RGBController_HyperXOrigins2_65(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
void DetectHyperXEve1800(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
HyperXEve1800Controller* controller = new HyperXEve1800Controller(dev, info->path, name);
RGBController_HyperXEve1800* rgb_controller = new RGBController_HyperXEve1800(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
REGISTER_HID_DETECTOR_IP("HyperX Alloy Elite RGB", DetectHyperXAlloyElite, HYPERX_KEYBOARD_VID, HYPERX_ALLOY_ELITE_PID, 2, 0xFF01);
REGISTER_HID_DETECTOR_IP("HyperX Alloy FPS RGB", DetectHyperXAlloyFPS, HYPERX_KEYBOARD_VID, HYPERX_ALLOY_FPS_RGB_PID, 2, 0xFF01);
REGISTER_HID_DETECTOR_I("HyperX Alloy Origins Core", DetectHyperXAlloyOriginsCore, HYPERX_KEYBOARD_VID, HYPERX_ALLOY_ORIGINS_CORE_PID, 2);
REGISTER_HID_DETECTOR_I("HyperX Alloy Origins Core (HP)", DetectHyperXAlloyOriginsCore, HP_KEYBOARD_VID, HYPERX_ALLOY_ORIGINS_CORE_HP_PID, 2);
REGISTER_HID_DETECTOR_I("HyperX Origins 2 65 (HP)", DetectHyperXOrigins2_65, HP_KEYBOARD_VID, HYPERX_ORIGINS_2_65_HP_PID, 3);
REGISTER_HID_DETECTOR_I("HyperX Eve 1800 (HP)", DetectHyperXEve1800, HP_KEYBOARD_VID, HYPERX_EVE_1800_HP_PID, 2);
#ifdef _WIN32
REGISTER_HID_DETECTOR_I("HyperX Alloy Origins", DetectHyperXAlloyOrigins, HYPERX_KEYBOARD_VID, HYPERX_ALLOY_ORIGINS_PID, 3);
REGISTER_HID_DETECTOR_IP("HyperX Alloy Elite 2", DetectHyperXAlloyElite2, HYPERX_KEYBOARD_VID, HYPERX_ALLOY_ELITE_2_PID, 3, 0xFF90);
@@ -0,0 +1,151 @@
/*---------------------------------------------------------*\
| HyperXOrigins2_65Controller.cpp |
| |
| Driver for HyperX Origins 2 65 keyboard |
| |
| Ricardo Amorim 28 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "HyperXOrigins2_65Controller.h"
#include "StringUtils.h"
HyperXOrigins2_65Controller::HyperXOrigins2_65Controller(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
}
HyperXOrigins2_65Controller::~HyperXOrigins2_65Controller()
{
hid_close(dev);
}
std::string HyperXOrigins2_65Controller::GetDeviceLocation()
{
return("HID " + location);
}
std::string HyperXOrigins2_65Controller::GetNameString()
{
return(name);
}
std::string HyperXOrigins2_65Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return ("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void HyperXOrigins2_65Controller::SetLEDsDirect(std::vector<RGBColor> colors)
{
/*-----------------------------------------------------*\
| Set up variables to track progress of color transmit |
| Do this after inserting blanks |
\*-----------------------------------------------------*/
int colors_to_send = (int)colors.size();
int colors_sent = 0;
SendDirectInitialization();
for(int pkt_idx = 0; pkt_idx < 4; pkt_idx++)
{
if(colors_to_send > 20)
{
SendDirectColorPacket(&colors[colors_sent], 20, pkt_idx);
colors_sent += 20;
colors_to_send -= 20;
}
else if(colors_to_send > 0)
{
SendDirectColorPacket(&colors[colors_sent], colors_to_send, pkt_idx);
colors_sent += colors_to_send;
colors_to_send -= colors_to_send;
}
else
{
RGBColor temp = 0x00000000;
SendDirectColorPacket(&temp, 1, pkt_idx);
}
}
}
void HyperXOrigins2_65Controller::SendDirectInitialization()
{
unsigned char buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(buf, 0x00, sizeof(buf));
/*-----------------------------------------------------*\
| Set up Direct Initialization packet |
\*-----------------------------------------------------*/
buf[0x00] = 0x44;
buf[0x01] = 0x01;
buf[0x02] = 0x04;
buf[0x03] = 0x00;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev, buf, 65);
}
void HyperXOrigins2_65Controller::SendDirectColorPacket
(
RGBColor* color_data,
unsigned int color_count,
unsigned int seq
)
{
unsigned char buf[65];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(buf, 0x00, sizeof(buf));
/*-----------------------------------------------------*\
| Set up Direct Initialization packet |
\*-----------------------------------------------------*/
buf[0x00] = 0x44;
buf[0x01] = 0x02;
buf[0x02] = (unsigned char)seq;
buf[0x03] = 0x00;
/*-----------------------------------------------------*\
| The maximum number of colors per packet is 20 |
\*-----------------------------------------------------*/
if(color_count > 20)
{
color_count = 20;
}
/*-----------------------------------------------------*\
| Copy in color data |
\*-----------------------------------------------------*/
for(unsigned int color_idx = 0; color_idx < color_count; color_idx++)
{
buf[4 + (color_idx * 3)] = RGBGetRValue(color_data[color_idx]);
buf[4 + (color_idx * 3) + 1] = RGBGetGValue(color_data[color_idx]);
buf[4 + (color_idx * 3) + 2] = RGBGetBValue(color_data[color_idx]);
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev, buf, 65);
}
@@ -0,0 +1,43 @@
/*---------------------------------------------------------*\
| HyperXOrigins2_65Controller.h |
| |
| Driver for HyperX Origins 2 65 keyboard |
| |
| Ricardo Amorim 28 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
class HyperXOrigins2_65Controller
{
public:
HyperXOrigins2_65Controller(hid_device* dev_handle, const char* path, std::string dev_name);
~HyperXOrigins2_65Controller();
std::string GetDeviceLocation();
std::string GetNameString();
std::string GetSerialString();
void SetLEDsDirect(std::vector<RGBColor> colors);
private:
hid_device* dev;
std::string location;
std::string name;
void SendDirectInitialization();
void SendDirectColorPacket
(
RGBColor* color_data,
unsigned int color_count,
unsigned int seq
);
};
@@ -0,0 +1,257 @@
/*---------------------------------------------------------*\
| RGBController_HyperXOrigins2_65.cpp |
| |
| RGBController for HyperX Origins 2 65 keyboard |
| |
| Ricardo Amorim 28 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBControllerKeyNames.h"
#include "RGBController_HyperXOrigins2_65.h"
using namespace std::chrono_literals;
//0xFFFFFFFF indicates an unused entry in matrix
#define NA 0xFFFFFFFF
static unsigned int matrix_map[5][15] =
{ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 },
{ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, NA, 29 },
{ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44 },
{ 45, 61, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59 },
{ 60, 62, 63, 65, NA, NA, 66, NA, NA, 68, 69, 70, 71, 72, 73 } };
static const char* zone_names[] =
{
ZONE_EN_KEYBOARD,
};
static zone_type zone_types[] =
{
ZONE_TYPE_MATRIX,
};
static const unsigned int zone_sizes[] =
{
74,
};
static const char *led_names[] =
{
KEY_EN_ESCAPE,
KEY_EN_1,
KEY_EN_2,
KEY_EN_3,
KEY_EN_4,
KEY_EN_5,
KEY_EN_6,
KEY_EN_7,
KEY_EN_8,
KEY_EN_9,
KEY_EN_0,
KEY_EN_MINUS,
KEY_EN_EQUALS,
KEY_EN_BACKSPACE,
KEY_EN_DELETE,
KEY_EN_TAB,
KEY_EN_Q,
KEY_EN_W,
KEY_EN_E,
KEY_EN_R,
KEY_EN_T,
KEY_EN_Y,
KEY_EN_U,
KEY_EN_I,
KEY_EN_O,
KEY_EN_P,
KEY_EN_LEFT_BRACKET,
KEY_EN_RIGHT_BRACKET,
KEY_EN_UNUSED,
KEY_EN_HOME,
KEY_EN_CAPS_LOCK,
KEY_EN_A,
KEY_EN_S,
KEY_EN_D,
KEY_EN_F,
KEY_EN_G,
KEY_EN_H,
KEY_EN_J,
KEY_EN_K,
KEY_EN_L,
KEY_EN_SEMICOLON,
KEY_EN_QUOTE,
KEY_EN_POUND,
KEY_EN_ISO_ENTER,
KEY_EN_PAGE_UP,
KEY_EN_LEFT_SHIFT,
KEY_EN_Z,
KEY_EN_X,
KEY_EN_C,
KEY_EN_V,
KEY_EN_B,
KEY_EN_N,
KEY_EN_M,
KEY_EN_COMMA,
KEY_EN_PERIOD,
KEY_EN_FORWARD_SLASH,
KEY_EN_RIGHT_SHIFT,
KEY_EN_UP_ARROW,
KEY_EN_UNUSED,
KEY_EN_PAGE_DOWN,
KEY_EN_LEFT_CONTROL,
KEY_EN_ISO_BACK_SLASH,
KEY_EN_LEFT_WINDOWS,
KEY_EN_LEFT_ALT,
KEY_EN_UNUSED,
"Left Space",
KEY_EN_SPACE,
"Right Space",
KEY_EN_UNUSED,
KEY_EN_RIGHT_ALT,
KEY_EN_RIGHT_FUNCTION,
KEY_EN_LEFT_ARROW,
KEY_EN_DOWN_ARROW,
KEY_EN_RIGHT_ARROW
};
/**------------------------------------------------------------------*\
@name HyperX Origins 2 65
@category Keyboard
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectHyperXOrigins2_65
@comment
\*-------------------------------------------------------------------*/
RGBController_HyperXOrigins2_65::RGBController_HyperXOrigins2_65(HyperXOrigins2_65Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "HyperX";
type = DEVICE_TYPE_KEYBOARD;
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFFFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
keepalive_thread_run = 1;
keepalive_thread = new std::thread(&RGBController_HyperXOrigins2_65::KeepaliveThread, this);
}
RGBController_HyperXOrigins2_65::~RGBController_HyperXOrigins2_65()
{
keepalive_thread_run = 0;
keepalive_thread->join();
delete keepalive_thread;
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
delete zones[zone_index].matrix_map;
}
}
delete controller;
}
void RGBController_HyperXOrigins2_65::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
unsigned int total_led_count = 0;
for(unsigned int zone_idx = 0; zone_idx < 1; zone_idx++)
{
zone new_zone;
new_zone.name = zone_names[zone_idx];
new_zone.type = zone_types[zone_idx];
new_zone.leds_min = zone_sizes[zone_idx];
new_zone.leds_max = zone_sizes[zone_idx];
new_zone.leds_count = zone_sizes[zone_idx];
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map = new matrix_map_type;
new_zone.matrix_map->height = 5;
new_zone.matrix_map->width = 15;
new_zone.matrix_map->map = (unsigned int *)&matrix_map;
}
else
{
new_zone.matrix_map = NULL;
}
zones.push_back(new_zone);
total_led_count += zone_sizes[zone_idx];
}
for(unsigned int led_idx = 0; led_idx < total_led_count; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx];
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_HyperXOrigins2_65::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_HyperXOrigins2_65::DeviceUpdateLEDs()
{
controller->SetLEDsDirect(colors);
}
void RGBController_HyperXOrigins2_65::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_HyperXOrigins2_65::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_HyperXOrigins2_65::DeviceUpdateMode()
{
}
void RGBController_HyperXOrigins2_65::KeepaliveThread()
{
while(keepalive_thread_run.load())
{
if(active_mode == 0)
{
if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(50))
{
UpdateLEDs();
}
}
std::this_thread::sleep_for(10ms);;
}
}
@@ -1,43 +1,42 @@
/*---------------------------------------------------------*\
| RGBController_CorsairWireless.h |
| |
| RGBController for Corsair wireless keyboard |
| |
| Adam Honse (CalcProgrammer1) 08 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "CorsairWirelessController.h"
class RGBController_CorsairWireless : public RGBController
{
public:
RGBController_CorsairWireless(CorsairWirelessController* controller_ptr);
~RGBController_CorsairWireless();
int physical_layout;
int logical_layout;
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void KeepaliveThread();
private:
CorsairWirelessController* controller;
std::thread* keepalive_thread;
std::atomic<bool> keepalive_thread_run;
std::chrono::time_point<std::chrono::steady_clock> last_update_time;
};
/*---------------------------------------------------------*\
| RGBController_HyperXOrigins2_65.h |
| |
| RGBController for HyperX Origins 2 65 keyboard |
| |
| Ricardo Amorim 28 Mar 2026 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <chrono>
#include "RGBController.h"
#include "HyperXOrigins2_65Controller.h"
class RGBController_HyperXOrigins2_65 : public RGBController
{
public:
RGBController_HyperXOrigins2_65(HyperXOrigins2_65Controller* controller_ptr);
~RGBController_HyperXOrigins2_65();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void KeepaliveThread();
private:
HyperXOrigins2_65Controller* controller;
std::thread* keepalive_thread;
std::atomic<bool> keepalive_thread_run;
std::chrono::time_point<std::chrono::steady_clock> last_update_time;
};
@@ -118,7 +118,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 16; led_idx++)
{
led fan_1_led;
fan_1_led.name = "Fan 1 LED";
fan_1_led.name = "Fan 1 LED " + std::to_string(led_idx + 1);
fan_1_led.value = fan_1_leds[led_idx];
leds.push_back(fan_1_led);
}
@@ -126,7 +126,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 16; led_idx++)
{
led fan_2_led;
fan_2_led.name = "Fan 2 LED";
fan_2_led.name = "Fan 2 LED " + std::to_string(led_idx + 1);
fan_2_led.value = fan_2_leds[led_idx];
leds.push_back(fan_2_led);
}
@@ -134,7 +134,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 8; led_idx++)
{
led back_led;
back_led.name = "Back LED";
back_led.name = "Back LED " + std::to_string(led_idx + 1);
back_led.value = back_leds[led_idx];
leds.push_back(back_led);
}
@@ -142,7 +142,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 50; led_idx++)
{
led ring_led;
ring_led.name = "Ring LED";
ring_led.name = "Ring LED " + std::to_string(led_idx + 1);
ring_led.value = ring_leds[led_idx];
leds.push_back(ring_led);
}
@@ -150,7 +150,7 @@ void RGBController_IntelArcA770LE::SetupZones()
for(unsigned int led_idx = 0; led_idx < 1; led_idx++)
{
led logo_led;
logo_led.name = "Logo LED";
logo_led.name = "Logo LED " + std::to_string(led_idx + 1);
logo_led.value = logo_leds[led_idx];
leds.push_back(logo_led);
}
@@ -38,6 +38,7 @@
#define LEGION_7GEN9 0xC997
#define LEGION_7GEN9_H 0xC998
#define LEGION_7GEN10 0xC197
#define LEGION_5GEN10 0xC195
enum LENOVO_KEYBOARD
{
@@ -67,3 +67,4 @@ REGISTER_HID_DETECTOR_PU("Lenovo Legion 7S Gen 8", DetectLenovoLegionUSBControl
REGISTER_HID_DETECTOR_PU("Lenovo Legion 7 Gen 9", DetectLenovoLegionUSBControllersGen7And8, ITE_VID, LEGION_7GEN9, LENOVO_PAGE, LENOVO_USAGE);
REGISTER_HID_DETECTOR_PU("Lenovo Legion 7 Gen 9", DetectLenovoLegionUSBControllersGen7And8, ITE_VID, LEGION_7GEN9_H, LENOVO_PAGE, LENOVO_USAGE);
REGISTER_HID_DETECTOR_PU("Lenovo Legion 7 Gen 10", DetectLenovoLegionUSBControllersGen7And8, ITE_VID, LEGION_7GEN10, LENOVO_PAGE, LENOVO_USAGE);
REGISTER_HID_DETECTOR_PU("Lenovo Legion 5 Gen 10", DetectLenovoLegionUSBControllersGen7And8, ITE_VID, LEGION_5GEN10, LENOVO_PAGE, LENOVO_USAGE);
@@ -17,7 +17,7 @@ using namespace std;
static void SetGen10PayloadLength(uint16_t pid, uint8_t* buffer, uint16_t payload_length)
{
if(pid != LEGION_7GEN10)
if(pid != LEGION_7GEN10 && pid != LEGION_5GEN10)
{
return;
}
@@ -26,6 +26,11 @@ static void SetGen10PayloadLength(uint16_t pid, uint8_t* buffer, uint16_t payloa
buffer[3] = (payload_length >> 8) & 0xFF;
}
static bool UsesGen10PacketFormat(uint16_t pid)
{
return pid == LEGION_7GEN10 || pid == LEGION_5GEN10;
}
LenovoGen7And8USBController::LenovoGen7And8USBController(hid_device* dev_handle, const char* path, uint16_t in_pid, std::string dev_name)
{
dev = dev_handle;
@@ -114,7 +119,7 @@ void LenovoGen7And8USBController::setLedsByGroup(uint8_t profile_id, vector<led_
i+= led_count * sizeof(uint16_t);
}
if(pid == LEGION_7GEN10)
if(UsesGen10PacketFormat(pid))
{
SetGen10PayloadLength(pid, buffer, static_cast<uint16_t>(i - 4));
}
@@ -162,7 +167,7 @@ void LenovoGen7And8USBController::setLedsDirectOff(uint8_t profile_id)
void LenovoGen7And8USBController::setLedsDirect(std::vector<led> &leds, std::vector<RGBColor> &colors)
{
if(pid == LEGION_7GEN10)
if(UsesGen10PacketFormat(pid))
{
/*---------------------------------------------------------*\
| Gen10 uses 0x07/A1 direct updates, with payload length |
@@ -13,6 +13,78 @@
using namespace std;
static bool UsesGen10PacketFormat(uint16_t pid)
{
return pid == LEGION_7GEN10 || pid == LEGION_5GEN10;
}
static bool Is24ZoneDevice(uint16_t pid)
{
return pid == LEGION_5GEN10;
}
static bool IsKeyboardOnlyDevice(uint16_t pid)
{
return pid == LEGION_5GEN10;
}
static const lenovo_led legion_5gen10_24zone_leds[] =
{
{0x01, "Zone R1C1"},
{0x02, "Zone R1C2"},
{0x03, "Zone R1C3"},
{0x04, "Zone R1C4"},
{0x05, "Zone R1C5"},
{0x06, "Zone R1C6"},
{0x07, "Zone R2C1"},
{0x08, "Zone R2C2"},
{0x09, "Zone R2C3"},
{0x0A, "Zone R2C4"},
{0x0B, "Zone R2C5"},
{0x0C, "Zone R2C6"},
{0x0D, "Zone R3C1"},
{0x0E, "Zone R3C2"},
{0x0F, "Zone R3C3"},
{0x10, "Zone R3C4"},
{0x11, "Zone R3C5"},
{0x12, "Zone R3C6"},
{0x13, "Zone R4C1"},
{0x14, "Zone R4C2"},
{0x15, "Zone R4C3"},
{0x16, "Zone R4C4"},
{0x17, "Zone R4C5"},
{0x18, "Zone R4C6"},
};
static const unsigned int legion_5gen10_24zone_matrix_map[] =
{
0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23,
};
static const lenovo_zone legion_5gen10_kbd_24zone =
{
"Keyboard (24-zone)",
ZONE_TYPE_MATRIX,
0,
4,
6,
legion_5gen10_24zone_matrix_map,
legion_5gen10_24zone_leds,
0,
23,
};
static const RGBColor legion_5gen10_zone_visualization_colors[24] =
{
0xFF0000, 0x00FFFF, 0xFFFF00, 0x0000FF, 0x00FF00, 0xFF00FF,
0xFF7F00, 0x007FFF, 0x7FFF00, 0x7F00FF, 0xFF007F, 0x00FF7F,
0xFFD700, 0x0055FF, 0x55FF00, 0xFF0055, 0x00BFFF, 0xBF00FF,
0xFF3B00, 0x00FF3B, 0x3B00FF, 0xFF1493, 0x14FF93, 0x9314FF,
};
LenovoRGBController_Gen7_8::LenovoRGBController_Gen7_8(LenovoGen7And8USBController* controller_ptr)
{
controller = controller_ptr;
@@ -139,72 +211,79 @@ LenovoRGBController_Gen7_8::LenovoRGBController_Gen7_8(LenovoGen7And8USBControll
Smooth.brightness = brightness;
modes.push_back(Smooth);
mode Rain;
Rain.name = "Rain";
Rain.value = LENOVO_LEGION_GEN7_8_MODE_RAIN;
Rain.flags = MODE_FLAG_HAS_SPEED |
MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
MODE_FLAG_HAS_RANDOM_COLOR |
MODE_FLAG_HAS_BRIGHTNESS |
MODE_FLAG_AUTOMATIC_SAVE;
Rain.speed_min = 0x01;
Rain.speed_max = 0x03;
Rain.speed = 2;
Rain.colors_min = 1;
Rain.colors_max = 4;
Rain.colors.resize(4);
Rain.colors[0] = 0xFFF500;
Rain.color_mode = MODE_COLORS_RANDOM;
Rain.brightness_min = 0;
Rain.brightness_max = 9;
Rain.brightness = brightness;
modes.push_back(Rain);
if(!Is24ZoneDevice(controller->getPid()))
{
mode Rain;
Rain.name = "Rain";
Rain.value = LENOVO_LEGION_GEN7_8_MODE_RAIN;
Rain.flags = MODE_FLAG_HAS_SPEED |
MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
MODE_FLAG_HAS_RANDOM_COLOR |
MODE_FLAG_HAS_BRIGHTNESS |
MODE_FLAG_AUTOMATIC_SAVE;
Rain.speed_min = 0x01;
Rain.speed_max = 0x03;
Rain.speed = 2;
Rain.colors_min = 1;
Rain.colors_max = 4;
Rain.colors.resize(4);
Rain.colors[0] = 0xFFF500;
Rain.color_mode = MODE_COLORS_RANDOM;
Rain.brightness_min = 0;
Rain.brightness_max = 9;
Rain.brightness = brightness;
modes.push_back(Rain);
}
mode Ripple;
Ripple.name = "Ripple";
Ripple.value = LENOVO_LEGION_GEN7_8_MODE_RIPPLE;
Ripple.flags = MODE_FLAG_HAS_SPEED |
MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
MODE_FLAG_HAS_RANDOM_COLOR |
MODE_FLAG_HAS_BRIGHTNESS |
MODE_FLAG_AUTOMATIC_SAVE;
Ripple.speed_min = 0x01;
Ripple.speed_max = 0x03;
Ripple.speed = 2;
Ripple.colors_min = 1;
Ripple.colors_max = 4;
Ripple.colors.resize(4);
Ripple.colors[0] = 0xFFF500;
Ripple.color_mode = MODE_COLORS_RANDOM;
Ripple.brightness_min = 0;
Ripple.brightness_max = 9;
Ripple.brightness = brightness;
modes.push_back(Ripple);
if(!IsKeyboardOnlyDevice(controller->getPid()))
{
mode Ripple;
Ripple.name = "Ripple";
Ripple.value = LENOVO_LEGION_GEN7_8_MODE_RIPPLE;
Ripple.flags = MODE_FLAG_HAS_SPEED |
MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
MODE_FLAG_HAS_RANDOM_COLOR |
MODE_FLAG_HAS_BRIGHTNESS |
MODE_FLAG_AUTOMATIC_SAVE;
Ripple.speed_min = 0x01;
Ripple.speed_max = 0x03;
Ripple.speed = 2;
Ripple.colors_min = 1;
Ripple.colors_max = 4;
Ripple.colors.resize(4);
Ripple.colors[0] = 0xFFF500;
Ripple.color_mode = MODE_COLORS_RANDOM;
Ripple.brightness_min = 0;
Ripple.brightness_max = 9;
Ripple.brightness = brightness;
modes.push_back(Ripple);
mode AudioBounce;
AudioBounce.name = "Audio Bounce Lighting";
AudioBounce.value = LENOVO_LEGION_GEN7_8_MODE_AUDIO_BOUNCE;
AudioBounce.flags = MODE_FLAG_HAS_BRIGHTNESS;
AudioBounce.color_mode = MODE_COLORS_NONE;
AudioBounce.brightness_min = 0;
AudioBounce.brightness_max = 9;
AudioBounce.brightness = brightness;
modes.push_back(AudioBounce);
mode AudioBounce;
AudioBounce.name = "Audio Bounce Lighting";
AudioBounce.value = LENOVO_LEGION_GEN7_8_MODE_AUDIO_BOUNCE;
AudioBounce.flags = MODE_FLAG_HAS_BRIGHTNESS;
AudioBounce.color_mode = MODE_COLORS_NONE;
AudioBounce.brightness_min = 0;
AudioBounce.brightness_max = 9;
AudioBounce.brightness = brightness;
modes.push_back(AudioBounce);
mode AudioRipple;
AudioRipple.name = "Audio Ripple Lighting";
AudioRipple.value = LENOVO_LEGION_GEN7_8_MODE_AUDIO_RIPPLE;
AudioRipple.flags = MODE_FLAG_HAS_BRIGHTNESS;
AudioRipple.color_mode = MODE_COLORS_NONE;
AudioRipple.brightness_min = 0;
AudioRipple.brightness_max = 9;
AudioRipple.brightness = brightness;
modes.push_back(AudioRipple);
mode AudioRipple;
AudioRipple.name = "Audio Ripple Lighting";
AudioRipple.value = LENOVO_LEGION_GEN7_8_MODE_AUDIO_RIPPLE;
AudioRipple.flags = MODE_FLAG_HAS_BRIGHTNESS;
AudioRipple.color_mode = MODE_COLORS_NONE;
AudioRipple.brightness_min = 0;
AudioRipple.brightness_max = 9;
AudioRipple.brightness = brightness;
modes.push_back(AudioRipple);
}
mode Static;
Static.name = "Static";
Static.value = LENOVO_LEGION_GEN7_8_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_PER_LED_COLOR |
MODE_FLAG_HAS_RANDOM_COLOR |
MODE_FLAG_HAS_BRIGHTNESS |
MODE_FLAG_AUTOMATIC_SAVE;
Static.color_mode = MODE_COLORS_PER_LED;
@@ -213,37 +292,43 @@ LenovoRGBController_Gen7_8::LenovoRGBController_Gen7_8(LenovoGen7And8USBControll
Static.brightness = brightness;
modes.push_back(Static);
mode Type;
Type.name = "Type Lighting";
Type.value = LENOVO_LEGION_GEN7_8_MODE_TYPE;
Type.flags = MODE_FLAG_HAS_SPEED |
MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
MODE_FLAG_HAS_RANDOM_COLOR |
MODE_FLAG_HAS_BRIGHTNESS |
MODE_FLAG_AUTOMATIC_SAVE;
Type.speed_min = 0x01;
Type.speed_max = 0x03;
Type.speed = 2;
Type.colors_min = 1;
Type.colors_max = 4;
Type.colors.resize(4);
Type.colors[0] = 0xFFF500;
Type.color_mode = MODE_COLORS_RANDOM;
Type.brightness_min = 0;
Type.brightness_max = 9;
Type.brightness = brightness;
modes.push_back(Type);
if(!Is24ZoneDevice(controller->getPid()))
{
mode Type;
Type.name = "Type Lighting";
Type.value = LENOVO_LEGION_GEN7_8_MODE_TYPE;
Type.flags = MODE_FLAG_HAS_SPEED |
MODE_FLAG_HAS_MODE_SPECIFIC_COLOR |
MODE_FLAG_HAS_RANDOM_COLOR |
MODE_FLAG_HAS_BRIGHTNESS |
MODE_FLAG_AUTOMATIC_SAVE;
Type.speed_min = 0x01;
Type.speed_max = 0x03;
Type.speed = 2;
Type.colors_min = 1;
Type.colors_max = 4;
Type.colors.resize(4);
Type.colors[0] = 0xFFF500;
Type.color_mode = MODE_COLORS_RANDOM;
Type.brightness_min = 0;
Type.brightness_max = 9;
Type.brightness = brightness;
modes.push_back(Type);
}
mode Direct;
Direct.name = "Direct";
Direct.value = LENOVO_LEGION_GEN7_8_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 = 9;
Direct.brightness = brightness;
modes.push_back(Direct);
if(!IsKeyboardOnlyDevice(controller->getPid()))
{
mode Direct;
Direct.name = "Direct";
Direct.value = LENOVO_LEGION_GEN7_8_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 = 9;
Direct.brightness = brightness;
modes.push_back(Direct);
}
name = controller->getName();
type = DEVICE_TYPE_KEYBOARD;
@@ -275,6 +360,10 @@ LenovoRGBController_Gen7_8::LenovoRGBController_Gen7_8(LenovoGen7And8USBControll
case LEGION_7GEN10:
description = "Lenovo Legion 7 Generation 10";
break;
case LEGION_5GEN10:
description = "Lenovo Legion 5 Gen 10";
break;
}
brightness = controller->getCurrentBrightness();
@@ -310,8 +399,19 @@ LenovoRGBController_Gen7_8::~LenovoRGBController_Gen7_8()
void LenovoRGBController_Gen7_8::SetupZones()
{
vector<lenovo_zone> lenovo_zones;
lenovo_zones.push_back(legion7_gen7and8_kbd_ansi);
lenovo_zones.push_back(legion7_gen7and8_neon);
if(Is24ZoneDevice(controller->getPid()))
{
lenovo_zones.push_back(legion_5gen10_kbd_24zone);
}
else
{
lenovo_zones.push_back(legion7_gen7and8_kbd_ansi);
}
if(!IsKeyboardOnlyDevice(controller->getPid()))
{
lenovo_zones.push_back(legion7_gen7and8_neon);
}
if (controller->getPid() == LEGION_7GEN7)
{
@@ -422,7 +522,7 @@ void LenovoRGBController_Gen7_8::DeviceUpdateMode()
{
controller->setLedsDirectOn(profile_id);
direct_enabled = true;
if(controller->getPid() != LEGION_7GEN10)
if(!UsesGen10PacketFormat(controller->getPid()))
{
controller->setLedsByGroup(profile_id, GetLedGroups());
}
@@ -446,7 +546,7 @@ void LenovoRGBController_Gen7_8::DeviceUpdateLEDs()
{
if(modes[active_mode].value == LENOVO_LEGION_GEN7_8_MODE_DIRECT)
{
if(controller->getPid() == LEGION_7GEN10)
if(UsesGen10PacketFormat(controller->getPid()))
{
/*---------------------------------------------*\
| Gen10 may ignore A1 updates unless D0 is |
@@ -570,6 +670,29 @@ void LenovoRGBController_Gen7_8::ReadDeviceSettings()
std::vector<led_group> LenovoRGBController_Gen7_8::GetLedGroups()
{
if(Is24ZoneDevice(controller->getPid()) &&
modes[active_mode].value == LENOVO_LEGION_GEN7_8_MODE_STATIC &&
modes[active_mode].color_mode == MODE_COLORS_RANDOM)
{
vector<led_group> led_groups;
led_groups.reserve(leds.size());
for(size_t i = 0; i < leds.size(); i++)
{
led_group group;
group.mode = modes[active_mode].value;
group.speed = modes[active_mode].speed;
group.spin = 0x00;
group.direction = 0x00;
group.color_mode = 0x02;
group.colors.push_back(legion_5gen10_zone_visualization_colors[i % 24]);
group.leds.push_back(leds[i].value);
led_groups.push_back(group);
}
return led_groups;
}
std::unordered_map<RGBColor, vector<uint16_t>> led_map;
if(modes[active_mode].color_mode == MODE_COLORS_PER_LED &&
@@ -235,7 +235,7 @@ void LianLiUniHubSLV2Controller::SetChannelMode(unsigned char channel, const mod
void LianLiUniHubSLV2Controller::SendStartAction(unsigned char channel, unsigned int num_fans)
{
unsigned char usb_buf[65];
unsigned char usb_buf[353];
/*-----------------------------------------------------*\
| Zero out buffer |
@@ -291,7 +291,7 @@ void LianLiUniHubSLV2Controller::SendColorData(unsigned char channel, unsigned
void LianLiUniHubSLV2Controller::SendCommitAction(unsigned char channel, unsigned char effect, unsigned char speed, unsigned int direction, unsigned int brightness)
{
unsigned char usb_buf[65];
unsigned char usb_buf[353];
/*-----------------------------------------------------*\
| Zero out buffer |
@@ -10,6 +10,7 @@
#include <thread>
#include <hidapi.h>
#include "Detector.h"
#include "ResourceManager.h"
#include "LogManager.h"
#include "LogitechProtocolCommon.h"
#include "LogitechG203LController.h"
@@ -40,6 +41,8 @@
#include "RGBController_LogitechLightspeed.h"
#include "RGBController_LogitechGPowerPlay.h" // Linux-only
#include "RGBController_LogitechX56.h"
#include "LogitechHIDPP20Controller.h"
#include "RGBController_LogitechHIDPP20.h"
using namespace std::chrono_literals;
@@ -84,6 +87,9 @@ using namespace std::chrono_literals;
#define LOGITECH_G502_HERO_PID 0xC08B
#define LOGITECH_G502_LIGHTSPEED_PID 0xC08D
#define LOGITECH_G502_X_PLUS_PID 0xC095
#define LOGITECH_G515_LS_TKL_PID 0xC355
#define LOGITECH_G522_LIGHTSPEED_USB_PID 0x0B19
#define LOGITECH_G522_LIGHTSPEED_DONGLE_PID 0x0B18
#define LOGITECH_G600_PID 0xC24A
#define LOGITECH_G703_LIGHTSPEED_PID 0xC087
#define LOGITECH_G703_HERO_LIGHTSPEED_PID 0xC090
@@ -135,6 +141,8 @@ using namespace std::chrono_literals;
#define LOGITECH_G903_LIGHTSPEED_VIRTUAL_HERO_PID 0x4087
#define LOGITECH_G_PRO_WIRELESS_VIRTUAL_PID 0x4079
#define LOGITECH_POWERPLAY_MAT_VIRTUAL_PID 0x405F
#define LOGITECH_G502_X_PLUS_LIGHTSPEED_VIRTUAL_PID 0x4099
#define LOGITECH_G515_LS_TKL_LIGHTSPEED_VIRTUAL_PID 0x40B4
/*-----------------------------------------------------*\
| Joystick product IDs |
@@ -358,6 +366,106 @@ void DetectLogitechKeyboardG915(hid_device_info* info, const std::string& name)
}
}
static bool ProbeG915ReceiverName(hid_device* dev, std::string& out_name)
{
/*---------------------------------------------------------*\n | HID++ short message name probe. |
| Request: 10 01 03 0E 00 00 00 (get name length) |
| Request: 10 01 03 1E 00 00 00 (get name string) |
| Response: 11 01 03 1E <name bytes...> |
| Verified against G915 TKL (PID 0xC547) which returns |
| "G915 TKL LIGHTSP" (truncated, full: G915 TKL LIGHTSPEED) |
\*---------------------------------------------------------*/
const unsigned char req_len[7] = { 0x10, 0x01, 0x03, 0x0E, 0x00, 0x00, 0x00 };
const unsigned char req_name[7] = { 0x10, 0x01, 0x03, 0x1E, 0x00, 0x00, 0x00 };
unsigned char resp[64] = { 0 };
hid_write(dev, req_len, sizeof(req_len));
hid_read_timeout(dev, resp, sizeof(resp), 100);
hid_write(dev, req_name, sizeof(req_name));
for(int attempt = 0; attempt < 3; attempt++)
{
int rd = hid_read_timeout(dev, resp, sizeof(resp), 200);
if(rd < 8)
{
continue;
}
if(resp[0] == 0x11 && resp[1] == 0x01 && resp[2] == 0x03 && resp[3] == 0x1E)
{
std::string name_str;
for(int i = 4; i < rd; i++)
{
if(resp[i] == 0x00)
{
break;
}
name_str.push_back(static_cast<char>(resp[i]));
}
out_name = name_str;
return true;
}
}
return false;
}
void DetectLogitechKeyboardG915Receiver2(hid_device_info* info, const std::string& name)
{
/*---------------------------------------------------------*\
| PID 0xC547 is shared by multiple Logitech keyboards. |
| Use a HID++ name probe to identify the exact device and |
| route to the correct controller. |
| |
| Known devices behind this PID: |
| "G915 TKL LIGHTSP..." -> G915 TKL (is_tkl = true) |
| "G915 LIGHTSP..." -> G915 full-size (is_tkl = false)|
| "G515..." -> G515 (not handled here) |
\*---------------------------------------------------------*/
hid_device* dev = hid_open_path(info->path);
if(!dev)
{
return;
}
std::string probed_name;
bool ok = ProbeG915ReceiverName(dev, probed_name);
if(!ok)
{
LOG_DEBUG("[LogitechControllerDetect] 0xC547 name probe failed, skipping device");
hid_close(dev);
return;
}
LOG_DEBUG("[LogitechControllerDetect] 0xC547 probe returned name=\"%s\"", probed_name.c_str());
/*---------------------------------------------------------*\
| Route based on probed name. Check for TKL before full |
| G915 since both contain "G915". |
\*---------------------------------------------------------*/
if(probed_name.find("G915 TKL") != std::string::npos)
{
LogitechG915Controller* controller = new LogitechG915Controller(dev, false, name);
RGBController_LogitechG915* rgb_controller = new RGBController_LogitechG915(controller, true);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else if(probed_name.find("G915") != std::string::npos)
{
LogitechG915Controller* controller = new LogitechG915Controller(dev, false, name);
RGBController_LogitechG915* rgb_controller = new RGBController_LogitechG915(controller, false);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else
{
/*-----------------------------------------------------*\
| Unknown device (e.g. G515 or future hardware). |
| Close and leave it for another detector to claim. |
\*-----------------------------------------------------*/
LOG_DEBUG("[LogitechControllerDetect] 0xC547 unrecognised device name \"%s\", skipping",
probed_name.c_str());
hid_close(dev);
}
}
void DetectLogitechKeyboardG915Wired(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
@@ -650,6 +758,220 @@ void DetectLogitechX56(hid_device_info* info, const std::string& name)
}
}
/*------------------------------------------------------------------------------*\
| Unified HID++ 2.0 Detection |
| Probes IRoot (feature 0x0000) to determine if the device speaks HID++ 2.0. |
| If it does and has RGB features, the unified controller handles it. |
| If not, the device is released for legacy controllers. |
\*------------------------------------------------------------------------------*/
void DetectLogitechHIDPP20(hid_device_info* info, const std::string& /*name*/)
{
hid_device* dev = hid_open_path(info->path);
if(!dev)
{
return;
}
LogitechHIDPP20Controller* controller = new LogitechHIDPP20Controller(
dev, info->path, LOGITECH_DEFAULT_DEVICE_INDEX, false, nullptr,
info->usage_page);
if(controller->Probe())
{
controller->Initialize();
const HIDPP20DeviceCapabilities& caps = controller->GetCapabilities();
if(caps.has_zone_effects || caps.has_perkey)
{
/*-------------------------------------------------*\
| Device has RGB features — create and register |
| RGBController for the UI. |
\*-------------------------------------------------*/
RGBController_LogitechHIDPP20* rgb_controller = new RGBController_LogitechHIDPP20(controller);
LOG_INFO("[%s] Registering RGB controller", caps.device_name.c_str());
ResourceManager::get()->RegisterRGBController(rgb_controller);
/*--------------------------------------------------*\
| Start reader + power threads immediately so we |
| detect connection events and handle power mgmt |
| from the start — not deferred to DeviceUpdateMode. |
\*--------------------------------------------------*/
if(caps.has_power_mgmt || caps.idx_wireless_status != 0)
{
controller->StartPowerManager();
if(!caps.has_power_mgmt && caps.idx_wireless_status != 0)
{
controller->StartEventWatcher();
}
}
}
else if(controller->HasBridge())
{
/*--------------------------------------------------*\
| Centurion dongle with no sub-device — keep the |
| controller alive and start reader thread to watch |
| for sub-device connection events. |
\*--------------------------------------------------*/
LOG_INFO("[%s] Dongle registered, watching for sub-device",
caps.device_name.c_str());
controller->SetRegisterCallback([](RGBController* rgb)
{
ResourceManager::get()->RegisterRGBController(rgb);
});
controller->StartEventWatcher();
}
else
{
/*--------------------------------------------------*\
| Device probed successfully but has no RGB and no |
| bridge — nothing to do (e.g., headset without RGB) |
\*--------------------------------------------------*/
LOG_INFO("[%s] No RGB features, skipping", caps.device_name.c_str());
delete controller;
}
}
else
{
/*--------------------------------------------------*\
| Probe failed. Could be an offline paired device, |
| a stale pairing slot, or a receiver itself. |
| |
| Only skip if the name explicitly says "Receiver". |
| Everything else gets a watcher — devices can come |
| back at any time (power cycle, dongle swap, etc.) |
\*--------------------------------------------------*/
std::string hid_name;
if(info->product_string)
{
std::wstring ws(info->product_string);
hid_name = std::string(ws.begin(), ws.end());
}
if(hid_name.find("Receiver") != std::string::npos ||
hid_name.find("receiver") != std::string::npos)
{
delete controller;
}
else
{
LOG_INFO("[HID++2.0 %s] Probe failed — watching for device (name='%s')",
info->path, hid_name.c_str());
controller->SetRegisterCallback([](RGBController* rgb)
{
ResourceManager::get()->RegisterRGBController(rgb);
});
controller->StartProbeWatcher();
}
}
}
#if defined(_WIN32) || defined(__APPLE__)
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Unified HID++ 2.0 Lightspeed Receiver Detection (Windows / macOS) |
| |
| On Linux, hid-logitech-dj splits receiver traffic into per-slot virtual child hidraw nodes with their own 0x40XX PIDs, so Linux detection can |
| use DetectLogitechHIDPP20 directly against the virtual PIDs. Windows and macOS have no DJ driver — the receiver appears as a single HID device |
| and we must probe each paired slot by hand, addressing it via the HID++ device_index header byte. |
| |
| Iterates device indices 0x01..0x06. c547 is dual-pair, but the loop covers Unifying-style receivers and any future wider-pair variants. Each |
| responding slot gets its own hid_device handle and a shared std::mutex so sibling slots serialize HID writes — matching the pattern in the |
| legacy LogitechLightspeedController (see CreateLogitechLightspeedDevice around line 860). |
| |
| TODO (untested on Windows): runtime reader-thread coordination. Each slot controller starts its own reader thread; on a shared receiver both |
| threads will see both slots' incoming packets. The reader needs to drop frames whose device_index doesn't match its own, or dispatch across |
| sibling controllers. Safe during probe (mutex serializes writes, reads are direct); becomes an issue post-StartPowerManager. |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
void DetectLogitechHIDPP20LightspeedReceiver(hid_device_info* info, const std::string& /*name*/)
{
std::shared_ptr<std::mutex> receiver_mutex = std::make_shared<std::mutex>();
for(uint8_t idx = 0x01; idx <= 0x06; idx++)
{
hid_device* dev = hid_open_path(info->path);
if(!dev)
{
continue;
}
LogitechHIDPP20Controller* controller = new LogitechHIDPP20Controller(
dev, info->path, idx, true, receiver_mutex, info->usage_page);
if(!controller->Probe())
{
/*--------------------------------------------------*\
| Slot is empty, stale, or not HID++ 2.0. Destructor |
| closes the per-slot handle we opened above. |
\*--------------------------------------------------*/
delete controller;
continue;
}
controller->Initialize();
const HIDPP20DeviceCapabilities& caps = controller->GetCapabilities();
if(caps.has_zone_effects || caps.has_perkey)
{
RGBController_LogitechHIDPP20* rgb_controller = new RGBController_LogitechHIDPP20(controller);
LOG_INFO("[%s slot=%u] Registering RGB controller", caps.device_name.c_str(), idx);
ResourceManager::get()->RegisterRGBController(rgb_controller);
if(caps.has_power_mgmt || caps.idx_wireless_status != 0)
{
controller->StartPowerManager();
if(!caps.has_power_mgmt && caps.idx_wireless_status != 0)
{
controller->StartEventWatcher();
}
}
}
else
{
LOG_INFO("[%s slot=%u] No RGB features, skipping", caps.device_name.c_str(), idx);
delete controller;
}
}
}
#endif
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Unified HID++ 2.0 Devices |
| PID-specific registrations for devices tested with the unified controller. |
| Wired paths use the device's own USB PID; wireless paths on Linux use the hid-logitech-dj virtual child PIDs (0x40XX range). |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 G502 X Plus (wired)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G502_X_PLUS_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 G515 LS TKL (wired)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G515_LS_TKL_PID, 2, 0xFF00, 2);
#ifdef __linux__
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 G502 X Plus (wireless)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G502_X_PLUS_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 G515 LS TKL (wireless)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G515_LS_TKL_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
#endif
#if defined(_WIN32) || defined(__APPLE__)
REGISTER_HID_DETECTOR_IPU("Logitech HID++ 2.0 Lightspeed Receiver (C547)", DetectLogitechHIDPP20LightspeedReceiver, LOGITECH_VID, 0xC547, 2, 0xFF00, 2);
#endif
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Centurion-transport devices (63-byte reports on usage page 0xFFA0, 0x50 addressed or 0x51 direct). |
| Centurion receivers are not DJ-style Lightspeed receivers and are not split by hid-logitech-dj — the dongle PID enumerates as a single hidraw |
| on all platforms. The controller's DiscoverTransport parses the report descriptor to pick the 0x50/0x51 variant and runs a 0x00..0xFF address |
| sweep for the 0x50 (addressed) variant, so the detector only needs VID/PID + usage page 0xFFA0. |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_P("Logitech HID++ 2.0 G522 Lightspeed (wired)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G522_LIGHTSPEED_USB_PID, 0xFFA0);
REGISTER_HID_DETECTOR_P("Logitech HID++ 2.0 G522 Lightspeed (dongle)", DetectLogitechHIDPP20, LOGITECH_VID, LOGITECH_G522_LIGHTSPEED_DONGLE_PID, 0xFFA0);
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Keyboards |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
@@ -667,7 +989,7 @@ REGISTER_HID_DETECTOR_IP ("Logitech G910 Orion Spectrum", Dete
REGISTER_HID_DETECTOR_IP ("Logitech G Pro RGB Mechanical Gaming Keyboard", DetectLogitechKeyboardGPro, LOGITECH_VID, LOGITECH_GPRO_KEYBOARD_1_PID, 1, 0xFF43);
REGISTER_HID_DETECTOR_IPU("Logitech G915 Wireless RGB Mechanical Gaming Keyboard", DetectLogitechKeyboardG915, LOGITECH_VID, LOGITECH_G915_RECEIVER_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915 Wireless RGB Mechanical Gaming Keyboard", DetectLogitechKeyboardG915, LOGITECH_VID, LOGITECH_G915_RECEIVER_2_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915 Wireless RGB Mechanical Gaming Keyboard (Receiver 2)", DetectLogitechKeyboardG915Receiver2, LOGITECH_VID, LOGITECH_G915_RECEIVER_2_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915 Wireless RGB Mechanical Gaming Keyboard (Wired)", DetectLogitechKeyboardG915Wired, LOGITECH_VID, LOGITECH_G915_WIRED_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915TKL Wireless RGB Mechanical Gaming Keyboard", DetectLogitechKeyboardG915, LOGITECH_VID, LOGITECH_G915TKL_RECEIVER_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G915TKL Wireless RGB Mechanical Gaming Keyboard (Wired)", DetectLogitechKeyboardG915Wired, LOGITECH_VID, LOGITECH_G915TKL_WIRED_PID, 2, 0xFF00, 2);
@@ -683,11 +1005,11 @@ REGISTER_HID_DETECTOR_IP ("Logitech G600 Gaming Mouse", Dete
REGISTER_HID_DETECTOR_IP ("Logitech G Pro Gaming Mouse", DetectLogitechMouseGPRO, LOGITECH_VID, LOGITECH_G_PRO_PID, 1, 0xFF00);
REGISTER_HID_DETECTOR_IP ("Logitech G Pro HERO Gaming Mouse", DetectLogitechMouseGPRO, LOGITECH_VID, LOGITECH_G_PRO_HERO_PID, 1, 0xFF00);
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Speakers |
| Speakers |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Logitech G560 Lightsync Speaker", DetectLogitechG560, LOGITECH_VID, LOGITECH_G560_PID, 2, 0xFF43, 514);
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Headsets |
| Headsets |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Logitech G933 Lightsync Headset", DetectLogitechG933, LOGITECH_VID, LOGITECH_G933_PID, 3, 0xFF43, 514);
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
@@ -890,12 +1212,12 @@ void DetectLogitechWireless(hid_device_info* info, const std::string& /*name*/)
}
}
/*-------------------------------------------------------------------------------------------------------------------------------------------------*\
| Lightspeed Devices (Linux Wireless) |
| |
| DUMMY_DEVICE_DETECTOR("Logitech G Lightspeed Receiver", DetectLogitechWireless, 0x046D, 0xC539 ) |
/*--------------------------------------------------------------------------------------------------------------------------------------------------*\
| Lightspeed Devices (Linux Wireless) |
| |
| DUMMY_DEVICE_DETECTOR("Logitech G Lightspeed Receiver", DetectLogitechWireless, 0x046D, 0xC539 ) |
| DUMMY_DEVICE_DETECTOR("Logitech Powerplay Mat Receiver", DetectLogitechWireless, 0x046D, 0xC53A ) |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
\*--------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_IPU("Logitech G403 Wireless Gaming Mouse", DetectLogitechWireless, LOGITECH_VID, LOGITECH_G403_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G502 Wireless Gaming Mouse", DetectLogitechWireless, LOGITECH_VID, LOGITECH_G502_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G703 Wireless Gaming Mouse", DetectLogitechWireless, LOGITECH_VID, LOGITECH_G703_LIGHTSPEED_VIRTUAL_PID, 2, 0xFF00, 2);
@@ -913,7 +1235,6 @@ REGISTER_HID_DETECTOR_IPU("Logitech Powerplay Mat",
| G502 changed to PU to accomodate old and new firmware. Other devices may require similar update #4627 |
\*-------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_PU("Logitech G502 Wireless Gaming Mouse (wired)", DetectLogitechWired, LOGITECH_VID, LOGITECH_G502_LIGHTSPEED_PID, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G502 X Plus (wired)", DetectLogitechWired, LOGITECH_VID, LOGITECH_G502_X_PLUS_PID, 2, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G502 Proteus Spectrum Gaming Mouse", DetectLogitechWired, LOGITECH_VID, LOGITECH_G502_PROTEUS_SPECTRUM_PID, 1, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G502 HERO Gaming Mouse", DetectLogitechWired, LOGITECH_VID, LOGITECH_G502_HERO_PID, 1, 0xFF00, 2);
REGISTER_HID_DETECTOR_IPU("Logitech G403 Prodigy Gaming Mouse", DetectLogitechWired, LOGITECH_VID, LOGITECH_G403_PID, 1, 0xFF00, 2);
@@ -146,7 +146,7 @@ void LogitechG915Controller::SendCommit()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, 4);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read_timeout(dev_handle, usb_buf, RESPONSE_LEN, LOGITECH_READ_TIMEOUT);
}
@@ -170,7 +170,7 @@ void LogitechG915Controller::BeginModeSet()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, 4);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
@@ -189,7 +189,7 @@ void LogitechG915Controller::BeginModeSet()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, 4);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -216,7 +216,7 @@ void LogitechG915Controller::InitializeModeSet()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, 7);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -240,7 +240,7 @@ void LogitechG915Controller::InitializeDirect()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, 4);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
@@ -259,7 +259,7 @@ void LogitechG915Controller::InitializeDirect()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, 4);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
@@ -279,7 +279,7 @@ void LogitechG915Controller::InitializeDirect()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, 17);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, MESSAGE_LEN);
/*-----------------------------------------------------*\
@@ -300,7 +300,7 @@ void LogitechG915Controller::InitializeDirect()
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, 17);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -355,7 +355,7 @@ void LogitechG915Controller::SendDirectFrame
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, HEADER_SIZE + length);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read_timeout(dev_handle, usb_buf, RESPONSE_LEN, LOGITECH_READ_TIMEOUT);
}
@@ -370,7 +370,7 @@ void LogitechG915Controller::SendMode
unsigned char blue
)
{
unsigned char usb_buf[HEADER_SIZE + 13];
unsigned char usb_buf[MESSAGE_LEN];
/*-----------------------------------------------------*\
| Zero out buffer |
@@ -433,6 +433,6 @@ void LogitechG915Controller::SendMode
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, HEADER_SIZE + 13);
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -0,0 +1,874 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller.h |
| |
| Unified Logitech HID++ 2.0 controller |
| |
| Uses feature discovery (IRoot 0x0000) to dynamically |
| determine device capabilities and adapt to any HID++ |
| 2.0 device with RGB lighting support. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <map>
#include <mutex>
#include <memory>
#include <thread>
#include <atomic>
#include <functional>
#include <chrono>
#include <deque>
#include <condition_variable>
#include <hidapi.h>
#include "RGBController.h"
#include "LogitechProtocolCommon.h"
/*-----------------------------------------------------*\
| HID++ 2.0 Feature Page IDs |
\*-----------------------------------------------------*/
#define HIDPP20_FEAT_IROOT 0x0000
#define HIDPP20_FEAT_FEATURE_SET 0x0001
#define HIDPP20_FEAT_DEVICE_NAME_TYPE 0x0005
#define HIDPP20_FEAT_ONBOARD_PROFILES 0x8100
#define HIDPP20_FEAT_PROFILE_MANAGEMENT 0x8101
#define HIDPP20_FEAT_FIRMWARE_INFO 0x0003
#define HIDPP20_FEAT_CENTPPBRIDGE 0x0003 /* same ID, different meaning on Centurion */
#define HIDPP20_FEAT_COLOR_LED_EFFECTS 0x8070
#define HIDPP20_FEAT_RGB_EFFECTS 0x8071
#define HIDPP20_FEAT_PER_KEY_LIGHTING_V1 0x8080
#define HIDPP20_FEAT_PER_KEY_LIGHTING_V2 0x8081
#define HIDPP20_FEAT_KEYBOARD_LAYOUT 0x4540
#define HIDPP20_FEAT_DISABLE_KEYS_BY_USAGE 0x4522
#define HIDPP20_FEAT_CENTURION_RGB 0x0600
#define HIDPP20_FEAT_HEADSET_RGB_HOSTMODE 0x0620
#define HIDPP20_FEAT_CENTURION_DEVICE_INFO 0x0100
#define HIDPP20_FEAT_CENTURION_DEVICE_NAME 0x0101
#define HIDPP20_FEAT_UNIFIED_BATTERY 0x1004
#define HIDPP20_FEAT_WIRELESS_STATUS 0x1D4B
/*-----------------------------------------------------*\
| HID++ 2.0 Function IDs (byte 3 high nibble) |
| Function ID is shifted left 4 bits, low nibble = swID |
\*-----------------------------------------------------*/
/*-----------------------------------------------------*\
| HID++ Software ID — identifies our responses. |
| Must avoid: 0x00 (firmware), 0x01 (vendor app), |
| 0x02-0x0F (Solaar cycles these). |
| There are only 16 values (4-bit field), and all are |
| claimed. We pick a fixed value and will coordinate |
| with Solaar to exclude it from its cycle. |
\*-----------------------------------------------------*/
#define HIDPP20_SW_ID 0x07
/*-----------------------------------------------------*\
| Feature 0x8071 functions |
\*-----------------------------------------------------*/
#define FN_8071_GET_INFO 0x00
#define FN_8071_SET_EFFECT 0x10
#define FN_8071_SET_PATTERN 0x20
#define FN_8071_NV_CONFIG 0x30
#define FN_8071_BIN_INFO 0x40
#define FN_8071_SW_CONTROL 0x50
#define FN_8071_SYNC 0x60
#define FN_8071_PWR_CONFIG 0x70
#define FN_8071_PWR_MODE 0x80
/*-----------------------------------------------------*\
| Feature 0x8081 functions |
\*-----------------------------------------------------*/
#define FN_8081_GET_INFO 0x00
#define FN_8081_SET_INDIVIDUAL 0x10
#define FN_8081_SET_CONSECUTIVE 0x20
#define FN_8081_SET_DELTA_5BIT 0x30
#define FN_8081_SET_DELTA_4BIT 0x40
#define FN_8081_SET_RANGE 0x50
#define FN_8081_SET_SINGLE_VALUE 0x60
#define FN_8081_FRAME_END 0x70
/*-----------------------------------------------------*\
| Feature 0x0620 functions (headset RGB hostmode) |
\*-----------------------------------------------------*/
#define FN_0620_GET_INFO 0x00
#define FN_0620_GET_RGB_ZONE_INFO 0x10
#define FN_0620_SET_INDIVIDUAL_RGB_ZONES 0x20
#define FN_0620_SET_CONSECUTIVE_RGB_ZONES 0x30
#define FN_0620_SET_RANGE_RGB_ZONES 0x40
#define FN_0620_SET_RGB_ZONES_SINGLE_VALUE 0x50
#define FN_0620_FRAME_END 0x60
#define FN_0620_GET_HOST_MODE_STATE 0x70
#define FN_0620_SET_HOST_MODE_STATE 0x80
/*-----------------------------------------------------*\
| Feature 0x8100 functions |
\*-----------------------------------------------------*/
#define FN_8100_SET_ONBOARD_MODE 0x10
#define FN_8100_GET_ONBOARD_MODE 0x20
/*-----------------------------------------------------*\
| Feature 0x8101 functions |
\*-----------------------------------------------------*/
#define FN_8101_GET_SET_MODE 0x60
#define FN_8101_LOAD 0x80 // load(partition, sector, size)
#define FN_8101_READBUFFER 0xC0 // readBuffer(offset)
/*-----------------------------------------------------*\
| Zone cluster effect entry |
\*-----------------------------------------------------*/
struct HIDPP20Effect
{
uint8_t index;
uint16_t effect_id;
uint16_t capabilities;
uint16_t default_period;
};
/*-----------------------------------------------------*\
| Zone cluster info from 0x8071 GetRgbClusterInfo |
\*-----------------------------------------------------*/
struct HIDPP20ZoneCluster
{
uint8_t index;
uint16_t location;
uint8_t effect_count;
std::vector<HIDPP20Effect> effects;
};
/*-----------------------------------------------------*\
| Per-model device quirks — behavioral differences that |
| can't be detected via feature probing. |
\*-----------------------------------------------------*/
enum HIDPP20DeviceQuirks : uint32_t
{
HIDPP20_QUIRK_NONE = 0,
HIDPP20_QUIRK_FADE_ACCEPTS_WRITES = (1 << 0), // firmware accepts host frames during sleep fade without waking
};
struct HIDPP20DeviceQuirkEntry
{
uint16_t pid_wireless;
uint16_t pid_wired;
uint32_t quirks;
};
/*---------------------------------------------------------*\
| Default: suppress frames while SLEEPING. Safe everywhere |
| — it cannot wake a device that treats writes as activity. |
| Devices listed here opt out of suppression because their |
| firmware accepts writes during the fade without |
| cancelling sleep. |
\*---------------------------------------------------------*/
static constexpr HIDPP20DeviceQuirkEntry HIDPP20_DEVICE_QUIRK_TABLE[] =
{
{ 0x40B4, 0xC355, HIDPP20_QUIRK_FADE_ACCEPTS_WRITES }, // G515 LS TKL
};
/*-----------------------------------------------------*\
| Device capabilities discovered via feature probing |
\*-----------------------------------------------------*/
struct HIDPP20DeviceCapabilities
{
std::string device_name;
uint8_t device_type;
std::string firmware_version;
std::string serial_number;
std::string unit_id; // stable hardware ID (from FirmwareInfo fn0)
uint16_t pid_wireless; // wireless virtual PID (from FirmwareInfo fn0)
uint16_t pid_wired; // wired USB PID (from FirmwareInfo fn0)
uint32_t quirks; // resolved from HIDPP20_DEVICE_QUIRK_TABLE after PID discovery
/*--------------------------------------------------*\
| Complete feature map (feature_id → runtime index) |
| Built once by EnumerateFeatures, used by all |
| subsequent GetFeatureIndex lookups (no wire). |
\*--------------------------------------------------*/
std::map<uint16_t, uint8_t> feature_map;
std::map<uint16_t, uint8_t> feature_versions; /* feature_id -> version byte */
bool feature_map_complete; // true after bulk enumeration
/*-------------------------------------------------*\
| Feature indices (0 = not supported) |
\*-------------------------------------------------*/
uint8_t idx_onboard_profiles;
uint8_t idx_profile_management;
uint8_t idx_rgb_effects;
uint8_t idx_headset_rgb_hostmode; /* 0x0620 — Centurion headset RGB */
uint8_t idx_perkey_v2;
uint8_t idx_perkey_v1;
uint8_t idx_wireless_status;
uint8_t idx_disable_keys_by_usage; /* 0x4522 — keyboard-family handshake */
uint16_t rgb_feature_page;
/*--------------------------------------------------*\
| Resolved function IDs for the RGB effects feature |
| Varies between 0x8070, 0x8071, 0x0600 |
\*--------------------------------------------------*/
uint8_t fn_set_effect;
uint8_t fn_sw_control;
uint8_t fn_pwr_config;
uint8_t fn_pwr_mode;
bool has_power_mgmt;
bool sw_control_simple;
/*--------------------------------------------------*\
| Persistent NV settings read from RGBEffects fn3 |
| (FN_8071_NV_CONFIG). Capability 0x0020 is the |
| sleep ramp / off-ramp transition (enabled + |
| ramp_seconds). |
\*--------------------------------------------------*/
bool nv_sleep_ramp_known;
bool nv_sleep_ramp_enabled;
uint8_t nv_sleep_ramp_seconds;
/*---------------------------------------------------*\
| Device-firmware effect cards (0x8071 fn0 |
| GetEffectSpecificInfo). Populated by |
| DiscoverEffectCards at feature-discovery time. |
| |
| has_effect_cards — probe returned a valid response |
| for firmware card 0 page 1 (no InvalidArgument). |
| effect_card_template[0..1] — device-wide constant |
| bytes read from that response at data[10..11]. |
| Echoed back into prep1 of DoObservedPerKeyPrep |
| at SetEffectByIndex params[6..7]. |
| |
| Gate for the observed per-key prep is now |
| has_effect_cards — replaces the earlier |
| "effects.size() < 5" heuristic, which was a proxy |
| that correlated with "has cards" on the devices we |
| happened to know but had no principled meaning. |
\*---------------------------------------------------*/
bool has_effect_cards;
uint8_t effect_card_template[2];
/*-------------------------------------------------*\
| Discovered zone and LED data |
\*-------------------------------------------------*/
std::vector<HIDPP20ZoneCluster> zone_clusters;
std::vector<uint16_t> perkey_zone_ids;
std::vector<uint8_t> headset_rgb_hostmode_zone_ids; /* 0x0620 fn1 result */
bool has_perkey;
bool has_zone_effects;
bool is_headset_rgb_hostmode; /* 0x0620 path selected */
bool has_numpad;
uint8_t keyboard_layout_code;
};
/*------------------------------------------------------*\
| Transport type — determines wire framing |
\*------------------------------------------------------*/
enum HIDPP20TransportType
{
HIDPP20_TRANSPORT_STANDARD, // 0xFF00/0xFF43: report IDs 0x10/0x11, 7/20 bytes
HIDPP20_TRANSPORT_CENTURION // 0xFFA0: report ID 0x51 or 0x50, 64 bytes,
// with CPL framing and CentPPBridge sub-device routing
};
/*------------------------------------------------------*\
| Transport layer — abstracts wire format differences |
| |
| Standard HID++ and Centurion both carry the same |
| feature/function/data payload, but with different |
| report framing. This struct holds transport state |
| so SendMessage/ReadMessage can adapt. |
\*------------------------------------------------------*/
struct HIDPP20Transport
{
HIDPP20TransportType type;
uint16_t usage_page; // 0xFF00, 0xFF43, or 0xFFA0
uint8_t report_id; // 0x10/0x11 for standard, 0x51/0x50 for Centurion
bool addressed; // Centurion 0x50 has device address byte
uint8_t device_address; // Centurion 0x50: device address (e.g., 0x23)
uint8_t bridge_feat_idx; // CentPPBridge feature index on parent (0 if N/A)
uint8_t sub_device_id; // CentPPBridge sub-device ID (typically 0)
uint16_t bridge_mtu; // CentPPBridge MTU from getConnectionInfo:
// 0 = no sub-device, sendFragment will fail
// >0 = sub-device present, payload size in bytes
};
/*-----------------------------------------------------*\
| Power management state machine |
| Matches Solaar's RGBPowerManager states |
\*-----------------------------------------------------*/
enum HIDPP20PowerState
{
HIDPP20_POWER_ACTIVE = 0,
HIDPP20_POWER_DIMMING = 1,
HIDPP20_POWER_IDLE = 2,
HIDPP20_POWER_SLEEPING = 3,
};
/*-----------------------------------------------------*\
| Parsed HID++ message for the response queue |
\*-----------------------------------------------------*/
struct HIDPP20RawMessage
{
uint8_t feat;
uint8_t func;
uint8_t data[60];
int result;
};
/*------------------------------------------------------*\
| Per-key write tracking. SendPerKeyData is fire-and- |
| forget at the wire layer; we track which zones each |
| outstanding write covers so PerKeyFrameEnd can match |
| ACKs back by FIFO order and report which zones the |
| firmware actually committed. |
\*------------------------------------------------------*/
struct OutstandingPerKeyWrite
{
uint8_t function; // FN_8081_* (high nibble carries the type)
std::vector<uint8_t> zone_ids; // zones covered by this packet
};
/*-----------------------------------------------------*\
| Result of a per-key frame commit. The caller uses |
| these to update its delta-tracking state: |
| - frame_end_acked: did the FrameEnd packet ACK? |
| - acked_zones: zones whose write packet ACKed |
| - attempted_zones: every zone written this frame |
\*-----------------------------------------------------*/
struct PerKeyFrameResult
{
bool frame_end_acked;
std::vector<uint8_t> acked_zones;
std::vector<uint8_t> attempted_zones;
};
/*-------------------------------------------------------*\
| Retry policy for SendAcked. |
| |
| Controls the send/read/retry loop for a single HID++ |
| request. Three canned policies cover all use cases: |
| |
| Reliable: probe/discovery/set/get (~6s worst case) |
| FrameEnd: per-key commit gate (~230ms worst) |
| Streaming: per-key write inside the animation loop |
| (~80ms worst) |
| |
| backoff_ms[i] is the delay applied BEFORE the i-th |
| attempt. backoff_ms[0] is normally 0 (no delay before |
| the first send). The schedule mirrors the firmware's |
| own event burst pattern (63→125→250→500→1000→2000ms, |
| "catch at least one of N"). |
\*-------------------------------------------------------*/
struct HIDPP20RetryPolicy
{
const uint16_t* backoff_ms; // schedule[i] = delay before attempt i
uint8_t attempts; // length of backoff_ms (>=1)
uint16_t read_window_ms; // per-attempt read budget
bool flush_before; // flush response queue at call start
bool retry_on_busy; // BUSY (0x08) -> retry the send
const char* name; // for logging ("reliable", etc.)
};
/*------------------------------------------------------*\
| Canned backoff schedules. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_BACKOFF_RELIABLE[] =
{ 0, 63, 125, 250, 500, 1000, 2000 };
static constexpr uint16_t HIDPP20_BACKOFF_PROBE[] =
{ 0, 100 };
/*------------------------------------------------------*\
| SW-control reclaim backoff. Used by ReconnectDevice |
| to retry the claim+push sequence after a wireless |
| reconnect, racing the firmware boot animation. The |
| vendor app typically lands control in ~50ms; this |
| schedule fits |
| 6 attempts inside ~620ms so the animation never gets |
| a chance to become visible. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_RECLAIM_BACKOFF_MS[] =
{ 0, 20, 40, 80, 160, 320 };
/*------------------------------------------------------*\
| FrameEnd BUSY retry backoff. Used by PerKeyFrameEnd |
| when the firmware returns HID++ error 0x08 (BUSY) |
| because it's still draining the per-key write queue. |
| First retry is fast (~2 USB round trips) for the |
| common case where BUSY was transient; subsequent |
| retries give actual drain headroom. Total worst case |
| ~180ms, fits inside the PerKeyFrameEnd 300ms deadline |
| with margin for the eventual ACK to land. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_FRAME_END_BUSY_BACKOFF_MS[] =
{ 30, 60, 90 };
/*-----------------------------------------------------*\
| Deep-sleep detection threshold. After StartSleep() |
| commands the firmware fade, the device eventually |
| enters deep sleep and returns BUSY to every FrameEnd. |
| Once this many consecutive FrameEnd attempts exhaust |
| all BUSY retries while power_state == SLEEPING, we |
| suppress further frame sends until Wake() fires. |
\*-----------------------------------------------------*/
static constexpr int HIDPP20_DEEP_SLEEP_FAILURE_THRESHOLD = 5;
/*------------------------------------------------------*\
| Per-key frame retry backoff. Used when a full |
| DeviceUpdateLEDs pass completes with some zones |
| unacked (partial commit). The retry re-runs a whole |
| frame from the power thread, so the backoff is |
| between full frames, not individual packets. |
| |
| First value aligned to the power thread's 50ms poll |
| cadence — anything shorter rounds up anyway, and |
| matching the tick makes latency predictable. |
| |
| Worst case: 5 retries, cumulative ~1550ms. This |
| covers the reconnect-transient window where the G502 |
| firmware silently drops per-key writes for several |
| hundred ms after the wireless link re-establishes. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_REPAINT_RETRY_BACKOFF_MS[] =
{ 50, 100, 200, 400, 800 };
static constexpr HIDPP20RetryPolicy HIDPP20_POLICY_RELIABLE = {
HIDPP20_BACKOFF_RELIABLE,
sizeof(HIDPP20_BACKOFF_RELIABLE) / sizeof(uint16_t),
300, // read window
true, // flush_before
true, // retry_on_busy
"reliable"
};
/*------------------------------------------------------*\
| Probe policy: tight budget for is-this-HID++ checks |
| during initial discovery. ~500ms worst case for dead |
| devices, vs ~6s for reliable. One retry handles a |
| transient hiccup on the first IRoot call (e.g. on a |
| busy mouse), but we bail fast on truly non-responsive |
| or non-HID++ hidraws so probe latency stays bounded. |
\*------------------------------------------------------*/
static constexpr HIDPP20RetryPolicy HIDPP20_POLICY_PROBE = {
HIDPP20_BACKOFF_PROBE,
sizeof(HIDPP20_BACKOFF_PROBE) / sizeof(uint16_t),
200, // read window
true, // flush_before
true, // retry_on_busy
"probe"
};
class LogitechHIDPP20Controller
{
public:
LogitechHIDPP20Controller(hid_device* dev, const char* path,
uint8_t device_index, bool wireless,
std::shared_ptr<std::mutex> mutex_ptr,
uint16_t usage_page = 0xFF00);
~LogitechHIDPP20Controller();
/*-------------------------------------------------*\
| Lifecycle |
\*-------------------------------------------------*/
bool Probe();
void Initialize();
void Shutdown();
/*-------------------------------------------------*\
| Accessors |
\*-------------------------------------------------*/
const HIDPP20DeviceCapabilities& GetCapabilities() const;
std::string GetDeviceLocation();
std::string GetSerialString();
uint32_t GetInitGeneration() const;
/*--------------------------------------------------*\
| Per-key lighting (0x8081) |
| |
| Per-key writes are fire-and-forget at the wire |
| layer. SendPerKeyData enqueues an outstanding |
| write entry; PerKeyFrameEnd drains the response |
| queue, matches ACKs by FIFO, and returns which |
| zones the firmware actually committed plus |
| whether the FrameEnd itself ACKed. |
\*--------------------------------------------------*/
void SetPerKeyColors(const std::vector<std::pair<uint16_t, RGBColor>>& zone_colors);
void SetAllPerKeyColor(RGBColor color);
void SendPerKeyData(uint8_t perkey_idx, uint8_t function,
const uint8_t* data, size_t len,
const std::vector<uint8_t>& zone_ids);
PerKeyFrameResult PerKeyFrameEnd();
/*-------------------------------------------------*\
| Zone effects (0x8071 / 0x8070) |
\*-------------------------------------------------*/
void SetZoneEffect(uint8_t cluster_idx, uint8_t effect_idx,
uint16_t effect_id,
unsigned char r, unsigned char g, unsigned char b,
uint16_t period, unsigned char brightness,
unsigned char direction, bool persist);
/*---------------------------------------------------*\
| Headset RGB hostmode (0x0620). |
| Sticky-claim model: SetHostMode() claims once via |
| fn8, then each write is fn5 (single-value) or fn2 |
| (individual) + fn6 FrameEnd[0x01]. 0x02 persist was |
| tested and does not work on G522 firmware. |
\*---------------------------------------------------*/
void SetHeadsetRGBHostmodeColors(const std::vector<RGBColor>& zone_colors);
/*-------------------------------------------------*\
| SW control management |
\*-------------------------------------------------*/
int SetSWControl(uint8_t mode, uint8_t flags);
void SetRGBPowerMode(uint8_t mode);
void SetHostMode();
bool ClaimSWControlIfNeeded();
void UpgradeSwControlAfterFirstPaint();
/*-------------------------------------------------*\
| Keyboard-family handshake (0x4522 fn3 + fn1). |
| G815 / G915 / G Pro send this before any mode |
| write. Feature-gated no-op on devices (G502 / |
| G515) that don't enumerate 0x4522. |
\*-------------------------------------------------*/
void DoDisableKeysByUsageHandshake();
/*--------------------------------------------------*\
| Keyboard-family per-key takeover prep. |
| Per-cluster SetEffectByIndex(effectIdx=0=Off, |
| persist=1) + primer key via SetIndividualRgbZones |
| + FrameEnd. Matches G815 / G915 InitializeDirect. |
| Gated on 0x4522 + per-key V2 presence. |
\*--------------------------------------------------*/
void DoKeyboardFamilyPerKeyPrep();
/*--------------------------------------------------*\
| Wake-repaint flag. Set by Wake() before calling |
| request_repaint_fn so the repaint callback knows |
| to invalidate sent_colors (force a full per-key |
| push) without triggering the claim/prep sequence. |
\*--------------------------------------------------*/
bool ConsumeWakeFullRepaint();
bool NeedsPrepSequence() const { return sw_control_needs_upgrade_to_5; }
/*--------------------------------------------------*\
| Per-key retry scheduling. Called by the RGB |
| controller's DeviceUpdateLEDs on partial-commit |
| frames; the power thread polls and fires |
| request_repaint_fn when a retry deadline expires. |
\*--------------------------------------------------*/
void ScheduleRetryPaint();
void CancelRetryPaint();
void TickRetryPaintIfPending();
/*--------------------------------------------------*\
| Observed per-key prep sequence. Two |
| SetEffectByIndex calls cloned byte-for-byte from a |
| wire capture on a G502 X PLUS. |
| Used in place of the Static-pass-through prep when |
| the device's RGBEffects enumeration matches the |
| G502 shape — see DeviceUpdateLEDs for gating. |
\*--------------------------------------------------*/
void DoObservedPerKeyPrep();
/*--------------------------------------------------*\
| Power management (idle/dim/sleep/wake) |
\*--------------------------------------------------*/
void StartPowerManager();
void StopPowerManager();
void StartEventWatcher();
void StartProbeWatcher();
bool HasBridge() const;
void SetRepaintCallback(std::function<void()> repaint);
void SetReapplyActiveModeCallback(std::function<bool()> cb);
void SetRegisterCallback(std::function<void(RGBController*)> cb);
HIDPP20PowerState GetPowerState() const;
int GetDimBrightness() const;
bool IsOnline() const;
bool IsDeepSleep() const;
void SetWireless(bool w) { wireless = w; }
bool QueryWirelessStatus();
bool QueryExternalPower();
void FlushResponseQueue();
private:
hid_device* dev;
std::string location;
uint8_t device_index;
bool wireless;
std::shared_ptr<std::mutex> mutex;
HIDPP20DeviceCapabilities caps;
HIDPP20Transport transport;
bool initialized;
bool sw_control_claimed;
bool sw_control_needs_upgrade_to_5;
uint32_t frame_counter;
/*--------------------------------------------------*\
| Retry-paint state (partial-commit recovery). |
| retry_paint_deadline_ zero = no retry pending. |
| retry_paint_attempt_ indexes into |
| HIDPP20_REPAINT_RETRY_BACKOFF_MS; once it reaches |
| the array length, we give up for this sequence. |
| Atomic so both the paint thread (RGB controller) |
| and the power thread can access without locks. |
\*--------------------------------------------------*/
std::atomic<std::chrono::steady_clock::time_point> retry_paint_deadline_;
std::atomic<uint8_t> retry_paint_attempt_;
std::atomic<bool> wake_full_repaint_pending_;
uint32_t init_generation;
std::string log_tag;
/*---------------------------------------------------*\
| Transport-layer I/O |
| |
| SendMessage/ReadMessage handle wire framing based |
| on transport.type. Upper layers pass feature index, |
| function ID, and payload — the transport layer |
| wraps them in the correct report format. |
\*---------------------------------------------------*/
int SendMessage(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadMessage(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms = LOGITECH_PROTOCOL_TIMEOUT);
/*--------------------------------------------------*\
| Standard HID++ transport (0xFF00/0xFF43) |
\*--------------------------------------------------*/
int SendStandard(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadStandardDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*--------------------------------------------------*\
| Centurion transport (0xFFA0) |
| Wraps messages in CPL framing, routes through |
| CentPPBridge for sub-device access. |
\*--------------------------------------------------*/
int SendCenturion(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadCenturionDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*--------------------------------------------------*\
| Reader thread dispatch layer |
| ReadHIDDirect: raw HID read (used by reader thread |
| and during Probe before reader starts). |
| ReadFromQueue: waits on response queue filled by |
| the reader thread. |
\*--------------------------------------------------*/
int ReadHIDDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
int ReadFromQueue(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*---------------------------------------------------*\
| High-level helpers |
| SendAndReceive is retained as a thin wrapper |
| around SendAcked with the reliable policy, for |
| call-site stability. |
\*---------------------------------------------------*/
int SendAndReceive(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
uint8_t* recv_data, size_t recv_max);
/*---------------------------------------------------*\
| Unified send-and-ack primitive with retry policy. |
| All command paths converge here. Returns: |
| >0 : bytes copied into recv_data |
| 0 : timeout / BUSY exhaustion |
| -1 : non-BUSY HID++ error |
| -2 : wire error (SendMessage failed) |
| If hidpp20_error_out is non-null and return is -1, |
| the HID++ error code is stored there. |
\*---------------------------------------------------*/
int SendAcked(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
uint8_t* recv_data, size_t recv_max,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE,
uint8_t* hidpp20_error_out = nullptr);
/*--------------------------------------------------*\
| Compatibility shim: same as SendAcked but writes |
| the response into a blankFAPmessage. Used by |
| callers that inherited the SendLong+ReadResponse |
| interface and inspect response.data[] downstream. |
\*--------------------------------------------------*/
int SendAckedIntoFAP(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
blankFAPmessage& response,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE);
/*-------------------------------------------------*\
| Feature discovery |
\*-------------------------------------------------*/
uint8_t GetFeatureIndex(uint16_t feature_page,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE);
uint8_t GetFeatureVersion(uint16_t feature_page) const;
void DiscoverTransport();
void DiscoverDeviceName();
void DiscoverDeviceType();
void EnumerateFeatures(uint8_t feature_set_idx);
void DiscoverFirmwareInfo();
void DiscoverRGBEffects();
void DiscoverEffectCards();
void DiscoverHeadsetRGBHostmode();
void DiscoverPerKeyZones();
void DiscoverKeyboardLayout();
/*-------------------------------------------------*\
| Power management internals |
\*-------------------------------------------------*/
void ReaderThreadFunc();
void PowerThreadFunc();
void DispatchEvent(uint8_t feat, uint8_t func, const uint8_t* data);
void OnUserActivity(uint8_t activity_type);
void StartDimRamp();
void DimRampStep();
void StartSleep();
void Wake();
void ReadFirmwareTimers();
void ReadNvSleepRampConfig();
void WritePowerConfig(uint16_t idle_s, uint16_t sleep_s);
void ReadActiveProfileSector();
void ReprobeSubDevice();
void ReconnectDevice();
void FullReprobe();
void RediscoverFeatures();
/*----------------------------------------------------*\
| Platform-specific. ScanForDevice walks the OS-level |
| HID enumeration to find the same physical device on |
| a new path (USB<->wireless transitions). Linux uses |
| sysfs; Windows and macOS use hidapi + serial_number |
| matching. Bodies live in |
| LogitechHIDPP20Controller_Linux.cpp and |
| LogitechHIDPP20Controller_Windows_MacOS.cpp. |
\*----------------------------------------------------*/
bool ScanForDevice(bool force = false);
/*----------------------------------------------------*\
| Platform-specific. Returns the friendly name for a |
| Centurion sub-device at the given hidapi path, or "" |
| if no name is available. Linux reads HID_NAME from |
| sysfs; Windows uses hid_device_info::product_string. |
\*----------------------------------------------------*/
std::string GetCenturionSubDeviceName(const std::string& path);
void SwapHIDHandle(hid_device* new_dev, const std::string& new_path);
/*-------------------------------------------------*\
| Reader thread + response queue |
\*-------------------------------------------------*/
std::thread* reader_thread;
std::atomic<bool> reader_running;
std::mutex response_mutex;
std::condition_variable response_cv;
std::deque<HIDPP20RawMessage> response_queue;
/*-------------------------------------------------*\
| Power thread (state machine + command sender) |
\*-------------------------------------------------*/
std::thread* power_thread;
std::atomic<bool> power_thread_running;
std::atomic<int> pending_activity; // -1=none, 0=idle, 1+=active
std::atomic<int> pending_connection; // 0=none, +1=connected, -1=disconnected
std::atomic<int> pending_path_check; // HID++1.0 DJ notification → force-scan retries remaining (0=idle)
std::atomic<bool> device_online; // false when device is unreachable
std::atomic<int> consecutive_timeouts; // reset on successful response
std::atomic<bool> watcher_mode; // true when retrying failed probe
/*-------------------------------------------------*\
| Power management state |
\*-------------------------------------------------*/
HIDPP20PowerState power_state;
std::mutex power_mutex;
std::atomic<bool> deep_sleep; // true once device stops responding after StartSleep()
std::atomic<int> consecutive_frame_end_failures; // FrameEnd BUSY exhaustions while SLEEPING
/*-------------------------------------------------*\
| Dim ramp state |
| dim_brightness_pct is applied by DeviceUpdateLEDs |
| to scale colors before pushing to device. |
| This is our own host-side animation, independent |
| of any firmware dim/sleep timers. |
\*-------------------------------------------------*/
#define DIM_STEPS 25
#define DIM_INTERVAL_MS 200
#define DIM_TARGET_PCT 50
std::atomic<int> dim_brightness_pct; // 100=full, 50=dimmed
int dim_step;
std::chrono::steady_clock::time_point next_dim_time;
/*-------------------------------------------------*\
| Sleep timer |
\*-------------------------------------------------*/
std::chrono::steady_clock::time_point sleep_deadline;
/*--------------------------------------------------*\
| Last idle-settings re-read timestamp. Drives the |
| 500ms poll in PowerThreadFunc that re-reads the |
| LogitechHIDPP20IdleSettings JSON key so updates |
| from the plugin (or manual edits) apply within |
| about half a second without any callback plumbing. |
\*--------------------------------------------------*/
std::chrono::steady_clock::time_point last_idle_poll;
/*-------------------------------------------------*\
| Effective idle/sleep timers used by the state |
| machine. Populated from the firmware snapshot by |
| default, then possibly overridden by profile |
| values in ApplyPowerSavingProfile. |
\*-------------------------------------------------*/
uint16_t idle_timeout_s;
uint16_t sleep_timeout_s;
/*--------------------------------------------------*\
| Firmware-configured timer snapshot. Read at init |
| (and on reconnect) by ReadFirmwareTimers and |
| never overwritten by profile application, so |
| that the unconfigured fallback path and transition |
| back from a user profile both have a clean set |
| of defaults to return to. |
\*--------------------------------------------------*/
uint16_t fw_idle_timeout_s = 60;
uint16_t fw_sleep_timeout_s = 300;
uint16_t written_idle_s = 0; // last value written to device RAM (0 = not written yet)
uint16_t written_sleep_s = 0;
/*--------------------------------------------------*\
| Host-side idle/dim/sleep state. |
| Populated from LogitechHIDPP20IdleSettings on each |
| ApplyPowerSavingProfile() invocation. |
\*--------------------------------------------------*/
bool ps_dim_enabled = false;
int ps_dim_target_pct = DIM_TARGET_PCT;
bool ps_sleep_enabled = false;
bool ps_on_external_power = false;
bool ps_last_logged_external = false;
int ps_last_logged_pct = -1;
int ps_last_logged_idle = -1;
int ps_last_logged_sleep = -1;
uint16_t last_power_raw = 0xFFFF; // dedup for QueryExternalPower TRACE
uint8_t idx_unified_battery = 0;
void ApplyPowerSavingProfile();
bool IsCurrentlyWireless() const;
/*-------------------------------------------------*\
| Per-key write tracking (per active frame) |
| Populated by SendPerKeyData, drained by |
| PerKeyFrameEnd. Single-threaded — only the RGB |
| controller thread touches per-key state. |
\*-------------------------------------------------*/
std::vector<OutstandingPerKeyWrite> outstanding_writes;
/*-------------------------------------------------*\
| Callbacks |
\*-------------------------------------------------*/
std::function<void()> request_repaint_fn;
std::function<bool()> reapply_active_mode_fn;
std::function<void(RGBController*)> register_controller_fn;
};
@@ -0,0 +1,433 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller_Linux.cpp |
| |
| Linux-specific path-migration and device-name lookup |
| for the unified Logitech HID++ 2.0 controller. |
| |
| Uses sysfs (/sys/class/hidraw) to find the same |
| physical device on a new hidraw path after a USB <-> |
| wireless transition, and to read Centurion sub-device |
| friendly names from HID_NAME uevent fields. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <cctype>
#include <cstdio>
#include <chrono>
#include <vector>
#include <string>
#include <fstream>
#include <dirent.h>
#include "LogitechHIDPP20Controller.h"
#include "LogManager.h"
#define LOG_TAG log_tag.c_str()
std::string LogitechHIDPP20Controller::GetCenturionSubDeviceName(const std::string& path)
{
/*--------------------------------------------------------*\
| Centurion sub-device friendly name comes from sysfs |
| HID_NAME=, same field Solaar reads. The `path` arg is a |
| hidraw dev path like /dev/hidraw5; extract the hidrawN |
| basename and read /sys/class/hidraw/<basename>/device/ |
| uevent. |
\*--------------------------------------------------------*/
std::string sysfs_name;
size_t pos = path.rfind("hidraw");
if(pos == std::string::npos)
{
return sysfs_name;
}
std::string uevent_path = "/sys/class/hidraw/" + path.substr(pos) + "/device/uevent";
FILE* f = fopen(uevent_path.c_str(), "r");
if(!f)
{
return sysfs_name;
}
char line[256];
while(fgets(line, sizeof(line), f))
{
if(strncmp(line, "HID_NAME=", 9) == 0)
{
sysfs_name = line + 9;
while(!sysfs_name.empty() && (sysfs_name.back() == '\n' || sysfs_name.back() == '\r'))
{
sysfs_name.pop_back();
}
break;
}
}
fclose(f);
return sysfs_name;
}
bool LogitechHIDPP20Controller::ScanForDevice(bool force)
{
/*---------------------------------------------------------*\
| Scan sysfs for a hidraw with matching unitId. Called by |
| the power thread either periodically (normal reactive |
| mode — only when device_online==false, USB/wireless |
| transition or physical unplug) or on demand from the |
| reader thread after a HID++1.0 Device Connection |
| notification arrives (force=true, bypasses the online |
| gate). Finds the device on its new connection path. |
| |
| Reactive-only: we never migrate while the current path |
| still works. On devices like the G502 X PLUS that expose |
| both a USB-direct hidraw AND a wireless-via-dongle hidraw |
| simultaneously (when paired to the receiver and plugged |
| in at the same time), eager migration would bounce us off |
| a working path onto one the firmware has actively muted, |
| breaking control. The device signals which path is |
| active by returning errors/going silent on the inactive |
| path — our reader thread picks that up as a hid_read |
| failure and flips device_online=false, at which point the |
| power thread calls this function to pick the new path. |
| |
| Linux implementation: walks /sys/class/hidraw and matches |
| on HID_UNIQ. The Windows counterpart (same class method, |
| different .cpp file) uses hid_enumerate + serial_number. |
| |
| Match criteria: |
| - device_online == false (caller should already ensure) |
| - HID_UNIQ matches our unitId |
| - Logitech VID (046D) |
| - HID++ interface (usage_page 0xFF00, usage 2) |
| - Different from our current path (device moved) |
\*---------------------------------------------------------*/
if(caps.unit_id.empty() || caps.unit_id == "00000000")
{
return false;
}
/*---------------------------------------------------------*\
| Online guard: never migrate off a working path unless |
| the caller explicitly forces a re-check. The normal |
| periodic scan path stays gated on device_online==false |
| so it's a no-op when everything is healthy. |
| |
| The reader thread bypasses this guard (force=true) after |
| seeing a HID++1.0 Device Connection Status notification |
| from the Lightspeed receiver, which fires BEFORE the |
| firmware fully switches its data flow from wireless to |
| USB. At that moment device_online is still true (the |
| current path hasn't failed yet), but we want the scan to |
| run so we can migrate to the new path proactively. |
\*---------------------------------------------------------*/
if(!force && device_online.load())
{
return false;
}
/*---------------------------------------------------------*\
| Normalize our unitId to lowercase hex without dashes for |
| comparison. Sysfs HID_UNIQ varies between drivers: |
| - Lightspeed virtual: "0d-12-5d-47" (dashes) |
| - USB direct: "0D125D47" (no dashes) |
| We strip dashes and lowercase both sides for matching. |
\*---------------------------------------------------------*/
std::string target_norm;
for(char c : caps.unit_id)
{
if(c != '-')
{
target_norm += (char)tolower(c);
}
}
/*---------------------------------------------------------*\
| Read our current PID from sysfs. Only migrate to paths |
| with a DIFFERENT PID — same PID means same receiver, |
| just a different pairing slot (e.g., stale pairing). |
\*---------------------------------------------------------*/
unsigned int current_pid = 0;
{
std::string cur_hidraw = location.substr(location.rfind('/') + 1);
std::string cur_uevent = "/sys/class/hidraw/" + cur_hidraw + "/device/uevent";
std::ifstream cur_file(cur_uevent);
std::string line;
while(std::getline(cur_file, line))
{
if(line.compare(0, 7, "HID_ID=") == 0)
{
size_t lc = line.rfind(':');
if(lc != std::string::npos)
{
sscanf(line.c_str() + lc + 1, "%x", &current_pid);
}
break;
}
}
}
/*----------------------------------------------------------*\
| Collect ALL sysfs hidraws with matching unit_id + Logitech |
| VID + different PID. With multiple Lightspeed dongles in |
| the system, several hidraws can share the same HID_UNIQ: |
| one is the live virt-slot on the connected dongle, others |
| are stale virt-slots on dongles where our device is not |
| actually present. We have to probe each one to find out |
| which slot is real — sysfs alone can't tell them apart. |
\*----------------------------------------------------------*/
struct Candidate
{
std::string dev_path;
unsigned int pid;
};
std::vector<Candidate> candidates;
DIR* dir = opendir("/sys/class/hidraw");
if(!dir)
{
return false;
}
struct dirent* entry;
while((entry = readdir(dir)) != nullptr)
{
if(strncmp(entry->d_name, "hidraw", 6) != 0)
{
continue;
}
std::string uevent_path = "/sys/class/hidraw/" +
std::string(entry->d_name) + "/device/uevent";
std::ifstream uevent(uevent_path);
if(!uevent.is_open())
{
continue;
}
std::string line;
std::string hid_uniq;
std::string hid_id;
while(std::getline(uevent, line))
{
if(line.compare(0, 9, "HID_UNIQ=") == 0)
{
hid_uniq = line.substr(9);
}
else if(line.compare(0, 7, "HID_ID=") == 0)
{
hid_id = line.substr(7);
}
}
std::string uniq_norm;
for(char c : hid_uniq)
{
if(c != '-')
{
uniq_norm += (char)tolower(c);
}
}
if(uniq_norm != target_norm)
{
continue;
}
if(hid_id.find("0000046D") == std::string::npos &&
hid_id.find("0000046d") == std::string::npos)
{
continue;
}
std::string dev_path = "/dev/" + std::string(entry->d_name);
if(dev_path == location)
{
continue;
}
size_t last_colon = hid_id.rfind(':');
if(last_colon == std::string::npos)
{
continue;
}
unsigned int pid = 0;
sscanf(hid_id.c_str() + last_colon + 1, "%x", &pid);
if(pid == current_pid)
{
continue;
}
candidates.push_back({dev_path, pid});
}
closedir(dir);
if(candidates.empty())
{
return false;
}
/*----------------------------------------------------------*\
| Probe each candidate before committing. Two-dongle systems |
| can expose multiple sysfs hidraws with the same HID_UNIQ: |
| one is the live slot, others are stale pairings that |
| respond with short-format UNKNOWN_DEVICE errors. Issue a |
| cheap IRoot GetFeature (feat 0x0001) to distinguish them. |
| A live slot returns a long-form response with feat_idx=0 |
| and feat_byte matching the sub-index we asked for. A stale |
| slot returns r[10 xx 8F 00 00 08 ...] (short error, code |
| 0x08 UNKNOWN_DEVICE). We accept the first candidate that |
| passes; the overall pending_path_check retry loop will |
| naturally re-probe all candidates on subsequent scans if |
| none pass on the current pass (e.g., mid-transition). |
\*----------------------------------------------------------*/
std::string found_path;
hid_device* found_dev = nullptr;
for(size_t c = 0; c < candidates.size(); c++)
{
const Candidate& cand = candidates[c];
LOG_DEBUG("%s Scan: migration candidate at %s (pid=0x%04X, current=%s pid=0x%04X)",
LOG_TAG, cand.dev_path.c_str(), cand.pid,
location.c_str(), current_pid);
/*-----------------------------------------------------*\
| Multi-dongle caveat: hid_enumerate returns entries |
| for ALL dongles with this PID (046D:4099 Lightspeed |
| can appear several times in a single machine). Match |
| strictly on the sysfs candidate's dev_path so each |
| candidate resolves to its OWN dongle's HID++ |
| interface — not the first match we stumble across. |
\*-----------------------------------------------------*/
hid_device_info* devs = hid_enumerate(0x046D, (uint16_t)cand.pid);
std::string hidpp20_path;
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
LOG_TRACE("%s Scan: enumerate PID=0x%04X path=%s page=0x%04X usage=%d",
LOG_TAG, cand.pid, d->path, d->usage_page, d->usage);
if(std::string(d->path) == cand.dev_path &&
d->usage_page == 0xFF00 && d->usage == 2 &&
std::string(d->path) != location)
{
hidpp20_path = d->path;
break;
}
}
hid_free_enumeration(devs);
if(hidpp20_path.empty())
{
LOG_DEBUG("%s Scan: %s no matching LogitechHID++ interface in enum",
LOG_TAG, cand.dev_path.c_str());
continue;
}
hid_device* test_dev = hid_open_path(hidpp20_path.c_str());
if(!test_dev)
{
LOG_DEBUG("%s Scan: %s failed to open", LOG_TAG, hidpp20_path.c_str());
continue;
}
/*------------------------------------------------------*\
| Probe: HID++2.0 IRoot GetFeature for feat 0x0001 |
| (IFeatureSet). Wire: w[10 FF 0000 000100] |
| - report_id 0x10 (short) |
| - device_index 0xFF |
| - feat_idx 0x00 (IRoot) |
| - address 0x00 (func 0 GetFeature, sw_id 0) |
| - payload 00 01 00 (feat_id 0x0001) |
| |
| A live device returns r[11 xx 00 0X ...] — long form, |
| feat_idx 0x00, the address byte we sent back, and the |
| feature index in the payload. |
| A stale slot returns r[10 xx 8F 00 00 08 ...] — short |
| error form. Reject anything that starts with 0x10. |
\*------------------------------------------------------*/
uint8_t probe[7] = {0x10, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00};
uint8_t reply[20] = {};
int write_rc = hid_write(test_dev, probe, sizeof(probe));
bool probe_ok = false;
if(write_rc >= 0)
{
/*-----------------------------------------------------*\
| Drain up to ~100ms, looking for a long-form response |
| matching our probe. Skip stray reports from unrelated |
| firmware events that might be queued on the hidraw. |
\*-----------------------------------------------------*/
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(100);
while(std::chrono::steady_clock::now() < deadline)
{
int read_rc = hid_read_timeout(test_dev, reply, sizeof(reply), 50);
if(read_rc <= 0)
{
continue;
}
if(reply[0] == 0x11 && reply[2] == 0x00 && reply[3] == 0x00)
{
probe_ok = true;
break;
}
if(reply[0] == 0x10 && reply[2] == 0x8F)
{
LOG_DEBUG("%s Scan: %s probe rejected — err=0x%02X (stale slot)",
LOG_TAG, hidpp20_path.c_str(), reply[5]);
break;
}
}
}
if(!probe_ok)
{
hid_close(test_dev);
continue;
}
LOG_DEBUG("%s Scan: %s probe accepted (feat_idx=0x%02X)",
LOG_TAG, hidpp20_path.c_str(), reply[4]);
found_path = hidpp20_path;
found_dev = test_dev;
break;
}
if(!found_dev)
{
return false;
}
LOG_INFO("%s Device migrated: %s -> %s", LOG_TAG, location.c_str(), found_path.c_str());
SwapHIDHandle(found_dev, found_path);
return true;
}
@@ -0,0 +1,302 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller_Windows_MacOS.cpp |
| |
| Path-migration and device-name lookup for the unified |
| Logitech HID++ 2.0 controller on Windows and macOS. |
| |
| Uses hidapi's hid_enumerate + hid_device_info fields |
| (serial_number, product_string, product_id, usage_page) |
| on platforms with no /sys/class/hidraw equivalent. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <algorithm>
#include <chrono>
#include <cstring>
#include <string>
#include <vector>
#include "LogitechHIDPP20Controller.h"
#include "LogManager.h"
#include "StringUtils.h"
#define LOG_TAG log_tag.c_str()
std::string LogitechHIDPP20Controller::GetCenturionSubDeviceName(const std::string& path)
{
/*---------------------------------------------------------*\
| On Windows, hidapi's hid_device_info carries a |
| product_string field (wchar_t*). Enumerate all Logitech |
| devices and find the one whose path matches `path`. |
| |
| Caveat: Windows hidapi typically returns the parent |
| product string on every (interface, usage_page, usage) |
| entry that maps to the same USB device, so Centurion |
| sub-devices may share a name with the parent dongle. |
| That's a less specific name than Linux's HID_NAME, but |
| still better than "Logitech Centurion Device". |
\*---------------------------------------------------------*/
std::string friendly;
hid_device_info* devs = hid_enumerate(0x046D, 0x0000);
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path == nullptr)
{
continue;
}
if(std::string(d->path) != path)
{
continue;
}
friendly = StringUtils::wchar_to_string(d->product_string);
break;
}
hid_free_enumeration(devs);
return friendly;
}
bool LogitechHIDPP20Controller::ScanForDevice(bool force)
{
/*---------------------------------------------------------*\
| Scan hidapi for a Logitech device with matching unitId. |
| Called by the power thread either periodically (normal |
| reactive mode — only when device_online==false, USB/ |
| wireless transition or physical unplug) or on demand |
| from the reader thread after a HID++1.0 Device |
| Connection notification arrives (force=true, bypasses |
| the online gate). Finds the device on its new |
| connection path. |
| |
| Reactive-only: we never migrate while the current path |
| still works (same rationale as Linux — see the Linux |
| companion file for the full explanation). |
| |
| Windows implementation: walks hid_enumerate(046D, *) and |
| matches on hid_device_info::serial_number, which for |
| Logitech devices generally corresponds to the same |
| stable identity as the HID++-reported unit_id. If |
| serial_number matching yields nothing (hidapi may not |
| populate it for some Logitech devices on Windows), we |
| fall through to IRoot probing every candidate on the |
| matching usage_page/usage so we can still find the |
| device albeit more slowly. |
\*---------------------------------------------------------*/
if(caps.unit_id.empty() || caps.unit_id == "00000000")
{
return false;
}
if(!force && device_online.load())
{
return false;
}
std::string target_norm = StringUtils::normalize_hex_id(caps.unit_id);
/*---------------------------------------------------------*\
| Candidate = {path, pid, has_serial_match}. Serial matches |
| sort first so we try the cheapest/most-likely candidate. |
\*---------------------------------------------------------*/
struct Candidate
{
std::string dev_path;
unsigned int pid;
bool serial_match;
};
std::vector<Candidate> candidates;
unsigned int current_pid = 0;
hid_device_info* devs = hid_enumerate(0x046D, 0x0000);
/*---------------------------------------------------------*\
| First pass: find the entry matching our current path and |
| record its product_id so we can skip same-PID candidates. |
\*---------------------------------------------------------*/
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path != nullptr && std::string(d->path) == location)
{
current_pid = d->product_id;
break;
}
}
/*---------------------------------------------------------*\
| Second pass: collect candidates that (a) aren't our |
| current path, (b) speak the HID++ interface, and (c) have |
| a plausible identity match — either the serial_number |
| matches our unit_id, or at minimum their PID differs from |
| ours so they can't be another slot on the same dongle. |
\*---------------------------------------------------------*/
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path == nullptr)
{
continue;
}
if(std::string(d->path) == location)
{
continue;
}
/*-----------------------------------------------------*\
| Only HID++ interface (usage_page 0xFF00, usage 2). |
| Note: on Windows hidapi may or may not populate |
| usage / usage_page consistently. If either is zero, |
| fall through — we'll probe unconditionally in that |
| case rather than dropping a potential candidate. |
\*-----------------------------------------------------*/
bool usage_known = (d->usage_page != 0 || d->usage != 0);
if(usage_known && (d->usage_page != 0xFF00 || d->usage != 2))
{
continue;
}
/*-----------------------------------------------------*\
| Compare serial_number (wchar_t*) against our unit_id. |
| Normalize both and do an exact-match comparison. |
| Empty/missing serial is allowed — falls through as a |
| serial_match=false candidate so the probe path can |
| still find it via a different-PID filter. |
\*-----------------------------------------------------*/
bool serial_match = false;
if(d->serial_number != nullptr && d->serial_number[0] != L'\0')
{
std::string sn = StringUtils::wchar_to_string(d->serial_number);
std::string sn_norm = StringUtils::normalize_hex_id(sn);
if(!sn_norm.empty() && sn_norm == target_norm)
{
serial_match = true;
}
}
/*-----------------------------------------------------*\
| If we have no serial match AND this path shares the |
| current PID, skip. Same PID with no identity evidence |
| is probably a sibling slot on the same dongle, not a |
| valid migration target. |
\*-----------------------------------------------------*/
if(!serial_match && d->product_id == current_pid && current_pid != 0)
{
continue;
}
Candidate c;
c.dev_path = d->path;
c.pid = d->product_id;
c.serial_match = serial_match;
candidates.push_back(c);
}
hid_free_enumeration(devs);
if(candidates.empty())
{
return false;
}
/*---------------------------------------------------------*\
| Sort so serial-matched candidates get probed first. |
\*---------------------------------------------------------*/
std::stable_sort(candidates.begin(), candidates.end(),
[](const Candidate& a, const Candidate& b)
{
return a.serial_match && !b.serial_match;
});
/*---------------------------------------------------------*\
| Probe each candidate with IRoot GetFeature feat 0x0001. |
| A live slot returns a long-form (0x11) response; a stale |
| slot returns a short-form (0x10) error. See the Linux |
| companion file for the wire-level explanation. |
\*---------------------------------------------------------*/
std::string found_path;
hid_device* found_dev = nullptr;
for(size_t c = 0; c < candidates.size(); c++)
{
const Candidate& cand = candidates[c];
LOG_DEBUG("%s Scan: migration candidate at %s (pid=0x%04X, serial_match=%d)",
LOG_TAG, cand.dev_path.c_str(), cand.pid,
cand.serial_match ? 1 : 0);
hid_device* test_dev = hid_open_path(cand.dev_path.c_str());
if(!test_dev)
{
LOG_DEBUG("%s Scan: %s failed to open",
LOG_TAG, cand.dev_path.c_str());
continue;
}
uint8_t probe[7] = {0x10, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00};
uint8_t reply[20] = {};
int write_rc = hid_write(test_dev, probe, sizeof(probe));
bool probe_ok = false;
if(write_rc >= 0)
{
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(100);
while(std::chrono::steady_clock::now() < deadline)
{
int read_rc = hid_read_timeout(test_dev, reply, sizeof(reply), 50);
if(read_rc <= 0)
{
continue;
}
if(reply[0] == 0x11 && reply[2] == 0x00 && reply[3] == 0x00)
{
probe_ok = true;
break;
}
if(reply[0] == 0x10 && reply[2] == 0x8F)
{
LOG_DEBUG("%s Scan: %s probe rejected — err=0x%02X (stale slot)",
LOG_TAG, cand.dev_path.c_str(), reply[5]);
break;
}
}
}
if(!probe_ok)
{
hid_close(test_dev);
continue;
}
LOG_DEBUG("%s Scan: %s probe accepted (feat_idx=0x%02X)",
LOG_TAG, cand.dev_path.c_str(), reply[4]);
found_path = cand.dev_path;
found_dev = test_dev;
break;
}
if(!found_dev)
{
return false;
}
LOG_INFO("%s Device migrated: %s -> %s",
LOG_TAG, location.c_str(), found_path.c_str());
SwapHIDHandle(found_dev, found_path);
return true;
}
@@ -0,0 +1,146 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20IdleSettings.cpp |
| |
| Host-side idle/dim/sleep settings storage helper for |
| Logitech HID++ 2.0 devices. Loads the configuration |
| block from SettingsManager JSON, caches it, and writes |
| changes back through SettingsManager::SetSettings(). |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "LogitechHIDPP20IdleSettings.h"
#include "ResourceManager.h"
#include "SettingsManager.h"
static const char* SETTINGS_KEY = "LogitechHIDPP20IdleSettings";
LogitechHIDPP20IdleSettings* LogitechHIDPP20IdleSettings::instance()
{
static LogitechHIDPP20IdleSettings inst;
return &inst;
}
/*---------------------------------------------------------*\
| Minimum idle / sleep values the controller will honor. |
| Matches what Logitech firmware typically clamps to on |
| HID++ 2.0 devices, and guarantees the skip-dim path's |
| sleep_delay math always produces a positive window. |
\*---------------------------------------------------------*/
static const int MIN_IDLE_S = 15;
static const int MIN_SLEEP_S = 45;
static const int MIN_DIM_WINDOW = 30; // sleep must exceed idle by at least this
static void ClampProfile(LogitechHIDPP20IdleProfile& p)
{
if(p.dim_brightness < 0) p.dim_brightness = 0;
if(p.dim_brightness > 100) p.dim_brightness = 100;
if(p.idle_timeout_s < MIN_IDLE_S)
{
p.idle_timeout_s = MIN_IDLE_S;
}
if(p.sleep_timeout_s < MIN_SLEEP_S)
{
p.sleep_timeout_s = MIN_SLEEP_S;
}
if(p.sleep_timeout_s < p.idle_timeout_s + MIN_DIM_WINDOW)
{
p.sleep_timeout_s = p.idle_timeout_s + MIN_DIM_WINDOW;
}
}
static LogitechHIDPP20IdleProfile ProfileFromJson(const json& j)
{
LogitechHIDPP20IdleProfile p;
if(j.contains("dim_when_idle")) p.dim_when_idle = j["dim_when_idle"];
if(j.contains("dim_brightness")) p.dim_brightness = j["dim_brightness"];
if(j.contains("idle_timeout_s")) p.idle_timeout_s = j["idle_timeout_s"];
if(j.contains("allow_sleep")) p.allow_sleep = j["allow_sleep"];
if(j.contains("sleep_timeout_s")) p.sleep_timeout_s = j["sleep_timeout_s"];
ClampProfile(p);
return p;
}
static json ProfileToJson(const LogitechHIDPP20IdleProfile& p)
{
json j;
j["dim_when_idle"] = p.dim_when_idle;
j["dim_brightness"] = p.dim_brightness;
j["idle_timeout_s"] = p.idle_timeout_s;
j["allow_sleep"] = p.allow_sleep;
j["sleep_timeout_s"] = p.sleep_timeout_s;
return j;
}
void LogitechHIDPP20IdleSettings::load()
{
json settings = ResourceManager::get()->GetSettingsManager()->GetSettings(SETTINGS_KEY);
/*---------------------------------------------------------*\
| Empty / missing key means the plugin is not in use. |
| Reset both profiles to defaults with configured=false so |
| the controller defers to firmware. |
\*---------------------------------------------------------*/
if(!settings.is_object() || settings.empty())
{
configured = false;
on_battery = LogitechHIDPP20IdleProfile{};
plugged_in = LogitechHIDPP20IdleProfile{};
return;
}
configured = true;
if(settings.contains("on_battery"))
{
on_battery = ProfileFromJson(settings["on_battery"]);
}
else
{
on_battery = LogitechHIDPP20IdleProfile{};
}
if(settings.contains("plugged_in"))
{
plugged_in = ProfileFromJson(settings["plugged_in"]);
}
else
{
plugged_in = LogitechHIDPP20IdleProfile{};
}
}
void LogitechHIDPP20IdleSettings::save()
{
json settings;
settings["on_battery"] = ProfileToJson(on_battery);
settings["plugged_in"] = ProfileToJson(plugged_in);
SettingsManager* mgr = ResourceManager::get()->GetSettingsManager();
mgr->SetSettings(SETTINGS_KEY, settings);
mgr->SaveSettings();
configured = true;
}
void LogitechHIDPP20IdleSettings::setOnBattery(const LogitechHIDPP20IdleProfile& p)
{
on_battery = p;
configured = true;
}
void LogitechHIDPP20IdleSettings::setPluggedIn(const LogitechHIDPP20IdleProfile& p)
{
plugged_in = p;
configured = true;
}
@@ -0,0 +1,47 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20IdleSettings.h |
| |
| Host-side idle/dim/sleep configuration for Logitech |
| HID++ 2.0 devices. Two profiles (on_battery, plugged_in)|
| selected at runtime based on the device's external- |
| power flag. `configured == false` means the JSON key |
| is absent entirely — the controller defers to firmware. |
| Qt-free so the controller can consume it directly. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
struct LogitechHIDPP20IdleProfile
{
bool dim_when_idle = false;
int dim_brightness = 50;
int idle_timeout_s = 60;
bool allow_sleep = false;
int sleep_timeout_s = 300;
};
class LogitechHIDPP20IdleSettings
{
public:
static LogitechHIDPP20IdleSettings* instance();
void load();
void save();
bool isConfigured() const { return configured; }
const LogitechHIDPP20IdleProfile& onBattery() const { return on_battery; }
const LogitechHIDPP20IdleProfile& pluggedIn() const { return plugged_in; }
void setOnBattery(const LogitechHIDPP20IdleProfile& p);
void setPluggedIn(const LogitechHIDPP20IdleProfile& p);
private:
LogitechHIDPP20IdleSettings() = default;
bool configured = false;
LogitechHIDPP20IdleProfile on_battery;
LogitechHIDPP20IdleProfile plugged_in;
};
@@ -0,0 +1,72 @@
/*---------------------------------------------------------*\
| RGBController_LogitechHIDPP20.h |
| |
| RGBController for unified Logitech HID++ 2.0 devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechHIDPP20Controller.h"
class RGBController_LogitechHIDPP20 : public RGBController
{
public:
RGBController_LogitechHIDPP20(LogitechHIDPP20Controller* controller_ptr);
~RGBController_LogitechHIDPP20();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void DeviceSaveMode();
bool ReapplyActiveMode();
private:
LogitechHIDPP20Controller* controller;
/*---------------------------------------------------------*\
| Repaint callback handler. Registered with the controller |
| as request_repaint_fn and invoked from the power thread |
| for dim/wake when no animation is driving updates. |
\*---------------------------------------------------------*/
void OnRepaintRequest();
/*---------------------------------------------------------*\
| When true, the next DeviceUpdateMode cycle sends its |
| SetZoneEffect calls with persist=true instead of the |
| default ephemeral write. Used by DeviceSaveMode to replay |
| the active mode as a NVM-committed effect on 0x8070 |
| devices, which default to non-persistent live writes. |
\*---------------------------------------------------------*/
bool save_pending = false;
/*---------------------------------------------------------*\
| Maps OpenRGB LED index -> HID++ per-key zone ID |
\*---------------------------------------------------------*/
std::vector<uint16_t> led_to_zone_id;
/*---------------------------------------------------------*\
| Reverse map: zone_id -> LED index (-1 if no LED). |
| Indexed 0..255 (zone IDs are bytes). Built once in |
| SetupZones to avoid scanning led_to_zone_id at commit |
| time, which would be O(N) per acked zone. |
\*---------------------------------------------------------*/
std::vector<int> zone_id_to_led_idx;
/*---------------------------------------------------------*\
| Last successfully committed colors for delta updates. |
| An entry of HIDPP20_UNCOMMITTED (0xFF000000) marks an LED |
| whose last write didn't ACK and which therefore needs to |
| be re-pushed in the next frame regardless of color delta. |
| The high byte (0xFF) is impossible for any value produced |
| by ToRGBColor() so it never collides with a real color. |
\*---------------------------------------------------------*/
std::vector<RGBColor> sent_colors;
uint32_t last_init_gen = 0;
};
@@ -47,6 +47,7 @@ REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4070 Gaming X Slim",
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4070 Gaming X Slim White", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4070_DEV, MSI_SUB_VEN, MSI_RTX4070S_GAMING_X_SLIM_WHITE_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4070 SUPER Gaming X Slim", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, MSI_SUB_VEN, MSI_RTX4070S_GAMING_X_SLIM_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4070 SUPER Gaming X Slim White", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, MSI_SUB_VEN, MSI_RTX4070S_GAMING_X_SLIM_WHITE_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4070 SUPER Gaming X Slim MLG", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4070S_DEV, MSI_SUB_VEN, MSI_RTX4070S_GAMING_X_SLIM_MLG_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4070 Ti Gaming X Trio", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4070TI_DEV, MSI_SUB_VEN, MSI_RTX4070TI_GAMING_X_TRIO_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4070 Ti Gaming X Trio White", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4070TI_DEV, MSI_SUB_VEN, MSI_RTX4070TI_GAMING_X_TRIO_WHITE_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4070 Ti Gaming X Slim White", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4070TI_DEV, MSI_SUB_VEN, MSI_RTX4070TI_GAMING_X_SLIM_WHITE_SUB_DEV, 0x68);
@@ -67,11 +68,13 @@ REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4090 Gaming X Trio",
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4090 Gaming X Slim", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, MSI_SUB_VEN, MSI_RTX4090_GAMING_X_SLIM_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4090 Suprim Liquid X", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, MSI_SUB_VEN, MSI_RTX4090_SUPRIM_LIQUID_X_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 4090 Suprim X", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX4090_DEV, MSI_SUB_VEN, MSI_RTX4090_SUPRIM_X_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5070 Gaming Trio", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5070_DEV, MSI_SUB_VEN, MSI_RTX5070_GAMING_TRIO_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5070 Ti Gaming Trio OC Plus", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, MSI_SUB_VEN, MSI_RTX5070TI_GAMING_TRIO_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5070 Ti VANGUARD SOC", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5070TI_DEV, MSI_SUB_VEN, MSI_RTX5070TI_VANGUARD_SOC_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5080 Gaming Trio OC", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, MSI_SUB_VEN, MSI_RTX5080_GAMING_TRIO_OC_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5080 VANGUARD OC SOC", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, MSI_SUB_VEN, MSI_RTX5080_VANGUARD_SOC_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5080 SUPRIM SOC", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, MSI_SUB_VEN, MSI_RTX5080_SUPRIM_SOC_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5080 SUPRIM LIQUID SOC", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5080_DEV, MSI_SUB_VEN, MSI_RTX5080_SUPRIM_LIQUID_SOC_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5090 Gaming Trio OC", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, MSI_SUB_VEN, MSI_RTX5090_GAMING_TRIO_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5090 VANGUARD SOC", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, MSI_SUB_VEN, MSI_RTX5090_VANGUARD_SOC_SUB_DEV, 0x68);
REGISTER_I2C_PCI_DETECTOR("MSI GeForce RTX 5090 SUPRIM SOC", DetectMSIGPUv2Controllers, NVIDIA_VEN, NVIDIA_RTX5090_DEV, MSI_SUB_VEN, MSI_RTX5090_SUPRIM_SOC_SUB_DEV, 0x68);
@@ -20,7 +20,7 @@
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectMSIMysticLight1565Controller
@detectors DetectMSIKeyboardController
@comment
\*-------------------------------------------------------------------*/
@@ -0,0 +1,95 @@
/*---------------------------------------------------------*\
| MSILaptopController.cpp |
| |
| Driver for MSI laptop SteelSeries KLC/ALC RGB devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "MSILaptopController.h"
#include "StringUtils.h"
#define MSI_LAPTOP_REPORT_ID 0x00
#define MSI_LAPTOP_COMMAND 0x0C
#define MSI_LAPTOP_KLC_PACKET_ID 0x66
#define MSI_LAPTOP_ALC_PACKET_ID 0x06
#define MSI_LAPTOP_PACKET_SIZE 525
#define MSI_LAPTOP_PAYLOAD_OFFSET 5
MSILaptopController::MSILaptopController(hid_device* dev_handle, const char* path, std::string dev_name, msi_laptop_device device_type)
{
dev = dev_handle;
location = path;
name = dev_name;
type = device_type;
}
MSILaptopController::~MSILaptopController()
{
hid_close(dev);
}
std::string MSILaptopController::GetDeviceLocation()
{
return("HID: " + location);
}
std::string MSILaptopController::GetDeviceName()
{
return(name);
}
std::string MSILaptopController::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));
}
msi_laptop_device MSILaptopController::GetDeviceType()
{
return(type);
}
void MSILaptopController::SetLEDs(std::vector<led> leds, std::vector<RGBColor> colors)
{
unsigned char buf[MSI_LAPTOP_PACKET_SIZE];
unsigned int led_count = (leds.size() < colors.size()) ? leds.size() : colors.size();
memset(buf, 0x00, sizeof(buf));
buf[0x00] = MSI_LAPTOP_REPORT_ID;
buf[0x01] = MSI_LAPTOP_COMMAND;
buf[0x03] = (type == MSI_LAPTOP_KLC) ? MSI_LAPTOP_KLC_PACKET_ID : MSI_LAPTOP_ALC_PACKET_ID;
// Fill unused LED IDs with 0xFF so they are ignored by the controller
for(int i = 0; i < (MSI_LAPTOP_PACKET_SIZE - MSI_LAPTOP_PAYLOAD_OFFSET) / 4; i++)
{
buf[MSI_LAPTOP_PAYLOAD_OFFSET + (i * 4)] = 0xFF;
}
for(unsigned int led_idx = 0; led_idx < led_count; led_idx++)
{
unsigned int offset = MSI_LAPTOP_PAYLOAD_OFFSET + (led_idx * 4);
if((offset + 3) >= sizeof(buf))
{
break;
}
buf[offset + 0] = leds[led_idx].value;
buf[offset + 1] = RGBGetRValue(colors[led_idx]);
buf[offset + 2] = RGBGetGValue(colors[led_idx]);
buf[offset + 3] = RGBGetBValue(colors[led_idx]);
}
hid_send_feature_report(dev, buf, sizeof(buf));
}
@@ -0,0 +1,67 @@
/*---------------------------------------------------------*\
| MSILaptopController.h |
| |
| Driver for MSI laptop SteelSeries KLC/ALC RGB devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <hidapi.h>
#include "RGBController.h"
typedef enum
{
MSI_LAPTOP_KLC,
MSI_LAPTOP_ALC,
} msi_laptop_device;
typedef struct
{
const char* name;
unsigned char id;
} msi_laptop_led;
struct MSILaptopModel
{
const char* sys_vendor;
const char* product_name;
/* Keyboard layout */
const msi_laptop_led* klc_leds;
unsigned int klc_leds_count;
unsigned int klc_matrix_height;
unsigned int klc_matrix_width;
const unsigned int* klc_matrix_map;
/* Lightbar layout */
const msi_laptop_led* alc_leds;
unsigned int alc_leds_count;
unsigned int alc_lightbar_leds;
};
class MSILaptopController
{
public:
MSILaptopController(hid_device* dev_handle, const char* path, std::string dev_name, msi_laptop_device device_type);
~MSILaptopController();
std::string GetDeviceLocation();
std::string GetDeviceName();
std::string GetSerialString();
msi_laptop_device GetDeviceType();
void SetLEDs(std::vector<led> leds, std::vector<RGBColor> colors);
private:
hid_device* dev;
std::string location;
std::string name;
msi_laptop_device type;
};
@@ -0,0 +1,235 @@
/*---------------------------------------------------------*\
| MSILaptopControllerDetect.cpp |
| |
| Detector for MSI laptop SteelSeries RGB devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "Detector.h"
#include "RGBControllerKeyNames.h"
#include "RGBController_MSILaptop.h"
#include "MSILaptopController.h"
#include "dmiinfo.h"
#define MSI_LAPTOP_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define STEELSERIES_VID 0x1038
#define STEELSERIES_MSI_RAIDER_A18_KLC_PID 0x1122
#define STEELSERIES_MSI_RAIDER_A18_ALC_PID 0x1161
#define NA 0xFFFFFFFF
#define MSI_LAPTOP_KLC_MATRIX_HEIGHT 6
#define MSI_LAPTOP_KLC_MATRIX_WIDTH 23
#define MSI_LAPTOP_ALC_LIGHTBAR_LEDS 3
static const msi_laptop_led msi_raider_a18_klc_leds[] =
{
{ KEY_EN_A, 0x04 },
{ KEY_EN_B, 0x05 },
{ KEY_EN_C, 0x06 },
{ KEY_EN_D, 0x07 },
{ KEY_EN_E, 0x08 },
{ KEY_EN_F, 0x09 },
{ KEY_EN_G, 0x0A },
{ KEY_EN_H, 0x0B },
{ KEY_EN_I, 0x0C },
{ KEY_EN_J, 0x0D },
{ KEY_EN_K, 0x0E },
{ KEY_EN_L, 0x0F },
{ KEY_EN_M, 0x10 },
{ KEY_EN_N, 0x11 },
{ KEY_EN_O, 0x12 },
{ KEY_EN_P, 0x13 },
{ KEY_EN_Q, 0x14 },
{ KEY_EN_R, 0x15 },
{ KEY_EN_S, 0x16 },
{ KEY_EN_T, 0x17 },
{ KEY_EN_U, 0x18 },
{ KEY_EN_V, 0x19 },
{ KEY_EN_W, 0x1A },
{ KEY_EN_X, 0x1B },
{ KEY_EN_Y, 0x1C },
{ KEY_EN_Z, 0x1D },
{ KEY_EN_1, 0x1E },
{ KEY_EN_2, 0x1F },
{ KEY_EN_3, 0x20 },
{ KEY_EN_4, 0x21 },
{ KEY_EN_5, 0x22 },
{ KEY_EN_6, 0x23 },
{ KEY_EN_7, 0x24 },
{ KEY_EN_8, 0x25 },
{ KEY_EN_9, 0x26 },
{ KEY_EN_0, 0x27 },
{ KEY_EN_ESCAPE, 0x29 },
{ KEY_EN_TAB, 0x2B },
{ KEY_EN_SPACE, 0x2C },
{ KEY_EN_MINUS, 0x2D },
{ KEY_EN_EQUALS, 0x2E },
{ KEY_EN_LEFT_BRACKET, 0x2F },
{ KEY_EN_RIGHT_BRACKET, 0x30 },
{ KEY_EN_SEMICOLON, 0x33 },
{ KEY_EN_QUOTE, 0x34 },
{ KEY_EN_BACK_TICK, 0x35 },
{ KEY_EN_COMMA, 0x36 },
{ KEY_EN_PERIOD, 0x37 },
{ KEY_EN_FORWARD_SLASH, 0x38 },
{ KEY_EN_CAPS_LOCK, 0x39 },
{ KEY_EN_F1, 0x3A },
{ KEY_EN_F2, 0x3B },
{ KEY_EN_F3, 0x3C },
{ KEY_EN_F4, 0x3D },
{ KEY_EN_F5, 0x3E },
{ KEY_EN_F6, 0x3F },
{ KEY_EN_F7, 0x40 },
{ KEY_EN_F8, 0x41 },
{ KEY_EN_F9, 0x42 },
{ KEY_EN_F10, 0x43 },
{ KEY_EN_F11, 0x44 },
{ KEY_EN_F12, 0x45 },
{ KEY_EN_PRINT_SCREEN, 0x46 },
{ KEY_EN_SCROLL_LOCK, 0x47 },
{ KEY_EN_INSERT, 0x49 },
{ "Home/Page Up", 0x4B },
{ KEY_EN_DELETE, 0x4C },
{ KEY_EN_PAGE_DOWN, 0x4E },
{ KEY_EN_RIGHT_ARROW, 0x4F },
{ KEY_EN_LEFT_ARROW, 0x50 },
{ KEY_EN_DOWN_ARROW, 0x51 },
{ KEY_EN_UP_ARROW, 0x52 },
{ KEY_EN_NUMPAD_LOCK, 0x53 },
{ KEY_EN_NUMPAD_DIVIDE, 0x54 },
{ KEY_EN_NUMPAD_TIMES, 0x55 },
{ KEY_EN_NUMPAD_MINUS, 0x56 },
{ KEY_EN_NUMPAD_PLUS, 0x57 },
{ KEY_EN_NUMPAD_ENTER, 0x58 },
{ KEY_EN_NUMPAD_1, 0x59 },
{ KEY_EN_NUMPAD_2, 0x5A },
{ KEY_EN_NUMPAD_3, 0x5B },
{ KEY_EN_NUMPAD_4, 0x5C },
{ KEY_EN_NUMPAD_5, 0x5D },
{ KEY_EN_NUMPAD_6, 0x5E },
{ KEY_EN_NUMPAD_7, 0x5F },
{ KEY_EN_NUMPAD_8, 0x60 },
{ KEY_EN_NUMPAD_9, 0x61 },
{ KEY_EN_NUMPAD_0, 0x62 },
{ KEY_EN_NUMPAD_PERIOD, 0x63 },
{ KEY_EN_POWER, 0x66 },
{ KEY_EN_LEFT_CONTROL, 0xE0 },
{ KEY_EN_LEFT_SHIFT, 0xE1 },
{ KEY_EN_LEFT_ALT, 0xE2 },
{ KEY_EN_LEFT_WINDOWS, 0xE3 },
{ KEY_EN_RIGHT_WINDOWS, 0xE4 },
{ KEY_EN_RIGHT_FUNCTION, 0xF0 },
{ KEY_EN_ANSI_ENTER, 0x28 },
{ KEY_EN_BACKSPACE, 0x2A },
{ KEY_EN_BACK_SLASH, 0x31 },
{ KEY_EN_ISO_BACK_SLASH, 0x64 },
{ KEY_EN_RIGHT_SHIFT, 0xE5 },
{ KEY_EN_RIGHT_ALT, 0xE6 },
};
static unsigned int msi_raider_a18_klc_matrix_map[MSI_LAPTOP_KLC_MATRIX_HEIGHT][MSI_LAPTOP_KLC_MATRIX_WIDTH] =
{
{ 36, NA, 50, 51, 52, 53, NA, 54, 55, 56, 57, NA, 58, 59, 60, 61, 62, 63, NA, NA, NA, NA, 89 },
{ 45, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 39, 40, 97, NA, 64, 65, NA, 72, 73, 74, 75, NA },
{ 37, 16, 22, 4, 17, 19, 24, 20, 8, 14, 15, 41, 42, 98, NA, 66, 67, NA, 84, 85, 86, 76, NA },
{ 49, 0, 18, 3, 5, 6, 7, 9, 10, 11, 43, 44, 96, NA, NA, NA, NA, NA, 81, 82, 83, NA, NA },
{ 91, 25, 23, 2, 21, 1, 13, 12, 46, 47, 48,100, NA, NA, NA, NA, 71, NA, 78, 79, 80, 77, NA },
{ 90, 93, 92, NA, NA, NA, 38, NA, NA, NA,101, 94, 95, NA, 69, 70, 68, NA, 87, NA, 88, NA, NA },
};
/*---------------------------------------------------------*\
| Note on Raider A18 HX ALC (Lightbar) LED mappings: |
| Initial reverse engineering packet captures showed 6 zones|
| being updated (indexes 0x00 to 0x05). |
| However, indexes 0x04 (L4) and 0x05 (L5) are "dummy" zones|
| on this specific model and do not correspond to physical |
| LEDs. To avoid user confusion, they are omitted from the |
| UI profile here. |
| The SetLEDs function handles zero-padding the unused |
| payload slots with 0xFF so that omitting these zones |
| doesn't accidentally overwrite the 0x00 index (L1). |
\*---------------------------------------------------------*/
static const msi_laptop_led msi_raider_a18_alc_leds[] =
{
{ "Lightbar 1", 0x00 },
{ "Lightbar 2", 0x01 },
{ "Lightbar 3", 0x02 },
{ "Logo", 0x03 },
};
static const MSILaptopModel msi_laptop_models[] =
{
{
"Micro-Star International Co., Ltd.",
"Raider A18 HX A9WJG",
/* Keyboard layout */
msi_raider_a18_klc_leds,
MSI_LAPTOP_ARRAY_SIZE(msi_raider_a18_klc_leds),
MSI_LAPTOP_KLC_MATRIX_HEIGHT,
MSI_LAPTOP_KLC_MATRIX_WIDTH,
(const unsigned int*)msi_raider_a18_klc_matrix_map,
/* Lightbar layout */
msi_raider_a18_alc_leds,
MSI_LAPTOP_ARRAY_SIZE(msi_raider_a18_alc_leds),
MSI_LAPTOP_ALC_LIGHTBAR_LEDS,
},
};
static const MSILaptopModel* GetMSILaptopModelDMI()
{
DMIInfo dmi;
for(unsigned int i = 0; i < MSI_LAPTOP_ARRAY_SIZE(msi_laptop_models); i++)
{
if((dmi.getManufacturer() == msi_laptop_models[i].sys_vendor) &&
(dmi.getProductName() == msi_laptop_models[i].product_name))
{
return &msi_laptop_models[i];
}
}
return nullptr;
}
void DetectMSILaptop(hid_device_info* info, const std::string& name)
{
const MSILaptopModel* model = GetMSILaptopModelDMI();
if(!model)
{
return;
}
msi_laptop_device device_type;
if(info->product_id == STEELSERIES_MSI_RAIDER_A18_KLC_PID)
{
device_type = MSI_LAPTOP_KLC;
}
else if(info->product_id == STEELSERIES_MSI_RAIDER_A18_ALC_PID)
{
device_type = MSI_LAPTOP_ALC;
}
else
{
return;
}
hid_device* dev = hid_open_path(info->path);
if(dev)
{
MSILaptopController* controller = new MSILaptopController(dev, info->path, name, device_type);
RGBController_MSILaptop* rgb_controller = new RGBController_MSILaptop(controller, model);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
REGISTER_HID_DETECTOR("MSI Laptop Keyboard", DetectMSILaptop, STEELSERIES_VID, STEELSERIES_MSI_RAIDER_A18_KLC_PID);
REGISTER_HID_DETECTOR_I("MSI Laptop Lightbar", DetectMSILaptop, STEELSERIES_VID, STEELSERIES_MSI_RAIDER_A18_ALC_PID, 0);
@@ -0,0 +1,141 @@
/*---------------------------------------------------------*\
| RGBController_MSILaptop.cpp |
| |
| RGBController for MSI laptop SteelSeries RGB devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBControllerKeyNames.h"
#include "RGBController_MSILaptop.h"
#define NA 0xFFFFFFFF
/**------------------------------------------------------------------*\
@name MSI Laptop SteelSeries RGB
@category Keyboard
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectMSILaptop
@comment
\*-------------------------------------------------------------------*/
RGBController_MSILaptop::RGBController_MSILaptop(MSILaptopController* controller_ptr, const MSILaptopModel* model_ptr)
{
controller = controller_ptr;
model = model_ptr;
name = controller->GetDeviceName();
vendor = "SteelSeries";
description = std::string(model->sys_vendor) + " " + std::string(model->product_name) + " RGB Device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
type = (controller->GetDeviceType() == MSI_LAPTOP_KLC) ? DEVICE_TYPE_KEYBOARD : DEVICE_TYPE_LEDSTRIP;
mode Direct;
Direct.name = "Direct";
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
}
RGBController_MSILaptop::~RGBController_MSILaptop()
{
for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
if(zones[zone_idx].matrix_map != NULL)
{
delete zones[zone_idx].matrix_map;
}
}
delete controller;
}
void RGBController_MSILaptop::SetupZones()
{
if(controller->GetDeviceType() == MSI_LAPTOP_KLC)
{
zone keyboard_zone;
keyboard_zone.name = ZONE_EN_KEYBOARD;
keyboard_zone.type = ZONE_TYPE_MATRIX;
keyboard_zone.leds_min = model->klc_leds_count;
keyboard_zone.leds_max = model->klc_leds_count;
keyboard_zone.leds_count = model->klc_leds_count;
keyboard_zone.matrix_map = new matrix_map_type;
keyboard_zone.matrix_map->height = model->klc_matrix_height;
keyboard_zone.matrix_map->width = model->klc_matrix_width;
keyboard_zone.matrix_map->map = (unsigned int *)model->klc_matrix_map;
zones.push_back(keyboard_zone);
for(unsigned int led_idx = 0; led_idx < model->klc_leds_count; led_idx++)
{
led new_led;
new_led.name = model->klc_leds[led_idx].name;
new_led.value = model->klc_leds[led_idx].id;
leds.push_back(new_led);
}
}
else
{
zone lightbar_zone;
lightbar_zone.name = "Lightbar";
lightbar_zone.type = ZONE_TYPE_LINEAR;
lightbar_zone.start_idx = 0;
lightbar_zone.leds_min = model->alc_lightbar_leds;
lightbar_zone.leds_max = model->alc_lightbar_leds;
lightbar_zone.leds_count = model->alc_lightbar_leds;
lightbar_zone.matrix_map = NULL;
zones.push_back(lightbar_zone);
zone logo_zone;
logo_zone.name = "Logo";
logo_zone.type = ZONE_TYPE_SINGLE;
logo_zone.start_idx = model->alc_lightbar_leds;
logo_zone.leds_min = 1;
logo_zone.leds_max = 1;
logo_zone.leds_count = 1;
logo_zone.matrix_map = NULL;
zones.push_back(logo_zone);
for(unsigned int led_idx = 0; led_idx < model->alc_leds_count; led_idx++)
{
led new_led;
new_led.name = model->alc_leds[led_idx].name;
new_led.value = model->alc_leds[led_idx].id;
leds.push_back(new_led);
}
}
SetupColors();
}
void RGBController_MSILaptop::ResizeZone(int /*zone*/, int /*new_size*/)
{
}
void RGBController_MSILaptop::DeviceUpdateLEDs()
{
controller->SetLEDs(leds, colors);
}
void RGBController_MSILaptop::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_MSILaptop::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_MSILaptop::DeviceUpdateMode()
{
DeviceUpdateLEDs();
}
@@ -0,0 +1,34 @@
/*---------------------------------------------------------*\
| RGBController_MSILaptop.h |
| |
| RGBController for MSI laptop SteelSeries RGB devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "MSILaptopController.h"
class RGBController_MSILaptop : public RGBController
{
public:
RGBController_MSILaptop(MSILaptopController* controller_ptr, const MSILaptopModel* model_ptr);
~RGBController_MSILaptop();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
MSILaptopController* controller;
const MSILaptopModel* model;
};
@@ -0,0 +1,121 @@
/*---------------------------------------------------------*\
| RGBController_MSIMonitor.cpp |
| |
| RGBController for MSI monitor (gaming controller) |
| |
| Andy Herbert 2026 June 4 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <string.h>
#include "MSIMonitorController.h"
#include "StringUtils.h"
MSIMonitorController::MSIMonitorController(hid_device* dev_handle, const hid_device_info& info, std::string dev_name)
{
dev = dev_handle;
location = info.path;
name = dev_name;
}
MSIMonitorController::~MSIMonitorController()
{
hid_close(dev);
}
std::string MSIMonitorController::GetDeviceLocation()
{
return("HID: " + location);
}
std::string MSIMonitorController::GetNameString()
{
return(name);
}
std::string MSIMonitorController::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void MSIMonitorController::Set(uint8_t mode_value, const std::vector<RGBColor> colors, uint8_t last_bit)
{
/*---------------------------------------------------------*\
| Prepare color data |
\*---------------------------------------------------------*/
uint8_t data[MSI_MONITOR_PACKET_SIZE];
memset(data, 0x00, MSI_MONITOR_PACKET_SIZE);
unsigned int offset = 0;
data[offset++] = 0x71;
data[offset++] = 0x01;
for(int i = 0; i < 3; i++)
{
data[offset++] = 0x00;
}
data[offset++] = 0x01;
data[offset++] = 0x64;
for(int i = 0; i < 5; i++)
{
data[offset++] = 0x00;
}
/*---------------------------------------------------------*\
| put mode_value |
\*---------------------------------------------------------*/
data[offset++] = mode_value;
for(int i = 0; i < 3; i++)
{
data[offset++] = 0x00;
}
data[offset++] = 0x01;
data[offset++] = 0x64;
for(int i = 0; i < 5; i++)
{
data[offset++] = 0x00;
}
/*-----------------------------------------------------------------------*\
| this data looks like placeholder for additional LEDs in other monitors |
\*-----------------------------------------------------------------------*/
for(int i = 0; i < 9; i++)
{
data[offset++] = 0xff;
data[offset++] = 0x00;
data[offset++] = 0x00;
}
//RGB values begin
for(const RGBColor color: colors)
{
data[offset++] = RGBGetRValue(color);
data[offset++] = RGBGetGValue(color);
data[offset++] = RGBGetBValue(color);
}
/*-------------------------------------------------------------------------------*\
| last bit is probably a write to device bit- 0x01 saves to device, 0x00 doesn't. |
| For direct mode, bit is set to 0x00, otherwise lights will flicker |
\*-------------------------------------------------------------------------------*/
data[offset++] = last_bit;
/*---------------------------------------------------------*\
| Send the data (1 packet) |
\*---------------------------------------------------------*/
hid_send_feature_report(dev, data, MSI_MONITOR_PACKET_SIZE);
}
@@ -0,0 +1,53 @@
/*---------------------------------------------------------*\
| RGBController_MSIMonitor.cpp |
| |
| RGBController for MSI monitor (gaming controller) |
| |
| Andy Herbert 2026 June 1 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
#define MSI_MONITOR_LEDS 9
#define MSI_MONITOR_PACKET_SIZE 78
enum
{
MSI_MONITOR_OFF_MODE_VALUE = 0x00,
MSI_MONITOR_STATIC_MODE_VALUE = 0x01,
MSI_MONITOR_BREATHING_MODE_VALUE = 0x02,
MSI_MONITOR_FLASHING_MODE_VALUE = 0x03,
MSI_MONITOR_LIGHTNING_MODE_VALUE = 0x05,
MSI_MONITOR_MARQUEE_MODE_VALUE = 0x06,
MSI_MONITOR_METEOR_MODE_VALUE = 0x08,
MSI_MONITOR_RAINBOW_MODE_VALUE = 0x1A,
MSI_MONITOR_RANDOM_MODE_VALUE = 0x1F
};
class MSIMonitorController
{
public:
MSIMonitorController(hid_device *dev_handle, const hid_device_info &info, std::string dev_name);
~MSIMonitorController();
std::string GetDeviceLocation();
std::string GetFirmwareVersion();
std::string GetNameString();
std::string GetSerialString();
void Set(uint8_t mode_value, const std::vector<RGBColor> colors, uint8_t last_bit);
private:
hid_device *dev;
std::string description;
std::string location;
std::string name;
std::string version;
};
@@ -0,0 +1,38 @@
/*---------------------------------------------------------*\
| MSIKeyboardControllerDetect.cpp |
| |
| Detector for MSI monitor (MSI Gaming Controller) |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "Detector.h"
#include "MSIMonitorController.h"
#include "RGBController_MSIMonitor.h"
#define MSI_USB_VID 0x1462
#define MSI_USB_PID 0x3FA4
/*----------------------------------------------------------*\
| |
| DetectMSIMonitorController |
| |
| Detect MSI monitor and maybe others |
| |
\*----------------------------------------------------------*/
static void DetectMSIMonitorController(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
MSIMonitorController* controller = new MSIMonitorController(dev, *info, name);
RGBController_MSIMonitor* rgb_controller = new RGBController_MSIMonitor(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
REGISTER_HID_DETECTOR_IPU("MSI Monitor (Gaming Controller)", DetectMSIMonitorController, MSI_USB_VID, MSI_USB_PID, 0, 0x01, 0);
@@ -0,0 +1,171 @@
/*---------------------------------------------------------*\
| RGBController_MSIMonitor.cpp |
| |
| RGBController for MSI monitor (gaming controller) |
| |
| Andy Herbert 2026 June 1 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <chrono>
#include <thread>
#include "RGBController_MSIMonitor.h"
using namespace std::chrono_literals;
/**------------------------------------------------------------------*\
@name MSIMonitor
@category Accessory
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectMSIMonitorController
@comment Developed with MSI MAG272CQR
\*-------------------------------------------------------------------*/
RGBController_MSIMonitor::RGBController_MSIMonitor(MSIMonitorController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "MSI";
type = DEVICE_TYPE_MONITOR;
description = "MSI Monitor (Gaming Controller)";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = MSI_MONITOR_STATIC_MODE_VALUE;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = MSI_MONITOR_STATIC_MODE_VALUE;
Static.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Static.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Static);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = MSI_MONITOR_BREATHING_MODE_VALUE;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Breathing.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Breathing);
mode Flashing;
Flashing.name = "Flashing";
Flashing.value = MSI_MONITOR_FLASHING_MODE_VALUE;
Flashing.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Flashing.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Flashing);
mode Lightning;
Lightning.name = "Lightning";
Lightning.value = MSI_MONITOR_LIGHTNING_MODE_VALUE;
Lightning.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Lightning.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Lightning);
mode Marquee;
Marquee.name = "Marquee";
Marquee.value = MSI_MONITOR_MARQUEE_MODE_VALUE;
Marquee.flags = MODE_FLAG_AUTOMATIC_SAVE;
Marquee.color_mode = MODE_COLORS_NONE;
modes.push_back(Marquee);
mode Meteor;
Meteor.name = "Meteor";
Meteor.value = MSI_MONITOR_METEOR_MODE_VALUE;
Meteor.flags = MODE_FLAG_AUTOMATIC_SAVE;
Meteor.color_mode = MODE_COLORS_NONE;
modes.push_back(Meteor);
mode Rainbow;
Rainbow.name = "Rainbow";
Rainbow.value = MSI_MONITOR_RAINBOW_MODE_VALUE;
Rainbow.flags = MODE_FLAG_AUTOMATIC_SAVE;
Rainbow.color_mode = MODE_COLORS_NONE;
modes.push_back(Rainbow);
mode Random;
Random.name = "Random";
Random.value = MSI_MONITOR_RANDOM_MODE_VALUE;
Random.flags = MODE_FLAG_AUTOMATIC_SAVE;
Random.color_mode = MODE_COLORS_RANDOM;
modes.push_back(Random);
mode Off;
Off.name = "Off";
Off.value = MSI_MONITOR_OFF_MODE_VALUE;
Off.flags = MODE_FLAG_AUTOMATIC_SAVE;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
SetupZones();
}
RGBController_MSIMonitor::~RGBController_MSIMonitor()
{
keepalive_thread_run = 0;
keepalive_thread->join();
delete keepalive_thread;
delete controller;
}
void RGBController_MSIMonitor::SetupZones()
{
zone new_zone;
new_zone.name = "Rear";
new_zone.type = ZONE_TYPE_LINEAR;
new_zone.leds_min = 9;
new_zone.leds_max = 9;
new_zone.leds_count = 9;
new_zone.matrix_map = nullptr;
zones.emplace_back(new_zone);
for(unsigned int i = 0 ; i < 9; i ++)
{
led new_led;
new_led.name = "LED " + std::to_string(i + 1);
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_MSIMonitor::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_MSIMonitor::DeviceUpdateLEDs()
{
last_update_time = std::chrono::steady_clock::now();
controller->Set(modes[active_mode].value, colors, active_mode == 0 ? 0x00 : 0x01);
}
void RGBController_MSIMonitor::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_MSIMonitor::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_MSIMonitor::DeviceUpdateMode()
{
controller->Set(modes[active_mode].value, colors, 0x01);
}
@@ -0,0 +1,38 @@
/*---------------------------------------------------------*\
| RGBController_MSIMonitor.cpp |
| |
| RGBController for MSI monitor (gaming controller) |
| |
| Andy Herbert 2026 May 16 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <chrono>
#include "RGBController.h"
#include "MSIMonitorController.h"
class RGBController_MSIMonitor : public RGBController
{
public:
RGBController_MSIMonitor(MSIMonitorController* controller_ptr);
~RGBController_MSIMonitor();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
MSIMonitorController* controller;
std::thread* keepalive_thread;
std::atomic<bool> keepalive_thread_run;
std::chrono::time_point<std::chrono::steady_clock> last_update_time;
void KeepaliveThread();
};
@@ -232,6 +232,18 @@ const std::vector<MSI_ZONE> zones_set20 =
MSI_ZONE_J_RGB_1,
};
const std::vector<MSI_ZONE> zones_set21 =
{
MSI_ZONE_J_RGB_1,
MSI_ZONE_J_RGB_2,
MSI_ZONE_J_RAINBOW_1,
MSI_ZONE_J_RAINBOW_2,
MSI_ZONE_J_CORSAIR,
MSI_ZONE_J_PIPE_1,
MSI_ZONE_J_PIPE_2,
MSI_ZONE_ON_BOARD_LED_0
};
/*---------------------------------------------------------------------------------------------------------------------------------*\
| Definition of the board sepcific configurations (number of onboard LEDs and supported zones). |
@@ -295,6 +307,7 @@ static const mystic_light_185_config board_configs[] =
{ 0x7D32, 1, 0, 0, 1, &zones_set10, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MAG Z690 TOMAHAWK WIFI DDR4
{ 0x7D33, 6, 0, 0, 2, &zones_set5, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // PRO Z790-VC WIFI
{ 0x7D36, 6, 0, 0, 2, &zones_set5, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // PRO Z690-P DDR4
{ 0x7D37, 6, 0, 0, 2, &zones_set11, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // PRO B760M-VC WIFI (MS-7D37) - fans on JRAINBOW2
{ 0x7D38, 0, 0, 0, 1, &zones_set1, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MEG Z590 UNIFY-X
{ 0x7D40, 0, 0, 0, 1, &zones_set5, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MPG B760i EDGE WIFI DDR4
{ 0x7D41, 6, 0, 0, 2, &zones_set13, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MAG B660M TOMAHAWK WIFI DDR4
@@ -333,10 +346,12 @@ static const mystic_light_185_config board_configs[] =
{ 0x7E09, 0, 0, 0, 0, &zones_set19, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // B650M PROJECT ZERO
{ 0x7E10, 0, 6, 0, 2, &zones_set17, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MPG B650 EDGE WIFI
{ 0x7E12, 0, 0, 0, 2, &zones_set13, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MAG X670E TOMAHAWK WIFI
{ 0x0076, 0, 0, 0, 2, &zones_set13, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // MAG X670E TOMAHAWK WIFI (Common PID)
{ 0x7E16, 0, 0, 0, 2, &zones_set13, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // X670E GAMING PLUS WIFI
{ 0x7E24, 0, 0, 0, 2, &zones_set13, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // B650M GAMING PLUS WIFI
{ 0x7E26, 0, 0, 0, 2, &zones_set13, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // B650 GAMING PLUS WIFI
{ 0x7E27, 0, 0, 0, 2, &zones_set13, MSIMysticLight185Controller::DIRECT_MODE_PER_LED }, // PRO B650M-P
{ 0x7E28, 6, 0, 0, 2, &zones_set21, MSIMysticLight185Controller::DIRECT_MODE_ZONE_BASED }, // MSI PRO A620M-B (MS-7E28)
};
@@ -704,6 +719,19 @@ bool MSIMysticLight185Controller::Update
}
else
{
/*-----------------------------------------------------*\
| Save new state, read current state from board, |
| send old state first, then new state. |
| Windows Mystic Light sends two consecutive reports. |
\*-----------------------------------------------------*/
FeaturePacket_185 new_data = data;
memcpy((unsigned char*)&new_data, (unsigned char*)&data, sizeof(data));
ReadSettings();
data.save_data = save;
hid_send_feature_report(dev, (unsigned char*)&data, sizeof(data));
memcpy((unsigned char*)&data, (unsigned char*)&new_data, sizeof(data));
data.save_data = save;
return (hid_send_feature_report(dev, (unsigned char*)&data, sizeof(data)) == sizeof(data));
}
@@ -478,7 +478,7 @@ void RGBController_MSIMysticLight185::GetDeviceConfig()
if(zones[i].colors != nullptr)
{
for(size_t j = 0; j < zones[i].leds_count; ++j)
for(size_t j = 0; j < GetLEDsInZone((unsigned int)i); ++j)
{
zones[i].colors[j] = color;
}

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