SDL: Separated PS4/PS5 effects support into individual capabilities

From 5b3b7e6e7c69df020118307f40ddd189aec680c7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 23 Sep 2022 16:46:36 -0700
Subject: [PATCH] Separated PS4/PS5 effects support into individual
 capabilities

---
 src/joystick/hidapi/SDL_hidapi_ps4.c |  60 +++++++++----
 src/joystick/hidapi/SDL_hidapi_ps5.c | 124 +++++++++++++++++----------
 2 files changed, 120 insertions(+), 64 deletions(-)

diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index d5b3d29bd30..04c3d4031f0 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -126,9 +126,11 @@ typedef struct {
     SDL_bool is_dongle;
     SDL_bool is_bluetooth;
     SDL_bool official_controller;
-    SDL_bool effects_supported;
     SDL_bool sensors_supported;
+    SDL_bool lightbar_supported;
+    SDL_bool vibration_supported;
     SDL_bool touchpad_supported;
+    SDL_bool effects_supported;
     SDL_bool enhanced_mode;
     SDL_bool report_sensors;
     SDL_bool report_touchpad;
@@ -306,8 +308,9 @@ HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device)
 
     /* Get the device capabilities */
     if (device->vendor_id == USB_VENDOR_SONY) {
-        ctx->effects_supported = SDL_TRUE;
         ctx->sensors_supported = SDL_TRUE;
+        ctx->lightbar_supported = SDL_TRUE;
+        ctx->vibration_supported = SDL_TRUE;
         ctx->touchpad_supported = SDL_TRUE;
     } else if ((size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data))) == 48 &&
                data[2] == 0x27) {
@@ -316,20 +319,24 @@ HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device)
 #ifdef DEBUG_PS4_PROTOCOL
         HIDAPI_DumpPacket("PS4 capabilities: size = %d", data, size);
 #endif
-        if ((capabilities & 0x0C) != 0) {
-            ctx->effects_supported = SDL_TRUE;
-        }
         if ((capabilities & 0x02) != 0) {
             ctx->sensors_supported = SDL_TRUE;
         }
+        if ((capabilities & 0x04) != 0) {
+            ctx->lightbar_supported = SDL_TRUE;
+        }
+        if ((capabilities & 0x08) != 0) {
+            ctx->vibration_supported = SDL_TRUE;
+        }
         if ((capabilities & 0x40) != 0) {
             ctx->touchpad_supported = SDL_TRUE;
         }
     } else if (device->vendor_id == USB_VENDOR_RAZER) {
         /* The Razer Raiju doesn't respond to the detection protocol, but has a touchpad and vibration */
-        ctx->effects_supported = SDL_TRUE;
+        ctx->vibration_supported = SDL_TRUE;
         ctx->touchpad_supported = SDL_TRUE;
     }
+    ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported);
 
     device->type = SDL_CONTROLLER_TYPE_PS4;
     if (device->vendor_id == USB_VENDOR_SONY) {
@@ -516,22 +523,26 @@ HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device)
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
     DS4EffectsState_t effects;
 
