mirror of
https://github.com/CalcProgrammer1/OpenRGB.git
synced 2026-05-18 19:46:27 -04:00
Add support for Thrustmaster Sol series joystick RGB LEDs
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RGBController_ThrustmasterSol.cpp |
|
||||
| |
|
||||
| RGBController 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 "RGBController_ThrustmasterSol.h"
|
||||
|
||||
/**------------------------------------------------------------------*\
|
||||
@name Thrustmaster Sol
|
||||
@category Gamepad
|
||||
@type USB
|
||||
@save :white_check_mark:
|
||||
@direct :white_check_mark:
|
||||
@effects :x:
|
||||
@detectors DetectThrustmasterSolControllers
|
||||
@comment Thrustmaster Sol series joystick RGB LED control. Supports
|
||||
Sol-R, Sol F16, and Sol F18 variants. Only Sol-R has been tested.
|
||||
Uses vendor-specific USB interface 1 (not HID).
|
||||
\*-------------------------------------------------------------------*/
|
||||
|
||||
#define NA 0xFFFFFFFF
|
||||
|
||||
static unsigned int logo_matrix_map[2][2] =
|
||||
{
|
||||
{ 0, 3 },
|
||||
{ 1, 2 },
|
||||
};
|
||||
|
||||
static unsigned int ring_matrix_map[3][5] =
|
||||
{
|
||||
{ NA, 7, 0, 1, NA },
|
||||
{ 6, NA, NA, NA, 2 },
|
||||
{ NA, 5, 4, 3, NA },
|
||||
};
|
||||
|
||||
static unsigned int right_buttons_matrix_map[2][2] =
|
||||
{
|
||||
{ 0, 1 },
|
||||
{ 2, 3 },
|
||||
};
|
||||
|
||||
static unsigned int left_buttons_matrix_map[2][2] =
|
||||
{
|
||||
{ 0, 1 },
|
||||
{ 2, 3 },
|
||||
};
|
||||
|
||||
RGBController_ThrustmasterSol::RGBController_ThrustmasterSol(ThrustmasterSolController* controller_ptr)
|
||||
{
|
||||
controller = controller_ptr;
|
||||
|
||||
vendor = "Thrustmaster";
|
||||
type = DEVICE_TYPE_GAMEPAD;
|
||||
description = "Thrustmaster Sol Series Joystick";
|
||||
location = controller->GetDeviceLocation();
|
||||
serial = controller->GetSerialString();
|
||||
|
||||
mode Direct;
|
||||
Direct.name = "Direct";
|
||||
Direct.value = 0;
|
||||
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE;
|
||||
Direct.color_mode = MODE_COLORS_PER_LED;
|
||||
modes.push_back(Direct);
|
||||
|
||||
SetupZones();
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Read current EEPROM colors from device so the UI starts |
|
||||
| with the actual hardware state rather than all-black |
|
||||
\*---------------------------------------------------------*/
|
||||
std::vector<unsigned int> hw_zones;
|
||||
std::vector<RGBColor> hw_colors;
|
||||
controller->ReadColors(hw_zones, hw_colors);
|
||||
|
||||
for(unsigned int i = 0; i < hw_zones.size(); i++)
|
||||
{
|
||||
for(unsigned int j = 0; j < leds.size(); j++)
|
||||
{
|
||||
if(leds[j].value == hw_zones[i])
|
||||
{
|
||||
colors[j] = hw_colors[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RGBController_ThrustmasterSol::~RGBController_ThrustmasterSol()
|
||||
{
|
||||
delete controller;
|
||||
}
|
||||
|
||||
void RGBController_ThrustmasterSol::SetupZones()
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| Thumbstick zone (grip, 1 LED) |
|
||||
\*---------------------------------------------------------*/
|
||||
zone thumbstick_zone;
|
||||
thumbstick_zone.name = "Thumbstick";
|
||||
thumbstick_zone.type = ZONE_TYPE_SINGLE;
|
||||
thumbstick_zone.leds_min = 1;
|
||||
thumbstick_zone.leds_max = 1;
|
||||
thumbstick_zone.leds_count = 1;
|
||||
thumbstick_zone.matrix_map = NULL;
|
||||
zones.push_back(thumbstick_zone);
|
||||
|
||||
led thumbstick_led;
|
||||
thumbstick_led.name = "Thumbstick";
|
||||
thumbstick_led.value = THRUSTMASTER_SOL_GRIP_FLAG | 0x00;
|
||||
leds.push_back(thumbstick_led);
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| TM Logo zone (3 LEDs) |
|
||||
\*---------------------------------------------------------*/
|
||||
zone logo_zone;
|
||||
logo_zone.name = "TM Logo";
|
||||
logo_zone.type = ZONE_TYPE_MATRIX;
|
||||
logo_zone.leds_min = 4;
|
||||
logo_zone.leds_max = 4;
|
||||
logo_zone.leds_count = 4;
|
||||
logo_zone.matrix_map = new matrix_map_type;
|
||||
logo_zone.matrix_map->height = 2;
|
||||
logo_zone.matrix_map->width = 2;
|
||||
logo_zone.matrix_map->map = (unsigned int *)&logo_matrix_map;
|
||||
zones.push_back(logo_zone);
|
||||
|
||||
led logo_top_left;
|
||||
logo_top_left.name = "TM Logo Top Left";
|
||||
logo_top_left.value = 0x01;
|
||||
leds.push_back(logo_top_left);
|
||||
|
||||
led logo_bottom_left;
|
||||
logo_bottom_left.name = "TM Logo Bottom Left";
|
||||
logo_bottom_left.value = 0x02;
|
||||
leds.push_back(logo_bottom_left);
|
||||
|
||||
led logo_bottom_right;
|
||||
logo_bottom_right.name = "TM Logo Bottom Right";
|
||||
logo_bottom_right.value = 0x03;
|
||||
leds.push_back(logo_bottom_right);
|
||||
|
||||
led logo_top_right;
|
||||
logo_top_right.name = "TM Logo Top Right";
|
||||
logo_top_right.value = 0x00;
|
||||
leds.push_back(logo_top_right);
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Left Buttons zone (4 LEDs: buttons 5-8) |
|
||||
\*---------------------------------------------------------*/
|
||||
zone left_buttons_zone;
|
||||
left_buttons_zone.name = "Left Buttons";
|
||||
left_buttons_zone.type = ZONE_TYPE_MATRIX;
|
||||
left_buttons_zone.leds_min = 4;
|
||||
left_buttons_zone.leds_max = 4;
|
||||
left_buttons_zone.leds_count = 4;
|
||||
left_buttons_zone.matrix_map = new matrix_map_type;
|
||||
left_buttons_zone.matrix_map->height = 2;
|
||||
left_buttons_zone.matrix_map->width = 2;
|
||||
left_buttons_zone.matrix_map->map = (unsigned int *)&left_buttons_matrix_map;
|
||||
zones.push_back(left_buttons_zone);
|
||||
|
||||
led btn5;
|
||||
btn5.name = "Button 5";
|
||||
btn5.value = 0x11;
|
||||
leds.push_back(btn5);
|
||||
|
||||
led btn6;
|
||||
btn6.name = "Button 6";
|
||||
btn6.value = 0x10;
|
||||
leds.push_back(btn6);
|
||||
|
||||
led btn7;
|
||||
btn7.name = "Button 7";
|
||||
btn7.value = 0x12;
|
||||
leds.push_back(btn7);
|
||||
|
||||
led btn8;
|
||||
btn8.name = "Button 8";
|
||||
btn8.value = 0x13;
|
||||
leds.push_back(btn8);
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Base Ring zone (8 LEDs, clockwise from top) |
|
||||
\*---------------------------------------------------------*/
|
||||
zone ring_zone;
|
||||
ring_zone.name = "Base Ring";
|
||||
ring_zone.type = ZONE_TYPE_MATRIX;
|
||||
ring_zone.leds_min = 8;
|
||||
ring_zone.leds_max = 8;
|
||||
ring_zone.leds_count = 8;
|
||||
ring_zone.matrix_map = new matrix_map_type;
|
||||
ring_zone.matrix_map->height = 3;
|
||||
ring_zone.matrix_map->width = 5;
|
||||
ring_zone.matrix_map->map = (unsigned int *)&ring_matrix_map;
|
||||
zones.push_back(ring_zone);
|
||||
|
||||
led ring_upper;
|
||||
ring_upper.name = "Upper";
|
||||
ring_upper.value = 0x04;
|
||||
leds.push_back(ring_upper);
|
||||
|
||||
led ring_upper_right;
|
||||
ring_upper_right.name = "Upper Right";
|
||||
ring_upper_right.value = 0x05;
|
||||
leds.push_back(ring_upper_right);
|
||||
|
||||
led ring_right;
|
||||
ring_right.name = "Right";
|
||||
ring_right.value = 0x06;
|
||||
leds.push_back(ring_right);
|
||||
|
||||
led ring_bottom_right;
|
||||
ring_bottom_right.name = "Bottom Right";
|
||||
ring_bottom_right.value = 0x0B;
|
||||
leds.push_back(ring_bottom_right);
|
||||
|
||||
led ring_bottom;
|
||||
ring_bottom.name = "Bottom";
|
||||
ring_bottom.value = 0x0C;
|
||||
leds.push_back(ring_bottom);
|
||||
|
||||
led ring_bottom_left;
|
||||
ring_bottom_left.name = "Bottom Left";
|
||||
ring_bottom_left.value = 0x0D;
|
||||
leds.push_back(ring_bottom_left);
|
||||
|
||||
led ring_left;
|
||||
ring_left.name = "Left";
|
||||
ring_left.value = 0x0E;
|
||||
leds.push_back(ring_left);
|
||||
|
||||
led ring_upper_left;
|
||||
ring_upper_left.name = "Upper Left";
|
||||
ring_upper_left.value = 0x0F;
|
||||
leds.push_back(ring_upper_left);
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| Right Buttons zone (4 LEDs: buttons 16-19) |
|
||||
\*---------------------------------------------------------*/
|
||||
zone right_buttons_zone;
|
||||
right_buttons_zone.name = "Right Buttons";
|
||||
right_buttons_zone.type = ZONE_TYPE_MATRIX;
|
||||
right_buttons_zone.leds_min = 4;
|
||||
right_buttons_zone.leds_max = 4;
|
||||
right_buttons_zone.leds_count = 4;
|
||||
right_buttons_zone.matrix_map = new matrix_map_type;
|
||||
right_buttons_zone.matrix_map->height = 2;
|
||||
right_buttons_zone.matrix_map->width = 2;
|
||||
right_buttons_zone.matrix_map->map = (unsigned int *)&right_buttons_matrix_map;
|
||||
zones.push_back(right_buttons_zone);
|
||||
|
||||
led btn17;
|
||||
btn17.name = "Button 17";
|
||||
btn17.value = 0x07;
|
||||
leds.push_back(btn17);
|
||||
|
||||
led btn16;
|
||||
btn16.name = "Button 16";
|
||||
btn16.value = 0x08;
|
||||
leds.push_back(btn16);
|
||||
|
||||
led btn19;
|
||||
btn19.name = "Button 19";
|
||||
btn19.value = 0x0A;
|
||||
leds.push_back(btn19);
|
||||
|
||||
led btn18;
|
||||
btn18.name = "Button 18";
|
||||
btn18.value = 0x09;
|
||||
leds.push_back(btn18);
|
||||
|
||||
SetupColors();
|
||||
}
|
||||
|
||||
void RGBController_ThrustmasterSol::ResizeZone(int /*zone*/, int /*new_size*/)
|
||||
{
|
||||
/*---------------------------------------------------------*\
|
||||
| This device does not support resizing zones |
|
||||
\*---------------------------------------------------------*/
|
||||
}
|
||||
|
||||
void RGBController_ThrustmasterSol::DeviceUpdateLEDs()
|
||||
{
|
||||
unsigned int led_zones[THRUSTMASTER_SOL_R_ZONE_COUNT];
|
||||
RGBColor led_colors[THRUSTMASTER_SOL_R_ZONE_COUNT];
|
||||
|
||||
for(unsigned int led_idx = 0; led_idx < leds.size(); led_idx++)
|
||||
{
|
||||
led_zones[led_idx] = leds[led_idx].value;
|
||||
led_colors[led_idx] = colors[led_idx];
|
||||
}
|
||||
|
||||
controller->SetLEDColors(led_zones, led_colors, static_cast<unsigned int>(leds.size()));
|
||||
}
|
||||
|
||||
void RGBController_ThrustmasterSol::UpdateZoneLEDs(int zone)
|
||||
{
|
||||
unsigned int start_idx = 0;
|
||||
unsigned int zone_size = 0;
|
||||
|
||||
for(unsigned int z_idx = 0; z_idx < zones.size(); z_idx++)
|
||||
{
|
||||
if(z_idx == (unsigned int)zone)
|
||||
{
|
||||
zone_size = zones[z_idx].leds_count;
|
||||
break;
|
||||
}
|
||||
|
||||
start_idx += zones[z_idx].leds_count;
|
||||
}
|
||||
|
||||
unsigned int led_zones[THRUSTMASTER_SOL_R_ZONE_COUNT];
|
||||
RGBColor led_colors[THRUSTMASTER_SOL_R_ZONE_COUNT];
|
||||
|
||||
for(unsigned int led_idx = 0; led_idx < zone_size; led_idx++)
|
||||
{
|
||||
unsigned int current_idx = start_idx + led_idx;
|
||||
led_zones[led_idx] = leds[current_idx].value;
|
||||
led_colors[led_idx] = colors[current_idx];
|
||||
}
|
||||
|
||||
controller->SetLEDColors(led_zones, led_colors, zone_size);
|
||||
}
|
||||
|
||||
void RGBController_ThrustmasterSol::UpdateSingleLED(int led)
|
||||
{
|
||||
controller->SetLEDColor(leds[led].value, colors[led]);
|
||||
}
|
||||
|
||||
void RGBController_ThrustmasterSol::DeviceUpdateMode()
|
||||
{
|
||||
DeviceUpdateLEDs();
|
||||
}
|
||||
|
||||
void RGBController_ThrustmasterSol::DeviceSaveMode()
|
||||
{
|
||||
unsigned int led_zones[THRUSTMASTER_SOL_R_ZONE_COUNT];
|
||||
RGBColor led_colors[THRUSTMASTER_SOL_R_ZONE_COUNT];
|
||||
|
||||
for(unsigned int led_idx = 0; led_idx < leds.size(); led_idx++)
|
||||
{
|
||||
led_zones[led_idx] = leds[led_idx].value;
|
||||
led_colors[led_idx] = colors[led_idx];
|
||||
}
|
||||
|
||||
controller->SaveColors(led_zones, led_colors, static_cast<unsigned int>(leds.size()));
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| RGBController_ThrustmasterSol.h |
|
||||
| |
|
||||
| RGBController 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 |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RGBController.h"
|
||||
#include "ThrustmasterSolController.h"
|
||||
|
||||
class RGBController_ThrustmasterSol : public RGBController
|
||||
{
|
||||
public:
|
||||
RGBController_ThrustmasterSol(ThrustmasterSolController* controller_ptr);
|
||||
~RGBController_ThrustmasterSol();
|
||||
|
||||
void SetupZones();
|
||||
void ResizeZone(int zone, int new_size);
|
||||
|
||||
void DeviceUpdateLEDs();
|
||||
void UpdateZoneLEDs(int zone);
|
||||
void UpdateSingleLED(int led);
|
||||
|
||||
void DeviceUpdateMode();
|
||||
void DeviceSaveMode();
|
||||
|
||||
private:
|
||||
ThrustmasterSolController* controller;
|
||||
};
|
||||
@@ -0,0 +1,315 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| ThrustmasterSolController.h |
|
||||
| |
|
||||
| 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 |
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "RGBController.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "dependencies/libusb-1.0.27/include/libusb.h"
|
||||
#else
|
||||
#include <libusb.h>
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Thrustmaster vendor ID |
|
||||
\*-----------------------------------------------------*/
|
||||
#define THRUSTMASTER_VID 0x044F
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Thrustmaster Sol series product IDs |
|
||||
\*-----------------------------------------------------*/
|
||||
#define THRUSTMASTER_SOL_R_RIGHT_PID 0x0422
|
||||
#define THRUSTMASTER_SOL_R_LEFT_PID 0x042A
|
||||
#define THRUSTMASTER_SOL_F16_RIGHT_PID 0x0420
|
||||
#define THRUSTMASTER_SOL_F18_RIGHT_PID 0x0421
|
||||
#define THRUSTMASTER_SOL_F16_LEFT_PID 0x0428
|
||||
#define THRUSTMASTER_SOL_F18_LEFT_PID 0x0429
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Thrustmaster Sol USB constants |
|
||||
\*-----------------------------------------------------*/
|
||||
#define THRUSTMASTER_SOL_INTERFACE 1
|
||||
#define THRUSTMASTER_SOL_ENDPOINT_OUT 0x02
|
||||
#define THRUSTMASTER_SOL_ENDPOINT_IN 0x82
|
||||
#define THRUSTMASTER_SOL_PACKET_SIZE 64
|
||||
#define THRUSTMASTER_SOL_MAX_ENTRIES 15
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Thrustmaster Sol report type identifiers |
|
||||
\*-----------------------------------------------------*/
|
||||
#define THRUSTMASTER_SOL_REPORT_BASE_LO 0x01
|
||||
#define THRUSTMASTER_SOL_REPORT_BASE_HI 0x08
|
||||
#define THRUSTMASTER_SOL_REPORT_GRIP_LO 0x01
|
||||
#define THRUSTMASTER_SOL_REPORT_GRIP_HI 0x88
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Thrustmaster Sol persistence flags |
|
||||
\*-----------------------------------------------------*/
|
||||
#define THRUSTMASTER_SOL_VOLATILE 0x00
|
||||
#define THRUSTMASTER_SOL_PERSISTENT 0x80
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Thrustmaster Sol read query report types |
|
||||
\*-----------------------------------------------------*/
|
||||
#define THRUSTMASTER_SOL_READ_BASE_LO 0x02
|
||||
#define THRUSTMASTER_SOL_READ_BASE_HI 0x00
|
||||
#define THRUSTMASTER_SOL_READ_GRIP_LO 0x02
|
||||
#define THRUSTMASTER_SOL_READ_GRIP_HI 0x80
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Thrustmaster Sol zone constants |
|
||||
| |
|
||||
| Zone 0x00 is shared: on the base it is a logo LED, |
|
||||
| on the grip it is the thumbstick LED. The grip flag |
|
||||
| (bit 8) selects the grip report type for zone 0x00. |
|
||||
\*-----------------------------------------------------*/
|
||||
#define THRUSTMASTER_SOL_ZONE_THUMBSTICK 0x00
|
||||
#define THRUSTMASTER_SOL_GRIP_FLAG 0x100
|
||||
#define THRUSTMASTER_SOL_MARKER 0xFF
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Thrustmaster Sol zone count for Sol-R |
|
||||
| 19 base zones + thumbstick + logo zone 0x00 = 21 |
|
||||
\*-----------------------------------------------------*/
|
||||
#define THRUSTMASTER_SOL_R_ZONE_COUNT 21
|
||||
|
||||
class ThrustmasterSolController
|
||||
{
|
||||
public:
|
||||
ThrustmasterSolController(libusb_device_handle* dev_handle,
|
||||
const char* path,
|
||||
unsigned short pid);
|
||||
~ThrustmasterSolController();
|
||||
|
||||
std::string GetDeviceLocation();
|
||||
std::string GetSerialString();
|
||||
unsigned short GetPID();
|
||||
|
||||
void SetLEDColor(unsigned int zone, RGBColor color);
|
||||
void SetLEDColors(unsigned int* zones, RGBColor* colors,
|
||||
unsigned int count);
|
||||
void SaveColors(unsigned int* zones, RGBColor* colors,
|
||||
unsigned int count);
|
||||
void ReadColors(std::vector<unsigned int>& zones,
|
||||
std::vector<RGBColor>& colors);
|
||||
|
||||
private:
|
||||
libusb_device_handle* dev;
|
||||
std::string location;
|
||||
std::string serial;
|
||||
unsigned short pid;
|
||||
|
||||
void SendPacket(unsigned char* packet, unsigned int size);
|
||||
void BuildAndSendPackets(unsigned int* zones, RGBColor* colors,
|
||||
unsigned int count, bool persistent);
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
/*---------------------------------------------------------*\
|
||||
| ThrustmasterSolControllerDetect.cpp |
|
||||
| |
|
||||
| Detector 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 "Detector.h"
|
||||
#include "ThrustmasterSolController.h"
|
||||
#include "RGBController_ThrustmasterSol.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short usb_vid;
|
||||
unsigned short usb_pid;
|
||||
const char* name;
|
||||
} thrustmaster_sol_device;
|
||||
|
||||
#define THRUSTMASTER_SOL_NUM_DEVICES (sizeof(device_list) / sizeof(device_list[0]))
|
||||
|
||||
static const thrustmaster_sol_device device_list[] =
|
||||
{
|
||||
/*-----------------------------------------------------------------------------------------------------*\
|
||||
| Sol-R variants (tested) |
|
||||
\*-----------------------------------------------------------------------------------------------------*/
|
||||
{ THRUSTMASTER_VID, THRUSTMASTER_SOL_R_RIGHT_PID, "Thrustmaster Sol-R Right" },
|
||||
{ THRUSTMASTER_VID, THRUSTMASTER_SOL_R_LEFT_PID, "Thrustmaster Sol-R Left" },
|
||||
/*-----------------------------------------------------------------------------------------------------*\
|
||||
| Sol F16/F18 variants (untested, same base protocol) |
|
||||
\*-----------------------------------------------------------------------------------------------------*/
|
||||
{ THRUSTMASTER_VID, THRUSTMASTER_SOL_F16_RIGHT_PID, "Thrustmaster Sol F16 Right" },
|
||||
{ THRUSTMASTER_VID, THRUSTMASTER_SOL_F18_RIGHT_PID, "Thrustmaster Sol F18 Right" },
|
||||
{ THRUSTMASTER_VID, THRUSTMASTER_SOL_F16_LEFT_PID, "Thrustmaster Sol F16 Left" },
|
||||
{ THRUSTMASTER_VID, THRUSTMASTER_SOL_F18_LEFT_PID, "Thrustmaster Sol F18 Left" },
|
||||
};
|
||||
|
||||
/******************************************************************************************\
|
||||
* *
|
||||
* DetectThrustmasterSolControllers *
|
||||
* *
|
||||
* Detect Thrustmaster Sol series joysticks via libusb. *
|
||||
* LED control is on USB interface 1 (vendor-specific class 255, not HID). *
|
||||
* Interface 0 (joystick HID) is not touched. *
|
||||
* *
|
||||
\******************************************************************************************/
|
||||
|
||||
void DetectThrustmasterSolControllers()
|
||||
{
|
||||
libusb_init(NULL);
|
||||
|
||||
#ifdef _WIN32
|
||||
libusb_set_option(NULL, LIBUSB_OPTION_USE_USBDK);
|
||||
#endif
|
||||
|
||||
libusb_device** devs;
|
||||
ssize_t num_devs = libusb_get_device_list(NULL, &devs);
|
||||
|
||||
if(num_devs <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(ssize_t i = 0; i < num_devs; i++)
|
||||
{
|
||||
libusb_device_descriptor desc;
|
||||
|
||||
if(libusb_get_device_descriptor(devs[i], &desc) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for(std::size_t d = 0; d < THRUSTMASTER_SOL_NUM_DEVICES; d++)
|
||||
{
|
||||
if(desc.idVendor == device_list[d].usb_vid &&
|
||||
desc.idProduct == device_list[d].usb_pid)
|
||||
{
|
||||
libusb_device_handle* handle = NULL;
|
||||
|
||||
if(libusb_open(devs[i], &handle) != LIBUSB_SUCCESS)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
libusb_set_auto_detach_kernel_driver(handle, 1);
|
||||
|
||||
if(libusb_claim_interface(handle, THRUSTMASTER_SOL_INTERFACE) != LIBUSB_SUCCESS)
|
||||
{
|
||||
libusb_close(handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t bus = libusb_get_bus_number(devs[i]);
|
||||
uint8_t address = libusb_get_device_address(devs[i]);
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "%d-%d", bus, address);
|
||||
|
||||
ThrustmasterSolController* controller = new ThrustmasterSolController(handle, path, desc.idProduct);
|
||||
RGBController_ThrustmasterSol* rgb_controller = new RGBController_ThrustmasterSol(controller);
|
||||
|
||||
rgb_controller->name = device_list[d].name;
|
||||
|
||||
ResourceManager::get()->RegisterRGBController(rgb_controller);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_device_list(devs, 1);
|
||||
}
|
||||
|
||||
REGISTER_DETECTOR("Thrustmaster Sol", DetectThrustmasterSolControllers);
|
||||
/*---------------------------------------------------------------------------------------------------------*\
|
||||
| Entries for dynamic UDEV rules |
|
||||
| |
|
||||
| DUMMY_DEVICE_DETECTOR("Thrustmaster Sol", DetectThrustmasterSolControllers, 0x044F, 0x0420 ) |
|
||||
| DUMMY_DEVICE_DETECTOR("Thrustmaster Sol", DetectThrustmasterSolControllers, 0x044F, 0x0421 ) |
|
||||
| DUMMY_DEVICE_DETECTOR("Thrustmaster Sol", DetectThrustmasterSolControllers, 0x044F, 0x0422 ) |
|
||||
| DUMMY_DEVICE_DETECTOR("Thrustmaster Sol", DetectThrustmasterSolControllers, 0x044F, 0x0428 ) |
|
||||
| DUMMY_DEVICE_DETECTOR("Thrustmaster Sol", DetectThrustmasterSolControllers, 0x044F, 0x0429 ) |
|
||||
| DUMMY_DEVICE_DETECTOR("Thrustmaster Sol", DetectThrustmasterSolControllers, 0x044F, 0x042A ) |
|
||||
\*---------------------------------------------------------------------------------------------------------*/
|
||||
Reference in New Issue
Block a user