SDL: Get the correct USB VID/PID information for /dev/input/js* devices

From dc9de1e2bd09825fc3ee4482de1fcda42bf2baa6 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 11 Nov 2021 07:03:30 -0800
Subject: [PATCH] Get the correct USB VID/PID information for /dev/input/js*
 devices

---
 src/core/linux/SDL_udev.c            | 139 +++++++++++++++++++--------
 src/core/linux/SDL_udev.h            |   1 +
 src/joystick/linux/SDL_sysjoystick.c |   7 +-
 3 files changed, 103 insertions(+), 44 deletions(-)

diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c
index b786f6fa46..23d628ef40 100644
--- a/src/core/linux/SDL_udev.c
+++ b/src/core/linux/SDL_udev.c
@@ -19,7 +19,7 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
-/* 
+/*
  * To list the properties of a device, try something like:
  * udevadm info -a -n snd/hwC0D0 (for a sound card)
  * udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc)
@@ -115,23 +115,23 @@ int
 SDL_UDEV_Init(void)
 {
     int retval = 0;
-    
+
     if (_this == NULL) {
         _this = (SDL_UDEV_PrivateData *) SDL_calloc(1, sizeof(*_this));
         if(_this == NULL) {
             return SDL_OutOfMemory();
         }
-        
+
         retval = SDL_UDEV_LoadLibrary();
         if (retval < 0) {
             SDL_UDEV_Quit();
             return retval;
         }
-        
-        /* Set up udev monitoring 
+
+        /* Set up udev monitoring
          * Listen for input devices (mouse, keyboard, joystick, etc) and sound devices
          */
-        
+
         _this->udev = _this->syms.udev_new();
         if (_this->udev == NULL) {
             SDL_UDEV_Quit();
@@ -143,18 +143,18 @@ SDL_UDEV_Init(void)
             SDL_UDEV_Quit();
             return SDL_SetError("udev_monitor_new_from_netlink() failed");
         }
-        
+
         _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL);
         _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);
         _this->syms.udev_monitor_enable_receiving(_this->udev_mon);
-        
+
         /* Do an initial scan of existing devices */
         SDL_UDEV_Scan();
 
     }
-    
+
     _this->ref_count += 1;
-    
+
     return retval;
 }
 
@@ -162,15 +162,15 @@ void
 SDL_UDEV_Quit(void)
 {
     SDL_UDEV_CallbackList *item;
-    
+
     if (_this == NULL) {
         return;
     }
-    
+
     _this->ref_count -= 1;
-    
+
     if (_this->ref_count < 1) {
-        
+
         if (_this->udev_mon != NULL) {
             _this->syms.udev_monitor_unref(_this->udev_mon);
             _this->udev_mon = NULL;
@@ -179,14 +179,14 @@ SDL_UDEV_Quit(void)
             _this->syms.udev_unref(_this->udev);
             _this->udev = NULL;
         }
-        
+
         /* Remove existing devices */
         while (_this->first != NULL) {
             item = _this->first;
             _this->first = _this->first->next;
             SDL_free(item);
         }
-        
+
         SDL_UDEV_UnloadLibrary();
         SDL_free(_this);
         _this = NULL;
@@ -198,22 +198,22 @@ SDL_UDEV_Scan(void)
 {
     struct udev_enumerate *enumerate = NULL;
     struct udev_list_entry *devs = NULL;
-    struct udev_list_entry *item = NULL;  
-    
+    struct udev_list_entry *item = NULL;
+
     if (_this == NULL) {
         return;
     }
-   
+
     enumerate = _this->syms.udev_enumerate_new(_this->udev);
     if (enumerate == NULL) {
         SDL_UDEV_Quit();
         SDL_SetError("udev_enumerate_new() failed");
         return;
     }
-    
+
     _this->syms.udev_enumerate_add_match_subsystem(enumerate, "input");
     _this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound");
-    
+
     _this->syms.udev_enumerate_scan_devices(enumerate);
     devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
     for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) {
@@ -228,6 +228,62 @@ SDL_UDEV_Scan(void)
     _this->syms.udev_enumerate_unref(enumerate);
 }
 
+SDL_bool
+SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version)
+{
+    struct udev_enumerate *enumerate = NULL;
+    struct udev_list_entry *devs = NULL;
+    struct udev_list_entry *item = NULL;
+    SDL_bool found = SDL_FALSE;
+
+    if (_this == NULL) {
+        return SDL_FALSE;
+    }
+
+    enumerate = _this->syms.udev_enumerate_new(_this->udev);
+    if (enumerate == NULL) {
+        SDL_SetError("udev_enumerate_new() failed");
+        return SDL_FALSE;
+    }
+
+    _this->syms.udev_enumerate_scan_devices(enumerate);
+    devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
+    for (item = devs; item && !found; item = _this->syms.udev_list_entry_get_next(item)) {
+        const char *path = _this->syms.udev_list_entry_get_name(item);
+        struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path);
+        if (dev != NULL) {
+            const char *val = NULL;
+            const char *existing_path;
+
+            existing_path = _this->syms.udev_device_get_devnode(dev);
+            if (existing_path && SDL_strcmp(device_path, existing_path) == 0) {
+                found = SDL_TRUE;
+
+                val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
+                if (val != NULL) {
+                    *vendor = (Uint16)SDL_strtol(val, NULL, 16);
+                }
+
+                val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
+                if (val != NULL) {
+                    *product = (Uint16)SDL_strtol(val, NULL, 16);
+                }
+
+                val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
+                if (val != NULL) {
+                    *version = (Uint16)SDL_strtol(val, NULL, 16);
+                }
+            }
+            _this->syms.udev_device_unref(dev);
+        }
+    }
+    _this->syms.udev_enumerate_unref(enumerate);
+
+    return found;
+}
+
+
+
 
 void
 SDL_UDEV_UnloadLibrary(void)
