SDL: Make sure HID devices can be opened before making them available to the application

From aa2e2f4843567cfe4e6f2f4ffa3f0b74dd98f1ba Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 22 Sep 2022 18:22:17 -0700
Subject: [PATCH] Make sure HID devices can be opened before making them
 available to the application

This prevents a number of issues where devices are enumerated but not actually able to be opened, like https://github.com/libsdl-org/SDL/issues/5781.

We currently leave the devices open, allowing us to more easily do controller feature detection, protocol negotiation, detect dropped Bluetooth connections, etc. with the expectation that the application is likely to open the controllers shortly.
---
 src/hidapi/mac/hid.c                       |  12 +-
 src/joystick/hidapi/SDL_hidapi_combined.c  |   7 -
 src/joystick/hidapi/SDL_hidapi_gamecube.c  |  47 +--
 src/joystick/hidapi/SDL_hidapi_luna.c      |  55 ++--
 src/joystick/hidapi/SDL_hidapi_ps3.c       | 170 +++++-----
 src/joystick/hidapi/SDL_hidapi_ps4.c       | 253 +++++++-------
 src/joystick/hidapi/SDL_hidapi_ps5.c       | 267 ++++++++-------
 src/joystick/hidapi/SDL_hidapi_shield.c    |  72 ++--
 src/joystick/hidapi/SDL_hidapi_stadia.c    |  54 ++-
 src/joystick/hidapi/SDL_hidapi_steam.c     |  78 ++---
 src/joystick/hidapi/SDL_hidapi_switch.c    | 365 ++++++++-------------
 src/joystick/hidapi/SDL_hidapi_wii.c       | 214 +++++-------
 src/joystick/hidapi/SDL_hidapi_xbox360.c   |  60 ++--
 src/joystick/hidapi/SDL_hidapi_xbox360w.c  |  37 +--
 src/joystick/hidapi/SDL_hidapi_xboxone.c   | 116 ++++---
 src/joystick/hidapi/SDL_hidapijoystick.c   |  72 +++-
 src/joystick/hidapi/SDL_hidapijoystick_c.h |   4 +-
 17 files changed, 811 insertions(+), 1072 deletions(-)

diff --git a/src/hidapi/mac/hid.c b/src/hidapi/mac/hid.c
index 4ea6bdb6a3c1..35ca3803745e 100644
--- a/src/hidapi/mac/hid.c
+++ b/src/hidapi/mac/hid.c
@@ -130,7 +130,6 @@ struct hid_device_list_node
 
 static 	IOHIDManagerRef hid_mgr = 0x0;
 static 	struct hid_device_list_node *device_list = 0x0;
-static	int hid_input_monitoring_denied = 0;
 
 static hid_device *new_hid_device(void)
 {
@@ -522,11 +521,7 @@ struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
 	/* Set up the HID Manager if it hasn't been done */
 	if (hid_init() < 0)
 		return NULL;
-
-	/* If we don't have permission to open devices, don't enumerate them */
-	if (hid_input_monitoring_denied)
-		return NULL;
-
+	
 	/* give the IOHIDManager a chance to update itself */
 	process_pending_events();
 	
@@ -867,11 +862,6 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path, int bExclusive)
 				
 				return dev;
 			}
-			else if (ret == kIOReturnNotPermitted) {
-				/* This application doesn't have input monitoring permissions */
-				hid_input_monitoring_denied = 1;
-				goto return_error;
-			}
 			else {
 				goto return_error;
 			}
diff --git a/src/joystick/hidapi/SDL_hidapi_combined.c b/src/joystick/hidapi/SDL_hidapi_combined.c
index 04710b45e7b4..df092404ccb8 100644
--- a/src/joystick/hidapi/SDL_hidapi_combined.c
+++ b/src/joystick/hidapi/SDL_hidapi_combined.c
@@ -52,12 +52,6 @@ HIDAPI_DriverCombined_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *n
     return SDL_FALSE;
 }
 
