SDL: added hueristic to differentiate digital vs analog 'hat' input axes and expose the latter as regular axes; added automatic...

From 0b8e796e2c09a91637f3a455dbb477984ff99354 Mon Sep 17 00:00:00 2001
From: atfrase <[EMAIL REDACTED]>
Date: Wed, 12 Jan 2022 22:00:46 -0600
Subject: [PATCH] added hueristic to differentiate digital vs analog 'hat'
 input axes and expose the latter as regular axes; added automatic deadzones
 to hat outputs, in case analog axes are still mapped to digital hats; updated
 automatic gamepad control mapping to more completely follow the spec

---
 src/joystick/linux/SDL_sysjoystick.c   | 285 ++++++++++++++++++-------
 src/joystick/linux/SDL_sysjoystick_c.h |   5 +
 2 files changed, 209 insertions(+), 81 deletions(-)

diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index e16ed688d59..28bec9db317 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -78,7 +78,7 @@
 #include "../../core/linux/SDL_evdev_capabilities.h"
 #include "../../core/linux/SDL_udev.h"
 
-#if 0
+#if 1
 #define DEBUG_INPUT_EVENTS 1
 #endif
 
@@ -926,6 +926,33 @@ allocate_balldata(SDL_Joystick *joystick)
     return (0);
 }
 
+static SDL_bool
+GuessIfAxesAreDigitalHat(struct input_absinfo *absinfo_x, struct input_absinfo *absinfo_y)
+{
+    /* A "hat" is assumed to be a digital input with at most 9 possible states
+     * (3 per axis: negative/zero/positive), as opposed to a true "axis" which
+     * can report a continuous range of possible values. Unfortunately the Linux
+     * joystick interface makes no distinction between digital hat axes and any
+     * other continuous analog axis, so we have to guess. */
+
+    /* If both axes are missing, they're not anything. */
+    if (!absinfo_x && !absinfo_y)
+        return SDL_FALSE;
+
+    /* If both axes have ranges constrained between -1 and 1, they're definitely digital. */
+    if ((!absinfo_x || (absinfo_x->minimum == -1 && absinfo_x->maximum == 1)) &&
+        (!absinfo_y || (absinfo_y->minimum == -1 && absinfo_y->maximum == 1)))
+        return SDL_TRUE;
+
+    /* If both axes lack fuzz, flat, and resolution values, they're probably digital. */
+    if ((!absinfo_x || (!absinfo_x->fuzz && !absinfo_x->flat && !absinfo_x->resolution)) &&
+        (!absinfo_y || (!absinfo_y->fuzz && !absinfo_y->flat && !absinfo_y->resolution)))
+        return SDL_TRUE;
+
+    /* Otherwise, treat them as analog. */
+    return SDL_FALSE;
+}
+
 static void
 ConfigJoystick(SDL_Joystick *joystick, int fd)
 {
@@ -963,10 +990,43 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
                 ++joystick->nbuttons;
             }
         }
