Commit a30adae5676a20729d26cb9616c461853ea53c4a

Sam Lantinga 2020-12-22T20:58:32

Make it possible to turn on PS4 rumble effects at runtime using the hint

diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index a207571..4e32355 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -30,6 +30,7 @@
 #include "SDL_timer.h"
 #include "SDL_joystick.h"
 #include "SDL_gamecontroller.h"
+#include "../../SDL_hints_c.h"
 #include "../SDL_sysjoystick.h"
 #include "SDL_hidapijoystick_c.h"
 #include "SDL_hidapi_rumble.h"
@@ -119,11 +120,14 @@ typedef struct {
 } IMUCalibrationData;
 
 typedef struct {
+    SDL_HIDAPI_Device *device;
+    SDL_Joystick *joystick;
     SDL_bool is_dongle;
     SDL_bool is_bluetooth;
     SDL_bool official_controller;
     SDL_bool audio_supported;
     SDL_bool effects_supported;
+    SDL_bool enhanced_mode;
     SDL_bool report_sensors;
     SDL_bool hardware_calibration;
     IMUCalibrationData calibration[6];
@@ -387,6 +391,10 @@ HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device)
         return SDL_Unsupported();
     }
 
+    if (!ctx->enhanced_mode) {
+        return SDL_Unsupported();
+    }
+
     SDL_zero(data);
 
     if (ctx->is_bluetooth) {
@@ -433,6 +441,32 @@ HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device)
 }
 
 static void
+HIDAPI_DriverPS4_SetEnhancedMode(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
+
+    if (!ctx->enhanced_mode) {
+        ctx->enhanced_mode = SDL_TRUE;
+
+        SDL_PrivateJoystickAddTouchpad(joystick, 2);
+        SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO);
+        SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL);
+
+        HIDAPI_DriverPS4_UpdateEffects(device);
+    }
+}
+
+static void SDLCALL SDL_PS4RumbleHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
+
+    /* This is a one-way trip, you can't switch the controller back to simple report mode */
+    if (SDL_GetStringBoolean(hint, SDL_FALSE)) {
+        HIDAPI_DriverPS4_SetEnhancedMode(ctx->device, ctx->joystick);
+    }
+}
+
+static void
 HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
@@ -451,12 +485,15 @@ static SDL_bool
 HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
     SDL_DriverPS4_Context *ctx;
+    SDL_bool enhanced_mode = SDL_FALSE;
 
     ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx));
     if (!ctx) {
         SDL_OutOfMemory();
         return SDL_FALSE;
     }
+    ctx->device = device;
+    ctx->joystick = joystick;
 
     device->dev = hid_open_path(device->path, 0);
     if (!device->dev) {
@@ -471,6 +508,7 @@ HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
     if (ctx->is_dongle) {
         ctx->is_bluetooth = SDL_FALSE;
         ctx->official_controller = SDL_TRUE;
+        enhanced_mode = SDL_TRUE;
     } else if (device->vendor_id == USB_VENDOR_SONY) {
         Uint8 data[USB_PACKET_LENGTH];
         int size;
@@ -484,13 +522,30 @@ HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
                 data[6], data[5], data[4], data[3], data[2], data[1]);
             joystick->serial = SDL_strdup(serial);
             ctx->is_bluetooth = SDL_FALSE;
+            enhanced_mode = SDL_TRUE;
         } else {
             ctx->is_bluetooth = SDL_TRUE;
+
+            /* Read a report to see if we're in enhanced mode */
+            size = hid_read_timeout(device->dev, data, sizeof(data), 16);
+#ifdef DEBUG_PS4_PROTOCOL
+            if (size > 0) {
+                HIDAPI_DumpPacket("PS4 first packet: size = %d", data, size);
+            } else {
+                SDL_Log("PS4 first packet: size = %d\n", size);
+            }
+#endif
+            if (size > 0 &&
+                data[0] >= k_EPS4ReportIdBluetoothState1 &&
+                data[0] <= k_EPS4ReportIdBluetoothState9) {
+                enhanced_mode = SDL_TRUE;
+            }
         }
         ctx->official_controller = SDL_TRUE;
     } else {
         /* Third party controllers appear to all be wired */
         ctx->is_bluetooth = SDL_FALSE;
+        enhanced_mode = SDL_TRUE;
     }
 #ifdef DEBUG_PS4
     SDL_Log("PS4 dongle = %s, bluetooth = %s\n", ctx->is_dongle ? "TRUE" : "FALSE", ctx->is_bluetooth ? "TRUE" : "FALSE");
