SDL: Translate steam deck HID reports to SDL events.

From 6dd68273438e979590e8211d8a85cd785c9d4bd3 Mon Sep 17 00:00:00 2001
From: Max Maisel <[EMAIL REDACTED]>
Date: Thu, 7 Sep 2023 17:18:12 +0200
Subject: [PATCH] Translate steam deck HID reports to SDL events.

---
 src/joystick/hidapi/SDL_hidapi_steamdeck.c    | 121 +++++++++++++++++-
 .../hidapi/steam/controller_constants.h       |  31 +++++
 .../hidapi/steam/controller_structs.h         |  62 +++++++++
 3 files changed, 213 insertions(+), 1 deletion(-)

diff --git a/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/src/joystick/hidapi/SDL_hidapi_steamdeck.c
index 02566e94b0f7..a8bce66bca7b 100644
--- a/src/joystick/hidapi/SDL_hidapi_steamdeck.c
+++ b/src/joystick/hidapi/SDL_hidapi_steamdeck.c
@@ -31,9 +31,21 @@
 
 #include <stdint.h>
 
+#define bool  SDL_bool
+#define true  SDL_TRUE
+#define false SDL_FALSE
+
+typedef uint32_t uint32;
+typedef uint64_t uint64;
+
+#include "steam/controller_constants.h"
+#include "steam/controller_structs.h"
+
 typedef struct
 {
     Uint32 update_rate_us;
+    Uint32 sensor_timestamp_us;
+    Uint64 last_button_state;
 } SDL_DriverSteamDeck_Context;
 
 /*****************************************************************************************************/
@@ -109,7 +121,114 @@ static void HIDAPI_DriverSteamDeck_SetDevicePlayerIndex(SDL_HIDAPI_Device *devic
 
 static SDL_bool HIDAPI_DriverSteamDeck_UpdateDevice(SDL_HIDAPI_Device *device)
 {
-    return SDL_FALSE;
+    SDL_DriverSteamDeck_Context *ctx = (SDL_DriverSteamDeck_Context *)device->context;
+    SDL_Joystick *joystick = NULL;
+    int r;
+    uint8_t data[64];
+    float values[3];
+    ValveInReport_t *pInReport = (ValveInReport_t *)data;
+
+    if (device->num_joysticks > 0) {
+        joystick = SDL_GetJoystickFromInstanceID(device->joysticks[0]);
+        if (joystick == NULL) {
+            return SDL_FALSE;
+        }
+    } else {
+        return SDL_FALSE;
+    }
+
+    SDL_memset(data, 0, sizeof(data));
+    r = SDL_hid_read(device->dev, data, sizeof(data));
+    if (r == 0) {
+        return SDL_FALSE;
+    } else if (r <= 0) {
+        /* Failed to read from controller */
+        HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
+        return SDL_FALSE;
+    }
+
+    if (!(r == 64 && pInReport->header.unReportVersion == k_ValveInReportMsgVersion && pInReport->header.ucType == ID_CONTROLLER_DECK_STATE && pInReport->header.ucLength == 64)) {
+        return SDL_FALSE;
+    }
+
+    Uint64 timestamp = SDL_GetTicksNS();
+
+    if (pInReport->payload.deckState.ulButtons != ctx->last_button_state) {
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_A) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_B) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_X) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_Y) ? SDL_PRESSED : SDL_RELEASED);
+
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_LT) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_RT) ? SDL_PRESSED : SDL_RELEASED);
+
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_SELECT) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_START) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_MODE) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_MISC1,
+                               (pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_BASE) ? SDL_PRESSED : SDL_RELEASED);
+
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STICKL) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_STICKR) ? SDL_PRESSED : SDL_RELEASED);
+
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1,
+                               (pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_PADDLE1) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_PADDLE1,
+                               (pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_PADDLE2) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_PADDLE3) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_PADDLE2,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_PADDLE4) ? SDL_PRESSED : SDL_RELEASED);
+
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_UP,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_DOWN,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_DOWN) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_LEFT,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_LEFT) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
+                               (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_RIGHT) ? SDL_PRESSED : SDL_RELEASED);
+        ctx->last_button_state = pInReport->payload.deckState.ulButtons;
+    }
+
+    SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
+                         (int)pInReport->payload.deckState.sLeftTrigger * 2 - 32768);
+    SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
+                         (int)pInReport->payload.deckState.sRightTrigger * 2 - 32768);
+
+    SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX,
+                         pInReport->payload.deckState.sLeftStickX);
+    SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY,
+                         -pInReport->payload.deckState.sLeftStickY);
+    SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX,
+                         pInReport->payload.deckState.sRightStickX);
+    SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY,
+                         -pInReport->payload.deckState.sRightStickY);
+
+    ctx->sensor_timestamp_us += ctx->update_rate_us;
+
+    values[0] = (pInReport->payload.deckState.sGyroX / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
+    values[1] = (pInReport->payload.deckState.sGyroZ / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
+    values[2] = (-pInReport->payload.deckState.sGyroY / 32768.0f) * (2000.0f * (SDL_PI_F / 180.0f));
+    SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, ctx->sensor_timestamp_us, values, 3);
+
+    values[0] = (pInReport->payload.deckState.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
+    values[1] = (pInReport->payload.deckState.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
+    values[2] = (-pInReport->payload.deckState.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
+    SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, ctx->sensor_timestamp_us, values, 3);
+
+    return SDL_TRUE;
 }
 
 static SDL_bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
diff --git a/src/joystick/hidapi/steam/controller_constants.h b/src/joystick/hidapi/steam/controller_constants.h
index d0d568851662..42c84f247dd5 100644
--- a/src/joystick/hidapi/steam/controller_constants.h
+++ b/src/joystick/hidapi/steam/controller_constants.h
@@ -315,6 +315,37 @@ enum GamepadButtons
 	GAMEPAD_BTN_COUNT
 };
 
+typedef enum
+{
+	// Low word button bits
+	STEAMDECK_LBUTTON_RT2 = (1 << 0),
+	STEAMDECK_LBUTTON_LT2 = (1 << 1),
+	STEAMDECK_LBUTTON_RT = (1 << 2),
+	STEAMDECK_LBUTTON_LT = (1 << 3),
+	STEAMDECK_LBUTTON_Y = (1 << 4),
+	STEAMDECK_LBUTTON_B = (1 << 5),
+	STEAMDECK_LBUTTON_X = (1 << 6),
+	STEAMDECK_LBUTTON_A = (1 << 7),
+	STEAMDECK_LBUTTON_DPAD_UP = (1 << 8),
+	STEAMDECK_LBUTTON_DPAD_RIGHT = (1 << 9),
+	STEAMDECK_LBUTTON_DPAD_LEFT = (1 << 10),
+	STEAMDECK_LBUTTON_DPAD_DOWN = (1 << 11),
+	STEAMDECK_LBUTTON_SELECT = (1 << 12),
+	STEAMDECK_LBUTTON_MODE = (1 << 13),
+	STEAMDECK_LBUTTON_START = (1 << 14),
+	STEAMDECK_LBUTTON_PADDLE3 = (1 << 15),
+	STEAMDECK_LBUTTON_PADDLE4 = (1 << 16),
+	STEAMDECK_LBUTTON_PADL = (1 << 17),
+	STEAMDECK_LBUTTON_PADR = (1 << 18),
+	STEAMDECK_LBUTTON_STICKL = (1 << 22),
+	STEAMDECK_LBUTTON_STICKR = (1 << 26),
+
+	// High word button bits
+	STEAMDECK_HBUTTON_PADDLE1 = (1 << 9),
+	STEAMDECK_HBUTTON_PADDLE2 = (1 << 10),
+	STEAMDECK_HBUTTON_BASE = (1 << 18)
+} DeckButtons;
+
 // Mode adjust
 enum ModeAdjustModes
 {
diff --git a/src/joystick/hidapi/steam/controller_structs.h b/src/joystick/hidapi/steam/controller_structs.h
index e60428e26bf7..6e86426d7e73 100644
--- a/src/joystick/hidapi/steam/controller_structs.h
+++ b/src/joystick/hidapi/steam/controller_structs.h
@@ -77,6 +77,7 @@ typedef enum
 	ID_CONTROLLER_DEBUG2 = 5,
 	ID_CONTROLLER_SECONDARY_STATE = 6,
 	ID_CONTROLLER_BLE_STATE = 7,
+	ID_CONTROLLER_DECK_STATE = 9,
 	ID_CONTROLLER_MSG_COUNT
 } ValveInReportMessageIDs; 
 
@@ -258,6 +259,66 @@ typedef struct
 	unsigned char ucBatteryLevel;
 } SteamControllerStatusEvent_t;
 
