diff --git a/src/SDL_utils.c b/src/SDL_utils.c index b9a42e2370..c1ceacb189 100644 --- a/src/SDL_utils.c +++ b/src/SDL_utils.c @@ -24,6 +24,9 @@ #include #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 557dad49a5..e8e98f241e 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 a8bbe0c23f..9221bce9cb 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 727f3a4e70..897d999bb5 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'; + char *name = NULL; - 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 { + // These are 126 for USB, but can be longer for Bluetooth devices + WCHAR vend[256], prod[256]; + vend[0] = 0; + prod[0] = 0; + + 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); + } + } + } + + 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); + } + + 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; + } + } + + 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; } } - - char DeviceInstanceId[64]; - if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL)) - continue; - - if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) { - SetupDiGetDeviceRegistryPropertyA(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)name, (DWORD)len, NULL); - break; - } } - - size_t desc_len = 0; - for (size_t i = 0; i < len; i++) { - if (name[i] == '\0') { - desc_len = i; - break; - } - } - - 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) { - break; - } - } - 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]; - } - } - } - } - } - 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)) {