-static const char *
-HIDAPI_DriverCombined_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
-{
-    return NULL;
-}
-
 static SDL_bool
 HIDAPI_DriverCombined_InitDevice(SDL_HIDAPI_Device *device)
 {
@@ -241,7 +235,6 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined =
     HIDAPI_DriverCombined_UnregisterHints,
     HIDAPI_DriverCombined_IsEnabled,
     HIDAPI_DriverCombined_IsSupportedDevice,
-    HIDAPI_DriverCombined_GetDeviceName,
     HIDAPI_DriverCombined_InitDevice,
     HIDAPI_DriverCombined_GetDevicePlayerIndex,
     HIDAPI_DriverCombined_SetDevicePlayerIndex,
diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c
index bbd7a05cbca8..d3bd2cb226ea 100644
--- a/src/joystick/hidapi/SDL_hidapi_gamecube.c
+++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c
@@ -89,12 +89,6 @@ HIDAPI_DriverGameCube_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *n
     return SDL_FALSE;
 }
 
-static const char *
-HIDAPI_DriverGameCube_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
-{
-    return "Nintendo GameCube Controller";
-}
-
 static void
 ResetAxisRange(SDL_DriverGameCube_Context *ctx, int joystick_index)
 {
@@ -151,18 +145,13 @@ HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
     SDL_EnableGameCubeAdaptors();
 #endif
 
+    HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller");
+
     ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
     if (!ctx) {
         SDL_OutOfMemory();
         return SDL_FALSE;
     }
-
-    device->dev = SDL_hid_open_path(device->path, 0);
-    if (!device->dev) {
-        SDL_free(ctx);
-        SDL_SetError("Couldn't open %s", device->path);
-        return SDL_FALSE;
-    }
     device->context = ctx;
 
     ctx->joysticks[0] = -1;
@@ -184,8 +173,9 @@ HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
     } else {
         /* This is all that's needed to initialize the device. Really! */
         if (SDL_hid_write(device->dev, &initMagic, sizeof(initMagic)) != sizeof(initMagic)) {
-            SDL_SetError("Couldn't initialize WUP-028");
-            goto error;
+            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                         "HIDAPI_DriverGameCube_InitDevice(): Couldn't initialize WUP-028");
+            return SDL_FALSE;
         }
 
         /* Wait for the adapter to initialize */
@@ -230,22 +220,6 @@ HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
                         SDL_GameControllerButtonReportingHintChanged, ctx);
 
     return SDL_TRUE;
-
-error:
-    SDL_LockMutex(device->dev_lock);
-    {
-        if (device->dev) {
-            SDL_hid_close(device->dev);
-            device->dev = NULL;
-        }
-        if (device->context) {
-            SDL_free(device->context);
-            device->context = NULL;
-        }
-    }
-    SDL_UnlockMutex(device->dev_lock);
-
-    return SDL_FALSE;
 }
 
 static int
@@ -565,16 +539,6 @@ HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
                         SDL_GameControllerButtonReportingHintChanged, ctx);
     SDL_DelHintCallback(SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE,
                         SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
-
-    SDL_LockMutex(device->dev_lock);
-    {
-        SDL_hid_close(device->dev);
-        device->dev = NULL;
-
-        SDL_free(device->context);
-        device->context = NULL;
-    }
-    SDL_UnlockMutex(device->dev_lock);
 }
 
 SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
@@ -585,7 +549,6 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
     HIDAPI_DriverGameCube_UnregisterHints,
     HIDAPI_DriverGameCube_IsEnabled,
     HIDAPI_DriverGameCube_IsSupportedDevice,
-    HIDAPI_DriverGameCube_GetDeviceName,
     HIDAPI_DriverGameCube_InitDevice,
     HIDAPI_DriverGameCube_GetDevicePlayerIndex,
     HIDAPI_DriverGameCube_SetDevicePlayerIndex,
diff --git a/src/joystick/hidapi/SDL_hidapi_luna.c b/src/joystick/hidapi/SDL_hidapi_luna.c
index 7808052a8b3c..0f8cfd48850f 100644
--- a/src/joystick/hidapi/SDL_hidapi_luna.c
+++ b/src/joystick/hidapi/SDL_hidapi_luna.c
@@ -72,15 +72,20 @@ HIDAPI_DriverLuna_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name,
     return (type == SDL_CONTROLLER_TYPE_AMAZON_LUNA) ? SDL_TRUE : SDL_FALSE;
 }
 
