Add support for new Steam Controller input report on mobile devices

(cherry picked from commit f6ffa69890)
This commit is contained in:
Sam Lantinga
2026-05-28 11:18:20 -07:00
parent 8d5ee8b38c
commit 05d140a128
6 changed files with 93 additions and 98 deletions

View File

@@ -434,10 +434,11 @@ static void ExceptionCheck( JNIEnv *env, const char *pszClassName, const char *p
class CHIDDevice
{
public:
CHIDDevice( int nDeviceID, hid_device_info *pInfo )
CHIDDevice( int nDeviceID, hid_device_info *pInfo, int nReportID )
{
m_nId = nDeviceID;
m_pInfo = pInfo;
m_nReportID = nReportID;
// The Bluetooth Steam Controller needs special handling
const int VALVE_USB_VID = 0x28DE;
@@ -606,14 +607,7 @@ public:
size_t nDataLen = buffer.size() > length ? length : buffer.size();
if ( m_bIsBLESteamController )
{
if ( m_pInfo->product_id == TRITON_BLE_PID )
{
data[0] = 0x45;
}
else
{
data[0] = 0x03;
}
data[0] = m_nReportID;
SDL_memcpy( data + 1, buffer.data(), nDataLen );
++nDataLen;
}
@@ -788,6 +782,7 @@ private:
pthread_mutex_t m_refCountLock = PTHREAD_MUTEX_INITIALIZER;
int m_nRefCount = 0;
int m_nId = 0;
int m_nReportID = 0;
hid_device_info *m_pInfo = nullptr;
hid_device *m_pDevice = nullptr;
bool m_bIsBLESteamController = false;
@@ -837,7 +832,7 @@ extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth );
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth, jint nReportID );
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, jint nDeviceID);
@@ -917,7 +912,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallbac
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth )
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth, jint nReportID )
{
LOGV( "HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface );
@@ -943,7 +938,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNI
pInfo->bus_type = HID_API_BUS_USB;
}
hid_device_ref<CHIDDevice> pDevice( new CHIDDevice( nDeviceID, pInfo ) );
hid_device_ref<CHIDDevice> pDevice( new CHIDDevice( nDeviceID, pInfo, nReportID ) );
hid_mutex_guard l( &g_DevicesMutex );
hid_device_ref<CHIDDevice> pLast, pCurr;
@@ -1423,7 +1418,7 @@ extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz);
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth );
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth, jint nReportID );
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, jint nDeviceID);
@@ -1454,7 +1449,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallbac
}
extern "C"
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth )
JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, jint nDeviceID, jstring sIdentifier, jint nVendorId, jint nProductId, jstring sSerialNumber, jint nReleaseNumber, jstring sManufacturer, jstring sProduct, jint nInterface, jint nInterfaceClass, jint nInterfaceSubclass, jint nInterfaceProtocol, jboolean bBluetooth, jint nReportID )
{
LOGV("Stub HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface);
}
@@ -1495,7 +1490,7 @@ extern "C"
JNINativeMethod HIDDeviceManager_tab[8] = {
{ "HIDDeviceRegisterCallback", "()V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback) },
{ "HIDDeviceReleaseCallback", "()V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback) },
{ "HIDDeviceConnected", "(ILjava/lang/String;IILjava/lang/String;ILjava/lang/String;Ljava/lang/String;IIIIZ)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected) },
{ "HIDDeviceConnected", "(ILjava/lang/String;IILjava/lang/String;ILjava/lang/String;Ljava/lang/String;IIIIZI)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected) },
{ "HIDDeviceOpenPending", "(I)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending) },
{ "HIDDeviceOpenResult", "(IZ)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult) },
{ "HIDDeviceDisconnected", "(I)V", (void*)HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected) },

View File

@@ -78,7 +78,8 @@ typedef uint64_t uint64;
// (READ/NOTIFICATIONS)
#define VALVE_INPUT_CHAR_0x1106 @"100F6C33-1735-4313-B402-38567131E5F3"
#define VALVE_INPUT_CHAR_0x1303 @"100F6C7A-1735-4313-B402-38567131E5F3"
#define VALVE_INPUT_CHAR_0x1303_0x45 @"100F6C7A-1735-4313-B402-38567131E5F3"
#define VALVE_INPUT_CHAR_0x1303_0x47 @"100F6C7C-1735-4313-B402-38567131E5F3"
//  (READ/WRITE)
#define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3"
@@ -220,6 +221,8 @@ typedef enum
}
@property (nonatomic, readwrite) uint16_t pid;
@property (nonatomic, readwrite) uint8_t report_id;
@property (nonatomic, readwrite) int report_length;
@property (nonatomic, readwrite) bool connected;
@property (nonatomic, readwrite) bool ready;
@@ -523,7 +526,9 @@ static void process_pending_events(void)
if ( self = [super init] )
{
self.pid = 0;
_inputReports = NULL;
self.report_id = 0;
self.report_length = 0;
_inputReports = NULL;
_outputReports = [[NSMutableDictionary alloc] init];
_connected = NO;
_ready = NO;
@@ -539,7 +544,9 @@ static void process_pending_events(void)
if ( self = [super init] )
{
self.pid = 0;
_inputReports = NULL;
self.report_id = 0;
self.report_length = 0;
_inputReports = NULL;
_outputReports = [[NSMutableDictionary alloc] init];
_connected = NO;
_ready = NO;
@@ -583,17 +590,7 @@ static void process_pending_events(void)
{
if ( RingBuffer_read( _inputReports, dst+1 ) )
{
switch ( self.pid )
{
case D0G_BLE2_PID:
*dst = 0x03;
break;
case TRITON_BLE_PID:
*dst = 0x45;
break;
default:
abort();
}
*dst = self.report_id;
return _inputReports->_cbElem + 1;
}
return 0;
@@ -603,9 +600,9 @@ static void process_pending_events(void)
{
if ( self.pid == D0G_BLE2_PID )
{
[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
return (int)length;
}
[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
return (int)length;
}
// We need to look up the correct characteristic for this output report
if ( length > 0 )
@@ -748,11 +745,22 @@ static void process_pending_events(void)
if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1106]] )
{
self.pid = D0G_BLE2_PID;
self.report_id = 0x03;
self.report_length = 19;
self.bleCharacteristicInput = aChar;
}
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303]] )
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303_0x45]] )
{
self.pid = TRITON_BLE_PID;
self.report_id = 0x45;
self.report_length = 45;
self.bleCharacteristicInput = aChar;
}
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303_0x47]] )
{
self.pid = TRITON_BLE_PID;
self.report_id = 0x47;
self.report_length = 45;
self.bleCharacteristicInput = aChar;
}
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
@@ -789,22 +797,10 @@ 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 = 45;
break;
default:
abort();
}
_inputReports = RingBuffer_alloc( cbElem );
}
if ( _inputReports == NULL )
{
_inputReports = RingBuffer_alloc( self.report_length );
}
HIDBLEManager.sharedInstance.nPendingPairs -= 1;
}

