SDL: Added support for /dev/input/js* on Linux

From 1c78b0800759174abe1bac01ce5b9cd788a6a372 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 10 Nov 2021 20:02:25 -0800
Subject: [PATCH] Added support for /dev/input/js* on Linux

Added the hint SDL_HINT_LINUX_JOYSTICK_CLASSIC to control whether /dev/input/js* or /dev/input/event* are used as joystick devices

Added the hint SDL_HINT_JOYSTICK_DEVICE to allow the user to specify devices t
hat will be opened in addition to the normal joystick detection

Fixes https://github.com/libsdl-org/SDL/issues/1314
Fixes https://github.com/libsdl-org/SDL/issues/1727
Fixes https://github.com/libsdl-org/SDL/issues/1981
Closes https://github.com/libsdl-org/SDL/pull/4727
---
 WhatsNew.txt                           |   4 +
 include/SDL_hints.h                    |  18 ++
 src/joystick/linux/SDL_sysjoystick.c   | 246 +++++++++++++++++++------
 src/joystick/linux/SDL_sysjoystick_c.h |   5 +
 4 files changed, 213 insertions(+), 60 deletions(-)

diff --git a/WhatsNew.txt b/WhatsNew.txt
index 4822970b30..e6076c552c 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -5,6 +5,10 @@ This is a list of major changes in SDL's version history.
 2.0.18:
 ---------------------------------------------------------------------------
 
+Linux:
+* Added the hint SDL_HINT_LINUX_JOYSTICK_CLASSIC to control whether /dev/input/js* or /dev/input/event* are used as joystick devices
+* Added the hint SDL_HINT_JOYSTICK_DEVICE to allow the user to specify devices that will be opened in addition to the normal joystick detection
+
 Android:
 * Added support for audio output and capture using AAudio on Android 8.1 and newer
 
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index db286a24ca..d7782591b2 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -816,6 +816,24 @@ extern "C" {
  */
 #define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER      "SDL_KMSDRM_REQUIRE_DRM_MASTER"
 
+ /**
+  *  \brief  A comma separated list of devices to open as joysticks
+  *
+  *  This variable is currently only used by the Linux joystick driver.
+  */
+#define SDL_HINT_JOYSTICK_DEVICE "SDL_JOYSTICK_DEVICE"
+
+ /**
+  *  \brief  A variable controlling whether to use the classic /dev/input/js* joystick interface or the newer /dev/input/event* joystick interface on Linux
+  *
+  *  This variable can be set to the following values:
+  *    "0"       - Use /dev/input/event*
+  *    "1"       - Use /dev/input/js*
+  *
+  *  By default the /dev/input/event* interfaces are used
+  */
+#define SDL_HINT_LINUX_JOYSTICK_CLASSIC "SDL_LINUX_JOYSTICK_CLASSIC"
+
  /**
   *  \brief  A variable controlling whether joysticks on Linux adhere to their HID-defined deadzones or return unfiltered values.
   *
diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index aef8e332e9..52859d576d 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -85,6 +85,7 @@ typedef enum
 
 static EnumerationMethod enumeration_method = ENUMERATION_UNSET;
 
+static SDL_bool IsJoystickDeviceNode(const char *node);
 static int MaybeAddDevice(const char *path);
 static int MaybeRemoveDevice(const char *path);
 
@@ -105,6 +106,7 @@ typedef struct SDL_joylist_item
     SDL_GamepadMapping *mapping;
 } SDL_joylist_item;
 
+static SDL_bool SDL_classic_joysticks = SDL_TRUE;
 static SDL_joylist_item *SDL_joylist = NULL;
 static SDL_joylist_item *SDL_joylist_tail = NULL;
 static int numjoysticks = 0;
@@ -183,17 +185,22 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid)
     char *name;
     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_LIBUDEV && !GuessIsJoystick(fd)) {
-        return 0;
-    }
+    if (ioctl(fd, JSIOCGNAME(sizeof(product_string)), product_string) >= 0) {
+        inpid.vendor = 0;
+        inpid.product = 0;
+    } else {
+        /* When udev is enabled we only get joystick devices here, so there's no need to test them */
+        if (enumeration_method != ENUMERATION_LIBUDEV && !GuessIsJoystick(fd)) {
+            return 0;
+        }
 
-    if (ioctl(fd, EVIOCGID, &inpid) < 0) {
-        return 0;
-    }
+        if (ioctl(fd, EVIOCGID, &inpid) < 0) {
+            return 0;
+        }
 
-    if (ioctl(fd, EVIOCGNAME(sizeof(product_string)), product_string) < 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);
@@ -213,7 +220,7 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid)
     FixupDeviceInfoForMapping(fd, &inpid);
 
 #ifdef DEBUG_JOYSTICK