-static const char *
-HIDAPI_DriverLuna_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
-{
-    return "Amazon Luna Controller";
-}
-
 static SDL_bool
 HIDAPI_DriverLuna_InitDevice(SDL_HIDAPI_Device *device)
 {
+    SDL_DriverLuna_Context *ctx;
+
+    HIDAPI_SetDeviceName(device, "Amazon Luna Controller");
+
+    ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        SDL_OutOfMemory();
+        return SDL_FALSE;
+    }
+    device->context = ctx;
+
     return HIDAPI_JoystickConnected(device, NULL);
 }
 
@@ -98,27 +103,14 @@ HIDAPI_DriverLuna_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID
 static SDL_bool
 HIDAPI_DriverLuna_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    SDL_DriverLuna_Context *ctx;
-
-    ctx = (SDL_DriverLuna_Context *)SDL_calloc(1, sizeof(*ctx));
-    if (!ctx) {
-        SDL_OutOfMemory();
-        return SDL_FALSE;
-    }
+    SDL_DriverLuna_Context *ctx = (SDL_DriverLuna_Context *)device->context;
 
-    device->dev = SDL_hid_open_path(device->path, 0);
-    if (!device->dev) {
-        SDL_SetError("Couldn't open %s", device->path);
-        SDL_free(ctx);
-        return SDL_FALSE;
-    }
-    device->context = ctx;
+    SDL_zeroa(ctx->last_state);
 
     /* Initialize the joystick capabilities */
     joystick->nbuttons = SDL_CONTROLLER_NUM_LUNA_BUTTONS;
     joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
     joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL;
-    joystick->serial = NULL;
 
     return SDL_TRUE;
 }
@@ -406,8 +398,7 @@ HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)
 
     if (device->num_joysticks > 0) {
         joystick = SDL_JoystickFromInstanceID(device->joysticks[0]);
-    }
-    if (!joystick) {
+    } else {
         return SDL_FALSE;
     }
 
@@ -415,6 +406,10 @@ HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)
 #ifdef DEBUG_LUNA_PROTOCOL
         HIDAPI_DumpPacket("Amazon Luna packet: size = %d", data, size);
 #endif
+        if (!joystick) {
+            continue;
+        }
+
         switch (size) {
         case 10:
             HIDAPI_DriverLuna_HandleUSBStatePacket(joystick, ctx, data, size);
@@ -427,7 +422,7 @@ HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)
 
     if (size < 0) {
         /* Read error, device is disconnected */
-        HIDAPI_JoystickDisconnected(device, joystick->instance_id);
+        HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
     }
     return (size >= 0);
 }
@@ -435,17 +430,6 @@ HIDAPI_DriverLuna_UpdateDevice(SDL_HIDAPI_Device *device)
 static void
 HIDAPI_DriverLuna_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    SDL_LockMutex(device->dev_lock);
-    {
-        if (device->dev) {
-            SDL_hid_close(device->dev);
-            device->dev = NULL;
-        }
-
-        SDL_free(device->context);
-        device->context = NULL;
-    }
-    SDL_UnlockMutex(device->dev_lock);
 }
 
 static void
@@ -461,7 +445,6 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna =
     HIDAPI_DriverLuna_UnregisterHints,
     HIDAPI_DriverLuna_IsEnabled,
     HIDAPI_DriverLuna_IsSupportedDevice,
-    HIDAPI_DriverLuna_GetDeviceName,
     HIDAPI_DriverLuna_InitDevice,
     HIDAPI_DriverLuna_GetDevicePlayerIndex,
     HIDAPI_DriverLuna_SetDevicePlayerIndex,
diff --git a/src/joystick/hidapi/SDL_hidapi_ps3.c b/src/joystick/hidapi/SDL_hidapi_ps3.c
index 1af5c9b77ecc..99bb613ceaa8 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps3.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps3.c
@@ -52,10 +52,10 @@ typedef struct {
     SDL_bool is_shanwan;
     SDL_bool report_sensors;
     SDL_bool effects_updated;
-    Uint8 last_state[USB_PACKET_LENGTH];
     int player_index;
     Uint8 rumble_left;
     Uint8 rumble_right;
+    Uint8 last_state[USB_PACKET_LENGTH];
 } SDL_DriverPS3_Context;
 
 