@@ -235,7 +291,7 @@ SDL_UDEV_UnloadLibrary(void)
     if (_this == NULL) {
         return;
     }
-    
+
     if (_this->udev_handle != NULL) {
         SDL_UnloadObject(_this->udev_handle);
         _this->udev_handle = NULL;
@@ -246,11 +302,11 @@ int
 SDL_UDEV_LoadLibrary(void)
 {
     int retval = 0, i;
-    
+
     if (_this == NULL) {
         return SDL_SetError("UDEV not initialized");
     }
- 
+
     /* See if there is a udev library already loaded */
     if (SDL_UDEV_load_syms() == 0) {
         return 0;
@@ -282,7 +338,7 @@ SDL_UDEV_LoadLibrary(void)
                 }
             }
         }
-        
+
         if (_this->udev_handle == NULL) {
             retval = -1;
             /* Don't call SDL_SetError(): SDL_LoadObject already did. */
@@ -352,26 +408,26 @@ guess_device_class(struct udev_device *dev)
                                       &bitmask_rel[0]);
 }
 
-static void 
-device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) 
+static void
+device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
 {
     const char *subsystem;
     const char *val = NULL;
     int devclass = 0;
     const char *path;
     SDL_UDEV_CallbackList *item;
-    
+
     path = _this->syms.udev_device_get_devnode(dev);
     if (path == NULL) {
         return;
     }
-    
+
     subsystem = _this->syms.udev_device_get_subsystem(dev);
     if (SDL_strcmp(subsystem, "sound") == 0) {
         devclass = SDL_UDEV_DEVICE_SOUND;
     } else if (SDL_strcmp(subsystem, "input") == 0) {
         /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */
-        
+
         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
             devclass |= SDL_UDEV_DEVICE_JOYSTICK;
@@ -381,13 +437,13 @@ device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
         if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE) &&
             val != NULL && SDL_strcmp(val, "1") == 0 ) {
             devclass |= SDL_UDEV_DEVICE_JOYSTICK;
-	}
-        
+        }
+
         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
             devclass |= SDL_UDEV_DEVICE_MOUSE;
         }
-        
+
         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
             devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;
@@ -396,7 +452,7 @@ device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
         /* The undocumented rule is:
            - All devices with keys get ID_INPUT_KEY
            - From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
-           
+
            Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
         */
         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");
@@ -425,14 +481,14 @@ device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
     } else {
         return;
     }
-    
+
     /* Process callbacks */
     for (item = _this->first; item != NULL; item = item->next) {
         item->callback(type, devclass, path);
     }
 }
 
-void 
+void
 SDL_UDEV_Poll(void)
 {
     struct udev_device *dev = NULL;
@@ -456,12 +512,12 @@ SDL_UDEV_Poll(void)
                 device_event(SDL_UDEV_DEVICEREMOVED, dev);
             }
         }
-        
+
         _this->syms.udev_device_unref(dev);
     }
 }
 
-int 
+int
 SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
 {
     SDL_UDEV_CallbackList *item;
@@ -469,7 +525,7 @@ SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
     if (item == NULL) {
         return SDL_OutOfMemory();
     }
-    
+
     item->callback = cb;
 
     if (_this->last == NULL) {
@@ -478,11 +534,11 @@ SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
         _this->last->next = item;
         _this->last = item;
     }
-    
+
     return 1;
 }
 
-void 
+void
 SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
 {
     SDL_UDEV_CallbackList *item;
@@ -505,7 +561,6 @@ SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
         }
         prev = item;
     }
-    
 }
 
 const SDL_UDEV_Symbols *
diff --git a/src/core/linux/SDL_udev.h b/src/core/linux/SDL_udev.h
index da81474e73..5f64e2d798 100644
--- a/src/core/linux/SDL_udev.h
+++ b/src/core/linux/SDL_udev.h
@@ -101,6 +101,7 @@ extern void SDL_UDEV_UnloadLibrary(void);
 extern int SDL_UDEV_LoadLibrary(void);
 extern void SDL_UDEV_Poll(void);
 extern void SDL_UDEV_Scan(void);
+extern SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version);
 extern int SDL_UDEV_AddCallback(SDL_UDEV_Callback cb);
 extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb);
 extern const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void);
diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index fe82ae82ff..188d05a066 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -178,7 +178,7 @@ GuessIsJoystick(int fd)
 }
 
 static int
-IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid)
+IsJoystick(const char *path, int fd, char **name_return, SDL_JoystickGUID *guid)
 {
     struct input_id inpid;
     Uint16 *guid16 = (Uint16 *)guid->data;
@@ -187,6 +187,9 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid)
 
     if (ioctl(fd, JSIOCGNAME(sizeof(product_string)), product_string) >= 0) {
         SDL_zero(inpid);
+#if SDL_USE_LIBUDEV
+        SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version);
+#endif
     } else {
         /* When udev is enabled we only get joystick devices here, so there's no need to test them */
         if (enumeration_method != ENUMERATION_LIBUDEV && !GuessIsJoystick(fd)) {
@@ -326,7 +329,7 @@ MaybeAddDevice(const char *path)
     SDL_Log("Checking %s\n", path);
 #endif
 
-    isstick = IsJoystick(fd, &name, &guid);
+    isstick = IsJoystick(path, fd, &name, &guid);
     close(fd);
     if (!isstick) {
         return -1;