From 2c3269152a9b363b0b67c035f79a745b2791b2dc Mon Sep 17 00:00:00 2001 From: Ludovico de Nittis Date: Tue, 16 Feb 2021 12:39:48 +0100 Subject: [PATCH] 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 --- src/joystick/hidapi/SDL_hidapijoystick.c | 125 +++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index e751bb15f..25eb7b6dc 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -52,6 +52,13 @@ #ifdef SDL_USE_LIBUDEV #include #endif +#ifdef HAVE_INOTIFY +#include /* errno, strerror */ +#include +#include /* For the definition of NAME_MAX */ +#include +#include +#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); }