@@ -76,16 +76,16 @@ HIDAPI_DriverPS3_UnregisterHints(SDL_HintCallback callback, void *userdata)
 static SDL_bool
 HIDAPI_DriverPS3_IsEnabled(void)
 {
+    SDL_bool default_value;
+
 #if defined(__MACOSX__)
     /* This works well on macOS */
-    return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3,
-               SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI,
-                   SDL_HIDAPI_DEFAULT));
+    default_value = SDL_TRUE;
 #elif defined(__WINDOWS__)
     /* You can't initialize the controller with the stock Windows drivers
      * See https://github.com/ViGEm/DsHidMini as an alternative driver
      */
-    return SDL_FALSE;
+    default_value = SDL_FALSE;
 #elif defined(__LINUX__)
     /* Linux drivers do a better job of managing the transition between
      * USB and Bluetooth. There are also some quirks in communicating
@@ -93,11 +93,16 @@ HIDAPI_DriverPS3_IsEnabled(void)
      * for libusb, but are not possible to support using hidraw if the
      * kernel doesn't already know about them.
      */
-    return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, SDL_FALSE);
+    default_value = SDL_FALSE;
 #else
     /* Untested, default off */
-    return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, SDL_FALSE);
+    default_value = SDL_FALSE;
 #endif
+
+    if (default_value) {
+        default_value = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT);
+    }
+    return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS3, default_value);
 }
 
 static SDL_bool
@@ -112,19 +117,6 @@ HIDAPI_DriverPS3_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name,
     return SDL_FALSE;
 }
 
-static const char *
-HIDAPI_DriverPS3_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
-{
-    if (vendor_id == USB_VENDOR_SONY) {
-        if (name && SDL_strncasecmp(name, "ShanWan", 7) == 0) {
-            return "ShanWan PS3 Controller";
-        } else {
-            return "PS3 Controller";
-        }
-    }
-    return NULL;
-}
-
 static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
 {
     SDL_memset(report, 0, length);
@@ -140,6 +132,63 @@ static int SendFeatureReport(SDL_hid_device *dev, Uint8 *report, size_t length)
 static SDL_bool
 HIDAPI_DriverPS3_InitDevice(SDL_HIDAPI_Device *device)
 {
+    SDL_DriverPS3_Context *ctx;
+
+    if (device->vendor_id == USB_VENDOR_SONY) {
+        if (SDL_strncasecmp(device->name, "ShanWan", 7) == 0) {
+            HIDAPI_SetDeviceName(device, "ShanWan PS3 Controller");
+        } else {
+            HIDAPI_SetDeviceName(device, "PS3 Controller");
+        }
+    }
+
+    ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        SDL_OutOfMemory();
+        return SDL_FALSE;
+    }
+    ctx->device = device;
+
+    device->context = ctx;
+
+    if (SDL_strncasecmp(device->name, "ShanWan", 7) == 0) {
+        ctx->is_shanwan = SDL_TRUE;
+    }
+
+    /* Set the controller into report mode over Bluetooth */
+    {
+        Uint8 data[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 };
+
+        SendFeatureReport(device->dev, data, sizeof(data));
+    }
+
+    /* Set the controller into report mode over USB */
+    {
+        Uint8 data[USB_PACKET_LENGTH];
+        int size;
+
+        if ((size = ReadFeatureReport(device->dev, 0xf2, data, 17)) < 0) {
+            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                         "HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf2");
+            return SDL_FALSE;
+        }
+#ifdef DEBUG_PS3_PROTOCOL
+        HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size);
+#endif
+        if ((size = ReadFeatureReport(device->dev, 0xf5, data, 8)) < 0) {
+            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+                         "HIDAPI_DriverPS3_InitDevice(): Couldn't read feature report 0xf5");
+            return SDL_FALSE;
+        }
+#ifdef DEBUG_PS3_PROTOCOL
+        HIDAPI_DumpPacket("PS3 0xF5 packet: size = %d", data, size);
+#endif
+        if (!ctx->is_shanwan) {
+            /* An output report could cause ShanWan controllers to rumble non-stop */
+            SDL_hid_write(device->dev, data, 1);
+        }
+    }
+
     return HIDAPI_JoystickConnected(device, NULL);
 }
 
