From 9df023d803966f60d7dbe6ef1ccf1356edf99c30 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 11 Dec 2012 12:07:06 -0500 Subject: [PATCH] Updated Linux joystick code to support hotplug, GUIDs, etc. This uses libudev for hotplug, but it's optional, so we'll just try to find some reasonable defaults without it (maybe an older Linux box or under FreeBSD's Linux emulation?). --- CMakeLists.txt | 4 +- configure.in | 17 + include/SDL_config.h.cmake | 1 + include/SDL_config.h.in | 1 + src/joystick/linux/SDL_sysjoystick.c | 651 +++++++++++++++++++------ src/joystick/linux/SDL_sysjoystick_c.h | 9 +- 6 files changed, 534 insertions(+), 149 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80389d100..0b561c91c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -648,7 +648,9 @@ if(UNIX AND NOT APPLE) set(SOURCE_FILES ${SOURCE_FILES} ${HAPTIC_SOURCES}) set(HAVE_SDL_HAPTIC TRUE) endif(SDL_HAPTIC AND HAVE_INPUT_EVENTS) - endif(LINUX + + check_include_file("libudev.h" HAVE_LIBUDEV_H) + endif(LINUX) if(INPUT_TSLIB) check_c_source_compiles(" diff --git a/configure.in b/configure.in index b7f974df5..8fd9280c4 100644 --- a/configure.in +++ b/configure.in @@ -1667,6 +1667,22 @@ CheckInputEvents() fi } +dnl See if the platform offers libudev for device enumeration and hotplugging. +CheckLibUDev() +{ + AC_ARG_ENABLE(libudev, +AC_HELP_STRING([--enable-libudev], [enable libudev support [[default=yes]]]), + , enable_libudev=yes) + if test x$enable_libudev = xyes; then + AC_CHECK_HEADER(libudev.h, + have_libudev_h_hdr=yes, + have_libudev_h_hdr=no) + if test x$have_libudev_h_hdr = xyes; then + AC_DEFINE(HAVE_LIBUDEV_H, 1, [ ]) + fi + fi +} + dnl See if we can use the Touchscreen input library CheckTslib() { @@ -2191,6 +2207,7 @@ case "$host" in CheckFusionSound CheckOpenGLX11 CheckOpenGLESX11 + CheckLibUDev CheckInputEvents CheckTslib CheckUSBHID diff --git a/include/SDL_config.h.cmake b/include/SDL_config.h.cmake index 6328eb9f5..d5ae1d815 100644 --- a/include/SDL_config.h.cmake +++ b/include/SDL_config.h.cmake @@ -65,6 +65,7 @@ #cmakedefine HAVE_SIGNAL_H 1 #cmakedefine HAVE_ALTIVEC_H 1 #cmakedefine HAVE_PTHREAD_NP_H 1 +#cmakedefine HAVE_LIBUDEV_H 1 /* C library functions */ #cmakedefine HAVE_MALLOC 1 diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in index 0e271a83c..bcf443d48 100644 --- a/include/SDL_config.h.in +++ b/include/SDL_config.h.in @@ -70,6 +70,7 @@ #undef HAVE_SIGNAL_H #undef HAVE_ALTIVEC_H #undef HAVE_PTHREAD_NP_H +#undef HAVE_LIBUDEV_H /* C library functions */ #undef HAVE_MALLOC diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index 938d1d6d7..77d75800f 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -35,31 +35,168 @@ #include /* For the definition of PATH_MAX */ #include +#include "SDL_assert.h" #include "SDL_joystick.h" +#include "SDL_endian.h" #include "../SDL_sysjoystick.h" #include "../SDL_joystick_c.h" #include "SDL_sysjoystick_c.h" -/* The maximum number of joysticks we'll detect */ -#define MAX_JOYSTICKS 32 +/* !!! FIXME: move this somewhere else. */ +#if !SDL_EVENTS_DISABLED +#include "../../events/SDL_events_c.h" +#endif + +/* + * !!! FIXME: move all the udev stuff to src/core/linux, so I can reuse it + * !!! FIXME: for audio hardware disconnects. + */ +#ifdef HAVE_LIBUDEV_H +#define SDL_USE_LIBUDEV 1 +#include "SDL_loadso.h" +#include +#include +#include +#include + +/* we never link directly to libudev. */ +/* !!! FIXME: can we generalize this? ALSA, etc, do the same things. */ +static const char *udev_library = "libudev.so"; +static void *udev_handle = NULL; + +/* !!! FIXME: this is kinda ugly. */ +static SDL_bool +load_udev_sym(const char *fn, void **addr) +{ + *addr = SDL_LoadFunction(udev_handle, fn); + if (*addr == NULL) { + /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ + return SDL_FALSE; + } + + return SDL_TRUE; +} + +/* libudev entry points... */ +static const char *(*UDEV_udev_device_get_action)(struct udev_device *) = NULL; +static const char *(*UDEV_udev_device_get_devnode)(struct udev_device *) = NULL; +static const char *(*UDEV_udev_device_get_property_value)(struct udev_device *, const char *) = NULL; +static struct udev_device *(*UDEV_udev_device_new_from_syspath)(struct udev *, const char *) = NULL; +static void (*UDEV_udev_device_unref)(struct udev_device *) = NULL; +static int (*UDEV_udev_enumerate_add_match_property)(struct udev_enumerate *, const char *, const char *) = NULL; +static int (*UDEV_udev_enumerate_add_match_subsystem)(struct udev_enumerate *, const char *) = NULL; +static struct udev_list_entry *(*UDEV_udev_enumerate_get_list_entry)(struct udev_enumerate *) = NULL; +static struct udev_enumerate *(*UDEV_udev_enumerate_new)(struct udev *) = NULL; +static int (*UDEV_udev_enumerate_scan_devices)(struct udev_enumerate *) = NULL; +static void (*UDEV_udev_enumerate_unref)(struct udev_enumerate *) = NULL; +static const char *(*UDEV_udev_list_entry_get_name)(struct udev_list_entry *) = NULL; +static struct udev_list_entry *(*UDEV_udev_list_entry_get_next)(struct udev_list_entry *) = NULL; +static int (*UDEV_udev_monitor_enable_receiving)(struct udev_monitor *) = NULL; +static int (*UDEV_udev_monitor_filter_add_match_subsystem_devtype)(struct udev_monitor *, const char *, const char *) = NULL; +static int (*UDEV_udev_monitor_get_fd)(struct udev_monitor *) = NULL; +static struct udev_monitor *(*UDEV_udev_monitor_new_from_netlink)(struct udev *, const char *) = NULL; +static struct udev_device *(*UDEV_udev_monitor_receive_device)(struct udev_monitor *) = NULL; +static void (*UDEV_udev_monitor_unref)(struct udev_monitor *) = NULL; +static struct udev *(*UDEV_udev_new)(void) = NULL; +static void (*UDEV_udev_unref)(struct udev *) = NULL; + +static int +load_udev_syms(void) +{ + /* cast funcs to char* first, to please GCC's strict aliasing rules. */ + #define SDL_UDEV_SYM(x) \ + if (!load_udev_sym(#x, (void **) (char *) &UDEV_##x)) return -1 + + SDL_UDEV_SYM(udev_device_get_action); + SDL_UDEV_SYM(udev_device_get_devnode); + SDL_UDEV_SYM(udev_device_get_property_value); + SDL_UDEV_SYM(udev_device_new_from_syspath); + SDL_UDEV_SYM(udev_device_unref); + SDL_UDEV_SYM(udev_enumerate_add_match_property); + SDL_UDEV_SYM(udev_enumerate_add_match_subsystem); + SDL_UDEV_SYM(udev_enumerate_get_list_entry); + SDL_UDEV_SYM(udev_enumerate_new); + SDL_UDEV_SYM(udev_enumerate_scan_devices); + SDL_UDEV_SYM(udev_enumerate_unref); + SDL_UDEV_SYM(udev_list_entry_get_name); + SDL_UDEV_SYM(udev_list_entry_get_next); + SDL_UDEV_SYM(udev_monitor_enable_receiving); + SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype); + SDL_UDEV_SYM(udev_monitor_get_fd); + SDL_UDEV_SYM(udev_monitor_new_from_netlink); + SDL_UDEV_SYM(udev_monitor_receive_device); + SDL_UDEV_SYM(udev_monitor_unref); + SDL_UDEV_SYM(udev_new); + SDL_UDEV_SYM(udev_unref); + + #undef SDL_UDEV_SYM + + return 0; +} + +static void +UnloadUDEVLibrary(void) +{ + if (udev_handle != NULL) { + SDL_UnloadObject(udev_handle); + udev_handle = NULL; + } +} + +static int +LoadUDEVLibrary(void) +{ + int retval = 0; + if (udev_handle == NULL) { + udev_handle = SDL_LoadObject(udev_library); + if (udev_handle == NULL) { + retval = -1; + /* Don't call SDL_SetError(): SDL_LoadObject already did. */ + } else { + retval = load_udev_syms(); + if (retval < 0) { + UnloadUDEVLibrary(); + } + } + } + + return retval; +} + +static struct udev *udev = NULL; +static struct udev_monitor *udev_mon = NULL; +#endif -/* A list of available joysticks */ -static struct + +/* A linked list of available joysticks */ +typedef struct SDL_joylist_item { - char *fname; -} SDL_joylist[MAX_JOYSTICKS]; + int device_instance; + char *path; /* "/dev/input/event2" or whatever */ + char *name; /* "SideWinder 3D Pro" or whatever */ + JoystickGUID guid; + dev_t devnum; + struct joystick_hwdata *hwdata; + struct SDL_joylist_item *next; +} SDL_joylist_item; +static SDL_joylist_item *SDL_joylist = NULL; +static SDL_joylist_item *SDL_joylist_tail = NULL; +static int numjoysticks = 0; +static int instance_counter = 0; #define test_bit(nr, addr) \ - (((1UL << ((nr) % (sizeof(long) * 8))) & ((addr)[(nr) / (sizeof(long) * 8)])) != 0) + (((1UL << ((nr) % (sizeof(long) * 8))) & ((addr)[(nr) / (sizeof(long) * 8)])) != 0) #define NBITS(x) ((((x)-1)/(sizeof(long) * 8))+1) static int -IsJoystick(int fd) +IsJoystick(int fd, char *namebuf, const size_t namebuflen, JoystickGUID *guid) { unsigned long evbit[NBITS(EV_MAX)] = { 0 }; unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; + struct input_id inpid; + Uint16 *guid16 = (Uint16 *) ((char *) &guid->data); if ((ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0) || (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) || @@ -71,30 +208,206 @@ IsJoystick(int fd) test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) { return 0; } - return (1); + + if (ioctl(fd, EVIOCGNAME(namebuflen), namebuf) < 0) { + return 0; + } + + if (ioctl(fd, EVIOCGID, &inpid) < 0) { + return 0; + } + + /* We only need 16 bits for each of these; space them out to fill 128. */ + /* Byteswap so devices get same GUID on little/big endian platforms. */ + *(guid16++) = SDL_SwapLE16(inpid.bustype); + *(guid16++) = 0; + *(guid16++) = SDL_SwapLE16(inpid.vendor); + *(guid16++) = 0; + *(guid16++) = SDL_SwapLE16(inpid.product); + *(guid16++) = 0; + *(guid16++) = SDL_SwapLE16(inpid.version); + *(guid16++) = 0; + + return 1; } -static int SDL_SYS_numjoysticks = 0; +/* !!! FIXME: I would love to dump this code and use libudev instead. */ +static int +MaybeAddDevice(const char *path) +{ + struct stat sb; + int fd = -1; + int isstick = 0; + char namebuf[128]; + JoystickGUID guid; + SDL_joylist_item *item; -/* Function to scan the system for joysticks */ -int -SDL_SYS_JoystickInit(void) + if (path == NULL) { + return -1; + } + + if (stat(path, &sb) == -1) { + return -1; + } + + /* Check to make sure it's not already in list. */ + for (item = SDL_joylist; item != NULL; item = item->next) { + if (sb.st_rdev == item->devnum) { + return -1; /* already have this one */ + } + } + + fd = open(path, O_RDONLY, 0); + if (fd < 0) { + return -1; + } + +#ifdef DEBUG_INPUT_EVENTS + printf("Checking %s\n", path); +#endif + + isstick = IsJoystick(fd, namebuf, sizeof (namebuf), &guid); + close(fd); + if (!isstick) { + return -1; + } + + item = (SDL_joylist_item *) SDL_malloc(sizeof (SDL_joylist_item)); + if (item == NULL) { + return -1; + } + + SDL_zerop(item); + item->devnum = sb.st_rdev; + item->path = SDL_strdup(path); + item->name = SDL_strdup(namebuf); + item->guid = guid; + + if ( (item->path == NULL) || (item->name == NULL) ) { + SDL_free(item->path); + SDL_free(item->name); + SDL_free(item); + return -1; + } + + item->device_instance = instance_counter++; + if (SDL_joylist_tail == NULL) { + SDL_joylist = SDL_joylist_tail = item; + } else { + SDL_joylist_tail->next = item; + SDL_joylist_tail = item; + } + + return numjoysticks++; +} + +/* !!! FIXME: I would love to dump this code and use libudev instead. */ +static int +MaybeRemoveDevice(const char *path) { - /* The base path of the joystick devices */ - const char *joydev_pattern[] = { - "/dev/input/event%d", - }; - int numjoysticks; - int i, j; - int fd; + SDL_joylist_item *item; + SDL_joylist_item *prev = NULL; + + if (path == NULL) { + return -1; + } + + for (item = SDL_joylist; item != NULL; item = item->next) { + /* found it, remove it. */ + if (SDL_strcmp(path, item->path) == 0) { + const int retval = item->device_instance; + if (item->hwdata) { + item->hwdata->removed = SDL_TRUE; + } + if (prev != NULL) { + prev->next = item->next; + if (item == SDL_joylist_tail) { + SDL_joylist_tail = prev; + } + } else { + SDL_assert(!SDL_joylist); + SDL_assert(!SDL_joylist_tail); + SDL_joylist = SDL_joylist_tail = NULL; + } + SDL_free(item->path); + SDL_free(item->name); + SDL_free(item); + numjoysticks--; + return retval; + } + prev = item; + } + + return -1; +} + +static int +JoystickInitWithoutUdev(void) +{ + int i; char path[PATH_MAX]; - dev_t dev_nums[MAX_JOYSTICKS]; /* major/minor device numbers */ - struct stat sb; - int n, duplicate; - numjoysticks = 0; + /* !!! FIXME: only finds sticks if they're called /dev/input/event[0..31] */ + /* !!! FIXME: we could at least readdir() through /dev/input...? */ + /* !!! FIXME: (or delete this and rely on libudev?) */ + for (i = 0; i < 32; i++) { + SDL_snprintf(path, SDL_arraysize(path), "/dev/input/event%d", i); + MaybeAddDevice(path); + } + return numjoysticks; +} + + +#if SDL_USE_LIBUDEV +static int +JoystickInitWithUdev(void) +{ + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *devs = NULL; + struct udev_list_entry *item = NULL; + + SDL_assert(udev == NULL); + udev = UDEV_udev_new(); + if (udev == NULL) { + SDL_SetError("udev_new() failed"); + return -1; + } + + udev_mon = UDEV_udev_monitor_new_from_netlink(udev, "udev"); + if (udev_mon != NULL) { /* okay if it's NULL, we just lose hotplugging. */ + UDEV_udev_monitor_filter_add_match_subsystem_devtype(udev_mon, + "input", NULL); + UDEV_udev_monitor_enable_receiving(udev_mon); + } + + enumerate = UDEV_udev_enumerate_new(udev); + if (enumerate == NULL) { + SDL_SetError("udev_enumerate_new() failed"); + return -1; + } + + UDEV_udev_enumerate_add_match_subsystem(enumerate, "input"); + UDEV_udev_enumerate_add_match_property(enumerate, "ID_INPUT_JOYSTICK", "1"); + UDEV_udev_enumerate_scan_devices(enumerate); + devs = UDEV_udev_enumerate_get_list_entry(enumerate); + for (item = devs; item; item = UDEV_udev_list_entry_get_next(item)) { + const char *path = UDEV_udev_list_entry_get_name(item); + struct udev_device *dev = UDEV_udev_device_new_from_syspath(udev, path); + MaybeAddDevice(UDEV_udev_device_get_devnode(dev)); + UDEV_udev_device_unref(dev); + } + + UDEV_udev_enumerate_unref(enumerate); + + return numjoysticks; +} +#endif + +int +SDL_SYS_JoystickInit(void) +{ /* First see if the user specified one or more joysticks to use */ if (SDL_getenv("SDL_JOYSTICK_DEVICE") != NULL) { char *envcopy, *envpath, *delim; @@ -105,121 +418,150 @@ SDL_SYS_JoystickInit(void) if (delim != NULL) { *delim++ = '\0'; } - if (stat(envpath, &sb) == 0) { - fd = open(envpath, O_RDONLY, 0); - if (fd >= 0) { - /* Assume the user knows what they're doing. */ - SDL_joylist[numjoysticks].fname = SDL_strdup(envpath); - if (SDL_joylist[numjoysticks].fname) { - dev_nums[numjoysticks] = sb.st_rdev; - ++numjoysticks; - } - close(fd); - } - } + MaybeAddDevice(envpath); envpath = delim; } SDL_free(envcopy); } - for (i = 0; i < SDL_arraysize(joydev_pattern); ++i) { - for (j = 0; j < MAX_JOYSTICKS; ++j) { - SDL_snprintf(path, SDL_arraysize(path), joydev_pattern[i], j); - - /* rcg06302000 replaced access(F_OK) call with stat(). - * stat() will fail if the file doesn't exist, so it's - * equivalent behaviour. - */ - if (stat(path, &sb) == 0) { - /* Check to make sure it's not already in list. - * This happens when we see a stick via symlink. - */ - duplicate = 0; - for (n = 0; (n < numjoysticks) && !duplicate; ++n) { - if (sb.st_rdev == dev_nums[n]) { - duplicate = 1; - } - } - if (duplicate) { - continue; - } - - fd = open(path, O_RDONLY, 0); - if (fd < 0) { - continue; - } -#ifdef DEBUG_INPUT_EVENTS - printf("Checking %s\n", path); -#endif - if ((i == 0) && !IsJoystick(fd)) { - close(fd); - continue; - } - close(fd); - - /* We're fine, add this joystick */ - SDL_joylist[numjoysticks].fname = SDL_strdup(path); - if (SDL_joylist[numjoysticks].fname) { - dev_nums[numjoysticks] = sb.st_rdev; - ++numjoysticks; - } - } - } - - /* This is a special case... - If the event devices are valid then the joystick devices - will be duplicates but without extra information about their - hats or balls. Unfortunately, the event devices can't - currently be calibrated, so it's a win-lose situation. - So : /dev/input/eventX = /dev/input/jsY = /dev/js - */ - if ((i == 0) && (numjoysticks > 0)) - break; +#if SDL_USE_LIBUDEV + if (LoadUDEVLibrary() == 0) { /* okay if this fails, FOR NOW. */ + return JoystickInitWithUdev(); } +#endif - SDL_SYS_numjoysticks = numjoysticks; - return (numjoysticks); + return JoystickInitWithoutUdev(); } int SDL_SYS_NumJoysticks() { - return SDL_SYS_numjoysticks; + return numjoysticks; +} + +static SDL_bool +HotplugUpdateAvailable(void) +{ +#if SDL_USE_LIBUDEV + if (udev_mon != NULL) { + const int fd = UDEV_udev_monitor_get_fd(udev_mon); + fd_set fds; + struct timeval tv; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec = 0; + tv.tv_usec = 0; + if ((select(fd+1, &fds, NULL, NULL, &tv) > 0) && (FD_ISSET(fd, &fds))) { + return SDL_TRUE; + } + } +#endif + + return SDL_FALSE; } void SDL_SYS_JoystickDetect() { +#if SDL_USE_LIBUDEV + struct udev_device *dev = NULL; + const char *devnode = NULL; + const char *action = NULL; + const char *val = NULL; + + while (HotplugUpdateAvailable()) { + dev = UDEV_udev_monitor_receive_device(udev_mon); + if (dev == NULL) { + break; + } + val = UDEV_udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"); + if ((!val) || (SDL_strcmp(val, "1") != 0)) { + continue; + } + + action = UDEV_udev_device_get_action(dev); + devnode = UDEV_udev_device_get_devnode(dev); + + if (SDL_strcmp(action, "add") == 0) { + const int device_index = MaybeAddDevice(devnode); + if (device_index != -1) { + /* !!! FIXME: Move this to an SDL_PrivateJoyDeviceAdded() function? */ + #if !SDL_EVENTS_DISABLED + SDL_Event event; + event.type = SDL_JOYDEVICEADDED; + + if (SDL_GetEventState(event.type) == SDL_ENABLE) { + event.jdevice.which = device_index; + if ( (SDL_EventOK == NULL) || + (*SDL_EventOK) (SDL_EventOKParam, &event) ) { + SDL_PushEvent(&event); + } + } + #endif // !SDL_EVENTS_DISABLED + } + } else if (SDL_strcmp(action, "remove") == 0) { + const int inst = MaybeRemoveDevice(devnode); + if (inst != -1) { + /* !!! FIXME: Move this to an SDL_PrivateJoyDeviceRemoved() function? */ + #if !SDL_EVENTS_DISABLED + SDL_Event event; + event.type = SDL_JOYDEVICEREMOVED; + + if (SDL_GetEventState(event.type) == SDL_ENABLE) { + event.jdevice.which = inst; + if ( (SDL_EventOK == NULL) || + (*SDL_EventOK) (SDL_EventOKParam, &event) ) { + SDL_PushEvent(&event); + } + } + #endif // !SDL_EVENTS_DISABLED + } + } + UDEV_udev_device_unref(dev); + } +#endif } SDL_bool SDL_SYS_JoystickNeedsPolling() { - return SDL_FALSE; + /* + * This results in a select() call, so technically we're polling to + * decide if we should poll, but I think this function is here because + * Windows has to do an enormous amount of work to detect new sticks, + * whereas libudev just needs to see if there's more data available on + * a socket...so this should be acceptable, I hope. + */ + return HotplugUpdateAvailable(); +} + +static SDL_joylist_item * +JoystickByDevIndex(int device_index) +{ + SDL_joylist_item *item = SDL_joylist; + + if ((device_index < 0) || (device_index >= numjoysticks)) { + return NULL; + } + + while (device_index > 0) { + SDL_assert(item != NULL); + device_index--; + item = item->next; + } + + return item; } /* Function to get the device-dependent name of a joystick */ const char * SDL_SYS_JoystickNameForDeviceIndex(int device_index) { - int fd; - static char namebuf[128]; - const char *name; - - name = NULL; - fd = open(SDL_joylist[device_index].fname, O_RDONLY, 0); - if (fd >= 0) { - if (ioctl(fd, EVIOCGNAME(sizeof(namebuf)), namebuf) <= 0) { - name = SDL_joylist[device_index].fname; - } else { - name = namebuf; - } - close(fd); - } - return name; + return JoystickByDevIndex(device_index)->name; } /* Function to perform the mapping from device index to the instance id for this index */ SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) { - return device_index; + return JoystickByDevIndex(device_index)->device_instance; } static int @@ -362,28 +704,46 @@ ConfigJoystick(SDL_Joystick * joystick, int fd) int SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index) { - int fd; - char *fname; + SDL_joylist_item *item = JoystickByDevIndex(device_index); + char *fname = NULL; + int fd = -1; - /* Open the joystick and set the joystick file descriptor */ - fd = open(SDL_joylist[joystick->instance_id].fname, O_RDONLY, 0); - fname = SDL_joylist[joystick->instance_id].fname; + if (item == NULL) { + SDL_SetError("No such device"); + return -1; + } + fname = item->path; + fd = open(fname, O_RDONLY, 0); if (fd < 0) { - SDL_SetError("Unable to open %s\n", SDL_joylist[joystick->instance_id]); - return (-1); + SDL_SetError("Unable to open %s", fname); + return -1; } + joystick->instance_id = device_index; joystick->hwdata = (struct joystick_hwdata *) SDL_malloc(sizeof(*joystick->hwdata)); if (joystick->hwdata == NULL) { - SDL_OutOfMemory(); close(fd); + SDL_OutOfMemory(); return (-1); } SDL_memset(joystick->hwdata, 0, sizeof(*joystick->hwdata)); + joystick->hwdata->removed = SDL_FALSE; + joystick->hwdata->device_instance = item->device_instance; + joystick->hwdata->guid = item->guid; joystick->hwdata->fd = fd; - joystick->hwdata->fname = fname; + joystick->hwdata->fname = SDL_strdup(item->path); + if (joystick->hwdata->fname == NULL) { + SDL_free(joystick->hwdata); + joystick->hwdata = NULL; + close(fd); + SDL_OutOfMemory(); + return (-1); + } + + SDL_assert(item->hwdata == NULL); + item->hwdata = joystick->hwdata; /* Set the joystick to non-blocking read mode */ fcntl(fd, F_SETFL, O_NONBLOCK); @@ -397,7 +757,7 @@ SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index) /* Function to determine is this joystick is attached to the system right now */ SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick) { - return SDL_TRUE; + return !joystick->closed && !joystick->hwdata->removed; } static __inline__ void @@ -552,12 +912,9 @@ SDL_SYS_JoystickClose(SDL_Joystick * joystick) { if (joystick->hwdata) { close(joystick->hwdata->fd); - if (joystick->hwdata->hats) { - SDL_free(joystick->hwdata->hats); - } - if (joystick->hwdata->balls) { - SDL_free(joystick->hwdata->balls); - } + SDL_free(joystick->hwdata->hats); + SDL_free(joystick->hwdata->balls); + SDL_free(joystick->hwdata->fname); SDL_free(joystick->hwdata); joystick->hwdata = NULL; } @@ -568,34 +925,42 @@ SDL_SYS_JoystickClose(SDL_Joystick * joystick) void SDL_SYS_JoystickQuit(void) { - int i; + SDL_joylist_item *item = NULL; + SDL_joylist_item *next = NULL; + + for (item = SDL_joylist; item; item = next) { + next = item->next; + SDL_free(item->path); + SDL_free(item->name); + SDL_free(item); + } - for (i = 0; SDL_joylist[i].fname; ++i) { - if (SDL_joylist[i].fname) { - SDL_free(SDL_joylist[i].fname); - SDL_joylist[i].fname = NULL; - } + SDL_joylist = SDL_joylist_tail = NULL; + + numjoysticks = 0; + instance_counter = 0; + +#if SDL_USE_LIBUDEV + if (udev_mon != NULL) { + UDEV_udev_monitor_unref(udev_mon); + udev_mon = NULL; + } + if (udev != NULL) { + UDEV_udev_unref(udev); + udev = NULL; } + UnloadUDEVLibrary(); +#endif } JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index ) { - JoystickGUID guid; - // the GUID is just the first 16 chars of the name for now - const char *name = SDL_SYS_JoystickNameForDeviceIndex( device_index ); - SDL_zero( guid ); - SDL_memcpy( &guid, name, SDL_min( sizeof(guid), SDL_strlen( name ) ) ); - return guid; + return JoystickByDevIndex(device_index)->guid; } JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick) { - JoystickGUID guid; - // the GUID is just the first 16 chars of the name for now - const char *name = joystick->name; - SDL_zero( guid ); - SDL_memcpy( &guid, name, SDL_min( sizeof(guid), SDL_strlen( name ) ) ); - return guid; + return joystick->hwdata->guid; } #endif /* SDL_JOYSTICK_LINUX */ diff --git a/src/joystick/linux/SDL_sysjoystick_c.h b/src/joystick/linux/SDL_sysjoystick_c.h index a6c382f0e..88bc2f344 100644 --- a/src/joystick/linux/SDL_sysjoystick_c.h +++ b/src/joystick/linux/SDL_sysjoystick_c.h @@ -19,14 +19,16 @@ 3. This notice may not be removed or altered from any source distribution. */ -#if SDL_INPUT_LINUXEV #include -#endif /* The private structure used to keep track of a joystick */ struct joystick_hwdata { int fd; + int device_instance; + SDL_bool removed; + + JoystickGUID guid; char *fname; /* Used in haptic subsystem */ /* The current linux joystick driver maps hats to two axes */ @@ -41,8 +43,6 @@ struct joystick_hwdata } *balls; /* Support for the Linux 2.4 unified input interface */ -#if SDL_INPUT_LINUXEV - SDL_bool is_hid; Uint8 key_map[KEY_MAX - BTN_MISC]; Uint8 abs_map[ABS_MAX]; struct axis_correct @@ -50,5 +50,4 @@ struct joystick_hwdata int used; int coef[3]; } abs_correct[ABS_MAX]; -#endif };