Fixed input device detection on systems that aren't running the udev daemon
authorSam Lantinga <slouken@libsdl.org>
Wed, 15 Oct 2014 09:10:50 -0700
changeset 9160 25a97df364fb
parent 9159 d743b9d03a52
child 9161 6e34073ef024
Fixed input device detection on systems that aren't running the udev daemon
src/core/linux/SDL_udev.c
src/core/linux/SDL_udev.h
--- a/src/core/linux/SDL_udev.c	Wed Oct 15 09:09:57 2014 -0700
+++ b/src/core/linux/SDL_udev.c	Wed Oct 15 09:10:50 2014 -0700
@@ -25,18 +25,19 @@
  * udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc)
  * udevadm info --query=property -n input/event2
  */
-
 #include "SDL_udev.h"
 
 #ifdef SDL_USE_LIBUDEV
 
+#include <linux/input.h>
+
+#include "SDL.h"
+
 static char* SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };
 
 #define _THIS SDL_UDEV_PrivateData *_this
 static _THIS = NULL;
 
-#include "SDL.h"
-
 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);
@@ -64,7 +65,9 @@
     SDL_UDEV_SYM(udev_device_get_action);
     SDL_UDEV_SYM(udev_device_get_devnode);
     SDL_UDEV_SYM(udev_device_get_subsystem);
+    SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);
     SDL_UDEV_SYM(udev_device_get_property_value);
+    SDL_UDEV_SYM(udev_device_get_sysattr_value);
     SDL_UDEV_SYM(udev_device_new_from_syspath);
     SDL_UDEV_SYM(udev_device_unref);
     SDL_UDEV_SYM(udev_enumerate_add_match_property);
@@ -274,6 +277,109 @@
     return retval;
 }
 
