From 7a40db0973ce67a19e1bbaad9236eb80de276cb4 Mon Sep 17 00:00:00 2001 From: meyraud705 Date: Thu, 11 May 2023 19:51:07 +0200 Subject: [PATCH 1/3] Add support for sensor to joystick on Linux (evdev) --- src/joystick/linux/SDL_sysjoystick.c | 448 +++++++++++++++++++++++-- src/joystick/linux/SDL_sysjoystick_c.h | 19 ++ 2 files changed, 430 insertions(+), 37 deletions(-) diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index 2e52ceccc8036..3ec1fa77fefcf 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -115,10 +115,20 @@ typedef struct SDL_joylist_item SDL_GamepadMapping *mapping; } SDL_joylist_item; +/* A linked list of available gamepad sensors */ +typedef struct SDL_sensorlist_item +{ + char *path; /* "/dev/input/event2" or whatever */ + dev_t devnum; + struct joystick_hwdata *hwdata; + struct SDL_sensorlist_item *next; +} SDL_sensorlist_item; + static SDL_bool SDL_classic_joysticks = SDL_FALSE; static SDL_joylist_item *SDL_joylist = NULL; static SDL_joylist_item *SDL_joylist_tail = NULL; static int numjoysticks = 0; +static SDL_sensorlist_item *SDL_sensorlist = NULL; static int inotify_fd = -1; static Uint64 last_joy_detect_time; @@ -182,6 +192,30 @@ static int GuessIsJoystick(int fd) return 0; } +static int GuessIsSensor(int fd) +{ + unsigned long evbit[NBITS(EV_MAX)] = { 0 }; + unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; + unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; + unsigned long relbit[NBITS(REL_MAX)] = { 0 }; + int devclass; + + if ((ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0) || + (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) || + (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) < 0) || + (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0)) { + return 0; + } + + devclass = SDL_EVDEV_GuessDeviceClass(evbit, absbit, keybit, relbit); + + if (devclass & SDL_UDEV_DEVICE_ACCELEROMETER) { + return 1; + } + + return 0; +} + static int IsJoystick(const char *path, int fd, char **name_return, SDL_JoystickGUID *guid) { struct input_id inpid; @@ -238,6 +272,11 @@ static int IsJoystick(const char *path, int fd, char **name_return, SDL_Joystick return 1; } +static int IsSensor(const char *path, int fd) +{ + return GuessIsSensor(fd); +} + #ifdef SDL_USE_LIBUDEV static void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath) { @@ -284,14 +323,20 @@ static void FreeJoylistItem(SDL_joylist_item *item) SDL_free(item); } +static void FreeSensorlistItem(SDL_sensorlist_item *item) +{ + SDL_free(item->path); + SDL_free(item); +} + static int MaybeAddDevice(const char *path) { struct stat sb; int fd = -1; - int isstick = 0; char *name = NULL; SDL_JoystickGUID guid; SDL_joylist_item *item; + SDL_sensorlist_item *item_sensor; if (path == NULL) { return -1; @@ -307,6 +352,11 @@ static int MaybeAddDevice(const char *path) return -1; /* already have this one */ } } + for (item_sensor = SDL_sensorlist; item_sensor != NULL; item_sensor = item_sensor->next) { + if (sb.st_rdev == item_sensor->devnum) { + return -1; /* already have this one */ + } + } fd = open(path, O_RDONLY | O_CLOEXEC, 0); if (fd < 0) { @@ -317,42 +367,66 @@ static int MaybeAddDevice(const char *path) SDL_Log("Checking %s\n", path); #endif - isstick = IsJoystick(path, fd, &name, &guid); - close(fd); - if (!isstick) { - return -1; - } + if (IsJoystick(path, fd, &name, &guid)) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("found joystick: %s\n", path); +#endif + close(fd); + item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item)); + if (item == NULL) { + SDL_free(name); + return -1; + } - item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item)); - if (item == NULL) { - SDL_free(name); - return -1; - } + item->devnum = sb.st_rdev; + item->path = SDL_strdup(path); + item->name = name; + item->guid = guid; - item->devnum = sb.st_rdev; - item->path = SDL_strdup(path); - item->name = name; - item->guid = guid; + if ((item->path == NULL) || (item->name == NULL)) { + FreeJoylistItem(item); + return -1; + } - if ((item->path == NULL) || (item->name == NULL)) { - FreeJoylistItem(item); - return -1; - } + item->device_instance = SDL_GetNextJoystickInstanceID(); + if (SDL_joylist_tail == NULL) { + SDL_joylist = SDL_joylist_tail = item; + } else { + SDL_joylist_tail->next = item; + SDL_joylist_tail = item; + } - item->device_instance = SDL_GetNextJoystickInstanceID(); - if (SDL_joylist_tail == NULL) { - SDL_joylist = SDL_joylist_tail = item; - } else { - SDL_joylist_tail->next = item; - SDL_joylist_tail = item; + /* Need to increment the joystick count before we post the event */ + ++numjoysticks; + + SDL_PrivateJoystickAdded(item->device_instance); + return numjoysticks; } - /* Need to increment the joystick count before we post the event */ - ++numjoysticks; + if (IsSensor(path, fd)) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("found sensor: %s\n", path); +#endif + close(fd); + item_sensor = (SDL_sensorlist_item *)SDL_calloc(1, sizeof(SDL_sensorlist_item)); + if (item_sensor == NULL) { + return -1; + } + item_sensor->devnum = sb.st_rdev; + item_sensor->path = SDL_strdup(path); - SDL_PrivateJoystickAdded(item->device_instance); + if (item_sensor->path == NULL) { + FreeSensorlistItem(item_sensor); + return -1; + } - return numjoysticks; + item_sensor->next = SDL_sensorlist; + SDL_sensorlist = item_sensor; + return -1; + } + + close(fd); + return -1; } static void RemoveJoylistItem(SDL_joylist_item *item, SDL_joylist_item *prev) @@ -379,10 +453,30 @@ static void RemoveJoylistItem(SDL_joylist_item *item, SDL_joylist_item *prev) FreeJoylistItem(item); } +static void RemoveSensorlistItem(SDL_sensorlist_item *item, SDL_sensorlist_item *prev) +{ + if (item->hwdata) { + item->hwdata->item_sensor = NULL; + } + + if (prev != NULL) { + prev->next = item->next; + } else { + SDL_assert(SDL_sensorlist == item); + SDL_sensorlist = item->next; + } + + /* Do not call SDL_PrivateJoystickRemoved here as RemoveJoylistItem will do it, + * assuming both sensor and joy item are removed at the same time */ + FreeSensorlistItem(item); +} + static int MaybeRemoveDevice(const char *path) { SDL_joylist_item *item; SDL_joylist_item *prev = NULL; + SDL_sensorlist_item *item_sensor; + SDL_sensorlist_item *prev_sensor = NULL; if (path == NULL) { return -1; @@ -397,6 +491,14 @@ static int MaybeRemoveDevice(const char *path) } prev = item; } + for (item_sensor = SDL_sensorlist; item_sensor != NULL; item_sensor = item_sensor->next) { + /* found it, remove it. */ + if (SDL_strcmp(path, item_sensor->path) == 0) { + RemoveSensorlistItem(item_sensor, prev_sensor); + return -1; + } + prev_sensor = item_sensor; + } return -1; } @@ -405,6 +507,8 @@ static void HandlePendingRemovals(void) { SDL_joylist_item *prev = NULL; SDL_joylist_item *item = SDL_joylist; + SDL_sensorlist_item *prev_sensor = NULL; + SDL_sensorlist_item *item_sensor = SDL_sensorlist; while (item != NULL) { if (item->hwdata && item->hwdata->gone) { @@ -420,6 +524,21 @@ static void HandlePendingRemovals(void) item = item->next; } } + + while (item_sensor != NULL) { + if (item_sensor->hwdata && item_sensor->hwdata->sensor_gone) { + RemoveSensorlistItem(item_sensor, prev_sensor); + + if (prev_sensor != NULL) { + item_sensor = prev_sensor->next; + } else { + item_sensor = SDL_sensorlist; + } + } else { + prev_sensor = item_sensor; + item_sensor = item_sensor->next; + } + } } static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickGUID guid, SDL_JoystickID *device_instance) @@ -896,7 +1015,7 @@ static SDL_bool GuessIfAxesAreDigitalHat(struct input_absinfo *absinfo_x, struct return SDL_FALSE; } -static void ConfigJoystick(SDL_Joystick *joystick, int fd) +static void ConfigJoystick(SDL_Joystick *joystick, int fd, int fd_sensor) { int i, t; unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; @@ -1084,6 +1203,43 @@ static void ConfigJoystick(SDL_Joystick *joystick, int fd) } } + /* Sensors are only available through the new unified event API */ + if (fd_sensor >= 0 && (ioctl(fd_sensor, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) >= 0)) { + if (test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) && test_bit(ABS_Z, absbit)) { + for (i = 0; i < 3; ++i) { + struct input_absinfo absinfo; + if (ioctl(fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) < 0) { + break; /* do not report an accelerometer if we can't read all axes */ + } + joystick->hwdata->accelerometer_scale[i] = absinfo.resolution; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has accelerometer axis: 0x%.2x\n", ABS_X + i); + SDL_Log("Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }\n", + absinfo.value, absinfo.minimum, absinfo.maximum, + absinfo.fuzz, absinfo.flat, absinfo.resolution); +#endif /* DEBUG_INPUT_EVENTS */ + } + joystick->hwdata->has_accelerometer = SDL_TRUE; + } + + if (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit) && test_bit(ABS_RZ, absbit)) { + for (i = 0; i < 3; ++i) { + struct input_absinfo absinfo; + if (ioctl(fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) < 0) { + break; /* do not report an gyro if we can't read all axes */ + } + joystick->hwdata->gyro_scale[i] = absinfo.resolution; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has gyro axis: 0x%.2x\n", ABS_RX + i); + SDL_Log("Values = { val:%d, min:%d, max:%d, fuzz:%d, flat:%d, res:%d }\n", + absinfo.value, absinfo.minimum, absinfo.maximum, + absinfo.fuzz, absinfo.flat, absinfo.resolution); +#endif /* DEBUG_INPUT_EVENTS */ + } + joystick->hwdata->has_gyro = SDL_TRUE; + } + } + /* Allocate data to keep track of these thingamajigs */ if (joystick->nhats > 0) { if (allocate_hatdata(joystick) < 0) { @@ -1106,11 +1262,12 @@ static void ConfigJoystick(SDL_Joystick *joystick, int fd) without adding an opened SDL_Joystick object to the system. This expects `joystick->hwdata` to be allocated and will not free it on error. Returns -1 on error, 0 on success. */ -static int PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item) +static int PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item, SDL_sensorlist_item *item_sensor) { SDL_AssertJoysticksLocked(); joystick->hwdata->item = item; + joystick->hwdata->item_sensor = item_sensor; joystick->hwdata->guid = item->guid; joystick->hwdata->effect.id = -1; joystick->hwdata->m_bSteamController = item->m_bSteamController; @@ -1119,12 +1276,14 @@ static int PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item) if (item->m_bSteamController) { joystick->hwdata->fd = -1; + joystick->hwdata->fd_sensor = -1; SDL_GetSteamControllerInputs(&joystick->nbuttons, &joystick->naxes, &joystick->nhats); } else { + int fd = -1, fd_sensor = -1; /* Try read-write first, so we can do rumble */ - int fd = open(item->path, O_RDWR | O_CLOEXEC, 0); + fd = open(item->path, O_RDWR | O_CLOEXEC, 0); if (fd < 0) { /* Try read-only again, at least we'll get events in this case */ fd = open(item->path, O_RDONLY | O_CLOEXEC, 0); @@ -1132,23 +1291,80 @@ static int PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item) if (fd < 0) { return SDL_SetError("Unable to open %s", item->path); } + /* If openning sensor fail, continue with buttons and axes only */ + if (item_sensor != NULL) { + fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0); + } joystick->hwdata->fd = fd; + joystick->hwdata->fd_sensor = fd_sensor; joystick->hwdata->fname = SDL_strdup(item->path); if (joystick->hwdata->fname == NULL) { close(fd); + if (fd_sensor >= 0) { + close(fd_sensor); + } return SDL_OutOfMemory(); } /* Set the joystick to non-blocking read mode */ fcntl(fd, F_SETFL, O_NONBLOCK); + if (fd_sensor >= 0) { + fcntl(fd_sensor, F_SETFL, O_NONBLOCK); + } /* Get the number of buttons and axes on the joystick */ - ConfigJoystick(joystick, fd); + ConfigJoystick(joystick, fd, fd_sensor); } return 0; } +static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) +{ + SDL_sensorlist_item *item_sensor; + char uniq_item[128]; + int fd_item = -1; + + if (item == NULL || SDL_sensorlist == NULL) { + return NULL; + } + + SDL_memset(uniq_item, 0, sizeof(uniq_item)); + fd_item = open(item->path, O_RDONLY | O_CLOEXEC, 0); + if (ioctl(fd_item, EVIOCGUNIQ(sizeof(uniq_item) - 1), &uniq_item) < 0) { + return NULL; + } + close(fd_item); +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick UNIQ: %s\n", uniq_item); +#endif /* DEBUG_INPUT_EVENTS */ + + for (item_sensor = SDL_sensorlist; item_sensor != NULL; item_sensor = item_sensor->next) { + char uniq_sensor[128]; + int fd_sensor = -1; + if (item_sensor->hwdata != NULL) { + /* already associated with another joystick */ + continue; + } + + SDL_memset(uniq_sensor, 0, sizeof(uniq_sensor)); + fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0); + if (ioctl(fd_sensor, EVIOCGUNIQ(sizeof(uniq_sensor) - 1), &uniq_sensor) < 0) { + close(fd_sensor); + continue; + } + close(fd_sensor); +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Sensor UNIQ: %s\n", uniq_sensor); +#endif /* DEBUG_INPUT_EVENTS */ + + if (SDL_strcmp(uniq_item, uniq_sensor) == 0) { + return item_sensor; + } + } + return NULL; +} + /* Function to open a joystick for use. The joystick to open is specified by the device index. This should fill the nbuttons and naxes fields of the joystick structure. @@ -1157,6 +1373,7 @@ static int PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item) static int LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) { SDL_joylist_item *item = GetJoystickByDevIndex(device_index); + SDL_sensorlist_item *item_sensor = GetSensor(item); SDL_AssertJoysticksLocked(); @@ -1171,18 +1388,29 @@ static int LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) return SDL_OutOfMemory(); } - if (PrepareJoystickHwdata(joystick, item) == -1) { + if (PrepareJoystickHwdata(joystick, item, item_sensor) == -1) { SDL_free(joystick->hwdata); joystick->hwdata = NULL; return -1; /* SDL_SetError will already have been called */ } SDL_assert(item->hwdata == NULL); + SDL_assert(!item_sensor || item_sensor->hwdata == NULL); item->hwdata = joystick->hwdata; + if (item_sensor != NULL) { + item_sensor->hwdata = joystick->hwdata; + } /* mark joystick as fresh and ready */ joystick->hwdata->fresh = SDL_TRUE; + if (joystick->hwdata->has_gyro) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f); + } + if (joystick->hwdata->has_accelerometer) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f); + } + return 0; } @@ -1259,7 +1487,13 @@ static int LINUX_JoystickSendEffect(SDL_Joystick *joystick, const void *data, in static int LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled) { - return SDL_Unsupported(); + if (!joystick->hwdata->has_accelerometer && !joystick->hwdata->has_gyro) { + return SDL_Unsupported(); + } + + joystick->hwdata->report_sensor = enabled; + + return 0; } static void HandleHat(Uint64 timestamp, SDL_Joystick *stick, int hatidx, int axis, int value) @@ -1403,6 +1637,38 @@ static void PollAllValues(Uint64 timestamp, SDL_Joystick *joystick) } } +static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) +{ + struct input_absinfo absinfo; + int i; + + if (joystick->hwdata->has_gyro) { + float data[3] = {0.0f, 0.0f, 0.0f}; + SDL_assert(joystick->hwdata->fd_sensor >= 0); + for (i = 0; i < 3; i++) { + if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) >= 0) { + data[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i]; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick : Re-read Gyro (axis %d) val= %f\n", i, data[i]); +#endif + } + } + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); + } + if (joystick->hwdata->has_accelerometer) { + float data[3] = {0.0f, 0.0f, 0.0f}; + SDL_assert(joystick->hwdata->fd_sensor >= 0); + for (i = 0; i < 3; i++) { + if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) >= 0) { + data[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i]; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick : Re-read Accelerometer (axis %d) val= %f\n", i, data[i]); +#endif + } + } + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); + } +} static void HandleInputEvents(SDL_Joystick *joystick) { struct input_event events[32]; @@ -1411,10 +1677,14 @@ static void HandleInputEvents(SDL_Joystick *joystick) SDL_AssertJoysticksLocked(); if (joystick->hwdata->fresh) { - PollAllValues(SDL_GetTicksNS(), joystick); + Uint64 ticks = SDL_GetTicksNS(); + PollAllValues(ticks, joystick); + PollAllSensors(ticks, joystick); joystick->hwdata->fresh = SDL_FALSE; } + errno = 0; + while ((len = read(joystick->hwdata->fd, events, sizeof(events))) > 0) { len /= sizeof(events[0]); for (i = 0; i < len; ++i) { @@ -1485,6 +1755,97 @@ static void HandleInputEvents(SDL_Joystick *joystick) if (errno == ENODEV) { /* We have to wait until the JoystickDetect callback to remove this */ joystick->hwdata->gone = SDL_TRUE; + errno = 0; + } + + if (joystick->hwdata->report_sensor) { + SDL_assert(joystick->hwdata->fd_sensor >= 0); + + while ((len = read(joystick->hwdata->fd_sensor, events, sizeof(events))) > 0) { + len /= sizeof(events[0]); + for (i = 0; i < len; ++i) { + unsigned int j; + struct input_event *event = &events[i]; + + code = event->code; + + /* If the kernel sent a SYN_DROPPED, we are supposed to ignore the + rest of the packet (the end of it signified by a SYN_REPORT) */ + if (joystick->hwdata->recovering_from_dropped_sensor && + ((event->type != EV_SYN) || (code != SYN_REPORT))) { + continue; + } + + switch (event->type) { + case EV_KEY: + SDL_assert(0); + break; + case EV_ABS: + switch (code) { + case ABS_X: + case ABS_Y: + case ABS_Z: + j = code - ABS_X; + joystick->hwdata->accel_data[j] = event->value * SDL_STANDARD_GRAVITY + / joystick->hwdata->accelerometer_scale[j]; + break; + case ABS_RX: + case ABS_RY: + case ABS_RZ: + j = code - ABS_RX; + joystick->hwdata->gyro_data[j] = event->value * (SDL_PI_F / 180.f) + / joystick->hwdata->gyro_scale[j]; + break; + } + break; + case EV_MSC: + if (code == MSC_TIMESTAMP) { + Sint32 tick = event->value; + Sint32 delta; + if (joystick->hwdata->last_tick < tick) { + delta = (tick - joystick->hwdata->last_tick); + } else { + delta = (SDL_MAX_SINT32 - joystick->hwdata->last_tick + tick + 1); + } + joystick->hwdata->sensor_tick += delta; + joystick->hwdata->last_tick = tick; + } + break; + case EV_SYN: + switch (code) { + case SYN_DROPPED: + #ifdef DEBUG_INPUT_EVENTS + SDL_Log("Event SYN_DROPPED detected\n"); + #endif + joystick->hwdata->recovering_from_dropped_sensor = SDL_TRUE; + break; + case SYN_REPORT: + if (joystick->hwdata->recovering_from_dropped_sensor) { + joystick->hwdata->recovering_from_dropped_sensor = SDL_FALSE; + PollAllSensors(SDL_GetTicksNS(), joystick); /* try to sync up to current state now */ + } else { + Uint64 timestamp = SDL_EVDEV_GetEventTimestamp(event); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, + SDL_US_TO_NS(joystick->hwdata->sensor_tick), + joystick->hwdata->gyro_data, 3); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, + SDL_US_TO_NS(joystick->hwdata->sensor_tick), + joystick->hwdata->accel_data, 3); + } + break; + default: + break; + } + default: + break; + } + } + } + } + + if (errno == ENODEV) { + /* We have to wait until the JoystickDetect callback to remove this */ + joystick->hwdata->sensor_gone = SDL_TRUE; } } @@ -1564,9 +1925,15 @@ static void LINUX_JoystickClose(SDL_Joystick *joystick) if (joystick->hwdata->fd >= 0) { close(joystick->hwdata->fd); } + if (joystick->hwdata->fd_sensor >= 0) { + close(joystick->hwdata->fd_sensor); + } if (joystick->hwdata->item) { joystick->hwdata->item->hwdata = NULL; } + if (joystick->hwdata->item_sensor) { + joystick->hwdata->item_sensor->hwdata = NULL; + } SDL_free(joystick->hwdata->key_pam); SDL_free(joystick->hwdata->abs_pam); SDL_free(joystick->hwdata->hats); @@ -1580,6 +1947,8 @@ static void LINUX_JoystickQuit(void) { SDL_joylist_item *item = NULL; SDL_joylist_item *next = NULL; + SDL_sensorlist_item *item_sensor = NULL; + SDL_sensorlist_item *next_sensor = NULL; if (inotify_fd >= 0) { close(inotify_fd); @@ -1590,8 +1959,13 @@ static void LINUX_JoystickQuit(void) next = item->next; FreeJoylistItem(item); } + for (item_sensor = SDL_sensorlist; item_sensor; item_sensor = next_sensor) { + next_sensor = item_sensor->next; + FreeSensorlistItem(item_sensor); + } SDL_joylist = SDL_joylist_tail = NULL; + SDL_sensorlist = NULL; numjoysticks = 0; @@ -1648,7 +2022,7 @@ static SDL_bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap item->checked_mapping = SDL_TRUE; - if (PrepareJoystickHwdata(joystick, item) == -1) { + if (PrepareJoystickHwdata(joystick, item, NULL) == -1) { SDL_free(joystick->hwdata); SDL_free(joystick); return SDL_FALSE; /* SDL_SetError will already have been called */ diff --git a/src/joystick/linux/SDL_sysjoystick_c.h b/src/joystick/linux/SDL_sysjoystick_c.h index 69ca9893e0553..e737332d6af3d 100644 --- a/src/joystick/linux/SDL_sysjoystick_c.h +++ b/src/joystick/linux/SDL_sysjoystick_c.h @@ -25,12 +25,16 @@ #include struct SDL_joylist_item; +struct SDL_sensorlist_item; /* The private structure used to keep track of a joystick */ struct joystick_hwdata { int fd; + /* linux driver creates a separate device for gyro/accelerometer */ + int fd_sensor; struct SDL_joylist_item *item; + struct SDL_sensorlist_item *item_sensor; SDL_JoystickGUID guid; char *fname; /* Used in haptic subsystem */ @@ -50,6 +54,8 @@ struct joystick_hwdata Uint8 abs_map[ABS_MAX]; SDL_bool has_key[KEY_MAX]; SDL_bool has_abs[ABS_MAX]; + SDL_bool has_accelerometer; + SDL_bool has_gyro; /* Support for the classic joystick interface */ SDL_bool classic; @@ -69,8 +75,20 @@ struct joystick_hwdata float scale; } abs_correct[ABS_MAX]; + float accelerometer_scale[3]; + float gyro_scale[3]; + + /* Each axis is read independently, if we don't get all axis this call to + * LINUX_JoystickUpdateupdate(), store them for the next one */ + float gyro_data[3]; + float accel_data[3]; + Uint64 sensor_tick; + Sint32 last_tick; + + SDL_bool report_sensor; SDL_bool fresh; SDL_bool recovering_from_dropped; + SDL_bool recovering_from_dropped_sensor; /* Steam Controller support */ SDL_bool m_bSteamController; @@ -87,6 +105,7 @@ struct joystick_hwdata /* Set when gamepad is pending removal due to ENODEV read error */ SDL_bool gone; + SDL_bool sensor_gone; }; #endif /* SDL_sysjoystick_c_h_ */ From 5941bde3dbb75bb8b0d97e920ea2d3184ee00593 Mon Sep 17 00:00:00 2001 From: meyraud705 Date: Sat, 13 May 2023 18:25:45 +0200 Subject: [PATCH 2/3] Improve sensor detection for Linux gamepad --- src/SDL_utils.c | 13 ++++++++++ src/SDL_utils_c.h | 2 ++ src/joystick/SDL_gamepad.c | 16 +----------- src/joystick/linux/SDL_sysjoystick.c | 39 ++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/SDL_utils.c b/src/SDL_utils.c index 653e671dfd44d..7401d1cbef91b 100644 --- a/src/SDL_utils.c +++ b/src/SDL_utils.c @@ -48,3 +48,16 @@ int SDL_powerof2(int x) return value; } + +SDL_bool SDL_endswith(const char *string, const char *suffix) +{ + size_t string_length = string ? SDL_strlen(string) : 0; + size_t suffix_length = suffix ? SDL_strlen(suffix) : 0; + + if (suffix_length > 0 && suffix_length <= string_length) { + if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h index 30061016b4002..f6dfc195e80b3 100644 --- a/src/SDL_utils_c.h +++ b/src/SDL_utils_c.h @@ -28,4 +28,6 @@ /* Return the smallest power of 2 greater than or equal to 'x' */ int SDL_powerof2(int x); +SDL_bool SDL_endswith(const char *string, const char *suffix); + #endif /* SDL_utils_h_ */ diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index d6cc3c92e82de..acd08237a6754 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -22,6 +22,7 @@ /* This is the gamepad API for Simple DirectMedia Layer */ +#include "../SDL_utils_c.h" #include "SDL_sysjoystick.h" #include "SDL_joystick_c.h" #include "SDL_gamepad_c.h" @@ -2056,21 +2057,6 @@ SDL_bool SDL_IsGamepad(SDL_JoystickID instance_id) return retval; } -#ifdef __LINUX__ -static SDL_bool SDL_endswith(const char *string, const char *suffix) -{ - size_t string_length = string ? SDL_strlen(string) : 0; - size_t suffix_length = suffix ? SDL_strlen(suffix) : 0; - - if (suffix_length > 0 && suffix_length <= string_length) { - if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) { - return SDL_TRUE; - } - } - return SDL_FALSE; -} -#endif - /* * Return 1 if the gamepad should be ignored by SDL */ diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index 3ec1fa77fefcf..d2733214aca6b 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -41,6 +41,7 @@ #include #include +#include "../../SDL_utils_c.h" #include "../../events/SDL_events_c.h" #include "../../core/linux/SDL_evdev.h" #include "../SDL_sysjoystick.h" @@ -274,6 +275,44 @@ static int IsJoystick(const char *path, int fd, char **name_return, SDL_Joystick static int IsSensor(const char *path, int fd) { + struct input_id inpid; + char *name; + char product_string[128]; + + if (ioctl(fd, EVIOCGID, &inpid) < 0) { + return 0; + } + + if (ioctl(fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) { + return 0; + } + + name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string); + if (name == NULL) { + return 0; + } + + if (SDL_endswith(name, " Motion Sensors")) { + /* PS3 and PS4 motion controls */ + SDL_free(name); + return 1; + } + if (SDL_strncmp(name, "Nintendo ", 9) == 0 && SDL_strstr(name, " IMU") != NULL) { + /* Nintendo Switch Joy-Con and Pro Controller IMU */ + SDL_free(name); + return 1; + } + if (SDL_endswith(name, " Accelerometer") || + SDL_endswith(name, " IR") || + SDL_endswith(name, " Motion Plus") || + SDL_endswith(name, " Nunchuk")) { + /* Wii extension controls */ + /* These may create 3 sensor devices but we only support reading from 1: ignore them */ + SDL_free(name); + return 0; + } + + SDL_free(name); return GuessIsSensor(fd); } From dfa69a63fdf4ebff6a1d1526edced3fb29c402d4 Mon Sep 17 00:00:00 2001 From: meyraud705 Date: Sat, 13 May 2023 19:22:12 +0200 Subject: [PATCH 3/3] Don't keep sensor file open while sensors are disabled --- src/joystick/linux/SDL_sysjoystick.c | 37 +++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index d2733214aca6b..360685f9c103e 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -1370,6 +1370,9 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) SDL_memset(uniq_item, 0, sizeof(uniq_item)); fd_item = open(item->path, O_RDONLY | O_CLOEXEC, 0); + if (fd_item < 0) { + return NULL; + } if (ioctl(fd_item, EVIOCGUNIQ(sizeof(uniq_item) - 1), &uniq_item) < 0) { return NULL; } @@ -1388,6 +1391,9 @@ static SDL_sensorlist_item *GetSensor(SDL_joylist_item *item) SDL_memset(uniq_sensor, 0, sizeof(uniq_sensor)); fd_sensor = open(item_sensor->path, O_RDONLY | O_CLOEXEC, 0); + if (fd_sensor < 0) { + continue; + } if (ioctl(fd_sensor, EVIOCGUNIQ(sizeof(uniq_sensor) - 1), &uniq_sensor) < 0) { close(fd_sensor); continue; @@ -1449,6 +1455,11 @@ static int LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) if (joystick->hwdata->has_accelerometer) { SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f); } + if (joystick->hwdata->fd_sensor >= 0) { + /* Don't keep fd_sensor opened while sensor is disabled */ + close(joystick->hwdata->fd_sensor); + joystick->hwdata->fd_sensor = -1; + } return 0; } @@ -1529,9 +1540,24 @@ static int LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enab if (!joystick->hwdata->has_accelerometer && !joystick->hwdata->has_gyro) { return SDL_Unsupported(); } + if (enabled == joystick->hwdata->report_sensor) { + return 0; + } - joystick->hwdata->report_sensor = enabled; + if (enabled) { + SDL_assert(joystick->hwdata->item_sensor); + joystick->hwdata->fd_sensor = open(joystick->hwdata->item_sensor->path, O_RDONLY | O_CLOEXEC, 0); + if (joystick->hwdata->fd_sensor < 0) { + return SDL_SetError("Couldn't open sensor file %s.", joystick->hwdata->item_sensor->path); + } + fcntl(joystick->hwdata->fd_sensor, F_SETFL, O_NONBLOCK); + } else { + SDL_assert(joystick->hwdata->fd_sensor >= 0); + close(joystick->hwdata->fd_sensor); + joystick->hwdata->fd_sensor = -1; + } + joystick->hwdata->report_sensor = enabled; return 0; } @@ -1681,9 +1707,10 @@ static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) struct input_absinfo absinfo; int i; + SDL_assert(joystick->hwdata->fd_sensor >= 0); + if (joystick->hwdata->has_gyro) { float data[3] = {0.0f, 0.0f, 0.0f}; - SDL_assert(joystick->hwdata->fd_sensor >= 0); for (i = 0; i < 3; i++) { if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) >= 0) { data[i] = absinfo.value * (SDL_PI_F / 180.f) / joystick->hwdata->gyro_scale[i]; @@ -1696,7 +1723,6 @@ static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) } if (joystick->hwdata->has_accelerometer) { float data[3] = {0.0f, 0.0f, 0.0f}; - SDL_assert(joystick->hwdata->fd_sensor >= 0); for (i = 0; i < 3; i++) { if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) >= 0) { data[i] = absinfo.value * SDL_STANDARD_GRAVITY / joystick->hwdata->accelerometer_scale[i]; @@ -1708,6 +1734,7 @@ static void PollAllSensors(Uint64 timestamp, SDL_Joystick *joystick) SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, SDL_US_TO_NS(joystick->hwdata->sensor_tick), data, 3); } } + static void HandleInputEvents(SDL_Joystick *joystick) { struct input_event events[32]; @@ -1718,7 +1745,9 @@ static void HandleInputEvents(SDL_Joystick *joystick) if (joystick->hwdata->fresh) { Uint64 ticks = SDL_GetTicksNS(); PollAllValues(ticks, joystick); - PollAllSensors(ticks, joystick); + if (joystick->hwdata->report_sensor) { + PollAllSensors(ticks, joystick); + } joystick->hwdata->fresh = SDL_FALSE; }