@@ -190,59 +239,13 @@ HIDAPI_DriverPS3_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID
 static SDL_bool
 HIDAPI_DriverPS3_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    SDL_DriverPS3_Context *ctx;
+    SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
 
-    ctx = (SDL_DriverPS3_Context *)SDL_calloc(1, sizeof(*ctx));
-    if (!ctx) {
-        SDL_OutOfMemory();
-        return SDL_FALSE;
-    }
-    ctx->device = device;
     ctx->joystick = joystick;
-
-    device->dev = SDL_hid_open_path(device->path, 0);
-    if (!device->dev) {
-        SDL_free(ctx);
-        SDL_SetError("Couldn't open %s", device->path);
-        return SDL_FALSE;
-    }
-    device->context = ctx;
-
-    if (SDL_strncasecmp(device->name, "ShanWan", 7) == 0) {
-        ctx->is_shanwan = SDL_TRUE;
-    }
-
-    /* Set the controller into report mode over Bluetooth */
-    {
-        Uint8 data[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 };
-
-        SendFeatureReport(device->dev, data, sizeof(data));
-    }
-
-    /* Set the controller into report mode over USB */
-    {
-        Uint8 data[USB_PACKET_LENGTH];
-        int size;
-
-        if ((size = ReadFeatureReport(device->dev, 0xf2, data, 17)) < 0) {
-            SDL_SetError("Couldn't read feature report 0xf2");
-            return SDL_FALSE;
-        }
-#ifdef DEBUG_PS3_PROTOCOL
-        HIDAPI_DumpPacket("PS3 0xF2 packet: size = %d", data, size);
-#endif
-        if ((size = ReadFeatureReport(device->dev, 0xf5, data, 8)) < 0) {
-            SDL_SetError("Couldn't read feature report 0xf5");
-            return SDL_FALSE;
-        }
-#ifdef DEBUG_PS3_PROTOCOL
-        HIDAPI_DumpPacket("PS3 0xF5 packet: size = %d", data, size);
-#endif
-        if (!ctx->is_shanwan) {
-            /* An output report could cause ShanWan controllers to rumble non-stop */
-            SDL_hid_write(device->dev, data, 1);
-        }
-    }
+    ctx->effects_updated = SDL_FALSE;
+    ctx->rumble_left = 0;
+    ctx->rumble_right = 0;
+    SDL_zeroa(ctx->last_state);
 
     /* Initialize player index (needed for setting LEDs) */
     ctx->player_index = SDL_JoystickGetPlayerIndex(joystick);
@@ -498,8 +501,7 @@ HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device)
 
     if (device->num_joysticks > 0) {
         joystick = SDL_JoystickFromInstanceID(device->joysticks[0]);
-    }
-    if (!joystick) {
+    } else {
         return SDL_FALSE;
     }
 
@@ -507,10 +509,19 @@ HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device)
 #ifdef DEBUG_PS3_PROTOCOL
         HIDAPI_DumpPacket("PS3 packet: size = %d", data, size);
 #endif
+        if (!joystick) {
+            continue;
+        }
 
         if (size == 7) {
             /* Seen on a ShanWan PS2 -> PS3 USB converter */
             HIDAPI_DriverPS3_HandleMiniStatePacket(joystick, ctx, data, size);
+
+            /* Wait for the first report to set the LED state after the controller stops blinking */
+            if (!ctx->effects_updated) {
+                HIDAPI_DriverPS3_UpdateEffects(device);
+                ctx->effects_updated = SDL_TRUE;
+            }
             continue;
         }
 
@@ -538,7 +549,7 @@ HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device)
 
     if (size < 0) {
         /* Read error, device is disconnected */
-        HIDAPI_JoystickDisconnected(device, joystick->instance_id);
+        HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
     }
     return (size >= 0);
 }
@@ -546,15 +557,9 @@ HIDAPI_DriverPS3_UpdateDevice(SDL_HIDAPI_Device *device)
 static void
 HIDAPI_DriverPS3_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    SDL_LockMutex(device->dev_lock);
-    {
-        SDL_hid_close(device->dev);
-        device->dev = NULL;
+    SDL_DriverPS3_Context *ctx = (SDL_DriverPS3_Context *)device->context;
 
-        SDL_free(device->context);
-        device->context = NULL;
-    }
-    SDL_UnlockMutex(device->dev_lock);
+    ctx->joystick = NULL;
 }
 
 static void
