Files
OpenRGB/Controllers/AlienwareController/AlienwareController.cpp

1314 lines
46 KiB
C++

/*---------------------------------------------------------*\
| AlienwareController.cpp |
| |
| Driver for Dell Alienware RGB USB controller |
| |
| Gabriel Marcano (gemarcano) 21 Apr 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <cstdint>
#include <map>
#include <thread>
#include <chrono>
#include <algorithm>
#include <sstream>
#include "RGBController.h"
#include "AlienwareController.h"
#include "LogManager.h"
#include "StringUtils.h"
typedef uint32_t alienware_platform_id;
/*---------------------------------------------------------*\
| Some devices appear to report the wrong number of zones. |
| Record that here. |
\*---------------------------------------------------------*/
static const std::map<alienware_platform_id, uint8_t> zone_quirks_table =
{
{ 0x0C01, 4 }, // Dell G5 SE 5505
{ 0x0A01, 16 }, // Dell G7 15 7500
{ 0x0E03, 4 }, // Dell G15 5511
{ 0x0E07, 4 }, // Dell G15 5520
{ 0x0E0A, 4 } // Dell G15 5530
};
/*---------------------------------------------------------*\
| Add zones for devices here, mapping the platform ID to |
| the zone names |
\*---------------------------------------------------------*/
static const std::map<alienware_platform_id, std::vector<const char*>> 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",
"Light Bar 4", "Light Bar 5", "Light Bar 6",
"Light Bar 7", "Light Bar 8", "Light Bar 9",
"Light Bar 10", "Light Bar 11", "Light Bar 12" } },
{ 0x0E03, { "Left", "Middle", "Right", "Numpad" } },
{ 0x0E07, { "Left", "Middle", "Right", "Numpad" } },
{ 0x0E0A, { "Left", "Middle", "Right", "Numpad" } },
};
/*---------------------------------------------------------*\
| Mapping the platform ID to the actual hardware zone IDs |
\*---------------------------------------------------------*/
static const std::map<alienware_platform_id, std::vector<uint8_t>> zone_ids_table =
{
{ 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
// 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<alienware_platform_id, std::vector<std::vector<uint8_t>>> 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)
{
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 |
\*-----------------------------------------------------*/
serial_number = StringUtils::wstring_to_string(info.serial_number);
/*-----------------------------------------------------*\
| 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];
/*-----------------------------------------------------*\
| Check if the device reports the wrong number of zones |
\*-----------------------------------------------------*/
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 |
\*-----------------------------------------------------*/
report = Report(ALIENWARE_COMMAND_REPORT_FIRMWARE);
std::stringstream fw_string;
fw_string << static_cast<unsigned>(report.data[4]) << '.' << static_cast<unsigned>(report.data[5]) << '.' << static_cast<unsigned>(report.data[6]);
version = fw_string.str();
/*-----------------------------------------------------*\
| Initialize Alienware zones |
\*-----------------------------------------------------*/
zones.resize(number_of_zones);
if(zone_names_table.count(platform_id))
{
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<std::vector<uint8_t>>& 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<uint8_t>& 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
{
LOG_WARNING("[%s] Unknown platform: %8X, Number of zones: %d", ALIENWARE_CONTROLLER_NAME, platform_id, number_of_zones);
/*-------------------------------------------------*\
| 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");
}
}
/*-----------------------------------------------------*\
| Initialize hardware zone IDs |
\*-----------------------------------------------------*/
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
{
for(size_t i = 0; i < number_of_zones; i++)
{
zone_ids.push_back(static_cast<uint8_t>(i));
}
zone_led_counts.assign(zones.size(), 1);
}
/*-----------------------------------------------------*\
| 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 = 0; 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;
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 |
\*-----------------------------------------------------*/
dirty = true;
dirty_dim = true;
}
AlienwareController::~AlienwareController()
{
}
unsigned int AlienwareController::GetZoneCount()
{
return((unsigned int)zones.size());
}
std::vector<const char*> 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<unsigned int> 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);
}
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<uint8_t> zones, double percent)
{
/*-----------------------------------------------------*\
| Bail out if there are no zones to update |
\*-----------------------------------------------------*/
if(!zones.size())
{
return(true);
}
bool result = true;
/*-----------------------------------------------------*\
| Split the zone list into packet-sized chunks so it |
| never overflows usb_buf |
\*-----------------------------------------------------*/
for(size_t offset = 0; offset < zones.size(); offset += ALIENWARE_MAX_ZONES_PER_PACKET)
{
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)std::min<size_t>(ALIENWARE_MAX_ZONES_PER_PACKET, zones.size() - offset);
usb_buf[0x00] = 0x00;
usb_buf[0x01] = 0x03;
usb_buf[0x02] = ALIENWARE_COMMAND_DIM;
usb_buf[0x03] = static_cast<uint8_t>(percent);
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[offset + i];
}
/*-------------------------------------------------*\
| Send packet |
\*-------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-------------------------------------------------*\
| For this command, error is if the output equals |
| the input |
\*-------------------------------------------------*/
result &= (response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE);
}
return(result);
}
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 = 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:
/*---------------------------------------------*\
| 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:
return(true);
}
}
bool AlienwareController::SelectZonesSingle(const std::vector<uint8_t>& 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<uint8_t>& zones)
{
/*-----------------------------------------------------*\
| Bail if zones is empty, and return false to indicate |
| nothing has changed |
\*-----------------------------------------------------*/
if(!zones.size())
{
return(false);
}
bool result = true;
/*-----------------------------------------------------*\
| Split the zone list into packet-sized chunks so it |
| never overflows usb_buf |
\*-----------------------------------------------------*/
for(size_t offset = 0; offset < zones.size(); offset += ALIENWARE_MAX_ZONES_PER_PACKET)
{
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)std::min<size_t>(ALIENWARE_MAX_ZONES_PER_PACKET, zones.size() - offset);
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[offset + i];
}
/*-------------------------------------------------*\
| Send packet |
\*-------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-------------------------------------------------*\
| For this command, error is if the output equals |
| the input |
\*-------------------------------------------------*/
result &= (response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE);
}
return(result);
}
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 = 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<uint8_t> zones)
{
/*-----------------------------------------------------*\
| Bail if zones is empty |
\*-----------------------------------------------------*/
if(zones.empty())
{
return(true);
}
bool result = true;
/*-----------------------------------------------------*\
| Split the zone list into packet-sized chunks so it |
| never overflows usb_buf |
\*-----------------------------------------------------*/
for(size_t offset = 0; offset < zones.size(); offset += ALIENWARE_MAX_ZONES_PER_PACKET)
{
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)std::min<size_t>(ALIENWARE_MAX_ZONES_PER_PACKET, zones.size() - offset);
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[offset + i];
}
/*-------------------------------------------------*\
| Send packet |
\*-------------------------------------------------*/
SendHIDReport(dev, usb_buf, sizeof(usb_buf));
HidapiAlienwareReport response = GetResponse();
/*-------------------------------------------------*\
| For this command, error is if the output equals |
| the input |
\*-------------------------------------------------*/
result &= (response.data[1] == 0x03) && memcmp(usb_buf, response.data, HIDAPI_ALIENWARE_REPORT_SIZE);
}
return(result);
}
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 = 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(zone >= zones.size())
{
return;
}
if(mode != zones[zone].mode)
{
zones[zone].mode = mode;
dirty = true;
}
}
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;
}
zones[zone].color[0] = color1;
zones[zone].color[1] = color2;
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;
dirty = true;
}
}
void AlienwareController::SetTempo(uint8_t zone, uint16_t tempo)
{
if(zone >= zones.size())
{
return;
}
if(tempo != zones[zone].tempo)
{
zones[zone].tempo = tempo;
dirty = true;
}
}
void AlienwareController::SetDim(uint8_t zone, uint8_t dim)
{
if(zone >= zones.size())
{
return;
}
if(dim != zones[zone].dim)
{
zones[zone].dim = dim;
dirty_dim = true;
}
}
void AlienwareController::SetDirection(uint8_t zone, uint8_t direction)
{
if(zone < zones.size())
{
zones[zone].direction = direction;
dirty = true;
}
}
void AlienwareController::UpdateDim()
{
if(!dirty_dim)
{
return;
}
/*-----------------------------------------------------*\
| Collect all zones that share dim settings, and update |
| them together |
\*-----------------------------------------------------*/
std::map<uint8_t, std::vector<uint8_t>> dim_zone_map;
for(size_t led = 0; led < zone_ids.size(); led++)
{
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<const uint8_t, std::vector<uint8_t>> &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<RGBColor, std::vector<uint8_t>> color_zone_map;
for(size_t led = 0; led < zone_ids.size() && led < led_colors[0].size(); led++)
{
color_zone_map[led_colors[0][led]].emplace_back(zone_ids[led]);
}
for(std::pair<const RGBColor, std::vector<uint8_t>> &pair : color_zone_map)
{
/*-------------------------------------------------*\
| Bail on an error... |
\*-------------------------------------------------*/
if(!SetColorDirect(pair.first, pair.second))
{
return false;
}
}
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 },
{ 0x800080, 0xFF0000, 0xFFA500, 0xFFFF00, 0x008000, 0x00BFFF, 0x0000FF },
{ 0x0000FF, 0x800080, 0xFF0000, 0xFFA500, 0xFFFF00, 0x008000, 0x00BFFF },
{ 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<uint8_t>(zone_idx));
}
void AlienwareController::BuildAnimGroups(std::vector<AlienwareAnimGroup>& 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<AlienwareAnimGroup>& 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<AlienwareAnimGroup>& 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<size_t>(ALIENWARE_MAX_ZONES_PER_PACKET, group.hw_zones.size() - offset);
std::vector<uint8_t> 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()
{
/*-----------------------------------------------------*\
| If there are no updates, don't bother running this |
\*-----------------------------------------------------*/
if(!dirty)
{
return;
}
/*-----------------------------------------------------*\
| Batch identical zones into shared pairs |
\*-----------------------------------------------------*/
std::vector<AlienwareAnimGroup> 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)
{
return;
}
result = ProgramAnimGroups(groups);
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::SaveController()
{
/*-----------------------------------------------------*\
| Save current mode configuration to EEPROM slot 0x0061 |
\*-----------------------------------------------------*/
UserAnimation(ALIENWARE_COMMAND_USER_ANIM_REMOVE, ALIENWARE_ANIM_DEFAULT, 0);
bool result = UserAnimation(ALIENWARE_COMMAND_USER_ANIM_NEW, ALIENWARE_ANIM_DEFAULT, 0);
if(!result)
{
return;
}
/*-----------------------------------------------------*\
| Reuse the UpdateMode batching |
\*-----------------------------------------------------*/
std::vector<AlienwareAnimGroup> groups;
BuildAnimGroups(groups);
if(groups.empty() || CountAnimPairs(groups) > ALIENWARE_MAX_ANIM_PAIRS)
{
LOG_WARNING("[%s] Save needs %d pairs (max %d); skipping EEPROM save",
ALIENWARE_CONTROLLER_NAME, (int)CountAnimPairs(groups), ALIENWARE_MAX_ANIM_PAIRS);
return;
}
result = ProgramAnimGroups(groups);
if(!result)
{
return;
}
UserAnimation(ALIENWARE_COMMAND_USER_ANIM_FINISH_SAVE, ALIENWARE_ANIM_DEFAULT, 0);
UserAnimation(ALIENWARE_COMMAND_USER_ANIM_DEFAULT, ALIENWARE_ANIM_DEFAULT, 0);
UserAnimation(ALIENWARE_COMMAND_USER_ANIM_STARTUP, ALIENWARE_ANIM_DEFAULT, 0);
}
void AlienwareController::UpdateController()
{
UpdateMode();
UpdateDim();
}