Fixed bug 5161 - Autodetect controller mappings based on the Linux Gamepad Specification
authorSam Lantinga
Fri, 29 May 2020 13:37:21 -0700
changeset 1387275c33f89d791
parent 13871 e58323b0cf8e
child 13873 5bb6be4f2425
Fixed bug 5161 - Autodetect controller mappings based on the Linux Gamepad Specification

Jan Bujak

I wrote a new driver for my gamepad on Linux. I'd like SDL to support it out-of-box, as currently it just treats it as a generic joystick instead of a gamepad. From what I can see the only way to do that is to either 1) pick one of the already supported controllers' PID, VID and button layouts and have my driver send that (effectively lying that it's something else), or 2) submit a preconfigured, hardcoded mapping to SDL.

Both of those, in my opinion, are silly when we already have the Linux Gamepad Specification which standarizes this:

https://www.kernel.org/doc/html/v4.15/input/gamepad.html

Unfortunately SDL doesn't make use of it currently. So I've took it upon myself to add it; patch is in the attachments.

Basically what the patch does is that if SDL finds no built-it controller mappings for a given joystick it then asks the joystick backend to autodetect it, and that uses the relevant evdev bits to figure out which button/axis is which. (See the specs for more details.)

With this patch applied my own driver for my controller works out-of-box with SDL with no extra configuration and is correctly recognized as a gamepad; this is also going to be the case for any other driver which follows the Linux Gamepad Specification.
src/joystick/SDL_gamecontroller.c
src/joystick/SDL_joystick.c
src/joystick/SDL_joystick_c.h
src/joystick/SDL_sysjoystick.h
src/joystick/android/SDL_sysjoystick.c
src/joystick/bsd/SDL_sysjoystick.c
src/joystick/darwin/SDL_sysjoystick.c
src/joystick/dummy/SDL_sysjoystick.c
src/joystick/emscripten/SDL_sysjoystick.c
src/joystick/haiku/SDL_haikujoystick.cc
src/joystick/hidapi/SDL_hidapijoystick.c
src/joystick/iphoneos/SDL_sysjoystick.m
src/joystick/linux/SDL_sysjoystick.c
src/joystick/linux/SDL_sysjoystick_c.h
src/joystick/virtual/SDL_virtualjoystick.c
src/joystick/windows/SDL_rawinputjoystick.c
src/joystick/windows/SDL_windows_gaming_input.c
src/joystick/windows/SDL_windowsjoystick.c
     1.1 --- a/src/joystick/SDL_gamecontroller.c	Fri May 29 13:05:37 2020 +0100
     1.2 +++ b/src/joystick/SDL_gamecontroller.c	Fri May 29 13:37:21 2020 -0700
     1.3 @@ -1089,6 +1089,91 @@
     1.4      return mapping;
     1.5  }
     1.6  
     1.7 +static void SDL_PrivateAppendToMappingString(char *mapping_string,
     1.8 +                                             size_t mapping_string_len,
     1.9 +                                             const char *input_name,
    1.10 +                                             SDL_InputMapping *mapping)
    1.11 +{
    1.12 +    char buffer[16];
    1.13 +    if (mapping->kind == EMappingKind_None) {
    1.14 +        return;
    1.15 +    }
    1.16 +
    1.17 +    SDL_strlcat(mapping_string, input_name, mapping_string_len);
    1.18 +    SDL_strlcat(mapping_string, ":", mapping_string_len);
    1.19 +    switch (mapping->kind) {
    1.20 +        case EMappingKind_Button:
    1.21 +            SDL_snprintf(buffer, sizeof(buffer), "b%i", mapping->target);
    1.22 +            break;
    1.23 +        case EMappingKind_Axis:
    1.24 +            SDL_snprintf(buffer, sizeof(buffer), "a%i", mapping->target);
    1.25 +            break;
    1.26 +        case EMappingKind_Hat:
    1.27 +            SDL_snprintf(buffer, sizeof(buffer), "h%i.%i", mapping->target >> 4, mapping->target & 0x0F);
    1.28 +            break;
    1.29 +        default:
    1.30 +            SDL_assert(SDL_FALSE);
    1.31 +    }
    1.32 +
    1.33 +    SDL_strlcat(mapping_string, buffer, mapping_string_len);
    1.34 +    SDL_strlcat(mapping_string, ",", mapping_string_len);
    1.35 +}
    1.36 +
    1.37 +static ControllerMapping_t *SDL_PrivateGenerateAutomaticControllerMapping(const char *name,
    1.38 +                                                                          SDL_JoystickGUID guid,
    1.39 +                                                                          SDL_GamepadMapping *raw_map)
    1.40 +{
    1.41 +    SDL_bool existing;
    1.42 +    char name_string[128];
    1.43 +    char mapping[1024];
    1.44 +
    1.45 +    /* Remove any commas in the name */
    1.46 +    SDL_strlcpy(name_string, name, sizeof(name_string));
    1.47 +    {
    1.48 +        char *spot;
    1.49 +        for (spot = name_string; *spot; ++spot) {
    1.50 +            if (*spot == ',') {
    1.51 +                *spot = ' ';
    1.52 +            }
    1.53 +        }
    1.54 +    }
    1.55 +    SDL_snprintf(mapping, sizeof(mapping), "none,%s,", name_string);
    1.56 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "a", &raw_map->a);
    1.57 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "b", &raw_map->b);
    1.58 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "x", &raw_map->x);
    1.59 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "y", &raw_map->y);
    1.60 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "back", &raw_map->back);
    1.61 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "guide", &raw_map->guide);
    1.62 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "start", &raw_map->start);
    1.63 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftstick", &raw_map->leftstick);
    1.64 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightstick", &raw_map->rightstick);
    1.65 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftshoulder", &raw_map->leftshoulder);
    1.66 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightshoulder", &raw_map->rightshoulder);
    1.67 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpup", &raw_map->dpup);
    1.68 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpdown", &raw_map->dpdown);
    1.69 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpleft", &raw_map->dpleft);
    1.70 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpright", &raw_map->dpright);
    1.71 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftx", &raw_map->leftx);
    1.72 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefty", &raw_map->lefty);
    1.73 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightx", &raw_map->rightx);
    1.74 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righty", &raw_map->righty);
    1.75 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefttrigger", &raw_map->lefttrigger);
    1.76 +    SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righttrigger", &raw_map->righttrigger);
    1.77 +
    1.78 +    /* Remove trailing comma */
    1.79 +    {
    1.80 +        int pos = (int)SDL_strlen(mapping) - 1;
    1.81 +        if (pos >= 0) {
    1.82 +            if (mapping[pos] == ',') {
    1.83 +                mapping[pos] = '\0';
    1.84 +            }
    1.85 +        }
    1.86 +    }
    1.87 +
    1.88 +    return SDL_PrivateAddMappingForGUID(guid, mapping,
    1.89 +                      &existing, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT);
    1.90 +}
    1.91 +
    1.92  static ControllerMapping_t *SDL_PrivateGetControllerMapping(int device_index)
    1.93  {
    1.94      const char *name;
    1.95 @@ -1106,6 +1191,13 @@
    1.96      name = SDL_JoystickNameForIndex(device_index);
    1.97      guid = SDL_JoystickGetDeviceGUID(device_index);
    1.98      mapping = SDL_PrivateGetControllerMappingForNameAndGUID(name, guid);
    1.99 +    if (!mapping) {
   1.100 +        SDL_GamepadMapping raw_map;
   1.101 +        if (SDL_PrivateJoystickGetAutoGamepadMapping(device_index, &raw_map)) {
   1.102 +            mapping = SDL_PrivateGenerateAutomaticControllerMapping(name, guid, &raw_map);
   1.103 +        }
   1.104 +    }
   1.105 +
   1.106      SDL_UnlockJoysticks();
   1.107      return mapping;
   1.108  }
     2.1 --- a/src/joystick/SDL_joystick.c	Fri May 29 13:05:37 2020 +0100
     2.2 +++ b/src/joystick/SDL_joystick.c	Fri May 29 13:37:21 2020 -0700
     2.3 @@ -584,6 +584,21 @@
     2.4      return valid;
     2.5  }
     2.6  
     2.7 +SDL_bool
     2.8 +SDL_PrivateJoystickGetAutoGamepadMapping(int device_index, SDL_GamepadMapping * out)
     2.9 +{
    2.10 +    SDL_JoystickDriver *driver;
    2.11 +    SDL_bool is_ok = SDL_FALSE;
    2.12 +
    2.13 +    SDL_LockJoysticks();
    2.14 +    if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &device_index)) {
    2.15 +        is_ok = driver->GetGamepadMapping(device_index, out);
    2.16 +    }
    2.17 +    SDL_UnlockJoysticks();
    2.18 +
    2.19 +    return is_ok;
    2.20 +}
    2.21 +
    2.22  /*
    2.23   * Get the number of multi-dimensional axis controls on a joystick
    2.24   */
     3.1 --- a/src/joystick/SDL_joystick_c.h	Fri May 29 13:05:37 2020 +0100
     3.2 +++ b/src/joystick/SDL_joystick_c.h	Fri May 29 13:37:21 2020 -0700
     3.3 @@ -111,6 +111,49 @@
     3.4  /* Internal sanity checking functions */
     3.5  extern SDL_bool SDL_PrivateJoystickValid(SDL_Joystick * joystick);
     3.6  
     3.7 +typedef enum
     3.8 +{
     3.9 +    EMappingKind_None = 0,
    3.10 +    EMappingKind_Button = 1,
    3.11 +    EMappingKind_Axis = 2,
    3.12 +    EMappingKind_Hat = 3
    3.13 +} EMappingKind;
    3.14 +
    3.15 +typedef struct _SDL_InputMapping
    3.16 +{
    3.17 +    EMappingKind kind;
    3.18 +    Uint8 target;
    3.19 +} SDL_InputMapping;
    3.20 +
    3.21 +typedef struct _SDL_GamepadMapping
    3.22 +{
    3.23 +    SDL_InputMapping a;
    3.24 +    SDL_InputMapping b;
    3.25 +    SDL_InputMapping x;
    3.26 +    SDL_InputMapping y;
    3.27 +    SDL_InputMapping back;
    3.28 +    SDL_InputMapping guide;
    3.29 +    SDL_InputMapping start;
    3.30 +    SDL_InputMapping leftstick;
    3.31 +    SDL_InputMapping rightstick;
    3.32 +    SDL_InputMapping leftshoulder;
    3.33 +    SDL_InputMapping rightshoulder;
    3.34 +    SDL_InputMapping dpup;
    3.35 +    SDL_InputMapping dpdown;
    3.36 +    SDL_InputMapping dpleft;
    3.37 +    SDL_InputMapping dpright;
    3.38 +    SDL_InputMapping leftx;
    3.39 +    SDL_InputMapping lefty;
    3.40 +    SDL_InputMapping rightx;
    3.41 +    SDL_InputMapping righty;
    3.42 +    SDL_InputMapping lefttrigger;
    3.43 +    SDL_InputMapping righttrigger;
    3.44 +} SDL_GamepadMapping;
    3.45 +
    3.46 +/* Function to get autodetected gamepad controller mapping from the driver */
    3.47 +extern SDL_bool SDL_PrivateJoystickGetAutoGamepadMapping(int device_index,
    3.48 +                                                         SDL_GamepadMapping *out);
    3.49 +
    3.50  #endif /* SDL_joystick_c_h_ */
    3.51  
    3.52  /* vi: set ts=4 sw=4 expandtab: */
     4.1 --- a/src/joystick/SDL_sysjoystick.h	Fri May 29 13:05:37 2020 +0100
     4.2 +++ b/src/joystick/SDL_sysjoystick.h	Fri May 29 13:37:21 2020 -0700
     4.3 @@ -136,6 +136,9 @@
     4.4      /* Function to perform any system-specific joystick related cleanup */
     4.5      void (*Quit)(void);
     4.6  
     4.7 +    /* Function to get the autodetected controller mapping; returns false if there isn't any. */
     4.8 +    SDL_bool (*GetGamepadMapping)(int device_index, SDL_GamepadMapping * out);
     4.9 +
    4.10  } SDL_JoystickDriver;
    4.11  
    4.12  /* Windows and Mac OSX has a limit of MAX_DWORD / 1000, Linux kernel has a limit of 0xFFFF */
     5.1 --- a/src/joystick/android/SDL_sysjoystick.c	Fri May 29 13:05:37 2020 +0100
     5.2 +++ b/src/joystick/android/SDL_sysjoystick.c	Fri May 29 13:37:21 2020 -0700
     5.3 @@ -693,6 +693,12 @@
     5.4  #endif /* 0 */
     5.5  }
     5.6  
     5.7 +static SDL_bool
     5.8 +ANDROID_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
     5.9 +{
    5.10 +    return SDL_FALSE;
    5.11 +}
    5.12 +
    5.13  SDL_JoystickDriver SDL_ANDROID_JoystickDriver =
    5.14  {
    5.15      ANDROID_JoystickInit,
    5.16 @@ -708,6 +714,7 @@
    5.17      ANDROID_JoystickUpdate,
    5.18      ANDROID_JoystickClose,
    5.19      ANDROID_JoystickQuit,
    5.20 +    ANDROID_JoystickGetGamepadMapping
    5.21  };
    5.22  
    5.23  #endif /* SDL_JOYSTICK_ANDROID */
     6.1 --- a/src/joystick/bsd/SDL_sysjoystick.c	Fri May 29 13:05:37 2020 +0100
     6.2 +++ b/src/joystick/bsd/SDL_sysjoystick.c	Fri May 29 13:37:21 2020 -0700
     6.3 @@ -762,6 +762,12 @@
     6.4      return SDL_Unsupported();
     6.5  }
     6.6  
     6.7 +static SDL_bool
     6.8 +BSD_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
     6.9 +{
    6.10 +    return SDL_FALSE;
    6.11 +}
    6.12 +
    6.13  SDL_JoystickDriver SDL_BSD_JoystickDriver =
    6.14  {
    6.15      BSD_JoystickInit,
    6.16 @@ -777,6 +783,7 @@
    6.17      BSD_JoystickUpdate,
    6.18      BSD_JoystickClose,
    6.19      BSD_JoystickQuit,
    6.20 +    BSD_JoystickGetGamepadMapping
    6.21  };
    6.22  
    6.23  #endif /* SDL_JOYSTICK_USBHID */
     7.1 --- a/src/joystick/darwin/SDL_sysjoystick.c	Fri May 29 13:05:37 2020 +0100
     7.2 +++ b/src/joystick/darwin/SDL_sysjoystick.c	Fri May 29 13:37:21 2020 -0700
     7.3 @@ -1051,6 +1051,12 @@
     7.4      }
     7.5  }
     7.6  
     7.7 +static SDL_bool
     7.8 +DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
     7.9 +{
    7.10 +    return SDL_FALSE;
    7.11 +}
    7.12 +
    7.13  SDL_JoystickDriver SDL_DARWIN_JoystickDriver =
    7.14  {
    7.15      DARWIN_JoystickInit,
    7.16 @@ -1066,6 +1072,7 @@
    7.17      DARWIN_JoystickUpdate,
    7.18      DARWIN_JoystickClose,
    7.19      DARWIN_JoystickQuit,
    7.20 +    DARWIN_JoystickGetGamepadMapping
    7.21  };
    7.22  
    7.23  #endif /* SDL_JOYSTICK_IOKIT */
     8.1 --- a/src/joystick/dummy/SDL_sysjoystick.c	Fri May 29 13:05:37 2020 +0100
     8.2 +++ b/src/joystick/dummy/SDL_sysjoystick.c	Fri May 29 13:37:21 2020 -0700
     8.3 @@ -104,6 +104,12 @@
     8.4  {
     8.5  }
     8.6  
     8.7 +static SDL_bool
     8.8 +DUMMY_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
     8.9 +{
    8.10 +    return SDL_FALSE;
    8.11 +}
    8.12 +
    8.13  SDL_JoystickDriver SDL_DUMMY_JoystickDriver =
    8.14  {
    8.15      DUMMY_JoystickInit,
    8.16 @@ -119,6 +125,7 @@
    8.17      DUMMY_JoystickUpdate,
    8.18      DUMMY_JoystickClose,
    8.19      DUMMY_JoystickQuit,
    8.20 +    DUMMY_JoystickGetGamepadMapping
    8.21  };
    8.22  
    8.23  #endif /* SDL_JOYSTICK_DUMMY || SDL_JOYSTICK_DISABLED */
     9.1 --- a/src/joystick/emscripten/SDL_sysjoystick.c	Fri May 29 13:05:37 2020 +0100
     9.2 +++ b/src/joystick/emscripten/SDL_sysjoystick.c	Fri May 29 13:37:21 2020 -0700
     9.3 @@ -403,6 +403,12 @@
     9.4      return SDL_Unsupported();
     9.5  }
     9.6  
     9.7 +static SDL_bool
     9.8 +EMSCRIPTEN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
     9.9 +{
    9.10 +    return SDL_FALSE;
    9.11 +}
    9.12 +
    9.13  SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver =
    9.14  {
    9.15      EMSCRIPTEN_JoystickInit,
    9.16 @@ -418,6 +424,7 @@
    9.17      EMSCRIPTEN_JoystickUpdate,
    9.18      EMSCRIPTEN_JoystickClose,
    9.19      EMSCRIPTEN_JoystickQuit,
    9.20 +    EMSCRIPTEN_JoystickGetGamepadMapping
    9.21  };
    9.22  
    9.23  #endif /* SDL_JOYSTICK_EMSCRIPTEN */
    10.1 --- a/src/joystick/haiku/SDL_haikujoystick.cc	Fri May 29 13:05:37 2020 +0100
    10.2 +++ b/src/joystick/haiku/SDL_haikujoystick.cc	Fri May 29 13:37:21 2020 -0700
    10.3 @@ -259,6 +259,12 @@
    10.4          return SDL_Unsupported();
    10.5      }
    10.6  
    10.7 +    static SDL_bool
    10.8 +    HAIKU_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
    10.9 +    {
   10.10 +        return SDL_FALSE;
   10.11 +    }
   10.12 +
   10.13      SDL_JoystickDriver SDL_HAIKU_JoystickDriver =
   10.14      {
   10.15          HAIKU_JoystickInit,
   10.16 @@ -274,6 +280,7 @@
   10.17          HAIKU_JoystickUpdate,
   10.18          HAIKU_JoystickClose,
   10.19          HAIKU_JoystickQuit,
   10.20 +        HAIKU_JoystickGetGamepadMapping
   10.21      };
   10.22  
   10.23  }                              // extern "C"
    11.1 --- a/src/joystick/hidapi/SDL_hidapijoystick.c	Fri May 29 13:05:37 2020 +0100
    11.2 +++ b/src/joystick/hidapi/SDL_hidapijoystick.c	Fri May 29 13:37:21 2020 -0700
    11.3 @@ -1101,6 +1101,12 @@
    11.4      initialized = SDL_FALSE;
    11.5  }
    11.6  
    11.7 +static SDL_bool
    11.8 +HIDAPI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
    11.9 +{
   11.10 +    return SDL_FALSE;
   11.11 +}
   11.12 +
   11.13  SDL_JoystickDriver SDL_HIDAPI_JoystickDriver =
   11.14  {
   11.15      HIDAPI_JoystickInit,
   11.16 @@ -1116,6 +1122,7 @@
   11.17      HIDAPI_JoystickUpdate,
   11.18      HIDAPI_JoystickClose,
   11.19      HIDAPI_JoystickQuit,
   11.20 +    HIDAPI_JoystickGetGamepadMapping
   11.21  };
   11.22  
   11.23  #endif /* SDL_JOYSTICK_HIDAPI */
    12.1 --- a/src/joystick/iphoneos/SDL_sysjoystick.m	Fri May 29 13:05:37 2020 +0100
    12.2 +++ b/src/joystick/iphoneos/SDL_sysjoystick.m	Fri May 29 13:37:21 2020 -0700
    12.3 @@ -850,6 +850,12 @@
    12.4      numjoysticks = 0;
    12.5  }
    12.6  
    12.7 +static SDL_bool
    12.8 +IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
    12.9 +{
   12.10 +    return SDL_FALSE;
   12.11 +}
   12.12 +
   12.13  SDL_JoystickDriver SDL_IOS_JoystickDriver =
   12.14  {
   12.15      IOS_JoystickInit,
   12.16 @@ -865,6 +871,7 @@
   12.17      IOS_JoystickUpdate,
   12.18      IOS_JoystickClose,
   12.19      IOS_JoystickQuit,
   12.20 +    IOS_JoystickGetGamepadMapping
   12.21  };
   12.22  
   12.23  /* vi: set ts=4 sw=4 expandtab: */
    13.1 --- a/src/joystick/linux/SDL_sysjoystick.c	Fri May 29 13:05:37 2020 +0100
    13.2 +++ b/src/joystick/linux/SDL_sysjoystick.c	Fri May 29 13:37:21 2020 -0700
    13.3 @@ -656,6 +656,7 @@
    13.4                  printf("Joystick has button: 0x%x\n", i);
    13.5  #endif
    13.6                  joystick->hwdata->key_map[i] = joystick->nbuttons;
    13.7 +                joystick->hwdata->has_key[i] = SDL_TRUE;
    13.8                  ++joystick->nbuttons;
    13.9              }
   13.10          }
   13.11 @@ -665,6 +666,7 @@
   13.12                  printf("Joystick has button: 0x%x\n", i);
   13.13  #endif
   13.14                  joystick->hwdata->key_map[i] = joystick->nbuttons;
   13.15 +                joystick->hwdata->has_key[i] = SDL_TRUE;
   13.16                  ++joystick->nbuttons;
   13.17              }
   13.18          }
   13.19 @@ -687,6 +689,7 @@
   13.20                         absinfo.fuzz, absinfo.flat);
   13.21  #endif /* DEBUG_INPUT_EVENTS */
   13.22                  joystick->hwdata->abs_map[i] = joystick->naxes;
   13.23 +                joystick->hwdata->has_abs[i] = SDL_TRUE;
   13.24                  if (absinfo.minimum == absinfo.maximum) {
   13.25                      joystick->hwdata->abs_correct[i].used = 0;
   13.26                  } else {
   13.27 @@ -721,6 +724,7 @@
   13.28                         absinfo.fuzz, absinfo.flat);
   13.29  #endif /* DEBUG_INPUT_EVENTS */
   13.30                  joystick->hwdata->hats_indices[hat_index] = joystick->nhats++;
   13.31 +                joystick->hwdata->has_hat[hat_index] = SDL_TRUE;
   13.32              }
   13.33          }
   13.34          if (test_bit(REL_X, relbit) || test_bit(REL_Y, relbit)) {
   13.35 @@ -1100,6 +1104,181 @@
   13.36      SDL_QuitSteamControllers();
   13.37  }
   13.38  
   13.39 +/*
   13.40 +   This is based on the Linux Gamepad Specification
   13.41 +   available at: https://www.kernel.org/doc/html/v4.15/input/gamepad.html
   13.42 + */
   13.43 +static SDL_bool
   13.44 +LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
   13.45 +{
   13.46 +    SDL_Joystick * joystick;
   13.47 +
   13.48 +    joystick = (SDL_Joystick *) SDL_calloc(sizeof(*joystick), 1);
   13.49 +    if (joystick == NULL) {
   13.50 +        SDL_OutOfMemory();
   13.51 +        return SDL_FALSE;
   13.52 +    }
   13.53 +
   13.54 +    /* We temporarily open the device to check how it's configured. */
   13.55 +    if (LINUX_JoystickOpen(joystick, device_index) < 0) {
   13.56 +        SDL_free(joystick);
   13.57 +        return SDL_FALSE;
   13.58 +    }
   13.59 +
   13.60 +    if (!joystick->hwdata->has_key[BTN_GAMEPAD]) {
   13.61 +        /* Not a gamepad according to the specs. */
   13.62 +        LINUX_JoystickClose(joystick);
   13.63 +        SDL_free(joystick);
   13.64 +        return SDL_FALSE;
   13.65 +    }
   13.66 +
   13.67 +    /* We have a gamepad; start filling out the mappings. */
   13.68 +    memset(out, 0, sizeof(SDL_GamepadMapping));
   13.69 +
   13.70 +    if (joystick->hwdata->has_key[BTN_SOUTH]) {
   13.71 +        out->a.kind = EMappingKind_Button;
   13.72 +        out->a.target = joystick->hwdata->key_map[BTN_SOUTH];
   13.73 +    }
   13.74 +
   13.75 +    if (joystick->hwdata->has_key[BTN_EAST]) {
   13.76 +        out->b.kind = EMappingKind_Button;
   13.77 +        out->b.target = joystick->hwdata->key_map[BTN_EAST];
   13.78 +    }
   13.79 +
   13.80 +    if (joystick->hwdata->has_key[BTN_NORTH]) {
   13.81 +        out->y.kind = EMappingKind_Button;
   13.82 +        out->y.target = joystick->hwdata->key_map[BTN_NORTH];
   13.83 +    }
   13.84 +
   13.85 +    if (joystick->hwdata->has_key[BTN_WEST]) {
   13.86 +        out->x.kind = EMappingKind_Button;
   13.87 +        out->x.target = joystick->hwdata->key_map[BTN_WEST];
   13.88 +    }
   13.89 +
   13.90 +    if (joystick->hwdata->has_key[BTN_SELECT]) {
   13.91 +        out->back.kind = EMappingKind_Button;
   13.92 +        out->back.target = joystick->hwdata->key_map[BTN_SELECT];
   13.93 +    }
   13.94 +
   13.95 +    if (joystick->hwdata->has_key[BTN_START]) {
   13.96 +        out->start.kind = EMappingKind_Button;
   13.97 +        out->start.target = joystick->hwdata->key_map[BTN_START];
   13.98 +    }
   13.99 +
  13.100 +    if (joystick->hwdata->has_key[BTN_THUMBL]) {
  13.101 +        out->leftstick.kind = EMappingKind_Button;
  13.102 +        out->leftstick.target = joystick->hwdata->key_map[BTN_THUMBL];
  13.103 +    }
  13.104 +
  13.105 +    if (joystick->hwdata->has_key[BTN_THUMBR]) {
  13.106 +        out->rightstick.kind = EMappingKind_Button;
  13.107 +        out->rightstick.target = joystick->hwdata->key_map[BTN_THUMBR];
  13.108 +    }
  13.109 +
  13.110 +    if (joystick->hwdata->has_key[BTN_MODE]) {
  13.111 +        out->guide.kind = EMappingKind_Button;
  13.112 +        out->guide.target = joystick->hwdata->key_map[BTN_MODE];
  13.113 +    }
  13.114 +
  13.115 +    /*
  13.116 +       According to the specs the D-Pad, the shoulder buttons and the triggers
  13.117 +       can be digital, or analog, or both at the same time.
  13.118 +     */
  13.119 +
  13.120 +    /* Prefer digital shoulder buttons, but settle for analog if missing. */
  13.121 +    if (joystick->hwdata->has_key[BTN_TL]) {
  13.122 +        out->leftshoulder.kind = EMappingKind_Button;
  13.123 +        out->leftshoulder.target = joystick->hwdata->key_map[BTN_TL];
  13.124 +    }
  13.125 +
  13.126 +    if (joystick->hwdata->has_key[BTN_TR]) {
  13.127 +        out->rightshoulder.kind = EMappingKind_Button;
  13.128 +        out->rightshoulder.target = joystick->hwdata->key_map[BTN_TR];
  13.129 +    }
  13.130 +
  13.131 +    if (joystick->hwdata->has_hat[1] && /* Check if ABS_HAT1{X, Y} is available. */
  13.132 +       (!joystick->hwdata->has_key[BTN_TL] || !joystick->hwdata->has_key[BTN_TR])) {
  13.133 +        int hat = joystick->hwdata->hats_indices[1] << 4;
  13.134 +        out->leftshoulder.kind = EMappingKind_Hat;
  13.135 +        out->rightshoulder.kind = EMappingKind_Hat;
  13.136 +        out->leftshoulder.target = hat | 0x4;
  13.137 +        out->rightshoulder.target = hat | 0x2;
  13.138 +    }
  13.139 +
  13.140 +    /* Prefer analog triggers, but settle for digital if missing. */
  13.141 +    if (joystick->hwdata->has_hat[2]) { /* Check if ABS_HAT2{X,Y} is available. */
  13.142 +        int hat = joystick->hwdata->hats_indices[2] << 4;
  13.143 +        out->lefttrigger.kind = EMappingKind_Hat;
  13.144 +        out->righttrigger.kind = EMappingKind_Hat;
  13.145 +        out->lefttrigger.target = hat | 0x4;
  13.146 +        out->righttrigger.target = hat | 0x2;
  13.147 +    } else {
  13.148 +        if (joystick->hwdata->has_key[BTN_TL2]) {
  13.149 +            out->lefttrigger.kind = EMappingKind_Button;
  13.150 +            out->lefttrigger.target = joystick->hwdata->key_map[BTN_TL2];
  13.151 +        }
  13.152 +
  13.153 +        if (joystick->hwdata->has_key[BTN_TR2]) {
  13.154 +            out->righttrigger.kind = EMappingKind_Button;
  13.155 +            out->righttrigger.target = joystick->hwdata->key_map[BTN_TR2];
  13.156 +        }
  13.157 +    }
  13.158 +
  13.159 +    /* Prefer digital D-Pad, but settle for analog if missing. */
  13.160 +    if (joystick->hwdata->has_key[BTN_DPAD_UP]) {
  13.161 +        out->dpup.kind = EMappingKind_Button;
  13.162 +        out->dpup.target = joystick->hwdata->key_map[BTN_DPAD_UP];
  13.163 +    }
  13.164 +
  13.165 +    if (joystick->hwdata->has_key[BTN_DPAD_DOWN]) {
  13.166 +        out->dpdown.kind = EMappingKind_Button;
  13.167 +        out->dpdown.target = joystick->hwdata->key_map[BTN_DPAD_DOWN];
  13.168 +    }
  13.169 +
  13.170 +    if (joystick->hwdata->has_key[BTN_DPAD_LEFT]) {
  13.171 +        out->dpleft.kind = EMappingKind_Button;
  13.172 +        out->dpleft.target = joystick->hwdata->key_map[BTN_DPAD_LEFT];
  13.173 +    }
  13.174 +
  13.175 +    if (joystick->hwdata->has_key[BTN_DPAD_RIGHT]) {
  13.176 +        out->dpright.kind = EMappingKind_Button;
  13.177 +        out->dpright.target = joystick->hwdata->key_map[BTN_DPAD_RIGHT];
  13.178 +    }
  13.179 +
  13.180 +    if (joystick->hwdata->has_hat[0] && /* Check if ABS_HAT0{X,Y} is available. */
  13.181 +       (!joystick->hwdata->has_key[BTN_DPAD_LEFT] || !joystick->hwdata->has_key[BTN_DPAD_RIGHT] ||
  13.182 +        !joystick->hwdata->has_key[BTN_DPAD_UP] || !joystick->hwdata->has_key[BTN_DPAD_DOWN])) {
  13.183 +       int hat = joystick->hwdata->hats_indices[0] << 4;
  13.184 +       out->dpleft.kind = EMappingKind_Hat;
  13.185 +       out->dpright.kind = EMappingKind_Hat;
  13.186 +       out->dpup.kind = EMappingKind_Hat;
  13.187 +       out->dpdown.kind = EMappingKind_Hat;
  13.188 +       out->dpleft.target = hat | 0x8;
  13.189 +       out->dpright.target = hat | 0x2;
  13.190 +       out->dpup.target = hat | 0x1;
  13.191 +       out->dpdown.target = hat | 0x4;
  13.192 +    }
  13.193 +
  13.194 +    if (joystick->hwdata->has_abs[ABS_X] && joystick->hwdata->has_abs[ABS_Y]) {
  13.195 +        out->leftx.kind = EMappingKind_Axis;
  13.196 +        out->lefty.kind = EMappingKind_Axis;
  13.197 +        out->leftx.target = joystick->hwdata->abs_map[ABS_X];
  13.198 +        out->lefty.target = joystick->hwdata->abs_map[ABS_Y];
  13.199 +    }
  13.200 +
  13.201 +    if (joystick->hwdata->has_abs[ABS_RX] && joystick->hwdata->has_abs[ABS_RY]) {
  13.202 +        out->rightx.kind = EMappingKind_Axis;
  13.203 +        out->righty.kind = EMappingKind_Axis;
  13.204 +        out->rightx.target = joystick->hwdata->abs_map[ABS_RX];
  13.205 +        out->righty.target = joystick->hwdata->abs_map[ABS_RY];
  13.206 +    }
  13.207 +
  13.208 +    LINUX_JoystickClose(joystick);
  13.209 +    SDL_free(joystick);
  13.210 +
  13.211 +    return SDL_TRUE;
  13.212 +}
  13.213 +
  13.214  SDL_JoystickDriver SDL_LINUX_JoystickDriver =
  13.215  {
  13.216      LINUX_JoystickInit,
  13.217 @@ -1115,6 +1294,7 @@
  13.218      LINUX_JoystickUpdate,
  13.219      LINUX_JoystickClose,
  13.220      LINUX_JoystickQuit,
  13.221 +    LINUX_JoystickGetGamepadMapping
  13.222  };
  13.223  
  13.224  #endif /* SDL_JOYSTICK_LINUX */
    14.1 --- a/src/joystick/linux/SDL_sysjoystick_c.h	Fri May 29 13:05:37 2020 +0100
    14.2 +++ b/src/joystick/linux/SDL_sysjoystick_c.h	Fri May 29 13:37:21 2020 -0700
    14.3 @@ -53,6 +53,9 @@
    14.4      /* Support for the Linux 2.4 unified input interface */
    14.5      Uint8 key_map[KEY_MAX];
    14.6      Uint8 abs_map[ABS_MAX];
    14.7 +    SDL_bool has_key[KEY_MAX];
    14.8 +    SDL_bool has_abs[ABS_MAX];
    14.9 +
   14.10      struct axis_correct
   14.11      {
   14.12          int used;
   14.13 @@ -65,6 +68,7 @@
   14.14      SDL_bool m_bSteamController;
   14.15      /* 4 = (ABS_HAT3X-ABS_HAT0X)/2 (see input-event-codes.h in kernel) */
   14.16      int hats_indices[4];
   14.17 +    SDL_bool has_hat[4];
   14.18  
   14.19      /* Set when gamepad is pending removal due to ENODEV read error */
   14.20      SDL_bool gone;
    15.1 --- a/src/joystick/virtual/SDL_virtualjoystick.c	Fri May 29 13:05:37 2020 +0100
    15.2 +++ b/src/joystick/virtual/SDL_virtualjoystick.c	Fri May 29 13:37:21 2020 -0700
    15.3 @@ -391,6 +391,11 @@
    15.4      }
    15.5  }
    15.6  
    15.7 +static SDL_bool
    15.8 +VIRTUAL_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
    15.9 +{
   15.10 +    return SDL_FALSE;
   15.11 +}
   15.12  
   15.13  SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver =
   15.14  {
   15.15 @@ -407,6 +412,7 @@
   15.16      VIRTUAL_JoystickUpdate,
   15.17      VIRTUAL_JoystickClose,
   15.18      VIRTUAL_JoystickQuit,
   15.19 +    VIRTUAL_JoystickGetGamepadMapping
   15.20  };
   15.21  
   15.22  #endif /* SDL_JOYSTICK_VIRTUAL || SDL_JOYSTICK_DISABLED */
    16.1 --- a/src/joystick/windows/SDL_rawinputjoystick.c	Fri May 29 13:05:37 2020 +0100
    16.2 +++ b/src/joystick/windows/SDL_rawinputjoystick.c	Fri May 29 13:37:21 2020 -0700
    16.3 @@ -725,6 +725,11 @@
    16.4      SDL_RAWINPUT_inited = SDL_FALSE;
    16.5  }
    16.6  
    16.7 +static SDL_bool
    16.8 +RAWINPUT_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
    16.9 +{
   16.10 +    return SDL_FALSE;
   16.11 +}
   16.12  
   16.13  SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver =
   16.14  {
   16.15 @@ -741,6 +746,7 @@
   16.16      RAWINPUT_JoystickUpdate,
   16.17      RAWINPUT_JoystickClose,
   16.18      RAWINPUT_JoystickQuit,
   16.19 +    RAWINPUT_JoystickGetGamepadMapping
   16.20  };
   16.21  
   16.22  #endif /* SDL_JOYSTICK_RAWINPUT */
    17.1 --- a/src/joystick/windows/SDL_windows_gaming_input.c	Fri May 29 13:05:37 2020 +0100
    17.2 +++ b/src/joystick/windows/SDL_windows_gaming_input.c	Fri May 29 13:37:21 2020 -0700
    17.3 @@ -699,6 +699,12 @@
    17.4      WIN_CoUninitialize();
    17.5  }
    17.6  
    17.7 +static SDL_bool
    17.8 +WGI_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
    17.9 +{
   17.10 +    return SDL_FALSE;
   17.11 +}
   17.12 +
   17.13  SDL_JoystickDriver SDL_WGI_JoystickDriver =
   17.14  {
   17.15      WGI_JoystickInit,
   17.16 @@ -714,6 +720,7 @@
   17.17      WGI_JoystickUpdate,
   17.18      WGI_JoystickClose,
   17.19      WGI_JoystickQuit,
   17.20 +    WGI_JoystickGetGamepadMapping
   17.21  };
   17.22  
   17.23  #endif /* SDL_JOYSTICK_WGI */
    18.1 --- a/src/joystick/windows/SDL_windowsjoystick.c	Fri May 29 13:05:37 2020 +0100
    18.2 +++ b/src/joystick/windows/SDL_windowsjoystick.c	Fri May 29 13:37:21 2020 -0700
    18.3 @@ -555,6 +555,12 @@
    18.4      s_bDeviceRemoved = SDL_FALSE;
    18.5  }
    18.6  
    18.7 +static SDL_bool
    18.8 +WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
    18.9 +{
   18.10 +    return SDL_FALSE;
   18.11 +}
   18.12 +
   18.13  SDL_JoystickDriver SDL_WINDOWS_JoystickDriver =
   18.14  {
   18.15      WINDOWS_JoystickInit,
   18.16 @@ -570,6 +576,7 @@
   18.17      WINDOWS_JoystickUpdate,
   18.18      WINDOWS_JoystickClose,
   18.19      WINDOWS_JoystickQuit,
   18.20 +    WINDOWS_JoystickGetGamepadMapping
   18.21  };
   18.22  
   18.23  #endif /* SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT */