SDL: Added support for the Steam Virtual Gamepad on macOS Sequoia (cfb3d)

From cfb3db01dc91e036d16c192c21a6cf5a74a7f93e Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 16 Oct 2024 10:46:08 -0700
Subject: [PATCH] Added support for the Steam Virtual Gamepad on macOS Sequoia

(cherry picked from commit d7b1ba1bfca7c9bf4f57075258ccfa0e0c6b2051)
---
 src/joystick/SDL_joystick.c                |  9 ++++++++
 src/joystick/SDL_joystick_c.h              |  3 +++
 src/joystick/hidapi/SDL_hidapi_xbox360.c   | 24 ++++++++++++++++++----
 src/joystick/hidapi/SDL_hidapijoystick.c   |  7 +++++++
 src/joystick/hidapi/SDL_hidapijoystick_c.h |  1 +
 src/joystick/iphoneos/SDL_mfijoystick.m    |  6 +++++-
 6 files changed, 45 insertions(+), 5 deletions(-)

diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index 3b72fd3601328..4fe0b6a2591f4 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -2843,6 +2843,15 @@ SDL_bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product
     return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
 }
 
+SDL_bool SDL_IsJoystickSteamVirtualGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version)
+{
+#ifdef __MACOSX__
+    return (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER && version == 0);
+#else
+    return (vendor_id == USB_VENDOR_VALVE && product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD);
+#endif
+}
+
 SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id)
 {
     EControllerType eType = GuessControllerType(vendor_id, product_id);
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index 44d8e8cc91121..35c255f49eaf7 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -123,6 +123,9 @@ extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16
 extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id);
 extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id);
 
+/* Function to return whether a joystick is a Steam Virtual Gamepad */
+extern SDL_bool SDL_IsJoystickSteamVirtualGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version);
+
 /* Function to return whether a joystick is a Steam Controller */
 extern SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);
 
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c
index 628346f3cd6f4..5a23ed18d154a 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c
@@ -85,10 +85,15 @@ static SDL_bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device
         return SDL_FALSE;
     }
 #if defined(__MACOSX__) && defined(SDL_JOYSTICK_MFI)
-    /* On macOS you can't write output reports to wired XBox controllers,
-       so we'll just use the GCController support instead.
-    */
-    return SDL_FALSE;
+    if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {
+        /* GCController support doesn't work with the Steam Virtual Gamepad */
+        return SDL_TRUE;
+    } else {
+        /* On macOS you can't write output reports to wired XBox controllers,
+           so we'll just use the GCController support instead.
+        */
+        return SDL_FALSE;
+    }
 #else
     return (type == SDL_CONTROLLER_TYPE_XBOX360) ? SDL_TRUE : SDL_FALSE;
 #endif
@@ -143,6 +148,13 @@ static SDL_bool HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device)
 
     device->type = SDL_CONTROLLER_TYPE_XBOX360;
 
+    if (SDL_IsJoystickSteamVirtualGamepad(device->vendor_id, device->product_id, device->version) &&
+        device->product_string && SDL_strncmp(device->product_string, "GamePad-", 8) == 0) {
+        int slot = 0;
+        SDL_sscanf(device->product_string, "GamePad-%d", &slot);
+        device->steam_virtual_gamepad_slot = (slot - 1);
+    }
+
     return HIDAPI_JoystickConnected(device, NULL);
 }
 
@@ -231,7 +243,11 @@ static int HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *dev
 static void HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size)
 {
     Sint16 axis;
+#ifdef __MACOSX__
+    const SDL_bool invert_y_axes = SDL_FALSE;
+#else
     const SDL_bool invert_y_axes = SDL_TRUE;
+#endif
 
     if (ctx->last_state[2] != data[2]) {
         SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index 863785eec0bb2..28209fa5ed07b 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -929,6 +929,7 @@ static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *inf
     device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, device->vendor_id, device->product_id, device->version, device->manufacturer_string, device->product_string, 'h', 0);
     device->joystick_type = SDL_JOYSTICK_TYPE_GAMECONTROLLER;
     device->type = SDL_GetJoystickGameControllerProtocol(device->name, device->vendor_id, device->product_id, device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol);
+    device->steam_virtual_gamepad_slot = -1;
 
     if (num_children > 0) {
         int i;
@@ -1380,6 +1381,12 @@ static const char *HIDAPI_JoystickGetDevicePath(int device_index)
 
 static int HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
 {
+    SDL_HIDAPI_Device *device;
+
+    device = HIDAPI_GetDeviceByIndex(device_index, NULL);
+    if (device) {
+        return device->steam_virtual_gamepad_slot;
+    }
     return -1;
 }
 
diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h
index 738c43abebe46..8a315da37ba35 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick_c.h
+++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -83,6 +83,7 @@ typedef struct _SDL_HIDAPI_Device
     SDL_bool is_bluetooth;
     SDL_JoystickType joystick_type;
     SDL_GameControllerType type;
+    int steam_virtual_gamepad_slot;
 
     struct _SDL_HIDAPI_DeviceDriver *driver;
     void *context;
diff --git a/src/joystick/iphoneos/SDL_mfijoystick.m b/src/joystick/iphoneos/SDL_mfijoystick.m
index 2dedc9710d192..3b09127e20e38 100644
--- a/src/joystick/iphoneos/SDL_mfijoystick.m
+++ b/src/joystick/iphoneos/SDL_mfijoystick.m
@@ -419,6 +419,10 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
         return FALSE;
     }
 #endif
+    if (device->is_xbox && SDL_strncmp(name, "GamePad-", 8) == 0) {
+        /* This is a Steam Virtual Gamepad, which isn't supported by GCController */
+        return FALSE;
+    }
     CheckControllerSiriRemote(controller, &device->is_siri_remote);
 
     if (device->is_siri_remote && !SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) {
@@ -438,7 +442,7 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
             device->has_xbox_share_button = TRUE;
         }
     }
-#endif // ENABLE_PHYSICAL_INPUT_PROFILE
+#endif /* ENABLE_PHYSICAL_INPUT_PROFILE */
 
     if (device->is_backbone_one) {
         vendor = USB_VENDOR_BACKBONE;