-    if (!ctx->enhanced_mode) {
+    if (!ctx->enhanced_mode || !ctx->effects_supported) {
         return SDL_Unsupported();
     }
 
     SDL_zero(effects);
 
-    effects.ucRumbleLeft = ctx->rumble_left;
-    effects.ucRumbleRight = ctx->rumble_right;
+    if (ctx->vibration_supported) {
+        effects.ucRumbleLeft = ctx->rumble_left;
+        effects.ucRumbleRight = ctx->rumble_right;
+    }
 
-    /* Populate the LED state with the appropriate color from our lookup table */
-    if (ctx->color_set) {
-        effects.ucLedRed = ctx->led_red;
-        effects.ucLedGreen = ctx->led_green;
-        effects.ucLedBlue = ctx->led_blue;
-    } else {
-        SetLedsForPlayerIndex(&effects, ctx->player_index);
+    if (ctx->lightbar_supported) {
+        /* Populate the LED state with the appropriate color from our lookup table */
+        if (ctx->color_set) {
+            effects.ucLedRed = ctx->led_red;
+            effects.ucLedGreen = ctx->led_green;
+            effects.ucLedBlue = ctx->led_blue;
+        } else {
+            SetLedsForPlayerIndex(&effects, ctx->player_index);
+        }
     }
     return HIDAPI_DriverPS4_SendJoystickEffect(device, ctx->joystick, &effects, sizeof(effects));
 }
@@ -634,6 +645,10 @@ HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystic
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
 
+    if (!ctx->vibration_supported) {
+        return SDL_Unsupported();
+    }
+
     ctx->rumble_left = (low_frequency_rumble >> 8);
     ctx->rumble_right = (high_frequency_rumble >> 8);
 
@@ -652,8 +667,13 @@ HIDAPI_DriverPS4_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick
     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;
+    if (ctx->enhanced_mode) {
+        if (ctx->lightbar_supported) {
+            result |= SDL_JOYCAP_LED;
+        }
+        if (ctx->vibration_supported) {
+            result |= SDL_JOYCAP_RUMBLE;
+        }
     }
 
     return result;
@@ -664,6 +684,10 @@ HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystic
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
 
+    if (!ctx->lightbar_supported) {
+        return SDL_Unsupported();
+    }
+
     ctx->color_set = SDL_TRUE;
     ctx->led_red = red;
     ctx->led_green = green;
diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c
index 1508d4edfff..6f26c3b91f2 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps5.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -200,9 +200,12 @@ typedef struct {
     SDL_Joystick *joystick;
     SDL_bool is_bluetooth;
     SDL_bool use_alternate_report;
-    SDL_bool effects_supported;
     SDL_bool sensors_supported;
+    SDL_bool lightbar_supported;
+    SDL_bool vibration_supported;
+    SDL_bool playerled_supported;
     SDL_bool touchpad_supported;
+    SDL_bool effects_supported;
     SDL_bool enhanced_mode;
     SDL_bool report_sensors;
     SDL_bool report_touchpad;
@@ -410,28 +413,38 @@ HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
 
     /* Get the device capabilities */
     if (device->vendor_id == USB_VENDOR_SONY) {
-        ctx->effects_supported = SDL_TRUE;
         ctx->sensors_supported = SDL_TRUE;
+        ctx->lightbar_supported = SDL_TRUE;
+        ctx->vibration_supported = SDL_TRUE;
+        ctx->playerled_supported = SDL_TRUE;
         ctx->touchpad_supported = SDL_TRUE;
     } else if ((size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data))) == 48 &&
                data[2] == 0x28) {
         Uint8 capabilities = data[4];
+        Uint8 capabilities2 = data[20];
 
 #ifdef DEBUG_PS5_PROTOCOL
         HIDAPI_DumpPacket("PS5 capabilities: size = %d", data, size);
 #endif
-        if ((capabilities & 0x0C) != 0) {
-            ctx->effects_supported = SDL_TRUE;
-        }
         if ((capabilities & 0x02) != 0) {
             ctx->sensors_supported = SDL_TRUE;
         }
+        if ((capabilities & 0x04) != 0) {
+            ctx->lightbar_supported = SDL_TRUE;
+        }
+        if ((capabilities & 0x08) != 0) {
+            ctx->vibration_supported = SDL_TRUE;
+        }
         if ((capabilities & 0x40) != 0) {
             ctx->touchpad_supported = SDL_TRUE;
         }
+        if ((capabilities2 & 0x80) != 0) {
+            ctx->playerled_supported = SDL_TRUE;
+        }
 
         ctx->use_alternate_report = SDL_TRUE;
     }
+    ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported);
 
     device->type = SDL_CONTROLLER_TYPE_PS5;
     if (device->vendor_id == USB_VENDOR_SONY) {
@@ -571,7 +584,7 @@ HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_mask)
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
     DS5EffectsState_t effects;
 
-    if (!ctx->enhanced_mode) {
+    if (!ctx->enhanced_mode || !ctx->effects_supported) {
         return SDL_Unsupported();
     }
 
@@ -586,52 +599,58 @@ HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_mask)
         }
     }
 
