SDL: use hidapi to get mouse/keyboard string

From 3293eb1a162d99914c7737b8e7a3ea4d7a97e841 Mon Sep 17 00:00:00 2001
From: expikr <[EMAIL REDACTED]>
Date: Wed, 19 Feb 2025 02:57:43 +0800
Subject: [PATCH] use hidapi to get mouse/keyboard string

---
 src/core/windows/SDL_hid.c            |   2 +
 src/core/windows/SDL_hid.h            |   2 +
 src/video/windows/SDL_windowsevents.c | 109 ++++++++++++++++++++++++--
 3 files changed, 108 insertions(+), 5 deletions(-)

diff --git a/src/core/windows/SDL_hid.c b/src/core/windows/SDL_hid.c
index 98748154bc271..87e873544ab04 100644
--- a/src/core/windows/SDL_hid.c
+++ b/src/core/windows/SDL_hid.c
@@ -22,6 +22,7 @@
 
 #include "SDL_hid.h"
 
+HidD_GetAttributes_t SDL_HidD_GetAttributes;
 HidD_GetString_t SDL_HidD_GetManufacturerString;
 HidD_GetString_t SDL_HidD_GetProductString;
 HidP_GetCaps_t SDL_HidP_GetCaps;
@@ -50,6 +51,7 @@ bool WIN_LoadHIDDLL(void)
     SDL_assert(s_HIDDLLRefCount == 0);
     s_HIDDLLRefCount = 1;
 
+    SDL_HidD_GetAttributes = (HidD_GetAttributes_t)GetProcAddress(s_pHIDDLL, "HidD_GetAttributes");
     SDL_HidD_GetManufacturerString = (HidD_GetString_t)GetProcAddress(s_pHIDDLL, "HidD_GetManufacturerString");
     SDL_HidD_GetProductString = (HidD_GetString_t)GetProcAddress(s_pHIDDLL, "HidD_GetProductString");
     SDL_HidP_GetCaps = (HidP_GetCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetCaps");
diff --git a/src/core/windows/SDL_hid.h b/src/core/windows/SDL_hid.h
index 8b20df11e54b6..46c22f243cff8 100644
--- a/src/core/windows/SDL_hid.h
+++ b/src/core/windows/SDL_hid.h
@@ -191,6 +191,7 @@ typedef struct
 extern bool WIN_LoadHIDDLL(void);
 extern void WIN_UnloadHIDDLL(void);
 
+typedef BOOLEAN (WINAPI *HidD_GetAttributes_t)(HANDLE HidDeviceObject, PHIDD_ATTRIBUTES Attributes);
 typedef BOOLEAN (WINAPI *HidD_GetString_t)(HANDLE HidDeviceObject, PVOID Buffer, ULONG BufferLength);
 typedef NTSTATUS (WINAPI *HidP_GetCaps_t)(PHIDP_PREPARSED_DATA PreparsedData, PHIDP_CAPS Capabilities);
 typedef NTSTATUS (WINAPI *HidP_GetButtonCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData);
@@ -198,6 +199,7 @@ typedef NTSTATUS (WINAPI *HidP_GetValueCaps_t)(HIDP_REPORT_TYPE ReportType, PHID
 typedef ULONG (WINAPI *HidP_MaxDataListLength_t)(HIDP_REPORT_TYPE ReportType, PHIDP_PREPARSED_DATA PreparsedData);
 typedef NTSTATUS (WINAPI *HidP_GetData_t)(HIDP_REPORT_TYPE ReportType, PHIDP_DATA DataList, PULONG DataLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength);
 
+extern HidD_GetAttributes_t SDL_HidD_GetAttributes;
 extern HidD_GetString_t SDL_HidD_GetManufacturerString;
 extern HidD_GetString_t SDL_HidD_GetProductString;
 extern HidP_GetCaps_t SDL_HidP_GetCaps;
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index af3407c407dc7..727f3a4e70dd7 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -861,7 +861,7 @@ static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count)
 }
 
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
-static void GetDeviceName(HDEVINFO devinfo, const char *instance, char *name, size_t len)
+static void GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, char *name, size_t len)
 {
     name[0] = '\0';
 
@@ -883,9 +883,108 @@ static void GetDeviceName(HDEVINFO devinfo, const char *instance, char *name, si
 
         if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) {
             SetupDiGetDeviceRegistryPropertyA(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)name, (DWORD)len, NULL);
-            return;
+            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
 }
 
 void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check)
@@ -938,7 +1037,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[64];
+        char *instance, *ptr, name[256];
 
         if (dwType != RIM_TYPEKEYBOARD && dwType != RIM_TYPEMOUSE) {
             continue;
@@ -976,7 +1075,7 @@ 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(devinfo, instance, name, sizeof(name));
+                    GetDeviceName(raw_devices[i].hDevice, devinfo, instance, name, sizeof(name));
                     SDL_AddKeyboard(keyboardID, name, send_event);
                 }
             }
@@ -986,7 +1085,7 @@ 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(devinfo, instance, name, sizeof(name));
+                    GetDeviceName(raw_devices[i].hDevice, devinfo, instance, name, sizeof(name));
                     SDL_AddMouse(mouseID, name, send_event);
                 }
             }