diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index fc145e9ca..1838cb36c 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -32,6 +32,7 @@ #include /* errno, strerror */ #include #include /* For the definition of PATH_MAX */ +#include #include #include #include @@ -40,6 +41,7 @@ #include "SDL_assert.h" #include "SDL_hints.h" #include "SDL_joystick.h" +#include "SDL_log.h" #include "SDL_endian.h" #include "SDL_timer.h" #include "../../events/SDL_events_c.h" @@ -92,16 +94,10 @@ typedef enum ENUMERATION_FALLBACK } EnumerationMethod; -#if SDL_USE_LIBUDEV static EnumerationMethod enumeration_method = ENUMERATION_UNSET; -#else -const EnumerationMethod enumeration_method = ENUMERATION_FALLBACK; -#endif static int MaybeAddDevice(const char *path); -#if SDL_USE_LIBUDEV static int MaybeRemoveDevice(const char *path); -#endif /* SDL_USE_LIBUDEV */ /* A linked list of available joysticks */ typedef struct SDL_joylist_item @@ -121,6 +117,7 @@ typedef struct SDL_joylist_item static SDL_joylist_item *SDL_joylist = NULL; static SDL_joylist_item *SDL_joylist_tail = NULL; static int numjoysticks = 0; +static int inotify_fd = -1; static Uint32 last_joy_detect_time; static time_t last_input_dir_mtime; @@ -188,7 +185,7 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid) char product_string[128]; /* When udev is enabled we only get joystick devices here, so there's no need to test them */ - if (enumeration_method == ENUMERATION_FALLBACK && !GuessIsJoystick(fd)) { + if (enumeration_method != ENUMERATION_LIBUDEV && !GuessIsJoystick(fd)) { return 0; } @@ -503,6 +500,75 @@ static void SteamControllerDisconnectedCallback(int device_instance) } } +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; +} + +static void +LINUX_InotifyJoystickDetect(void) +{ + 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) { + if (StrHasPrefix(buf.event.name, "event") && + StrIsInteger(buf.event.name + strlen ("event"))) { + char path[PATH_MAX]; + + SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", buf.event.name); + + if (buf.event.mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB)) { + MaybeAddDevice(path); + } + else if (buf.event.mask & (IN_DELETE | IN_MOVED_FROM)) { + MaybeRemoveDevice(path); + } + } + } + + len = sizeof (struct inotify_event) + buf.event.len; + remain -= len; + + if (remain != 0) { + memmove (&buf.storage[0], &buf.storage[len], remain); + } + } +} + static void LINUX_FallbackJoystickDetect(void) { @@ -547,7 +613,10 @@ LINUX_JoystickDetect(void) } else #endif - { + if (inotify_fd >= 0) { + LINUX_InotifyJoystickDetect(); + } + else { LINUX_FallbackJoystickDetect(); } @@ -589,6 +658,10 @@ LINUX_JoystickInit(void) SDL_InitSteamControllers(SteamControllerConnectedCallback, SteamControllerDisconnectedCallback); + /* Force immediate joystick detection if using fallback */ + last_joy_detect_time = 0; + last_input_dir_mtime = 0; + #if SDL_USE_LIBUDEV if (enumeration_method == ENUMERATION_LIBUDEV) { if (SDL_UDEV_Init() < 0) { @@ -607,9 +680,28 @@ LINUX_JoystickInit(void) else #endif { - /* Force immediate joystick detection */ - last_joy_detect_time = 0; - last_input_dir_mtime = 0; + inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + + if (inotify_fd < 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, + "Unable to initialize inotify, falling back to polling: %s", + strerror (errno)); + } + else { + /* 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/input", + 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)); + } + } /* Report all devices currently present */ LINUX_JoystickDetect(); @@ -1224,6 +1316,9 @@ LINUX_JoystickQuit(void) SDL_joylist_item *item = NULL; SDL_joylist_item *next = NULL; + close(inotify_fd); + inotify_fd = -1; + for (item = SDL_joylist; item; item = next) { next = item->next; SDL_free(item->path);