Added support for the new Steam Controller

This commit is contained in:
Sam Lantinga
2025-11-12 11:32:32 -08:00
parent 04a62cba1f
commit 1998b65045
15 changed files with 970 additions and 126 deletions

View File

@@ -82,16 +82,16 @@
<LibraryPath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(ProjectDir)/../../src;$(IncludePath)</IncludePath>
<IncludePath>$(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IncludePath>$(ProjectDir)/../../src;$(IncludePath)</IncludePath>
<IncludePath>$(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>$(ProjectDir)/../../src;$(IncludePath)</IncludePath>
<IncludePath>$(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>$(ProjectDir)/../../src;$(IncludePath)</IncludePath>
<IncludePath>$(ProjectDir)/../../src;$(ProjectDir)/../../src/core/windows;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<PreBuildEvent>
@@ -625,6 +625,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_stadia.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_hori.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_triton.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steamdeck.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch2.c" />

View File

@@ -1660,6 +1660,10 @@
<ClCompile Include="..\..\src\storage\generic\SDL_genericstorage.c" />
<ClCompile Include="..\..\src\storage\steam\SDL_steamstorage.c" />
<ClCompile Include="..\..\src\storage\SDL_storage.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_steam_triton.c">
<Filter>joystick\hidapi</Filter>
</ClCompile>
</ItemGroup>
<ClCompile Include="..\..\src\events\SDL_eventwatch.c" />
<ClCompile Include="..\..\src\core\windows\pch_cpp.cpp">
<Filter>core\windows</Filter>

View File

@@ -415,6 +415,7 @@
F386F6F92884663E001840AA /* SDL_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = F386F6E62884663E001840AA /* SDL_utils.c */; };
F388C95528B5F6F700661ECF /* SDL_hidapi_ps3.c in Sources */ = {isa = PBXBuildFile; fileRef = F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */; };
F39344CE2E99771B0056986F /* SDL_dlopennote.h in Headers */ = {isa = PBXBuildFile; fileRef = F39344CD2E99771B0056986F /* SDL_dlopennote.h */; settings = {ATTRIBUTES = (Public, ); }; };
F38C72492CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c in Sources */ = {isa = PBXBuildFile; fileRef = F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */; };
F395BF6525633B2400942BFF /* SDL_crc32.c in Sources */ = {isa = PBXBuildFile; fileRef = F395BF6425633B2400942BFF /* SDL_crc32.c */; };
F395C1932569C68F00942BFF /* SDL_iokitjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */; };
F395C19C2569C68F00942BFF /* SDL_iokitjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */; };
@@ -998,6 +999,7 @@
F386F6E62884663E001840AA /* SDL_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_utils.c; sourceTree = "<group>"; };
F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps3.c; sourceTree = "<group>"; };
F39344CD2E99771B0056986F /* SDL_dlopennote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_dlopennote.h; sourceTree = "<group>"; };
F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_steam_triton.c; sourceTree = "<group>"; };
F395BF6425633B2400942BFF /* SDL_crc32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_crc32.c; sourceTree = "<group>"; };
F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_iokitjoystick_c.h; sourceTree = "<group>"; };
F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_iokitjoystick.c; sourceTree = "<group>"; };
@@ -1962,6 +1964,7 @@
F3984CCF25BCC92800374F43 /* SDL_hidapi_stadia.c */,
A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */,
F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */,
F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */,
A797456F2B2E9D39009D224A /* SDL_hidapi_steamdeck.c */,
A7D8A7C623E2513E00DCD162 /* SDL_hidapi_switch.c */,
A7D8A7C623E2513E00DCD163 /* SDL_hidapi_switch2.c */,
@@ -3009,6 +3012,7 @@
F316ABD92B5C3185002EF551 /* SDL_memcpy.c in Sources */,
A7D8B97A23E2514400DCD162 /* SDL_render.c in Sources */,
A7D8ABD323E2514100DCD162 /* SDL_stretch.c in Sources */,
F38C72492CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c in Sources */,
A7D8AC3923E2514100DCD162 /* SDL_blit_copy.c in Sources */,
A7D8B5CF23E2514300DCD162 /* SDL_syspower.m in Sources */,
F3B439512C935C2400792030 /* SDL_dummyprocess.c in Sources */,

View File