@@ -570,7 +575,6 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS3 =
     HIDAPI_DriverPS3_UnregisterHints,
     HIDAPI_DriverPS3_IsEnabled,
     HIDAPI_DriverPS3_IsSupportedDevice,
-    HIDAPI_DriverPS3_GetDeviceName,
     HIDAPI_DriverPS3_InitDevice,
     HIDAPI_DriverPS3_GetDevicePlayerIndex,
     HIDAPI_DriverPS3_SetDevicePlayerIndex,
diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index e927ec7b3445..1fe59375bd91 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -142,8 +142,6 @@ typedef struct {
     Uint8 led_red;
     Uint8 led_green;
     Uint8 led_blue;
-    Uint8 volume;
-    Uint32 last_volume_check;
     PS4StatePacket_t last_state;
 } SDL_DriverPS4_Context;
 
@@ -176,15 +174,6 @@ HIDAPI_DriverPS4_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name,
     return (type == SDL_CONTROLLER_TYPE_PS4) ? SDL_TRUE : SDL_FALSE;
 }
 
-static const char *
-HIDAPI_DriverPS4_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
-{
-    if (vendor_id == USB_VENDOR_SONY) {
-        return "PS4 Controller";
-    }
-    return NULL;
-}
-
 static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
 {
     SDL_memset(report, 0, length);
@@ -222,6 +211,106 @@ SetLedsForPlayerIndex(DS4EffectsState_t *effects, int player_index)
 static SDL_bool
 HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device)
 {
+    SDL_DriverPS4_Context *ctx;
+    Uint8 data[USB_PACKET_LENGTH];
+    int size;
+    char serial[18];
+
+    if (device->vendor_id == USB_VENDOR_SONY) {
+        HIDAPI_SetDeviceName(device, "PS4 Controller");
+    }
+
+    ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        SDL_OutOfMemory();
+        return SDL_FALSE;
+    }
+    ctx->device = device;
+
+    device->context = ctx;
+
+    if (device->serial && SDL_strlen(device->serial) == 12) {
+        int i, j;
+
+        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';
+    } else {
+        serial[0] = '\0';
+    }
+
+    /* Check for type of connection */
+    ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE);
+    if (ctx->is_dongle) {
+        ctx->is_bluetooth = SDL_FALSE;
+        ctx->official_controller = SDL_TRUE;
+        ctx->enhanced_mode = SDL_TRUE;
+    } else if (device->vendor_id == USB_VENDOR_SONY) {
+        /* This will fail if we're on Bluetooth */
+        size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data));
+        if (size >= 7) {
+            SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
+                data[6], data[5], data[4], data[3], data[2], data[1]);
+            ctx->is_bluetooth = SDL_FALSE;
+            ctx->enhanced_mode = SDL_TRUE;
+        } else {
+            ctx->is_bluetooth = SDL_TRUE;
+
+            /* Read a report to see if we're in enhanced mode */
+            size = SDL_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) {
+                ctx->enhanced_mode = SDL_TRUE;
+            }
+        }
+        ctx->official_controller = SDL_TRUE;
+    } else {
+        /* Third party controllers appear to all be wired */
+        ctx->is_bluetooth = SDL_FALSE;
+        ctx->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");
+#endif
+
+    /* Get the device capabilities */
+    if (device->vendor_id == USB_VENDOR_SONY) {
+        ctx->effects_supported = SDL_TRUE;
+        ctx->sensors_supported = SDL_TRUE;
+        ctx->touchpad_supported = SDL_TRUE;
+    } else if ((size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data))) == 48 &&
+               data[2] == 0x27) {
+        Uint8 capabilities = data[4];
+
+#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 & 0x40) != 0) {
+            ctx->touchpad_supported = SDL_TRUE;
+        }
+    }
+
+    HIDAPI_SetDeviceSerial(device, serial);
+
     return HIDAPI_JoystickConnected(device, NULL);
 }
 
@@ -471,7 +560,7 @@ HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
 
