From 5d776c070a92efadde3469ed17881d78fbc273fd Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 21 Feb 2025 08:28:57 -0800
Subject: [PATCH] Refactored SDL_CreateJoystickName() into a general
SDL_CreateDeviceName()
---
src/SDL_utils.c | 151 +++++++++++++++++++++
src/SDL_utils_c.h | 2 +
src/joystick/SDL_joystick.c | 148 +--------------------
src/video/windows/SDL_windowsevents.c | 181 ++++++++++----------------
4 files changed, 226 insertions(+), 256 deletions(-)
diff --git a/src/SDL_utils.c b/src/SDL_utils.c
index b9a42e2370f4b..c1ceacb189d09 100644
--- a/src/SDL_utils.c
+++ b/src/SDL_utils.c
@@ -24,6 +24,9 @@
#include <unistd.h>
#endif
+#include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID()
+
+
// Common utility functions that aren't in the public API
int SDL_powerof2(int x)
@@ -396,3 +399,151 @@ const char *SDL_GetPersistentString(const char *string)
}
return result;
}
+
+static int PrefixMatch(const char *a, const char *b)
+{
+ int matchlen = 0;
+ while (*a && *b) {
+ if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) {
+ ++matchlen;
+ } else {
+ break;
+ }
+ }
+ return matchlen;
+}
+
+char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name)
+{
+ static struct
+ {
+ const char *prefix;
+ const char *replacement;
+ } replacements[] = {
+ { "ASTRO Gaming", "ASTRO" },
+ { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" },
+ { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" },
+ { "HORI CO.,LTD", "HORI" },
+ { "HORI CO.,LTD.", "HORI" },
+ { "Mad Catz Inc.", "Mad Catz" },
+ { "Nintendo Co., Ltd.", "Nintendo" },
+ { "NVIDIA Corporation ", "" },
+ { "Performance Designed Products", "PDP" },
+ { "QANBA USA, LLC", "Qanba" },
+ { "QANBA USA,LLC", "Qanba" },
+ { "Unknown ", "" },
+ };
+ char *name;
+ size_t i, len;
+
+ if (!vendor_name) {
+ vendor_name = "";
+ }
+ if (!product_name) {
+ product_name = "";
+ }
+
+ while (*vendor_name == ' ') {
+ ++vendor_name;
+ }
+ while (*product_name == ' ') {
+ ++product_name;
+ }
+
+ if (*vendor_name && *product_name) {
+ len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1);
+ name = (char *)SDL_malloc(len);
+ if (name) {
+ (void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name);
+ }
+ } else if (*product_name) {
+ name = SDL_strdup(product_name);
+ } else if (vendor || product) {
+ // Couldn't find a controller name, try to give it one based on device type
+ switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) {
+ case SDL_GAMEPAD_TYPE_XBOX360:
+ name = SDL_strdup("Xbox 360 Controller");
+ break;
+ case SDL_GAMEPAD_TYPE_XBOXONE:
+ name = SDL_strdup("Xbox One Controller");
+ break;
+ case SDL_GAMEPAD_TYPE_PS3:
+ name = SDL_strdup("PS3 Controller");
+ break;
+ case SDL_GAMEPAD_TYPE_PS4:
+ name = SDL_strdup("PS4 Controller");
+ break;
+ case SDL_GAMEPAD_TYPE_PS5:
+ name = SDL_strdup("DualSense Wireless Controller");
+ break;
+ case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
+ name = SDL_strdup("Nintendo Switch Pro Controller");
+ break;
+ default:
+ len = (6 + 1 + 6 + 1);
+ name = (char *)SDL_malloc(len);
+ if (name) {
+ (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product);
+ }
+ break;
+ }
+ } else {
+ name = SDL_strdup("Controller");
+ }
+
+ if (!name) {
+ return NULL;
+ }
+
+ // Trim trailing whitespace
+ for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) {
+ // continue
+ }
+ name[len] = '\0';
+
+ // Compress duplicate spaces
+ for (i = 0; i < (len - 1);) {
+ if (name[i] == ' ' && name[i + 1] == ' ') {
+ SDL_memmove(&name[i], &name[i + 1], (len - i));
+ --len;
+ } else {
+ ++i;
+ }
+ }
+
+ // Perform any manufacturer replacements
+ for (i = 0; i < SDL_arraysize(replacements); ++i) {
+ size_t prefixlen = SDL_strlen(replacements[i].prefix);
+ if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) {
+ size_t replacementlen = SDL_strlen(replacements[i].replacement);
+ if (replacementlen <= prefixlen) {
+ SDL_memcpy(name, replacements[i].replacement, replacementlen);
+ SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1);
+ len -= (prefixlen - replacementlen);
+ } else {
+ // FIXME: Need to handle the expand case by reallocating the string
+ }
+ break;
+ }
+ }
+
+ /* Remove duplicate manufacturer or product in the name
+ * e.g. Razer Razer Raiju Tournament Edition Wired
+ */
+ for (i = 1; i < (len - 1); ++i) {
+ int matchlen = PrefixMatch(name, &name[i]);
+ while (matchlen > 0) {
+ if (name[matchlen] == ' ' || name[matchlen] == '-') {
+ SDL_memmove(name, name + matchlen + 1, len - matchlen);
+ break;
+ }
+ --matchlen;
+ }
+ if (matchlen > 0) {
+ // We matched the manufacturer's name and removed it
+ break;
+ }
+ }
+
+ return name;
+}
diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h
index 557dad49a59d0..e8e98f241e9c3 100644
--- a/src/SDL_utils_c.h
+++ b/src/SDL_utils_c.h
@@ -73,4 +73,6 @@ extern void SDL_SetObjectsInvalid(void);
extern const char *SDL_GetPersistentString(const char *string);
+extern char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name);
+
#endif // SDL_utils_h_
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index a8bbe0c23f71b..9221bce9cb5b9 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -2546,158 +2546,14 @@ void SDL_GetJoystickGUIDInfo(SDL_GUID guid, Uint16 *vendor, Uint16 *product, Uin
}
}
-static int PrefixMatch(const char *a, const char *b)
-{
- int matchlen = 0;
- while (*a && *b) {
- if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) {
- ++matchlen;
- } else {
- break;
- }
- }
- return matchlen;
-}
-
char *SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name)
{
- static struct
- {
- const char *prefix;
- const char *replacement;
- } replacements[] = {
- { "ASTRO Gaming", "ASTRO" },
- { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" },
- { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" },
- { "HORI CO.,LTD", "HORI" },
- { "HORI CO.,LTD.", "HORI" },
- { "Mad Catz Inc.", "Mad Catz" },
- { "Nintendo Co., Ltd.", "Nintendo" },
- { "NVIDIA Corporation ", "" },
- { "Performance Designed Products", "PDP" },
- { "QANBA USA, LLC", "Qanba" },
- { "QANBA USA,LLC", "Qanba" },
- { "Unknown ", "" },
- };
- const char *custom_name;
- char *name;
- size_t i, len;
-
- custom_name = GuessControllerName(vendor, product);
+ const char *custom_name = GuessControllerName(vendor, product);
if (custom_name) {
return SDL_strdup(custom_name);
}
- if (!vendor_name) {
- vendor_name = "";
- }
- if (!product_name) {
- product_name = "";
- }
-
- while (*vendor_name == ' ') {
- ++vendor_name;
- }
- while (*product_name == ' ') {
- ++product_name;
- }
-
- if (*vendor_name && *product_name) {
- len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1);
- name = (char *)SDL_malloc(len);
- if (name) {
- (void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name);
- }
- } else if (*product_name) {
- name = SDL_strdup(product_name);
- } else if (vendor || product) {
- // Couldn't find a controller name, try to give it one based on device type
- switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) {
- case SDL_GAMEPAD_TYPE_XBOX360:
- name = SDL_strdup("Xbox 360 Controller");
- break;
- case SDL_GAMEPAD_TYPE_XBOXONE:
- name = SDL_strdup("Xbox One Controller");
- break;
- case SDL_GAMEPAD_TYPE_PS3:
- name = SDL_strdup("PS3 Controller");
- break;
- case SDL_GAMEPAD_TYPE_PS4:
- name = SDL_strdup("PS4 Controller");
- break;
- case SDL_GAMEPAD_TYPE_PS5:
- name = SDL_strdup("DualSense Wireless Controller");
- break;
- case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
- name = SDL_strdup("Nintendo Switch Pro Controller");
- break;
- default:
- len = (6 + 1 + 6 + 1);
- name = (char *)SDL_malloc(len);
- if (name) {
- (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product);
- }
- break;
- }
- } else {
- name = SDL_strdup("Controller");
- }
-
- if (!name) {
- return NULL;
- }
-
- // Trim trailing whitespace
- for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) {
- // continue
- }
- name[len] = '\0';
-
- // Compress duplicate spaces
- for (i = 0; i < (len - 1);) {
- if (name[i] == ' ' && name[i + 1] == ' ') {
- SDL_memmove(&name[i], &name[i + 1], (len - i));
- --len;
- } else {
- ++i;
- }
- }
-
- // Perform any manufacturer replacements
- for (i = 0; i < SDL_arraysize(replacements); ++i) {
- size_t prefixlen = SDL_strlen(replacements[i].prefix);
- if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) {
- size_t replacementlen = SDL_strlen(replacements[i].replacement);
- if (replacementlen <= prefixlen) {
- SDL_memcpy(name, replacements[i].replacement, replacementlen);
- SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1);
- len -= (prefixlen - replacementlen);
- } else {
- // FIXME: Need to handle the expand case by reallocating the string
- }
- break;
- }
- }
-
- /* Remove duplicate manufacturer or product in the name
- * e.g. Razer Razer Raiju Tournament Edition Wired
- */
- for (i = 1; i < (len - 1); ++i) {
- int matchlen = PrefixMatch(name, &name[i]);
- while (matchlen > 0) {
- if (name[matchlen] == ' ' || name[matchlen] == '-') {
- SDL_memmove(name, name + matchlen + 1, len - matchlen);
- break;
- }
- --matchlen;
- }
- if (matchlen > 0) {
- // We matched the manufacturer's name and removed it
- break;
- }
- }
-
- return name;
+ return SDL_CreateDeviceName(vendor, product, vendor_name, product_name);
}
SDL_GUID SDL_CreateJoystickGUID(Uint16 bus, Uint16 vendor, Uint16 product, Uint16 version, const char *vendor_name, const char *product_name, Uint8 driver_signature, Uint8 driver_data)
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 727f3a4e70dd7..897d999bb5c37 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -861,130 +861,85 @@ static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count)
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
-static void GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, char *name, size_t len)
+static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, bool hid_loaded)
{
- name[0] = '\0';
-
- SP_DEVINFO_DATA data;
- SDL_zero(data);
- data.cbSize = sizeof(data);
- for (DWORD i = 0;; ++i) {
- if (!SetupDiEnumDeviceInfo(devinfo, i, &data)) {
- if (GetLastError() == ERROR_NO_MORE_ITEMS) {
- break;
- } else {
- continue;
- }
- }
+ char *name = NULL;
- char DeviceInstanceId[64];
- if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL))
- continue;
+ // These are 126 for USB, but can be longer for Bluetooth devices
+ WCHAR vend[256], prod[256];
+ vend[0] = 0;
+ prod[0] = 0;
- if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) {
- SetupDiGetDeviceRegistryPropertyA(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)name, (DWORD)len, NULL);
- break;
+ HIDD_ATTRIBUTES attr;
+ attr.VendorID = 0;
+ attr.ProductID = 0;
+ attr.Size = sizeof(attr);
+
+ if (hid_loaded) {
+ char devName[MAX_PATH + 1];
+ UINT cap = sizeof(devName) - 1;
+ UINT len = GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, devName, &cap);
+ if (len != (UINT)-1) {
+ devName[len] = '\0';
+
+ // important: for devices with exclusive access mode as per
+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use
+ // they can only be opened with a desired access of none instead of generic read.
+ HANDLE hFile = CreateFileA(devName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (hFile != INVALID_HANDLE_VALUE) {
+ SDL_HidD_GetAttributes(hFile, &attr);
+ SDL_HidD_GetManufacturerString(hFile, vend, sizeof(vend));
+ SDL_HidD_GetProductString(hFile, prod, sizeof(prod));
+ CloseHandle(hFile);
+ }
}
}
- size_t desc_len = 0;
- for (size_t i = 0; i < len; i++) {
- if (name[i] == '\0') {
- desc_len = i;
- break;
+ if (prod[0]) {
+ char *vendor_name = WIN_StringToUTF8W(vend);
+ char *product_name = WIN_StringToUTF8W(prod);
+ if (product_name) {
+ name = SDL_CreateDeviceName(attr.VendorID, attr.ProductID, vendor_name, product_name);
}
+ SDL_free(vendor_name);
+ SDL_free(product_name);
}
- char devName[MAX_PATH + 1];
- devName[MAX_PATH] = '\0';
- UINT devName_cap = MAX_PATH;
- UINT devName_len = GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, devName, &devName_cap);
- if (0 != ~devName_len) {
- devName[devName_len] = '\0';
- }
-
- if (desc_len + 3 < len && WIN_LoadHIDDLL()) { // reference counted
- // important: for devices with exclusive access mode as per
- // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use
- // they can only be opened with a desired access of none instead of generic read.
- HANDLE hFile = CreateFileA(devName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
- if (hFile != INVALID_HANDLE_VALUE) {
- HIDD_ATTRIBUTES attr;
- WCHAR vend[128], prod[128];
- attr.VendorID = 0;
- attr.ProductID = 0;
- attr.Size = sizeof(attr);
- vend[0] = 0;
- prod[0] = 0;
- SDL_HidD_GetAttributes(hFile, &attr);
- SDL_HidD_GetManufacturerString(hFile, vend, sizeof(vend));
- SDL_HidD_GetProductString(hFile, prod, sizeof(prod));
- CloseHandle(hFile);
-
- size_t vend_len, prod_len;
- for (vend_len = 0; vend_len < 128; vend_len++) {
- if (vend[vend_len] == 0) {
- break;
- }
- }
- for (prod_len = 0; prod_len < 128; prod_len++) {
- if (prod[prod_len] == 0) {
+ if (!name) {
+ SP_DEVINFO_DATA data;
+ SDL_zero(data);
+ data.cbSize = sizeof(data);
+ for (DWORD i = 0;; ++i) {
+ if (!SetupDiEnumDeviceInfo(devinfo, i, &data)) {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS) {
break;
+ } else {
+ continue;
}
}
- if (vend_len > 0 || prod_len > 0) {
- name[desc_len] = ' ';
- name[desc_len+1] = '(';
- size_t start = desc_len + 2;
- size_t n = start;
- for (size_t i = 0; i < vend_len; i++) {
- n = start + i;
- if (n < len) {
- name[n] = (char)(vend[i] & 0x7f); // TODO: do proper WCHAR conversion
- }
- }
- if (vend_len > 0) {
- n += 1;
- if (n < len) {
- name[n] = ' ';
- }
- n += 1;
- }
- size_t m = n;
- for (size_t i = 0; i < prod_len; i++) {
- m = n + i;
- if (m < len) {
- name[m] = (char)(prod[i] & 0x7f); // TODO: do proper WCHAR conversion
- }
- }
- {
- m += 1;
- if (m < len) {
- name[m] = ')';
- }
- m += 1;
- if (m < len) {
- name[m] = '\0';
- }
- }
- } else if (attr.VendorID && attr.ProductID) {
- char buf[17];
- int written = SDL_snprintf(buf, sizeof(buf), " (0x%.4x/0x%.4x)", attr.VendorID, attr.ProductID);
- buf[16] = '\0'; // just to be safe
- if (written > 0 && desc_len > 0) {
- for (size_t i = 0; i < sizeof(buf); i++) {
- size_t j = desc_len + i;
- if (j < len) {
- name[j] = buf[j];
- }
+
+ char DeviceInstanceId[64];
+ if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL))
+ continue;
+
+ if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) {
+ DWORD size = 0;
+ if (SetupDiGetDeviceRegistryPropertyW(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)prod, sizeof(prod), &size)) {
+ // Make sure the device description is null terminated
+ size /= sizeof(*prod);
+ if (size >= SDL_arraysize(prod)) {
+ // Truncated description...
+ size = (SDL_arraysize(prod) - 1);
}
+ prod[size] = 0;
+
+ name = WIN_StringToUTF8W(prod);
}
+ break;
}
}
- WIN_UnloadHIDDLL();
}
-
- name[len-1] = '\0'; // just to be safe
+ return name;
}
void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check)
@@ -1030,6 +985,7 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check
old_keyboards = SDL_GetKeyboards(&old_keyboard_count);
old_mice = SDL_GetMice(&old_mouse_count);
+ bool hid_loaded = WIN_LoadHIDDLL();
for (UINT i = 0; i < raw_device_count; i++) {
RID_DEVICE_INFO rdi;
char devName[MAX_PATH] = { 0 };
@@ -1037,7 +993,7 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check
UINT nameSize = SDL_arraysize(devName);
int vendor = 0, product = 0;
DWORD dwType = raw_devices[i].dwType;
- char *instance, *ptr, name[256];
+ char *instance, *ptr, *name;
if (dwType != RIM_TYPEKEYBOARD && dwType != RIM_TYPEMOUSE) {
continue;
@@ -1075,8 +1031,9 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check
SDL_KeyboardID keyboardID = (Uint32)(uintptr_t)raw_devices[i].hDevice;
AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count);
if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) {
- GetDeviceName(raw_devices[i].hDevice, devinfo, instance, name, sizeof(name));
+ name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, hid_loaded);
SDL_AddKeyboard(keyboardID, name, send_event);
+ SDL_free(name);
}
}
break;
@@ -1085,8 +1042,9 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check
SDL_MouseID mouseID = (Uint32)(uintptr_t)raw_devices[i].hDevice;
AddDeviceID(mouseID, &new_mice, &new_mouse_count);
if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) {
- GetDeviceName(raw_devices[i].hDevice, devinfo, instance, name, sizeof(name));
+ name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, hid_loaded);
SDL_AddMouse(mouseID, name, send_event);
+ SDL_free(name);
}
}
break;
@@ -1094,6 +1052,9 @@ void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check
break;
}
}
+ if (hid_loaded) {
+ WIN_UnloadHIDDLL();
+ }
for (int i = old_keyboard_count; i--;) {
if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) {