mirror of
https://github.com/CalcProgrammer1/OpenRGB.git
synced 2026-05-11 08:04:16 -04:00
316 lines
11 KiB
C++
316 lines
11 KiB
C++
/*---------------------------------------------------------*\
|
|
| ThrustmasterSolController.cpp |
|
|
| |
|
|
| Driver for Thrustmaster Sol series joysticks |
|
|
| |
|
|
| Ken Sanislo 02 Apr 2026 |
|
|
| |
|
|
| This file is part of the OpenRGB project |
|
|
| SPDX-License-Identifier: GPL-2.0-or-later |
|
|
\*---------------------------------------------------------*/
|
|
|
|
#include <cstring>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include "ThrustmasterSolController.h"
|
|
#include "StringUtils.h"
|
|
|
|
ThrustmasterSolController::ThrustmasterSolController(libusb_device_handle* dev_handle,
|
|
const char* path,
|
|
unsigned short pid)
|
|
{
|
|
dev = dev_handle;
|
|
this->pid = pid;
|
|
|
|
location = "USB: ";
|
|
location += path;
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Get serial number string descriptor |
|
|
\*---------------------------------------------------------*/
|
|
libusb_device_descriptor desc;
|
|
libusb_get_device_descriptor(libusb_get_device(dev_handle), &desc);
|
|
|
|
if(desc.iSerialNumber != 0)
|
|
{
|
|
unsigned char serial_str[256];
|
|
int ret = libusb_get_string_descriptor_ascii(dev_handle,
|
|
desc.iSerialNumber,
|
|
serial_str,
|
|
sizeof(serial_str));
|
|
if(ret > 0)
|
|
{
|
|
serial = std::string(reinterpret_cast<char*>(serial_str), ret);
|
|
}
|
|
}
|
|
}
|
|
|
|
ThrustmasterSolController::~ThrustmasterSolController()
|
|
{
|
|
if(dev != nullptr)
|
|
{
|
|
libusb_release_interface(dev, THRUSTMASTER_SOL_INTERFACE);
|
|
libusb_close(dev);
|
|
}
|
|
}
|
|
|
|
std::string ThrustmasterSolController::GetDeviceLocation()
|
|
{
|
|
return(location);
|
|
}
|
|
|
|
std::string ThrustmasterSolController::GetSerialString()
|
|
{
|
|
return(serial);
|
|
}
|
|
|
|
unsigned short ThrustmasterSolController::GetPID()
|
|
{
|
|
return(pid);
|
|
}
|
|
|
|
void ThrustmasterSolController::SendPacket(unsigned char* packet, unsigned int size)
|
|
{
|
|
if(dev == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int actual_length = 0;
|
|
libusb_interrupt_transfer(dev,
|
|
THRUSTMASTER_SOL_ENDPOINT_OUT,
|
|
packet,
|
|
size,
|
|
&actual_length,
|
|
1000);
|
|
}
|
|
|
|
void ThrustmasterSolController::BuildAndSendPackets(unsigned int* zones,
|
|
RGBColor* colors,
|
|
unsigned int count,
|
|
bool persistent)
|
|
{
|
|
unsigned char persist_flag = persistent ? THRUSTMASTER_SOL_PERSISTENT
|
|
: THRUSTMASTER_SOL_VOLATILE;
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Separate grip zones from base zones. Zone 0x00 exists on |
|
|
| both the base (logo LED) and the grip (thumbstick LED). |
|
|
| The GRIP_FLAG bit selects the grip report type. |
|
|
\*---------------------------------------------------------*/
|
|
unsigned char base_zones[THRUSTMASTER_SOL_R_ZONE_COUNT];
|
|
RGBColor base_colors[THRUSTMASTER_SOL_R_ZONE_COUNT];
|
|
unsigned int base_count = 0;
|
|
|
|
for(unsigned int i = 0; i < count; i++)
|
|
{
|
|
if(zones[i] & THRUSTMASTER_SOL_GRIP_FLAG)
|
|
{
|
|
/*-------------------------------------------------*\
|
|
| Send grip packet for thumbstick immediately |
|
|
\*-------------------------------------------------*/
|
|
unsigned char zone_id = zones[i] & 0xFF;
|
|
|
|
unsigned char packet[THRUSTMASTER_SOL_PACKET_SIZE];
|
|
memset(packet, 0x00, THRUSTMASTER_SOL_PACKET_SIZE);
|
|
|
|
packet[0] = THRUSTMASTER_SOL_REPORT_GRIP_LO;
|
|
packet[1] = THRUSTMASTER_SOL_REPORT_GRIP_HI;
|
|
packet[2] = persist_flag | 0x01;
|
|
packet[3] = THRUSTMASTER_SOL_MARKER;
|
|
packet[4] = zone_id;
|
|
packet[5] = RGBGetRValue(colors[i]);
|
|
packet[6] = RGBGetGValue(colors[i]);
|
|
packet[7] = RGBGetBValue(colors[i]);
|
|
|
|
SendPacket(packet, THRUSTMASTER_SOL_PACKET_SIZE);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
}
|
|
else
|
|
{
|
|
base_zones[base_count] = zones[i] & 0xFF;
|
|
base_colors[base_count] = colors[i];
|
|
base_count++;
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Send base zone packets, batching up to 15 entries each |
|
|
\*---------------------------------------------------------*/
|
|
for(unsigned int offset = 0; offset < base_count; offset += THRUSTMASTER_SOL_MAX_ENTRIES)
|
|
{
|
|
unsigned int entries = base_count - offset;
|
|
|
|
if(entries > THRUSTMASTER_SOL_MAX_ENTRIES)
|
|
{
|
|
entries = THRUSTMASTER_SOL_MAX_ENTRIES;
|
|
}
|
|
|
|
unsigned char packet[THRUSTMASTER_SOL_PACKET_SIZE];
|
|
memset(packet, 0x00, THRUSTMASTER_SOL_PACKET_SIZE);
|
|
|
|
packet[0] = THRUSTMASTER_SOL_REPORT_BASE_LO;
|
|
packet[1] = THRUSTMASTER_SOL_REPORT_BASE_HI;
|
|
packet[2] = persist_flag | (unsigned char)entries;
|
|
packet[3] = THRUSTMASTER_SOL_MARKER;
|
|
|
|
for(unsigned int j = 0; j < entries; j++)
|
|
{
|
|
unsigned int idx = offset + j;
|
|
packet[4 + j * 4] = base_zones[idx];
|
|
packet[4 + j * 4 + 1] = RGBGetRValue(base_colors[idx]);
|
|
packet[4 + j * 4 + 2] = RGBGetGValue(base_colors[idx]);
|
|
packet[4 + j * 4 + 3] = RGBGetBValue(base_colors[idx]);
|
|
}
|
|
|
|
SendPacket(packet, THRUSTMASTER_SOL_PACKET_SIZE);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
}
|
|
}
|
|
|
|
void ThrustmasterSolController::SetLEDColor(unsigned int zone, RGBColor color)
|
|
{
|
|
BuildAndSendPackets(&zone, &color, 1, false);
|
|
}
|
|
|
|
void ThrustmasterSolController::SetLEDColors(unsigned int* zones,
|
|
RGBColor* colors,
|
|
unsigned int count)
|
|
{
|
|
BuildAndSendPackets(zones, colors, count, false);
|
|
}
|
|
|
|
void ThrustmasterSolController::SaveColors(unsigned int* zones,
|
|
RGBColor* colors,
|
|
unsigned int count)
|
|
{
|
|
BuildAndSendPackets(zones, colors, count, true);
|
|
}
|
|
|
|
void ThrustmasterSolController::ReadColors(std::vector<unsigned int>& zones,
|
|
std::vector<RGBColor>& colors)
|
|
{
|
|
if(dev == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
zones.clear();
|
|
colors.clear();
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Read base zones (report 0x0002). May require multiple |
|
|
| queries if more than 14 zones are returned per response. |
|
|
| Zone IDs are returned without the grip flag, so they |
|
|
| match the base LED values (including zone 0x00 = logo). |
|
|
\*---------------------------------------------------------*/
|
|
unsigned char start = 0;
|
|
|
|
for(int page = 0; page < 4; page++)
|
|
{
|
|
unsigned char query[THRUSTMASTER_SOL_PACKET_SIZE];
|
|
memset(query, 0x00, THRUSTMASTER_SOL_PACKET_SIZE);
|
|
|
|
query[0] = THRUSTMASTER_SOL_READ_BASE_LO;
|
|
query[1] = THRUSTMASTER_SOL_READ_BASE_HI;
|
|
query[2] = start;
|
|
|
|
SendPacket(query, THRUSTMASTER_SOL_PACKET_SIZE);
|
|
|
|
unsigned char resp[THRUSTMASTER_SOL_PACKET_SIZE];
|
|
int actual = 0;
|
|
int ret = libusb_interrupt_transfer(dev,
|
|
THRUSTMASTER_SOL_ENDPOINT_IN,
|
|
resp,
|
|
THRUSTMASTER_SOL_PACKET_SIZE,
|
|
&actual,
|
|
1000);
|
|
|
|
if(ret != LIBUSB_SUCCESS || actual < 4)
|
|
{
|
|
break;
|
|
}
|
|
|
|
unsigned int n = resp[2] & 0x0F;
|
|
|
|
if(n == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
unsigned char last = start;
|
|
|
|
for(unsigned int i = 0; i < n; i++)
|
|
{
|
|
unsigned int off = 4 + i * 4;
|
|
|
|
if(off + 3 >= (unsigned int)actual)
|
|
{
|
|
break;
|
|
}
|
|
|
|
unsigned char zid = resp[off];
|
|
unsigned char r = resp[off + 1];
|
|
unsigned char g = resp[off + 2];
|
|
unsigned char b = resp[off + 3];
|
|
|
|
zones.push_back(zid);
|
|
colors.push_back(ToRGBColor(r, g, b));
|
|
last = zid;
|
|
}
|
|
|
|
start = last + 1;
|
|
|
|
if(n < 14 || start > 0x20)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*---------------------------------------------------------*\
|
|
| Read grip zones (report 0x8002). Grip zone IDs are |
|
|
| returned with the GRIP_FLAG set so they match the |
|
|
| thumbstick LED value. They are added as new entries, |
|
|
| not overwriting base entries (zone 0x00 exists on both). |
|
|
\*---------------------------------------------------------*/
|
|
unsigned char grip_query[THRUSTMASTER_SOL_PACKET_SIZE];
|
|
memset(grip_query, 0x00, THRUSTMASTER_SOL_PACKET_SIZE);
|
|
|
|
grip_query[0] = THRUSTMASTER_SOL_READ_GRIP_LO;
|
|
grip_query[1] = THRUSTMASTER_SOL_READ_GRIP_HI;
|
|
|
|
SendPacket(grip_query, THRUSTMASTER_SOL_PACKET_SIZE);
|
|
|
|
unsigned char grip_resp[THRUSTMASTER_SOL_PACKET_SIZE];
|
|
int grip_actual = 0;
|
|
int grip_ret = libusb_interrupt_transfer(dev,
|
|
THRUSTMASTER_SOL_ENDPOINT_IN,
|
|
grip_resp,
|
|
THRUSTMASTER_SOL_PACKET_SIZE,
|
|
&grip_actual,
|
|
1000);
|
|
|
|
if(grip_ret == LIBUSB_SUCCESS && grip_actual >= 8)
|
|
{
|
|
unsigned int grip_n = grip_resp[2] & 0x0F;
|
|
|
|
for(unsigned int i = 0; i < grip_n; i++)
|
|
{
|
|
unsigned int off = 4 + i * 4;
|
|
|
|
if(off + 3 >= (unsigned int)grip_actual)
|
|
{
|
|
break;
|
|
}
|
|
|
|
unsigned char zid = grip_resp[off];
|
|
unsigned char r = grip_resp[off + 1];
|
|
unsigned char g = grip_resp[off + 2];
|
|
unsigned char b = grip_resp[off + 3];
|
|
|
|
zones.push_back(THRUSTMASTER_SOL_GRIP_FLAG | zid);
|
|
colors.push_back(ToRGBColor(r, g, b));
|
|
}
|
|
}
|
|
}
|