SDL: hidapi: Add support for the Steam Controller wireless dongle

From 68c2cf84f6f27504f8607f0b6c36d90a58562ea6 Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Thu, 14 Nov 2024 00:36:06 -0600
Subject: [PATCH] hidapi: Add support for the Steam Controller wireless dongle

---
 src/joystick/hidapi/SDL_hidapi_steam.c | 65 ++++++++++++++++++++++----
 src/joystick/usb_ids.h                 |  1 +
 2 files changed, 56 insertions(+), 10 deletions(-)

diff --git a/src/joystick/hidapi/SDL_hidapi_steam.c b/src/joystick/hidapi/SDL_hidapi_steam.c
index 9612dd7bfc444..d5d12edf1e60e 100644
--- a/src/joystick/hidapi/SDL_hidapi_steam.c
+++ b/src/joystick/hidapi/SDL_hidapi_steam.c
@@ -136,6 +136,8 @@ typedef struct SteamControllerStateInternal_t
 #define D0G_WIRELESS_NEWLYPAIRED               3
 
 #define D0G_IS_WIRELESS_DISCONNECT(data, len) (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) == D0G_WIRELESS_DISCONNECTED)
+#define D0G_IS_WIRELESS_CONNECT(data, len)    (D0G_IS_VALID_WIRELESS_EVENT(data, len) && D0G_GET_WIRELESS_EVENT_TYPE(data) != D0G_WIRELESS_DISCONNECTED)
+
 
 #define MAX_REPORT_SEGMENT_PAYLOAD_SIZE 18
 /*
@@ -317,7 +319,14 @@ static int SetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65], i
             nRet = SDL_hid_send_feature_report(dev->dev, uPacketBuffer, sizeof(uPacketBuffer));
         }
     } else {
-        nRet = SDL_hid_send_feature_report(dev->dev, uBuffer, 65);
+        for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {
+            nRet = SDL_hid_send_feature_report(dev->dev, uBuffer, 65);
+            if (nRet >= 0) {
+                break;
+            }
+
+            SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);
+        }
     }
 
     DPRINTF("SetFeatureReport() ret = %d\n", nRet);
@@ -380,7 +389,15 @@ static int GetFeatureReport(SDL_HIDAPI_Device *dev, unsigned char uBuffer[65])
         return -1;
     } else {
         SDL_memset(uBuffer, 0, 65);
-        nRet = SDL_hid_get_feature_report(dev->dev, uBuffer, 65);
+
+        for (int nRetries = 0; nRetries < RADIO_WORKAROUND_SLEEP_ATTEMPTS; nRetries++) {
+            nRet = SDL_hid_get_feature_report(dev->dev, uBuffer, 65);
+            if (nRet >= 0) {
+                break;
+            }
+
+            SDL_DelayNS(RADIO_WORKAROUND_SLEEP_DURATION_US * 1000);
+        }
 
         DPRINTF("GetFeatureReport USB ret=%d\n", nRet);
         HEXDUMP(uBuffer, nRet);
@@ -992,7 +1009,24 @@ static bool HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device)
 
     HIDAPI_SetDeviceName(device, "Steam Controller");
 
-    return HIDAPI_JoystickConnected(device, NULL);
+    // If this is a wireless dongle, request a wireless state update
+    if (device->product_id == USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE) {
+        unsigned char buf[65];
+        int res;
+
+        buf[0] = 0;
+        buf[1] = ID_DONGLE_GET_WIRELESS_STATE;
+        res = SetFeatureReport(device, buf, 2);
+        if (res < 0) {
+            return SDL_SetError("Failed to send ID_DONGLE_GET_WIRELESS_STATE request");
+        }
+
+        // We will enumerate any attached controllers in UpdateDevices()
+        return true;
+    } else {
+        // Wired and BLE controllers are always connected if HIDAPI can see them
+        return HIDAPI_JoystickConnected(device, NULL);
+    }
 }
 
 static int HIDAPI_DriverSteam_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
@@ -1095,8 +1129,6 @@ static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
 
     if (device->num_joysticks > 0) {
         joystick = SDL_GetJoystickFromID(device->joysticks[0]);
-    } else {
-        return false;
     }
 
     for (;;) {
@@ -1109,10 +1141,6 @@ static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
             break;
         }
 
-        if (!joystick) {
-            continue;
-        }
-
         nPacketLength = 0;
         if (r > 0) {
             nPacketLength = WriteSegmentToSteamControllerPacketAssembler(&ctx->m_assembler, data, r);
@@ -1123,6 +1151,10 @@ static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
         if (nPacketLength > 0 && UpdateSteamControllerState(pPacket, nPacketLength, &ctx->m_state)) {
             Uint64 timestamp = SDL_GetTicksNS();
 
+            if (!joystick) {
+                continue;
+            }
+
             if (ctx->m_state.ulButtons != ctx->m_last_state.ulButtons) {
                 Uint8 hat = 0;
 
@@ -1203,11 +1235,24 @@ static bool HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
             }
 
             ctx->m_last_state = ctx->m_state;
+        } else if (joystick && D0G_IS_WIRELESS_DISCONNECT(pPacket, nPacketLength)) {
+            // Controller has disconnected from the wireless dongle
+            HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
+            joystick = NULL;
+        } else if (!joystick && D0G_IS_WIRELESS_CONNECT(pPacket, nPacketLength)) {
+            // Controller has connected to the wireless dongle
+            if (!HIDAPI_JoystickConnected(device, NULL)) {
+                return false;
+            }
+
+            joystick = SDL_GetJoystickFromID(device->joysticks[0]);
         }
 
         if (r <= 0) {
             // Failed to read from controller
-            HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
+            if (joystick) {
+                HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
+            }
             return false;
         }
     }
diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h
index 06efe0f6f3177..623ffcf49d2d2 100644
--- a/src/joystick/usb_ids.h
+++ b/src/joystick/usb_ids.h
@@ -125,6 +125,7 @@
 #define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO               0xd012
 #define USB_PRODUCT_TURTLE_BEACH_SERIES_X_REACT_R         0x7013
 #define USB_PRODUCT_TURTLE_BEACH_SERIES_X_RECON           0x7009
+#define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE         0x1142
 #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