+        for (i = ABS_HAT0X; i <= ABS_HAT3Y; i += 2) {
+            int hat_x = -1;
+            int hat_y = -1;
+            struct input_absinfo absinfo_x;
+            struct input_absinfo absinfo_y;
+            if (test_bit(i, absbit))
+                hat_x = ioctl(fd, EVIOCGABS(i), &absinfo_x);
+            if (test_bit(i + 1, absbit))
+                hat_y = ioctl(fd, EVIOCGABS(i + 1), &absinfo_y);
+            if (GuessIfAxesAreDigitalHat((hat_x < 0 ? (void*)0 : &absinfo_x),
+                                         (hat_y < 0 ? (void*)0 : &absinfo_y))) {
+                const int hat_index = (i - ABS_HAT0X) / 2;
+#ifdef DEBUG_INPUT_EVENTS
+                SDL_Log("Joystick has digital hat: %d\n", hat_index);
+                if (hat_x >= 0) {
+                    SDL_Log("X Values = { %d, %d, %d, %d, %d, %d }\n",
+                            absinfo_x.value, absinfo_x.minimum, absinfo_x.maximum,
+                            absinfo_x.fuzz, absinfo_x.flat, absinfo_x.resolution);
+                }
+                if (hat_y >= 0) {
+                    SDL_Log("Y Values = { %d, %d, %d, %d, %d, %d }\n",
+                            absinfo_y.value, absinfo_y.minimum, absinfo_y.maximum,
+                            absinfo_y.fuzz, absinfo_y.flat, absinfo_y.resolution);
+                }
+#endif /* DEBUG_INPUT_EVENTS */
+                joystick->hwdata->hats_indices[hat_index] = joystick->nhats;
+                joystick->hwdata->has_hat[hat_index] = SDL_TRUE;
+                joystick->hwdata->hat_correct[hat_index].minimum[0] = (hat_x < 0) ? -1 : absinfo_x.minimum;
+                joystick->hwdata->hat_correct[hat_index].maximum[0] = (hat_x < 0) ?  1 : absinfo_x.maximum;
+                joystick->hwdata->hat_correct[hat_index].minimum[1] = (hat_y < 0) ? -1 : absinfo_y.minimum;
+                joystick->hwdata->hat_correct[hat_index].maximum[1] = (hat_y < 0) ?  1 : absinfo_y.maximum;
+                ++joystick->nhats;
+            }
+        }
         for (i = 0; i < ABS_MAX; ++i) {
-            /* Skip hats */
-            if (i == ABS_HAT0X) {
-                i = ABS_HAT3Y;
+            /* Skip digital hats */
+            if (joystick->hwdata->has_hat[(i - ABS_HAT0X) / 2]) {
                 continue;
             }
             if (test_bit(i, absbit)) {
@@ -978,9 +1038,9 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
                 }
 #ifdef DEBUG_INPUT_EVENTS
                 SDL_Log("Joystick has absolute axis: 0x%.2x\n", i);
-                SDL_Log("Values = { %d, %d, %d, %d, %d }\n",
+                SDL_Log("Values = { %d, %d, %d, %d, %d, %d }\n",
                         absinfo.value, absinfo.minimum, absinfo.maximum,
-                        absinfo.fuzz, absinfo.flat);
+                        absinfo.fuzz, absinfo.flat, absinfo.resolution);
 #endif /* DEBUG_INPUT_EVENTS */
                 joystick->hwdata->abs_map[i] = joystick->naxes;
                 joystick->hwdata->has_abs[i] = SDL_TRUE;
@@ -1008,24 +1068,6 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
                 ++joystick->naxes;
             }
         }
-        for (i = ABS_HAT0X; i <= ABS_HAT3Y; i += 2) {
-            if (test_bit(i, absbit) || test_bit(i + 1, absbit)) {
-                struct input_absinfo absinfo;
-                int hat_index = (i - ABS_HAT0X) / 2;
-
-                if (ioctl(fd, EVIOCGABS(i), &absinfo) < 0) {
-                    continue;
-                }
-#ifdef DEBUG_INPUT_EVENTS
-                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;
-            }
-        }
         if (test_bit(REL_X, relbit) || test_bit(REL_Y, relbit)) {
             ++joystick->nballs;
         }
@@ -1071,14 +1113,19 @@ ConfigJoystick(SDL_Joystick *joystick, int fd)
         for (i = 0; i < abs_pam_size; ++i) {
             Uint8 code = joystick->hwdata->abs_pam[i];
 
+            // TODO: is there any way to detect analog hats in advance via this API?
             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);
+                    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;
+                    joystick->hwdata->hat_correct[hat_index].minimum[0] = -1;
+                    joystick->hwdata->hat_correct[hat_index].maximum[0] =  1;
+                    joystick->hwdata->hat_correct[hat_index].minimum[1] = -1;
+                    joystick->hwdata->hat_correct[hat_index].maximum[1] =  1;
                 }
             } else {
 #ifdef DEBUG_INPUT_EVENTS
@@ -1277,26 +1324,47 @@ LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 }
 
 static void