@@ -503,28 +558,42 @@ HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
     }
 
     if (HIDAPI_DriverPS4_CanRumble(device->vendor_id, device->product_id)) {
-        if (ctx->is_bluetooth) {
-            ctx->effects_supported = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, SDL_FALSE);
-        } else {
-            ctx->effects_supported = SDL_TRUE;
+        ctx->effects_supported = SDL_TRUE;
+    }
+
+    if (!joystick->serial && device->serial && SDL_strlen(device->serial) == 12) {
+        int i, j;
+        char serial[18];
+
+        j = -1;
+        for (i = 0; i < 12; i += 2) {
+            j += 1;
+            SDL_memcpy(&serial[j], &device->serial[i], 2);
+            j += 2;
+            serial[j] = '-';
         }
+        serial[j] = '\0';
+
+        joystick->serial = SDL_strdup(serial);
     }
 
     /* Initialize player index (needed for setting LEDs) */
     ctx->player_index = SDL_JoystickGetPlayerIndex(joystick);
 
-    /* Initialize LED and effect state */
-    HIDAPI_DriverPS4_UpdateEffects(device);
-
-    /* Initialize the joystick capabilities */
+    /* Initialize the joystick capabilities
+     *
+     * We can't dynamically add the touchpad button, so always report it here
+     */
     joystick->nbuttons = 16;
     joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
-    joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
-
-    SDL_PrivateJoystickAddTouchpad(joystick, 2);
-    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO);
-    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL);
+    joystick->epowerlevel = ctx->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED;
 
+    if (enhanced_mode) {
+        HIDAPI_DriverPS4_SetEnhancedMode(device, joystick);
+    } else {
+        SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
+                            SDL_PS4RumbleHintChanged, ctx);
+    }
     return SDL_TRUE;
 }
 
@@ -569,6 +638,10 @@ HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joysti
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
 
+    if (!ctx->enhanced_mode) {
+        return SDL_Unsupported();
+    }
+
     if (enabled) {
         HIDAPI_DriverPS4_LoadCalibrationData(device);
     }
@@ -729,7 +802,7 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
     SDL_Joystick *joystick = NULL;
-    Uint8 data[USB_PACKET_LENGTH];
+    Uint8 data[USB_PACKET_LENGTH*2];
     int size;
 
     if (device->num_joysticks > 0) {
@@ -756,6 +829,10 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
         case k_EPS4ReportIdBluetoothState7:
         case k_EPS4ReportIdBluetoothState8:
         case k_EPS4ReportIdBluetoothState9:
+            if (!ctx->enhanced_mode) {
+                /* This is the extended report, we can enable effects now */
+                HIDAPI_DriverPS4_SetEnhancedMode(device, joystick);
+            }
             /* Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present */
             if (data[1] & 0x80) {
                 HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t*)&data[3]);
@@ -779,6 +856,11 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
 static void
 HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
+
+    SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
+                        SDL_PS4RumbleHintChanged, ctx);
+
     hid_close(device->dev);
     device->dev = NULL;
 
diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c
index 4aa0f93..5b7553b 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps5.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -154,7 +154,7 @@ typedef struct {
     SDL_HIDAPI_Device *device;
     SDL_Joystick *joystick;
     SDL_bool is_bluetooth;
-    SDL_bool effects_supported;
+    SDL_bool enhanced_mode;
     SDL_bool report_sensors;
     SDL_bool hardware_calibration;
     IMUCalibrationData calibration[6];
@@ -382,7 +382,7 @@ HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, EDS5Effect effect)
     int *pending_size;
     int maximum_size;
 
