From 85240143b6ed52f2fa5d1cc927d36355c29d2803 Mon Sep 17 00:00:00 2001 From: Adam Honse Date: Thu, 15 Oct 2026 21:45:36 -0500 Subject: [PATCH] Alienware rework for 0x0812 - Direct mode, zones --- .../AlienwareController.cpp | 681 +++++++++++++----- .../AlienwareController/AlienwareController.h | 48 +- .../RGBController_Alienware.cpp | 158 ++-- 3 files changed, 631 insertions(+), 256 deletions(-) diff --git a/Controllers/AlienwareController/AlienwareController.cpp b/Controllers/AlienwareController/AlienwareController.cpp index 3f2a3e94..b08fa25d 100644 --- a/Controllers/AlienwareController/AlienwareController.cpp +++ b/Controllers/AlienwareController/AlienwareController.cpp @@ -43,6 +43,7 @@ static const std::map zone_quirks_table = \*---------------------------------------------------------*/ static const std::map> zone_names_table = { + { 0x0812, { "Infinity Loop", "Intake Fan Ring", "Bottom Fan Ring", "AIO Pump Head", "Alienware Logo" } }, { 0x0C01, { "Left", "Middle", "Right", "Numpad" } }, { 0x0A01, { "Left", "Center Left", "Center Right", "Right", "Light Bar 1", "Light Bar 2", "Light Bar 3", @@ -51,7 +52,7 @@ static const std::map> zone_name "Light Bar 10", "Light Bar 11", "Light Bar 12" } }, { 0x0E03, { "Left", "Middle", "Right", "Numpad" } }, { 0x0E07, { "Left", "Middle", "Right", "Numpad" } }, - { 0x0E0A, { "Left", "Middle", "Right", "Numpad" } } + { 0x0E0A, { "Left", "Middle", "Right", "Numpad" } }, }; /*---------------------------------------------------------*\ @@ -62,7 +63,27 @@ static const std::map> zone_ids_tabl { 0x0C01, { 0x00, 0x01, 0x02, 0x03 } }, // Dell G5 SE 5505 { 0x0E03, { 0x00, 0x01, 0x02, 0x03 } }, // Dell G15 5511 { 0x0E07, { 0x10, 0x11, 0x12, 0x13 } }, // Dell G15 5520 - { 0x0E0A, { 0x10, 0x11, 0x12, 0x13 } } // Dell G15 5530 + { 0x0E0A, { 0x10, 0x11, 0x12, 0x13 } }, // Dell G15 5530 + // 0x0812 (Area-51) intentionally absent: its OpenRGB + // zones group many hardware IDs (see zone_layout_table) +}; + +/*---------------------------------------------------------*\ +| Per-platform LED layout: hardware IDs per OpenRGB zone, | +| in LED order. Platforms absent here use one zone with | +| one LED per hardware ID (see zone_ids_table). | +\*---------------------------------------------------------*/ +static const std::map>> zone_leds_table = +{ + { 0x0812, + { + { 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94, + 95,93,91,89,87,85,83,81,79,77,75,73,71,69,67,65,63,61,59,57,55,53,51,49,47,45,43,41,39,37,35,33,31,29,27,25,23,21,19,17,15,13,11,9,7,5,3,1 }, + { 96 }, + { 97 }, + { 98 }, + { 99, 100 }, + } }, }; static void SendHIDReport(hid_device *dev, const unsigned char* usb_buf, size_t usb_buf_size) @@ -115,7 +136,21 @@ AlienwareController::AlienwareController(hid_device* dev_handle, const hid_devic /*-----------------------------------------------------*\ | Check if the device reports the wrong number of zones | \*-----------------------------------------------------*/ - unsigned number_of_zones = zone_quirks_table.count(platform_id) ? zone_quirks_table.at(platform_id) : report.data[6]; + unsigned number_of_zones = 0; + + if(report.data[1] == 0x03) + { + number_of_zones = zone_quirks_table.count(platform_id) ? zone_quirks_table.at(platform_id) : report.data[6]; + + if(number_of_zones == 0) + { + LOG_WARNING("[%s] Config report claimed zero zones, skipping zone init", ALIENWARE_CONTROLLER_NAME); + } + } + else + { + LOG_WARNING("[%s] Config report failed, claiming zero zones", ALIENWARE_CONTROLLER_NAME); + } /*-----------------------------------------------------*\ | Get firmware version | @@ -136,6 +171,41 @@ AlienwareController::AlienwareController(hid_device* dev_handle, const hid_devic { LOG_INFO("[%s] Known platform: %8X, Number of zones: %d", ALIENWARE_CONTROLLER_NAME, platform_id, number_of_zones); zone_names = zone_names_table.at(platform_id); + + /*-------------------------------------------------*\ + | Grouped zones: one OpenRGB zone per fixture, with | + | zone_leds_table giving the hardware IDs per zone | + \*-------------------------------------------------*/ + if(zone_leds_table.count(platform_id)) + { + const std::vector>& layout = zone_leds_table.at(platform_id); + + zone_names = zone_names_table.at(platform_id); + zones.resize(layout.size()); + zone_ids.clear(); + zone_led_counts.clear(); + + for(const std::vector& hw_ids : layout) + { + zone_led_counts.push_back((unsigned int)hw_ids.size()); + for(uint8_t hw_id : hw_ids) + { + zone_ids.push_back(hw_id); + } + } + + number_of_zones = (unsigned)zones.size(); + } + + if(zone_names.size() != number_of_zones) + { + LOG_WARNING("[%s] Zone name table has %d entries for %d zones; padding with Unknown", + ALIENWARE_CONTROLLER_NAME, (int)zone_names.size(), number_of_zones); + while(zone_names.size() < number_of_zones) + { + zone_names.emplace_back("Unknown"); + } + } } else { @@ -154,9 +224,14 @@ AlienwareController::AlienwareController(hid_device* dev_handle, const hid_devic /*-----------------------------------------------------*\ | Initialize hardware zone IDs | \*-----------------------------------------------------*/ - if(zone_ids_table.count(platform_id)) + if(zone_leds_table.count(platform_id)) + { + /* Already filled from the grouped layout above */ + } + else if(zone_ids_table.count(platform_id)) { zone_ids = zone_ids_table.at(platform_id); + zone_led_counts.assign(zones.size(), 1); } else { @@ -164,6 +239,7 @@ AlienwareController::AlienwareController(hid_device* dev_handle, const hid_devic { zone_ids.push_back(static_cast(i)); } + zone_led_counts.assign(zones.size(), 1); } /*-----------------------------------------------------*\ @@ -186,6 +262,12 @@ AlienwareController::AlienwareController(hid_device* dev_handle, const hid_devic zones[zone_idx].direction = 0; } + /*-----------------------------------------------------*\ + | Per-LED colors, parallel to zone_ids | + \*-----------------------------------------------------*/ + led_colors[0].assign(zone_ids.size(), 0x000000); + led_colors[1].assign(zone_ids.size(), 0x000000); + /*-----------------------------------------------------*\ | Initialize dirty flags | \*-----------------------------------------------------*/ @@ -208,6 +290,45 @@ std::vector AlienwareController::GetZoneNames() return(zone_names); } +unsigned int AlienwareController::GetLedCount() +{ + unsigned int total = 0; + for(unsigned int count : zone_led_counts) + { + total += count; + } + return(total); +} + +std::vector AlienwareController::GetZoneLedCounts() +{ + return(zone_led_counts); +} + +unsigned int AlienwareController::GetZoneLedOffset(size_t zone_idx) const +{ + unsigned int offset = 0; + for(size_t i = 0; i < zone_idx && i < zone_led_counts.size(); i++) + { + offset += zone_led_counts[i]; + } + return(offset); +} + +unsigned int AlienwareController::GetLedZone(size_t led_idx) const +{ + unsigned int offset = 0; + for(size_t zone_idx = 0; zone_idx < zone_led_counts.size(); zone_idx++) + { + offset += zone_led_counts[zone_idx]; + if(led_idx < offset) + { + return((unsigned int)zone_idx); + } + } + return((unsigned int)zones.size()); +} + std::string AlienwareController::GetDeviceLocation() { return("HID: " + location); @@ -391,7 +512,15 @@ bool AlienwareController::UserAnimation(uint16_t subcommand, uint16_t animation, case ALIENWARE_COMMAND_USER_ANIM_FINISH_SAVE: return(!response.data[7]); case ALIENWARE_COMMAND_USER_ANIM_FINISH_PLAY: - return(!response.data[5]); + /*---------------------------------------------*\ + | Payload byte 7: 0x00 accepted, 0xFF rejected | + \*---------------------------------------------*/ + if(response.data[8] == 0xFF || response.data[6] == 0xFF || response.data[5] == 0xFF) + { + LOG_WARNING("[%s] Animation rejected by device (FINISH_PLAY status 0xFF)", ALIENWARE_CONTROLLER_NAME); + return(false); + } + return(response.data[8] == 0x00 || response.data[6] == 0x00 || response.data[5] == 0x00); case ALIENWARE_COMMAND_USER_ANIM_PLAY: return(!response.data[7]); default: @@ -399,6 +528,65 @@ bool AlienwareController::UserAnimation(uint16_t subcommand, uint16_t animation, } } +bool AlienwareController::SelectZonesSingle(const std::vector& zones) +{ + /*-----------------------------------------------------*\ + | Bail if zones is empty, and return false to indicate | + | nothing has changed | + \*-----------------------------------------------------*/ + if(zones.empty()) + { + return(false); + } + + /*-----------------------------------------------------*\ + | Animation SELECT must fit in one packet | + \*-----------------------------------------------------*/ + if(zones.size() > ALIENWARE_MAX_ZONES_PER_PACKET) + { + LOG_WARNING("[%s] SelectZonesSingle: %d zones exceeds single-packet limit %d", + ALIENWARE_CONTROLLER_NAME, (int)zones.size(), ALIENWARE_MAX_ZONES_PER_PACKET); + return(false); + } + + unsigned char usb_buf[HIDAPI_ALIENWARE_REPORT_SIZE]; + + /*-----------------------------------------------------*\ + | Zero out buffer | + \*-----------------------------------------------------*/ + memset(usb_buf, 0x00, sizeof(usb_buf)); + + /*-----------------------------------------------------*\ + | Set up message packet with leading 00, per hidapi | + \*-----------------------------------------------------*/ + uint16_t num_zones = (uint16_t)zones.size(); + + usb_buf[0x00] = 0x00; + usb_buf[0x01] = 0x03; + usb_buf[0x02] = ALIENWARE_COMMAND_SELECT_ZONES; + usb_buf[0x03] = 1; // loop? + usb_buf[0x04] = num_zones >> 8; + usb_buf[0x05] = num_zones & 0xFF; + + for(size_t i = 0; i < num_zones; i++) + { + usb_buf[0x06+i] = zones[i]; + } + + /*-----------------------------------------------------*\ + | Send packet | + \*-----------------------------------------------------*/ + SendHIDReport(dev, usb_buf, sizeof(usb_buf)); + + HidapiAlienwareReport response = GetResponse(); + + /*-----------------------------------------------------*\ + | For this command, error is if the output equals the | + | input | + \*-----------------------------------------------------*/ + return((response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE)); +} + bool AlienwareController::SelectZones(const std::vector& zones) { /*-----------------------------------------------------*\ @@ -651,6 +839,10 @@ bool AlienwareController::Reset() void AlienwareController::SetMode(uint8_t zone, uint8_t mode) { + if(zone >= zones.size()) + { + return; + } if(mode != zones[zone].mode) { zones[zone].mode = mode; @@ -660,11 +852,19 @@ void AlienwareController::SetMode(uint8_t zone, uint8_t mode) void AlienwareController::SetColor(uint8_t zone, RGBColor color) { + if(zone >= zones.size()) + { + return; + } SetColor(zone, color, zones[zone].color[1]); } void AlienwareController::SetColor(uint8_t zone, RGBColor color1, RGBColor color2) { + if(zone >= zones.size()) + { + return; + } if ((color1 == zones[zone].color[0]) && (color2 == zones[zone].color[1])) { return; @@ -675,8 +875,37 @@ void AlienwareController::SetColor(uint8_t zone, RGBColor color1, RGBColor color dirty = true; } +void AlienwareController::SetLedColor(unsigned int led, RGBColor color) +{ + if(led >= led_colors[0].size()) + { + return; + } + SetLedColor(led, color, led_colors[1][led]); +} + +void AlienwareController::SetLedColor(unsigned int led, RGBColor color1, RGBColor color2) +{ + if(led >= led_colors[0].size()) + { + return; + } + if((color1 == led_colors[0][led]) && (color2 == led_colors[1][led])) + { + return; + } + + led_colors[0][led] = color1; + led_colors[1][led] = color2; + dirty = true; +} + void AlienwareController::SetPeriod(uint8_t zone, uint16_t period) { + if(zone >= zones.size()) + { + return; + } if(period != zones[zone].period) { zones[zone].period = period; @@ -686,6 +915,10 @@ void AlienwareController::SetPeriod(uint8_t zone, uint16_t period) void AlienwareController::SetTempo(uint8_t zone, uint16_t tempo) { + if(zone >= zones.size()) + { + return; + } if(tempo != zones[zone].tempo) { zones[zone].tempo = tempo; @@ -695,6 +928,10 @@ void AlienwareController::SetTempo(uint8_t zone, uint16_t tempo) void AlienwareController::SetDim(uint8_t zone, uint8_t dim) { + if(zone >= zones.size()) + { + return; + } if(dim != zones[zone].dim) { zones[zone].dim = dim; @@ -724,10 +961,14 @@ void AlienwareController::UpdateDim() \*-----------------------------------------------------*/ std::map> dim_zone_map; - for(size_t i = 0; i < zones.size(); i++) + for(size_t led = 0; led < zone_ids.size(); led++) { - uint8_t hw_zone = (i < zone_ids.size()) ? zone_ids[i] : static_cast(i); - dim_zone_map[zones[i].dim].emplace_back(hw_zone); + unsigned int zone_idx = GetLedZone(led); + if(zone_idx >= zones.size()) + { + continue; + } + dim_zone_map[zones[zone_idx].dim].emplace_back(zone_ids[led]); } for(std::pair> &pair : dim_zone_map) @@ -752,10 +993,9 @@ bool AlienwareController::UpdateDirect() \*-----------------------------------------------------*/ std::map> color_zone_map; - for(size_t i = 0; i < zones.size(); i++) + for(size_t led = 0; led < zone_ids.size() && led < led_colors[0].size(); led++) { - uint8_t hw_zone = (i < zone_ids.size()) ? zone_ids[i] : static_cast(i); - color_zone_map[zones[i].color[0]].emplace_back(hw_zone); + color_zone_map[led_colors[0][led]].emplace_back(zone_ids[led]); } for(std::pair> &pair : color_zone_map) @@ -771,6 +1011,31 @@ bool AlienwareController::UpdateDirect() return true; } +bool AlienwareController::ParkEmptyAnimation() +{ + /*-----------------------------------------------------*\ + | Empty animation lets 0x27 colors persist | + \*-----------------------------------------------------*/ + if(!UserAnimation(ALIENWARE_COMMAND_USER_ANIM_NEW, ALIENWARE_COMMAND_USER_ANIM_KEYBOARD, 0)) + { + return(false); + } + return(UserAnimation(ALIENWARE_COMMAND_USER_ANIM_FINISH_PLAY, ALIENWARE_COMMAND_USER_ANIM_KEYBOARD, 0)); +} + +void AlienwareController::UpdateDirectController() +{ + /*-----------------------------------------------------*\ + | Park empty animation, then stream colors | + \*-----------------------------------------------------*/ + ParkEmptyAnimation(); + if(UpdateDirect()) + { + dirty = false; + } + UpdateDim(); +} + static const RGBColor rainbow_colors[4][7] = { { 0xFF0000, 0xFFA500, 0xFFFF00, 0x008000, 0x00BFFF, 0x0000FF, 0x800080 }, @@ -779,6 +1044,165 @@ static const RGBColor rainbow_colors[4][7] = { 0x00BFFF, 0x0000FF, 0x800080, 0xFF0000, 0xFFA500, 0xFFFF00, 0x008000 } }; +uint8_t AlienwareController::GetHardwareZone(size_t zone_idx) const +{ + if(zone_idx < zone_ids.size()) + { + return(zone_ids[zone_idx]); + } + return(static_cast(zone_idx)); +} + +void AlienwareController::BuildAnimGroups(std::vector& groups) +{ + groups.clear(); + + for(size_t led = 0; led < zone_ids.size(); led++) + { + unsigned int zone_idx = GetLedZone(led); + if(zone_idx >= zones.size()) + { + continue; + } + + const alienware_zone& zone = zones[zone_idx]; + RGBColor color0 = (led < led_colors[0].size()) ? led_colors[0][led] : zone.color[0]; + RGBColor color1 = (led < led_colors[1].size()) ? led_colors[1][led] : zone.color[1]; + uint8_t hw_zone = zone_ids[led]; + AlienwareAnimGroup key; + + memset(&key, 0x00, sizeof(key)); + key.zone_mode = zone.mode; + key.period = zone.period; + key.tempo = zone.tempo; + key.direction = zone.direction; + + switch(zone.mode) + { + case ALIENWARE_MODE_COLOR: + key.action_mode = ALIENWARE_MODE_COLOR; + key.period = 2000; /* per AWCC traces */ + key.tempo = ALIENWARE_TEMPO_MAX; + key.colors[0] = color0; + key.color_count = 1; + break; + + case ALIENWARE_MODE_PULSE: + key.action_mode = ALIENWARE_MODE_PULSE; + key.colors[0] = color0; + key.color_count = 1; + break; + + case ALIENWARE_MODE_MORPH: + key.action_mode = ALIENWARE_MODE_MORPH; + key.colors[0] = color0; + key.colors[1] = color1; + key.color_count = 2; + break; + + case ALIENWARE_MODE_SPECTRUM: + key.action_mode = ALIENWARE_MODE_MORPH; + key.color_count = 7; + memcpy(key.colors, rainbow_colors[0], sizeof(key.colors)); + break; + + case ALIENWARE_MODE_RAINBOW: + key.action_mode = ALIENWARE_MODE_MORPH; + key.color_count = 7; + key.rainbow_idx = (zone.direction == MODE_DIRECTION_LEFT) ? (4 - 1 - (led % 4)) : (led % 4); + memcpy(key.colors, rainbow_colors[key.rainbow_idx], sizeof(key.colors)); + break; + + case ALIENWARE_MODE_BREATHING: + key.action_mode = ALIENWARE_MODE_MORPH; + key.colors[0] = color0; + key.colors[1] = 0x0; + key.color_count = 2; + break; + + case ALIENWARE_MODE_DIRECT: + default: + /*-----------------------------------------*\ + | Direct has no animation action | + \*-----------------------------------------*/ + if(zone.mode != ALIENWARE_MODE_DIRECT) + { + groups.clear(); + return; + } + continue; + } + + bool merged = false; + for(AlienwareAnimGroup& group : groups) + { + if(group.zone_mode == key.zone_mode + && group.action_mode == key.action_mode + && group.period == key.period + && group.tempo == key.tempo + && group.direction == key.direction + && group.rainbow_idx == key.rainbow_idx + && group.color_count == key.color_count + && memcmp(group.colors, key.colors, sizeof(key.colors)) == 0) + { + group.hw_zones.emplace_back(hw_zone); + merged = true; + break; + } + } + + if(!merged) + { + key.hw_zones.emplace_back(hw_zone); + groups.emplace_back(key); + } + } +} + +size_t AlienwareController::CountAnimPairs(const std::vector& groups) +{ + size_t pairs = 0; + for(const AlienwareAnimGroup& group : groups) + { + pairs += (group.hw_zones.size() + ALIENWARE_MAX_ZONES_PER_PACKET - 1) / ALIENWARE_MAX_ZONES_PER_PACKET; + } + return(pairs); +} + +bool AlienwareController::ProgramAnimGroups(const std::vector& groups) +{ + for(const AlienwareAnimGroup& group : groups) + { + for(size_t offset = 0; offset < group.hw_zones.size(); offset += ALIENWARE_MAX_ZONES_PER_PACKET) + { + size_t chunk = std::min(ALIENWARE_MAX_ZONES_PER_PACKET, group.hw_zones.size() - offset); + std::vector hw_chunk(group.hw_zones.begin() + offset, group.hw_zones.begin() + offset + chunk); + + if(!SelectZonesSingle(hw_chunk)) + { + return(false); + } + + uint8_t modes[7]; + uint16_t periods[7]; + uint16_t tempos[7]; + + for(unsigned int i = 0; i < group.color_count; i++) + { + modes[i] = group.action_mode; + periods[i] = group.period; + tempos[i] = group.tempo; + } + + if(!MultiModeAction(modes, periods, tempos, group.colors, group.color_count)) + { + return(false); + } + } + } + return(true); +} + void AlienwareController::UpdateMode() { /*-----------------------------------------------------*\ @@ -789,6 +1213,34 @@ void AlienwareController::UpdateMode() return; } + /*-----------------------------------------------------*\ + | Batch identical zones into shared pairs | + \*-----------------------------------------------------*/ + std::vector groups; + BuildAnimGroups(groups); + + /*-----------------------------------------------------*\ + | Direct-only update: park empty animation, stream 0x27 | + \*-----------------------------------------------------*/ + if(groups.empty()) + { + UpdateDirectController(); + return; + } + + /*-----------------------------------------------------*\ + | Groups over 25 zones need one pair per chunk | + \*-----------------------------------------------------*/ + size_t pairs = CountAnimPairs(groups); + + if(pairs > ALIENWARE_MAX_ANIM_PAIRS) + { + LOG_WARNING("[%s] Animation needs %d pairs (max %d); parking empty animation and using direct colors", + ALIENWARE_CONTROLLER_NAME, (int)pairs, ALIENWARE_MAX_ANIM_PAIRS); + UpdateDirectController(); + return; + } + bool result = UserAnimation(ALIENWARE_COMMAND_USER_ANIM_NEW, ALIENWARE_COMMAND_USER_ANIM_KEYBOARD, 0); if(!result) @@ -796,108 +1248,18 @@ void AlienwareController::UpdateMode() return; } - for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++) + result = ProgramAnimGroups(groups); + + if(!result) { - alienware_zone zone = zones[zone_idx]; - uint8_t hw_zone = (zone_idx < zone_ids.size()) ? zone_ids[zone_idx] : static_cast(zone_idx); - - result = SelectZones({hw_zone}); - - if(!result) - { - return; - } - - /*-------------------------------------------------*\ - | Some modes use 0x07D0 for their duration as sent | - | by AWCC traces, maybe 2000ms? | - \*-------------------------------------------------*/ - switch (zone.mode) - { - case ALIENWARE_MODE_COLOR: - result = ModeAction(zone.mode, 2000, ALIENWARE_TEMPO_MAX, zone.color[0]); - break; - - case ALIENWARE_MODE_PULSE: - result = ModeAction(zone.mode, zone.period, zone.tempo, zone.color[0]); - break; - - case ALIENWARE_MODE_MORPH: - { - uint8_t modes[2] = { zone.mode, zone.mode }; - uint16_t periods[2] = { zone.period, zone.period }; - uint16_t tempos[2] = { zone.tempo, zone.tempo }; - RGBColor colors[2] = { zone.color[0], zone.color[1] }; - - result = MultiModeAction(modes, periods, tempos, colors, 2); - } - break; - - case ALIENWARE_MODE_SPECTRUM: - { - uint8_t modes[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH }; - uint16_t periods[7] = { zone.period, zone.period, - zone.period, zone.period, - zone.period, zone.period, - zone.period }; - uint16_t tempos[7] = { zone.tempo, zone.tempo, - zone.tempo, zone.tempo, - zone.tempo, zone.tempo, - zone.tempo }; - - result = MultiModeAction(modes, periods, tempos, rainbow_colors[0], 7); - } - break; - - case ALIENWARE_MODE_RAINBOW: - { - uint8_t modes[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH }; - uint16_t periods[7] = { zone.period, zone.period, - zone.period, zone.period, - zone.period, zone.period, - zone.period }; - uint16_t tempos[7] = { zone.tempo, zone.tempo, - zone.tempo, zone.tempo, - zone.tempo, zone.tempo, - zone.tempo }; - - unsigned int color_idx = (zone.direction == MODE_DIRECTION_LEFT) ? (4 - 1 - (zone_idx % 4)) : (zone_idx % 4); - result = MultiModeAction(modes, periods, tempos, rainbow_colors[color_idx], 7); - } - break; - - case ALIENWARE_MODE_BREATHING: - { - uint8_t modes[2] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH }; - uint16_t periods[2] = { zone.period, zone.period }; - uint16_t tempos[2] = { zone.tempo, zone.tempo }; - RGBColor colors[2] = { zone.color[0], 0x0 }; - - result = MultiModeAction(modes, periods, tempos, colors, 2); - } - break; - - default: - result = false; - } - - if(!result) - { - return; - } + return; } result = UserAnimation(ALIENWARE_COMMAND_USER_ANIM_FINISH_PLAY, ALIENWARE_COMMAND_USER_ANIM_KEYBOARD, 0); - /*-------------------------------------------------*\ - | Don't update dirty flag if there's an error | - \*-------------------------------------------------*/ + /*-----------------------------------------------------*\ + | Don't update dirty flag if there's an error | + \*-----------------------------------------------------*/ if(!result) { return; @@ -919,97 +1281,24 @@ void AlienwareController::SaveController() return; } - for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++) + /*-----------------------------------------------------*\ + | Reuse the UpdateMode batching | + \*-----------------------------------------------------*/ + std::vector groups; + BuildAnimGroups(groups); + + if(groups.empty() || CountAnimPairs(groups) > ALIENWARE_MAX_ANIM_PAIRS) { - alienware_zone zone = zones[zone_idx]; - uint8_t hw_zone = (zone_idx < zone_ids.size()) ? zone_ids[zone_idx] : static_cast(zone_idx); + LOG_WARNING("[%s] Save needs %d pairs (max %d); skipping EEPROM save", + ALIENWARE_CONTROLLER_NAME, (int)CountAnimPairs(groups), ALIENWARE_MAX_ANIM_PAIRS); + return; + } - result = SelectZones({hw_zone}); + result = ProgramAnimGroups(groups); - if(!result) - { - return; - } - - switch (zone.mode) - { - case ALIENWARE_MODE_COLOR: - result = ModeAction(zone.mode, 2000, ALIENWARE_TEMPO_MAX, zone.color[0]); - break; - - case ALIENWARE_MODE_PULSE: - result = ModeAction(zone.mode, zone.period, zone.tempo, zone.color[0]); - break; - - case ALIENWARE_MODE_MORPH: - { - uint8_t modes[2] = { zone.mode, zone.mode }; - uint16_t periods[2] = { zone.period, zone.period }; - uint16_t tempos[2] = { zone.tempo, zone.tempo }; - RGBColor colors[2] = { zone.color[0], zone.color[1] }; - - result = MultiModeAction(modes, periods, tempos, colors, 2); - } - break; - - case ALIENWARE_MODE_SPECTRUM: - { - uint8_t modes[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH }; - uint16_t periods[7] = { zone.period, zone.period, - zone.period, zone.period, - zone.period, zone.period, - zone.period }; - uint16_t tempos[7] = { zone.tempo, zone.tempo, - zone.tempo, zone.tempo, - zone.tempo, zone.tempo, - zone.tempo }; - - result = MultiModeAction(modes, periods, tempos, rainbow_colors[0], 7); - } - break; - - case ALIENWARE_MODE_RAINBOW: - { - uint8_t modes[7] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH, - ALIENWARE_MODE_MORPH }; - uint16_t periods[7] = { zone.period, zone.period, - zone.period, zone.period, - zone.period, zone.period, - zone.period }; - uint16_t tempos[7] = { zone.tempo, zone.tempo, - zone.tempo, zone.tempo, - zone.tempo, zone.tempo, - zone.tempo }; - - unsigned int color_idx = (zone.direction == MODE_DIRECTION_LEFT) ? (4 - 1 - (zone_idx % 4)) : (zone_idx % 4); - result = MultiModeAction(modes, periods, tempos, rainbow_colors[color_idx], 7); - } - break; - - case ALIENWARE_MODE_BREATHING: - { - uint8_t modes[2] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH }; - uint16_t periods[2] = { zone.period, zone.period }; - uint16_t tempos[2] = { zone.tempo, zone.tempo }; - RGBColor colors[2] = { zone.color[0], 0x0 }; - - result = MultiModeAction(modes, periods, tempos, colors, 2); - } - break; - - default: - result = false; - } - - if(!result) - { - return; - } + if(!result) + { + return; } UserAnimation(ALIENWARE_COMMAND_USER_ANIM_FINISH_SAVE, ALIENWARE_ANIM_DEFAULT, 0); diff --git a/Controllers/AlienwareController/AlienwareController.h b/Controllers/AlienwareController/AlienwareController.h index 951ee257..b98378e5 100644 --- a/Controllers/AlienwareController/AlienwareController.h +++ b/Controllers/AlienwareController/AlienwareController.h @@ -12,6 +12,7 @@ #pragma once #include +#include #include #include "RGBController.h" @@ -24,12 +25,17 @@ #define ALIENWARE_CONTROLLER_NAME "AlienWare Controller" /*---------------------------------------------------------*\ -| Max zone IDs that fit in one report after the command | -| header. Selections larger than this must be split across | -| multiple packets; writing them into a single report | -| overflows usb_buf (e.g. desktops with ~100 zones). | +| Max zone IDs per report. DIM/SET_COLOR may split across | +| packets, but SELECT_ZONES for animation programming must | +| fit in one packet. | \*---------------------------------------------------------*/ #define ALIENWARE_MAX_ZONES_PER_PACKET 25 + +/*---------------------------------------------------------*\ +| Max select+action pairs per animation program (13 on | +| AW-ELC Area-51 firmware). | +\*---------------------------------------------------------*/ +#define ALIENWARE_MAX_ANIM_PAIRS 13 enum { ALIENWARE_COMMAND_REPORT = 0x20, /* Set report type to get */ @@ -82,6 +88,7 @@ enum ALIENWARE_MODE_SPECTRUM, /* Abitrary code for spectrum mode */ ALIENWARE_MODE_RAINBOW, /* Arbitrary code for rainbow wave mode */ ALIENWARE_MODE_BREATHING, /* Arbitrary code for rainbow wave mode */ + ALIENWARE_MODE_DIRECT = 0xFF, /* OpenRGB direct: empty anim + 0x27 */ }; enum @@ -104,6 +111,22 @@ typedef struct unsigned char data[ALIENWARE_REPORT_SIZE]; } AlienwareReport; +/*---------------------------------------------------------*\ +| One select+action pair shared by identical zones | +\*---------------------------------------------------------*/ +typedef struct +{ + uint8_t action_mode; /* resolved action (morph for spectrum/rainbow) */ + uint8_t zone_mode; /* requested mode (keeps rainbow direction split)*/ + uint16_t period; + uint16_t tempo; + uint8_t direction; + unsigned int rainbow_idx; /* rainbow_colors row, valid for RAINBOW only */ + RGBColor colors[7]; + unsigned int color_count; + std::vector hw_zones; +} AlienwareAnimGroup; + class AlienwareController { public: @@ -115,10 +138,21 @@ public: std::string GetDeviceName(); std::string GetFirmwareVersion(); unsigned GetZoneCount(); + unsigned GetLedCount(); std::vector GetZoneNames(); + std::vector GetZoneLedCounts(); + unsigned int GetZoneLedOffset(size_t zone_idx) const; + unsigned int GetLedZone(size_t led_idx) const; + + uint8_t GetHardwareZone(size_t zone_idx) const; + void BuildAnimGroups(std::vector& groups); + size_t CountAnimPairs(const std::vector& groups); + bool ProgramAnimGroups(const std::vector& groups); void SetColor(uint8_t zone, RGBColor color); void SetColor(uint8_t zone, RGBColor color1, RGBColor color2); + void SetLedColor(unsigned int led, RGBColor color); + void SetLedColor(unsigned int led, RGBColor color1, RGBColor color2); void SetMode(uint8_t zone, uint8_t mode); void SetPeriod(uint8_t zone, uint16_t period); void SetTempo(uint8_t zone, uint16_t tempo); @@ -130,6 +164,8 @@ public: void UpdateMode(); void UpdateController(); void SaveController(); + void UpdateDirectController(); + bool ParkEmptyAnimation(); protected: hid_device* dev; @@ -144,7 +180,6 @@ private: uint8_t dim; uint8_t direction; } alienware_zone; - typedef struct { unsigned char data[HIDAPI_ALIENWARE_REPORT_SIZE]; @@ -153,10 +188,12 @@ private: std::string device_name; std::string location; std::vector zones; + std::vector led_colors[2]; std::string serial_number; std::string version; std::vector zone_names; std::vector zone_ids; + std::vector zone_led_counts; bool dirty; bool dirty_dim; @@ -165,6 +202,7 @@ private: bool Dim(std::vector zones, double percent); bool UserAnimation(uint16_t subcommand, uint16_t animation, uint16_t duration); + bool SelectZonesSingle(const std::vector& zones); bool SelectZones(const std::vector& zones); bool ModeAction(uint8_t mode, uint16_t duration, uint16_t tempo, RGBColor color); bool ModeAction(const uint8_t *mode, const uint16_t *duration, const uint16_t *tempo, const RGBColor *color, unsigned amount); diff --git a/Controllers/AlienwareController/RGBController_Alienware.cpp b/Controllers/AlienwareController/RGBController_Alienware.cpp index 71cbc612..557aca1f 100644 --- a/Controllers/AlienwareController/RGBController_Alienware.cpp +++ b/Controllers/AlienwareController/RGBController_Alienware.cpp @@ -69,7 +69,7 @@ RGBController_Alienware::RGBController_Alienware(AlienwareController* controller Morph.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED | MODE_FLAG_MANUAL_SAVE; Morph.color_mode = MODE_COLORS_MODE_SPECIFIC; Morph.colors_min = 2; - Morph.colors_max = 2 * controller->GetZoneCount(); + Morph.colors_max = 2 * controller->GetLedCount(); Morph.colors.resize(2); Morph.colors[0] = 0x0000FF; Morph.colors[1] = 0xFF0000; @@ -123,6 +123,18 @@ RGBController_Alienware::RGBController_Alienware(AlienwareController* controller Breathing.brightness = 0; modes.push_back(Breathing); + mode Direct; + Direct.name = "Direct"; + Direct.value = ALIENWARE_MODE_DIRECT; + Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS; + Direct.color_mode = MODE_COLORS_PER_LED; + Direct.colors_min = 1; + Direct.colors_max = 1; + Direct.brightness_min = 100; + Direct.brightness_max = 0; + Direct.brightness = 0; + modes.push_back(Direct); + SetupZones(); controller->UpdateDim(); @@ -140,28 +152,45 @@ void RGBController_Alienware::SetupZones() /*-------------------------------------------------*\ | Set zones and leds | \*-------------------------------------------------*/ - std::vector zone_names = controller->GetZoneNames(); + std::vector zone_names = controller->GetZoneNames(); + std::vector zone_led_counts = controller->GetZoneLedCounts(); + + /*-------------------------------------------------*\ + | Pad short name tables | + \*-------------------------------------------------*/ + if(zone_names.size() < controller->GetZoneCount()) + { + zone_names.resize(controller->GetZoneCount(), "Unknown"); + } + + if(zone_led_counts.size() < controller->GetZoneCount()) + { + zone_led_counts.resize(controller->GetZoneCount(), 1); + } for(unsigned int zone_idx = 0; zone_idx < controller->GetZoneCount(); zone_idx++) { zone new_zone; new_zone.name = zone_names[zone_idx]; - new_zone.type = ZONE_TYPE_SINGLE; - new_zone.leds_min = 1; - new_zone.leds_max = 1; - new_zone.leds_count = 1; + new_zone.type = (zone_led_counts[zone_idx] > 1) ? ZONE_TYPE_LINEAR : ZONE_TYPE_SINGLE; + new_zone.leds_min = zone_led_counts[zone_idx]; + new_zone.leds_max = zone_led_counts[zone_idx]; + new_zone.leds_count = zone_led_counts[zone_idx]; zones.emplace_back(new_zone); } - for(unsigned int led_idx = 0; led_idx < zones.size(); led_idx++) + for(unsigned int zone_idx = 0; zone_idx < zones.size(); zone_idx++) { - led new_led; + for(unsigned int led_idx = 0; led_idx < zones[zone_idx].leds_count; led_idx++) + { + led new_led; - new_led.name = zones[led_idx].name + std::string(" LED"); + new_led.name = zones[zone_idx].name + std::string(" LED ") + std::to_string(led_idx + 1); - leds.emplace_back(new_led); + leds.emplace_back(new_led); + } } SetupColors(); @@ -214,9 +243,16 @@ void RGBController_Alienware::DeviceUpdateMode() while(!done) { /*-----------------------------------------------------*\ - | Setup state per zone | + | Direct skips animation programming | \*-----------------------------------------------------*/ - for(uint8_t zone_idx = 0; zone_idx < zones.size(); zone_idx++) + bool direct_update = (current_mode.value == ALIENWARE_MODE_DIRECT); + + /*-----------------------------------------------------*\ + | Setup state per LED | + \*-----------------------------------------------------*/ + unsigned int led_global_idx = 0; + + for(size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++) { zone current_zone = zones[zone_idx]; @@ -226,59 +262,71 @@ void RGBController_Alienware::DeviceUpdateMode() \*-------------------------------------------------*/ uint16_t period = 0x07d0; - controller->SetMode(zone_idx, current_mode.value); - controller->SetDirection(zone_idx, current_mode.direction); + uint8_t hw_idx = (uint8_t)zone_idx; - switch(current_mode_idx) + controller->SetMode( hw_idx, direct_update ? (uint8_t)ALIENWARE_MODE_DIRECT : current_mode.value); + controller->SetDirection(hw_idx, current_mode.direction); + + for(unsigned int led_in_zone = 0; led_in_zone < current_zone.leds_count; led_in_zone++, led_global_idx++) { - case ALIENWARE_MODE_COLOR: - controller->SetPeriod(zone_idx, period); - controller->SetColor( zone_idx, colors[current_zone.start_idx]); - controller->SetTempo( zone_idx, ALIENWARE_TEMPO_MAX); - controller->SetDim( zone_idx, modes[current_mode_idx].brightness); - break; + RGBColor led_color = (led_global_idx < colors.size()) ? colors[led_global_idx] : 0x000000; - case ALIENWARE_MODE_PULSE: - period = std::max(500, (uint16_t)(current_mode.speed * 10)); - controller->SetPeriod(zone_idx, period); - controller->SetColor( zone_idx, colors[current_zone.start_idx]); - controller->SetTempo( zone_idx, current_mode.speed); - controller->SetDim( zone_idx, modes[current_mode_idx].brightness); - break; + switch(current_mode_idx) + { + case ALIENWARE_MODE_COLOR: + controller->SetPeriod( hw_idx, period); + controller->SetLedColor(led_global_idx, led_color); + controller->SetTempo( hw_idx, ALIENWARE_TEMPO_MAX); + controller->SetDim( hw_idx, modes[current_mode_idx].brightness); + break; - case ALIENWARE_MODE_MORPH: - { - RGBColor c1 = (current_mode.colors.size() >= 1) ? current_mode.colors[0] : 0x0000FF; - RGBColor c2 = (current_mode.colors.size() >= 2) ? current_mode.colors[1] : 0xFF0000; + case ALIENWARE_MODE_PULSE: + period = std::max(500, (uint16_t)(current_mode.speed * 10)); + controller->SetPeriod( hw_idx, period); + controller->SetLedColor(led_global_idx, led_color); + controller->SetTempo( hw_idx, current_mode.speed); + controller->SetDim( hw_idx, modes[current_mode_idx].brightness); + break; - if(current_mode.colors.size() >= (std::size_t)((zone_idx + 1) * 2)) + case ALIENWARE_MODE_MORPH: { - c1 = current_mode.colors[zone_idx * 2]; - c2 = current_mode.colors[(zone_idx * 2) + 1]; + RGBColor c1 = (current_mode.colors.size() >= 1) ? current_mode.colors[0] : 0x0000FF; + RGBColor c2 = (current_mode.colors.size() >= 2) ? current_mode.colors[1] : 0xFF0000; + + if(current_mode.colors.size() >= (std::size_t)((led_global_idx + 1) * 2)) + { + c1 = current_mode.colors[led_global_idx * 2]; + c2 = current_mode.colors[(led_global_idx * 2) + 1]; + } + + period = std::max(400, (uint16_t)(current_mode.speed * 10)); + controller->SetPeriod( hw_idx, period); + controller->SetLedColor(led_global_idx, c1, c2); + controller->SetTempo( hw_idx, current_mode.speed); + controller->SetDim( hw_idx, modes[current_mode_idx].brightness); } + break; - period = std::max(400, (uint16_t)(current_mode.speed * 10)); - controller->SetPeriod(zone_idx, period); - controller->SetColor( zone_idx, c1, c2); - controller->SetTempo( zone_idx, current_mode.speed); - controller->SetDim( zone_idx, modes[current_mode_idx].brightness); - } - break; + case ALIENWARE_MODE_SPECTRUM: + case ALIENWARE_MODE_RAINBOW: + controller->SetPeriod(hw_idx, ALIENWARE_DURATION_SPECTRUM); + controller->SetTempo( hw_idx, current_mode.speed); + controller->SetDim( hw_idx, modes[current_mode_idx].brightness); + break; - case ALIENWARE_MODE_SPECTRUM: - case ALIENWARE_MODE_RAINBOW: - controller->SetPeriod(zone_idx, ALIENWARE_DURATION_SPECTRUM); - controller->SetTempo( zone_idx, current_mode.speed); - controller->SetDim( zone_idx, modes[current_mode_idx].brightness); - break; + case ALIENWARE_MODE_BREATHING: + period = std::max(500, (uint16_t)(current_mode.speed * 10)); + controller->SetPeriod( hw_idx, period); + controller->SetLedColor(led_global_idx, led_color, 0x0); + controller->SetTempo( hw_idx, current_mode.speed); + controller->SetDim( hw_idx, modes[current_mode_idx].brightness); + break; - case ALIENWARE_MODE_BREATHING: - period = std::max(500, (uint16_t)(current_mode.speed * 10)); - controller->SetPeriod(zone_idx, period); - controller->SetColor( zone_idx, colors[current_zone.start_idx], 0x0); - controller->SetTempo( zone_idx, current_mode.speed); - controller->SetDim( zone_idx, modes[current_mode_idx].brightness); - break; + case ALIENWARE_MODE_DIRECT: + controller->SetLedColor(led_global_idx, led_color); + controller->SetDim( hw_idx, modes[current_mode_idx].brightness); + break; + } } }