-    if (!ctx) {
+    if (!ctx->joystick) {
         return;
     }
 
@@ -484,124 +573,28 @@ HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID
 static SDL_bool
 HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
-    SDL_DriverPS4_Context *ctx;
-    Uint8 data[USB_PACKET_LENGTH];
-    int size;
-    SDL_bool enhanced_mode = SDL_FALSE;
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *) device->context;
 
-    ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx));
-    if (!ctx) {
-        SDL_OutOfMemory();
-        return SDL_FALSE;
-    }
-    ctx->device = device;
     ctx->joystick = joystick;
     ctx->last_packet = SDL_GetTicks();
-
-    device->dev = SDL_hid_open_path(device->path, 0);
-    if (!device->dev) {
-        SDL_free(ctx);
-        SDL_SetError("Couldn't open %s", device->path);
-        return SDL_FALSE;
-    }
-    device->context = ctx;
-
-    /* Check for type of connection */
-    ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE);
-    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) {
-        /* This will fail if we're on Bluetooth */
-        size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data));
-        if (size >= 7) {
-            char serial[18];
-
-            SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
-                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 = SDL_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");
-#endif
-
-    /* Get the device capabilities */
-    if (device->vendor_id == USB_VENDOR_SONY) {
-        ctx->effects_supported = SDL_TRUE;
-        ctx->sensors_supported = SDL_TRUE;
-        ctx->touchpad_supported = SDL_TRUE;
-    } else if ((size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data))) == 48 &&
-               data[2] == 0x27) {
-        Uint8 capabilities = data[4];
-
-#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 & 0x40) != 0) {
-            ctx->touchpad_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);
-    }
+    ctx->report_sensors = SDL_FALSE;
+    ctx->report_touchpad = SDL_FALSE;
+    ctx->rumble_left = 0;
+    ctx->rumble_right = 0;
+    ctx->color_set = SDL_FALSE;
+    SDL_zero(ctx->last_state);
 
     /* Initialize player index (needed for setting LEDs) */
     ctx->player_index = SDL_JoystickGetPlayerIndex(joystick);
 
-    /* Initialize the joystick capabilities
-     *
-     * We can't dynamically add the touchpad button, so always report it here
-     */
-    joystick->nbuttons = 16;
+    /* Initialize the joystick capabilities */
+    joystick->nbuttons = ctx->touchpad_supported ? 16 : 15;
     joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
     joystick->epowerlevel = ctx->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED;
 
-    if (enhanced_mode) {
+    if (ctx->enhanced_mode) {
+        /* Force initialization when opening the joystick */
+        ctx->enhanced_mode = SDL_FALSE;
         HIDAPI_DriverPS4_SetEnhancedMode(device, joystick);
     } else {
         SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
@@ -879,8 +872,7 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
 
     if (device->num_joysticks > 0) {
         joystick = SDL_JoystickFromInstanceID(device->joysticks[0]);
-    }
-    if (!joystick) {
+    } else {
         return SDL_FALSE;
     }
 
@@ -891,6 +883,10 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
         ++packet_count;
         ctx->last_packet = SDL_GetTicks();
 
+        if (!joystick) {
+            continue;
+        }
+
         switch (data[0]) {
         case k_EPS4ReportIdUsbState:
             HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[1]);
@@ -931,7 +927,7 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
 
     if (size < 0) {
         /* Read error, device is disconnected */
-        HIDAPI_JoystickDisconnected(device, joystick->instance_id);
+        HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
     }
     return (size >= 0);
 }
@@ -944,15 +940,7 @@ HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick
     SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
                         SDL_PS4RumbleHintChanged, ctx);
 
-    SDL_LockMutex(device->dev_lock);
-    {
-        SDL_hid_close(device->dev);
-        device->dev = NULL;
-
-        SDL_free(device->context);
-        device->context = NULL;
-    }
-    SDL_UnlockMutex(device->dev_lock);
+    ctx->joystick = NULL;
 }
 
 static void
@@ -968,7 +956,6 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 =
     HIDAPI_DriverPS4_UnregisterHints,
     HIDAPI_DriverPS4_IsEnabled,
     HIDAPI_DriverPS4_IsSupportedDevice,
-    HIDAPI_DriverPS4_GetDeviceName,
     HIDAPI_DriverPS4_InitDevice,
     HIDAPI_Driver

(Patch may be truncated, please check the link at the top of this post.)