diff --git a/Controllers/MSIMysticLightController/MSIMysticLight290Controller/MSIMysticLight290Controller.cpp b/Controllers/MSIMysticLightController/MSIMysticLight290Controller/MSIMysticLight290Controller.cpp new file mode 100644 index 0000000000000000000000000000000000000000..91793d0ac0e23d49229a182dbd7254c918833ac8 --- /dev/null +++ b/Controllers/MSIMysticLightController/MSIMysticLight290Controller/MSIMysticLight290Controller.cpp @@ -0,0 +1,437 @@ +/*---------------------------------------------------------*\ +| MSIMysticLight290Controller.cpp | +| | +| Driver for MSI Mystic Light 290-byte motherboard | +| Uses the native MSI LedKeeper protocol format | +| | +| mysty Dec 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include "MSIMysticLight290Controller.h" +#include "StringUtils.h" +#include "LogManager.h" + +// Zone mapping for 18 zones (matching MSI LedKeeper indices) +// JARGB1(0), JARGB2(1), JARGB3(2), JAF(3), JPIPE1(4), JPIPE2(5), JPIPE3(6), JPIPE4(7), JPIPE5(8), +// JRGB1(9), JRGB2(10), Onboard1(11), Onboard2(12), Onboard3(13), Onboard4(14), Onboard5(15), Onboard6(16), SelectAll(17) + +// X870E zones for MSI MPG X870E EDGE TI WIFI (MS-7E59) - matching MSI LedKeeper exactly +const std::vector x870e_zones = +{ + MSI_ZONE_ON_BOARD_LED_0, // IO COVER (zone 0 in MSI mapping) + MSI_ZONE_J_RGB_1, // JRGB1 (zone 1 in MSI mapping) + MSI_ZONE_J_RAINBOW_1, // JARGB_V2_1 (zone 2 in MSI mapping) + MSI_ZONE_J_RAINBOW_2, // JARGB_V2_2 (zone 3 in MSI mapping) + MSI_ZONE_J_RAINBOW_3, // JARGB_V2_3 (zone 4 in MSI mapping) + MSI_ZONE_JAF, // EZ Conn (zone 5 in MSI mapping) +}; + +MSIMysticLight290Controller::MSIMysticLight290Controller + ( + hid_device* handle, + const char *path, + unsigned short pid + ) +{ + dev = handle; + + if(dev) + { + location = path; + ReadName(); + ReadFwVersion(); + ReadSettings(); + } + + // Set supported zones based on PID + if(pid == 0x7E59) // MSI MPG X870E EDGE TI WIFI + { + supported_zones = &x870e_zones; + } + else + { + // For other boards, just use X870E zones as default for now + supported_zones = &x870e_zones; + } + + // Initialize data packet with MSI LedKeeper compatible values + data.report_id = 0x50; + data.save_data = 0; + + // Initialize all zones with default MSI LedKeeper values matching the dumped code + for(int i = 0; i < 18; i++) + { + data.zones[i].effect = 2; // MSI Steady mode (enum value 2) + data.zones[i].color_1 = {0xFF, 0x00, 0x00}; // Red (255,0,0) + data.zones[i].color_2 = {0x00, 0xFF, 0x00}; // Green (0,255,0) + data.zones[i].color_3 = {0x00, 0x00, 0xFF}; // Blue (0,0,255) + data.zones[i].color_4 = {0xFF, 0xFF, 0xFF}; // White (255,255,255) + data.zones[i].option1 = 0x00; // ColorCount = 0 (1 color) + // Option2: SelectAll=0, Direction=0, ColorSelection=1 (User), Brightness=5, Speed=1 (medium) + data.zones[i].option2 = (0 << 7) | (0 << 6) | (1 << 5) | (5 << 2) | 1; + data.zones[i].cycle_num = 30; // Default cycle number + } +} + +MSIMysticLight290Controller::~MSIMysticLight290Controller() +{ + hid_close(dev); +} + +void MSIMysticLight290Controller::SetMode + ( + MSI_ZONE zone, + MSI_MODE mode, + MSI_SPEED speed, + MSI_BRIGHTNESS brightness, + bool rainbow_color + ) +{ + MSILedKeeperZoneData* zone_data = GetZoneData(zone); + if(zone_data == nullptr) + { + return; + } + + // Map OpenRGB modes to MSI modes based on Enum_LightingMode + switch(mode) + { + case MSI_MODE_DISABLE: + zone_data->effect = 0; // MSI Off mode + break; + case MSI_MODE_MSI_RAINBOW: + zone_data->effect = 1; // MSI Wave mode + break; + case MSI_MODE_STATIC: + zone_data->effect = 2; // MSI Steady mode + break; + case MSI_MODE_FLASHING: + zone_data->effect = 3; // MSI Flame mode + break; + case MSI_MODE_BREATHING: + zone_data->effect = 4; // MSI Breathing mode + break; + case MSI_MODE_COLOR_RING: + zone_data->effect = 5; // MSI ColorRing mode + break; + case MSI_MODE_LIGHTNING: + zone_data->effect = 6; // MSI Lightning mode + break; + case MSI_MODE_MOVIE: + zone_data->effect = 7; // MSI Recreation mode + break; + case MSI_MODE_METEOR: + zone_data->effect = 8; // MSI Meteor mode + break; + case MSI_MODE_RANDOM: + zone_data->effect = 9; // MSI Advanced mode + break; + case MSI_MODE_ENERGY: + zone_data->effect = 10; // MSI GodLike mode + break; + default: + zone_data->effect = 2; // Default to Steady/Static + break; + } + + // Option1: ColorCount (bits 0-1) - set to 1 color for basic modes + zone_data->option1 = 0; // ColorCount = 0 (1 color) + + // Option2 encoding based on MSI LedKeeper reverse engineering: + // Bit 7: SelectAll (0=false, 1=true) + // Bit 6: Direction (0=Out, 1=In) + // Bit 5: ColorSelection (0=Rainbow, 1=User) + // Bits 2-4: Brightness (0-7) + // Bits 0-1: Speed (0-3) + unsigned char selectall = 0; // SelectAll = false + unsigned char direction = 0; // Direction = Out + unsigned char colorsel = rainbow_color ? 0 : 1; // Rainbow=0, User=1 + unsigned char bright = brightness & 0x07; // Brightness (0-7) + unsigned char spd = speed & 0x03; // Speed (0-3) + + zone_data->option2 = (selectall << 7) | (direction << 6) | (colorsel << 5) | (bright << 2) | spd; +} + +void MSIMysticLight290Controller::SetZoneColor + ( + MSI_ZONE zone, + unsigned char red1, + unsigned char grn1, + unsigned char blu1, + unsigned char red2, + unsigned char grn2, + unsigned char blu2 + ) +{ + MSILedKeeperZoneData* zone_data = GetZoneData(zone); + if(zone_data == nullptr) + { + return; + } + + // Set primary color (this is the main color used) + zone_data->color_1.R = red1; + zone_data->color_1.G = grn1; + zone_data->color_1.B = blu1; + + // Set secondary color (for effects that use multiple colors) + zone_data->color_2.R = red2; + zone_data->color_2.G = grn2; + zone_data->color_2.B = blu2; + + // Ensure we have valid options if not already set + if(zone_data->option1 == 0 && zone_data->option2 == 0) + { + zone_data->option1 = 0; // ColorCount = 0 (1 color) + // Option2: SelectAll=0, Direction=0, ColorSelection=1 (User), Brightness=5, Speed=1 + zone_data->option2 = (0 << 7) | (0 << 6) | (1 << 5) | (5 << 2) | 1; + } +} + +void MSIMysticLight290Controller::SetZoneColors + ( + MSI_ZONE zone, + unsigned char mode, + unsigned char speed, + unsigned char brightness, + std::vector colors + ) +{ + MSILedKeeperZoneData* zone_data = GetZoneData(zone); + if(zone_data == nullptr) + { + return; + } + + // Set the lighting mode using MSI LedKeeper values directly + zone_data->effect = mode; + + // Set colors based on how many are provided + if(colors.size() > 0) + { + unsigned int color = colors[0]; + zone_data->color_1.R = (color >> 16) & 0xFF; + zone_data->color_1.G = (color >> 8) & 0xFF; + zone_data->color_1.B = color & 0xFF; + } + + if(colors.size() > 1) + { + unsigned int color = colors[1]; + zone_data->color_2.R = (color >> 16) & 0xFF; + zone_data->color_2.G = (color >> 8) & 0xFF; + zone_data->color_2.B = color & 0xFF; + } + else + { + // Default to green if not specified (matching MSI LedKeeper) + zone_data->color_2.R = 0x00; + zone_data->color_2.G = 0xFF; + zone_data->color_2.B = 0x00; + } + + if(colors.size() > 2) + { + unsigned int color = colors[2]; + zone_data->color_3.R = (color >> 16) & 0xFF; + zone_data->color_3.G = (color >> 8) & 0xFF; + zone_data->color_3.B = color & 0xFF; + } + else + { + // Default to blue if not specified (matching MSI LedKeeper) + zone_data->color_3.R = 0x00; + zone_data->color_3.G = 0x00; + zone_data->color_3.B = 0xFF; + } + + if(colors.size() > 3) + { + unsigned int color = colors[3]; + zone_data->color_4.R = (color >> 16) & 0xFF; + zone_data->color_4.G = (color >> 8) & 0xFF; + zone_data->color_4.B = color & 0xFF; + } + else + { + // Default to white if not specified (matching MSI LedKeeper) + zone_data->color_4.R = 0xFF; + zone_data->color_4.G = 0xFF; + zone_data->color_4.B = 0xFF; + } + + // Option1: ColorCount (bits 0-1) - set based on number of colors provided + unsigned char color_count = 0; + if(colors.size() > 3) color_count = 3; // 4 colors (0-based) + else if(colors.size() > 2) color_count = 2; // 3 colors + else if(colors.size() > 1) color_count = 1; // 2 colors + else color_count = 0; // 1 color + zone_data->option1 = color_count; + + // Option2 encoding based on MSI LedKeeper reverse engineering: + // Bit 7: SelectAll (0=false, 1=true) + // Bit 6: Direction (0=Out, 1=In) + // Bit 5: ColorSelection (0=Rainbow, 1=User) + // Bits 2-4: Brightness (0-7) + // Bits 0-1: Speed (0-3) + unsigned char selectall = 0; // SelectAll = false + unsigned char direction = 0; // Direction = Out + unsigned char colorsel = 1; // User mode for specific colors + unsigned char bright = brightness & 0x07; // Brightness (0-7) + unsigned char spd = speed & 0x03; // Speed (0-3) + + zone_data->option2 = (selectall << 7) | (direction << 6) | (colorsel << 5) | (bright << 2) | spd; +} + +void MSIMysticLight290Controller::GetMode + ( + MSI_ZONE zone, + MSI_MODE &mode, + MSI_SPEED &speed, + MSI_BRIGHTNESS &brightness, + bool &rainbow_color, + unsigned int &color + ) +{ + MSILedKeeperZoneData* zone_data = GetZoneData(zone); + if(zone_data == nullptr) + { + return; + } + + mode = (MSI_MODE)zone_data->effect; + + // Decode Option2 based on MSI LedKeeper encoding: + // Bits 0-1: Speed (0-3) + // Bits 2-4: Brightness (0-7) + // Bit 5: ColorSelection (0=Rainbow, 1=User) + speed = (MSI_SPEED)(zone_data->option2 & 0x03); + brightness = (MSI_BRIGHTNESS)((zone_data->option2 >> 2) & 0x07); + rainbow_color = ((zone_data->option2 >> 5) & 0x01) == 0; // Rainbow=0, User=1 + + color = (zone_data->color_1.R << 16) | (zone_data->color_1.G << 8) | zone_data->color_1.B; +} + +bool MSIMysticLight290Controller::Update(bool save) +{ + data.save_data = save ? 1 : 0; + + bool result = (hid_send_feature_report(dev, (unsigned char*)&data, sizeof(data)) == sizeof(data)); + LOG_DEBUG("[MSI 290] Update result: %s (sent %d bytes, report ID: 0x%02X)", + result ? "SUCCESS" : "FAILED", (int)sizeof(data), data.report_id); + return result; +} + +bool MSIMysticLight290Controller::ReadSettings() +{ + return (hid_get_feature_report(dev, (unsigned char*)&data, sizeof(data)) == sizeof(data)); +} + +std::string MSIMysticLight290Controller::GetDeviceName() +{ + return name; +} + +std::string MSIMysticLight290Controller::GetFWVersion() +{ + return std::string("AP/LD ").append(version_APROM).append(" / ").append(version_LDROM); +} + +std::string MSIMysticLight290Controller::GetDeviceLocation() +{ + return("HID: " + location); +} + +std::string MSIMysticLight290Controller::GetSerial() +{ + wchar_t serial_string[128]; + int ret = hid_get_serial_number_string(dev, serial_string, 128); + + if(ret != 0) + { + return(""); + } + + return(StringUtils::wstring_to_string(serial_string)); +} + +void MSIMysticLight290Controller::ReadName() +{ + wchar_t tname[256]; + + // Get the manufacturer string from HID + hid_get_manufacturer_string(dev, tname, 256); + name = StringUtils::wstring_to_string(tname); + + // Get the product string from HID + hid_get_product_string(dev, tname, 256); + name.append(" ").append(StringUtils::wstring_to_string(tname)); +} + +bool MSIMysticLight290Controller::ReadFwVersion() +{ + unsigned char request[64]; + unsigned char response[64]; + int ret_val = 64; + + // Zero out buffers + memset(request, 0x00, sizeof(request)); + memset(response, 0x00, sizeof(response)); + + // Read APROM version + request[0x00] = 0x01; + request[0x01] = 0xB0; + memset(&request[0x02], 0xCC, sizeof(request) - 2); + + ret_val &= hid_write(dev, request, 64); + ret_val &= hid_read(dev, response, 64); + + unsigned char highValue = response[2] >> 4; + unsigned char lowValue = response[2] & 0x0F; + version_APROM = std::to_string((int)highValue).append(".").append(std::to_string((int)lowValue)); + + // Read LDROM version + request[0x00] = 0x01; + request[0x01] = 0xB6; + + ret_val &= hid_write(dev, request, 64); + ret_val &= hid_read(dev, response, 64); + + highValue = response[2] >> 4; + lowValue = response[2] & 0x0F; + version_LDROM = std::to_string((int)highValue).append(".").append(std::to_string((int)lowValue)); + + return (ret_val > 0); +} + +MSILedKeeperZoneData* MSIMysticLight290Controller::GetZoneData(MSI_ZONE zone) +{ + int index = GetZoneIndex(zone); + if(index >= 0 && index < 18) + { + return &data.zones[index]; + } + return nullptr; +} + +int MSIMysticLight290Controller::GetZoneIndex(MSI_ZONE zone) +{ + // Map MSI zones to MSI LedKeeper array indices based on Enum_LedArea: + // JARGB1(0), JARGB2(1), JARGB3(2), JAF(3), JPIPE1(4), JPIPE2(5), JPIPE3(6), JPIPE4(7), JPIPE5(8), + // JRGB1(9), JRGB2(10), Onboard1(11), Onboard2(12), Onboard3(13), Onboard4(14), Onboard5(15), Onboard6(16), SelectAll(17) + + switch(zone) + { + case MSI_ZONE_J_RAINBOW_1: return 0; // JARGB1 -> JARGB_V2_1 + case MSI_ZONE_J_RAINBOW_2: return 1; // JARGB2 -> JARGB_V2_2 + case MSI_ZONE_J_RAINBOW_3: return 2; // JARGB3 -> JARGB_V2_3 + case MSI_ZONE_JAF: return 3; // JAF -> EZ Conn + case MSI_ZONE_J_RGB_1: return 9; // JRGB1 -> JRGB1 + case MSI_ZONE_ON_BOARD_LED_0: return 4; // Onboard1 -> JPIPE1 (IO COVER) + default: + return -1; // Zone not supported + } +} \ No newline at end of file diff --git a/Controllers/MSIMysticLightController/MSIMysticLight290Controller/MSIMysticLight290Controller.h b/Controllers/MSIMysticLightController/MSIMysticLight290Controller/MSIMysticLight290Controller.h new file mode 100644 index 0000000000000000000000000000000000000000..3a09dbfc415280f6b9d92ea49db90561e7e8656e --- /dev/null +++ b/Controllers/MSIMysticLightController/MSIMysticLight290Controller/MSIMysticLight290Controller.h @@ -0,0 +1,91 @@ +/*---------------------------------------------------------*\ +| MSIMysticLight290Controller.h | +| | +| Driver for MSI Mystic Light 290-byte motherboard | +| Uses the native MSI LedKeeper protocol format | +| | +| mysty Dec 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include "RGBController.h" +#include "MSIMysticLightCommon.h" +#include "hidapi_wrapper.h" +#include +#include + +class MSIMysticLight290Controller +{ +public: + MSIMysticLight290Controller(hid_device* handle, const char* path, unsigned short pid); + ~MSIMysticLight290Controller(); + + void SetMode + ( + MSI_ZONE zone, + MSI_MODE mode, + MSI_SPEED speed, + MSI_BRIGHTNESS brightness, + bool rainbow_color + ); + + void SetZoneColor + ( + MSI_ZONE zone, + unsigned char red1, + unsigned char grn1, + unsigned char blu1, + unsigned char red2, + unsigned char grn2, + unsigned char blu2 + ); + + void SetZoneColors + ( + MSI_ZONE zone, + unsigned char mode, + unsigned char speed, + unsigned char brightness, + std::vector colors + ); + + void GetMode + ( + MSI_ZONE zone, + MSI_MODE &mode, + MSI_SPEED &speed, + MSI_BRIGHTNESS &brightness, + bool &rainbow_color, + unsigned int &color + ); + + bool Update(bool save); + bool ReadSettings(); + + std::string GetDeviceName(); + std::string GetFWVersion(); + std::string GetDeviceLocation(); + std::string GetSerial(); + + const std::vector* GetSupportedZones() { return supported_zones; } + +private: + void ReadName(); + bool ReadFwVersion(); + + MSILedKeeperZoneData* GetZoneData(MSI_ZONE zone); + int GetZoneIndex(MSI_ZONE zone); + + hid_device* dev; + std::string name; + std::string location; + std::string version_APROM; + std::string version_LDROM; + + FeaturePacket_290 data; + const std::vector* supported_zones; +}; diff --git a/Controllers/MSIMysticLightController/MSIMysticLight290Controller/RGBController_MSIMysticLight290.cpp b/Controllers/MSIMysticLightController/MSIMysticLight290Controller/RGBController_MSIMysticLight290.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1ce42f606726a7a7cac0fc619f574de309fd1864 --- /dev/null +++ b/Controllers/MSIMysticLightController/MSIMysticLight290Controller/RGBController_MSIMysticLight290.cpp @@ -0,0 +1,484 @@ +/*---------------------------------------------------------*\ +| RGBController_MSIMysticLight290.cpp | +| | +| RGBController for MSI Mystic Light 290-byte controller | +| | +| mysty Dec 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include "RGBController_MSIMysticLight290.h" +#include + +/**------------------------------------------------------------------*\ + @name MSI Mystic Light 290 + @category Motherboard + @type USB + @save :white_check_mark: + @direct :x: + @effects :white_check_mark: + @detectors DetectMSIMysticLight290Controllers + @comment Uses native MSI LedKeeper 290-byte protocol +\*-------------------------------------------------------------------*/ + +RGBController_MSIMysticLight290::RGBController_MSIMysticLight290(MSIMysticLight290Controller* controller_ptr) +{ + controller = controller_ptr; + + name = controller->GetDeviceName(); + vendor = "MSI"; + type = DEVICE_TYPE_MOTHERBOARD; + description = "MSI Mystic Light 290-byte Device"; + location = controller->GetDeviceLocation(); + serial = controller->GetSerial(); + version = controller->GetFWVersion(); + + InitializeMode(); + + SetupZones(); + + /*---------------------------------------------------------*\ + | Initialize the device with current modes | + \*---------------------------------------------------------*/ + active_mode = 0; +} + +RGBController_MSIMysticLight290::~RGBController_MSIMysticLight290() +{ + delete controller; +} + +void RGBController_MSIMysticLight290::InitializeMode() +{ + /*-----------------------------------------------------------------*\ + | MSI LedKeeper supports the following lighting effects: | + | Off (0), Wave (1), Steady (2), Flame (3), Breathing (4), | + | ColorRing (5), Lightning (6), Recreation (7), Meteor (8), | + | Advanced (9), GodLike (10) | + \*-----------------------------------------------------------------*/ + + mode Off; + Off.name = "Off"; + Off.value = MSI_LEDKEEPER_OFF; + Off.flags = 0; + Off.color_mode = MODE_COLORS_NONE; + modes.push_back(Off); + + mode Wave; + Wave.name = "Wave"; + Wave.value = MSI_LEDKEEPER_WAVE; + Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR; + Wave.speed_min = MSI_SPEED_MIN; + Wave.speed_max = MSI_SPEED_MAX; + Wave.speed = MSI_SPEED_DEFAULT; + Wave.brightness_min = MSI_BRIGHTNESS_MIN; + Wave.brightness_max = MSI_BRIGHTNESS_MAX; + Wave.brightness = MSI_BRIGHTNESS_DEFAULT; + Wave.color_mode = MODE_COLORS_MODE_SPECIFIC; + Wave.colors_min = 1; + Wave.colors_max = 4; + Wave.colors.resize(4); + Wave.colors[0] = 0x00FF0000; // Red + Wave.colors[1] = 0x0000FF00; // Green + Wave.colors[2] = 0x000000FF; // Blue + Wave.colors[3] = 0x00FFFFFF; // White + modes.push_back(Wave); + + mode Steady; + Steady.name = "Steady"; + Steady.value = MSI_LEDKEEPER_STEADY; + Steady.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_PER_LED_COLOR; + Steady.brightness_min = MSI_BRIGHTNESS_MIN; + Steady.brightness_max = MSI_BRIGHTNESS_MAX; + Steady.brightness = MSI_BRIGHTNESS_DEFAULT; + Steady.color_mode = MODE_COLORS_PER_LED; + Steady.colors_min = 1; + Steady.colors_max = 1; + Steady.colors.resize(1); + Steady.colors[0] = 0x00FF0000; // Red + modes.push_back(Steady); + + mode Flame; + Flame.name = "Flame"; + Flame.value = MSI_LEDKEEPER_FLAME; + Flame.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_PER_LED_COLOR; + Flame.speed_min = MSI_SPEED_MIN; + Flame.speed_max = MSI_SPEED_MAX; + Flame.speed = MSI_SPEED_DEFAULT; + Flame.brightness_min = MSI_BRIGHTNESS_MIN; + Flame.brightness_max = MSI_BRIGHTNESS_MAX; + Flame.brightness = MSI_BRIGHTNESS_DEFAULT; + Flame.color_mode = MODE_COLORS_PER_LED; + Flame.colors_min = 1; + Flame.colors_max = 1; + Flame.colors.resize(1); + Flame.colors[0] = 0x00FF0000; // Red + modes.push_back(Flame); + + mode Breathing; + Breathing.name = "Breathing"; + Breathing.value = MSI_LEDKEEPER_BREATHING; + Breathing.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_PER_LED_COLOR; + Breathing.speed_min = MSI_SPEED_MIN; + Breathing.speed_max = MSI_SPEED_MAX; + Breathing.speed = MSI_SPEED_DEFAULT; + Breathing.brightness_min = MSI_BRIGHTNESS_MIN; + Breathing.brightness_max = MSI_BRIGHTNESS_MAX; + Breathing.brightness = MSI_BRIGHTNESS_DEFAULT; + Breathing.color_mode = MODE_COLORS_PER_LED; + Breathing.colors_min = 1; + Breathing.colors_max = 1; + Breathing.colors.resize(1); + Breathing.colors[0] = 0x00FF0000; // Red + modes.push_back(Breathing); + + mode ColorRing; + ColorRing.name = "Color Ring"; + ColorRing.value = MSI_LEDKEEPER_COLOR_RING; + ColorRing.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR; + ColorRing.speed_min = MSI_SPEED_MIN; + ColorRing.speed_max = MSI_SPEED_MAX; + ColorRing.speed = MSI_SPEED_DEFAULT; + ColorRing.brightness_min = MSI_BRIGHTNESS_MIN; + ColorRing.brightness_max = MSI_BRIGHTNESS_MAX; + ColorRing.brightness = MSI_BRIGHTNESS_DEFAULT; + ColorRing.color_mode = MODE_COLORS_MODE_SPECIFIC; + ColorRing.colors_min = 1; + ColorRing.colors_max = 4; + ColorRing.colors.resize(4); + ColorRing.colors[0] = 0x00FF0000; // Red + ColorRing.colors[1] = 0x0000FF00; // Green + ColorRing.colors[2] = 0x000000FF; // Blue + ColorRing.colors[3] = 0x00FFFFFF; // White + modes.push_back(ColorRing); + + mode Lightning; + Lightning.name = "Lightning"; + Lightning.value = MSI_LEDKEEPER_LIGHTNING; + Lightning.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_PER_LED_COLOR; + Lightning.speed_min = MSI_SPEED_MIN; + Lightning.speed_max = MSI_SPEED_MAX; + Lightning.speed = MSI_SPEED_DEFAULT; + Lightning.brightness_min = MSI_BRIGHTNESS_MIN; + Lightning.brightness_max = MSI_BRIGHTNESS_MAX; + Lightning.brightness = MSI_BRIGHTNESS_DEFAULT; + Lightning.color_mode = MODE_COLORS_PER_LED; + Lightning.colors_min = 1; + Lightning.colors_max = 1; + Lightning.colors.resize(1); + Lightning.colors[0] = 0x00FF0000; // Red + modes.push_back(Lightning); + + mode Recreation; + Recreation.name = "Recreation"; + Recreation.value = MSI_LEDKEEPER_RECREATION; + Recreation.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_PER_LED_COLOR; + Recreation.speed_min = MSI_SPEED_MIN; + Recreation.speed_max = MSI_SPEED_MAX; + Recreation.speed = MSI_SPEED_DEFAULT; + Recreation.brightness_min = MSI_BRIGHTNESS_MIN; + Recreation.brightness_max = MSI_BRIGHTNESS_MAX; + Recreation.brightness = MSI_BRIGHTNESS_DEFAULT; + Recreation.color_mode = MODE_COLORS_PER_LED; + Recreation.colors_min = 1; + Recreation.colors_max = 1; + Recreation.colors.resize(1); + Recreation.colors[0] = 0x00FF0000; // Red + modes.push_back(Recreation); + + mode Meteor; + Meteor.name = "Meteor"; + Meteor.value = MSI_LEDKEEPER_METEOR; + Meteor.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR; + Meteor.speed_min = MSI_SPEED_MIN; + Meteor.speed_max = MSI_SPEED_MAX; + Meteor.speed = MSI_SPEED_DEFAULT; + Meteor.brightness_min = MSI_BRIGHTNESS_MIN; + Meteor.brightness_max = MSI_BRIGHTNESS_MAX; + Meteor.brightness = MSI_BRIGHTNESS_DEFAULT; + Meteor.color_mode = MODE_COLORS_MODE_SPECIFIC; + Meteor.colors_min = 1; + Meteor.colors_max = 4; + Meteor.colors.resize(4); + Meteor.colors[0] = 0x00FF0000; // Red + Meteor.colors[1] = 0x0000FF00; // Green + Meteor.colors[2] = 0x000000FF; // Blue + Meteor.colors[3] = 0x00FFFFFF; // White + modes.push_back(Meteor); + + mode Advanced; + Advanced.name = "Advanced"; + Advanced.value = MSI_LEDKEEPER_ADVANCED; + Advanced.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR; + Advanced.speed_min = MSI_SPEED_MIN; + Advanced.speed_max = MSI_SPEED_MAX; + Advanced.speed = MSI_SPEED_DEFAULT; + Advanced.brightness_min = MSI_BRIGHTNESS_MIN; + Advanced.brightness_max = MSI_BRIGHTNESS_MAX; + Advanced.brightness = MSI_BRIGHTNESS_DEFAULT; + Advanced.color_mode = MODE_COLORS_MODE_SPECIFIC; + Advanced.colors_min = 1; + Advanced.colors_max = 4; + Advanced.colors.resize(4); + Advanced.colors[0] = 0x00FF0000; // Red + Advanced.colors[1] = 0x0000FF00; // Green + Advanced.colors[2] = 0x000000FF; // Blue + Advanced.colors[3] = 0x00FFFFFF; // White + modes.push_back(Advanced); + + mode GodLike; + GodLike.name = "GodLike"; + GodLike.value = MSI_LEDKEEPER_GODLIKE; + GodLike.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR; + GodLike.speed_min = MSI_SPEED_MIN; + GodLike.speed_max = MSI_SPEED_MAX; + GodLike.speed = MSI_SPEED_DEFAULT; + GodLike.brightness_min = MSI_BRIGHTNESS_MIN; + GodLike.brightness_max = MSI_BRIGHTNESS_MAX; + GodLike.brightness = MSI_BRIGHTNESS_DEFAULT; + GodLike.color_mode = MODE_COLORS_MODE_SPECIFIC; + GodLike.colors_min = 1; + GodLike.colors_max = 4; + GodLike.colors.resize(4); + GodLike.colors[0] = 0x00FF0000; // Red + GodLike.colors[1] = 0x0000FF00; // Green + GodLike.colors[2] = 0x000000FF; // Blue + GodLike.colors[3] = 0x00FFFFFF; // White + modes.push_back(GodLike); +} + +void RGBController_MSIMysticLight290::SetupZones() +{ + zones.clear(); + leds.clear(); + + const std::vector* supported_zones = controller->GetSupportedZones(); + + for(size_t zone_idx = 0; zone_idx < supported_zones->size(); zone_idx++) + { + MSI_ZONE msi_zone = (*supported_zones)[zone_idx]; + + zone new_zone; + + switch(msi_zone) + { + case MSI_ZONE_J_RAINBOW_1: + new_zone.name = "JARGB_V2_1"; + new_zone.type = ZONE_TYPE_SINGLE; + new_zone.leds_min = 1; + new_zone.leds_max = 1; + new_zone.leds_count = 1; + new_zone.matrix_map = NULL; + break; + + case MSI_ZONE_J_RAINBOW_2: + new_zone.name = "JARGB_V2_2"; + new_zone.type = ZONE_TYPE_SINGLE; + new_zone.leds_min = 1; + new_zone.leds_max = 1; + new_zone.leds_count = 1; + new_zone.matrix_map = NULL; + break; + + case MSI_ZONE_J_RAINBOW_3: + new_zone.name = "JARGB_V2_3"; + new_zone.type = ZONE_TYPE_SINGLE; + new_zone.leds_min = 1; + new_zone.leds_max = 1; + new_zone.leds_count = 1; + new_zone.matrix_map = NULL; + break; + + case MSI_ZONE_JAF: + new_zone.name = "EZ Conn"; + new_zone.type = ZONE_TYPE_SINGLE; + new_zone.leds_min = 1; + new_zone.leds_max = 1; + new_zone.leds_count = 1; + new_zone.matrix_map = NULL; + break; + + case MSI_ZONE_J_RGB_1: + new_zone.name = "JRGB1"; + new_zone.type = ZONE_TYPE_SINGLE; + new_zone.leds_min = 1; + new_zone.leds_max = 1; + new_zone.leds_count = 1; + new_zone.matrix_map = NULL; + break; + + case MSI_ZONE_ON_BOARD_LED_0: + new_zone.name = "IO COVER"; + new_zone.type = ZONE_TYPE_SINGLE; + new_zone.leds_min = 1; + new_zone.leds_max = 1; + new_zone.leds_count = 1; + new_zone.matrix_map = NULL; + break; + + default: + new_zone.name = "Unknown Zone"; + new_zone.type = ZONE_TYPE_SINGLE; + new_zone.leds_min = 1; + new_zone.leds_max = 1; + new_zone.leds_count = 1; + new_zone.matrix_map = NULL; + break; + } + + zones.push_back(new_zone); + + /*---------------------------------------------------------*\ + | Create LEDs for this zone | + \*---------------------------------------------------------*/ + for(unsigned int led_idx = 0; led_idx < new_zone.leds_count; led_idx++) + { + led new_led; + new_led.name = new_zone.name + " LED " + std::to_string(led_idx + 1); + new_led.value = msi_zone; + leds.push_back(new_led); + } + } + + SetupColors(); +} + +void RGBController_MSIMysticLight290::ResizeZone(int /*zone*/, int /*new_size*/) +{ + /*---------------------------------------------------------*\ + | This device does not support resizing zones | + \*---------------------------------------------------------*/ +} + +void RGBController_MSIMysticLight290::DeviceUpdateLEDs() +{ + for(size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++) + { + UpdateZoneLEDs(zone_idx, false); // Don't update hardware for each zone + } + + controller->Update(false); // Update hardware once for all zones +} + +void RGBController_MSIMysticLight290::UpdateZoneLEDs(int zone_idx) +{ + UpdateZoneLEDs(zone_idx, true); // Default to updating hardware +} + +void RGBController_MSIMysticLight290::UpdateZoneLEDs(int zone_idx, bool update_hardware) +{ + LOG_DEBUG("[MSI 290] Updating zone %d", zone_idx); + if(zone_idx >= (int)zones.size()) + { + return; + } + + /*---------------------------------------------------------*\ + | Get the MSI zone ID for this zone | + \*---------------------------------------------------------*/ + const std::vector* supported_zones = controller->GetSupportedZones(); + MSI_ZONE msi_zone = (*supported_zones)[zone_idx]; + + /*---------------------------------------------------------*\ + | Get the first LED color for this zone | + \*---------------------------------------------------------*/ + size_t led_idx = 0; + for(size_t i = 0; i < zone_idx; i++) + { + led_idx += zones[i].leds_count; + } + + RGBColor zone_color = colors[led_idx]; + + /*---------------------------------------------------------*\ + | Update only colors, preserving the current mode | + \*---------------------------------------------------------*/ + controller->SetZoneColor(msi_zone, + RGBGetRValue(zone_color), + RGBGetGValue(zone_color), + RGBGetBValue(zone_color), + RGBGetRValue(zone_color), // Use same color for secondary + RGBGetGValue(zone_color), + RGBGetBValue(zone_color)); + + // Update hardware immediately for individual zone changes + if(update_hardware) + { + controller->Update(false); + } +} + +void RGBController_MSIMysticLight290::UpdateSingleLED(int led_idx) +{ + /*---------------------------------------------------------*\ + | Find which zone this LED belongs to | + \*---------------------------------------------------------*/ + int zone_idx = 0; + int led_offset = 0; + + for(size_t zone = 0; zone < zones.size(); zone++) + { + if(led_idx < led_offset + (int)zones[zone].leds_count) + { + zone_idx = zone; + break; + } + led_offset += zones[zone].leds_count; + } + + UpdateZoneLEDs(zone_idx); + + // UpdateZoneLEDs already calls controller->Update(), so no need to call it again +} + +void RGBController_MSIMysticLight290::DeviceUpdateMode() +{ + for(size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++) + { + const std::vector* supported_zones = controller->GetSupportedZones(); + MSI_ZONE msi_zone = (*supported_zones)[zone_idx]; + + /*---------------------------------------------------------*\ + | Get the zone's LED color | + \*---------------------------------------------------------*/ + size_t led_idx = 0; + for(size_t i = 0; i < zone_idx; i++) + { + led_idx += zones[i].leds_count; + } + RGBColor zone_color = colors[led_idx]; + + // Use appropriate colors based on the mode's color mode + std::vector zone_colors; + + if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC) + { + // For mode-specific colors, use the colors defined in the mode + for(size_t i = 0; i < modes[active_mode].colors.size(); i++) + { + zone_colors.push_back(modes[active_mode].colors[i]); + } + } + else + { + // For per-LED modes, use the zone's LED color + zone_colors.push_back(zone_color); + } + + controller->SetZoneColors(msi_zone, + (unsigned char)modes[active_mode].value, + (unsigned char)modes[active_mode].speed, + (unsigned char)modes[active_mode].brightness, + zone_colors); + } + + controller->Update(false); +} + +void RGBController_MSIMysticLight290::DeviceSaveMode() +{ + DeviceUpdateMode(); + controller->Update(true); +} \ No newline at end of file diff --git a/Controllers/MSIMysticLightController/MSIMysticLight290Controller/RGBController_MSIMysticLight290.h b/Controllers/MSIMysticLightController/MSIMysticLight290Controller/RGBController_MSIMysticLight290.h new file mode 100644 index 0000000000000000000000000000000000000000..ec44f45b7405cf34ffe90a045a384b2c333568e9 --- /dev/null +++ b/Controllers/MSIMysticLightController/MSIMysticLight290Controller/RGBController_MSIMysticLight290.h @@ -0,0 +1,39 @@ +/*---------------------------------------------------------*\ +| RGBController_MSIMysticLight290.h | +| | +| RGBController for MSI Mystic Light 290-byte controller | +| | +| mysty Dec 2024 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include "RGBController.h" +#include "MSIMysticLight290Controller.h" + +class RGBController_MSIMysticLight290 : public RGBController +{ +public: + RGBController_MSIMysticLight290(MSIMysticLight290Controller* controller_ptr); + ~RGBController_MSIMysticLight290(); + + void SetupZones(); + + void ResizeZone(int zone, int new_size); + + void DeviceUpdateLEDs(); + void UpdateZoneLEDs(int zone); + void UpdateZoneLEDs(int zone, bool update_hardware); + void UpdateSingleLED(int led); + + void DeviceUpdateMode(); + void DeviceSaveMode(); + +private: + MSIMysticLight290Controller* controller; + + void InitializeMode(); +}; diff --git a/Controllers/MSIMysticLightController/MSIMysticLightCommon.h b/Controllers/MSIMysticLightController/MSIMysticLightCommon.h index 18563fc0659f60e01d523c5d0d7fb8b00497fc7a..b2290f2bffed90374da712636875155be374560c 100644 --- a/Controllers/MSIMysticLightController/MSIMysticLightCommon.h +++ b/Controllers/MSIMysticLightController/MSIMysticLightCommon.h @@ -119,6 +119,36 @@ enum MSI_BRIGHTNESS MSI_BRIGHTNESS_LEVEL_100 = 10, }; +/*---------------------------------------------------------------*\ +| MSI LedKeeper modes (for 290-byte controllers) | +\*---------------------------------------------------------------*/ +enum MSI_LEDKEEPER_MODE +{ + MSI_LEDKEEPER_OFF = 0, // Off + MSI_LEDKEEPER_WAVE = 1, // Wave + MSI_LEDKEEPER_STEADY = 2, // Steady + MSI_LEDKEEPER_FLAME = 3, // Flame + MSI_LEDKEEPER_BREATHING = 4, // Breathing + MSI_LEDKEEPER_COLOR_RING = 5, // ColorRing + MSI_LEDKEEPER_LIGHTNING = 6, // Lightning + MSI_LEDKEEPER_RECREATION = 7, // Recreation + MSI_LEDKEEPER_METEOR = 8, // Meteor + MSI_LEDKEEPER_ADVANCED = 9, // Advanced + MSI_LEDKEEPER_GODLIKE = 10 // GodLike +}; + +/*---------------------------------------------------------------*\ +| MSI LedKeeper constants (for 290-byte controllers) | +\*---------------------------------------------------------------*/ +#define MSI_SPEED_MIN 0 // Low +#define MSI_SPEED_MAX 2 // High +#define MSI_SPEED_DEFAULT 1 // Medium + +#define MSI_BRIGHTNESS_MIN 0 // B0 +#define MSI_BRIGHTNESS_MAX 5 // B100 +#define MSI_BRIGHTNESS_DEFAULT 5 // B100 + + #define NUMOF_PER_LED_MODE_LEDS 240 #define NUM_LEDS_761 720 @@ -275,4 +305,24 @@ struct FeaturePacket_761 FeaturePacket_Zone_761 jaf; }; +// MSI LedKeeper 290-byte protocol structure +struct MSILedKeeperZoneData +{ + unsigned char effect; // LightingMode + Color color_1; // Color_1 (RGB) + Color color_2; // Color_2 (RGB) + Color color_3; // Color_3 (RGB) + Color color_4; // Color_4 (RGB) + unsigned char option1; // Option1 + unsigned char option2; // Option2 + unsigned char cycle_num; // Cycle_Num +}; + +struct FeaturePacket_290 +{ + unsigned char report_id = 0x50; // Report ID + MSILedKeeperZoneData zones[18]; // 18 zones × 16 bytes = 288 bytes + unsigned char save_data = 0; // Save flag (289th byte) +}; + #define MSI_USB_PID_COMMON 0x0076 // Common PID for a certain set of 185-byte boards diff --git a/Controllers/MSIMysticLightController/MSIMysticLightControllerDetect.cpp b/Controllers/MSIMysticLightController/MSIMysticLightControllerDetect.cpp index 9fb24b712ee0e22192045e2f1567ee680bacbcda..a017f71ebca6f0fe1ecb7ec651d214fe77d93b13 100644 --- a/Controllers/MSIMysticLightController/MSIMysticLightControllerDetect.cpp +++ b/Controllers/MSIMysticLightController/MSIMysticLightControllerDetect.cpp @@ -20,6 +20,8 @@ #include "RGBController_MSIMysticLight761.h" #include "dmiinfo.h" #include "LogManager.h" +#include "MSIMysticLight290Controller/MSIMysticLight290Controller.h" +#include "MSIMysticLight290Controller/RGBController_MSIMysticLight290.h" #define MSI_USB_VID 0x1462 #define MSI_USB_VID_COMMON 0x0DB0 @@ -47,12 +49,20 @@ void DetectMSIMysticLightControllers ) { hid_device* dev = hid_open_path(info->path); + LOG_INFO("[MSI Detection] Start with dev: %p", dev); if(dev != nullptr) { unsigned char temp_buffer[200]; temp_buffer[0] = 0x52; size_t packet_length = hid_get_feature_report(dev, temp_buffer, 200); + LOG_DEBUG("[MSI Detection] Packet length: %d", packet_length); + + if (packet_length == -1) { + temp_buffer[0] = 0x50; + packet_length = hid_get_feature_report(dev, temp_buffer, 300); + LOG_DEBUG("[MSI Detection] Packet length (attempt 2): %d", packet_length); + } DMIInfo dmi; std::string dmi_name = "MSI " + dmi.getMainboard(); @@ -78,6 +88,15 @@ void DetectMSIMysticLightControllers ResourceManager::get()->RegisterRGBController(rgb_controller); } + else if(packet_length == sizeof(FeaturePacket_290)) + { + LOG_DEBUG("[MSI 290 Detection] Using 290-byte MSI LedKeeper controller"); + DMIInfo dmi; + MSIMysticLight290Controller* controller = new MSIMysticLight290Controller(dev, info->path, info->product_id); + RGBController_MSIMysticLight290* rgb_controller = new RGBController_MSIMysticLight290(controller); + rgb_controller->name = "MSI " + dmi.getMainboard(); + ResourceManager::get()->RegisterRGBController(rgb_controller); + } else // no supported length returned { @@ -146,7 +165,6 @@ void DetectMSIMysticLight64Controllers } } - REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_1562", DetectMSIMysticLight64Controllers, MSI_USB_VID, 0x1562, 0x00FF, 0x01); REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_1563", DetectMSIMysticLight64Controllers, MSI_USB_VID, 0x1563, 0x00FF, 0x01); REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_1564", DetectMSIMysticLight64Controllers, MSI_USB_VID, 0x1564, 0x00FF, 0x01); @@ -244,6 +262,7 @@ REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7E06", DetectMSIMysticLightCont REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7E07", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7E07, 0x0001, 0x00); REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7E09", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7E09, 0x0001, 0x00); REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7E10", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7E10, 0x0001, 0x00); +REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7E59", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7E59, 0x0001, 0x00); REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_B926", DetectMSIMysticLightControllers, MSI_USB_VID, 0xB926, 0x0001, 0x00); REGISTER_HID_DETECTOR_PU("MSI Mystic Light MS_7E81", DetectMSIMysticLightControllers, MSI_USB_VID, 0x7E81, 0x0001, 0x00); // Detector for the set of common boards