-    if (ctx->rumble_left || ctx->rumble_right) {
-        if (ctx->firmware_version < 0x0224) {
-            effects.ucEnableBits1 |= 0x01; /* Enable rumble emulation */
+    if (ctx->vibration_supported) {
+        if (ctx->rumble_left || ctx->rumble_right) {
+            if (ctx->firmware_version < 0x0224) {
+                effects.ucEnableBits1 |= 0x01; /* Enable rumble emulation */
 
-            /* Shift to reduce effective rumble strength to match Xbox controllers */
-            effects.ucRumbleLeft = ctx->rumble_left >> 1;
-            effects.ucRumbleRight = ctx->rumble_right >> 1;
-        } else {
-            effects.ucEnableBits3 |= 0x04; /* Enable improved rumble emulation on 2.24 firmware and newer */
+                /* Shift to reduce effective rumble strength to match Xbox controllers */
+                effects.ucRumbleLeft = ctx->rumble_left >> 1;
+                effects.ucRumbleRight = ctx->rumble_right >> 1;
+            } else {
+                effects.ucEnableBits3 |= 0x04; /* Enable improved rumble emulation on 2.24 firmware and newer */
 
-            effects.ucRumbleLeft = ctx->rumble_left;
-            effects.ucRumbleRight = ctx->rumble_right;
+                effects.ucRumbleLeft = ctx->rumble_left;
+                effects.ucRumbleRight = ctx->rumble_right;
+            }
+            effects.ucEnableBits1 |= 0x02; /* Disable audio haptics */
+        } else {
+            /* Leaving emulated rumble bits off will restore audio haptics */
         }
-        effects.ucEnableBits1 |= 0x02; /* Disable audio haptics */
-    } else {
-        /* Leaving emulated rumble bits off will restore audio haptics */
-    }
 
-    if ((effect_mask & k_EDS5EffectRumbleStart) != 0) {
-        effects.ucEnableBits1 |= 0x02; /* Disable audio haptics */
-    }
-    if ((effect_mask & k_EDS5EffectRumble) != 0) {
-        /* Already handled above */
-    }
-    if ((effect_mask & k_EDS5EffectLEDReset) != 0) {
-        effects.ucEnableBits2 |= 0x08; /* Reset LED state */
+        if ((effect_mask & k_EDS5EffectRumbleStart) != 0) {
+            effects.ucEnableBits1 |= 0x02; /* Disable audio haptics */
+        }
+        if ((effect_mask & k_EDS5EffectRumble) != 0) {
+            /* Already handled above */
+        }
     }
-    if ((effect_mask & k_EDS5EffectLED) != 0) {
-        effects.ucEnableBits2 |= 0x04; /* Enable LED color */
-
-        /* Populate the LED state with the appropriate color from our lookup table */
-        if (ctx->color_set) {
-            effects.ucLedRed = ctx->led_red;
-            effects.ucLedGreen = ctx->led_green;
-            effects.ucLedBlue = ctx->led_blue;
-        } else {
-            SetLedsForPlayerIndex(&effects, ctx->player_index);
+    if (ctx->lightbar_supported) {
+        if ((effect_mask & k_EDS5EffectLEDReset) != 0) {
+            effects.ucEnableBits2 |= 0x08; /* Reset LED state */
+        }
+        if ((effect_mask & k_EDS5EffectLED) != 0) {
+            effects.ucEnableBits2 |= 0x04; /* Enable LED color */
+
+            /* Populate the LED state with the appropriate color from our lookup table */
+            if (ctx->color_set) {
+                effects.ucLedRed = ctx->led_red;
+                effects.ucLedGreen = ctx->led_green;
+                effects.ucLedBlue = ctx->led_blue;
+            } else {
+                SetLedsForPlayerIndex(&effects, ctx->player_index);
+            }
         }
     }
-    if ((effect_mask & k_EDS5EffectPadLights) != 0) {
-        effects.ucEnableBits2 |= 0x10; /* Enable touchpad lights */
+    if (ctx->playerled_supported) {
+        if ((effect_mask & k_EDS5EffectPadLights) != 0) {
+            effects.ucEnableBits2 |= 0x10; /* Enable touchpad lights */
 
-        if (ctx->player_lights) {
-            SetLightsForPlayerIndex(&effects, ctx->player_index);
-        } else {
-            effects.ucPadLights = 0x00;
+            if (ctx->player_lights) {
+                SetLightsForPlayerIndex(&effects, ctx->player_index);
+            } else {
+                effects.ucPadLights = 0x00;
+            }
         }
     }
     if ((effect_mask & k_EDS5EffectMicLight) != 0) {
@@ -795,6 +814,10 @@ HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystic
 {
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
 
+    if (!ctx->vibration_supported) {
+        return SDL_Unsupported();
+    }
+
     if (!ctx->rumble_left && !ctx->rumble_right) {
         HIDAPI_DriverPS5_UpdateEffects(device, k_EDS5EffectRumbleStart);
     }
@@ -817,8 +840,13 @@ HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
     Uint32 result = 0;
 
-    if (ctx->enhanced_mode && ctx->effects_supported) {
-        result |= SDL_JOYCAP_LED | SDL_JOYCAP_RUMBLE;
+    if (ctx->enhanced_mode) {
+        if (ctx->lightbar_supported) {
+            result |= SDL_JOYCAP_LED;
+        }
+        if (ctx->vibration_supported) {
+            result |= SDL_JOYCAP_RUMBLE;
+        }
     }
 
     return result;
@@ -829,6 +857,10 @@ HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystic
 {
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
 
+    if (!ctx->lightbar_supported) {
+        return SDL_Unsupported();
+    }
+
     ctx->color_set = SDL_TRUE;
     ctx->led_red = red;
     ctx->led_green = green;