From 1998b650452bdf0bee5209e20e4715b4295abe8c Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 12 Nov 2025 11:32:32 -0800
Subject: [PATCH] Added support for the new Steam Controller
---
VisualC/SDL/SDL.vcxproj | 9 +-
VisualC/SDL/SDL.vcxproj.filters | 4 +
Xcode/SDL/SDL.xcodeproj/project.pbxproj | 4 +
src/hidapi/ios/hid.m | 283 +++++++---
src/joystick/SDL_gamepad.c | 3 +
src/joystick/SDL_joystick.c | 6 +
src/joystick/SDL_joystick_c.h | 3 +
src/joystick/controller_list.h | 10 +-
src/joystick/controller_type.h | 2 +
src/joystick/hidapi/SDL_hidapi_steam_triton.c | 532 ++++++++++++++++++
src/joystick/hidapi/SDL_hidapijoystick.c | 3 +
src/joystick/hidapi/SDL_hidapijoystick_c.h | 2 +
.../hidapi/steam/controller_constants.h | 8 +-
.../hidapi/steam/controller_structs.h | 227 ++++++--
src/joystick/usb_ids.h | 2 +
15 files changed, 971 insertions(+), 127 deletions(-)
create mode 100644 src/joystick/hidapi/SDL_hidapi_steam_triton.c
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 7af7132b29a66..f5a1612f4dee0 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -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" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 77bd59d5fb9a3..7e5aa144c717c 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -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>
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index bd3c41722cc05..6d57ae7e17250 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
@@ -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 */,
diff --git a/src/hidapi/ios/hid.m b/src/hidapi/ios/hid.m
index cb5e2317599ad..24a3c791ee1d7 100644
--- a/src/hidapi/ios/hid.m
+++ b/src/hidapi/ios/hid.m
@@ -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 @@
#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 {
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 @@ static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
@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 @@ @interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
- (id)initWithPeripheral:(CBPeripheral *)peripheral;
+- (void)onDisconnect;
@end
@@ -278,8 +302,7 @@ - (void)appWillResignActiveNotification:(NSNotification *)note
HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
if ( steamController )
{
- steamController.connected = NO;
- steamController.ready = NO;
+ [steamController onDisconnect];
[self.centralManager cancelPeripheralConnection:peripheral];
}
}
@@ -474,8 +497,7 @@ - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPe
HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
if ( steamController )
{
- steamController.connected = NO;
- steamController.ready = NO;
+ [steamController onDisconnect];
[self.deviceMap removeObjectForKey:peripheral];
}
}
@@ -500,12 +522,14 @@ - (id)init
{
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 @@ - (id)initWithPeripheral:(CBPeripheral *)peripheral
{
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 @@ - (id)initWithPeripheral:(CBPeripheral *)peripheral
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 @@ - (void)setConnected:(bool)connected
- (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 @@ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForServi
{
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 @@ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForServi
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 @@ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(C
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 @@ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(C
}
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 @@ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(C
}
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 @@ int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
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 @@ int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
{ @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 @@ int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
}
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],
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index cab6221d1241e..b63a47a8791e2 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -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
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index d09992828205a..831cc541caf93 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -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;
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index c6e1a7b792746..f1866164548bc 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -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);
diff --git a/src/joystick/controller_list.h b/src/joystick/controller_list.h
index 4abd8135519da..080983fa471ad 100644
--- a/src/joystick/controller_list.h
+++ b/src/joystick/controller_list.h
@@ -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, 0x1
(Patch may be truncated, please check the link at the top of this post.)