SDL: Try SDL_UDEV_deviceclass to detect joysticks even if in a container

From 3b1e0e163ba3933daa9aa19f06a7bb3909e05c8a Mon Sep 17 00:00:00 2001
From: Tyson Whitehead <[EMAIL REDACTED]>
Date: Wed, 29 Nov 2023 18:19:03 -0500
Subject: [PATCH] Try SDL_UDEV_deviceclass to detect joysticks even if in a
 container

The udev container issue is mostly to do with device notifications
and netlink. The device classification stuff just pokes file in /sys
and /run/udev. Doesn't hurt to try it first for classifying joysticks
and then fall to the guess heuristics if it fails.
---
 src/core/linux/SDL_udev.c            | 43 +++++++++++++++++++++-------
 src/core/linux/SDL_udev.h            |  2 +-
 src/joystick/linux/SDL_sysjoystick.c | 12 ++++----
 3 files changed, 40 insertions(+), 17 deletions(-)

diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c
index 7eab8200cd25..1d7e0596616a 100644
--- a/src/core/linux/SDL_udev.c
+++ b/src/core/linux/SDL_udev.c
@@ -47,6 +47,9 @@ static _THIS = NULL;
 static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr);
 static int SDL_UDEV_load_syms(void);
 static SDL_bool SDL_UDEV_hotplug_update_available(void);
+static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len);
+static int guess_device_class(struct udev_device *dev);
+static int device_class(struct udev_device *dev);
 static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);
 
 static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr)
@@ -222,7 +225,7 @@ void 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)
+SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)
 {
     struct udev_enumerate *enumerate = NULL;
     struct udev_list_entry *devs = NULL;
@@ -250,6 +253,7 @@ SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16
 
             existing_path = _this->syms.udev_device_get_devnode(dev);
             if (existing_path && SDL_strcmp(device_path, existing_path) == 0) {
+                int class_temp;
                 found = SDL_TRUE;
 
                 val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
@@ -266,6 +270,11 @@ SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16
                 if (val) {
                     *version = (Uint16)SDL_strtol(val, NULL, 16);
                 }
+
+                class_temp = device_class(dev);
+                if (class_temp) {
+                    *class = class_temp;
+                }
             }
             _this->syms.udev_device_unref(dev);
         }
@@ -394,20 +403,17 @@ static int guess_device_class(struct udev_device *dev)
                                       &bitmask_rel[0]);
 }
 
-static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
+static int device_class(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) {
-        return;
+    subsystem = _this->syms.udev_device_get_subsystem(dev);
+    if (!subsystem) {
+        return 0;
     }
 
-    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) {
@@ -455,18 +461,33 @@ static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
                     devclass = SDL_UDEV_DEVICE_MOUSE;
                 } else if (SDL_strcmp(val, "kbd") == 0) {
                     devclass = SDL_UDEV_DEVICE_KEYBOARD;
-                } else {
-                    return;
                 }
             } else {
                 /* We could be linked with libudev on a system that doesn't have udev running */
                 devclass = guess_device_class(dev);
             }
         }
-    } else {
+    }
+
+    return devclass;
+}
+
+static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
+{
+    int devclass = 0;
+    const char *path;
+    SDL_UDEV_CallbackList *item;
+
+    path = _this->syms.udev_device_get_devnode(dev);
+    if (!path) {
         return;
     }
 
+    devclass = device_class(dev);
+    if (!devclass) {
+         return;
+    }
+
     /* Process callbacks */
     for (item = _this->first; item; item = item->next) {
         item->callback(type, devclass, path);
diff --git a/src/core/linux/SDL_udev.h b/src/core/linux/SDL_udev.h
index 2011d1835540..571d48bc0145 100644
--- a/src/core/linux/SDL_udev.h
+++ b/src/core/linux/SDL_udev.h
@@ -103,7 +103,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 SDL_bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class);
 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 af8646267d8d..223654e7790b 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -278,18 +278,20 @@ static int IsJoystick(const char *path, int fd, char **name_return, Uint16 *vend
     struct input_id inpid;
     char *name;
     char product_string[128];
+    int class = 0;
 
-    if (ioctl(fd, JSIOCGNAME(sizeof(product_string)), product_string) >= 0) {
-        SDL_zero(inpid);
+    SDL_zero(inpid);
 #ifdef SDL_USE_LIBUDEV
-        SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version);
+    SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class);
 #endif
-    } else {
+    if (ioctl(fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) {
         /* 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)) {
+        if (enumeration_method != ENUMERATION_LIBUDEV &&
+            !(class & SDL_UDEV_DEVICE_JOYSTICK) && ( class || !GuessIsJoystick(fd))) {
             return 0;
         }
 
+        /* Could have vendor and product already from udev, but should agree with evdev */
         if (ioctl(fd, EVIOCGID, &inpid) < 0) {
             return 0;
         }