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>
This commit is contained in:
Ludovico de Nittis 2021-02-16 12:39:48 +01:00 committed by Sam Lantinga
parent b17242bf98
commit 2c3269152a
1 changed files with 125 additions and 0 deletions

View File

@ -52,6 +52,13 @@
#ifdef SDL_USE_LIBUDEV #ifdef SDL_USE_LIBUDEV
#include <poll.h> #include <poll.h>
#endif #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 #endif
typedef enum typedef enum
@ -101,6 +108,7 @@ static SDL_HIDAPI_Device *SDL_HIDAPI_devices;
static int SDL_HIDAPI_numjoysticks = 0; static int SDL_HIDAPI_numjoysticks = 0;
static SDL_bool initialized = SDL_FALSE; static SDL_bool initialized = SDL_FALSE;
static SDL_bool shutting_down = SDL_FALSE; static SDL_bool shutting_down = SDL_FALSE;
static int inotify_fd = -1;
#if defined(SDL_USE_LIBUDEV) #if defined(SDL_USE_LIBUDEV)
static const SDL_UDEV_Symbols * usyms = NULL; static const SDL_UDEV_Symbols * usyms = NULL;
@ -194,6 +202,46 @@ static void CallbackIOServiceFunc(void *context, io_iterator_t portIterator)
} }
#endif /* __MACOSX__ */ #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 static void
HIDAPI_InitializeDiscovery() HIDAPI_InitializeDiscovery()
{ {
@ -301,7 +349,37 @@ HIDAPI_InitializeDiscovery()
} }
} }
} }
else
#endif /* SDL_USE_LIBUDEV */ #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 static void
@ -368,7 +446,49 @@ HIDAPI_UpdateDiscovery()
} }
} }
} }
else
#endif /* SDL_USE_LIBUDEV */ #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 static void
@ -1283,6 +1403,11 @@ HIDAPI_JoystickQuit(void)
SDL_HIDAPI_QuitRumble(); SDL_HIDAPI_QuitRumble();
if (inotify_fd >= 0) {
close(inotify_fd);
inotify_fd = -1;
}
while (SDL_HIDAPI_devices) { while (SDL_HIDAPI_devices) {
HIDAPI_DelDevice(SDL_HIDAPI_devices); HIDAPI_DelDevice(SDL_HIDAPI_devices);
} }