SDL: joystick: Add capability flags for rumble and trigger rumble

From afccabb88183a0c7c3513223b1e026fe16233819 Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Thu, 11 Nov 2021 12:12:05 -0600
Subject: [PATCH] joystick: Add capability flags for rumble and trigger rumble

When API limitations force us to guess, we favor a false positive (reporting
support when it doesn't exist) compared to a false negative.
---
 src/joystick/SDL_sysjoystick.h                |  2 ++
 src/joystick/darwin/SDL_iokitjoystick.c       | 13 +++++++++-
 src/joystick/hidapi/SDL_hidapi_gamecube.c     | 18 +++++++++++++-
 src/joystick/hidapi/SDL_hidapi_luna.c         |  8 ++++++-
 src/joystick/hidapi/SDL_hidapi_ps4.c          |  9 ++++++-
 src/joystick/hidapi/SDL_hidapi_ps5.c          |  9 ++++++-
 src/joystick/hidapi/SDL_hidapi_stadia.c       |  2 +-
 src/joystick/hidapi/SDL_hidapi_switch.c       | 11 +++++++--
 src/joystick/hidapi/SDL_hidapi_xbox360.c      |  2 +-
 src/joystick/hidapi/SDL_hidapi_xbox360w.c     |  2 +-
 src/joystick/hidapi/SDL_hidapi_xboxone.c      |  5 ++++
 src/joystick/iphoneos/SDL_mfijoystick.m       | 19 +++++++++++----
 src/joystick/linux/SDL_sysjoystick.c          |  8 ++++++-
 src/joystick/vita/SDL_sysjoystick.c           |  4 ++--
 src/joystick/windows/SDL_dinputjoystick.c     |  8 ++++++-
 src/joystick/windows/SDL_rawinputjoystick.c   | 24 +++++++++++++++++++
 .../windows/SDL_windows_gaming_input.c        |  9 ++++++-
 src/joystick/windows/SDL_xinputjoystick.c     |  2 +-
 18 files changed, 135 insertions(+), 20 deletions(-)

diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index e897c44b0b..dff6b3692d 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -123,6 +123,8 @@ struct _SDL_Joystick
 
 /* Joystick capability flags for GetCapabilities() */
 #define SDL_JOYCAP_LED              0x01
+#define SDL_JOYCAP_RUMBLE           0x02
+#define SDL_JOYCAP_RUMBLE_TRIGGERS  0x04
 
 /* Macro to combine a USB vendor ID and product ID into a single Uint32 value */
 #define MAKE_VIDPID(VID, PID)   (((Uint32)(VID))<<16|(PID))
diff --git a/src/joystick/darwin/SDL_iokitjoystick.c b/src/joystick/darwin/SDL_iokitjoystick.c
index ee02891f91..1d5b80a301 100644
--- a/src/joystick/darwin/SDL_iokitjoystick.c
+++ b/src/joystick/darwin/SDL_iokitjoystick.c
@@ -942,7 +942,18 @@ DARWIN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16
 static Uint32
 DARWIN_JoystickGetCapabilities(SDL_Joystick *joystick)
 {
-    return 0;
+    recDevice *device = joystick->hwdata;
+    Uint32 result = 0;
+
+    if (!device) {
+        return 0;
+    }
+
+    if (device->ffservice) {
+        result |= SDL_JOYCAP_RUMBLE;
+    }
+
+    return result;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c
index 9fc3ff7914..2b6f7b9d8e 100644
--- a/src/joystick/hidapi/SDL_hidapi_gamecube.c
+++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c
@@ -467,7 +467,23 @@ HIDAPI_DriverGameCube_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joys
 static Uint32
 HIDAPI_DriverGameCube_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    return 0;
+    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)device->context;
+    Uint32 result = 0;
+
+    if (!ctx->pc_mode) {
+        Uint8 i;
+
+        for (i = 0; i < MAX_CONTROLLERS; i += 1) {
+            if (joystick->instance_id == ctx->joysticks[i]) {
+                if (!ctx->wireless[i] && ctx->rumbleAllowed[i]) {
+                    result |= SDL_JOYCAP_RUMBLE;
+                    break;
+                }
+            }
+        }
+    }
+
+    return result;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_luna.c b/src/joystick/hidapi/SDL_hidapi_luna.c
index 48de69f5ac..90d50d41ef 100644
--- a/src/joystick/hidapi/SDL_hidapi_luna.c
+++ b/src/joystick/hidapi/SDL_hidapi_luna.c
@@ -135,7 +135,13 @@ HIDAPI_DriverLuna_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick
 static Uint32
 HIDAPI_DriverLuna_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    return 0;
+    Uint32 result = 0;
+
+    if (device->product_id == BLUETOOTH_PRODUCT_LUNA_CONTROLLER) {
+        result |= SDL_JOYCAP_RUMBLE;
+    }
+
+    return result;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index 7bd2434f62..b6df375c8b 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -601,7 +601,14 @@ HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick
 static Uint32
 HIDAPI_DriverPS4_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    return SDL_JOYCAP_LED;
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
+    Uint32 result = 0;
+
+    if (ctx->enhanced_mode && ctx->effects_supported) {
+        result |= SDL_JOYCAP_LED | SDL_JOYCAP_RUMBLE;
+    }
+
+    return result;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c
index ff8fc66a0a..c52c6df89d 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps5.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -666,7 +666,14 @@ HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick
 static Uint32
 HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    return SDL_JOYCAP_LED;
+    SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
+    Uint32 result = 0;
+
+    if (ctx->enhanced_mode) {
+        result |= SDL_JOYCAP_LED | SDL_JOYCAP_RUMBLE;
+    }
+
+    return result;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_stadia.c b/src/joystick/hidapi/SDL_hidapi_stadia.c
index f6ddd350ef..0df431555a 100644
--- a/src/joystick/hidapi/SDL_hidapi_stadia.c
+++ b/src/joystick/hidapi/SDL_hidapi_stadia.c
@@ -129,7 +129,7 @@ HIDAPI_DriverStadia_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joysti
 static Uint32
 HIDAPI_DriverStadia_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    return 0;
+    return SDL_JOYCAP_RUMBLE;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index a12aa267da..9341d1d8f8 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -1109,8 +1109,15 @@ HIDAPI_DriverSwitch_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joysti
 static Uint32
 HIDAPI_DriverSwitch_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    /* Doesn't have an RGB LED, so don't return SDL_JOYCAP_LED here */
-    return 0;
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
+    Uint32 result = 0;
+
+    if (!ctx->m_bInputOnly) {
+        /* Doesn't have an RGB LED, so don't return SDL_JOYCAP_LED here */
+        result |= SDL_JOYCAP_RUMBLE;
+    }
+
+    return result;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c
index 13911ff620..39cbbfa99b 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c
@@ -207,7 +207,7 @@ static Uint32
 HIDAPI_DriverXbox360_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
     /* Doesn't have an RGB LED, so don't return SDL_JOYCAP_LED here */
-    return 0;
+    return SDL_JOYCAP_RUMBLE;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/src/joystick/hidapi/SDL_hidapi_xbox360w.c
index 9603e32e9e..ae8bbdcafd 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360w.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360w.c
@@ -177,7 +177,7 @@ static Uint32
 HIDAPI_DriverXbox360W_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
     /* Doesn't have an RGB LED, so don't return SDL_JOYCAP_LED here */
-    return 0;
+    return SDL_JOYCAP_RUMBLE;
 }
 
 static int
diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c
index a96740541e..097deac5bd 100644
--- a/src/joystick/hidapi/SDL_hidapi_xboxone.c
+++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c
@@ -428,6 +428,11 @@ HIDAPI_DriverXboxOne_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joys
     SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
     Uint32 result = 0;
 
+    result |= SDL_JOYCAP_RUMBLE;
+    if (ctx->has_trigger_rumble) {
+        result |= SDL_JOYCAP_RUMBLE_TRIGGERS;
+    }
+
     if (ctx->has_color_led) {
         result |= SDL_JOYCAP_LED;
     }
diff --git a/src/joystick/iphoneos/SDL_mfijoystick.m b/src/joystick/iphoneos/SDL_mfijoystick.m
index e15df71a25..08ed418a10 100644
--- a/src/joystick/iphoneos/SDL_mfijoystick.m
+++ b/src/joystick/iphoneos/SDL_mfijoystick.m
@@ -1331,7 +1331,6 @@ -(void)cleanup
 {
     Uint32 result = 0;
 
-#ifdef ENABLE_MFI_LIGHT
     @autoreleasepool {
         SDL_JoystickDeviceItem *device = joystick->hwdata;
 
@@ -1341,13 +1340,25 @@ -(void)cleanup
 
         if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) {
             GCController *controller = device->controller;
-            GCDeviceLight *light = controller.light;
-            if (light) {
+#ifdef ENABLE_MFI_LIGHT
+            if (controller.light) {
                 result |= SDL_JOYCAP_LED;
             }
+#endif /* ENABLE_MFI_LIGHT */
+
+#ifdef ENABLE_MFI_RUMBLE
+            if (controller.haptics) {
+                for (GCHapticsLocality locality in controller.haptics.supportedLocalities) {
+                    if ([locality isEqualToString:GCHapticsLocalityHandles]) {
+                        result |= SDL_JOYCAP_RUMBLE;
+                    } else if ([locality isEqualToString:GCHapticsLocalityTriggers]) {
+                        result |= SDL_JOYCAP_RUMBLE_TRIGGERS;
+                    }
+                }
+            }
+#endif /* ENABLE_MFI_RUMBLE */
         }
     }
-#endif /* ENABLE_MFI_LIGHT */
 
     return result;
 }
diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index f04924884c..56019cc8fe 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -1193,7 +1193,13 @@ LINUX_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16
 static Uint32
 LINUX_JoystickGetCapabilities(SDL_Joystick *joystick)
 {
-    return 0;
+    Uint32 result = 0;
+
+    if (joystick->hwdata->ff_rumble || joystick->hwdata->ff_sine) {
+        result |= SDL_JOYCAP_RUMBLE;
+    }
+
+    return result;
 }
 
 static int
diff --git a/src/joystick/vita/SDL_sysjoystick.c b/src/joystick/vita/SDL_sysjoystick.c
index 1d80207a42..387679f366 100644
--- a/src/joystick/vita/SDL_sysjoystick.c
+++ b/src/joystick/vita/SDL_sysjoystick.c
@@ -368,8 +368,8 @@ VITA_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left, Uint16 right)
 static Uint32
 VITA_JoystickGetCapabilities(SDL_Joystick *joystick)
 {
-    // always return LED supported for now
-    return SDL_JOYCAP_LED;
+    // always return LED and rumble supported for now
+    return SDL_JOYCAP_LED | SDL_JOYCAP_RUMBLE;
 }
 
 
diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c
index 43a5b49f44..16deb512a9 100644
--- a/src/joystick/windows/SDL_dinputjoystick.c
+++ b/src/joystick/windows/SDL_dinputjoystick.c
@@ -927,7 +927,13 @@ SDL_DINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble,
 Uint32
 SDL_DINPUT_JoystickGetCapabilities(SDL_Joystick * joystick)
 {
-    return 0;
+    Uint32 result = 0;
+
+    if (joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) {
+        result |= SDL_JOYCAP_RUMBLE;
+    }
+
+    return result;
 }
 
 static Uint8
diff --git a/src/joystick/windows/SDL_rawinputjoystick.c b/src/joystick/windows/SDL_rawinputjoystick.c
index d66fe00d32..5765686abb 100644
--- a/src/joystick/windows/SDL_rawinputjoystick.c
+++ b/src/joystick/windows/SDL_rawinputjoystick.c
@@ -40,6 +40,7 @@
 #include "SDL_timer.h"
 #include "../usb_ids.h"
 #include "../SDL_sysjoystick.h"
+#include "../controller_type.h"
 #include "../../core/windows/SDL_windows.h"
 #include "../../core/windows/SDL_hid.h"
 #include "../hidapi/SDL_hidapijoystick_c.h"