+#define BITS_PER_LONG           (sizeof(unsigned long) * 8)
+#define NBITS(x)                ((((x)-1)/BITS_PER_LONG)+1)
+#define OFF(x)                  ((x)%BITS_PER_LONG)
+#define BIT(x)                  (1UL<<OFF(x))
+#define LONG(x)                 ((x)/BITS_PER_LONG)
+#define test_bit(bit, array)    ((array[LONG(bit)] >> OFF(bit)) & 1)
+
+static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)
+{
+    const char *value;
+    char text[4096];
+    char *word;
+    int i;
+    unsigned long v;
+
+    SDL_memset(bitmask, 0, bitmask_len*sizeof(*bitmask));
+    value = _this->udev_device_get_sysattr_value(pdev, attr);
+    if (!value) {
+        return;
+    }
+
+    SDL_strlcpy(text, value, sizeof(text));
+    i = 0;
+    while ((word = SDL_strrchr(text, ' ')) != NULL) {
+        v = SDL_strtoul(word+1, NULL, 16);
+        if (i < bitmask_len) {
+            bitmask[i] = v;
+        }
+        ++i;
+        *word = '\0';
+    }
+    v = SDL_strtoul(text, NULL, 16);
+    if (i < bitmask_len) {
+        bitmask[i] = v;
+    }
+}
+
+static int
+guess_device_class(struct udev_device *dev)
+{
+    int devclass = 0;
+    struct udev_device *pdev;
+    unsigned long bitmask_ev[NBITS(EV_MAX)];
+    unsigned long bitmask_abs[NBITS(ABS_MAX)];
+    unsigned long bitmask_key[NBITS(KEY_MAX)];
+    unsigned long bitmask_rel[NBITS(REL_MAX)];
+    unsigned long keyboard_mask;
+
+    /* walk up the parental chain until we find the real input device; the
+     * argument is very likely a subdevice of this, like eventN */
+    pdev = dev;
+    while (pdev && !_this->udev_device_get_sysattr_value(pdev, "capabilities/ev")) {
+        pdev = _this->udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
+    }
+    if (!pdev) {
+        return 0;
+    }
+
+    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));
+
+    if (test_bit(EV_ABS, bitmask_ev) &&
+        test_bit(ABS_X, bitmask_abs) && test_bit(ABS_Y, bitmask_abs)) {
+        if (test_bit(BTN_STYLUS, bitmask_key) || test_bit(BTN_TOOL_PEN, bitmask_key)) {
+            ; /* ID_INPUT_TABLET */
+        } else if (test_bit(BTN_TOOL_FINGER, bitmask_key) && !test_bit(BTN_TOOL_PEN, bitmask_key)) {
+            ; /* ID_INPUT_TOUCHPAD */
+        } else if (test_bit(BTN_MOUSE, bitmask_key)) {
+            devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */
+        } else if (test_bit(BTN_TOUCH, bitmask_key)) {
+            ; /* ID_INPUT_TOUCHSCREEN */
+        } else if (test_bit(BTN_TRIGGER, bitmask_key) ||
+                 test_bit(BTN_A, bitmask_key) ||
+                 test_bit(BTN_1, bitmask_key) ||
+                 test_bit(ABS_RX, bitmask_abs) ||
+                 test_bit(ABS_RY, bitmask_abs) ||
+                 test_bit(ABS_RZ, bitmask_abs) ||
+                 test_bit(ABS_THROTTLE, bitmask_abs) ||
+                 test_bit(ABS_RUDDER, bitmask_abs) ||
+                 test_bit(ABS_WHEEL, bitmask_abs) ||
+                 test_bit(ABS_GAS, bitmask_abs) ||
+                 test_bit(ABS_BRAKE, bitmask_abs)) {
+            devclass |= SDL_UDEV_DEVICE_JOYSTICK; /* ID_INPUT_JOYSTICK */
+        }
+    }
+
+    if (test_bit(EV_REL, bitmask_ev) &&
+        test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) &&
+        test_bit(BTN_MOUSE, bitmask_key)) {
+        devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */
+    }
+
+    /* the first 32 bits are ESC, numbers, and Q to D; if we have all of
+     * those, consider it a full keyboard; do not test KEY_RESERVED, though */
+    keyboard_mask = 0xFFFFFFFE;
+    if ((bitmask_key[0] & keyboard_mask) == keyboard_mask)
+        devclass |= SDL_UDEV_DEVICE_KEYBOARD; /* ID_INPUT_KEYBOARD */
+
+    return devclass;
+}
+
 static void 
 device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) 
 {
@@ -308,7 +414,7 @@
         }
 
         if (devclass == 0) {
-            // Fall back to old style input classes
+            /* Fall back to old style input classes */
             val = _this->udev_device_get_property_value(dev, "ID_CLASS");
             if (val != NULL) {
                 if (SDL_strcmp(val, "joystick") == 0) {
@@ -321,7 +427,8 @@
                     return;
                 }
             } else {
-                return;
+                /* We could be linked with libudev on a system that doesn't have udev running */
+                devclass = guess_device_class(dev);
             }
         }
     } else {
--- a/src/core/linux/SDL_udev.h	Wed Oct 15 09:09:57 2014 -0700
+++ b/src/core/linux/SDL_udev.h	Wed Oct 15 09:10:50 2014 -0700
@@ -75,7 +75,9 @@
     const char *(*udev_device_get_action)(struct udev_device *);
     const char *(*udev_device_get_devnode)(struct udev_device *);
     const char *(*udev_device_get_subsystem)(struct udev_device *);
+	struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype);
     const char *(*udev_device_get_property_value)(struct udev_device *, const char *);
+	const char *(*udev_device_get_sysattr_value)(struct udev_device *udev_device, const char *sysattr);
     struct udev_device *(*udev_device_new_from_syspath)(struct udev *, const char *);
     void (*udev_device_unref)(struct udev_device *);
     int (*udev_enumerate_add_match_property)(struct udev_enumerate *, const char *, const char *);