From f7a880122719ab463e3aa00c7f3af7a60f8717a2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 27 May 2026 18:19:27 -0700
Subject: [PATCH] Add support for new Steam Controller input report
---
src/joystick/hidapi/SDL_hidapi_steam_triton.c | 104 +++++++++++++++---
.../hidapi/steam/controller_structs.h | 48 +++++++-
2 files changed, 133 insertions(+), 19 deletions(-)
diff --git a/src/joystick/hidapi/SDL_hidapi_steam_triton.c b/src/joystick/hidapi/SDL_hidapi_steam_triton.c
index 9339bcb4aaf78..92cd1e703e439 100644
--- a/src/joystick/hidapi/SDL_hidapi_steam_triton.c
+++ b/src/joystick/hidapi/SDL_hidapi_steam_triton.c
@@ -137,14 +137,10 @@ static bool DisableSteamTritonLizardMode(SDL_hid_device *dev)
return true;
}
-static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
- SDL_Joystick *joystick,
- TritonMTUNoQuat_t *pTritonReport)
+// 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 )
{
- float values[3];
- SDL_DriverSteamTriton_Context *ctx = (SDL_DriverSteamTriton_Context *)device->context;
- Uint64 timestamp = SDL_GetTicksNS();
-
if (pTritonReport->buttons != ctx->last_button_state) {
Uint8 hat = 0;
@@ -217,13 +213,11 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
ctx->last_button_state = pTritonReport->buttons;
}
- // RKRK There're button bits for this if you so choose.
- 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);
@@ -231,6 +225,45 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
pTritonReport->sRightStickX);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,
-pTritonReport->sRightStickY);
+}
+
+static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
+ 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);
+
+ 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_x = pTritonReport->sLeftPadX / 65536.0f + 0.5f;
+ ctx->left_touch_y = -(float)pTritonReport->sLeftPadY / 65536.0f + 0.5f;
+ }
+ SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0,
+ left_touch_down,
+ ctx->left_touch_x,
+ ctx->left_touch_y,
+ pTritonReport->ucPressureLeft / 32768.0f);
+ ctx->left_touch_down = left_touch_down;
+ }
+ if (right_touch_down || ctx->right_touch_down) {
+ if (right_touch_down) {
+ ctx->right_touch_x = pTritonReport->sRightPadX / 65536.0f + 0.5f;
+ ctx->right_touch_y = -(float)pTritonReport->sRightPadY / 65536.0f + 0.5f;
+ }
+ SDL_SendJoystickTouchpad(timestamp, joystick, 1, 0,
+ right_touch_down,
+ ctx->right_touch_x,
+ ctx->right_touch_y,
+ pTritonReport->ucPressureRight / 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);
@@ -249,20 +282,31 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
ctx->last_sensor_tick = pTritonReport->imu.timestamp;
}
+}
+// New Ibex MTU has IMU data in
+static void HIDAPI_DriverSteamTriton_HandleState_Timestamp(SDL_HIDAPI_Device *device,
+ 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);
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_x = pTritonReport->sLeftPadX / 65536.0f + 0.5f;
ctx->left_touch_y = -(float)pTritonReport->sLeftPadY / 65536.0f + 0.5f;
-
}
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0,
left_touch_down,
ctx->left_touch_x,
ctx->left_touch_y,
- pTritonReport->sPressureLeft / 32768.0f);
+ pTritonReport->unPressureLeft / 32768.0f);
ctx->left_touch_down = left_touch_down;
}
if (right_touch_down || ctx->right_touch_down) {
@@ -274,9 +318,27 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
right_touch_down,
ctx->right_touch_x,
ctx->right_touch_y,
- pTritonReport->sPressureRight / 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);
+
+ 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.timestamp;
+ }
}
static void HIDAPI_DriverSteamTriton_HandleBatteryStatus(SDL_HIDAPI_Device *device,
@@ -463,7 +525,19 @@ static bool HIDAPI_DriverSteamTriton_UpdateDevice(SDL_HIDAPI_Device *device)
HIDAPI_DriverSteamTriton_HandleState(device, joystick, pTritonReport);
}
break;
- case ID_TRITON_BATTERY_STATUS:
+ case ID_TRITON_CONTROLLER_STATE_TIMESTAMP:
+ if (!joystick) {
+ HIDAPI_DriverSteamTriton_SetControllerConnected(device, true);
+ if (device->num_joysticks > 0) {
+ joystick = SDL_GetJoystickFromID(device->joysticks[0]);
+ }
+ }
+ if (joystick && r >= (1 + sizeof(TritonMTUNoQuat32TS_t))) {
+ TritonMTUNoQuat32TS_t *pTritonReport = (TritonMTUNoQuat32TS_t *)&data[1];
+ HIDAPI_DriverSteamTriton_HandleState_Timestamp(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);
diff --git a/src/joystick/hidapi/steam/controller_structs.h b/src/joystick/hidapi/steam/controller_structs.h
index b0f86ea32b185..ae0c043e2de5c 100644
--- a/src/joystick/hidapi/steam/controller_structs.h
+++ b/src/joystick/hidapi/steam/controller_structs.h
@@ -556,6 +556,8 @@ enum ETritonReportIDTypes
ID_TRITON_BATTERY_STATUS = 0x43,
ID_TRITON_CONTROLLER_STATE_BLE = 0x45,
ID_TRITON_WIRELESS_STATUS_X = 0x46,
+ ID_TRITON_CONTROLLER_STATE_TIMESTAMP = 0x47,
+
ID_TRITON_WIRELESS_STATUS = 0x79,
};
@@ -593,6 +595,18 @@ typedef struct {
short sGyroZ;
} TritonMTUIMUNoQuat_t;
+typedef struct
+{
+ uint16_t timestamp;
+ short sAccelX;
+ short sAccelY;
+ short sAccelZ;
+
+ short sGyroX;
+ short sGyroY;
+ short sGyroZ;
+} TritonMTUIMUNoQuat32usTS_t;
+
typedef struct
{
uint8_t seq_num;
@@ -607,11 +621,11 @@ typedef struct
short sLeftPadX;
short sLeftPadY;
- unsigned short sPressureLeft;
+ unsigned short ucPressureLeft;
short sRightPadX;
short sRightPadY;
- unsigned short sPressureRight;
+ unsigned short ucPressureRight;
TritonMTUIMU_t imu;
} TritonMTUFull_t;
@@ -628,14 +642,40 @@ typedef struct {
short sLeftPadX;
short sLeftPadY;
- unsigned short sPressureLeft;
+ unsigned short ucPressureLeft;
short sRightPadX;
short sRightPadY;
- unsigned short sPressureRight;
+ unsigned short ucPressureRight;
TritonMTUIMUNoQuat_t imu;
} TritonMTUNoQuat_t;
+// New Ibex packet that adds a timestamp to the trackpad sampling
+// and reduces the size of the IMU timestamp. Timestamps are now 16 bits
+typedef struct
+{
+ uint8_t seq_num;
+ uint32_t buttons;
+ short sTriggerLeft;
+ short sTriggerRight;
+
+ short sLeftStickX;
+ short sLeftStickY;
+ short sRightStickX;
+ short sRightStickY;
+
+ unsigned short unTrackpadTimestamp;
+ short sLeftPadX;
+ short sLeftPadY;
+ unsigned short unPressureLeft;
+
+ short sRightPadX;
+ short sRightPadY;
+ unsigned short unPressureRight;
+
+ TritonMTUIMUNoQuat32usTS_t imu;
+} TritonMTUNoQuat32TS_t;
+
enum EChargeState
{
k_EChargeStateReset,