Browse Source

Added support for the new Steam Controller

Sam Lantinga 1 month ago
parent
commit
1998b65045

+ 5 - 4
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" />

+ 4 - 0
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>

+ 4 - 0
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 */,

+ 197 - 86
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 @@ 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],

+ 3 - 0
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

+ 6 - 0
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;

+ 3 - 0
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);
 

+ 7 - 3
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, 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)
 };

+ 2 - 0
src/joystick/controller_type.h

@@ -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,

+ 532 - 0
src/joystick/hidapi/SDL_hidapi_steam_triton.c

@@ -0,0 +1,532 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 2023 Max Maisel <[email protected]>
+
+  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

+ 3 - 0
src/joystick/hidapi/SDL_hidapijoystick.c

@@ -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,

+ 2 - 0
src/joystick/hidapi/SDL_hidapijoystick_c.h

@@ -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;

+ 7 - 1
src/joystick/hidapi/steam/controller_constants.h

@@ -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,

+ 194 - 33
src/joystick/hidapi/steam/controller_structs.h

@@ -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;
-	
-} ValveInReport_t;
+    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,
-
-	// Second full byte
-	k_EBLELeftTrackpadChunk = 0x100,
-	k_EBLERightTrackpadChunk = 0x200,
-	k_EBLEIMUAccelChunk = 0x400,
-	k_EBLEIMUGyroChunk = 0x800,
-	k_EBLEIMUQuatChunk = 0x1000,
+    // 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,
 };
 
+// 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

+ 2 - 0
src/joystick/usb_ids.h

@@ -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