SDL: linux: Pass evdev properties when guessing device type

From 0d5aa70e62e477e99d024287070c70e3ee7b9da1 Mon Sep 17 00:00:00 2001
From: Simon McVittie <[EMAIL REDACTED]>
Date: Fri, 16 Jun 2023 15:14:33 +0100
Subject: [PATCH] linux: Pass evdev properties when guessing device type

In newer kernels, devices that can be positively identified as a
particular device type (for example accelerometers) get a property
bit set. Plumb this information through into the function, but don't
use it for anything just yet.

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 src/core/linux/SDL_evdev_capabilities.c |  3 ++-
 src/core/linux/SDL_evdev_capabilities.h |  7 ++++++-
 src/core/linux/SDL_udev.c               |  5 ++++-
 src/joystick/linux/SDL_sysjoystick.c    |  7 ++++++-
 test/testevdev.c                        | 10 ++++++++--
 5 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/src/core/linux/SDL_evdev_capabilities.c b/src/core/linux/SDL_evdev_capabilities.c
index 054a8b9b6bbc..233426da4482 100644
--- a/src/core/linux/SDL_evdev_capabilities.c
+++ b/src/core/linux/SDL_evdev_capabilities.c
@@ -37,7 +37,8 @@
 #endif
 
 extern int
-SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_ev[NBITS(EV_MAX)],
+SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)],
+                           const unsigned long bitmask_ev[NBITS(EV_MAX)],
                            const unsigned long bitmask_abs[NBITS(ABS_MAX)],
                            const unsigned long bitmask_key[NBITS(KEY_MAX)],
                            const unsigned long bitmask_rel[NBITS(REL_MAX)])
diff --git a/src/core/linux/SDL_evdev_capabilities.h b/src/core/linux/SDL_evdev_capabilities.h
index aa0ddb41a019..91364df6a8ff 100644
--- a/src/core/linux/SDL_evdev_capabilities.h
+++ b/src/core/linux/SDL_evdev_capabilities.h
@@ -28,6 +28,10 @@
 
 #include <linux/input.h>
 
+#ifndef INPUT_PROP_MAX
+#define INPUT_PROP_MAX 0x1f
+#endif
+
 /* A device can be any combination of these classes */
 typedef enum
 {
@@ -48,7 +52,8 @@ typedef enum
 #define EVDEV_LONG(x)        ((x) / BITS_PER_LONG)
 #define test_bit(bit, array) ((array[EVDEV_LONG(bit)] >> EVDEV_OFF(bit)) & 1)
 
-extern int SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_ev[NBITS(EV_MAX)],
+extern int SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)],
+                                      const unsigned long bitmask_ev[NBITS(EV_MAX)],
                                       const unsigned long bitmask_abs[NBITS(ABS_MAX)],
                                       const unsigned long bitmask_key[NBITS(KEY_MAX)],
                                       const unsigned long bitmask_rel[NBITS(REL_MAX)]);
diff --git a/src/core/linux/SDL_udev.c b/src/core/linux/SDL_udev.c
index 71fbcf00551f..9e72d79ed1cb 100644
--- a/src/core/linux/SDL_udev.c
+++ b/src/core/linux/SDL_udev.c
@@ -362,6 +362,7 @@ static void get_caps(struct udev_device *dev, struct udev_device *pdev, const ch
 static int guess_device_class(struct udev_device *dev)
 {
     struct udev_device *pdev;
+    unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];
     unsigned long bitmask_ev[NBITS(EV_MAX)];
     unsigned long bitmask_abs[NBITS(ABS_MAX)];
     unsigned long bitmask_key[NBITS(KEY_MAX)];
@@ -377,12 +378,14 @@ static int guess_device_class(struct udev_device *dev)
         return 0;
     }
 
+    get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props));
     get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));
     get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));
     get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));
     get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));
 
-    return SDL_EVDEV_GuessDeviceClass(&bitmask_ev[0],
+    return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0],
+                                      &bitmask_ev[0],
                                       &bitmask_abs[0],
                                       &bitmask_key[0],
                                       &bitmask_rel[0]);
diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index 25c71b85edee..e7628f112891 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -204,6 +204,7 @@ static SDL_bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version,
 
 static int GuessIsJoystick(int fd)
 {
+    unsigned long propbit[NBITS(INPUT_PROP_MAX)] = { 0 };
     unsigned long evbit[NBITS(EV_MAX)] = { 0 };
     unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
     unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
@@ -217,7 +218,11 @@ static int GuessIsJoystick(int fd)
         return 0;
     }
 
-    devclass = SDL_EVDEV_GuessDeviceClass(evbit, absbit, keybit, relbit);
+    /* This is a newer feature, so it's allowed to fail - if so, then the
+     * device just doesn't have any properties. */
+    (void) ioctl(fd, EVIOCGPROP(sizeof(propbit)), propbit);
+
+    devclass = SDL_EVDEV_GuessDeviceClass(propbit, evbit, absbit, keybit, relbit);
 
     if (devclass & SDL_UDEV_DEVICE_JOYSTICK) {
         return 1;
diff --git a/test/testevdev.c b/test/testevdev.c
index 0ab85aefd294..e8315b1baff5 100644
--- a/test/testevdev.c
+++ b/test/testevdev.c
@@ -1794,6 +1794,7 @@ run_test(void)
         int actual;
         struct
         {
+            unsigned long props[NBITS(INPUT_PROP_MAX)];
             unsigned long ev[NBITS(EV_MAX)];
             unsigned long abs[NBITS(ABS_MAX)];
             unsigned long keys[NBITS(KEY_MAX)];
@@ -1803,11 +1804,16 @@ run_test(void)
         printf("%s...\n", t->name);
 
         memset(&caps, '\0', sizeof(caps));
+        memcpy(caps.props, t->props, sizeof(t->props));
         memcpy(caps.ev, t->ev, sizeof(t->ev));
         memcpy(caps.keys, t->keys, sizeof(t->keys));
         memcpy(caps.abs, t->abs, sizeof(t->abs));
         memcpy(caps.rel, t->rel, sizeof(t->rel));
 
+        for (j = 0; j < SDL_arraysize(caps.props); j++) {
+            caps.props[j] = SwapLongLE(caps.props[j]);
+        }
+
         for (j = 0; j < SDL_arraysize(caps.ev); j++) {
             caps.ev[j] = SwapLongLE(caps.ev[j]);
         }
@@ -1824,8 +1830,8 @@ run_test(void)
             caps.rel[j] = SwapLongLE(caps.rel[j]);
         }
 
-        actual = SDL_EVDEV_GuessDeviceClass(caps.ev, caps.abs, caps.keys,
-                                            caps.rel);
+        actual = SDL_EVDEV_GuessDeviceClass(caps.props, caps.ev, caps.abs,
+                                            caps.keys, caps.rel);
 
         if (actual == t->expected) {
             printf("\tOK\n");