@@ -63,6 +63,7 @@
#define VALVE_USB_VID 0x28DE
#define D0G_BLE2_PID 0x1106
#define TRITON_BLE_PID 0x1303
typedef uint32_t uint32;
typedef uint64_t uint64;
@@ -76,7 +77,8 @@ typedef uint64_t uint64;
#define VALVE_SERVICE @"100F6C32-1735-4313-B402-38567131E5F3"
// (READ/NOTIFICATIONS)
#define VALVE_INPUT_CHAR @"100F6C33-1735-4313-B402-38567131E5F3"
#define VALVE_INPUT_CHAR_0x1106 @"100F6C33-1735-4313-B402-38567131E5F3"
#define VALVE_INPUT_CHAR_0x1303 @"100F6C77-1735-4313-B402-38567131E5F3"
//  (READ/WRITE)
#define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3"
@@ -101,21 +103,7 @@ typedef struct
typedef struct {
uint8_t id;
union {
bluetoothSegment segment;
struct {
uint8_t segmentHeader;
uint8_t featureReportMessageID;
uint8_t length;
uint8_t settingIdentifier;
union {
uint16_t usPayload;
uint32_t uPayload;
uint64_t ulPayload;
uint8_t ucPayload[15];
};
};
};
bluetoothSegment segment;
} hidFeatureReport;
#pragma pack(pop)
@@ -125,34 +113,62 @@ size_t GetBluetoothSegmentSize(bluetoothSegment *segment)
return segment->length + 3;
}
#define RingBuffer_cbElem 19
#define RingBuffer_nElem 4096
#define RingBuffer_nElem 256
typedef struct {
int _first, _last;
uint8_t _data[ ( RingBuffer_nElem * RingBuffer_cbElem ) ];
int _cbElem;
uint8_t *_data;
pthread_mutex_t accessLock;
} RingBuffer;
static void RingBuffer_init( RingBuffer *this )
static RingBuffer *RingBuffer_alloc( int cbElem )
{
RingBuffer *this = (RingBuffer *)malloc( sizeof(*this) );
if (!this)
{
return NULL;
}
this->_first = -1;
this->_last = 0;
this->_cbElem = cbElem;
this->_data = (uint8_t *)malloc(RingBuffer_nElem * cbElem);
if ( !this->_data )
{
free( this );
return NULL;
}
pthread_mutex_init( &this->accessLock, 0 );
return this;
}
static void RingBuffer_free( RingBuffer *this )
{
if ( this )
{
free( this->_data );
free( this );
}
}
static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
{
if ( !this )
{
return false;
}
pthread_mutex_lock( &this->accessLock );
memcpy( &this->_data[ this->_last ], src, RingBuffer_cbElem );
memcpy( &this->_data[ this->_last ], src, this->_cbElem );
if ( this->_first == -1 )
{
this->_first = this->_last;
}
this->_last = ( this->_last + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
this->_last = ( this->_last + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
if ( this->_last == this->_first )
{
this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
pthread_mutex_unlock( &this->accessLock );
return false;
}
@@ -162,14 +178,19 @@ static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
{
if ( !this )
{
return false;
}
pthread_mutex_lock( &this->accessLock );
if ( this->_first == -1 )
{
pthread_mutex_unlock( &this->accessLock );
return false;
}
memcpy( dst, &this->_data[ this->_first ], RingBuffer_cbElem );
this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
memcpy( dst, &this->_data[ this->_first ], this->_cbElem );
this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
if ( this->_first == this->_last )
{
this->_first = -1;
@@ -191,12 +212,14 @@ typedef enum
@interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
{
RingBuffer _inputReports;
uint8_t _featureReport[20];
RingBuffer *_inputReports;
NSData *_featureReport;
NSMutableDictionary *_outputReports;
BLEDeviceWaitState _waitStateForReadFeatureReport;
BLEDeviceWaitState _waitStateForWriteFeatureReport;
}
@property (nonatomic, readwrite) uint16_t pid;
@property (nonatomic, readwrite) bool connected;
@property (nonatomic, readwrite) bool ready;
@@ -205,6 +228,7 @@ typedef enum
@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
- (id)initWithPeripheral:(CBPeripheral *)peripheral;
- (void)onDisconnect;
@end
@@ -278,8 +302,7 @@ typedef enum
HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
if ( steamController )
{
steamController.connected = NO;
steamController.ready = NO;
[steamController onDisconnect];
[self.centralManager cancelPeripheralConnection:peripheral];
}
}
@@ -474,8 +497,7 @@ typedef enum
HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
if ( steamController )
{
steamController.connected = NO;
steamController.ready = NO;
[steamController onDisconnect];
[self.deviceMap removeObjectForKey:peripheral];
}
}
@@ -500,12 +522,14 @@ static void process_pending_events(void)
{
if ( self = [super init] )
{
RingBuffer_init( &_inputReports );
self.pid = 0;
_inputReports = NULL;
_outputReports = [[NSMutableDictionary alloc] init];
_connected = NO;
_ready = NO;
self.bleSteamController = nil;
self.bleCharacteristicInput = nil;
self.bleCharacteristicReport = nil;
_connected = NO;
_ready = NO;
}
return self;
}
@@ -514,7 +538,9 @@ static void process_pending_events(void)
{
if ( self = [super init] )
{
RingBuffer_init( &_inputReports );
self.pid = 0;
_inputReports = NULL;
_outputReports = [[NSMutableDictionary alloc] init];
_connected = NO;
_ready = NO;
self.bleSteamController = peripheral;
@@ -528,6 +554,18 @@ static void process_pending_events(void)
return self;
}
- (void)onDisconnect
{
self.connected = NO;
self.ready = NO;
if ( _inputReports )
{
RingBuffer_free( _inputReports );
_inputReports = NULL;
}
}
- (void)setConnected:(bool)connected
{
_connected = connected;
@@ -543,94 +581,134 @@ static void process_pending_events(void)
- (size_t)read_input_report:(uint8_t *)dst
{
if ( RingBuffer_read( &_inputReports, dst+1 ) )
if ( RingBuffer_read( _inputReports, dst+1 ) )
{
*dst = 0x03;
return 20;
switch ( self.pid )
{
case D0G_BLE2_PID:
*dst = 0x03;
break;
case TRITON_BLE_PID:
*dst = 0x42;
break;
default:
abort();
}
return _inputReports->_cbElem + 1;
}
return 0;
}
- (int)send_report:(const uint8_t *)data length:(size_t)length
{
if ( self.pid == D0G_BLE2_PID )
{
[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
return (int)length;
}
- (int)send_feature_report:(hidFeatureReport *)report
// We need to look up the correct characteristic for this output report
if ( length > 0 )
{
CBCharacteristic *aChar = [_outputReports objectForKey:[NSNumber numberWithInt:data[0]]];
if ( aChar != nil )
{
[_bleSteamController writeValue:[NSData dataWithBytes:&data[1] length:(length - 1)] forCharacteristic:aChar type:CBCharacteristicWriteWithResponse];
return (int)length;
}
}
return -1;
}
- (int)send_feature_report:(hidFeatureReport *)report length:(size_t)length
{
#if FEATURE_REPORT_LOGGING
uint8_t *reportBytes = (uint8_t *)report;
NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", GetBluetoothSegmentSize( report->segment ),
NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", length,
reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
reportBytes[19] );
#endif
int sendSize = (int)GetBluetoothSegmentSize( &report->segment );
if ( sendSize > 20 )
sendSize = 20;
#if 1
// fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
// except errors.
[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64)] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
// pretend we received a result anybody cares about
return 19;
return (int)length;
#else
// this is technically the correct send_feature_report logic if you want to make sure it gets through and is
// acknowledged or errors out
_waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting;
[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64)
] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
while ( _connected && _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
{
process_pending_events();
}
if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
if ( !_connected || _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
{
_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
return -1;
}
_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
return 19;
return (int)length;
#endif
}
- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer length:(size_t)length
{
_waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
[_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
process_pending_events();
while ( _connected && _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
{
process_pending_events();
}
if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
if ( !_connected || _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
{
_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
return -1;
}
memcpy( buffer, _featureReport, sizeof(_featureReport) );
int amount = 0;
if ( _featureReport.length > 0 )
{
uint8_t *data = (uint8_t *)_featureReport.bytes;
if ( *data == *buffer )
{
amount = (int)MIN( length, _featureReport.length );
memcpy( buffer, _featureReport.bytes, amount );
}
else
{
// Leave the report in the buffer
amount = (int)MIN( length - 1, _featureReport.length );
memcpy( &buffer[ 1 ], _featureReport.bytes, amount );
++amount;
}
}
_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
#if FEATURE_REPORT_LOGGING
NSLog( @"HIDBLE:get_feature_report (19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]",
buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
buffer[19] );
NSLog( @"HIDBLE:get_feature_report (%lu/%zu) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]",
_featureReport.length, length,
buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
buffer[19] );
#endif
return 19;
return amount;
}
#pragma mark CBPeripheralDelegate Implementation
@@ -667,8 +745,14 @@ static void process_pending_events(void)
{
NSLog( @"Found Characteristic %@", aChar );
if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] )
if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1106]] )
{
self.pid = D0G_BLE2_PID;
self.bleCharacteristicInput = aChar;
}
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303]] )
{
self.pid = TRITON_BLE_PID;
self.bleCharacteristicInput = aChar;
}
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
@@ -676,6 +760,21 @@ static void process_pending_events(void)
self.bleCharacteristicReport = aChar;
[self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
}
else
{
NSString *UUIDString = [aChar.UUID UUIDString];
int report_id = 0;
if ( sscanf( UUIDString.UTF8String, "100F6C%x", &report_id ) == 1 && report_id > 0x35 )
{
report_id -= 0x35;
//NSLog( @"Found characteristic for output report 0x%.2x", report_id );
if (report_id >= 0x80) {
// An output report
[_outputReports setObject:aChar forKey:[NSNumber numberWithInt:report_id]];
}
}
}
}
}
}
@@ -690,17 +789,33 @@ static void process_pending_events(void)
if ( self.ready == NO )
{
self.ready = YES;
if ( _inputReports == NULL )
{
int cbElem = 0;
switch ( self.pid )
{
case D0G_BLE2_PID:
cbElem = 19;
break;
case TRITON_BLE_PID:
cbElem = 53;
break;
default:
abort();
}
_inputReports = RingBuffer_alloc( cbElem );
}
HIDBLEManager.sharedInstance.nPendingPairs -= 1;
}
if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
{
NSData *data = [characteristic value];
if ( data.length != 19 )
if ( _inputReports && data.length != _inputReports->_cbElem )
{
NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly %d", (unsigned long)data.length, _inputReports->_cbElem );
}
if ( !RingBuffer_write( &_inputReports, (const uint8_t *)data.bytes ) )
if ( !RingBuffer_write( _inputReports, (const uint8_t *)data.bytes ) )
{
uint64_t ticksNow = mach_approximate_time();
if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
@@ -712,8 +827,6 @@ static void process_pending_events(void)
}
else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
{
memset( _featureReport, 0, sizeof(_featureReport) );
if ( error != nil )
{
NSLog( @"HIDBLE: get_feature_report error: %@", error );
@@ -721,12 +834,7 @@ static void process_pending_events(void)
}
else
{
NSData *data = [characteristic value];
if ( data.length != 20 )
{
NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
}
memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
_featureReport = [characteristic value];
_waitStateForReadFeatureReport = BLEDeviceWaitState_Complete;
}
}
@@ -850,7 +958,7 @@ static struct hid_device_info *create_device_info_for_hid_device(HIDBLEDevice *d
memset( device_info, 0, sizeof(struct hid_device_info) );
device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
device_info->vendor_id = VALVE_USB_VID;
device_info->product_id = D0G_BLE2_PID;
device_info->product_id = device.pid;
device_info->product_string = wcsdup( L"Steam Controller" );
device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
device_info->bus_type = HID_API_BUS_BLUETOOTH;
@@ -861,14 +969,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
{ @autoreleasepool {
struct hid_device_info *root = NULL;
/* See if there are any devices we should skip in enumeration */
if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, D0G_BLE2_PID, 0, 0)) {
return NULL;
}
if ( ( vendor_id == 0 || vendor_id == VALVE_USB_VID ) &&
( product_id == 0 || product_id == D0G_BLE2_PID ) )
{
HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
[bleManager updateConnectedSteamControllers:false];
NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
@@ -891,11 +991,22 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
}
continue;
}
if ( ( vendor_id != 0 && vendor_id != VALVE_USB_VID ) ||
( product_id != 0 && product_id != device.pid ) )
{
continue;
}
/* See if there are any devices we should skip in enumeration */
if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, device.pid, 0, 0)) {
continue;
}
struct hid_device_info *device_info = create_device_info_for_hid_device(device);
device_info->next = root;
root = device_info;
}
}
return root;
}}
@@ -975,7 +1086,7 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char
if ( !device_handle.connected )
return -1;
return [device_handle send_feature_report:(hidFeatureReport *)(void *)data];
return [device_handle send_feature_report:(hidFeatureReport *)(void *)data length:length];
}
int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
@@ -985,7 +1096,7 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data,
if ( !device_handle.connected )
return -1;
size_t written = [device_handle get_feature_report:data[0] into:data];
size_t written = [device_handle get_feature_report:data[0] into:data length:length];
return written == length-1 ? (int)length : (int)written;
}
@@ -1018,7 +1129,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
NSLog( @"hid_read_timeout with non-zero wait" );
}
int result = (int)[device_handle read_input_report:data];
#if FEATURE_REPORT_LOGGING
#if 0 //FEATURE_REPORT_LOGGING
NSLog( @"HIDBLE:hid_read_timeout (%d) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", result,
data[1], data[2], data[3], data[4], data[5], data[6],
data[7], data[8], data[9], data[10], data[11], data[12],

View File

@@ -1252,6 +1252,9 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
if (SDL_IsJoystickSteamController(vendor, product)) {
// Steam controllers have 2 back paddle buttons
SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,", sizeof(mapping_string));
} else if (SDL_IsJoystickSteamTriton(vendor, product)) {
// Steam controllers have 2 back paddle buttons
SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15", sizeof(mapping_string));
} else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) ||
SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) {
// Nintendo Switch Pro controllers have a screenshot button

View File

@@ -3295,6 +3295,12 @@ bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id)
return eType == k_eControllerType_SteamControllerNeptune;
}
bool SDL_IsJoystickSteamTriton(Uint16 vendor_id, Uint16 product_id)
{
EControllerType eType = GuessControllerType(vendor_id, product_id);
return eType == k_eControllerType_SteamControllerTriton;
}
bool SDL_IsJoystickXInput(SDL_GUID guid)
{
return (guid.data[14] == 'x') ? true : false;

View File

@@ -144,6 +144,9 @@ extern bool SDL_IsJoystickFlydigiController(Uint16 vendor_id, Uint16 product_id)
// Function to return whether a joystick is a Steam Deck
extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a Steam Triton
extern bool SDL_IsJoystickSteamTriton(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick guid comes from the XInput driver
extern bool SDL_IsJoystickXInput(SDL_GUID guid);

View File

@@ -588,9 +588,9 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa715 ), k_eControllerType_SwitchInputOnlyController, NULL }, // Power A Fusion Wireless Arcade Stick (USB Mode) Over BT is shows up as 057e 2009
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa716 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Pro Controller - USB requires toggling switch on back of device
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa718 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Nano Wired Controller
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ??
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0001 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Black
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0002 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch ??
{ MAKE_CONTROLLER_ID( 0x33dd, 0x0003 ), k_eControllerType_SwitchInputOnlyController, NULL }, // ZUIKI MasCon for Nintendo Switch Red
// Valve products
{ MAKE_CONTROLLER_ID( 0x0000, 0x11fb ), k_eControllerType_MobileTouch, NULL }, // Streaming mobile touch virtual controls
@@ -603,4 +603,8 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x28de, 0x1201 ), k_eControllerType_SteamControllerV2, NULL }, // Valve wired Steam Controller (HEADCRAB)
{ MAKE_CONTROLLER_ID( 0x28de, 0x1202 ), k_eControllerType_SteamControllerV2, NULL }, // Valve Bluetooth Steam Controller (HEADCRAB)
{ MAKE_CONTROLLER_ID( 0x28de, 0x1205 ), k_eControllerType_SteamControllerNeptune, NULL }, // Valve Steam Deck Builtin Controller
{ MAKE_CONTROLLER_ID( 0x28de, 0x1302 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Triton Controller
{ MAKE_CONTROLLER_ID( 0x28de, 0x1303 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Triton Controller (BLE)
{ MAKE_CONTROLLER_ID( 0x28de, 0x1304 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Proteus Dongle (Proprietary)
{ MAKE_CONTROLLER_ID( 0x28de, 0x1305 ), k_eControllerType_SteamControllerTriton, NULL }, // Valve Steam Nereid Dongle (Proprietary)
};

View File

@@ -39,6 +39,8 @@ typedef enum
k_eControllerType_SteamControllerV2 = 3,
k_eControllerType_SteamControllerNeptune = 4,
k_eControllerType_SteamControllerTriton = 10,
// Other Controllers
k_eControllerType_UnknownNonSteamController = 30,
k_eControllerType_XBox360Controller = 31,

View File

@@ -0,0 +1,532 @@
/*
Simple DirectMedia Layer
Copyright (C) 2023 Max Maisel <max.maisel@posteo.de>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_JOYSTICK_HIDAPI
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#ifdef SDL_JOYSTICK_HIDAPI_STEAM_TRITON
/*****************************************************************************************************/
#include "steam/controller_constants.h"
#include "steam/controller_structs.h"
// Always 1kHz according to USB descriptor, but actually about 4 ms.
#define TRITON_SENSOR_UPDATE_INTERVAL_US 4032
enum
{
SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM = 11,
SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
SDL_GAMEPAD_NUM_TRITON_BUTTONS,
};
typedef enum
{
TRITON_LBUTTON_A = 0x00000001,
TRITON_LBUTTON_B = 0x00000002,
TRITON_LBUTTON_X = 0x00000004,
TRITON_LBUTTON_Y = 0x00000008,
TRITON_HBUTTON_QAM = 0x00000010,
TRITON_LBUTTON_R3 = 0x00000020,
TRITON_LBUTTON_VIEW = 0x00000040,
TRITON_HBUTTON_R4 = 0x00000080,
TRITON_LBUTTON_R5 = 0x00000100,
TRITON_LBUTTON_R = 0x00000200,
TRITON_LBUTTON_DPAD_DOWN = 0x00000400,
TRITON_LBUTTON_DPAD_RIGHT = 0x00000800,
TRITON_LBUTTON_DPAD_LEFT = 0x00001000,
TRITON_LBUTTON_DPAD_UP = 0x00002000,
TRITON_LBUTTON_MENU = 0x00004000,
TRITON_LBUTTON_L3 = 0x00008000,
TRITON_LBUTTON_STEAM = 0x00010000,
TRITON_HBUTTON_L4 = 0x00020000,
TRITON_LBUTTON_L5 = 0x00040000,
TRITON_LBUTTON_L = 0x00080000,
/*
STEAM_RIGHTSTICK_FINGERDOWN_MASK, // Right Stick Touch 0x00100000
STEAM_RIGHTPAD_FINGERDOWN_MASK, // Right Pad Touch 0x00200000
STEAM_BUTTON_RIGHTPAD_CLICKED_MASK, // Right Pressure Click 0x00400000
STEAM_RIGHT_TRIGGER_MASK, // Right Trigger Click 0x00800000
STEAM_LEFTSTICK_FINGERDOWN_MASK, // Left Stick Touch 0x01000000
STEAM_LEFTPAD_FINGERDOWN_MASK, // Left Pad Touch 0x02000000
STEAM_BUTTON_LEFTPAD_CLICKED_MASK, // Left Pressure Click 0x04000000
STEAM_LEFT_TRIGGER_MASK, // Left Trigger Click 0x08000000
STEAM_RIGHT_AUX_MASK, // Right Pinky Touch 0x10000000
STEAM_LEFT_AUX_MASK, // Left Pinky Touch 0x20000000
*/
} TritonButtons;
typedef struct
{
bool connected;
bool report_sensors;
Uint32 last_sensor_tick;
Uint64 sensor_timestamp_ns;
Uint64 last_button_state;
Uint64 last_lizard_update;
} SDL_DriverSteamTriton_Context;
static bool IsProteusDongle(Uint16 product_id)
{
return (product_id == USB_PRODUCT_VALVE_STEAM_PROTEUS_DONGLE ||
product_id == USB_PRODUCT_VALVE_STEAM_NEREID_DONGLE);
}
static bool DisableSteamTritonLizardMode(SDL_hid_device *dev)
{
int rc;
Uint8 buffer[HID_FEATURE_REPORT_BYTES] = { 1 };
FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
msg->header.type = ID_SET_SETTINGS_VALUES;
msg->header.length = 1 * sizeof(ControllerSetting);
msg->payload.setSettingsValues.settings[0].settingNum = SETTING_LIZARD_MODE;
msg->payload.setSettingsValues.settings[0].settingValue = LIZARD_MODE_OFF;
rc = SDL_hid_send_feature_report(dev, buffer, sizeof(buffer));
if (rc != sizeof(buffer)) {
return false;
}
return true;
}
static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
SDL_Joystick *joystick,
TritonMTUFull_t *pTritonReport)
{
float values[3];
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
Uint64 timestamp = SDL_GetTicksNS();
if (pTritonReport->uButtons != ctx->last_button_state) {
Uint8 hat = 0;
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
((pTritonReport->uButtons & TRITON_LBUTTON_A) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
((pTritonReport->uButtons & TRITON_LBUTTON_B) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
((pTritonReport->uButtons & TRITON_LBUTTON_X) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
((pTritonReport->uButtons & TRITON_LBUTTON_Y) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
((pTritonReport->uButtons & TRITON_LBUTTON_L) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
((pTritonReport->uButtons & TRITON_LBUTTON_R) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
((pTritonReport->uButtons & TRITON_LBUTTON_MENU) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
((pTritonReport->uButtons & TRITON_LBUTTON_VIEW) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
((pTritonReport->uButtons & TRITON_LBUTTON_STEAM) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_QAM,
((pTritonReport->uButtons & TRITON_HBUTTON_QAM) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
((pTritonReport->uButtons & TRITON_LBUTTON_L3) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
((pTritonReport->uButtons & TRITON_LBUTTON_R3) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE1,
((pTritonReport->uButtons & TRITON_HBUTTON_R4) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE1,
((pTritonReport->uButtons & TRITON_HBUTTON_L4) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_PADDLE2,
((pTritonReport->uButtons & TRITON_LBUTTON_R5) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
((pTritonReport->uButtons & TRITON_LBUTTON_L5) != 0));
if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_UP) {
hat |= SDL_HAT_UP;
}
if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_DOWN) {
hat |= SDL_HAT_DOWN;
}
if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_LEFT) {
hat |= SDL_HAT_LEFT;
}
if (pTritonReport->uButtons & TRITON_LBUTTON_DPAD_RIGHT) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
ctx->last_button_state = pTritonReport->uButtons;
}
// RKRK There're button bits for this if you so choose.
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
(int)pTritonReport->sTriggerLeft * 2 - 32768);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
(int)pTritonReport->sTriggerRight * 2 - 32768);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX,
pTritonReport->sLeftStickX);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY,
-pTritonReport->sLeftStickY);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX,
pTritonReport->sRightStickX);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,
-pTritonReport->sRightStickY);
if (ctx->report_sensors && pTritonReport->imu.uTimestamp != ctx->last_sensor_tick) {
Uint32 delta_us = (pTritonReport->imu.uTimestamp - ctx->last_sensor_tick);
ctx->sensor_timestamp_ns += SDL_US_TO_NS(delta_us);
values[0] = (pTritonReport->imu.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
values[1] = (pTritonReport->imu.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
values[2] = (-pTritonReport->imu.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_ns, values, 3);
values[0] = (pTritonReport->imu.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[1] = (pTritonReport->imu.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[2] = (-pTritonReport->imu.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_ns, values, 3);
ctx->last_sensor_tick = pTritonReport->imu.uTimestamp;
}
}
static void HIDAPI_DriverSteamTriton_HandleBatteryStatus(SDL_HIDAPI_Device *device,
SDL_Joystick *joystick,
TritonBatteryStatus_t *pTritonBatteryStatus)
{
SDL_PowerState state;
if (device->is_bluetooth) {
state = SDL_POWERSTATE_ON_BATTERY;
} else if (IsProteusDongle(device->product_id)) {
state = SDL_POWERSTATE_ON_BATTERY;
} else if (pTritonBatteryStatus->ucBatteryLevel == 100) {
state = SDL_POWERSTATE_CHARGED;
} else {
state = SDL_POWERSTATE_CHARGING;
}
SDL_SendJoystickPowerInfo(joystick, state, pTritonBatteryStatus->ucBatteryLevel);
}
static bool HIDAPI_DriverSteamTriton_SetControllerConnected(SDL_HIDAPI_Device *device, bool connected)
{
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
if (ctx->connected != connected) {
if (connected) {
SDL_JoystickID joystickID;
if (!HIDAPI_JoystickConnected(device, &joystickID)) {
return false;
}
} else {
if (device->num_joysticks > 0) {
HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
}
}
ctx->connected = connected;
}
return true;
}
static void HIDAPI_DriverSteamTriton_HandleWirelessStatus(SDL_HIDAPI_Device *device,
TritonWirelessStatus_t *pTritonWirelessStatus)
{
switch (pTritonWirelessStatus->state) {
case k_ETritonWirelessStateConnect:
HIDAPI_DriverSteamTriton_SetControllerConnected(device, true);
break;
case k_ETritonWirelessStateDisconnect:
HIDAPI_DriverSteamTriton_SetControllerConnected(device, false);
break;
default:
break;
}
}
/*****************************************************************************************************/
static void HIDAPI_DriverSteamTriton_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
}
static void HIDAPI_DriverSteamTriton_UnregisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_STEAM, callback, userdata);
}
static bool HIDAPI_DriverSteamTriton_IsEnabled(void)
{
return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
}
static bool HIDAPI_DriverSteamTriton_IsSupportedDevice(
SDL_HIDAPI_Device *device,
const char *name,
SDL_GamepadType type,
Uint16 vendor_id,
Uint16 product_id,
Uint16 version,
int interface_number,
int interface_class,
int interface_subclass,
int interface_protocol)
{
if (IsProteusDongle(product_id)) {
if (interface_number >= 2 && interface_number <= 5) {
// The set of controller interfaces for Proteus & Nereid...currently
return true;
}
} else if (SDL_IsJoystickSteamTriton(vendor_id, product_id)) {
return true;
}
return false;
}
static bool HIDAPI_DriverSteamTriton_InitDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverSteamTriton_Context *ctx;
ctx = (SDL_DriverSteamTriton_Context *)SDL_calloc(1, sizeof(*ctx));
if (ctx == NULL) {
return false;
}
device->context = ctx;
HIDAPI_SetDeviceName(device, "Steam Controller");
if (IsProteusDongle(device->product_id)) {
return true;
}
// Wired controller, connected!
return HIDAPI_DriverSteamTriton_SetControllerConnected(device, true);
}
static int HIDAPI_DriverSteamTriton_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
{
return -1;
}
static void HIDAPI_DriverSteamTriton_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
{
}
static bool HIDAPI_DriverSteamTriton_UpdateDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
SDL_Joystick *joystick = NULL;
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
}
if (ctx->connected && joystick) {
Uint64 now = SDL_GetTicks();
if (!ctx->last_lizard_update || (now - ctx->last_lizard_update) >= 3000) {
DisableSteamTritonLizardMode(device->dev);
ctx->last_lizard_update = now;
}
}
for (;;) {
uint8_t data[64];
int r = SDL_hid_read(device->dev, data, sizeof(data));
if (r == 0) {
return true;
}
if (r < 0) {
// Failed to read from controller
HIDAPI_DriverSteamTriton_SetControllerConnected(device, false);
return false;
}
switch (data[0]) {
case ID_TRITON_CONTROLLER_STATE:
if (!joystick) {
HIDAPI_DriverSteamTriton_SetControllerConnected(device, true);
if (device->num_joysticks > 0) {
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
}
}
if (joystick && r >= (1 + sizeof(TritonMTUFull_t))) {
TritonMTUFull_t *pTritonReport = (TritonMTUFull_t *)&data[1];
HIDAPI_DriverSteamTriton_HandleState(device, joystick, pTritonReport);
}
break;
case ID_TRITON_BATTERY_STATUS:
if (joystick && r >= (1 + sizeof(TritonBatteryStatus_t))) {
TritonBatteryStatus_t *pTritonBatteryStatus = (TritonBatteryStatus_t *)&data[1];
HIDAPI_DriverSteamTriton_HandleBatteryStatus(device, joystick, pTritonBatteryStatus);
}
break;
case ID_TRITON_WIRELESS_STATUS_X:
case ID_TRITON_WIRELESS_STATUS:
if (r >= (1 + sizeof(TritonWirelessStatus_t))) {
TritonWirelessStatus_t *pTritonWirelessStatus = (TritonWirelessStatus_t *)&data[1];
HIDAPI_DriverSteamTriton_HandleWirelessStatus(device, pTritonWirelessStatus);
}
break;
default:
break;
}
}
}
static bool HIDAPI_DriverSteamTriton_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
float update_rate_in_hz = 1000000.0f / TRITON_SENSOR_UPDATE_INTERVAL_US;
SDL_AssertJoysticksLocked();
// Initialize the joystick capabilities
joystick->nbuttons = SDL_GAMEPAD_NUM_TRITON_BUTTONS;
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, update_rate_in_hz);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, update_rate_in_hz);
return true;
}
static bool HIDAPI_DriverSteamTriton_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
int rc;
//RKRK Not sure about size. Probalby 64+1 is OK for ORs
Uint8 buffer[HID_RUMBLE_OUTPUT_REPORT_BYTES];
OutputReportMsg *msg = (OutputReportMsg *)(buffer);
msg->report_id = ID_OUT_REPORT_HAPTIC_RUMBLE;
msg->payload.hapticRumble.type = 0;
msg->payload.hapticRumble.intensity = 0;
msg->payload.hapticRumble.left.speed = low_frequency_rumble;
msg->payload.hapticRumble.left.gain = 0;
msg->payload.hapticRumble.right.speed = high_frequency_rumble;
msg->payload.hapticRumble.right.gain = 0;
rc = SDL_hid_write(device->dev, buffer, sizeof(buffer));
if (rc != sizeof(buffer)) {
return false;
}
return true;
}
static bool HIDAPI_DriverSteamTriton_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 HIDAPI_DriverSteamTriton_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
return SDL_JOYSTICK_CAP_RUMBLE;
}
static bool HIDAPI_DriverSteamTriton_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverSteamTriton_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static bool HIDAPI_DriverSteamTriton_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
{
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
int rc;
Uint8 buffer[HID_FEATURE_REPORT_BYTES] = { 1 };
FeatureReportMsg *msg = (FeatureReportMsg *)(buffer + 1);
msg->header.type = ID_SET_SETTINGS_VALUES;
msg->header.length = 1 * sizeof(ControllerSetting);
msg->payload.setSettingsValues.settings[0].settingNum = SETTING_IMU_MODE;
if (enabled) {
msg->payload.setSettingsValues.settings[0].settingValue = (SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO);
} else {
msg->payload.setSettingsValues.settings[0].settingValue = SETTING_GYRO_MODE_OFF;
}
rc = SDL_hid_send_feature_report(device->dev, buffer, sizeof(buffer));
if (rc != sizeof(buffer)) {
return false;
}
ctx->report_sensors = enabled;
return true;
}
static void HIDAPI_DriverSteamTriton_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
{
// Lizard mode id automatically re-enabled by watchdog. Nothing to do here.
}
static void HIDAPI_DriverSteamTriton_FreeDevice(SDL_HIDAPI_Device *device)
{
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamTriton = {
SDL_HINT_JOYSTICK_HIDAPI_STEAM,
true,
HIDAPI_DriverSteamTriton_RegisterHints,
HIDAPI_DriverSteamTriton_UnregisterHints,
HIDAPI_DriverSteamTriton_IsEnabled,
HIDAPI_DriverSteamTriton_IsSupportedDevice,
HIDAPI_DriverSteamTriton_InitDevice,
HIDAPI_DriverSteamTriton_GetDevicePlayerIndex,
HIDAPI_DriverSteamTriton_SetDevicePlayerIndex,
HIDAPI_DriverSteamTriton_UpdateDevice,
HIDAPI_DriverSteamTriton_OpenJoystick,
HIDAPI_DriverSteamTriton_RumbleJoystick,
HIDAPI_DriverSteamTriton_RumbleJoystickTriggers,
HIDAPI_DriverSteamTriton_GetJoystickCapabilities,
HIDAPI_DriverSteamTriton_SetJoystickLED,
HIDAPI_DriverSteamTriton_SendJoystickEffect,
HIDAPI_DriverSteamTriton_SetSensorsEnabled,
HIDAPI_DriverSteamTriton_CloseJoystick,
HIDAPI_DriverSteamTriton_FreeDevice,
};
#endif // SDL_JOYSTICK_HIDAPI_STEAM_TRITON
#endif // SDL_JOYSTICK_HIDAPI

View File

@@ -70,6 +70,9 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = {
#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
&SDL_HIDAPI_DriverSteamDeck,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_STEAMDECK
&SDL_HIDAPI_DriverSteamTriton,
#endif
#ifdef SDL_JOYSTICK_HIDAPI_SWITCH
&SDL_HIDAPI_DriverNintendoClassic,
&SDL_HIDAPI_DriverJoyCons,

View File

@@ -43,6 +43,7 @@
#define SDL_JOYSTICK_HIDAPI_XBOXONE
#define SDL_JOYSTICK_HIDAPI_SHIELD
#define SDL_JOYSTICK_HIDAPI_STEAM_HORI
#define SDL_JOYSTICK_HIDAPI_STEAM_TRITON
#define SDL_JOYSTICK_HIDAPI_LG4FF
#define SDL_JOYSTICK_HIDAPI_8BITDO
#define SDL_JOYSTICK_HIDAPI_FLYDIGI
@@ -168,6 +169,7 @@ extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamHori;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteamTriton;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLg4ff;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverFlydigi;

View File

@@ -411,6 +411,12 @@ typedef enum {
TRACKPAD_NUM_MODES
} TrackpadDPadMode;
typedef enum
{
LIZARD_MODE_OFF,
LIZARD_MODE_ON,
} LizardModeState_t;
// Read-write controller settings (only add to this enum and never change the order)
typedef enum
{
@@ -423,7 +429,7 @@ typedef enum
SETTING_USB_DEBUG_MODE,
SETTING_LEFT_TRACKPAD_MODE,
SETTING_RIGHT_TRACKPAD_MODE,
SETTING_MOUSE_POINTER_ENABLED,
SETTING_LIZARD_MODE,
// 10
SETTING_DPAD_DEADZONE,

View File

@@ -154,6 +154,100 @@ typedef struct
} FeatureReportMsg;
// Triton and derivatives utilize output reports for haptic commands. This is a
// snapshot from Nov 2024 -- things may change.
// Triton Output Report Lengths #defs include +1 for the OR ID
// Output Report Haptic Messages for Triton
typedef struct
{
uint8_t type;
uint16_t intensity;
struct
{
uint16_t speed;
int8_t gain;
} left, right;
} MsgHapticRumble;
#define HID_RUMBLE_OUTPUT_REPORT_BYTES 10
typedef struct
{
uint8_t side;
uint16_t on_us;
uint16_t off_us;
uint16_t repeat_count;
uint16_t gain_db;
} MsgHapticPulse;
#define HID_HAPTIC_PULSE_OUTPUT_REPORT_BYTES 10
typedef struct
{
uint8_t side;
uint8_t command;
int8_t gain_db;
} MsgHapticCommand;
#define HID_HAPTIC_COMMAND_REPORT_BYTES 4
typedef struct
{
uint8_t side;
int8_t gain_db;
uint16_t frequency;
uint16_t duration_ms;
uint16_t lfo_freq;
uint8_t lfo_depth;
} MsgHapticLfoTone;
#define HID_HAPTIC_LFO_TONE_REPORT_BYTES 10
typedef struct
{
uint8_t side;
int8_t gain_db;
uint16_t duration_ms;
struct
{
uint16_t frequency;
} start, end;
} MsgHapticLogSweep;
#define HID_HAPTIC_LOG_SWEEP_REPORT_BYTES 9
typedef struct m
{
uint8_t side;
uint8_t script_id;
int8_t gain_db;
} MsgHapticScript;
#define HID_HAPTIC_SCRIPT_REPORT_BYTES 4
typedef enum
{
ID_OUT_REPORT_HAPTIC_RUMBLE = 0x80,
ID_OUT_REPORT_HAPTIC_PULSE = 0x81,
ID_OUT_REPORT_HAPTIC_COMMAND = 0x82,
ID_OUT_REPORT_HAPTIC_LFO_TONE = 0x83,
ID_OUT_REPORT_HAPTIC_LOG_SWEEP = 0x85,
ID_OUT_REPORT_HAPTIC_SCRIPT = 0x86,
} ValveTritonOutReportMessageIDs;
typedef struct
{
uint8_t report_id;
union
{
MsgHapticRumble hapticRumble;
MsgHapticPulse hapticPulse;
MsgHapticCommand hapticCommand;
MsgHapticLfoTone hapticLfoTone;
MsgHapticLogSweep hapticLogSweep;
MsgHapticScript hapticScript;
} payload;
} OutputReportMsg;
// Roll this version forward anytime that you are breaking compatibility of existing
// message types within ValveInReport_t or the header itself. Hopefully this should
// be super rare and instead you should just add new message payloads to the union,
@@ -413,51 +507,118 @@ typedef struct
unsigned short sPressurePadRight;
} SteamDeckStatePacket_t;
typedef struct
{
ValveInReportHeader_t header;
union
{
ValveControllerStatePacket_t controllerState;
ValveControllerBLEStatePacket_t controllerBLEState;
ValveControllerDebugPacket_t debugState;
ValveControllerTrackpadImage_t padImage;
ValveControllerRawTrackpadImage_t rawPadImage;
SteamControllerWirelessEvent_t wirelessEvent;
SteamControllerStatusEvent_t statusEvent;
SteamDeckStatePacket_t deckState;
} payload;
ValveInReportHeader_t header;
union
{
ValveControllerStatePacket_t controllerState;
ValveControllerBLEStatePacket_t controllerBLEState;
ValveControllerDebugPacket_t debugState;
ValveControllerTrackpadImage_t padImage;
ValveControllerRawTrackpadImage_t rawPadImage;
SteamControllerWirelessEvent_t wirelessEvent;
SteamControllerStatusEvent_t statusEvent;
SteamDeckStatePacket_t deckState;
} payload;
} ValveInReport_t;
// Enumeration for BLE packet protocol
enum EBLEPacketReportNums
{
// Skipping past 2-3 because they are escape characters in Uart protocol
k_EBLEReportState = 4,
k_EBLEReportStatus = 5,
k_EBLEReportState = 4,
k_EBLEReportStatus = 5,
};
// Enumeration of data chunks in BLE state packets
enum EBLEOptionDataChunksBitmask
{
// First byte upper nibble
k_EBLEButtonChunk1 = 0x10,
k_EBLEButtonChunk2 = 0x20,
k_EBLEButtonChunk3 = 0x40,
k_EBLELeftJoystickChunk = 0x80,
// First byte upper nibble
k_EBLEButtonChunk1 = 0x10,
k_EBLEButtonChunk2 = 0x20,
k_EBLEButtonChunk3 = 0x40,
k_EBLELeftJoystickChunk = 0x80,
// Second full byte
k_EBLELeftTrackpadChunk = 0x100,
k_EBLERightTrackpadChunk = 0x200,
k_EBLEIMUAccelChunk = 0x400,
k_EBLEIMUGyroChunk = 0x800,
k_EBLEIMUQuatChunk = 0x1000,
// Second full byte
k_EBLELeftTrackpadChunk = 0x100,
k_EBLERightTrackpadChunk = 0x200,
k_EBLEIMUAccelChunk = 0x400,
k_EBLEIMUGyroChunk = 0x800,
k_EBLEIMUQuatChunk = 0x1000,
};
// Triton and derivatives do not use the ValveInReport_t structure
enum ETritonReportIDTypes
{
ID_TRITON_CONTROLLER_STATE = 0x42,
ID_TRITON_BATTERY_STATUS = 0x43,
ID_TRITON_WIRELESS_STATUS_X = 0x46,
ID_TRITON_WIRELESS_STATUS = 0x79,
};
enum ETritonWirelessState
{
k_ETritonWirelessStateDisconnect = 1,
k_ETritonWirelessStateConnect = 2,
};
typedef struct
{
uint32_t uTimestamp;
short sAccelX;
short sAccelY;
short sAccelZ;
short sGyroX;
short sGyroY;
short sGyroZ;
short sGyroQuatW;
short sGyroQuatX;
short sGyroQuatY;
short sGyroQuatZ;
} TritonMTUIMU_t;
typedef struct
{
uint8_t cSeq_num;
uint32_t uButtons;
short sTriggerLeft;
short sTriggerRight;
short sLeftStickX;
short sLeftStickY;
short sRightStickX;
short sRightStickY;
short sLeftPadX;
short sLeftPadY;
unsigned short ucPressureLeft;
short sRightPadX;
short sRightPadY;
unsigned short ucPressureRight;
TritonMTUIMU_t imu;
} TritonMTUFull_t;
typedef struct
{
unsigned char ucBatteryLevel;
unsigned short sBatteryVoltage;
unsigned short sSystemVoltage;
unsigned short sInputVoltage;
unsigned short sCurrent;
unsigned short sInputCurrent;
char cTemperature;
} TritonBatteryStatus_t;
typedef struct
{
unsigned char state;
} TritonWirelessStatus_t;
#pragma pack()
#endif // _CONTROLLER_STRUCTS

View File

@@ -149,6 +149,8 @@
#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_PS4 0xd00e
#define USB_PRODUCT_THRUSTMASTER_T_FLIGHT_HOTAS_ONE 0xb68c
#define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142
#define USB_PRODUCT_VALVE_STEAM_PROTEUS_DONGLE 0x1304
#define USB_PRODUCT_VALVE_STEAM_NEREID_DONGLE 0x1305
#define USB_PRODUCT_VICTRIX_FS_PRO 0x0203
#define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207
#define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 // XUSB driver software PID