@@ -102,6 +103,7 @@ typedef struct _SDL_RAWINPUT_Device
     Uint16 version;
     SDL_JoystickGUID guid;
     SDL_bool is_xinput;
+    SDL_bool is_xboxone;
     PHIDP_PREPARSED_DATA preparsed_data;
 
     HANDLE hDevice;
@@ -114,6 +116,7 @@ typedef struct _SDL_RAWINPUT_Device
 struct joystick_hwdata
 {
     SDL_bool is_xinput;
+    SDL_bool is_xboxone;
     PHIDP_PREPARSED_DATA preparsed_data;
     ULONG max_data_length;
     HIDP_DATA *data;
@@ -705,6 +708,7 @@ RAWINPUT_AddDevice(HANDLE hDevice)
     device->product_id = (Uint16)rdi.hid.dwProductId;
     device->version = (Uint16)rdi.hid.dwVersionNumber;
     device->is_xinput = SDL_TRUE;
+    device->is_xboxone = GuessControllerType(device->vendor_id, device->product_id) == k_eControllerType_XBoxOneController;
 
     {
         const Uint16 vendor = device->vendor_id;
@@ -1054,6 +1058,7 @@ RAWINPUT_JoystickOpen(SDL_Joystick *joystick, int device_index)
     }
 
     ctx->is_xinput = device->is_xinput;
+    ctx->is_xboxone = device->is_xboxone;
     ctx->preparsed_data = device->preparsed_data;
     ctx->max_data_length = SDL_HidP_MaxDataListLength(HidP_Input, ctx->preparsed_data);
     ctx->data = (HIDP_DATA *)SDL_malloc(ctx->max_data_length * sizeof(*ctx->data));
@@ -1280,6 +1285,25 @@ RAWINPUT_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint
 static Uint32
 RAWINPUT_JoystickGetCapabilities(SDL_Joystick *joystick)
 {
+    RAWINPUT_DeviceContext *ctx = joystick->hwdata;
+    Uint32 result = 0;
+
+#ifdef SDL_JOYSTICK_RAWINPUT_XINPUT
+    if (ctx->is_xinput) {
+        result |= SDL_JOYCAP_RUMBLE;
+    }
+#endif
+
+#ifdef SDL_JOYSTICK_RAWINPUT_WGI
+    if (ctx->is_xinput) {
+        result |= SDL_JOYCAP_RUMBLE;
+
+        if (ctx->is_xboxone) {
+            result |= SDL_JOYCAP_RUMBLE_TRIGGERS;
+        }
+    }
+#endif
+
     return 0;
 }
 
diff --git a/src/joystick/windows/SDL_windows_gaming_input.c b/src/joystick/windows/SDL_windows_gaming_input.c
index 475b6cdcb1..33b4eb5a0b 100644
--- a/src/joystick/windows/SDL_windows_gaming_input.c
+++ b/src/joystick/windows/SDL_windows_gaming_input.c
@@ -668,7 +668,14 @@ WGI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 ri
 static Uint32
 WGI_JoystickGetCapabilities(SDL_Joystick *joystick)
 {
-    return 0;
+    struct joystick_hwdata *hwdata = joystick->hwdata;
+
+    if (hwdata->gamepad) {
+        /* FIXME: Can WGI even tell us if trigger rumble is supported? */
+        return SDL_JOYCAP_RUMBLE | SDL_JOYCAP_RUMBLE_TRIGGERS;
+    } else {
+        return 0;
+    }
 }
 
 static int
diff --git a/src/joystick/windows/SDL_xinputjoystick.c b/src/joystick/windows/SDL_xinputjoystick.c
index 350cace31f..b265e3fba5 100644
--- a/src/joystick/windows/SDL_xinputjoystick.c
+++ b/src/joystick/windows/SDL_xinputjoystick.c
@@ -506,7 +506,7 @@ SDL_XINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble,
 Uint32
 SDL_XINPUT_JoystickGetCapabilities(SDL_Joystick * joystick)
 {
-    return 0;
+    return SDL_JOYCAP_RUMBLE;
 }
 
 void