SDL: Use HIDAPI to provide better names for DirectInput controllers

From 27b828754a61b017ce04915705057ffd3af897b4 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 4 Nov 2024 21:14:46 -0800
Subject: [PATCH] Use HIDAPI to provide better names for DirectInput
 controllers

Fixes https://github.com/libsdl-org/SDL/issues/10207
---
 src/joystick/hidapi/SDL_hidapijoystick.c   | 32 +++++++++++++++++++++
 src/joystick/hidapi/SDL_hidapijoystick_c.h |  6 ++++
 src/joystick/windows/SDL_dinputjoystick.c  | 33 ++++++++++++++--------
 3 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index 3a0f8169c7d58..c3ec20645d6a2 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -1285,6 +1285,38 @@ bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version,
     return result;
 }
 
+char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id)
+{
+    SDL_HIDAPI_Device *device;
+    char *name = NULL;
+
+    SDL_LockJoysticks();
+    for (device = SDL_HIDAPI_devices; device; device = device->next) {
+        if (vendor_id == device->vendor_id && product_id == device->product_id) {
+            name = SDL_strdup(device->product_string);
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return name;
+}
+
+char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id)
+{
+    SDL_HIDAPI_Device *device;
+    char *name = NULL;
+
+    SDL_LockJoysticks();
+    for (device = SDL_HIDAPI_devices; device; device = device->next) {
+        if (vendor_id == device->vendor_id && product_id == device->product_id) {
+            name = SDL_strdup(device->manufacturer_string);
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return name;
+}
+
 SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid)
 {
     SDL_HIDAPI_Device *device;
diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h
index 5ec0a3931ec33..2069b30324e69 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick_c.h
+++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -160,6 +160,12 @@ extern bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type);
 // Return true if a HID device is present and supported as a joystick
 extern bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);
 
+// Return the name of a connected device, which should be freed with SDL_free(), or NULL if it's not available
+extern char *HIDAPI_GetDeviceProductName(Uint16 vendor_id, Uint16 product_id);
+
+// Return the manufacturer of a connected device, which should be freed with SDL_free(), or NULL if it's not available
+extern char *HIDAPI_GetDeviceManufacturerName(Uint16 vendor_id, Uint16 product_id);
+
 // Return the type of a joystick if it's present and supported
 extern SDL_JoystickType HIDAPI_GetJoystickTypeFromGUID(SDL_GUID guid);
 
diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c
index 03f2704faa1f5..05a123f5efdae 100644
--- a/src/joystick/windows/SDL_dinputjoystick.c
+++ b/src/joystick/windows/SDL_dinputjoystick.c
@@ -267,14 +267,22 @@ static bool SDL_IsXInputDevice(Uint16 vendor_id, Uint16 product_id, const char *
     return false;
 }
 
-static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, char **device_name)
+static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, Uint16 vendor_id, Uint16 product_id, char **manufacturer_string, char **product_string)
 {
     DIPROPSTRING dipstr;
 
-    if (!device || !device_name) {
+    if (!device || !manufacturer_string || !product_string) {
         return false;
     }
 
+#ifdef SDL_JOYSTICK_HIDAPI
+    *manufacturer_string = HIDAPI_GetDeviceManufacturerName(vendor_id, product_id);
+    *product_string = HIDAPI_GetDeviceProductName(vendor_id, product_id);
+    if (*product_string) {
+        return true;
+    }
+#endif
+
     dipstr.diph.dwSize = sizeof(dipstr);
     dipstr.diph.dwHeaderSize = sizeof(dipstr.diph);
     dipstr.diph.dwObj = 0;
@@ -284,7 +292,8 @@ static bool QueryDeviceName(LPDIRECTINPUTDEVICE8 device, char **device_name)
         return false;
     }
 
-    *device_name = WIN_StringToUTF8(dipstr.wsz);
+    *manufacturer_string = NULL;
+    *product_string = WIN_StringToUTF8(dipstr.wsz);
 
     return true;
 }
@@ -460,20 +469,21 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta
     Uint16 product = 0;
     Uint16 version = 0;
     char *hidPath = NULL;
-    char *name = NULL;
+    char *manufacturer_string = NULL;
+    char *product_string = NULL;
     LPDIRECTINPUTDEVICE8 device = NULL;
 
     // We are only supporting HID devices.
     CHECK(pDeviceInstance->dwDevType & DIDEVTYPE_HID);
 
     CHECK(SUCCEEDED(IDirectInput8_CreateDevice(dinput, &pDeviceInstance->guidInstance, &device, NULL)));
-    CHECK(QueryDeviceName(device, &name));
     CHECK(QueryDevicePath(device, &hidPath));
     CHECK(QueryDeviceInfo(device, &vendor, &product));
+    CHECK(QueryDeviceName(device, vendor, product, &manufacturer_string, &product_string));
 
     CHECK(!SDL_IsXInputDevice(vendor, product, hidPath));
-    CHECK(!SDL_ShouldIgnoreJoystick(vendor, product, version, name));
-    CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, name));
+    CHECK(!SDL_ShouldIgnoreJoystick(vendor, product, version, product_string));
+    CHECK(!SDL_JoystickHandledByAnotherDriver(&SDL_WINDOWS_JoystickDriver, vendor, product, version, product_string));
 
     pNewJoystick = *(JoyStick_DeviceData **)pContext;
     while (pNewJoystick) {
@@ -507,13 +517,13 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta
     SDL_strlcpy(pNewJoystick->path, hidPath, SDL_arraysize(pNewJoystick->path));
     SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE));
 
-    pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, NULL, name);
+    pNewJoystick->joystickname = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
     CHECK(pNewJoystick->joystickname);
 
     if (vendor && product) {
-        pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, NULL, name, 0, 0);
+        pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, vendor, product, version, manufacturer_string, product_string, 0, 0);
     } else {
-        pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, version, NULL, name, 0, 0);
+        pNewJoystick->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, version, manufacturer_string, product_string, 0, 0);
     }
 
     WINDOWS_AddJoystickDevice(pNewJoystick);
@@ -526,7 +536,8 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta
     }
 
     SDL_free(hidPath);
-    SDL_free(name);
+    SDL_free(manufacturer_string);
+    SDL_free(product_string);
 
     if (device) {
         IDirectInputDevice8_Release(device);