-HandleHat(SDL_Joystick *stick, Uint8 hat, int axis, int value)
+HandleHat(SDL_Joystick *stick, int hatidx, int axis, int value)
 {
+    const int hatnum = stick->hwdata->hats_indices[hatidx];
     struct hwdata_hat *the_hat;
+    struct hat_axis_correct *correct;
     const Uint8 position_map[3][3] = {
         {SDL_HAT_LEFTUP, SDL_HAT_UP, SDL_HAT_RIGHTUP},
         {SDL_HAT_LEFT, SDL_HAT_CENTERED, SDL_HAT_RIGHT},
         {SDL_HAT_LEFTDOWN, SDL_HAT_DOWN, SDL_HAT_RIGHTDOWN}
     };
 
-    the_hat = &stick->hwdata->hats[hat];
+    the_hat = &stick->hwdata->hats[hatnum];
+    correct = &stick->hwdata->hat_correct[hatidx];
+    /* If we failed to detect that this hat's axes are analog,
+     * we can at least make the hat output usable by applying
+     * a deadzone of half of the observed min/max values. If
+     * the axes really are digital, this won't hurt. */
     if (value < 0) {
-        value = 0;
-    } else if (value == 0) {
-        value = 1;
+        if (value <= correct->minimum[axis]) {
+            correct->minimum[axis] = value;
+            value = 0;
+        } else if (value < correct->minimum[axis] / 2) {
+            value = 0;
+        } else {
+            value = 1;
+        }
     } else if (value > 0) {
-        value = 2;
+        if (value >= correct->maximum[axis]) {
+            correct->maximum[axis] = value;
+            value = 2;
+        } else if (value > correct->maximum[axis] / 2) {
+            value = 2;
+        } else {
+            value = 1;
+        }
+    } else { // value == 0
+        value = 1;
     }
     if (value != the_hat->axis[axis]) {
         the_hat->axis[axis] = value;
-        SDL_PrivateJoystickHat(stick, hat,
+        SDL_PrivateJoystickHat(stick, hatnum,
                                position_map[the_hat->axis[1]][the_hat->axis[0]]);
     }
 }
@@ -1351,10 +1419,7 @@ PollAllValues(SDL_Joystick *joystick)
 
     /* Poll all axis */
     for (i = ABS_X; i < ABS_MAX; i++) {
-        if (i == ABS_HAT0X) {  /* we handle hats in the next loop, skip them for now. */
-            i = ABS_HAT3Y;
-            continue;
-        }
+        /* We don't need to test for digital hats here, they won't have has_abs[] set */
         if (joystick->hwdata->has_abs[i]) {
             if (ioctl(joystick->hwdata->fd, EVIOCGABS(i), &absinfo) >= 0) {
                 absinfo.value = AxisCorrect(joystick, i, absinfo.value);
@@ -1370,15 +1435,16 @@ PollAllValues(SDL_Joystick *joystick)
         }
     }
 
-    /* Poll all hats */
+    /* Poll all digital hats */
     for (i = ABS_HAT0X; i <= ABS_HAT3Y; i++) {
         const int baseaxis = i - ABS_HAT0X;
         const int hatidx = baseaxis / 2;
         SDL_assert(hatidx < SDL_arraysize(joystick->hwdata->has_hat));
+        /* We don't need to test for analog axes here, they won't have has_hat[] set */
         if (joystick->hwdata->has_hat[hatidx]) {
             if (ioctl(joystick->hwdata->fd, EVIOCGABS(i), &absinfo) >= 0) {
                 const int hataxis = baseaxis % 2;
-                HandleHat(joystick, joystick->hwdata->hats_indices[hatidx], hataxis, absinfo.value);
+                HandleHat(joystick, hatidx, hataxis, absinfo.value);
             }
         }
     }
@@ -1406,7 +1472,7 @@ static void
 HandleInputEvents(SDL_Joystick *joystick)
 {
     struct input_event events[32];
-    int i, len, code;
+    int i, len, code, hat_index;
 
     if (joystick->hwdata->fresh) {
         PollAllValues(joystick);
@@ -1441,9 +1507,11 @@ HandleInputEvents(SDL_Joystick *joystick)
                 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;
+                    hat_index = (code - ABS_HAT0X) / 2;
+                    if (joystick->hwdata->has_hat[hat_index]) {
+                        HandleHat(joystick, hat_index, code % 2, events[i].value);
+                        break;
+                    }
                 default:
                     events[i].value = AxisCorrect(joystick, code, events[i].value);
                     SDL_PrivateJoystickAxis(joystick,
@@ -1496,7 +1564,7 @@ static void
 HandleClassicEvents(SDL_Joystick *joystick)
 {
     struct js_event events[32];
-    int i, len, code;
+    int i, len, code, hat_index;
 
     joystick->hwdata->fresh = SDL_FALSE;
     while ((len = read(joystick->hwdata->fd, events, (sizeof events))) > 0) {
@@ -1520,9 +1588,11 @@ HandleClassicEvents(SDL_Joystick *joystick)
                 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;
+                    hat_index = (code - ABS_HAT0X) / 2;
+                    if (joystick->hwdata->has_hat[hat_index]) {
+                        HandleHat(joystick, hat_index, code % 2, events[i].value);
+                        break;
+                    }
                 default:
                     SDL_PrivateJoystickAxis(joystick,
                                             joystick->hwdata->abs_map[code],
@@ -1628,6 +1698,7 @@ LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
 {
     SDL_Joystick *joystick;
     SDL_joylist_item *item = JoystickByDevIndex(device_index);
+    unsigned int mapped;
 
     if (item->checked_mapping) {
         if (item->mapping) {
@@ -1739,84 +1810,136 @@ LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
        can be digital, or analog, or both at the same time.
      */
 
-    /* Prefer digital shoulder buttons, but settle for analog if missing. */
+    /* Prefer digital shoulder buttons, but settle for digital or analog hat. */
+    mapped = 0;
+
     if (joystick->hwdata->has_key[BTN_TL]) {
         out->leftshoulder.kind = EMappingKind_Button;
         out->leftshoulder.target = joystick->hwdata->key_map[BTN_TL];
+        mapped |= 0x1;
     }
 
     if (joystick->hwdata->has_key[BTN_TR]) {
         out->rightshoulder.kind = EMappingKind_Button;
         out->rightshoulder.target = joystick->hwdata->key_map[BTN_TR];
+        mapped |= 0x2;
     }
 
-    if (joystick->hwdata->has_hat[1] && /* Check if ABS_HAT1{X, Y} is available. */
-       (!joystick->hwdata->has_key[BTN_TL] || !joystick->hwdata->has_key[BTN_TR])) {
+    if (mapped != 0x3 && joystick->hwdata->has_hat[1]) {
         int hat = joystick->hwdata->hats_indices[1] << 4;
         out->leftshoulder.kind = EMappingKind_Hat;
         out->rightshoulder.kind = EMappingKind_Hat;
         out->leftshoulder.target = hat | 0x4;
         out->rightshoulder.target = hat | 0x2;
+        mapped |= 0x3;
+    }
+
+    if (!(mapped & 0x1) && joystick->hwdata->has_abs[ABS_HAT1Y]) {
+        out->leftshoulder.kind = EMappingKind_Axis;
+        out->leftshoulder.target = joystick->hwdata->abs_map[ABS_HAT1Y];
+        mapped |= 0x1;
+    }
+
+    if (!(mapped & 0x2) && joystick->hwdata->has_abs[ABS_HAT1X]) {
+        out->rightshoulder.kind = EMappingKind_Axis;
+        out->rightshoulder.target = joystick->hwdata->abs_map[ABS_HAT1X];
+        mapped |= 0x2;
     }
 
-    /* Prefer analog triggers, but settle for digital if missing. */
-    if (joystick->hwdata->has_hat[2]) { /* Check if ABS_HAT2{X,Y} is available. */
+    /* Prefer analog triggers, but settle for digital hat or buttons. */
+    mapped = 0;
+
+    if (joystick->hwdata->has_abs[ABS_HAT2Y]) {
+        out->lefttrigger.kind = EMappingKind_Axis;
+        out->lefttrigger.target = joystick->hwdata->abs_map[ABS_HAT2Y];
+        mapped |= 0x1;
+    } else if (joystick->hwdata->has_abs[ABS_Z]) {
+        out->lefttrigger.kind = EMappingKind_Axis;
+        out->lefttrigger.target = joystick->hwdata->abs_map[ABS_Z];
+        mapped |= 0x1;
+    }
+
+    if (joystick->hwdata->has_abs[ABS_HAT2X]) {
+        out->righttrigger.kind = EMappingKind_Axis;
+        out->righttrigger.target = joystick->hwdata->abs_map[ABS_HAT2X];
+        mapped |= 0x2;
+    } else if (joystick->hwdata->has_abs[ABS_RZ]) {
+        out->righttrigger.kind = EMappingKind_Axis;
+        out->righttrigger.target = joystick->hwdata->abs_map[ABS_RZ];
+        mapped |= 0x2;
+    }
+
+    if (mapped != 0x3 && joystick->hwdata->has_hat[2]) {
         int hat = joystick->hwdata->hats_indices[2] << 4;
         out->lefttrigger.kind = EMappingKind_Hat;
         out->righttrigger.kind = EMappingKind_Hat;
         out->lefttrigger.target = hat | 0x4;
         out->righttrigger.target = hat | 0x2;
-    } else {
-        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];
-        }
+        mapped |= 0x3;
+    }
 
-        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];
-        }
+    if (!(mapped & 0x1) && joystick->hwdata->has_key[BTN_TL2]) {
+        out->lefttrigger.kind = EMappingKind_Button;
+        out->lefttrigger.target = joystick->hwdata->key_map[BTN_TL2];
+        mapped |= 0x1;
+    }
+
+    if (!(mapped & 0x2) && joystick->hwdata->has_key[BTN_TR2]) {
+        out->righttrigger.kind = EMappingKind_Button;
+        out->righttrigger.target = joystick->hwdata->key_map[BTN_TR2];
+        mapped |= 0x2;
     }
 
-    /* Prefer digital D-Pad, but settle for analog if missing. */
+    /* Prefer digital D-Pad buttons, but settle for digital or analog hat. */
+    mapped = 0;
+
     if (joystick->hwdata->has_key[BTN_DPAD_UP]) {
         out->dpup.kind = EMappingKind_Button;
         out->dpup.target = joystick->hwdata->key_map[BTN_DPAD_UP];
+        mapped |= 0x1;
     }
 
     if (joystick->hwdata->has_key[BTN_DPAD_DOWN]) {
         out->dpdown.kind = EMappingKind_Button;
         out->dpdown.target = joystick->hwdata->key_map[BTN_DPAD_DOWN];
+        mapped |= 0x2;
     }
 
     if (joystick->hwdata->has_key[BTN_DPAD_LEFT]) {
         out->dpleft.kind = EMappingKind_Button;
         out->dpleft.target = joystick->hwdata->key_map[BTN_DPAD_LEFT];
+        mapped |= 0x4;
     }
 
     if (joystick->hwdata->has_key[BTN_DPAD_RIGHT]) {
         out->dpright.kind = EMappingKind_Button;
         out->dpright.target = joystick->hwdata->key_map[BTN_DPAD_RIGHT];
-    }
-
-    if (joystick->hwdata->has_hat[0] && /* Check if ABS_HAT0{X,Y} is available. */
-       (!joystick->hwdata->has_key[BTN_DPAD_LEFT] || !joystick->hwdata->has_key[BTN_DPAD_RIGHT] ||
-        !joystick->hwdata->has_key[BTN_DPAD_UP] || !joystick->hwdata->has_key[BTN_DPAD_DOWN])) {
-       int hat = joystick->hwdata->hats_indices[0] << 4;
-       out->dpleft.kind = EMappingKind_Hat;
-       out->dpright.kind = EMappingKind_Hat;
-       out->dpup.kind = EMappingKind_Hat;
-       out->dpdown.kind = EMappingKind_Hat;
-       out->dpleft.target = hat | 0x8;
-       out->dpright.target = hat | 0x2;
-       out->dpup.target = hat | 0x1;
-       out->dpdown.target = hat | 0x4;
+        mapped |= 0x8;
+    }
+
+    if (mapped != 0xF) {
+        if (joystick->hwdata->has_hat[0]) {
+            int hat = joystick->hwdata->hats_indices[0] << 4;
+            out->dpleft.kind = EMappingKind_Hat;
+            out->dpright.kind = EMappingKind_Hat;
+            out->dpup.kind = EMappingKind_Hat;
+            out->dpdown.kind = EMappingKind_Hat;
+            out->dpleft.target = hat | 0x8;
+            out->dpright.target = hat | 0x2;
+            out->dpup.target = hat | 0x1;
+            out->dpdown.target = hat | 0x4;
+            mapped |= 0xF;
+        } else if (joystick->hwdata->has_abs[ABS_HAT0X] && joystick->hwdata->has_abs[ABS_HAT0Y]) {
+            out->dpleft.kind = EMappingKind_Axis;
+            out->dpright.kind = EMappingKind_Axis;
+            out->dpup.kind = EMappingKind_Axis;
+            out->dpdown.kind = EMappingKind_Axis;
+            out->dpleft.target = joystick->hwdata->abs_map[ABS_HAT0X];
+            out->dpright.target = joystick->hwdata->abs_map[ABS_HAT0X];
+            out->dpup.target = joystick->hwdata->abs_map[ABS_HAT0Y];
+            out->dpdown.target = joystick->hwdata->abs_map[ABS_HAT0Y];
+            mapped |= 0xF;
+        }
     }
 
     if (joystick->hwdata->has_abs[ABS_X] && joystick->hwdata->has_abs[ABS_Y]) {
diff --git a/src/joystick/linux/SDL_sysjoystick_c.h b/src/joystick/linux/SDL_sysjoystick_c.h
index 49b8686d054..f6211d55db1 100644
--- a/src/joystick/linux/SDL_sysjoystick_c.h
+++ b/src/joystick/linux/SDL_sysjoystick_c.h
@@ -83,6 +83,11 @@ struct joystick_hwdata
     /* 4 = (ABS_HAT3X-ABS_HAT0X)/2 (see input-event-codes.h in kernel) */
     int hats_indices[4];
     SDL_bool has_hat[4];
+    struct hat_axis_correct
+    {
+        int minimum[2];
+        int maximum[2];
+    } hat_correct[4];
 
     /* Set when gamepad is pending removal due to ENODEV read error */
     SDL_bool gone;