-    printf("Joystick: %s, bustype = %d, vendor = 0x%.4x, product = 0x%.4x, version = %d\n", name, inpid.bustype, inpid.vendor, inpid.product, inpid.version);
+    SDL_Log("Joystick: %s, bustype = %d, vendor = 0x%.4x, product = 0x%.4x, version = %d\n", name, inpid.bustype, inpid.vendor, inpid.product, inpid.version);
 #endif
 
     SDL_memset(guid->data, 0, sizeof(guid->data));
@@ -254,6 +261,9 @@ static void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_clas
             if (!(udev_class & SDL_UDEV_DEVICE_JOYSTICK)) {
                 return;
             }
+            if (SDL_classic_joysticks && !IsJoystickDeviceNode(devpath)) {
+                return;
+            }
             MaybeAddDevice(devpath);
             break;
             
@@ -308,7 +318,7 @@ MaybeAddDevice(const char *path)
     }
 
 #ifdef DEBUG_INPUT_EVENTS
-    printf("Checking %s\n", path);
+    SDL_Log("Checking %s\n", path);
 #endif
 
     isstick = IsJoystick(fd, &name, &guid);
@@ -507,6 +517,20 @@ StrIsInteger(const char *string)
     return 1;
 }
 
+static SDL_bool
+IsJoystickDeviceNode(const char *node)
+{
+    const char *last_slash = SDL_strrchr(node, '/');
+    if (last_slash) {
+        node = last_slash + 1;
+    }
+    if (SDL_classic_joysticks) {
+        return (StrHasPrefix(node, "js") && StrIsInteger(node + 2));
+    } else {
+        return (StrHasPrefix(node, "event") && StrIsInteger(node + 5));
+    }
+}
+
 static void
 LINUX_InotifyJoystickDetect(void)
 {
@@ -519,6 +543,7 @@ LINUX_InotifyJoystickDetect(void)
     ssize_t bytes;
     size_t remain = 0;
     size_t len;
+    char path[PATH_MAX];
 
     bytes = read(inotify_fd, &buf, sizeof (buf));
 
@@ -528,10 +553,7 @@ LINUX_InotifyJoystickDetect(void)
 
     while (remain > 0) {
         if (buf.event.len > 0) {
-            if (StrHasPrefix(buf.event.name, "event") &&
-                StrIsInteger(buf.event.name + SDL_strlen ("event"))) {
-                char path[PATH_MAX];
-
+            if (IsJoystickDeviceNode(buf.event.name)) {
                 SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", buf.event.name);
 
                 if (buf.event.mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB)) {
@@ -560,7 +582,7 @@ LINUX_InotifyJoystickDetect(void)
 static int
 filter_entries(const struct dirent *entry)
 {
-    return (SDL_strlen(entry->d_name) > 5 && SDL_strncmp(entry->d_name, "event", 5) == 0);
+    return IsJoystickDeviceNode(entry->d_name);
 }
 static int
 sort_entries(const struct dirent **a, const struct dirent **b)
@@ -627,7 +649,9 @@ LINUX_JoystickDetect(void)
 static int
 LINUX_JoystickInit(void)
 {
-    const char *devices = SDL_GetHint("SDL_JOYSTICK_DEVICE");
+    const char *devices = SDL_GetHint(SDL_HINT_JOYSTICK_DEVICE);
+
+    SDL_classic_joysticks = SDL_GetHintBoolean(SDL_HINT_LINUX_JOYSTICK_CLASSIC, SDL_FALSE);
 
 #if SDL_USE_LIBUDEV
     if (enumeration_method == ENUMERATION_UNSET) {
@@ -824,6 +848,7 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
     unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
     unsigned long relbit[NBITS(REL_MAX)] = { 0 };
     unsigned long ffbit[NBITS(FF_MAX)] = { 0 };
+    Uint8 key_pam_size, abs_pam_size;
     SDL_bool use_deadzones = SDL_GetHintBoolean(SDL_HINT_LINUX_JOYSTICK_DEADZONES, SDL_FALSE);
 
     /* See if this device uses the new unified event API */
@@ -835,7 +860,7 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
         for (i = BTN_JOYSTICK; i < KEY_MAX; ++i) {
             if (test_bit(i, keybit)) {
 #ifdef DEBUG_INPUT_EVENTS
-                printf("Joystick has button: 0x%x\n", i);
+                SDL_Log("Joystick has button: 0x%x\n", i);
 #endif
                 joystick->hwdata->key_map[i] = joystick->nbuttons;
                 joystick->hwdata->has_key[i] = SDL_TRUE;
@@ -845,7 +870,7 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
         for (i = 0; i < BTN_JOYSTICK; ++i) {
             if (test_bit(i, keybit)) {
 #ifdef DEBUG_INPUT_EVENTS
-                printf("Joystick has button: 0x%x\n", i);
+                SDL_Log("Joystick has button: 0x%x\n", i);
 #endif
                 joystick->hwdata->key_map[i] = joystick->nbuttons;
                 joystick->hwdata->has_key[i] = SDL_TRUE;
@@ -866,10 +891,10 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
                     continue;
                 }
 #ifdef DEBUG_INPUT_EVENTS
-                printf("Joystick has absolute axis: 0x%.2x\n", i);
-                printf("Values = { %d, %d, %d, %d, %d }\n",
-                       absinfo.value, absinfo.minimum, absinfo.maximum,
-                       absinfo.fuzz, absinfo.flat);
+                SDL_Log("Joystick has absolute axis: 0x%.2x\n", i);
+                SDL_Log("Values = { %d, %d, %d, %d, %d }\n",
+                        absinfo.value, absinfo.minimum, absinfo.maximum,
+                        absinfo.fuzz, absinfo.flat);
 #endif /* DEBUG_INPUT_EVENTS */
                 joystick->hwdata->abs_map[i] = joystick->naxes;
                 joystick->hwdata->has_abs[i] = SDL_TRUE;
@@ -906,10 +931,10 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
                     continue;
                 }
 #ifdef DEBUG_INPUT_EVENTS
-                printf("Joystick has hat %d\n", hat_index);
-                printf("Values = { %d, %d, %d, %d, %d }\n",
-                       absinfo.value, absinfo.minimum, absinfo.maximum,
-                       absinfo.fuzz, absinfo.flat);
+                SDL_Log("Joystick has hat %d\n", hat_index);
+                SDL_Log("Values = { %d, %d, %d, %d, %d }\n",
+                        absinfo.value, absinfo.minimum, absinfo.maximum,
+                        absinfo.fuzz, absinfo.flat);
 #endif /* DEBUG_INPUT_EVENTS */
                 joystick->hwdata->hats_indices[hat_index] = joystick->nhats++;
                 joystick->hwdata->has_hat[hat_index] = SDL_TRUE;
@@ -919,19 +944,75 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
             ++joystick->nballs;
         }
 
-        /* Allocate data to keep track of these thingamajigs */
-        if (joystick->nhats > 0) {
-            if (allocate_hatdata(joystick) < 0) {
-                joystick->nhats = 0;
+    } else if ((ioctl(fd, JSIOCGBUTTONS, &key_pam_size, sizeof(key_pam_size)) >= 0) &&
+               (ioctl(fd, JSIOCGAXES, &abs_pam_size, sizeof(abs_pam_size)) >= 0)) {
+        size_t len;
+
+        joystick->hwdata->classic = SDL_TRUE;
+
+        len = (KEY_MAX - BTN_MISC + 1) * sizeof(*joystick->hwdata->key_pam);
+        joystick->hwdata->key_pam = (Uint16 *)SDL_calloc(1, len);
+        if (joystick->hwdata->key_pam) {
+            if (ioctl(fd, JSIOCGBTNMAP, joystick->hwdata->key_pam, len) < 0) {
+                SDL_free(joystick->hwdata->key_pam);
+                joystick->hwdata->key_pam = NULL;
+                key_pam_size = 0;
             }
         }
-        if (joystick->nballs > 0) {
-            if (allocate_balldata(joystick) < 0) {
-                joystick->nballs = 0;
+        for (i = 0; i < key_pam_size; ++i) {
+            Uint16 code = joystick->hwdata->key_pam[i];
+#ifdef DEBUG_INPUT_EVENTS
+            SDL_Log("Joystick has button: 0x%x\n", code);
+#endif
+            joystick->hwdata->key_map[code] = joystick->nbuttons;
+            joystick->hwdata->has_key[code] = SDL_TRUE;
+            ++joystick->nbuttons;
+        }
+
+        len = ABS_CNT * sizeof(*joystick->hwdata->abs_pam);
+        joystick->hwdata->abs_pam = (Uint8 *)SDL_calloc(1, len);
+        if (joystick->hwdata->abs_pam) {
+            if (ioctl(fd, JSIOCGAXMAP, joystick->hwdata->abs_pam, len) < 0) {
+                SDL_free(joystick->hwdata->abs_pam);
+                joystick->hwdata->abs_pam = NULL;
+                abs_pam_size = 0;
+            }
+        }
+        for (i = 0; i < abs_pam_size; ++i) {
+            Uint8 code = joystick->hwdata->abs_pam[i];
+
+            if (code >= ABS_HAT0X && code <= ABS_HAT3Y) {
+                int hat_index = (code - ABS_HAT0X) / 2;
+                if (!joystick->hwdata->has_hat[hat_index]) {
+#ifdef DEBUG_INPUT_EVENTS
+                    SDL_Log("Joystick has hat %d\n", hat_index);
+#endif
+                    joystick->hwdata->hats_indices[hat_index] = joystick->nhats++;
+                    joystick->hwdata->has_hat[hat_index] = SDL_TRUE;
+                }
+            } else {
+#ifdef DEBUG_INPUT_EVENTS
+                SDL_Log("Joystick has absolute axis: 0x%.2x\n", code);
+#endif
+                joystick->hwdata->abs_map[code] = joystick->naxes;
+                joystick->hwdata->has_abs[code] = SDL_TRUE;
+                ++joystick->naxes;
             }
         }
     }
 
+    /* Allocate data to keep track of these thingamajigs */
+    if (joystick->nhats > 0) {
+        if (allocate_hatdata(joystick) < 0) {
+            joystick->nhats = 0;
+        }
+    }
+    if (joystick->nballs > 0) {
+        if (allocate_balldata(joystick) < 0) {
+            joystick->nballs = 0;
+        }
+    }
+
     if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) >= 0) {
         if (test_bit(FF_RUMBLE, ffbit)) {
             joystick->hwdata->ff_rumble = SDL_TRUE;
@@ -955,6 +1036,7 @@ PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item)
     joystick->hwdata->guid = item->guid;
     joystick->hwdata->effect.id = -1;
     joystick->hwdata->m_bSteamController = item->m_bSteamController;
+    SDL_memset(joystick->hwdata->key_map, 0xFF, sizeof(joystick->hwdata->key_map));
     SDL_memset(joystick->hwdata->abs_map, 0xFF, sizeof(joystick->hwdata->abs_map));
 
     if (item->m_bSteamController) {
@@ -1098,7 +1180,7 @@ LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
     return SDL_Unsupported();
 }
 
-static SDL_INLINE void
+static void
 HandleHat(SDL_Joystick *stick, Uint8 hat, int axis, int value)
 {
     struct hwdata_hat *the_hat;
@@ -1123,14 +1205,14 @@ HandleHat(SDL_Joystick *stick, Uint8 hat, int axis, int value)
     }
 }
 
-static SDL_INLINE void
+static void
 HandleBall(SDL_Joystick *stick, Uint8 ball, int axis, int value)
 {
     stick->hwdata->balls[ball].axis[axis] += value;
 }
 
 
-static SDL_INLINE int
+static int
 AxisCorrect(SDL_Joystick *joystick, int which, int value)
 {
     struct axis_correct *correct;
@@ -1164,7 +1246,7 @@ AxisCorrect(SDL_Joystick *joystick, int which, int value)
     return value;
 }
 
-static SDL_INLINE void
+static void
 PollAllValues(SDL_Joystick *joystick)
 {
     struct input_absinfo absinfo;
@@ -1182,7 +1264,7 @@ PollAllValues(SDL_Joystick *joystick)
                 absinfo.value = AxisCorrect(joystick, i, absinfo.value);
 
 #ifdef DEBUG_INPUT_EVENTS
-                printf("Joystick : Re-read Axis %d (%d) val= %d\n",
+                SDL_Log("Joystick : Re-read Axis %d (%d) val= %d\n",
                     joystick->hwdata->abs_map[i], i, absinfo.value);
 #endif
                 SDL_PrivateJoystickAxis(joystick,
@@ -1212,7 +1294,7 @@ PollAllValues(SDL_Joystick *joystick)
             if (joystick->hwdata->has_key[i]) {
                 const Uint8 value = test_bit(i, keyinfo) ? SDL_PRESSED : SDL_RELEASED;
 #ifdef DEBUG_INPUT_EVENTS
-                printf("Joystick : Re-read Button %d (%d) val= %d\n",
+                SDL_Log("Joystick : Re-read Button %d (%d) val= %d\n",
                     joystick->hwdata->key_map[i], i, value);
 #endif
                 SDL_PrivateJoystickButton(joystick,
@@ -1224,12 +1306,11 @@ PollAllValues(SDL_Joystick *joystick)
     /* Joyballs are relative input, so there's no poll state. Events only! */
 }
 
-static SDL_INLINE void
+static void
 HandleInputEvents(SDL_Joystick *joystick)
 {
     struct input_event events[32];
-    int i, len;
-    int code;
+    int i, len, code;
 
     if (joystick->hwdata->fresh) {
         PollAllValues(joystick);
@@ -1268,13 +1349,10 @@ HandleInputEvents(SDL_Joystick *joystick)
                     HandleHat(joystick, joystick->hwdata->hats_indices[code / 2], code % 2, events[i].value);
                     break;
                 default:
-                    if (joystick->hwdata->abs_map[code] != 0xFF) {
-                        events[i].value =
-                            AxisCorrect(joystick, code, events[i].value);
-                        SDL_PrivateJoystickAxis(joystick,
-                                                joystick->hwdata->abs_map[code],
-                                                events[i].value);
-                    }
+                    events[i].value = AxisCorrect(joystick, code, events[i].value);
+                    SDL_PrivateJoystickAxis(joystick,
+                                            joystick->hwdata->abs_map[code],
+                                            events[i].value);
                     break;
                 }
                 break;
@@ -1293,7 +1371,7 @@ HandleInputEvents(SDL_Joystick *joystick)
                 switch (code) {
                 case SYN_DROPPED :
 #ifdef DEBUG_INPUT_EVENTS
-                    printf("Event SYN_DROPPED detected\n");
+                    SDL_Log("Event SYN_DROPPED detected\n");
 #endif
                     joystick->hwdata->recovering_from_dropped = SDL_TRUE;
                     break;
@@ -1318,6 +1396,48 @@ HandleInputEvents(SDL_Joystick *joystick)
     }
 }
 
+static void
+HandleClassicEvents(SDL_Joystick *joystick)
+{
+    struct js_event events[32];
+    int i, len, code;
+
+    joystick->hwdata->fresh = SDL_FALSE;
+    while ((len = read(joystick->hwdata->fd, events, (sizeof events))) > 0) {
+        len /= sizeof(events[0]);
+        for (i = 0; i < len; ++i) {
+            switch (events[i].type) {
+            case JS_EVENT_BUTTON:
+                code = joystick->hwdata->key_pam[events[i].number];
+                SDL_PrivateJoystickButton(joystick,
+                                          joystick->hwdata->key_map[code],
+                                          events[i].value);
+                break;
+            case JS_EVENT_AXIS:
+                code = joystick->hwdata->abs_pam[events[i].number];
+                switch (code) {
+                case ABS_HAT0X:
+                case ABS_HAT0Y:
+                case ABS_HAT1X:
+                case ABS_HAT1Y:
+                case ABS_HAT2X:
+                case ABS_HAT2Y:
+                case ABS_HAT3X:
+                case ABS_HAT3Y:
+                    code -= ABS_HAT0X;
+                    HandleHat(joystick, joystick->hwdata->hats_indices[code / 2], code % 2, events[i].value);
+                    break;
+                default:
+                    SDL_PrivateJoystickAxis(joystick,
+                                            joystick->hwdata->abs_map[code],
+                                            events[i].value);
+                    break;
+                }
+            }
+        }
+    }
+}
+
 static void
 LINUX_JoystickUpdate(SDL_Joystick *joystick)
 {
@@ -1328,7 +1448,11 @@ LINUX_JoystickUpdate(SDL_Joystick *joystick)
         return;
     }
 
-    HandleInputEvents(joystick);
+    if (joystick->hwdata->classic) {
+        HandleClassicEvents(joystick);
+    } else {
+        HandleInputEvents(joystick);
+    }
 
     /* Deliver ball motion updates */
     for (i = 0; i < joystick->nballs; ++i) {
@@ -1359,6 +1483,8 @@ LINUX_JoystickClose(SDL_Joystick *joystick)
         if (joystick->hwdata->item) {
             joystick->hwdata->item->hwdata = NULL;
         }
+        SDL_free(joystick->hwdata->key_pam);
+        SDL_free(joystick->hwdata->abs_pam);
         SDL_free(joystick->hwdata->hats);
         SDL_free(joystick->hwdata->balls);
         SDL_free(joystick->hwdata->fname);
@@ -1525,20 +1651,20 @@ LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
         out->lefttrigger.target = hat | 0x4;
         out->righttrigger.target = hat | 0x2;
     } else {
-        if (joystick->hwdata->has_key[BTN_TL2]) {
-            out->lefttrigger.kind = EMappingKind_Button;
-            out->lefttrigger.target = joystick->hwdata->key_map[BTN_TL2];
-        } else if (joystick->hwdata->has_abs[ABS_Z]) {
+        if (joystick->hwdata->has_abs[ABS_Z]) {
             out->lefttrigger.kind = EMappingKind_Axis;
             out->lefttrigger.target = joystick->hwdata->abs_map[ABS_Z];
+        } else if (joystick->hwdata->has_key[BTN_TL2]) {
+            out->lefttrigger.kind = EMappingKind_Button;
+            out->lefttrigger.target = joystick->hwdata->key_map[BTN_TL2];
         }
 
-        if (joystick->hwdata->has_key[BTN_TR2]) {
-            out->righttrigger.kind = EMappingKind_Button;
-            out->righttrigger.target = joystick->hwdata->key_map[BTN_TR2];
-        } else if (joystick->hwdata->has_abs[ABS_RZ]) {
+        if (joystick->hwdata->has_abs[ABS_RZ]) {
             out->righttrigger.kind = EMappingKind_Axis;
             out->righttrigger.target = joystick->hwdata->abs_map[ABS_RZ];
+        } else if (joystick->hwdata->has_key[BTN_TR2]) {
+            out->righttrigger.kind = EMappingKind_Button;
+            out->righttrigger.target = joystick->hwdata->key_map[BTN_TR2];
         }
     }
 
diff --git a/src/joystick/linux/SDL_sysjoystick_c.h b/src/joystick/linux/SDL_sysjoystick_c.h
index 898f183dd6..630e2f5c5b 100644
--- a/src/joystick/linux/SDL_sysjoystick_c.h
+++ b/src/joystick/linux/SDL_sysjoystick_c.h
@@ -56,6 +56,11 @@ struct joystick_hwdata
     SDL_bool has_key[KEY_MAX];
     SDL_bool has_abs[ABS_MAX];
 
+    /* Support for the classic joystick interface */
+    SDL_bool classic;
+    Uint16 *key_pam;
+    Uint8 *abs_pam;
+
     struct axis_correct
     {
         SDL_bool use_deadzones;