+// Deck State payload
+typedef struct
+{
+	// If packet num matches that on your prior call, then the controller
+	// state hasn't been changed since your last call and there is no need to
+	// process it
+	uint32 unPacketNum;
+
+	// Button bitmask and trigger data.
+	union
+	{
+		uint64 ulButtons;
+		struct
+		{
+			uint32 ulButtonsL;
+			uint32 ulButtonsH;
+		};
+	};
+
+	// Left pad coordinates
+	short sLeftPadX;
+	short sLeftPadY;
+
+	// Right pad coordinates
+	short sRightPadX;
+	short sRightPadY;
+
+	// Accelerometer values
+	short sAccelX;
+	short sAccelY;
+	short sAccelZ;
+
+	// Gyroscope values
+	short sGyroX;
+	short sGyroY;
+	short sGyroZ;
+
+	// Gyro quaternions
+	short sGyroQuatW;
+	short sGyroQuatX;
+	short sGyroQuatY;
+	short sGyroQuatZ;
+
+	// Uncalibrated trigger values
+	short sLeftTrigger;
+	short sRightTrigger;
+
+	// Left stick values
+	short sLeftStickX;
+	short sLeftStickY;
+
+	// Right stick values
+	short sRightStickX;
+	short sRightStickY;
+
+	// Touchpad pressures
+	short sLeftPadPressure;
+	short sRightPadPressure;
+} SteamDeckStatePacket_t;
+
 typedef struct
 {
 	ValveInReportHeader_t header;
@@ -271,6 +332,7 @@ typedef struct
 		ValveControllerRawTrackpadImage_t rawPadImage;
 		SteamControllerWirelessEvent_t wirelessEvent;
 		SteamControllerStatusEvent_t statusEvent;
+		SteamDeckStatePacket_t deckState;
 	} payload;
 	
 } ValveInReport_t;