From 7fc8aea930dd6b9f10faccf02e6c3a31ab13d4d9 Mon Sep 17 00:00:00 2001 From: Ken Sanislo Date: Tue, 21 Jul 2026 04:22:02 +0000 Subject: [PATCH] MSIMonitorController: 0x72 layout support (MPG 322URX QD-OLED + siblings) --- .../MSIMonitorController.cpp | 125 +++- .../MSIMonitorController.h | 91 ++- .../MSIMonitorControllerDetect.cpp | 136 ++++- .../RGBController_MSIMonitor.cpp | 9 - .../RGBController_MSIMonitor.h | 8 +- .../RGBController_MSIMonitor72.cpp | 570 ++++++++++++++++++ .../RGBController_MSIMonitor72.h | 40 ++ 7 files changed, 955 insertions(+), 24 deletions(-) create mode 100644 Controllers/MSIMonitorController/RGBController_MSIMonitor72.cpp create mode 100644 Controllers/MSIMonitorController/RGBController_MSIMonitor72.h diff --git a/Controllers/MSIMonitorController/MSIMonitorController.cpp b/Controllers/MSIMonitorController/MSIMonitorController.cpp index 9c9c4ed2c..b98ccbc4d 100644 --- a/Controllers/MSIMonitorController/MSIMonitorController.cpp +++ b/Controllers/MSIMonitorController/MSIMonitorController.cpp @@ -1,9 +1,10 @@ /*---------------------------------------------------------*\ -| RGBController_MSIMonitor.cpp | +| MSIMonitorController.cpp | | | -| RGBController for MSI monitor (gaming controller) | +| Driver for MSI monitor (gaming controller) | | | | Andy Herbert 2026 June 4 | +| Ken Sanislo 2026 July 20 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | @@ -119,3 +120,123 @@ void MSIMonitorController::Set(uint8_t mode_value, const std::vector c hid_send_feature_report(dev, data, MSI_MONITOR_PACKET_SIZE); } + +uint8_t MSIMonitorController::GetLayoutVersion() +{ + /*-----------------------------------------------------*\ + | Ask the MCU which buffer layout it speaks. Request | + | is [0x01][opcode] zero padded, reply is [0x01][0x5A] | + | then data with the version at byte 3. | + \*-----------------------------------------------------*/ + uint8_t request[MSI_MONITOR_QUERY_PACKET_SIZE]; + uint8_t reply[MSI_MONITOR_QUERY_PACKET_SIZE]; + + /*-----------------------------------------------------*\ + | Unimplemented opcodes never answer, so a reply left | + | queued by an earlier exchange would be read here and | + | parsed as this query's answer. Drain first. | + \*-----------------------------------------------------*/ + while(hid_read_timeout(dev, reply, MSI_MONITOR_QUERY_PACKET_SIZE, 0) > 0) + { + continue; + } + + memset(request, 0x00, MSI_MONITOR_QUERY_PACKET_SIZE); + + request[0] = MSI_MONITOR_QUERY_REPORT_ID; + request[1] = MSI_MONITOR_OPCODE_GET_VERSION; + + if(hid_write(dev, request, MSI_MONITOR_QUERY_PACKET_SIZE) < 0) + { + return(MSI_MONITOR_VERSION_UNKNOWN); + } + + memset(reply, 0x00, MSI_MONITOR_QUERY_PACKET_SIZE); + + if(hid_read_timeout(dev, reply, MSI_MONITOR_QUERY_PACKET_SIZE, 500) <= 0) + { + return(MSI_MONITOR_VERSION_UNKNOWN); + } + + if(reply[1] != MSI_MONITOR_QUERY_ACK) + { + return(MSI_MONITOR_VERSION_UNKNOWN); + } + + return(reply[3]); +} + +void MSIMonitorController::SetMode72(uint8_t mode_value, uint8_t speed, uint8_t brightness, RGBColor color1, RGBColor color2, bool user_palette, bool save, const std::vector& led_colors, bool fill_both_arrays) +{ + uint8_t data[MSI_MONITOR_72_PACKET_SIZE]; + memset(data, 0x00, MSI_MONITOR_72_PACKET_SIZE); + + data[0] = 0x72; + + /*-----------------------------------------------------*\ + | Identical 11 byte control blocks at index 1 and 12 | + \*-----------------------------------------------------*/ + for(unsigned int base = 1; base <= 12; base += 11) + { + data[base + 0] = mode_value; + data[base + 1] = RGBGetRValue(color1); + data[base + 2] = RGBGetGValue(color1); + data[base + 3] = RGBGetBValue(color1); + data[base + 4] = speed; + data[base + 5] = brightness; + data[base + 6] = RGBGetRValue(color2); + data[base + 7] = RGBGetGValue(color2); + data[base + 8] = RGBGetBValue(color2); + data[base + 9] = user_palette ? 0x80 : 0x00; + data[base + 10] = 0x00; + } + + /*-----------------------------------------------------*\ + | The engine paints from the second inline colour array | + | at index 95, not from Colour1 and not from the first | + | array at index 23: on a 322URX a buffer carrying only | + | Colour1 renders black, and walking one lit slot | + | through the buffer only lights LEDs for the second | + | array. That pairs with the control block at index 12 | + | being the operative one. Built in palette modes | + | leave the array clear so the firmware effect is not | + | painted over. | + | | + | fill_both_arrays also writes the first array at index | + | 23, for the dual block versions (11/13/15) whose two | + | control blocks each drive their own run. On a single | + | block layout the first array is inert, so filling it | + | is harmless. Those versions also route colour index | + | above 15 to a separate DragonShield emblem region not | + | handled here - one reason they stay disabled. | + \*-----------------------------------------------------*/ + if(user_palette) + { + for(unsigned int slot = 0; slot < MSI_MONITOR_72_ARRAY_SLOTS && slot < led_colors.size(); slot++) + { + unsigned int offset_b = MSI_MONITOR_72_ARRAY_B_INDEX + (slot * 3); + + data[offset_b + 0] = RGBGetRValue(led_colors[slot]); + data[offset_b + 1] = RGBGetGValue(led_colors[slot]); + data[offset_b + 2] = RGBGetBValue(led_colors[slot]); + + if(fill_both_arrays) + { + unsigned int offset_a = MSI_MONITOR_72_ARRAY_A_INDEX + (slot * 3); + + data[offset_a + 0] = RGBGetRValue(led_colors[slot]); + data[offset_a + 1] = RGBGetGValue(led_colors[slot]); + data[offset_a + 2] = RGBGetBValue(led_colors[slot]); + } + } + } + + /*-----------------------------------------------------*\ + | Final byte is store to flash: 0x01 persists across | + | power cycles, 0x00 applies without a flash write. | + | Keep 0x00 for streaming to avoid flicker and wear. | + \*-----------------------------------------------------*/ + data[MSI_MONITOR_72_STORE_INDEX] = save ? 0x01 : 0x00; + + hid_send_feature_report(dev, data, MSI_MONITOR_72_PACKET_SIZE); +} diff --git a/Controllers/MSIMonitorController/MSIMonitorController.h b/Controllers/MSIMonitorController/MSIMonitorController.h index 6df4437b0..591ab13e4 100644 --- a/Controllers/MSIMonitorController/MSIMonitorController.h +++ b/Controllers/MSIMonitorController/MSIMonitorController.h @@ -1,9 +1,10 @@ /*---------------------------------------------------------*\ -| RGBController_MSIMonitor.cpp | +| MSIMonitorController.h | | | -| RGBController for MSI monitor (gaming controller) | +| Driver for MSI monitor (gaming controller) | | | | Andy Herbert 2026 June 1 | +| Ken Sanislo 2026 July 20 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | @@ -18,6 +19,42 @@ #define MSI_MONITOR_LEDS 9 #define MSI_MONITOR_PACKET_SIZE 78 +/*---------------------------------------------------------*\ +| 0x72 layout (dual control block, e.g. MPG 322URX QD-OLED) | +\*---------------------------------------------------------*/ +#define MSI_MONITOR_72_PACKET_SIZE 168 +#define MSI_MONITOR_72_STORE_INDEX 167 +#define MSI_MONITOR_72_ARRAY_A_INDEX 23 +#define MSI_MONITOR_72_ARRAY_B_INDEX 95 +#define MSI_MONITOR_72_ARRAY_SLOTS 24 + +/*---------------------------------------------------------*\ +| Fill order the firmware expects (vendor Direction enum). | +| Only the physical origin differs; index 0 is that end. | +| The controller mirrors RightToLeft into left origin so | +| OpenRGB LED 0 is always the left/start. Circle and Ctype | +| are non linear shapes: driven as a plain line for now, | +| they want a custom layout once someone maps the shape. | +\*---------------------------------------------------------*/ +enum +{ + MSI_MONITOR_72_DIR_RIGHT_TO_LEFT = 0, + MSI_MONITOR_72_DIR_LEFT_TO_RIGHT = 1, + MSI_MONITOR_72_DIR_UP_TO_DOWN = 2, + MSI_MONITOR_72_DIR_CIRCLE = 3, + MSI_MONITOR_72_DIR_CTYPE = 4, +}; + +/*---------------------------------------------------------*\ +| Layout version query rides the short report channel: | +| write [0x01][opcode], read [0x01][0x5A][data] | +\*---------------------------------------------------------*/ +#define MSI_MONITOR_QUERY_PACKET_SIZE 64 +#define MSI_MONITOR_QUERY_REPORT_ID 0x01 +#define MSI_MONITOR_QUERY_ACK 0x5A +#define MSI_MONITOR_OPCODE_GET_VERSION 0xB0 +#define MSI_MONITOR_VERSION_UNKNOWN 0 + enum { MSI_MONITOR_OFF_MODE_VALUE = 0x00, @@ -31,6 +68,53 @@ enum MSI_MONITOR_RANDOM_MODE_VALUE = 0x1F }; +/*---------------------------------------------------------*\ +| 0x72 layout mode values. These match the MSI Mystic | +| Light motherboard mode enum value for value (see | +| MSIMysticLightController/MSIMysticLightCommon.h): the | +| monitor runs the same Nuvoton Mystic Light engine, so the | +| effect numbering is shared with its motherboard sibling. | +| Values 10-14 and 32 are the audio modes, not exposed | +| here (they need the music level feed). | +\*---------------------------------------------------------*/ +enum +{ + MSI_MONITOR_72_MODE_OFF = 0, + MSI_MONITOR_72_MODE_STATIC = 1, + MSI_MONITOR_72_MODE_BREATHING = 2, + MSI_MONITOR_72_MODE_FLASHING = 3, + MSI_MONITOR_72_MODE_DOUBLE_FLASHING = 4, + MSI_MONITOR_72_MODE_LIGHTNING = 5, + MSI_MONITOR_72_MODE_SCANNER = 6, + MSI_MONITOR_72_MODE_METEOR = 7, + MSI_MONITOR_72_MODE_WATER_DROP = 8, + MSI_MONITOR_72_MODE_RAINBOW_MSI = 9, + MSI_MONITOR_72_MODE_COLOR_RING = 15, + MSI_MONITOR_72_MODE_PLANETARY = 16, + MSI_MONITOR_72_MODE_DOUBLE_METEOR = 17, + MSI_MONITOR_72_MODE_ENERGY = 18, + MSI_MONITOR_72_MODE_BLINK = 19, + MSI_MONITOR_72_MODE_CLOCK = 20, + MSI_MONITOR_72_MODE_COLOR_PULSE = 21, + MSI_MONITOR_72_MODE_COLOR_SHIFT = 22, + MSI_MONITOR_72_MODE_COLOR_WAVE = 23, + MSI_MONITOR_72_MODE_MARQUEE = 24, + MSI_MONITOR_72_MODE_RAINBOW = 25, + MSI_MONITOR_72_MODE_RAINBOW_WAVE = 26, + MSI_MONITOR_72_MODE_VISOR = 27, + MSI_MONITOR_72_MODE_JRAINBOW = 28, + MSI_MONITOR_72_MODE_RAINBOW_FLASH = 29, + MSI_MONITOR_72_MODE_RAINBOW_DBLFLASH = 30, + MSI_MONITOR_72_MODE_RAINBOW_LINE = 33, +}; + +enum +{ + MSI_MONITOR_72_SPEED_LOW = 0, + MSI_MONITOR_72_SPEED_MEDIUM = 1, + MSI_MONITOR_72_SPEED_HIGH = 2, +}; + class MSIMonitorController { public: @@ -44,6 +128,9 @@ public: void Set(uint8_t mode_value, const std::vector colors, uint8_t last_bit); + uint8_t GetLayoutVersion(); + void SetMode72(uint8_t mode_value, uint8_t speed, uint8_t brightness, RGBColor color1, RGBColor color2, bool user_palette, bool save, const std::vector& led_colors, bool fill_both_arrays); + private: hid_device *dev; std::string description; diff --git a/Controllers/MSIMonitorController/MSIMonitorControllerDetect.cpp b/Controllers/MSIMonitorController/MSIMonitorControllerDetect.cpp index e68874eeb..786ead70a 100644 --- a/Controllers/MSIMonitorController/MSIMonitorControllerDetect.cpp +++ b/Controllers/MSIMonitorController/MSIMonitorControllerDetect.cpp @@ -1,5 +1,5 @@ /*---------------------------------------------------------*\ -| MSIKeyboardControllerDetect.cpp | +| MSIMonitorControllerDetect.cpp | | | | Detector for MSI monitor (MSI Gaming Controller) | | | @@ -10,10 +10,107 @@ #include "DetectionManager.h" #include "MSIMonitorController.h" #include "RGBController_MSIMonitor.h" +#include "RGBController_MSIMonitor72.h" #define MSI_USB_VID 0x1462 #define MSI_USB_PID 0x3FA4 +/*---------------------------------------------------------*\ +| Vendor layout table (from the version -> layout map). | +| The firmware reports its version to the 0xB0 query but | +| not a LED count, so count/direction/block-count come | +| from here. Fields: version, LED count, fill direction | +| (MSI_MONITOR_72_DIR_*), dual_block. | +| | +| Every entry here is a single linear run driven through | +| the 168 byte 0x72 buffer. Direction mirrors the fill so | +| OpenRGB LED 0 is the left/start on every model. Circle | +| and Ctype have no simple line mapping - they are driven | +| as a plain line here and should get a custom per model | +| layout once an owner maps the physical shape. | +| | +| Only version 34 is hardware verified (MPG 322URX). The | +| rest are derived from the vendor table: same buffer, LED | +| count and direction are the only differences, so effect | +| and single colour modes are correct by construction. | +| | +| Left out on purpose: | +| - dual_block versions 11/13/15 are stubbed but disabled | +| (see the commented block); their two control blocks | +| drive two runs and the per LED split is unconfirmed. | +| - Advanced/0x82 versions 31/47 need the 0x30 bulk report | +| this controller does not implement. | +| A version not matched here falls through to the legacy | +| 0x71 controller, home of the MAG272 class. | +\*---------------------------------------------------------*/ +struct MSIMonitor72Layout +{ + uint8_t version; + unsigned int leds; + unsigned int direction; + bool dual_block; +}; + +static const MSIMonitor72Layout msi_monitor_72_layouts[] = +{ + { 34, 9, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG271QRXQD_OLED (MPG 322URX reports this) - verified */ + { 5, 12, MSI_MONITOR_72_DIR_RIGHT_TO_LEFT, false }, /* MAG272R */ + { 8, 24, MSI_MONITOR_72_DIR_CIRCLE, false }, /* MAG301CR - Circle, wants custom layout */ + { 10, 9, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MAG273R */ + { 12, 9, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MAG271VCR */ + { 14, 14, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* NXG253R */ + { 17, 8, MSI_MONITOR_72_DIR_RIGHT_TO_LEFT, false }, /* MAG2x5R */ + { 20, 12, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MAG301RF */ + { 21, 18, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG341QR */ + { 22, 24, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG321QRF */ + { 24, 12, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MAG321QR */ + { 25, 24, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG321UR */ + { 28, 9, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MAG273CQR */ + { 29, 24, MSI_MONITOR_72_DIR_RIGHT_TO_LEFT, false }, /* MAG401QR */ + { 30, 21, MSI_MONITOR_72_DIR_RIGHT_TO_LEFT, false }, /* MAG325CQRF */ + { 32, 7, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MAG274QRF */ + { 33, 7, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG274URFQD */ + { 36, 9, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG275CQRXF */ + { 37, 22, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG345CQRD */ + { 38, 20, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MAG325CQRFQDE2 */ + { 39, 8, MSI_MONITOR_72_DIR_RIGHT_TO_LEFT, false }, /* MPG321CURX_QD_OLED */ + { 40, 12, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MAG322URDFE16 */ + { 41, 14, MSI_MONITOR_72_DIR_RIGHT_TO_LEFT, false }, /* MPG242RX60N */ + { 42, 22, MSI_MONITOR_72_DIR_CTYPE, false }, /* MPG274URDFWE16 - Ctype, wants custom layout */ + { 43, 21, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG346CQRFX24 */ + { 44, 9, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG271QRQD_OLEDX50 */ + { 46, 9, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, false }, /* MPG341CQRQD_OLEDX36 */ + + /*-----------------------------------------------------------------*\ + | Dual block - stubbed but DISABLED. The dual_block path fills | + | both colour arrays so uniform colour modes are correct, but each | + | block drives its own run and the per LED split is unconfirmed, so | + | Direct is a guess. These versions also map colour index above 15 | + | to a separate DragonShield emblem region (unhandled). An owner | + | should enable, test, and ideally give it a custom layout: | + | { 11, 16, MSI_MONITOR_72_DIR_RIGHT_TO_LEFT, true }, MPG273CQR | + | { 13, 21, MSI_MONITOR_72_DIR_LEFT_TO_RIGHT, true }, MPG343CQR | + | { 15, 18, MSI_MONITOR_72_DIR_RIGHT_TO_LEFT, true }, MPG323CQR | + | | + | Advanced/0x82 - not addable here, need the 0x30 bulk report: | + | version 31, 40 LEDs, MEG342CQD_OLED | + | version 47, 83 LEDs, 4 areas, MEG_X | + \*-----------------------------------------------------------------*/ +}; + +static const MSIMonitor72Layout* MSIMonitorLayout(uint8_t version) +{ + for(unsigned int i = 0; i < (sizeof(msi_monitor_72_layouts) / sizeof(msi_monitor_72_layouts[0])); i++) + { + if(msi_monitor_72_layouts[i].version == version) + { + return(&msi_monitor_72_layouts[i]); + } + } + + return(nullptr); +} + DetectedControllers DetectMSIMonitorController(hid_device_info* info, const std::string& name) { DetectedControllers detected_controllers; @@ -23,10 +120,41 @@ DetectedControllers DetectMSIMonitorController(hid_device_info* info, const std: if(dev) { - MSIMonitorController* controller = new MSIMonitorController(dev, *info, name); - RGBController_MSIMonitor* rgb_controller = new RGBController_MSIMonitor(controller); + /*-------------------------------------------------*\ + | One VID/PID covers several buffer layouts, so ask | + | the MCU which one it speaks. A version in the | + | 0x72 table selects the 168 byte layout and its | + | LED count; anything else keeps the legacy 0x71 | + | path, which is what the older monitors answer to. | + \*-------------------------------------------------*/ + MSIMonitorController* controller = new MSIMonitorController(dev, *info, name); - detected_controllers.push_back(rgb_controller); + uint8_t version = controller->GetLayoutVersion(); + const MSIMonitor72Layout* layout = MSIMonitorLayout(version); + + if(layout != nullptr) + { + detected_controllers.push_back(new RGBController_MSIMonitor72(controller, layout->leds, layout->direction, layout->dual_block)); + } + else + { + /*-------------------------------------------------*\ + | 0x71 family (78 byte buffer), handled by the | + | legacy controller as a plain line. For an owner | + | who wants to bring one onto this controller: same | + | operative block 12, but the second colour array | + | is at index 50 (not 95) and store-flash at 77. | + | RightToLeft mirroring applies. version, LEDs, | + | direction: | + | 1, 9, RightToLeft (early/generic) | + | 2, 7, LeftToRight (malformed stub, skip) | + | 3, 9, RightToLeft (early/generic) | + | 4, 9, LeftToRight (early/generic) | + | 6, 9, UpToDown MAG270CR | + | 7, 7, LeftToRight MAG251RX | + \*-------------------------------------------------*/ + detected_controllers.push_back(new RGBController_MSIMonitor(controller)); + } } return(detected_controllers); diff --git a/Controllers/MSIMonitorController/RGBController_MSIMonitor.cpp b/Controllers/MSIMonitorController/RGBController_MSIMonitor.cpp index 4dcded239..9d6cdd409 100644 --- a/Controllers/MSIMonitorController/RGBController_MSIMonitor.cpp +++ b/Controllers/MSIMonitorController/RGBController_MSIMonitor.cpp @@ -9,12 +9,8 @@ | SPDX-License-Identifier: GPL-2.0-or-later | \*---------------------------------------------------------*/ -#include -#include #include "RGBController_MSIMonitor.h" -using namespace std::chrono_literals; - /**------------------------------------------------------------------*\ @name MSIMonitor @category Accessory @@ -112,10 +108,6 @@ RGBController_MSIMonitor::RGBController_MSIMonitor(MSIMonitorController* control RGBController_MSIMonitor::~RGBController_MSIMonitor() { - keepalive_thread_run = 0; - keepalive_thread->join(); - delete keepalive_thread; - Shutdown(); delete controller; @@ -145,7 +137,6 @@ void RGBController_MSIMonitor::SetupZones() void RGBController_MSIMonitor::DeviceUpdateLEDs() { - last_update_time = std::chrono::steady_clock::now(); controller->Set(modes[active_mode].value, colors, active_mode == 0 ? 0x00 : 0x01); } diff --git a/Controllers/MSIMonitorController/RGBController_MSIMonitor.h b/Controllers/MSIMonitorController/RGBController_MSIMonitor.h index b388a1774..033f31ca3 100644 --- a/Controllers/MSIMonitorController/RGBController_MSIMonitor.h +++ b/Controllers/MSIMonitorController/RGBController_MSIMonitor.h @@ -1,5 +1,5 @@ /*---------------------------------------------------------*\ -| RGBController_MSIMonitor.cpp | +| RGBController_MSIMonitor.h | | | | RGBController for MSI monitor (gaming controller) | | | @@ -11,7 +11,6 @@ #pragma once -#include #include "RGBController.h" #include "MSIMonitorController.h" @@ -31,9 +30,4 @@ public: private: MSIMonitorController* controller; - std::thread* keepalive_thread; - std::atomic keepalive_thread_run; - std::chrono::time_point last_update_time; - - void KeepaliveThread(); }; diff --git a/Controllers/MSIMonitorController/RGBController_MSIMonitor72.cpp b/Controllers/MSIMonitorController/RGBController_MSIMonitor72.cpp new file mode 100644 index 000000000..eff9b034d --- /dev/null +++ b/Controllers/MSIMonitorController/RGBController_MSIMonitor72.cpp @@ -0,0 +1,570 @@ +/*---------------------------------------------------------*\ +| RGBController_MSIMonitor72.cpp | +| | +| RGBController for MSI monitors using the 0x72 dual | +| control block layout (MPG 322URX QD-OLED) | +| | +| Ken Sanislo 19 Jul 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#include +#include "RGBController_MSIMonitor72.h" + +/**------------------------------------------------------------------*\ + @name MSIMonitor72 + @category Accessory + @type USB + @save :robot: + @direct :white_check_mark: + @effects :white_check_mark: + @detectors DetectMSIMonitorController + @comment Developed with MSI MPG 322URX QD-OLED +\*-------------------------------------------------------------------*/ +RGBController_MSIMonitor72::RGBController_MSIMonitor72(MSIMonitorController* controller_ptr, unsigned int count, unsigned int direction, bool dual) +{ + controller = controller_ptr; + led_count = count; + reverse_order = (direction == MSI_MONITOR_72_DIR_RIGHT_TO_LEFT); + dual_block = dual; + + name = controller->GetNameString(); + vendor = "MSI"; + type = DEVICE_TYPE_MONITOR; + description = "MSI Monitor (Gaming Controller)"; + location = controller->GetDeviceLocation(); + serial = controller->GetSerialString(); + + /*-----------------------------------------------------*\ + | This layout has no Advanced mode, so Direct is Static | + | driven from the inline per LED colour arrays. | + \*-----------------------------------------------------*/ + mode Direct; + Direct.name = "Direct"; + Direct.value = MSI_MONITOR_72_MODE_STATIC; + Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Direct.brightness_min = 0; + Direct.brightness_max = 100; + Direct.brightness = 100; + Direct.color_mode = MODE_COLORS_PER_LED; + modes.push_back(Direct); + + mode Static; + Static.name = "Static"; + Static.value = MSI_MONITOR_72_MODE_STATIC; + Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Static.colors_min = 1; + Static.colors_max = 1; + Static.colors.resize(1); + Static.brightness_min = 0; + Static.brightness_max = 100; + Static.brightness = 100; + Static.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Static); + + mode Breathing; + Breathing.name = "Breathing"; + Breathing.value = MSI_MONITOR_72_MODE_BREATHING; + Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Breathing.colors_min = 1; + Breathing.colors_max = 1; + Breathing.colors.resize(1); + Breathing.speed_min = MSI_MONITOR_72_SPEED_LOW; + Breathing.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Breathing.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Breathing.brightness_min = 0; + Breathing.brightness_max = 100; + Breathing.brightness = 100; + Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Breathing); + + mode Flashing; + Flashing.name = "Flashing"; + Flashing.value = MSI_MONITOR_72_MODE_FLASHING; + Flashing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Flashing.colors_min = 2; + Flashing.colors_max = 2; + Flashing.colors.resize(2); + Flashing.speed_min = MSI_MONITOR_72_SPEED_LOW; + Flashing.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Flashing.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Flashing.brightness_min = 0; + Flashing.brightness_max = 100; + Flashing.brightness = 100; + Flashing.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Flashing); + + mode DoubleFlashing; + DoubleFlashing.name = "Double Flashing"; + DoubleFlashing.value = MSI_MONITOR_72_MODE_DOUBLE_FLASHING; + DoubleFlashing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + DoubleFlashing.colors_min = 2; + DoubleFlashing.colors_max = 2; + DoubleFlashing.colors.resize(2); + DoubleFlashing.speed_min = MSI_MONITOR_72_SPEED_LOW; + DoubleFlashing.speed_max = MSI_MONITOR_72_SPEED_HIGH; + DoubleFlashing.speed = MSI_MONITOR_72_SPEED_MEDIUM; + DoubleFlashing.brightness_min = 0; + DoubleFlashing.brightness_max = 100; + DoubleFlashing.brightness = 100; + DoubleFlashing.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(DoubleFlashing); + + mode Lightning; + Lightning.name = "Lightning"; + Lightning.value = MSI_MONITOR_72_MODE_LIGHTNING; + Lightning.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Lightning.colors_min = 1; + Lightning.colors_max = 1; + Lightning.colors.resize(1); + Lightning.speed_min = MSI_MONITOR_72_SPEED_LOW; + Lightning.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Lightning.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Lightning.brightness_min = 0; + Lightning.brightness_max = 100; + Lightning.brightness = 100; + Lightning.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Lightning); + + mode Scanner; + Scanner.name = "Scanner"; + Scanner.value = MSI_MONITOR_72_MODE_SCANNER; + Scanner.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Scanner.colors_min = 1; + Scanner.colors_max = 1; + Scanner.colors.resize(1); + Scanner.speed_min = MSI_MONITOR_72_SPEED_LOW; + Scanner.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Scanner.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Scanner.brightness_min = 0; + Scanner.brightness_max = 100; + Scanner.brightness = 100; + Scanner.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Scanner); + + mode Meteor; + Meteor.name = "Meteor"; + Meteor.value = MSI_MONITOR_72_MODE_METEOR; + Meteor.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Meteor.colors_min = 1; + Meteor.colors_max = 1; + Meteor.colors.resize(1); + Meteor.speed_min = MSI_MONITOR_72_SPEED_LOW; + Meteor.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Meteor.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Meteor.brightness_min = 0; + Meteor.brightness_max = 100; + Meteor.brightness = 100; + Meteor.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Meteor); + + mode WaterDrop; + WaterDrop.name = "Water Drop"; + WaterDrop.value = MSI_MONITOR_72_MODE_WATER_DROP; + WaterDrop.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + WaterDrop.colors_min = 1; + WaterDrop.colors_max = 1; + WaterDrop.colors.resize(1); + WaterDrop.speed_min = MSI_MONITOR_72_SPEED_LOW; + WaterDrop.speed_max = MSI_MONITOR_72_SPEED_HIGH; + WaterDrop.speed = MSI_MONITOR_72_SPEED_MEDIUM; + WaterDrop.brightness_min = 0; + WaterDrop.brightness_max = 100; + WaterDrop.brightness = 100; + WaterDrop.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(WaterDrop); + + mode RainbowMSI; + RainbowMSI.name = "Rainbow"; + RainbowMSI.value = MSI_MONITOR_72_MODE_RAINBOW_MSI; + RainbowMSI.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + RainbowMSI.speed_min = MSI_MONITOR_72_SPEED_LOW; + RainbowMSI.speed_max = MSI_MONITOR_72_SPEED_HIGH; + RainbowMSI.speed = MSI_MONITOR_72_SPEED_MEDIUM; + RainbowMSI.brightness_min = 0; + RainbowMSI.brightness_max = 100; + RainbowMSI.brightness = 100; + RainbowMSI.color_mode = MODE_COLORS_NONE; + modes.push_back(RainbowMSI); + + mode ColorRing; + ColorRing.name = "Color Ring"; + ColorRing.value = MSI_MONITOR_72_MODE_COLOR_RING; + ColorRing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + ColorRing.colors_min = 1; + ColorRing.colors_max = 1; + ColorRing.colors.resize(1); + ColorRing.speed_min = MSI_MONITOR_72_SPEED_LOW; + ColorRing.speed_max = MSI_MONITOR_72_SPEED_HIGH; + ColorRing.speed = MSI_MONITOR_72_SPEED_MEDIUM; + ColorRing.brightness_min = 0; + ColorRing.brightness_max = 100; + ColorRing.brightness = 100; + ColorRing.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(ColorRing); + + mode Planetary; + Planetary.name = "Planetary"; + Planetary.value = MSI_MONITOR_72_MODE_PLANETARY; + Planetary.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Planetary.colors_min = 1; + Planetary.colors_max = 1; + Planetary.colors.resize(1); + Planetary.speed_min = MSI_MONITOR_72_SPEED_LOW; + Planetary.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Planetary.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Planetary.brightness_min = 0; + Planetary.brightness_max = 100; + Planetary.brightness = 100; + Planetary.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Planetary); + + mode DoubleMeteor; + DoubleMeteor.name = "Double Meteor"; + DoubleMeteor.value = MSI_MONITOR_72_MODE_DOUBLE_METEOR; + DoubleMeteor.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + DoubleMeteor.colors_min = 1; + DoubleMeteor.colors_max = 1; + DoubleMeteor.colors.resize(1); + DoubleMeteor.speed_min = MSI_MONITOR_72_SPEED_LOW; + DoubleMeteor.speed_max = MSI_MONITOR_72_SPEED_HIGH; + DoubleMeteor.speed = MSI_MONITOR_72_SPEED_MEDIUM; + DoubleMeteor.brightness_min = 0; + DoubleMeteor.brightness_max = 100; + DoubleMeteor.brightness = 100; + DoubleMeteor.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(DoubleMeteor); + + mode Energy; + Energy.name = "Energy"; + Energy.value = MSI_MONITOR_72_MODE_ENERGY; + Energy.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Energy.colors_min = 1; + Energy.colors_max = 1; + Energy.colors.resize(1); + Energy.speed_min = MSI_MONITOR_72_SPEED_LOW; + Energy.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Energy.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Energy.brightness_min = 0; + Energy.brightness_max = 100; + Energy.brightness = 100; + Energy.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Energy); + + mode Blink; + Blink.name = "Blink"; + Blink.value = MSI_MONITOR_72_MODE_BLINK; + Blink.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Blink.colors_min = 1; + Blink.colors_max = 1; + Blink.colors.resize(1); + Blink.speed_min = MSI_MONITOR_72_SPEED_LOW; + Blink.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Blink.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Blink.brightness_min = 0; + Blink.brightness_max = 100; + Blink.brightness = 100; + Blink.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Blink); + + mode Clock; + Clock.name = "Clock"; + Clock.value = MSI_MONITOR_72_MODE_CLOCK; + Clock.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Clock.colors_min = 1; + Clock.colors_max = 1; + Clock.colors.resize(1); + Clock.speed_min = MSI_MONITOR_72_SPEED_LOW; + Clock.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Clock.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Clock.brightness_min = 0; + Clock.brightness_max = 100; + Clock.brightness = 100; + Clock.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Clock); + + mode ColorPulse; + ColorPulse.name = "Color Pulse"; + ColorPulse.value = MSI_MONITOR_72_MODE_COLOR_PULSE; + ColorPulse.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + ColorPulse.colors_min = 1; + ColorPulse.colors_max = 1; + ColorPulse.colors.resize(1); + ColorPulse.speed_min = MSI_MONITOR_72_SPEED_LOW; + ColorPulse.speed_max = MSI_MONITOR_72_SPEED_HIGH; + ColorPulse.speed = MSI_MONITOR_72_SPEED_MEDIUM; + ColorPulse.brightness_min = 0; + ColorPulse.brightness_max = 100; + ColorPulse.brightness = 100; + ColorPulse.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(ColorPulse); + + mode ColorShift; + ColorShift.name = "Color Shift"; + ColorShift.value = MSI_MONITOR_72_MODE_COLOR_SHIFT; + ColorShift.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + ColorShift.colors_min = 2; + ColorShift.colors_max = 2; + ColorShift.colors.resize(2); + ColorShift.speed_min = MSI_MONITOR_72_SPEED_LOW; + ColorShift.speed_max = MSI_MONITOR_72_SPEED_HIGH; + ColorShift.speed = MSI_MONITOR_72_SPEED_MEDIUM; + ColorShift.brightness_min = 0; + ColorShift.brightness_max = 100; + ColorShift.brightness = 100; + ColorShift.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(ColorShift); + + mode ColorWave; + ColorWave.name = "Color Wave"; + ColorWave.value = MSI_MONITOR_72_MODE_COLOR_WAVE; + ColorWave.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + ColorWave.colors_min = 2; + ColorWave.colors_max = 2; + ColorWave.colors.resize(2); + ColorWave.speed_min = MSI_MONITOR_72_SPEED_LOW; + ColorWave.speed_max = MSI_MONITOR_72_SPEED_HIGH; + ColorWave.speed = MSI_MONITOR_72_SPEED_MEDIUM; + ColorWave.brightness_min = 0; + ColorWave.brightness_max = 100; + ColorWave.brightness = 100; + ColorWave.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(ColorWave); + + /*-----------------------------------------------------*\ + | Mode 24 marquee behaviour is model dependent. On the | + | dual block / richer boards it wants the marker bytes | + | plus a per LED array of separate colours (a user | + | defined marquee). The vendor app does not even offer | + | it for version 34, but the firmware still runs it as | + | a one colour rolling band, so we expose that. | + \*-----------------------------------------------------*/ + mode Marquee; + Marquee.name = "Marquee"; + Marquee.value = MSI_MONITOR_72_MODE_MARQUEE; + Marquee.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Marquee.colors_min = 1; + Marquee.colors_max = 1; + Marquee.colors.resize(1); + Marquee.speed_min = MSI_MONITOR_72_SPEED_LOW; + Marquee.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Marquee.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Marquee.brightness_min = 0; + Marquee.brightness_max = 100; + Marquee.brightness = 100; + Marquee.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Marquee); + + mode SpectrumCycle; + SpectrumCycle.name = "Spectrum Cycle"; + SpectrumCycle.value = MSI_MONITOR_72_MODE_RAINBOW; + SpectrumCycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + SpectrumCycle.speed_min = MSI_MONITOR_72_SPEED_LOW; + SpectrumCycle.speed_max = MSI_MONITOR_72_SPEED_HIGH; + SpectrumCycle.speed = MSI_MONITOR_72_SPEED_MEDIUM; + SpectrumCycle.brightness_min = 0; + SpectrumCycle.brightness_max = 100; + SpectrumCycle.brightness = 100; + SpectrumCycle.color_mode = MODE_COLORS_NONE; + modes.push_back(SpectrumCycle); + + mode RainbowWave; + RainbowWave.name = "Rainbow Wave"; + RainbowWave.value = MSI_MONITOR_72_MODE_RAINBOW_WAVE; + RainbowWave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + RainbowWave.speed_min = MSI_MONITOR_72_SPEED_LOW; + RainbowWave.speed_max = MSI_MONITOR_72_SPEED_HIGH; + RainbowWave.speed = MSI_MONITOR_72_SPEED_MEDIUM; + RainbowWave.brightness_min = 0; + RainbowWave.brightness_max = 100; + RainbowWave.brightness = 100; + RainbowWave.color_mode = MODE_COLORS_NONE; + modes.push_back(RainbowWave); + + mode Visor; + Visor.name = "Visor"; + Visor.value = MSI_MONITOR_72_MODE_VISOR; + Visor.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + Visor.colors_min = 1; + Visor.colors_max = 1; + Visor.colors.resize(1); + Visor.speed_min = MSI_MONITOR_72_SPEED_LOW; + Visor.speed_max = MSI_MONITOR_72_SPEED_HIGH; + Visor.speed = MSI_MONITOR_72_SPEED_MEDIUM; + Visor.brightness_min = 0; + Visor.brightness_max = 100; + Visor.brightness = 100; + Visor.color_mode = MODE_COLORS_MODE_SPECIFIC; + modes.push_back(Visor); + + mode JRainbow; + JRainbow.name = "JRainbow"; + JRainbow.value = MSI_MONITOR_72_MODE_JRAINBOW; + JRainbow.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + JRainbow.speed_min = MSI_MONITOR_72_SPEED_LOW; + JRainbow.speed_max = MSI_MONITOR_72_SPEED_HIGH; + JRainbow.speed = MSI_MONITOR_72_SPEED_MEDIUM; + JRainbow.brightness_min = 0; + JRainbow.brightness_max = 100; + JRainbow.brightness = 100; + JRainbow.color_mode = MODE_COLORS_NONE; + modes.push_back(JRainbow); + + mode RainbowFlash; + RainbowFlash.name = "Rainbow Flashing"; + RainbowFlash.value = MSI_MONITOR_72_MODE_RAINBOW_FLASH; + RainbowFlash.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + RainbowFlash.speed_min = MSI_MONITOR_72_SPEED_LOW; + RainbowFlash.speed_max = MSI_MONITOR_72_SPEED_HIGH; + RainbowFlash.speed = MSI_MONITOR_72_SPEED_MEDIUM; + RainbowFlash.brightness_min = 0; + RainbowFlash.brightness_max = 100; + RainbowFlash.brightness = 100; + RainbowFlash.color_mode = MODE_COLORS_NONE; + modes.push_back(RainbowFlash); + + mode RainbowDblFlash; + RainbowDblFlash.name = "Rainbow Double Flashing"; + RainbowDblFlash.value = MSI_MONITOR_72_MODE_RAINBOW_DBLFLASH; + RainbowDblFlash.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + RainbowDblFlash.speed_min = MSI_MONITOR_72_SPEED_LOW; + RainbowDblFlash.speed_max = MSI_MONITOR_72_SPEED_HIGH; + RainbowDblFlash.speed = MSI_MONITOR_72_SPEED_MEDIUM; + RainbowDblFlash.brightness_min = 0; + RainbowDblFlash.brightness_max = 100; + RainbowDblFlash.brightness = 100; + RainbowDblFlash.color_mode = MODE_COLORS_NONE; + modes.push_back(RainbowDblFlash); + + mode RainbowLine; + RainbowLine.name = "Rainbow Line"; + RainbowLine.value = MSI_MONITOR_72_MODE_RAINBOW_LINE; + RainbowLine.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE; + RainbowLine.speed_min = MSI_MONITOR_72_SPEED_LOW; + RainbowLine.speed_max = MSI_MONITOR_72_SPEED_HIGH; + RainbowLine.speed = MSI_MONITOR_72_SPEED_MEDIUM; + RainbowLine.brightness_min = 0; + RainbowLine.brightness_max = 100; + RainbowLine.brightness = 100; + RainbowLine.color_mode = MODE_COLORS_NONE; + modes.push_back(RainbowLine); + + mode Off; + Off.name = "Off"; + Off.value = MSI_MONITOR_72_MODE_OFF; + Off.flags = MODE_FLAG_MANUAL_SAVE; + Off.color_mode = MODE_COLORS_NONE; + modes.push_back(Off); + + SetupZones(); +} + +RGBController_MSIMonitor72::~RGBController_MSIMonitor72() +{ + Shutdown(); + + delete controller; +} + +void RGBController_MSIMonitor72::SetupZones() +{ + zone new_zone; + + new_zone.name = "Rear"; + new_zone.type = ZONE_TYPE_LINEAR; + new_zone.leds_min = led_count; + new_zone.leds_max = led_count; + new_zone.leds_count = led_count; + + zones.emplace_back(new_zone); + + for(unsigned int led_idx = 0; led_idx < led_count; led_idx++) + { + led new_led; + new_led.name = "LED " + std::to_string(led_idx + 1); + leds.push_back(new_led); + } + + SetupColors(); +} + +void RGBController_MSIMonitor72::DeviceUpdateLEDs() +{ + DeviceUpdateMode(); +} + +void RGBController_MSIMonitor72::DeviceUpdateZoneLEDs(int /*zone*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_MSIMonitor72::DeviceUpdateSingleLED(int /*led*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_MSIMonitor72::ApplyMode(bool save) +{ + const mode& active = modes[active_mode]; + + RGBColor color1 = 0; + RGBColor color2 = 0; + bool user_palette = false; + std::vector led_colors; + + if(active.color_mode == MODE_COLORS_PER_LED) + { + user_palette = true; + led_colors = colors; + + if(led_colors.size() > 0) + { + color1 = led_colors[0]; + } + } + else if(active.color_mode == MODE_COLORS_MODE_SPECIFIC) + { + user_palette = true; + + if(active.colors.size() > 0) + { + color1 = active.colors[0]; + + if(active.colors.size() > 1) + { + color2 = active.colors[1]; + } + } + + led_colors.assign(led_count, color1); + } + + /*-----------------------------------------------------*\ + | RightToLeft layouts start at the right end, so mirror | + | the per LED order to keep OpenRGB LED 0 on the left. | + | Uniform colour modes are unaffected by the order. | + \*-----------------------------------------------------*/ + if(reverse_order) + { + std::reverse(led_colors.begin(), led_colors.end()); + } + + controller->SetMode72((uint8_t)active.value, (uint8_t)active.speed, (uint8_t)active.brightness, color1, color2, user_palette, save, led_colors, dual_block); +} + +void RGBController_MSIMonitor72::DeviceUpdateMode() +{ + /*-----------------------------------------------------*\ + | Updates are volatile. Persisting to flash is the | + | Save To Device button (DeviceSaveMode), so live edits | + | do not burn flash write cycles. | + \*-----------------------------------------------------*/ + ApplyMode(false); +} + +void RGBController_MSIMonitor72::DeviceSaveMode() +{ + ApplyMode(true); +} diff --git a/Controllers/MSIMonitorController/RGBController_MSIMonitor72.h b/Controllers/MSIMonitorController/RGBController_MSIMonitor72.h new file mode 100644 index 000000000..2a6854ebe --- /dev/null +++ b/Controllers/MSIMonitorController/RGBController_MSIMonitor72.h @@ -0,0 +1,40 @@ +/*---------------------------------------------------------*\ +| RGBController_MSIMonitor72.h | +| | +| RGBController for MSI monitors using the 0x72 dual | +| control block layout (MPG 322URX QD-OLED) | +| | +| Ken Sanislo 19 Jul 2026 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-or-later | +\*---------------------------------------------------------*/ + +#pragma once + +#include "RGBController.h" +#include "MSIMonitorController.h" + +class RGBController_MSIMonitor72 : public RGBController +{ +public: + RGBController_MSIMonitor72(MSIMonitorController* controller_ptr, unsigned int count, unsigned int direction, bool dual); + ~RGBController_MSIMonitor72(); + + void SetupZones(); + + void DeviceUpdateLEDs(); + void DeviceUpdateZoneLEDs(int zone); + void DeviceUpdateSingleLED(int led); + + void DeviceUpdateMode(); + void DeviceSaveMode(); + +private: + void ApplyMode(bool save); + + MSIMonitorController* controller; + unsigned int led_count; + bool reverse_order; + bool dual_block; +};