View File

@@ -100,7 +100,8 @@ typedef struct
{
bool connected;
bool report_sensors;
Uint32 last_sensor_tick;
Uint16 last_sensor_tick16;
Uint32 last_sensor_tick32;
Uint64 sensor_timestamp_ns;
Uint64 last_button_state;
Uint64 last_lizard_update;
@@ -143,7 +144,7 @@ static bool DisableSteamTritonLizardMode(SDL_hid_device *dev)
// Triton newer state MTUs are identical until touchpads. Parse them using this routine.
// Expects report to be a TritonMTUNoQuat_t, so cast as needed
static void Parse_SteamTriton_HandleGenericState( SDL_DriverSteamTriton_Context* ctx, SDL_Joystick* joystick, Uint64 timestamp, TritonMTUNoQuat_t* pTritonReport )
static void HIDAPI_DriverSteamTriton_HandleGenericState(SDL_DriverSteamTriton_Context *ctx, SDL_Joystick *joystick, Uint64 timestamp, TritonMTUNoQuat_t *pTritonReport)
{
if (pTritonReport->buttons != ctx->last_button_state) {
Uint8 hat = 0;
@@ -217,11 +218,11 @@ static void Parse_SteamTriton_HandleGenericState( SDL_DriverSteamTriton_Context*
ctx->last_button_state = pTritonReport->buttons;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
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,
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX,
pTritonReport->sLeftStickX);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY,
-pTritonReport->sLeftStickY);
@@ -232,19 +233,19 @@ static void Parse_SteamTriton_HandleGenericState( SDL_DriverSteamTriton_Context*
}
static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
SDL_Joystick *joystick,
TritonMTUNoQuat_t *pTritonReport)
SDL_Joystick *joystick,
TritonMTUNoQuat_t *pTritonReport)
{
float values[3];
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
Uint64 timestamp = SDL_GetTicksNS();
Parse_SteamTriton_HandleGenericState(ctx, joystick, timestamp, pTritonReport);
HIDAPI_DriverSteamTriton_HandleGenericState(ctx, joystick, timestamp, pTritonReport);
bool left_touch_down = (pTritonReport->buttons & TRITON_LEFT_TOUCHPAD_TOUCH) ? true : false;
bool right_touch_down = (pTritonReport->buttons & TRITON_RIGHT_TOUCHPAD_TOUCH) ? true : false;
if (left_touch_down || ctx->left_touch_down) {
if (left_touch_down || ctx->left_touch_down) {
if (left_touch_down) {
ctx->left_touch_x = pTritonReport->sLeftPadX / 65536.0f + 0.5f;
ctx->left_touch_y = -(float)pTritonReport->sLeftPadY / 65536.0f + 0.5f;
@@ -253,7 +254,7 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
left_touch_down,
ctx->left_touch_x,
ctx->left_touch_y,
pTritonReport->ucPressureLeft / 32768.0f);
pTritonReport->unPressureLeft / 32768.0f);
ctx->left_touch_down = left_touch_down;
}
if (right_touch_down || ctx->right_touch_down) {
@@ -265,12 +266,12 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
right_touch_down,
ctx->right_touch_x,
ctx->right_touch_y,
pTritonReport->ucPressureRight / 32768.0f);
pTritonReport->unPressureRight / 32768.0f);
ctx->right_touch_down = right_touch_down;
}
if (ctx->report_sensors && pTritonReport->imu.timestamp != ctx->last_sensor_tick) {
Uint32 delta_us = (pTritonReport->imu.timestamp - ctx->last_sensor_tick);
if (ctx->report_sensors && pTritonReport->imu.timestamp != ctx->last_sensor_tick32) {
Uint32 delta_us = (pTritonReport->imu.timestamp - ctx->last_sensor_tick32);
ctx->sensor_timestamp_ns += SDL_US_TO_NS(delta_us);
@@ -284,19 +285,19 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
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.timestamp;
ctx->last_sensor_tick32 = pTritonReport->imu.timestamp;
}
}
// New Ibex MTU has IMU data in
static void HIDAPI_DriverSteamTriton_HandleState_Timestamp(SDL_HIDAPI_Device *device,
SDL_Joystick *joystick,
SDL_Joystick *joystick,
TritonMTUNoQuat32TS_t *pTritonReport)
{
float values[3];
SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
Uint64 timestamp = SDL_GetTicksNS();
Parse_SteamTriton_HandleGenericState(ctx, joystick, timestamp, (TritonMTUNoQuat_t *) pTritonReport);
HIDAPI_DriverSteamTriton_HandleGenericState(ctx, joystick, timestamp, (TritonMTUNoQuat_t *) pTritonReport);
bool left_touch_down = (pTritonReport->buttons & TRITON_LEFT_TOUCHPAD_TOUCH) ? true : false;
bool right_touch_down = (pTritonReport->buttons & TRITON_RIGHT_TOUCHPAD_TOUCH) ? true : false;
@@ -326,8 +327,9 @@ static void HIDAPI_DriverSteamTriton_HandleState_Timestamp(SDL_HIDAPI_Device *de
ctx->right_touch_down = right_touch_down;
}
if (ctx->report_sensors && pTritonReport->imu.timestamp != ctx->last_sensor_tick) {
Uint32 delta_us = (pTritonReport->imu.timestamp - ctx->last_sensor_tick);
if (ctx->report_sensors && pTritonReport->imu.timestamp != ctx->last_sensor_tick16) {
// The timestamp is in units of 32 microseconds
Uint32 delta_us = (Uint32)(pTritonReport->imu.timestamp - ctx->last_sensor_tick16) * 32;
ctx->sensor_timestamp_ns += SDL_US_TO_NS(delta_us);
@@ -341,7 +343,7 @@ static void HIDAPI_DriverSteamTriton_HandleState_Timestamp(SDL_HIDAPI_Device *de
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.timestamp;
ctx->last_sensor_tick16 = pTritonReport->imu.timestamp;
}
}
@@ -442,8 +444,8 @@ static bool HIDAPI_DriverSteamTriton_IsSupportedDevice(
return true;
}
} else if (SDL_IsJoystickSteamTriton(vendor_id, product_id)) {
return true;
}
return true;
}
return false;
}
@@ -541,7 +543,7 @@ static bool HIDAPI_DriverSteamTriton_UpdateDevice(SDL_HIDAPI_Device *device)
HIDAPI_DriverSteamTriton_HandleState_Timestamp(device, joystick, pTritonReport);
}
break;
case ID_TRITON_BATTERY_STATUS:
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);
@@ -592,7 +594,7 @@ static bool HIDAPI_DriverSteamTriton_RumbleJoystick(SDL_HIDAPI_Device *device, S
Uint8 buffer[HID_RUMBLE_OUTPUT_REPORT_BYTES] = { 0 };
OutputReportMsg *msg = (OutputReportMsg *)(buffer);
msg->report_id = ID_OUT_REPORT_HAPTIC_RUMBLE;
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;

View File

@@ -620,11 +620,11 @@ typedef struct
short sLeftPadX;
short sLeftPadY;
unsigned short ucPressureLeft;
unsigned short unPressureLeft;
short sRightPadX;
short sRightPadY;
unsigned short ucPressureRight;
unsigned short unPressureRight;
TritonMTUIMU_t imu;
} TritonMTUFull_t;
@@ -641,11 +641,11 @@ typedef struct {
short sLeftPadX;
short sLeftPadY;
unsigned short ucPressureLeft;
unsigned short unPressureLeft;
short sRightPadX;
short sRightPadY;
unsigned short ucPressureRight;
unsigned short unPressureRight;
TritonMTUIMUNoQuat_t imu;
} TritonMTUNoQuat_t;