SDL: Backport #7697 to SDL 2

From 25b00813679997715f0b7d22eae416d284411456 Mon Sep 17 00:00:00 2001
From: KWottrich <[EMAIL REDACTED]>
Date: Mon, 2 Oct 2023 13:45:34 -0500
Subject: [PATCH] Backport #7697 to SDL 2

Backport fixes from #8349

Include changes from #8357
---
 src/joystick/linux/SDL_sysjoystick.c   | 484 +++++++++++++++++++++++--
 src/joystick/linux/SDL_sysjoystick_c.h |  19 +
 2 files changed, 463 insertions(+), 40 deletions(-)

diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index 4ba32ef35571..66ff60cace18 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -162,10 +162,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 Uint32 last_joy_detect_time;
@@ -205,13 +215,12 @@ static SDL_bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version,
 }
 #endif /* SDL_JOYSTICK_HIDAPI */
 
-static int GuessIsJoystick(int fd)
+static int GuessDeviceClass(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) ||
@@ -220,9 +229,21 @@ static int GuessIsJoystick(int fd)
         return 0;
     }
 
-    devclass = SDL_EVDEV_GuessDeviceClass(evbit, absbit, keybit, relbit);
+    return SDL_EVDEV_GuessDeviceClass(evbit, absbit, keybit, relbit);
+}
 
-    if (devclass & SDL_UDEV_DEVICE_JOYSTICK) {
+static int GuessIsJoystick(int fd)
+{
+    if (GuessDeviceClass(fd) & SDL_UDEV_DEVICE_JOYSTICK) {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int GuessIsSensor(int fd)
+{
+    if (GuessDeviceClass(fd) & SDL_UDEV_DEVICE_ACCELEROMETER) {
         return 1;
     }
 
@@ -285,6 +306,23 @@ static int IsJoystick(const char *path, int fd, char **name_return, SDL_Joystick
     return 1;
 }
 
+static int IsSensor(const char *path, int fd)
+{
+    struct input_id inpid;
+
+    if (ioctl(fd, EVIOCGID, &inpid) < 0) {
+        return 0;
+    }
+
+    if (inpid.vendor == USB_VENDOR_NINTENDO && inpid.product == USB_PRODUCT_NINTENDO_WII_REMOTE) {
+        /* Wii extension controls */
+        /* These may create 3 sensor devices but we only support reading from 1: ignore them */
+        return 0;
+    }
+
+    return GuessIsSensor(fd);
+}
+
 #if SDL_USE_LIBUDEV
 static void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
 {
@@ -331,14 +369,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;
@@ -354,6 +398,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) {
@@ -364,42 +413,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)
@@ -426,10 +499,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;
@@ -444,6 +537,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;
 }
@@ -452,6 +553,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) {
@@ -467,6 +570,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, int *device_instance)
@@ -962,7 +1080,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 };
@@ -1153,6 +1271,45 @@ 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)) {
+            joystick->hwdata->has_accelerometer = SDL_TRUE;
+            for (i = 0; i < 3; ++i) {
+                struct input_absinfo absinfo;
+                if (ioctl(fd_sensor, EVIOCGABS(ABS_X + i), &absinfo) < 0) {
+                    joystick->hwdata->has_accelerometer = SDL_FALSE;
+                    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 */
+            }
+        }
+
+        if (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit) && test_bit(ABS_RZ, absbit)) {
+            joystick->hwdata->has_gyro = SDL_TRUE;
+            for (i = 0; i < 3; ++i) {
+                struct input_absinfo absinfo;
+                if (ioctl(fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) < 0) {
+                    joystick->hwdata->has_gyro = SDL_FALSE;
+                    break; /* do not report a 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 */
+            }
+        }
+    }
+
     /* Allocate data to keep track of these thingamajigs */
     if (joystick->nhats > 0) {
         if (allocate_hatdata(joystick) < 0) {
@@ -1180,11 +1337,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;
@@ -1193,12 +1351,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);
@@ -1206,23 +1366,86 @@ static int PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item)
         if (fd < 0) {
             return SDL_SetError("Unable to open %s", item->path);
         }
+        /* If opening 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 (fd_item < 0) {
+        return NULL;
+    }
+    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 (fd_sensor < 0) {
+            continue;
+        }
+        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.
@@ -1231,6 +1454,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 = JoystickByDevIndex(device_index);
+    SDL_sensorlist_item *item_sensor = GetSensor(item);
 
     SDL_AssertJoysticksLocked();
 
@@ -1245,18 +1469,34 @@ 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);
+    }
+    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;
 }
 
@@ -1333,7 +1573,30 @@ 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();
+    }
+    if (enabled == joystick->hwdata->report_sensor) {
+        return 0;
+    }
+
+    if (enabled) {
+        if (joystick->hwdata->item_sensor == NULL) {
+            return SDL_SetError("Sensors unplugged.");
+        }
+        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;
 }
 
 static void HandleHat(SDL_Joystick *stick, int hatidx, int axis, int value)
@@ -1486,6 +1749,39 @@ static void PollAllValues(SDL_Joystick *joystick)
     /* Joyballs are relative input, so there's no poll state. Events only! */
 }
 
+static void PollAllSensors(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};
+        for (i = 0; i < 3; i++) {
+            if (ioctl(joystick->hwdata->fd_sensor, EVIOCGABS(ABS_RX + i), &absinfo) >= 0) {
+                data[i] = absinfo.value * (M_PI / 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_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, joystick->hwdata->sensor_tick, data, 3);
+    }
+    if (joystick->hwdata->has_accelerometer) {
+        float data[3] = {0.0f, 0.0f, 0.0f};
+        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_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, joystick->hwdata->sensor_tick, data, 3);
+    }
+}
+
 static void HandleInputEvents(SDL_Joystick *joystick)
 {
     struct input_event events[32];
@@ -1495,9 +1791,14 @@ static void HandleInputEvents(SDL_Joystick *joystick)
 
     if (joystick->hwdata->fresh) {
         PollAllValues(joystick);
+        if (joystick->hwdata->report_sensor) {
+            PollAllSensors(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) {
@@ -1577,6 +1878,96 @@ 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 * (M_PI / 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(joystick); /* try to sync up to current state now */
+                        } else {
+                            SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO,
+                                                   joystick->hwdata->sensor_tick,
+                                                   joystick->hwdata->gyro_data, 3);
+                            SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL,
+                                                   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;
     }
 }
 
@@ -1670,9 +2061,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);
@@ -1687,6 +2084,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);
@@ -1697,8 +2096,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;
 
@@ -1769,7 +2173,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 a567312a9165..cffe484c971e 100644
--- a/src/joystick/linux/SDL_sysjoystick_c.h
+++ b/src/joystick/linux/SDL_sysjoystick_c.h
@@ -25,12 +25,16 @@
 #include <linux/input.h>
 
 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 */
 
@@ -55,6 +59,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;
@@ -74,8 +80,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;
@@ -92,6 +110,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_ */