SDL: Try SDL_UDEV_deviceclass to detect joysticks even if in a container (6daf2)

From 6daf2e943f086d57ba85ad873cfde16756d3c1d2 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            | 50 +++++++++++++++++++---------
 src/core/linux/SDL_udev.h            |  2 +-
 src/joystick/linux/SDL_sysjoystick.c | 12 ++++---
 3 files changed, 42 insertions(+), 22 deletions(-)

diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c
index 59d37d0e9fdd..a2d5bb62f009 100644
--- a/src/core/linux/SDL_udev.c
+++ b/src/core/linux/SDL_udev.c
@@ -42,6 +42,9 @@ static SDL_UDEV_PrivateData *_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)
@@ -218,7 +221,7 @@ int SDL_UDEV_Scan(void)
     return 0;
 }
 
-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;
@@ -246,6 +249,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");
@@ -262,6 +266,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);
         }
@@ -393,29 +402,23 @@ 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;
+    }
 
     if (SDL_strcmp(subsystem, "sound") == 0) {
         devclass = SDL_UDEV_DEVICE_SOUND;
     } else if (SDL_strcmp(subsystem, "video4linux") == 0) {
-        devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;
-
         val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");
-        if (!val || !SDL_strcasestr(val, "capture")) {
-            return;
+        if (val && SDL_strcasestr(val, "capture")) {
+            devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;
         }
     } else if (SDL_strcmp(subsystem, "input") == 0) {
         /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */
@@ -467,18 +470,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_HAS_KEYS | 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 ff6399e40f41..60bef169c35f 100644
--- a/src/core/linux/SDL_udev.h
+++ b/src/core/linux/SDL_udev.h
@@ -102,7 +102,7 @@ extern void SDL_UDEV_UnloadLibrary(void);
 extern int SDL_UDEV_LoadLibrary(void);
 extern void SDL_UDEV_Poll(void);
 extern int 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 660887a93a90..6d55e9cc419d 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -282,18 +282,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;
         }