From 9062be446275cc9ae50f1487de0e508014f01a1d Mon Sep 17 00:00:00 2001 From: Gabriel Marcano Date: Wed, 21 Apr 2021 02:26:59 -0700 Subject: [PATCH] New device: Dell G5 SE Alienware LED Keyboard - 4 region keyboard LED controller (tried to keep controller generic for other setups as well) - Support for Static, Flashing, Morph, Spectrum Cycle, Rainbow Wave, and Breathing modes - Implemented rate limiting for sending reports since the controller crashes if too many reports are sent too quickly. Also implemented functionality to prevent sending frivolous update requests - Implemented support for setting mode, color, tempo, and period independently for each zone (not fully exposed in UI). Morph mode exposes 2 colors per zone. Commit amended for code style by Adam Honse --- .../AlienwareController.cpp | 838 ++++++++++++++++++ .../AlienwareController/AlienwareController.h | 161 ++++ .../AlienwareControllerDetect.cpp | 29 + .../RGBController_Alienware.cpp | 252 ++++++ .../RGBController_Alienware.h | 37 + OpenRGB.pro | 6 + 6 files changed, 1323 insertions(+) create mode 100644 Controllers/AlienwareController/AlienwareController.cpp create mode 100644 Controllers/AlienwareController/AlienwareController.h create mode 100644 Controllers/AlienwareController/AlienwareControllerDetect.cpp create mode 100644 Controllers/AlienwareController/RGBController_Alienware.cpp create mode 100644 Controllers/AlienwareController/RGBController_Alienware.h diff --git a/Controllers/AlienwareController/AlienwareController.cpp b/Controllers/AlienwareController/AlienwareController.cpp new file mode 100644 index 00000000..c132e22a --- /dev/null +++ b/Controllers/AlienwareController/AlienwareController.cpp @@ -0,0 +1,838 @@ +/*-----------------------------------------*\ +| AlienwareController.cpp | +| | +| Driver for Alienware lighting controller | +| | +| Gabriel Marcano (gemarcano) 4/21/2021 | +\*-----------------------------------------*/ + +#include "RGBController.h" +#include "AlienwareController.h" +#include +#include +#include +#include +#include +#include +#include + +typedef uint32_t alienware_platform_id; + +/*---------------------------------------------------------*\ +| Some devices appear to report the wrong number of zones. | +| Record that here. | +\*---------------------------------------------------------*/ +static const std::map zone_quirks_table = +{ + { 0x0C01, 4 } // Dell G5 SE 5505 +}; + +/*---------------------------------------------------------*\ +| Add zones for devices here, mapping the platform ID to | +| the zone names | +\*---------------------------------------------------------*/ +static const std::map> zone_names_table = +{ + { 0x0C01, { "Left", "Middle", "Right", "Numpad" } } +}; + +static void SendHIDReport(hid_device *dev, const unsigned char* usb_buf, size_t usb_buf_size) +{ + using namespace std::chrono_literals; + + hid_send_feature_report(dev, usb_buf, usb_buf_size); + + /*-----------------------------------------------------*\ + | The controller really doesn't like really spammed by | + | too many commands at once... the delay may be command | + | dependent also. Delay for longer if the command is | + | changing animation state | + \*-----------------------------------------------------*/ + unsigned char command = usb_buf[2]; + unsigned char subcommand = usb_buf[3]; + + if( ( command == ALIENWARE_COMMAND_USER_ANIM ) + && ( ( subcommand == ALIENWARE_COMMAND_USER_ANIM_FINISH_PLAY ) + || ( subcommand == ALIENWARE_COMMAND_USER_ANIM_FINISH_SAVE ) ) ) + { + std::this_thread::sleep_for(1s); + } + else + { + std::this_thread::sleep_for(60ms); + } +} + +AlienwareController::AlienwareController(hid_device* dev_handle, const hid_device_info& info, std::string name) +{ + HidapiAlienwareReport report; + + dev = dev_handle; + device_name = name; + location = info.path; + + /*-----------------------------------------------------*\ + | Get serial number | + \*-----------------------------------------------------*/ + std::wstring tmp_serial_number; + tmp_serial_number = info.serial_number; + serial_number = std::string(tmp_serial_number.begin(), tmp_serial_number.end()); + + /*-----------------------------------------------------*\ + | Get zone information by checking firmware | + | configuration | + \*-----------------------------------------------------*/ + report = Report(ALIENWARE_COMMAND_REPORT_CONFIG); + alienware_platform_id platform_id = report.data[4] << 8 | report.data[5]; + + /*-----------------------------------------------------*\ + | Get firmware version | + \*-----------------------------------------------------*/ + report = Report(ALIENWARE_COMMAND_REPORT_FIRMWARE); + + std::stringstream fw_string; + + fw_string << static_cast(report.data[4]) << '.' << static_cast(report.data[5]) << '.' << static_cast(report.data[6]); + version = fw_string.str(); + + /*-----------------------------------------------------*\ + | 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]; + + /*-----------------------------------------------------*\ + | Initialize Alienware zones | + \*-----------------------------------------------------*/ + zones.resize(number_of_zones); + + if(zone_names_table.count(platform_id)) + { + zone_names = zone_names_table.at(platform_id); + } + else + { + /*-------------------------------------------------*\ + | If this is an unknown controller, set the name of | + | all regions to "Unknown" | + \*-------------------------------------------------*/ + for(size_t i = 0; i < number_of_zones; i++) + { + zone_names.emplace_back("Unknown"); + } + } + + /*-----------------------------------------------------*\ + | Set defaults for all zones | + | It doesn't seem possible to read the controller's | + | current state, hence the default value being set here.| + \*-----------------------------------------------------*/ + for(unsigned int zone_idx; zone_idx < zones.size(); zone_idx++) + { + zones[zone_idx].color[0] = 0x000000; + zones[zone_idx].color[1] = 0x000000; + zones[zone_idx].mode = ALIENWARE_MODE_COLOR; + + /*-------------------------------------------------*\ + | Default period value from ACC | + \*-------------------------------------------------*/ + zones[zone_idx].period = 2000; + zones[zone_idx].tempo = ALIENWARE_TEMPO_MAX; + zones[zone_idx].dim = 0; + } + + /*-----------------------------------------------------*\ + | Initialize dirty flags | + \*-----------------------------------------------------*/ + dirty = true; + dirty_dim = true; +} + +AlienwareController::~AlienwareController() +{ + +} + +unsigned int AlienwareController::GetZoneCount() +{ + return(zones.size()); +} + +std::vector AlienwareController::GetZoneNames() +{ + return(zone_names); +} + +std::string AlienwareController::GetDeviceLocation() +{ + return("HID: " + location); +} + +std::string AlienwareController::GetDeviceName() +{ + return(device_name); +} + +std::string AlienwareController::GetSerialString() +{ + return(serial_number); +} + +std::string AlienwareController::GetFirmwareVersion() +{ + return(version); +} + +AlienwareController::HidapiAlienwareReport AlienwareController::GetResponse() +{ + /*-----------------------------------------------------*\ + | Zero init. This is not updated if there's a problem. | + \*-----------------------------------------------------*/ + HidapiAlienwareReport result; + + /*-----------------------------------------------------*\ + | Zero out buffer | + \*-----------------------------------------------------*/ + memset(result.data, 0x00, sizeof(result.data)); + + + hid_get_feature_report(dev, result.data, HIDAPI_ALIENWARE_REPORT_SIZE); + + return(result); +} + +AlienwareController::HidapiAlienwareReport AlienwareController::Report(uint8_t subcommand) +{ + 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 | + \*-----------------------------------------------------*/ + usb_buf[0x00] = 0x00; + usb_buf[0x01] = 0x03; + usb_buf[0x02] = ALIENWARE_COMMAND_REPORT; + usb_buf[0x03] = subcommand; + + /*-----------------------------------------------------*\ + | Send packet | + \*-----------------------------------------------------*/ + SendHIDReport(dev, usb_buf, sizeof(usb_buf)); + + return(GetResponse()); +} + +AlienwareReport AlienwareController::GetStatus(uint8_t subcommand) +{ + HidapiAlienwareReport data = Report(subcommand); + AlienwareReport result = AlienwareReport{}; + + /*-----------------------------------------------------*\ + | Skip first byte, as that's the report number, which | + | should be 0 | + \*-----------------------------------------------------*/ + memcpy(result.data, &data.data[1], sizeof(result.data)); + + return(result); +} + +bool AlienwareController::Dim(std::vector zones, double percent) +{ + /*-----------------------------------------------------*\ + | Bail out if there are no zones to update | + \*-----------------------------------------------------*/ + if(!zones.size()) + { + return(true); + } + + 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 = zones.size(); + + usb_buf[0x00] = 0x00; + usb_buf[0x01] = 0x03; + usb_buf[0x02] = ALIENWARE_COMMAND_DIM; + usb_buf[0x03] = static_cast(percent * 0x64); + 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; + 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::UserAnimation(uint16_t subcommand, uint16_t animation, uint16_t duration) +{ + 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 | + \*-----------------------------------------------------*/ + usb_buf[0x00] = 0x00; + usb_buf[0x01] = 0x03; + usb_buf[0x02] = ALIENWARE_COMMAND_USER_ANIM; + usb_buf[0x03] = subcommand >> 8; + usb_buf[0x04] = subcommand & 0xFF; + usb_buf[0x05] = animation >> 8; + usb_buf[0x06] = animation & 0xFF; + usb_buf[0x07] = duration >> 8; + usb_buf[0x08] = duration & 0xFF; + + /*-----------------------------------------------------*\ + | Send packet | + \*-----------------------------------------------------*/ + SendHIDReport(dev, usb_buf, sizeof(usb_buf)); + + /*-----------------------------------------------------*\ + | Every subcommand appears to report its result on a | + | different byte | + \*-----------------------------------------------------*/ + HidapiAlienwareReport response; + response = GetResponse(); + + /*-----------------------------------------------------*\ + | The only time the 0x03 byte is zero is if the | + | controller has crashed | + \*-----------------------------------------------------*/ + if(response.data[1] == 0) + { + return(false); + } + + switch(subcommand) + { + case ALIENWARE_COMMAND_USER_ANIM_FINISH_SAVE: + return(!response.data[7]); + case ALIENWARE_COMMAND_USER_ANIM_FINISH_PLAY: + return(!response.data[5]); + case ALIENWARE_COMMAND_USER_ANIM_PLAY: + return(!response.data[7]); + default: + return(true); + } +} + +bool AlienwareController::SelectZones(const std::vector& zones) +{ + /*-----------------------------------------------------*\ + | Bail if zones is empty, and return false to indicate | + | nothing has changed | + \*-----------------------------------------------------*/ + if(!zones.size()) + { + 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 = 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; + 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::ModeAction(uint8_t mode, uint16_t duration, uint16_t tempo, RGBColor color) +{ + return(ModeAction(&mode, &duration, &tempo, &color, 1)); +} + +bool AlienwareController::ModeAction + ( + const uint8_t* mode, + const uint16_t* duration, + const uint16_t* tempo, + const RGBColor* color, + unsigned amount + ) +{ + unsigned char usb_buf[HIDAPI_ALIENWARE_REPORT_SIZE]; + + /*-----------------------------------------------------*\ + | Zero out buffer | + \*-----------------------------------------------------*/ + memset(usb_buf, 0x00, sizeof(usb_buf)); + + /*-----------------------------------------------------*\ + | Amount must be 3 or less, as that's how many | + | subcommands can fit into one report | + \*-----------------------------------------------------*/ + if(amount > 3) + { + return(false); + } + + /*-----------------------------------------------------*\ + | Set up message packet with leading 00, per hidapi | + \*-----------------------------------------------------*/ + usb_buf[0x00] = 0x00; + usb_buf[0x01] = 0x03; + usb_buf[0x02] = ALIENWARE_COMMAND_ADD_ACTION; + + for(unsigned int i = 0; i < amount; i++) + { + usb_buf[0x03 + (8 * i)] = mode[i]; + usb_buf[0x04 + (8 * i)] = duration[i] >> 8; + usb_buf[0x05 + (8 * i)] = duration[i] & 0xFF; + usb_buf[0x06 + (8 * i)] = tempo[i] >> 8; + usb_buf[0x07 + (8 * i)] = tempo[i] & 0xFF; + usb_buf[0x08 + (8 * i)] = RGBGetRValue(color[i]); + usb_buf[0x09 + (8 * i)] = RGBGetGValue(color[i]); + usb_buf[0x0A + (8 * i)] = RGBGetBValue(color[i]); + } + + /*-----------------------------------------------------*\ + | Send packet | + \*-----------------------------------------------------*/ + SendHIDReport(dev, usb_buf, sizeof(usb_buf)); + + HidapiAlienwareReport response; + 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::MultiModeAction + ( + const uint8_t* mode, + const uint16_t* duration, + const uint16_t* tempo, + const RGBColor* color, + unsigned amount + ) +{ + bool result = true; + unsigned int left = amount; + + while(left && result) + { + unsigned int tmp_amount; + + tmp_amount = std::min(left, 3u); + result &= ModeAction(mode, duration, tempo, color, tmp_amount); + mode += tmp_amount; + duration += tmp_amount; + tempo += tmp_amount; + color += tmp_amount; + left -= tmp_amount; + } + + return(result); +} + +bool AlienwareController::SetColorDirect(RGBColor color, std::vector zones) +{ + /*-----------------------------------------------------*\ + | Bail if zones is empty | + \*-----------------------------------------------------*/ + if(zones.empty()) + { + return(true); + } + + 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 = zones.size(); + + usb_buf[0x00] = 0x00; + usb_buf[0x01] = 0x03; + usb_buf[0x02] = ALIENWARE_COMMAND_SET_COLOR; + usb_buf[0x03] = RGBGetRValue(color); + usb_buf[0x04] = RGBGetGValue(color); + usb_buf[0x05] = RGBGetBValue(color); + usb_buf[0x06] = num_zones >> 8; + usb_buf[0x07] = num_zones & 0xFF; + + for(size_t i = 0; i < num_zones; i++) + { + usb_buf[0x08 + i] = zones[i]; + } + + /*-----------------------------------------------------*\ + | Send packet | + \*-----------------------------------------------------*/ + SendHIDReport(dev, usb_buf, sizeof(usb_buf)); + + HidapiAlienwareReport response; + 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::Reset() +{ + /*-----------------------------------------------------*\ + | Bail if zones is empty | + \*-----------------------------------------------------*/ + if(zones.empty()) + { + return(true); + } + + 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 | + \*-----------------------------------------------------*/ + usb_buf[0x00] = 0x00; + usb_buf[0x01] = 0x03; + usb_buf[0x02] = ALIENWARE_COMMAND_RESET; + + /*-----------------------------------------------------*\ + | Send packet | + \*-----------------------------------------------------*/ + SendHIDReport(dev, usb_buf, sizeof(usb_buf)); + + HidapiAlienwareReport response; + response = GetResponse(); + + /*-----------------------------------------------------*\ + | For this command, error is if the output equals the | + | input | + \*-----------------------------------------------------*/ + return(response.data[1] == 0x03); +} + +void AlienwareController::SetMode(uint8_t zone, uint8_t mode) +{ + if(mode != zones[zone].mode) + { + zones[zone].mode = mode; + dirty = true; + } +} + +void AlienwareController::SetColor(uint8_t zone, RGBColor color) +{ + SetColor(zone, color, zones[zone].color[1]); +} + +void AlienwareController::SetColor(uint8_t zone, RGBColor color1, RGBColor color2) +{ + if((color1 != zones[zone].color[0])) + { + zones[zone].color[0] = color1; + dirty = true; + } + + if((color2 != zones[zone].color[1])) + { + zones[zone].color[1] = color2; + dirty = true; + } +} + +void AlienwareController::SetPeriod(uint8_t zone, uint16_t period) +{ + if(period != zones[zone].period) + { + zones[zone].period = period; + dirty = true; + } +} + +void AlienwareController::SetTempo(uint8_t zone, uint16_t tempo) +{ + if(tempo != zones[zone].tempo) + { + zones[zone].tempo = tempo; + dirty = true; + } +} + +void AlienwareController::SetDim(uint8_t zone, uint8_t dim) +{ + /*-----------------------------------------------------*\ + | Clamp dim to values between 0 and 100 | + \*-----------------------------------------------------*/ + if(dim > 100) + { + dim = 100; + } + + if(dim != zones[zone].dim) + { + zones[zone].dim = dim; + dirty_dim = true; + } +} + +void AlienwareController::UpdateDim() +{ + if(!dirty_dim) + { + return; + } + + /*-----------------------------------------------------*\ + | Collect all zones that share dim settings, and update | + | them together | + \*-----------------------------------------------------*/ + std::map> dim_zone_map; + + for(size_t i = 0; i < zones.size(); i++) + { + dim_zone_map[zones[i].dim].emplace_back(i); + } + + for(std::pair> &pair : dim_zone_map) + { + /*-------------------------------------------------*\ + | Bail on an error... | + \*-------------------------------------------------*/ + if(!Dim(pair.second, pair.first)) + { + return; + } + } + + dirty_dim = false; +} + +bool AlienwareController::UpdateDirect() +{ + /*-----------------------------------------------------*\ + | Collect all zones that share dim settings, and update | + | them together | + \*-----------------------------------------------------*/ + std::map> color_zone_map; + + for(size_t i = 0; i < zones.size(); i++) + { + color_zone_map[zones[i].color[0]].emplace_back(i); + } + + for(std::pair> &pair : color_zone_map) + { + /*-------------------------------------------------*\ + | Bail on an error... | + \*-------------------------------------------------*/ + if(!SetColorDirect(pair.first, pair.second)) + { + return false; + } + } + return true; +} + +static const RGBColor rainbow_colors[4][7] = +{ + { 0xFF0000, 0xFFA500, 0xFFFF00, 0x008000, 0x00BFFF, 0x0000FF, 0x800080 }, + { 0x800080, 0xFF0000, 0xFFA500, 0xFFFF00, 0x008000, 0x00BFFF, 0x0000FF }, + { 0x0000FF, 0x800080, 0xFF0000, 0xFFA500, 0xFFFF00, 0x008000, 0x00BFFF }, + { 0x00BFFF, 0x0000FF, 0x800080, 0xFF0000, 0xFFA500, 0xFFFF00, 0x008000 } +}; + +void AlienwareController::UpdateMode() +{ + /*-----------------------------------------------------*\ + | If there are no updates, don't bother running this | + \*-----------------------------------------------------*/ + if(!dirty) + { + return; + } + + bool result = UserAnimation(ALIENWARE_COMMAND_USER_ANIM_NEW, ALIENWARE_COMMAND_USER_ANIM_KEYBOARD, 0); + + if(!result) + { + return; + } + + for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++) + { + alienware_zone zone = zones[zone_idx]; + + result = SelectZones({static_cast(zone_idx)}); + + 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 zones[2] = { zone.mode, zone.mode }; + uint16_t periods[2] = { zone.period, zone.period }; + uint16_t tempos[2] = { zone.tempo, zone.tempo }; + RGBColor colors[2] = { zone.color[0], zone.color[1] }; + + result = MultiModeAction(zones, periods, tempos, colors, 2); + } + break; + + case ALIENWARE_MODE_SPECTRUM: + { + uint8_t zones[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(zones, periods, tempos, rainbow_colors[0], 7); + } + break; + + case ALIENWARE_MODE_RAINBOW: + { + uint8_t zones[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(zones, periods, tempos, rainbow_colors[zone_idx], 7); + } + break; + + case ALIENWARE_MODE_BREATHING: + { + uint8_t zones[2] = { ALIENWARE_MODE_MORPH, ALIENWARE_MODE_MORPH }; + uint16_t periods[2] = { zone.period, zone.period }; + uint16_t tempos[2] = { zone.tempo, zone.tempo }; + RGBColor colors[2] = { zone.color[0], 0x0 }; + + result = MultiModeAction(zones, periods, tempos, colors, 2); + } + break; + + default: + result = false; + } + + if(!result) + { + 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 | + \*-------------------------------------------------*/ + if(!result) + { + return; + } + + dirty = false; +} + +void AlienwareController::UpdateController() +{ + UpdateMode(); + UpdateDim(); +} diff --git a/Controllers/AlienwareController/AlienwareController.h b/Controllers/AlienwareController/AlienwareController.h new file mode 100644 index 00000000..26e37b52 --- /dev/null +++ b/Controllers/AlienwareController/AlienwareController.h @@ -0,0 +1,161 @@ +/*-----------------------------------------*\ +| AlienwareController.h | +| | +| Driver for Dell Alienware RGB USB | +| controller | +| | +| Gabriel Marcano (gemarcano) 4/19/2021 | +\*-----------------------------------------*/ + +#include "RGBController.h" +#include +#include + +#pragma once + +/*----------------------------------------------------------------------------------------------*\ +| Definitions for Alienware Controller | +\*----------------------------------------------------------------------------------------------*/ + +#define ALIENWARE_REPORT_SIZE 33 +#define HIDAPI_ALIENWARE_REPORT_SIZE (ALIENWARE_REPORT_SIZE + 1) + +enum +{ + ALIENWARE_COMMAND_REPORT = 0x20, /* Set report type to get */ + ALIENWARE_COMMAND_USER_ANIM = 0x21, /* Set user animation settings */ + ALIENWARE_COMMAND_POWER_ANIM = 0x22, /* Set power animation settings */ + ALIENWARE_COMMAND_SELECT_ZONES = 0x23, /* Select zones to apply actions to */ + ALIENWARE_COMMAND_ADD_ACTION = 0x24, /* Set actions to apply */ + ALIENWARE_COMMAND_UNKNOWN1 = 0x25, /* Supposedly set event? */ + ALIENWARE_COMMAND_DIM = 0x26, /* Set dim percentage */ + ALIENWARE_COMMAND_SET_COLOR = 0x27, /* Unclear (causes color flash) */ + ALIENWARE_COMMAND_RESET = 0x28, /* Reset */ + ALIENWARE_COMMAND_ERASE_FLASH = 0xFF, /* Erases flash memory on controller */ +}; + +enum +{ + ALIENWARE_COMMAND_REPORT_FIRMWARE = 0x00, /* Get firmware verion */ + ALIENWARE_COMMAND_REPORT_STATUS = 0x01, /* Get status */ + ALIENWARE_COMMAND_REPORT_CONFIG = 0x02, /* Get firmware config */ + ALIENWARE_COMMAND_REPORT_ANIMATION = 0x03, /* Get animation count and last id */ + ALIENWARE_COMMAND_REPORT_UNKNOWN1 = 0x04, /* Get ELC animation by ID */ + ALIENWARE_COMMAND_REPORT_UNKNOWN2 = 0x05, /* Read series??? */ + ALIENWARE_COMMAND_REPORT_UNKNOWN3 = 0x06, /* Get action??? */ + ALIENWARE_COMMAND_REPORT_UNKNOWN4 = 0x07, /* Get Caldera status??? */ +}; + +enum +{ + ALIENWARE_COMMAND_USER_ANIM_NEW = 0x0001, /* Start new animation */ + ALIENWARE_COMMAND_USER_ANIM_FINISH_SAVE = 0x0002, /* Finish and save animation */ + ALIENWARE_COMMAND_USER_ANIM_FINISH_PLAY = 0x0003, /* Finish and play animation */ + ALIENWARE_COMMAND_USER_ANIM_REMOVE = 0x0004, /* Remove/erase animation */ + ALIENWARE_COMMAND_USER_ANIM_PLAY = 0x0005, /* Play animation */ + ALIENWARE_COMMAND_USER_ANIM_DEFAULT = 0x0006, /* Set default animation */ + ALIENWARE_COMMAND_USER_ANIM_STARTUP = 0x0007, /* Set startup animation */ +}; + +enum +{ + ALIENWARE_ANIM_DEFAULT_STARTUP = 0x0008, /* Default slot for startup */ + ALIENWARE_ANIM_DEFAULT = 0x0061, /* Default slot */ + ALIENWARE_COMMAND_USER_ANIM_KEYBOARD = 0xFFFF, /* Non-saved animation slot */ +}; + +enum +{ + ALIENWARE_MODE_COLOR = 0x00, /* Action to set color mode */ + ALIENWARE_MODE_PULSE = 0x01, /* Action to set pulse mode */ + ALIENWARE_MODE_MORPH = 0x02, /* Action to set morph mode */ + 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 */ +}; + +enum +{ + ALIENWARE_TEMPO_MIN = 0x0064, /* Min tempo (as used by AWCC) */ + ALIENWARE_TEMPO_MAX = 0x00FA, /* Max tempo (as used by AWCC) */ + ALIENWARE_TEMPO_SPECTRUM = 0x000F, /* Used by Spectrum mode */ +}; + +enum +{ + ALIENWARE_DURATION_LONG = 0x09C4, /* Min tempo (as used by AWCC) */ + ALIENWARE_DURATION_MED = 0x05DC, /* Max tempo (as used by AWCC) */ + ALIENWARE_DURATION_SHORT = 0x01F3, /* Max tempo (as used by AWCC) */ + ALIENWARE_DURATION_SPECTRUM = 0x01AC, /* Used by Spectrum mode */ +}; + +typedef struct +{ + unsigned char data[ALIENWARE_REPORT_SIZE]; +} AlienwareReport; + +class AlienwareController +{ +public: + AlienwareController(hid_device* dev_handle, const hid_device_info& info, std::string name); + ~AlienwareController(); + + std::string GetSerialString(); + std::string GetDeviceLocation(); + std::string GetDeviceName(); + std::string GetFirmwareVersion(); + unsigned GetZoneCount(); + std::vector GetZoneNames(); + + void SetColor(uint8_t zone, RGBColor color); + void SetColor(uint8_t zone, 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); + void SetDim(uint8_t zone, uint8_t dim); + AlienwareReport GetStatus(uint8_t subcommand); + + void UpdateDim(); + void UpdateMode(); + void UpdateController(); + +protected: + hid_device* dev; + +private: + typedef struct + { + RGBColor color[2]; + uint8_t mode; + uint16_t period; + uint16_t tempo; + uint8_t dim; + } alienware_zone; + + typedef struct + { + unsigned char data[HIDAPI_ALIENWARE_REPORT_SIZE]; + } HidapiAlienwareReport; + + std::string device_name; + std::string location; + std::vector zones; + std::string serial_number; + std::string version; + std::vector zone_names; + bool dirty; + bool dirty_dim; + + HidapiAlienwareReport GetResponse(); + HidapiAlienwareReport Report(uint8_t subcommand); + + bool Dim(std::vector zones, double percent); + bool UserAnimation(uint16_t subcommand, uint16_t animation, uint16_t duration); + 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); + bool MultiModeAction(const uint8_t *mode, const uint16_t *duration, const uint16_t *tempo, const RGBColor *color, unsigned amount); + bool SetColorDirect(RGBColor color, std::vector zones); + bool UpdateDirect(); + bool Reset(); +}; diff --git a/Controllers/AlienwareController/AlienwareControllerDetect.cpp b/Controllers/AlienwareController/AlienwareControllerDetect.cpp new file mode 100644 index 00000000..97d2f884 --- /dev/null +++ b/Controllers/AlienwareController/AlienwareControllerDetect.cpp @@ -0,0 +1,29 @@ +#include "Detector.h" +#include "AlienwareController.h" +#include "RGBController.h" +#include "RGBController_Alienware.h" + +/*---------------------------------------------------------*\ +| Alienware vendor ID | +\*---------------------------------------------------------*/ +#define ALIENWARE_VID 0x187C + +/*---------------------------------------------------------*\ +| Alienware product ID | +\*---------------------------------------------------------*/ +#define ALIENWARE_G5_SE_PID 0x0550 + +void DetectAlienwareControllers(hid_device_info* info, const std::string& name) +{ + hid_device* dev = hid_open_path(info->path); + + if(dev) + { + AlienwareController* controller = new AlienwareController(dev, *info, name); + RGBController_Alienware* rgb_controller = new RGBController_Alienware(controller); + + ResourceManager::get()->RegisterRGBController(rgb_controller); + } +} + +REGISTER_HID_DETECTOR("Alienware G5 SE LED Controller", DetectAlienwareControllers, ALIENWARE_VID, ALIENWARE_G5_SE_PID); diff --git a/Controllers/AlienwareController/RGBController_Alienware.cpp b/Controllers/AlienwareController/RGBController_Alienware.cpp new file mode 100644 index 00000000..b4b6fe02 --- /dev/null +++ b/Controllers/AlienwareController/RGBController_Alienware.cpp @@ -0,0 +1,252 @@ +/*-----------------------------------------*\ +| RGBController_Alienware.cpp | +| | +| Generic RGB Interface for OpenRGB | +| Alienware USB Driver | +| | +| Gabriel Marcano (gemarcano) 4/19/2021 | +\*-----------------------------------------*/ + +#include "RGBController_Alienware.h" + +#include +#include + +RGBController_Alienware::RGBController_Alienware(AlienwareController* alienware_ptr) +{ + alienware = alienware_ptr; + + name = alienware->GetDeviceName(); + vendor = "Alienware Corporation"; + type = DEVICE_TYPE_KEYBOARD; + description = "Alienware USB Device"; + location = alienware->GetDeviceLocation(); + serial = alienware->GetSerialString(); + version = alienware->GetFirmwareVersion(); + + mode Color; + Color.name = "Static"; + Color.value = ALIENWARE_MODE_COLOR; + Color.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS; + Color.color_mode = MODE_COLORS_PER_LED; + Color.colors_min = 1; + Color.colors_max = 1; + modes.push_back(Color); + + mode Pulse; + Pulse.name = "Flashing"; + Pulse.value = ALIENWARE_MODE_PULSE; + Pulse.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED; + Pulse.color_mode = MODE_COLORS_PER_LED; + Pulse.colors_min = 1; + Pulse.colors_max = 1; + Pulse.speed_min = ALIENWARE_TEMPO_MIN; + Pulse.speed_max = ALIENWARE_TEMPO_MAX; + Pulse.speed = ALIENWARE_TEMPO_MIN; + modes.push_back(Pulse); + + mode Morph; + Morph.name = "Morph"; + Morph.value = ALIENWARE_MODE_MORPH; + Morph.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED; + Morph.color_mode = MODE_COLORS_MODE_SPECIFIC; + Morph.colors_min = 2 * alienware->GetZoneCount(); + Morph.colors_max = Morph.colors_min; + Morph.colors.resize(Morph.colors_max); + Morph.speed_min = ALIENWARE_TEMPO_MIN; + Morph.speed_max = ALIENWARE_TEMPO_MAX; + Morph.speed = ALIENWARE_TEMPO_MIN; + modes.push_back(Morph); + + mode Spectrum; + Spectrum.name = "Spectrum Cycle"; + Spectrum.value = ALIENWARE_MODE_SPECTRUM; + Spectrum.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED; + Spectrum.color_mode = MODE_COLORS_NONE; + Spectrum.speed_min = ALIENWARE_TEMPO_SPECTRUM; + Spectrum.speed_max = ALIENWARE_TEMPO_MAX; + Spectrum.speed = ALIENWARE_TEMPO_SPECTRUM; + modes.push_back(Spectrum); + + mode Rainbow; + Rainbow.name = "Rainbow Wave"; + Rainbow.value = ALIENWARE_MODE_RAINBOW; + Rainbow.flags = MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED; + Rainbow.color_mode = MODE_COLORS_NONE; + Rainbow.speed_min = ALIENWARE_TEMPO_SPECTRUM; + Rainbow.speed_max = ALIENWARE_TEMPO_MAX; + Rainbow.speed = ALIENWARE_TEMPO_SPECTRUM; + modes.push_back(Rainbow); + + mode Breathing; + Breathing.name = "Breathing"; + Breathing.value = ALIENWARE_MODE_BREATHING; + Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_SPEED; + Breathing.color_mode = MODE_COLORS_PER_LED; + Pulse.colors_min = 1; + Pulse.colors_max = 1; + Breathing.speed_min = ALIENWARE_TEMPO_MIN; + Breathing.speed_max = ALIENWARE_TEMPO_MAX; + Breathing.speed = ALIENWARE_TEMPO_MIN; + modes.push_back(Breathing); + + SetupZones(); + + alienware->UpdateDim(); +} + +void RGBController_Alienware::SetupZones() +{ + /*-------------------------------------------------*\ + | Set zones and leds | + \*-------------------------------------------------*/ + std::vector zone_names = alienware->GetZoneNames(); + + for(unsigned int zone_idx = 0; zone_idx < alienware->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.matrix_map = nullptr; + + zones.emplace_back(new_zone); + } + + for(unsigned int led_idx = 0; led_idx < zones.size(); led_idx++) + { + led new_led; + + new_led.name = zones[led_idx].name + std::string(" LED"); + + leds.emplace_back(new_led); + } + + SetupColors(); +} + +void RGBController_Alienware::ResizeZone(int /*zone*/, int /*new_size*/) +{ + /*---------------------------------------------------------*\ + | This device does not support resizing zones | + \*---------------------------------------------------------*/ +} + +void RGBController_Alienware::DeviceUpdateLEDs() +{ + DeviceUpdateMode(); +} + +void RGBController_Alienware::UpdateZoneLEDs(int /*zone*/) +{ + DeviceUpdateLEDs(); +} + +void RGBController_Alienware::UpdateSingleLED(int led) +{ + UpdateZoneLEDs(led); +} + +void RGBController_Alienware::SetCustomMode() +{ + active_mode = 0; +} + +static bool modes_eq(const mode& mode1, const mode& mode2) +{ + return( ( mode1.name == mode2.name ) + && ( mode1.value == mode2.value ) + && ( mode1.flags == mode2.flags ) + && ( mode1.speed_min == mode2.speed_min ) + && ( mode1.speed_max == mode2.speed_max ) + && ( mode1.colors_min == mode2.colors_min ) + && ( mode1.colors_max == mode2.colors_max ) + && ( mode1.speed == mode2.speed ) + && ( mode1.direction == mode2.direction ) + && ( mode1.color_mode == mode2.color_mode ) + && ( mode1.colors == mode2.colors ) ); +} + +void RGBController_Alienware::DeviceUpdateMode() +{ + /*---------------------------------------------------------*\ + | Copy mode to get the current state-- this is racy, as the | + | UI thread can be actively modifying this variable | + \*---------------------------------------------------------*/ + int current_mode_idx = active_mode; + mode current_mode = modes[current_mode_idx]; + + bool done = false; + + while(!done) + { + /*-----------------------------------------------------*\ + | Setup state per zone | + \*-----------------------------------------------------*/ + for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++) + { + zone current_zone = zones[zone_idx]; + + /*-------------------------------------------------*\ + | Some modes use 2000ms (0x07D0) for their duration,| + | per traces | + \*-------------------------------------------------*/ + uint16_t period = 0x07d0; + + alienware->SetMode(zone_idx, current_mode.value); + + switch(current_mode_idx) + { + case ALIENWARE_MODE_COLOR: + alienware->SetPeriod(zone_idx, period); + alienware->SetColor( zone_idx, colors[current_zone.start_idx]); + alienware->SetTempo( zone_idx, ALIENWARE_TEMPO_MAX); + break; + + case ALIENWARE_MODE_PULSE: + alienware->SetPeriod(zone_idx, period); + alienware->SetColor( zone_idx, colors[current_zone.start_idx]); + alienware->SetTempo( zone_idx, current_mode.speed); + break; + + case ALIENWARE_MODE_MORPH: + alienware->SetPeriod(zone_idx, period); + alienware->SetColor( zone_idx, current_mode.colors[zone_idx * 2], current_mode.colors[(zone_idx * 2) + 1]); + alienware->SetTempo( zone_idx, current_mode.speed); + break; + + case ALIENWARE_MODE_SPECTRUM: + case ALIENWARE_MODE_RAINBOW: + alienware->SetPeriod(zone_idx, ALIENWARE_DURATION_SPECTRUM); + alienware->SetTempo( zone_idx, current_mode.speed); + break; + + case ALIENWARE_MODE_BREATHING: + alienware->SetPeriod(zone_idx, period); + alienware->SetColor( zone_idx, colors[current_zone.start_idx], 0x0); + alienware->SetTempo( zone_idx, current_mode.speed); + break; + } + } + + /*-----------------------------------------------------*\ + | Due to rate-limiting, this can take more than one | + | second to execute | + \*-----------------------------------------------------*/ + alienware->UpdateController(); + + /*-----------------------------------------------------*\ + | Re-run update if there's anything that's changed from | + | under us... | + \*-----------------------------------------------------*/ + int new_current_mode_idx = active_mode; + mode new_current_mode = modes[current_mode_idx]; + + done = (current_mode_idx == new_current_mode_idx && modes_eq(new_current_mode, current_mode)); + current_mode_idx = new_current_mode_idx; + current_mode = new_current_mode; + } +} diff --git a/Controllers/AlienwareController/RGBController_Alienware.h b/Controllers/AlienwareController/RGBController_Alienware.h new file mode 100644 index 00000000..312046d5 --- /dev/null +++ b/Controllers/AlienwareController/RGBController_Alienware.h @@ -0,0 +1,37 @@ +/*-----------------------------------------*\ +| RGBController_Alienware.h | +| | +| Generic RGB Interface for OpenRGB | +| Alienware RGB USB Driver | +| | +| Gabriel Marcano (gemarcano) 4/19/2021 | +\*-----------------------------------------*/ + +#pragma once + +#include "RGBController.h" +#include "AlienwareController.h" + +#include + +class RGBController_Alienware : public RGBController +{ +public: + RGBController_Alienware(AlienwareController* alienware_ptr); + + void SetupZones(); + + void ResizeZone(int zone, int new_size); + + void DeviceUpdateLEDs(); + void UpdateZoneLEDs(int zone); + void UpdateSingleLED(int led); + + void DeviceUpdateMode(); + + void SetCustomMode(); + +private: + AlienwareController* alienware; + std::chrono::steady_clock::time_point last_packet_ts; +}; diff --git a/OpenRGB.pro b/OpenRGB.pro index b218e1ed..b658fcbd 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -62,6 +62,7 @@ INCLUDEPATH += serial_port/ \ super_io/ \ AutoStart/ \ + Controllers/AlienwareController/ \ Controllers/AMDWraithPrismController/ \ Controllers/ASRockPolychromeSMBusController/ \ Controllers/ASRockPolychromeUSBController/ \ @@ -174,6 +175,8 @@ HEADERS += serial_port/serial_port.h \ super_io/super_io.h \ AutoStart/AutoStart.h \ + Controllers/AlienwareController/AlienwareController.h \ + Controllers/AlienwareController/RGBController_Alienware.h \ Controllers/AMDWraithPrismController/AMDWraithPrismController.h \ Controllers/AMDWraithPrismController/RGBController_AMDWraithPrism.h \ Controllers/AnnePro2Controller/AnnePro2Controller.h \ @@ -464,6 +467,9 @@ SOURCES += serial_port/serial_port.cpp \ super_io/super_io.cpp \ AutoStart/AutoStart.cpp \ + Controllers/AlienwareController/AlienwareController.cpp \ + Controllers/AlienwareController/AlienwareControllerDetect.cpp \ + Controllers/AlienwareController/RGBController_Alienware.cpp \ Controllers/AMDWraithPrismController/AMDWraithPrismController.cpp \ Controllers/AMDWraithPrismController/AMDWraithPrismControllerDetect.cpp \ Controllers/AMDWraithPrismController/RGBController_AMDWraithPrism.cpp \