-    if (!ctx->effects_supported) {
+    if (!ctx->enhanced_mode) {
         return SDL_Unsupported();
     }
 
@@ -508,12 +508,12 @@ HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_HIDAPI_Device *device)
 }
 
 static void
-HIDAPI_DriverPS5_SetEffectsSupported(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+HIDAPI_DriverPS5_SetEnhancedMode(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
 
-    if (!ctx->effects_supported) {
-        ctx->effects_supported = SDL_TRUE;
+    if (!ctx->enhanced_mode) {
+        ctx->enhanced_mode = SDL_TRUE;
 
         SDL_PrivateJoystickAddTouchpad(joystick, 2);
         SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO);
@@ -530,7 +530,7 @@ static void SDLCALL SDL_PS5RumbleHintChanged(void *userdata, const char *name, c
 
     /* This is a one-way trip, you can't switch the controller back to simple report mode */
     if (SDL_GetStringBoolean(hint, SDL_FALSE)) {
-        HIDAPI_DriverPS5_SetEffectsSupported(ctx->device, ctx->joystick);
+        HIDAPI_DriverPS5_SetEnhancedMode(ctx->device, ctx->joystick);
     }
 }
 
@@ -556,7 +556,7 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
     SDL_DriverPS5_Context *ctx;
     Uint8 data[USB_PACKET_LENGTH*2];
     int size;
-    SDL_bool effects_supported = SDL_FALSE;
+    SDL_bool enhanced_mode = SDL_FALSE;
 
     ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx));
     if (!ctx) {
@@ -587,18 +587,18 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
     if (size == 64) {
         /* Connected over USB */
         ctx->is_bluetooth = SDL_FALSE;
-        effects_supported = SDL_TRUE;
+        enhanced_mode = SDL_TRUE;
     } else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) {
         /* Connected over Bluetooth, using enhanced reports */
         ctx->is_bluetooth = SDL_TRUE;
-        effects_supported = SDL_TRUE;
+        enhanced_mode = SDL_TRUE;
     } else {
         /* Connected over Bluetooth, using simple reports (DirectInput enabled) */
         ctx->is_bluetooth = SDL_TRUE;
-        effects_supported = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, SDL_FALSE);
+        enhanced_mode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, SDL_FALSE);
     }
 
-    if (effects_supported) {
+    if (enhanced_mode) {
         /* Read the serial number (Bluetooth address in reverse byte order)
            This will also enable enhanced reports over Bluetooth
         */
@@ -636,9 +636,10 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
      */
     joystick->nbuttons = 17;
     joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
+    joystick->epowerlevel = ctx->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED;
 
-    if (effects_supported) {
-        HIDAPI_DriverPS5_SetEffectsSupported(device, joystick);
+    if (enhanced_mode) {
+        HIDAPI_DriverPS5_SetEnhancedMode(device, joystick);
     } else {
         SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE,
                             SDL_PS5RumbleHintChanged, ctx);
@@ -691,7 +692,7 @@ HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joysti
 {
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
 
-    if (!ctx->effects_supported) {
+    if (!ctx->enhanced_mode) {
         return SDL_Unsupported();
     }
 
@@ -969,9 +970,9 @@ HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)
             }
             break;
         case k_EPS5ReportIdBluetoothState:
-            if (!ctx->effects_supported) {
+            if (!ctx->enhanced_mode) {
                 /* This is the extended report, we can enable effects now */
-                HIDAPI_DriverPS5_SetEffectsSupported(device, joystick);
+                HIDAPI_DriverPS5_SetEnhancedMode(device, joystick);
             }
             if (ctx->led_reset_state == k_EDS5LEDResetStatePending) {
                 HIDAPI_DriverPS5_CheckPendingLEDReset(device);