Use inotify for HIDAPI joystick enumeration if not using udev This improves SDL's ability to detect HIDAPI joystick hotplug in a container environment because we cannot reliably receive events from udev in a container. For a more detailed explanation of why this issue happens with containers, please check the previous commit "joystick: Use inotify to detect joystick unplug if not using udev" (b0eba1c5). Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index e751bb1..25eb7b6 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -52,6 +52,13 @@
#ifdef SDL_USE_LIBUDEV
#include <poll.h>
#endif
+#ifdef HAVE_INOTIFY
+#include <errno.h> /* errno, strerror */
+#include <fcntl.h>
+#include <limits.h> /* For the definition of NAME_MAX */
+#include <sys/inotify.h>
+#include <unistd.h>
+#endif
#endif
typedef enum
@@ -101,6 +108,7 @@ static SDL_HIDAPI_Device *SDL_HIDAPI_devices;
static int SDL_HIDAPI_numjoysticks = 0;
static SDL_bool initialized = SDL_FALSE;
static SDL_bool shutting_down = SDL_FALSE;
+static int inotify_fd = -1;
#if defined(SDL_USE_LIBUDEV)
static const SDL_UDEV_Symbols * usyms = NULL;
@@ -194,6 +202,46 @@ static void CallbackIOServiceFunc(void *context, io_iterator_t portIterator)
}
#endif /* __MACOSX__ */
+#ifdef HAVE_INOTIFY
+#ifdef HAVE_INOTIFY_INIT1
+static int SDL_inotify_init1(void) {
+ return inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
+}
+#else
+static int SDL_inotify_init1(void) {
+ int fd = inotify_init();
+ if (fd < 0) return -1;
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ return fd;
+}
+#endif
+
+static int
+StrHasPrefix(const char *string, const char *prefix)
+{
+ return (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0);
+}
+
+static int
+StrIsInteger(const char *string)
+{
+ const char *p;
+
+ if (*string == '\0') {
+ return 0;
+ }
+
+ for (p = string; *p != '\0'; p++) {
+ if (*p < '0' || *p > '9') {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+#endif
+
static void
HIDAPI_InitializeDiscovery()
{
@@ -301,7 +349,37 @@ HIDAPI_InitializeDiscovery()
}
}
}
+ else
#endif /* SDL_USE_LIBUDEV */
+ {
+#if defined(HAVE_INOTIFY)
+ inotify_fd = SDL_inotify_init1();
+
+ if (inotify_fd < 0) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "Unable to initialize inotify, falling back to polling: %s",
+ strerror(errno));
+ return;
+ }
+
+ /* We need to watch for attribute changes in addition to
+ * creation, because when a device is first created, it has
+ * permissions that we can't read. When udev chmods it to
+ * something that we maybe *can* read, we'll get an
+ * IN_ATTRIB event to tell us. */
+ if (inotify_add_watch(inotify_fd, "/dev",
+ IN_CREATE | IN_DELETE | IN_MOVE | IN_ATTRIB) < 0) {
+ close(inotify_fd);
+ inotify_fd = -1;
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "Unable to add inotify watch, falling back to polling: %s",
+ strerror (errno));
+ return;
+ }
+
+ SDL_HIDAPI_discovery.m_bCanGetNotifications = SDL_TRUE;
+#endif /* HAVE_INOTIFY */
+ }
}
static void
@@ -368,7 +446,49 @@ HIDAPI_UpdateDiscovery()
}
}
}
+ else
#endif /* SDL_USE_LIBUDEV */
+ {
+#if defined(HAVE_INOTIFY)
+ if (inotify_fd >= 0) {
+ union
+ {
+ struct inotify_event event;
+ char storage[4096];
+ char enough_for_inotify[sizeof (struct inotify_event) + NAME_MAX + 1];
+ } buf;
+ ssize_t bytes;
+ size_t remain = 0;
+ size_t len;
+
+ bytes = read(inotify_fd, &buf, sizeof (buf));
+
+ if (bytes > 0) {
+ remain = (size_t) bytes;
+ }
+
+ while (remain > 0) {
+ if (buf.event.len > 0 &&
+ !SDL_HIDAPI_discovery.m_bHaveDevicesChanged) {
+ if (StrHasPrefix(buf.event.name, "hidraw") &&
+ StrIsInteger(buf.event.name + strlen ("hidraw"))) {
+ SDL_HIDAPI_discovery.m_bHaveDevicesChanged = SDL_TRUE;
+ /* We found an hidraw change. We still continue to
+ * drain the inotify fd to avoid leaving old
+ * notifications in the queue. */
+ }
+ }
+
+ len = sizeof (struct inotify_event) + buf.event.len;
+ remain -= len;
+
+ if (remain != 0) {
+ memmove(&buf.storage[0], &buf.storage[len], remain);
+ }
+ }
+ }
+#endif /* HAVE_INOTIFY */
+ }
}
static void
@@ -1283,6 +1403,11 @@ HIDAPI_JoystickQuit(void)
SDL_HIDAPI_QuitRumble();
+ if (inotify_fd >= 0) {
+ close(inotify_fd);
+ inotify_fd = -1;
+ }
+
while (SDL_HIDAPI_devices) {
HIDAPI_DelDevice(SDL_HIDAPI_devices);
}