Path: blob/master/thirdparty/sdl/joystick/SDL_gamepad.c
21958 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122// This is the gamepad API for Simple DirectMedia Layer2324#include "SDL_sysjoystick.h"25#include "SDL_joystick_c.h"26#include "SDL_steam_virtual_gamepad.h"27#include "SDL_gamepad_c.h"28#include "SDL_gamepad_db.h"29#include "controller_type.h"30#include "usb_ids.h"31#include "hidapi/SDL_hidapi_nintendo.h"32#include "../events/SDL_events_c.h"3334#ifdef SDL_PLATFORM_WIN3235#include "../core/windows/SDL_windows.h"36#endif373839// Many gamepads turn the center button into an instantaneous button press40#define SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS 2504142#define SDL_GAMEPAD_CRC_FIELD "crc:"43#define SDL_GAMEPAD_CRC_FIELD_SIZE 4 // hard-coded for speed44#define SDL_GAMEPAD_TYPE_FIELD "type:"45#define SDL_GAMEPAD_TYPE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_TYPE_FIELD)46#define SDL_GAMEPAD_FACE_FIELD "face:"47#define SDL_GAMEPAD_FACE_FIELD_SIZE 5 // hard-coded for speed48#define SDL_GAMEPAD_PLATFORM_FIELD "platform:"49#define SDL_GAMEPAD_PLATFORM_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_PLATFORM_FIELD)50#define SDL_GAMEPAD_HINT_FIELD "hint:"51#define SDL_GAMEPAD_HINT_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_HINT_FIELD)52#define SDL_GAMEPAD_SDKGE_FIELD "sdk>=:"53#define SDL_GAMEPAD_SDKGE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKGE_FIELD)54#define SDL_GAMEPAD_SDKLE_FIELD "sdk<=:"55#define SDL_GAMEPAD_SDKLE_FIELD_SIZE SDL_strlen(SDL_GAMEPAD_SDKLE_FIELD)5657static bool SDL_gamepads_initialized;58static SDL_Gamepad *SDL_gamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL;5960// The face button style of a gamepad61typedef enum62{63SDL_GAMEPAD_FACE_STYLE_UNKNOWN,64SDL_GAMEPAD_FACE_STYLE_ABXY,65SDL_GAMEPAD_FACE_STYLE_BAYX,66SDL_GAMEPAD_FACE_STYLE_SONY,67} SDL_GamepadFaceStyle;6869// our hard coded list of mapping support70typedef enum71{72SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT,73SDL_GAMEPAD_MAPPING_PRIORITY_API,74SDL_GAMEPAD_MAPPING_PRIORITY_USER,75} SDL_GamepadMappingPriority;7677#define _guarded SDL_GUARDED_BY(SDL_joystick_lock)7879typedef struct GamepadMapping_t80{81SDL_GUID guid _guarded;82char *name _guarded;83char *mapping _guarded;84SDL_GamepadMappingPriority priority _guarded;85struct GamepadMapping_t *next _guarded;86} GamepadMapping_t;8788typedef struct89{90int refcount _guarded;91SDL_JoystickID *joysticks _guarded;92GamepadMapping_t **joystick_mappings _guarded;9394int num_changed_mappings _guarded;95GamepadMapping_t **changed_mappings _guarded;9697} MappingChangeTracker;9899#undef _guarded100101static SDL_GUID s_zeroGUID;102static GamepadMapping_t *s_pSupportedGamepads SDL_GUARDED_BY(SDL_joystick_lock) = NULL;103static GamepadMapping_t *s_pDefaultMapping SDL_GUARDED_BY(SDL_joystick_lock) = NULL;104static GamepadMapping_t *s_pXInputMapping SDL_GUARDED_BY(SDL_joystick_lock) = NULL;105static MappingChangeTracker *s_mappingChangeTracker SDL_GUARDED_BY(SDL_joystick_lock) = NULL;106static SDL_HashTable *s_gamepadInstanceIDs SDL_GUARDED_BY(SDL_joystick_lock) = NULL;107108#define _guarded SDL_GUARDED_BY(SDL_joystick_lock)109110// The SDL gamepad structure111struct SDL_Gamepad112{113SDL_Joystick *joystick _guarded; // underlying joystick device114int ref_count _guarded;115116const char *name _guarded;117SDL_GamepadType type _guarded;118SDL_GamepadFaceStyle face_style _guarded;119GamepadMapping_t *mapping _guarded;120int num_bindings _guarded;121SDL_GamepadBinding *bindings _guarded;122SDL_GamepadBinding **last_match_axis _guarded;123Uint8 *last_hat_mask _guarded;124Uint64 guide_button_down _guarded;125126struct SDL_Gamepad *next _guarded; // pointer to next gamepad we have allocated127};128129#undef _guarded130131#define CHECK_GAMEPAD_MAGIC(gamepad, result) \132if (!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD) || \133!SDL_IsJoystickValid(gamepad->joystick)) { \134SDL_InvalidParamError("gamepad"); \135SDL_UnlockJoysticks(); \136return result; \137}138139static SDL_vidpid_list SDL_allowed_gamepads = {140SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, 0, 0, NULL,141NULL, 0, 0, NULL,1420, NULL,143false144};145static SDL_vidpid_list SDL_ignored_gamepads = {146SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, 0, 0, NULL,147NULL, 0, 0, NULL,1480, NULL,149false150};151152/*153List of words in gamepad names that indicate that the gamepad should not be detected.154See also `initial_blacklist_devices` in SDL_joystick.c155*/156157enum SDL_GamepadBlacklistWordsPosition {158GAMEPAD_BLACKLIST_BEGIN,159GAMEPAD_BLACKLIST_END,160GAMEPAD_BLACKLIST_ANYWHERE,161};162163struct SDL_GamepadBlacklistWords {164const char* str;165enum SDL_GamepadBlacklistWordsPosition pos;166};167168static const struct SDL_GamepadBlacklistWords SDL_gamepad_blacklist_words[] = {169#ifdef SDL_PLATFORM_LINUX170{" Motion Sensors", GAMEPAD_BLACKLIST_END}, // Don't treat the PS3 and PS4 motion controls as a separate gamepad171{" IMU", GAMEPAD_BLACKLIST_END}, // Don't treat the Nintendo IMU as a separate gamepad172{" Touchpad", GAMEPAD_BLACKLIST_END}, // "Sony Interactive Entertainment DualSense Wireless Controller Touchpad"173174// Don't treat the Wii extension controls as a separate gamepad175{" Accelerometer", GAMEPAD_BLACKLIST_END},176{" IR", GAMEPAD_BLACKLIST_END},177{" Motion Plus", GAMEPAD_BLACKLIST_END},178{" Nunchuk", GAMEPAD_BLACKLIST_END},179#endif180181// The Google Pixel fingerprint sensor, as well as other fingerprint sensors, reports itself as a joystick182{"uinput-", GAMEPAD_BLACKLIST_BEGIN},183184{"Synaptics ", GAMEPAD_BLACKLIST_ANYWHERE}, // "Synaptics TM2768-001", "SynPS/2 Synaptics TouchPad"185{"Trackpad", GAMEPAD_BLACKLIST_ANYWHERE},186{"Clickpad", GAMEPAD_BLACKLIST_ANYWHERE},187// "PG-90215 Keyboard", "Usb Keyboard Usb Keyboard Consumer Control", "Framework Laptop 16 Keyboard Module - ISO System Control"188{" Keyboard", GAMEPAD_BLACKLIST_ANYWHERE},189{" Laptop ", GAMEPAD_BLACKLIST_ANYWHERE}, // "Framework Laptop 16 Numpad Module System Control"190{"Mouse ", GAMEPAD_BLACKLIST_BEGIN}, // "Mouse passthrough"191{" Pen", GAMEPAD_BLACKLIST_END}, // "Wacom One by Wacom S Pen"192{" Finger", GAMEPAD_BLACKLIST_END}, // "Wacom HID 495F Finger"193{" LED ", GAMEPAD_BLACKLIST_ANYWHERE}, // "ASRock LED Controller"194{" Thelio ", GAMEPAD_BLACKLIST_ANYWHERE}, // "System76 Thelio Io 2"195};196197static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_GUID jGUID, const char *mappingString, bool *existing, SDL_GamepadMappingPriority priority);198static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping);199static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, bool create_mapping);200static void SDL_SendGamepadAxis(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadAxis axis, Sint16 value);201static void SDL_SendGamepadButton(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadButton button, bool down);202203static bool HasSameOutput(SDL_GamepadBinding *a, SDL_GamepadBinding *b)204{205if (a->output_type != b->output_type) {206return false;207}208209if (a->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {210return a->output.axis.axis == b->output.axis.axis;211} else {212return a->output.button == b->output.button;213}214}215216static void ResetOutput(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadBinding *bind)217{218if (bind->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {219SDL_SendGamepadAxis(timestamp, gamepad, bind->output.axis.axis, 0);220} else {221SDL_SendGamepadButton(timestamp, gamepad, bind->output.button, false);222}223}224225static void HandleJoystickAxis(Uint64 timestamp, SDL_Gamepad *gamepad, int axis, int value)226{227int i;228SDL_GamepadBinding *last_match;229SDL_GamepadBinding *match = NULL;230231SDL_AssertJoysticksLocked();232233last_match = gamepad->last_match_axis[axis];234for (i = 0; i < gamepad->num_bindings; ++i) {235SDL_GamepadBinding *binding = &gamepad->bindings[i];236if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS &&237axis == binding->input.axis.axis) {238if (binding->input.axis.axis_min < binding->input.axis.axis_max) {239if (value >= binding->input.axis.axis_min &&240value <= binding->input.axis.axis_max) {241match = binding;242break;243}244} else {245if (value >= binding->input.axis.axis_max &&246value <= binding->input.axis.axis_min) {247match = binding;248break;249}250}251}252}253254if (last_match && (!match || !HasSameOutput(last_match, match))) {255// Clear the last input that this axis generated256ResetOutput(timestamp, gamepad, last_match);257}258259if (match) {260if (match->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {261if (match->input.axis.axis_min != match->output.axis.axis_min || match->input.axis.axis_max != match->output.axis.axis_max) {262float normalized_value = (float)(value - match->input.axis.axis_min) / (match->input.axis.axis_max - match->input.axis.axis_min);263value = match->output.axis.axis_min + (int)(normalized_value * (match->output.axis.axis_max - match->output.axis.axis_min));264}265SDL_SendGamepadAxis(timestamp, gamepad, match->output.axis.axis, (Sint16)value);266} else {267bool down;268int threshold = match->input.axis.axis_min + (match->input.axis.axis_max - match->input.axis.axis_min) / 2;269if (match->input.axis.axis_max < match->input.axis.axis_min) {270down = (value <= threshold);271} else {272down = (value >= threshold);273}274SDL_SendGamepadButton(timestamp, gamepad, match->output.button, down);275}276}277gamepad->last_match_axis[axis] = match;278}279280static void HandleJoystickButton(Uint64 timestamp, SDL_Gamepad *gamepad, int button, bool down)281{282int i;283284SDL_AssertJoysticksLocked();285286for (i = 0; i < gamepad->num_bindings; ++i) {287SDL_GamepadBinding *binding = &gamepad->bindings[i];288if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&289button == binding->input.button) {290if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {291int value = down ? binding->output.axis.axis_max : binding->output.axis.axis_min;292SDL_SendGamepadAxis(timestamp, gamepad, binding->output.axis.axis, (Sint16)value);293} else {294SDL_SendGamepadButton(timestamp, gamepad, binding->output.button, down);295}296break;297}298}299}300301static void HandleJoystickHat(Uint64 timestamp, SDL_Gamepad *gamepad, int hat, Uint8 value)302{303int i;304Uint8 last_mask, changed_mask;305306SDL_AssertJoysticksLocked();307308last_mask = gamepad->last_hat_mask[hat];309changed_mask = (last_mask ^ value);310for (i = 0; i < gamepad->num_bindings; ++i) {311SDL_GamepadBinding *binding = &gamepad->bindings[i];312if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT && hat == binding->input.hat.hat) {313if ((changed_mask & binding->input.hat.hat_mask) != 0) {314if (value & binding->input.hat.hat_mask) {315if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) {316SDL_SendGamepadAxis(timestamp, gamepad, binding->output.axis.axis, (Sint16)binding->output.axis.axis_max);317} else {318SDL_SendGamepadButton(timestamp, gamepad, binding->output.button, true);319}320} else {321ResetOutput(timestamp, gamepad, binding);322}323}324}325}326gamepad->last_hat_mask[hat] = value;327}328329/* The joystick layer will _also_ send events to recenter before disconnect,330but it has to make (sometimes incorrect) guesses at what being "centered"331is. The gamepad layer, however, can set a definite logical idle332position, so set them all here. If we happened to already be at the333center thanks to the joystick layer or idle hands, this won't generate334duplicate events. */335static void RecenterGamepad(SDL_Gamepad *gamepad)336{337int i;338Uint64 timestamp = SDL_GetTicksNS();339340for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {341SDL_GamepadButton button = (SDL_GamepadButton)i;342if (SDL_GetGamepadButton(gamepad, button)) {343SDL_SendGamepadButton(timestamp, gamepad, button, false);344}345}346347for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) {348SDL_GamepadAxis axis = (SDL_GamepadAxis)i;349if (SDL_GetGamepadAxis(gamepad, axis) != 0) {350SDL_SendGamepadAxis(timestamp, gamepad, axis, 0);351}352}353}354355void SDL_PrivateGamepadAdded(SDL_JoystickID instance_id)356{357SDL_Event event;358359if (!SDL_gamepads_initialized || SDL_IsJoystickBeingAdded()) {360return;361}362363event.type = SDL_EVENT_GAMEPAD_ADDED;364event.common.timestamp = 0;365event.gdevice.which = instance_id;366SDL_PushEvent(&event);367}368369void SDL_PrivateGamepadRemoved(SDL_JoystickID instance_id)370{371SDL_Event event;372SDL_Gamepad *gamepad;373374SDL_AssertJoysticksLocked();375376if (!SDL_gamepads_initialized) {377return;378}379380for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {381if (gamepad->joystick->instance_id == instance_id) {382RecenterGamepad(gamepad);383break;384}385}386387event.type = SDL_EVENT_GAMEPAD_REMOVED;388event.common.timestamp = 0;389event.gdevice.which = instance_id;390SDL_PushEvent(&event);391}392393static void SDL_PrivateGamepadRemapped(SDL_JoystickID instance_id)394{395SDL_Event event;396397if (!SDL_gamepads_initialized || SDL_IsJoystickBeingAdded()) {398return;399}400401event.type = SDL_EVENT_GAMEPAD_REMAPPED;402event.common.timestamp = 0;403event.gdevice.which = instance_id;404SDL_PushEvent(&event);405}406407/*408* Event filter to fire gamepad events from joystick ones409*/410static bool SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event)411{412SDL_Gamepad *gamepad;413414switch (event->type) {415case SDL_EVENT_JOYSTICK_AXIS_MOTION:416{417SDL_AssertJoysticksLocked();418419for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {420if (gamepad->joystick->instance_id == event->jaxis.which) {421HandleJoystickAxis(event->common.timestamp, gamepad, event->jaxis.axis, event->jaxis.value);422break;423}424}425} break;426case SDL_EVENT_JOYSTICK_BUTTON_DOWN:427case SDL_EVENT_JOYSTICK_BUTTON_UP:428{429SDL_AssertJoysticksLocked();430431for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {432if (gamepad->joystick->instance_id == event->jbutton.which) {433HandleJoystickButton(event->common.timestamp, gamepad, event->jbutton.button, event->jbutton.down);434break;435}436}437} break;438case SDL_EVENT_JOYSTICK_HAT_MOTION:439{440SDL_AssertJoysticksLocked();441442for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {443if (gamepad->joystick->instance_id == event->jhat.which) {444HandleJoystickHat(event->common.timestamp, gamepad, event->jhat.hat, event->jhat.value);445break;446}447}448} break;449case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:450{451SDL_AssertJoysticksLocked();452453if (SDL_EventEnabled(SDL_EVENT_GAMEPAD_UPDATE_COMPLETE)) {454for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {455if (gamepad->joystick->instance_id == event->jdevice.which) {456SDL_Event deviceevent;457458deviceevent.type = SDL_EVENT_GAMEPAD_UPDATE_COMPLETE;459deviceevent.common.timestamp = event->jdevice.timestamp;460deviceevent.gdevice.which = event->jdevice.which;461SDL_PushEvent(&deviceevent);462break;463}464}465}466} break;467default:468break;469}470471return true;472}473474/* SDL defines sensor orientation relative to the device natural475orientation, so when it's changed orientation to be used as a476gamepad, change the sensor orientation to match.477*/478static void AdjustSensorOrientation(const SDL_Joystick *joystick, const float *src, float *dst)479{480unsigned int i, j;481482SDL_AssertJoysticksLocked();483484for (i = 0; i < 3; ++i) {485dst[i] = 0.0f;486for (j = 0; j < 3; ++j) {487dst[i] += joystick->sensor_transform[i][j] * src[j];488}489}490}491492/*493* Event filter to fire gamepad sensor events from system sensor events494*495* We don't use SDL_GamepadEventWatcher() for this because we want to496* deliver gamepad sensor events when system sensor events are disabled,497* and we also need to avoid a potential deadlock where joystick event498* delivery locks the joysticks and then the event queue, but sensor499* event delivery would lock the event queue and then from within the500* event watcher function lock the joysticks.501*/502void SDL_GamepadSensorWatcher(Uint64 timestamp, SDL_SensorID sensor, Uint64 sensor_timestamp, float *data, int num_values)503{504SDL_Gamepad *gamepad;505506SDL_LockJoysticks();507for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {508if (gamepad->joystick->accel && gamepad->joystick->accel_sensor == sensor) {509float gamepad_data[3];510AdjustSensorOrientation(gamepad->joystick, data, gamepad_data);511SDL_SendJoystickSensor(timestamp, gamepad->joystick, SDL_SENSOR_ACCEL, sensor_timestamp, gamepad_data, SDL_arraysize(gamepad_data));512}513if (gamepad->joystick->gyro && gamepad->joystick->gyro_sensor == sensor) {514float gamepad_data[3];515AdjustSensorOrientation(gamepad->joystick, data, gamepad_data);516SDL_SendJoystickSensor(timestamp, gamepad->joystick, SDL_SENSOR_GYRO, sensor_timestamp, gamepad_data, SDL_arraysize(gamepad_data));517}518}519SDL_UnlockJoysticks();520}521522static void PushMappingChangeTracking(void)523{524MappingChangeTracker *tracker;525int i, num_joysticks;526527SDL_AssertJoysticksLocked();528529if (s_mappingChangeTracker) {530++s_mappingChangeTracker->refcount;531return;532}533s_mappingChangeTracker = (MappingChangeTracker *)SDL_calloc(1, sizeof(*tracker));534s_mappingChangeTracker->refcount = 1;535536// Save the list of joysticks and associated mappings537tracker = s_mappingChangeTracker;538tracker->joysticks = SDL_GetJoysticks(&num_joysticks);539if (!tracker->joysticks) {540return;541}542if (num_joysticks == 0) {543return;544}545tracker->joystick_mappings = (GamepadMapping_t **)SDL_malloc(num_joysticks * sizeof(*tracker->joystick_mappings));546if (!tracker->joystick_mappings) {547return;548}549for (i = 0; i < num_joysticks; ++i) {550tracker->joystick_mappings[i] = SDL_PrivateGetGamepadMapping(tracker->joysticks[i], false);551}552}553554static void AddMappingChangeTracking(GamepadMapping_t *mapping)555{556MappingChangeTracker *tracker;557int num_mappings;558GamepadMapping_t **new_mappings;559560SDL_AssertJoysticksLocked();561562SDL_assert(s_mappingChangeTracker != NULL);563tracker = s_mappingChangeTracker;564num_mappings = tracker->num_changed_mappings;565new_mappings = (GamepadMapping_t **)SDL_realloc(tracker->changed_mappings, (num_mappings + 1) * sizeof(*new_mappings));566if (new_mappings) {567tracker->changed_mappings = new_mappings;568tracker->changed_mappings[num_mappings] = mapping;569tracker->num_changed_mappings = (num_mappings + 1);570}571}572573static bool HasMappingChangeTracking(MappingChangeTracker *tracker, GamepadMapping_t *mapping)574{575int i;576577SDL_AssertJoysticksLocked();578579for (i = 0; i < tracker->num_changed_mappings; ++i) {580if (tracker->changed_mappings[i] == mapping) {581return true;582}583}584return false;585}586587static void PopMappingChangeTracking(void)588{589int i;590MappingChangeTracker *tracker;591592SDL_AssertJoysticksLocked();593594SDL_assert(s_mappingChangeTracker != NULL);595tracker = s_mappingChangeTracker;596--tracker->refcount;597if (tracker->refcount > 0) {598return;599}600s_mappingChangeTracker = NULL;601602// Now check to see what gamepads changed because of the mapping changes603if (tracker->joysticks && tracker->joystick_mappings) {604for (i = 0; tracker->joysticks[i]; ++i) {605// Looking up the new mapping might create one and associate it with the gamepad (and generate events)606SDL_JoystickID joystick = tracker->joysticks[i];607SDL_Gamepad *gamepad = SDL_GetGamepadFromID(joystick);608GamepadMapping_t *new_mapping = SDL_PrivateGetGamepadMapping(joystick, false);609GamepadMapping_t *old_mapping = gamepad ? gamepad->mapping : tracker->joystick_mappings[i];610611if (new_mapping && !old_mapping) {612SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)true, true);613SDL_PrivateGamepadAdded(joystick);614} else if (old_mapping && !new_mapping) {615SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)joystick, (const void *)false, true);616SDL_PrivateGamepadRemoved(joystick);617} else if (old_mapping != new_mapping || HasMappingChangeTracking(tracker, new_mapping)) {618if (gamepad) {619SDL_PrivateLoadButtonMapping(gamepad, new_mapping);620}621SDL_PrivateGamepadRemapped(joystick);622}623}624}625626SDL_free(tracker->joysticks);627SDL_free(tracker->joystick_mappings);628SDL_free(tracker->changed_mappings);629SDL_free(tracker);630}631632#ifdef SDL_PLATFORM_ANDROID633/*634* Helper function to guess at a mapping based on the elements reported for this gamepad635*/636static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_GUID guid)637{638const int face_button_mask = ((1 << SDL_GAMEPAD_BUTTON_SOUTH) |639(1 << SDL_GAMEPAD_BUTTON_EAST) |640(1 << SDL_GAMEPAD_BUTTON_WEST) |641(1 << SDL_GAMEPAD_BUTTON_NORTH));642bool existing;643char mapping_string[1024];644int button_mask;645int axis_mask;646647button_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 4]));648axis_mask = SDL_Swap16LE(*(Uint16 *)(&guid.data[sizeof(guid.data) - 2]));649if (!button_mask && !axis_mask) {650// Accelerometer, shouldn't have a gamepad mapping651return NULL;652}653if (!(button_mask & face_button_mask)) {654// We don't know what buttons or axes are supported, don't make up a mapping655return NULL;656}657658SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));659660if (button_mask & (1 << SDL_GAMEPAD_BUTTON_SOUTH)) {661SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string));662}663if (button_mask & (1 << SDL_GAMEPAD_BUTTON_EAST)) {664SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string));665} else if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {666// Use the back button as "B" for easy UI navigation with TV remotes667SDL_strlcat(mapping_string, "b:b4,", sizeof(mapping_string));668button_mask &= ~(1 << SDL_GAMEPAD_BUTTON_BACK);669}670if (button_mask & (1 << SDL_GAMEPAD_BUTTON_WEST)) {671SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string));672}673if (button_mask & (1 << SDL_GAMEPAD_BUTTON_NORTH)) {674SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string));675}676if (button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {677SDL_strlcat(mapping_string, "back:b4,", sizeof(mapping_string));678}679if (button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE)) {680// The guide button generally isn't functional (or acts as a home button) on most Android gamepads before Android 11681if (SDL_GetAndroidSDKVersion() >= 30 /* Android 11 */) {682SDL_strlcat(mapping_string, "guide:b5,", sizeof(mapping_string));683}684}685if (button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {686SDL_strlcat(mapping_string, "start:b6,", sizeof(mapping_string));687}688if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) {689SDL_strlcat(mapping_string, "leftstick:b7,", sizeof(mapping_string));690}691if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) {692SDL_strlcat(mapping_string, "rightstick:b8,", sizeof(mapping_string));693}694if (button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {695SDL_strlcat(mapping_string, "leftshoulder:b9,", sizeof(mapping_string));696}697if (button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {698SDL_strlcat(mapping_string, "rightshoulder:b10,", sizeof(mapping_string));699}700if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_UP)) {701SDL_strlcat(mapping_string, "dpup:b11,", sizeof(mapping_string));702}703if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_DOWN)) {704SDL_strlcat(mapping_string, "dpdown:b12,", sizeof(mapping_string));705}706if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_LEFT)) {707SDL_strlcat(mapping_string, "dpleft:b13,", sizeof(mapping_string));708}709if (button_mask & (1 << SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) {710SDL_strlcat(mapping_string, "dpright:b14,", sizeof(mapping_string));711}712if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTX)) {713SDL_strlcat(mapping_string, "leftx:a0,", sizeof(mapping_string));714}715if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFTY)) {716SDL_strlcat(mapping_string, "lefty:a1,", sizeof(mapping_string));717}718if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTX)) {719SDL_strlcat(mapping_string, "rightx:a2,", sizeof(mapping_string));720}721if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHTY)) {722SDL_strlcat(mapping_string, "righty:a3,", sizeof(mapping_string));723}724if (axis_mask & (1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) {725SDL_strlcat(mapping_string, "lefttrigger:a4,", sizeof(mapping_string));726}727if (axis_mask & (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) {728SDL_strlcat(mapping_string, "righttrigger:a5,", sizeof(mapping_string));729}730731return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);732}733#endif // SDL_PLATFORM_ANDROID734735/*736* Helper function to guess at a mapping for HIDAPI gamepads737*/738static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)739{740bool existing;741char mapping_string[1024];742Uint16 vendor;743Uint16 product;744745SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));746747SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);748749if ((vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) ||750(vendor == USB_VENDOR_DRAGONRISE &&751(product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 ||752product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2))) {753// GameCube driver has 12 buttons and 6 axes754SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b2,y:b3,", sizeof(mapping_string));755} else if (vendor == USB_VENDOR_NINTENDO &&756(guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCLeft ||757guid.data[15] == k_eSwitchDeviceInfoControllerType_HVCRight ||758guid.data[15] == k_eSwitchDeviceInfoControllerType_NESLeft ||759guid.data[15] == k_eSwitchDeviceInfoControllerType_NESRight ||760guid.data[15] == k_eSwitchDeviceInfoControllerType_SNES ||761guid.data[15] == k_eSwitchDeviceInfoControllerType_N64 ||762guid.data[15] == k_eSwitchDeviceInfoControllerType_SEGA_Genesis ||763guid.data[15] == k_eWiiExtensionControllerType_None ||764guid.data[15] == k_eWiiExtensionControllerType_Nunchuk ||765guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft ||766guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConRight)) {767switch (guid.data[15]) {768case k_eSwitchDeviceInfoControllerType_HVCLeft:769SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string));770break;771case k_eSwitchDeviceInfoControllerType_HVCRight:772SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,", sizeof(mapping_string));773break;774case k_eSwitchDeviceInfoControllerType_NESLeft:775case k_eSwitchDeviceInfoControllerType_NESRight:776SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string));777break;778case k_eSwitchDeviceInfoControllerType_SNES:779SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string));780break;781case k_eSwitchDeviceInfoControllerType_N64:782SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string));783break;784case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:785SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b11,", sizeof(mapping_string));786break;787case k_eWiiExtensionControllerType_None:788SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,start:b6,x:b2,y:b3,", sizeof(mapping_string));789break;790case k_eWiiExtensionControllerType_Nunchuk:791{792// FIXME: Should we map this to the left or right side?793const bool map_nunchuck_left_side = true;794795if (map_nunchuck_left_side) {796SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,start:b6,x:b2,y:b3,", sizeof(mapping_string));797} else {798SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b9,righttrigger:a4,rightx:a0,righty:a1,start:b6,x:b2,y:b3,", sizeof(mapping_string));799}800} break;801default:802if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS, false)) {803// Vertical mode804if (guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft) {805SDL_strlcat(mapping_string, "back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle2:b13,paddle4:b15,", sizeof(mapping_string));806} else {807SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,paddle1:b12,paddle3:b14,", sizeof(mapping_string));808}809} else {810// Mini gamepad mode811if (guid.data[15] == k_eSwitchDeviceInfoControllerType_JoyConLeft) {812SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,paddle2:b13,paddle4:b15,", sizeof(mapping_string));813} else {814SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,paddle1:b12,paddle3:b14,", sizeof(mapping_string));815}816}817break;818}819} else {820// All other gamepads have the standard set of 19 buttons and 6 axes821SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string));822823if (SDL_IsJoystickSteamController(vendor, product)) {824// Steam controllers have 2 back paddle buttons825SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,", sizeof(mapping_string));826} else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) ||827SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) {828// Nintendo Switch Pro controllers have a screenshot button829SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));830} else if (SDL_IsJoystickNintendoSwitchJoyConPair(vendor, product)) {831// The Nintendo Switch Joy-Con combined controllers has a share button and paddles832SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,", sizeof(mapping_string));833} else if (SDL_IsJoystickAmazonLunaController(vendor, product)) {834// Amazon Luna Controller has a mic button under the guide button835SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));836} else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) {837// The Google Stadia controller has a share button and a Google Assistant button838SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string));839} else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) {840// The NVIDIA SHIELD controller has a share button between back and start buttons841SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));842843if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {844// The original SHIELD controller has a touchpad and plus/minus buttons as well845SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string));846}847} else if (SDL_IsJoystickHoriSteamController(vendor, product)) {848/* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */849SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string));850} else {851switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) {852case SDL_GAMEPAD_TYPE_PS4:853// PS4 controllers have an additional touchpad button854SDL_strlcat(mapping_string, "touchpad:b11,", sizeof(mapping_string));855break;856case SDL_GAMEPAD_TYPE_PS5:857// PS5 controllers have a microphone button and an additional touchpad button858SDL_strlcat(mapping_string, "touchpad:b11,misc1:b12,", sizeof(mapping_string));859// DualSense Edge controllers have paddles860if (SDL_IsJoystickDualSenseEdge(vendor, product)) {861SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,paddle3:b14,paddle4:b13,", sizeof(mapping_string));862}863break;864case SDL_GAMEPAD_TYPE_XBOXONE:865if (SDL_IsJoystickXboxOneElite(vendor, product)) {866// XBox One Elite Controllers have 4 back paddle buttons867SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,", sizeof(mapping_string));868} else if (SDL_IsJoystickXboxSeriesX(vendor, product)) {869// XBox Series X Controllers have a share button under the guide button870SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));871}872break;873default:874if (vendor == 0 && product == 0) {875// This is a Bluetooth Nintendo Switch Pro controller876SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));877}878break;879}880}881}882883return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);884}885886/*887* Helper function to guess at a mapping for RAWINPUT gamepads888*/889static GamepadMapping_t *SDL_CreateMappingForRAWINPUTGamepad(SDL_GUID guid)890{891bool existing;892char mapping_string[1024];893894SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));895SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,guide:b10,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,", sizeof(mapping_string));896897return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);898}899900/*901* Helper function to guess at a mapping for WGI gamepads902*/903static GamepadMapping_t *SDL_CreateMappingForWGIGamepad(SDL_GUID guid)904{905bool existing;906char mapping_string[1024];907908if (guid.data[15] != SDL_JOYSTICK_TYPE_GAMEPAD) {909return NULL;910}911912SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string));913SDL_strlcat(mapping_string, "a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:b10,dpdown:b12,dpleft:b13,dpright:b11,leftx:a1,lefty:a0~,rightx:a3,righty:a2~,lefttrigger:a4,righttrigger:a5,", sizeof(mapping_string));914915return SDL_PrivateAddMappingForGUID(guid, mapping_string, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);916}917918/*919* Helper function to scan the mappings database for a gamepad with the specified GUID920*/921static GamepadMapping_t *SDL_PrivateMatchGamepadMappingForGUID(SDL_GUID guid, bool match_version, bool exact_match_crc)922{923GamepadMapping_t *mapping, *best_match = NULL;924Uint16 crc = 0;925926SDL_AssertJoysticksLocked();927928SDL_GetJoystickGUIDInfo(guid, NULL, NULL, NULL, &crc);929930// Clear the CRC from the GUID for matching, the mappings never include it in the GUID931SDL_SetJoystickGUIDCRC(&guid, 0);932933if (!match_version) {934SDL_SetJoystickGUIDVersion(&guid, 0);935}936937for (mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) {938SDL_GUID mapping_guid;939940if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) {941continue;942}943944SDL_memcpy(&mapping_guid, &mapping->guid, sizeof(mapping_guid));945if (!match_version) {946SDL_SetJoystickGUIDVersion(&mapping_guid, 0);947}948949if (SDL_memcmp(&guid, &mapping_guid, sizeof(guid)) == 0) {950const char *crc_string = SDL_strstr(mapping->mapping, SDL_GAMEPAD_CRC_FIELD);951if (crc_string) {952Uint16 mapping_crc = (Uint16)SDL_strtol(crc_string + SDL_GAMEPAD_CRC_FIELD_SIZE, NULL, 16);953if (mapping_crc != crc) {954// This mapping specified a CRC and they don't match955continue;956}957958// An exact match, including CRC959return mapping;960} else if (crc && exact_match_crc) {961continue;962}963964if (!best_match) {965best_match = mapping;966}967}968}969return best_match;970}971972/*973* Helper function to scan the mappings database for a gamepad with the specified GUID974*/975static GamepadMapping_t *SDL_PrivateGetGamepadMappingForGUID(SDL_GUID guid, bool adding_mapping)976{977GamepadMapping_t *mapping;978979mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, true, adding_mapping);980if (mapping) {981return mapping;982}983984if (adding_mapping) {985// We didn't find an existing mapping986return NULL;987}988989// Try harder to get the best match, or create a mapping990991if (SDL_JoystickGUIDUsesVersion(guid)) {992// Try again, ignoring the version993mapping = SDL_PrivateMatchGamepadMappingForGUID(guid, false, false);994if (mapping) {995return mapping;996}997}998999#ifdef SDL_JOYSTICK_XINPUT1000if (SDL_IsJoystickXInput(guid)) {1001// This is an XInput device1002return s_pXInputMapping;1003}1004#endif1005if (SDL_IsJoystickHIDAPI(guid)) {1006mapping = SDL_CreateMappingForHIDAPIGamepad(guid);1007} else if (SDL_IsJoystickRAWINPUT(guid)) {1008mapping = SDL_CreateMappingForRAWINPUTGamepad(guid);1009} else if (SDL_IsJoystickWGI(guid)) {1010mapping = SDL_CreateMappingForWGIGamepad(guid);1011} else if (SDL_IsJoystickVIRTUAL(guid)) {1012// We'll pick up a robust mapping in VIRTUAL_JoystickGetGamepadMapping1013#ifdef SDL_PLATFORM_ANDROID1014} else {1015mapping = SDL_CreateMappingForAndroidGamepad(guid);1016#endif1017}1018return mapping;1019}10201021static const char *map_StringForGamepadType[] = {1022"unknown",1023"standard",1024"xbox360",1025"xboxone",1026"ps3",1027"ps4",1028"ps5",1029"switchpro",1030"joyconleft",1031"joyconright",1032"joyconpair"1033};1034SDL_COMPILE_TIME_ASSERT(map_StringForGamepadType, SDL_arraysize(map_StringForGamepadType) == SDL_GAMEPAD_TYPE_COUNT);10351036/*1037* convert a string to its enum equivalent1038*/1039SDL_GamepadType SDL_GetGamepadTypeFromString(const char *str)1040{1041int i;10421043if (!str || str[0] == '\0') {1044return SDL_GAMEPAD_TYPE_UNKNOWN;1045}10461047if (*str == '+' || *str == '-') {1048++str;1049}10501051for (i = 0; i < SDL_arraysize(map_StringForGamepadType); ++i) {1052if (SDL_strcasecmp(str, map_StringForGamepadType[i]) == 0) {1053return (SDL_GamepadType)i;1054}1055}1056return SDL_GAMEPAD_TYPE_UNKNOWN;1057}10581059/*1060* convert an enum to its string equivalent1061*/1062const char *SDL_GetGamepadStringForType(SDL_GamepadType type)1063{1064if (type >= SDL_GAMEPAD_TYPE_STANDARD && type < SDL_GAMEPAD_TYPE_COUNT) {1065return map_StringForGamepadType[type];1066}1067return NULL;1068}10691070static const char *map_StringForGamepadAxis[] = {1071"leftx",1072"lefty",1073"rightx",1074"righty",1075"lefttrigger",1076"righttrigger"1077};1078SDL_COMPILE_TIME_ASSERT(map_StringForGamepadAxis, SDL_arraysize(map_StringForGamepadAxis) == SDL_GAMEPAD_AXIS_COUNT);10791080/*1081* convert a string to its enum equivalent1082*/1083SDL_GamepadAxis SDL_GetGamepadAxisFromString(const char *str)1084{1085int i;10861087if (!str || str[0] == '\0') {1088return SDL_GAMEPAD_AXIS_INVALID;1089}10901091if (*str == '+' || *str == '-') {1092++str;1093}10941095for (i = 0; i < SDL_arraysize(map_StringForGamepadAxis); ++i) {1096if (SDL_strcasecmp(str, map_StringForGamepadAxis[i]) == 0) {1097return (SDL_GamepadAxis)i;1098}1099}1100return SDL_GAMEPAD_AXIS_INVALID;1101}11021103/*1104* convert an enum to its string equivalent1105*/1106const char *SDL_GetGamepadStringForAxis(SDL_GamepadAxis axis)1107{1108if (axis > SDL_GAMEPAD_AXIS_INVALID && axis < SDL_GAMEPAD_AXIS_COUNT) {1109return map_StringForGamepadAxis[axis];1110}1111return NULL;1112}11131114static const char *map_StringForGamepadButton[] = {1115"a",1116"b",1117"x",1118"y",1119"back",1120"guide",1121"start",1122"leftstick",1123"rightstick",1124"leftshoulder",1125"rightshoulder",1126"dpup",1127"dpdown",1128"dpleft",1129"dpright",1130"misc1",1131"paddle1",1132"paddle2",1133"paddle3",1134"paddle4",1135"touchpad",1136"misc2",1137"misc3",1138"misc4",1139"misc5",1140"misc6"1141};1142SDL_COMPILE_TIME_ASSERT(map_StringForGamepadButton, SDL_arraysize(map_StringForGamepadButton) == SDL_GAMEPAD_BUTTON_COUNT);11431144/*1145* convert a string to its enum equivalent1146*/1147static SDL_GamepadButton SDL_PrivateGetGamepadButtonFromString(const char *str, bool baxy)1148{1149int i;11501151if (!str || str[0] == '\0') {1152return SDL_GAMEPAD_BUTTON_INVALID;1153}11541155for (i = 0; i < SDL_arraysize(map_StringForGamepadButton); ++i) {1156if (SDL_strcasecmp(str, map_StringForGamepadButton[i]) == 0) {1157if (baxy) {1158// Need to swap face buttons1159switch (i) {1160case SDL_GAMEPAD_BUTTON_SOUTH:1161return SDL_GAMEPAD_BUTTON_EAST;1162case SDL_GAMEPAD_BUTTON_EAST:1163return SDL_GAMEPAD_BUTTON_SOUTH;1164case SDL_GAMEPAD_BUTTON_WEST:1165return SDL_GAMEPAD_BUTTON_NORTH;1166case SDL_GAMEPAD_BUTTON_NORTH:1167return SDL_GAMEPAD_BUTTON_WEST;1168default:1169break;1170}1171}1172return (SDL_GamepadButton)i;1173}1174}1175return SDL_GAMEPAD_BUTTON_INVALID;1176}1177SDL_GamepadButton SDL_GetGamepadButtonFromString(const char *str)1178{1179return SDL_PrivateGetGamepadButtonFromString(str, false);1180}11811182/*1183* convert an enum to its string equivalent1184*/1185const char *SDL_GetGamepadStringForButton(SDL_GamepadButton button)1186{1187if (button > SDL_GAMEPAD_BUTTON_INVALID && button < SDL_GAMEPAD_BUTTON_COUNT) {1188return map_StringForGamepadButton[button];1189}1190return NULL;1191}11921193/*1194* given a gamepad button name and a joystick name update our mapping structure with it1195*/1196static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szGameButton, const char *szJoystickButton)1197{1198SDL_GamepadBinding bind;1199SDL_GamepadButton button;1200SDL_GamepadAxis axis;1201bool invert_input = false;1202char half_axis_input = 0;1203char half_axis_output = 0;1204int i;1205SDL_GamepadBinding *new_bindings;1206bool baxy_mapping = false;12071208SDL_AssertJoysticksLocked();12091210SDL_zero(bind);12111212if (*szGameButton == '+' || *szGameButton == '-') {1213half_axis_output = *szGameButton++;1214}12151216if (SDL_strstr(gamepad->mapping->mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) {1217baxy_mapping = true;1218}12191220axis = SDL_GetGamepadAxisFromString(szGameButton);1221button = SDL_PrivateGetGamepadButtonFromString(szGameButton, baxy_mapping);1222if (axis != SDL_GAMEPAD_AXIS_INVALID) {1223bind.output_type = SDL_GAMEPAD_BINDTYPE_AXIS;1224bind.output.axis.axis = axis;1225if (axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {1226bind.output.axis.axis_min = 0;1227bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;1228} else {1229if (half_axis_output == '+') {1230bind.output.axis.axis_min = 0;1231bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;1232} else if (half_axis_output == '-') {1233bind.output.axis.axis_min = 0;1234bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MIN;1235} else {1236bind.output.axis.axis_min = SDL_JOYSTICK_AXIS_MIN;1237bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;1238}1239}1240} else if (button != SDL_GAMEPAD_BUTTON_INVALID) {1241bind.output_type = SDL_GAMEPAD_BINDTYPE_BUTTON;1242bind.output.button = button;1243} else {1244return false;1245}12461247if (*szJoystickButton == '+' || *szJoystickButton == '-') {1248half_axis_input = *szJoystickButton++;1249}1250if (szJoystickButton[SDL_strlen(szJoystickButton) - 1] == '~') {1251invert_input = true;1252}12531254if (szJoystickButton[0] == 'a' && SDL_isdigit((unsigned char)szJoystickButton[1])) {1255bind.input_type = SDL_GAMEPAD_BINDTYPE_AXIS;1256bind.input.axis.axis = SDL_atoi(&szJoystickButton[1]);1257if (half_axis_input == '+') {1258bind.input.axis.axis_min = 0;1259bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;1260} else if (half_axis_input == '-') {1261bind.input.axis.axis_min = 0;1262bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MIN;1263} else {1264bind.input.axis.axis_min = SDL_JOYSTICK_AXIS_MIN;1265bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MAX;1266}1267if (invert_input) {1268int tmp = bind.input.axis.axis_min;1269bind.input.axis.axis_min = bind.input.axis.axis_max;1270bind.input.axis.axis_max = tmp;1271}1272} else if (szJoystickButton[0] == 'b' && SDL_isdigit((unsigned char)szJoystickButton[1])) {1273bind.input_type = SDL_GAMEPAD_BINDTYPE_BUTTON;1274bind.input.button = SDL_atoi(&szJoystickButton[1]);1275} else if (szJoystickButton[0] == 'h' && SDL_isdigit((unsigned char)szJoystickButton[1]) &&1276szJoystickButton[2] == '.' && SDL_isdigit((unsigned char)szJoystickButton[3])) {1277int hat = SDL_atoi(&szJoystickButton[1]);1278int mask = SDL_atoi(&szJoystickButton[3]);1279bind.input_type = SDL_GAMEPAD_BINDTYPE_HAT;1280bind.input.hat.hat = hat;1281bind.input.hat.hat_mask = mask;1282} else {1283return false;1284}12851286for (i = 0; i < gamepad->num_bindings; ++i) {1287if (SDL_memcmp(&gamepad->bindings[i], &bind, sizeof(bind)) == 0) {1288// We already have this binding, could be different face button names?1289return true;1290}1291}12921293++gamepad->num_bindings;1294new_bindings = (SDL_GamepadBinding *)SDL_realloc(gamepad->bindings, gamepad->num_bindings * sizeof(*gamepad->bindings));1295if (!new_bindings) {1296SDL_free(gamepad->bindings);1297gamepad->num_bindings = 0;1298gamepad->bindings = NULL;1299return false;1300}1301gamepad->bindings = new_bindings;1302gamepad->bindings[gamepad->num_bindings - 1] = bind;1303return true;1304}13051306/*1307* given a gamepad mapping string update our mapping object1308*/1309static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString)1310{1311char szGameButton[20];1312char szJoystickButton[20];1313bool bGameButton = true;1314int i = 0;1315const char *pchPos = pchString;13161317SDL_zeroa(szGameButton);1318SDL_zeroa(szJoystickButton);13191320while (pchPos && *pchPos) {1321if (*pchPos == ':') {1322i = 0;1323bGameButton = false;1324} else if (*pchPos == ' ') {13251326} else if (*pchPos == ',') {1327i = 0;1328bGameButton = true;1329SDL_PrivateParseGamepadElement(gamepad, szGameButton, szJoystickButton);1330SDL_zeroa(szGameButton);1331SDL_zeroa(szJoystickButton);13321333} else if (bGameButton) {1334if (i >= sizeof(szGameButton)) {1335szGameButton[sizeof(szGameButton) - 1] = '\0';1336return SDL_SetError("Button name too large: %s", szGameButton);1337}1338szGameButton[i] = *pchPos;1339i++;1340} else {1341if (i >= sizeof(szJoystickButton)) {1342szJoystickButton[sizeof(szJoystickButton) - 1] = '\0';1343return SDL_SetError("Joystick button name too large: %s", szJoystickButton);1344}1345szJoystickButton[i] = *pchPos;1346i++;1347}1348pchPos++;1349}13501351// No more values if the string was terminated by a comma. Don't report an error.1352if (szGameButton[0] != '\0' || szJoystickButton[0] != '\0') {1353SDL_PrivateParseGamepadElement(gamepad, szGameButton, szJoystickButton);1354}1355return true;1356}13571358static void SDL_UpdateGamepadType(SDL_Gamepad *gamepad)1359{1360char *type_string, *comma;13611362SDL_AssertJoysticksLocked();13631364gamepad->type = SDL_GAMEPAD_TYPE_UNKNOWN;13651366type_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_TYPE_FIELD);1367if (type_string) {1368type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;1369comma = SDL_strchr(type_string, ',');1370if (comma) {1371*comma = '\0';1372gamepad->type = SDL_GetGamepadTypeFromString(type_string);1373*comma = ',';1374} else {1375gamepad->type = SDL_GetGamepadTypeFromString(type_string);1376}1377}1378if (gamepad->type == SDL_GAMEPAD_TYPE_UNKNOWN) {1379gamepad->type = SDL_GetRealGamepadTypeForID(gamepad->joystick->instance_id);1380}1381}13821383static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleFromString(const char *string)1384{1385if (SDL_strcmp(string, "abxy") == 0) {1386return SDL_GAMEPAD_FACE_STYLE_ABXY;1387} else if (SDL_strcmp(string, "bayx") == 0) {1388return SDL_GAMEPAD_FACE_STYLE_BAYX;1389} else if (SDL_strcmp(string, "sony") == 0) {1390return SDL_GAMEPAD_FACE_STYLE_SONY;1391} else {1392return SDL_GAMEPAD_FACE_STYLE_UNKNOWN;1393}1394}13951396static SDL_GamepadFaceStyle SDL_GetGamepadFaceStyleForGamepadType(SDL_GamepadType type)1397{1398switch (type) {1399case SDL_GAMEPAD_TYPE_PS3:1400case SDL_GAMEPAD_TYPE_PS4:1401case SDL_GAMEPAD_TYPE_PS5:1402return SDL_GAMEPAD_FACE_STYLE_SONY;1403case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:1404case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:1405case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:1406case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:1407return SDL_GAMEPAD_FACE_STYLE_BAYX;1408default:1409return SDL_GAMEPAD_FACE_STYLE_ABXY;1410}1411}14121413static void SDL_UpdateGamepadFaceStyle(SDL_Gamepad *gamepad)1414{1415char *face_string, *comma;14161417SDL_AssertJoysticksLocked();14181419gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_UNKNOWN;14201421face_string = SDL_strstr(gamepad->mapping->mapping, SDL_GAMEPAD_FACE_FIELD);1422if (face_string) {1423face_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;1424comma = SDL_strchr(face_string, ',');1425if (comma) {1426*comma = '\0';1427gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string);1428*comma = ',';1429} else {1430gamepad->face_style = SDL_GetGamepadFaceStyleFromString(face_string);1431}1432}14331434if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN &&1435SDL_strstr(gamepad->mapping->mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") != NULL) {1436// This controller uses Nintendo button style1437gamepad->face_style = SDL_GAMEPAD_FACE_STYLE_BAYX;1438}1439if (gamepad->face_style == SDL_GAMEPAD_FACE_STYLE_UNKNOWN) {1440gamepad->face_style = SDL_GetGamepadFaceStyleForGamepadType(gamepad->type);1441}1442}14431444static void SDL_FixupHIDAPIMapping(SDL_Gamepad *gamepad)1445{1446// Check to see if we need fixup1447bool need_fixup = false;1448for (int i = 0; i < gamepad->num_bindings; ++i) {1449SDL_GamepadBinding *binding = &gamepad->bindings[i];1450if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&1451binding->output.button >= SDL_GAMEPAD_BUTTON_DPAD_UP) {1452if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&1453binding->input.button == binding->output.button) {1454// Old style binding1455need_fixup = true;1456}1457break;1458}1459}1460if (!need_fixup) {1461return;1462}14631464for (int i = 0; i < gamepad->num_bindings; ++i) {1465SDL_GamepadBinding *binding = &gamepad->bindings[i];1466if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON &&1467binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON) {1468switch (binding->output.button) {1469case SDL_GAMEPAD_BUTTON_DPAD_UP:1470binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT;1471binding->input.hat.hat = 0;1472binding->input.hat.hat_mask = SDL_HAT_UP;1473break;1474case SDL_GAMEPAD_BUTTON_DPAD_DOWN:1475binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT;1476binding->input.hat.hat = 0;1477binding->input.hat.hat_mask = SDL_HAT_DOWN;1478break;1479case SDL_GAMEPAD_BUTTON_DPAD_LEFT:1480binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT;1481binding->input.hat.hat = 0;1482binding->input.hat.hat_mask = SDL_HAT_LEFT;1483break;1484case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:1485binding->input_type = SDL_GAMEPAD_BINDTYPE_HAT;1486binding->input.hat.hat = 0;1487binding->input.hat.hat_mask = SDL_HAT_RIGHT;1488break;1489default:1490if (binding->output.button > SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {1491binding->input.button -= 4;1492}1493break;1494}1495}1496}1497}14981499/*1500* Make a new button mapping struct1501*/1502static void SDL_PrivateLoadButtonMapping(SDL_Gamepad *gamepad, GamepadMapping_t *pGamepadMapping)1503{1504int i;15051506SDL_AssertJoysticksLocked();15071508gamepad->name = pGamepadMapping->name;1509gamepad->num_bindings = 0;1510gamepad->mapping = pGamepadMapping;1511if (gamepad->joystick->naxes != 0 && gamepad->last_match_axis) {1512SDL_memset(gamepad->last_match_axis, 0, gamepad->joystick->naxes * sizeof(*gamepad->last_match_axis));1513}15141515SDL_UpdateGamepadType(gamepad);1516SDL_UpdateGamepadFaceStyle(gamepad);15171518SDL_PrivateParseGamepadConfigString(gamepad, pGamepadMapping->mapping);15191520if (SDL_IsJoystickHIDAPI(pGamepadMapping->guid)) {1521SDL_FixupHIDAPIMapping(gamepad);1522}15231524// Set the zero point for triggers1525for (i = 0; i < gamepad->num_bindings; ++i) {1526SDL_GamepadBinding *binding = &gamepad->bindings[i];1527if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS &&1528binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS &&1529(binding->output.axis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||1530binding->output.axis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) {1531if (binding->input.axis.axis < gamepad->joystick->naxes) {1532gamepad->joystick->axes[binding->input.axis.axis].value =1533gamepad->joystick->axes[binding->input.axis.axis].zero = (Sint16)binding->input.axis.axis_min;1534}1535}1536}1537}15381539/*1540* grab the guid string from a mapping string1541*/1542static char *SDL_PrivateGetGamepadGUIDFromMappingString(const char *pMapping)1543{1544const char *pFirstComma = SDL_strchr(pMapping, ',');1545if (pFirstComma) {1546char *pchGUID = (char *)SDL_malloc(pFirstComma - pMapping + 1);1547if (!pchGUID) {1548return NULL;1549}1550SDL_memcpy(pchGUID, pMapping, pFirstComma - pMapping);1551pchGUID[pFirstComma - pMapping] = '\0';15521553// Convert old style GUIDs to the new style in 2.0.51554#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)1555if (SDL_strlen(pchGUID) == 32 &&1556SDL_memcmp(&pchGUID[20], "504944564944", 12) == 0) {1557SDL_memcpy(&pchGUID[20], "000000000000", 12);1558SDL_memcpy(&pchGUID[16], &pchGUID[4], 4);1559SDL_memcpy(&pchGUID[8], &pchGUID[0], 4);1560SDL_memcpy(&pchGUID[0], "03000000", 8);1561}1562#elif defined(SDL_PLATFORM_MACOS)1563if (SDL_strlen(pchGUID) == 32 &&1564SDL_memcmp(&pchGUID[4], "000000000000", 12) == 0 &&1565SDL_memcmp(&pchGUID[20], "000000000000", 12) == 0) {1566SDL_memcpy(&pchGUID[20], "000000000000", 12);1567SDL_memcpy(&pchGUID[8], &pchGUID[0], 4);1568SDL_memcpy(&pchGUID[0], "03000000", 8);1569}1570#endif1571return pchGUID;1572}1573return NULL;1574}15751576/*1577* grab the name string from a mapping string1578*/1579static char *SDL_PrivateGetGamepadNameFromMappingString(const char *pMapping)1580{1581const char *pFirstComma, *pSecondComma;1582char *pchName;15831584pFirstComma = SDL_strchr(pMapping, ',');1585if (!pFirstComma) {1586return NULL;1587}15881589pSecondComma = SDL_strchr(pFirstComma + 1, ',');1590if (!pSecondComma) {1591return NULL;1592}15931594pchName = (char *)SDL_malloc(pSecondComma - pFirstComma);1595if (!pchName) {1596return NULL;1597}1598SDL_memcpy(pchName, pFirstComma + 1, pSecondComma - pFirstComma);1599pchName[pSecondComma - pFirstComma - 1] = 0;1600return pchName;1601}16021603/*1604* grab the button mapping string from a mapping string1605*/1606static char *SDL_PrivateGetGamepadMappingFromMappingString(const char *pMapping)1607{1608const char *pFirstComma, *pSecondComma;1609char *result;1610size_t length;16111612pFirstComma = SDL_strchr(pMapping, ',');1613if (!pFirstComma) {1614return NULL;1615}16161617pSecondComma = SDL_strchr(pFirstComma + 1, ',');1618if (!pSecondComma) {1619return NULL;1620}16211622// Skip whitespace1623while (SDL_isspace(pSecondComma[1])) {1624++pSecondComma;1625}16261627result = SDL_strdup(pSecondComma + 1); // mapping is everything after the 3rd comma16281629// Trim whitespace1630length = SDL_strlen(result);1631while (length > 0 && SDL_isspace(result[length - 1])) {1632--length;1633}1634result[length] = '\0';16351636return result;1637}16381639/*1640* Helper function to add a mapping for a guid1641*/1642static GamepadMapping_t *SDL_PrivateAddMappingForGUID(SDL_GUID jGUID, const char *mappingString, bool *existing, SDL_GamepadMappingPriority priority)1643{1644char *pchName;1645char *pchMapping;1646GamepadMapping_t *pGamepadMapping;1647Uint16 crc;16481649SDL_AssertJoysticksLocked();16501651pchName = SDL_PrivateGetGamepadNameFromMappingString(mappingString);1652if (!pchName) {1653SDL_SetError("Couldn't parse name from %s", mappingString);1654return NULL;1655}16561657pchMapping = SDL_PrivateGetGamepadMappingFromMappingString(mappingString);1658if (!pchMapping) {1659SDL_free(pchName);1660SDL_SetError("Couldn't parse %s", mappingString);1661return NULL;1662}16631664// Fix up the GUID and the mapping with the CRC, if needed1665SDL_GetJoystickGUIDInfo(jGUID, NULL, NULL, NULL, &crc);1666if (crc) {1667// Make sure the mapping has the CRC1668char *new_mapping;1669const char *optional_comma;1670size_t mapping_length;1671char *crc_end = "";1672char *crc_string = SDL_strstr(pchMapping, SDL_GAMEPAD_CRC_FIELD);1673if (crc_string) {1674crc_end = SDL_strchr(crc_string, ',');1675if (crc_end) {1676++crc_end;1677} else {1678crc_end = "";1679}1680*crc_string = '\0';1681}16821683// Make sure there's a comma before the CRC1684mapping_length = SDL_strlen(pchMapping);1685if (mapping_length == 0 || pchMapping[mapping_length - 1] == ',') {1686optional_comma = "";1687} else {1688optional_comma = ",";1689}16901691if (SDL_asprintf(&new_mapping, "%s%s%s%.4x,%s", pchMapping, optional_comma, SDL_GAMEPAD_CRC_FIELD, crc, crc_end) >= 0) {1692SDL_free(pchMapping);1693pchMapping = new_mapping;1694}1695} else {1696// Make sure the GUID has the CRC, for matching purposes1697char *crc_string = SDL_strstr(pchMapping, SDL_GAMEPAD_CRC_FIELD);1698if (crc_string) {1699crc = (Uint16)SDL_strtol(crc_string + SDL_GAMEPAD_CRC_FIELD_SIZE, NULL, 16);1700if (crc) {1701SDL_SetJoystickGUIDCRC(&jGUID, crc);1702}1703}1704}17051706PushMappingChangeTracking();17071708pGamepadMapping = SDL_PrivateGetGamepadMappingForGUID(jGUID, true);1709if (pGamepadMapping) {1710// Only overwrite the mapping if the priority is the same or higher.1711if (pGamepadMapping->priority <= priority) {1712// Update existing mapping1713SDL_free(pGamepadMapping->name);1714pGamepadMapping->name = pchName;1715SDL_free(pGamepadMapping->mapping);1716pGamepadMapping->mapping = pchMapping;1717pGamepadMapping->priority = priority;1718} else {1719SDL_free(pchName);1720SDL_free(pchMapping);1721}1722if (existing) {1723*existing = true;1724}1725AddMappingChangeTracking(pGamepadMapping);1726} else {1727pGamepadMapping = (GamepadMapping_t *)SDL_malloc(sizeof(*pGamepadMapping));1728if (!pGamepadMapping) {1729PopMappingChangeTracking();1730SDL_free(pchName);1731SDL_free(pchMapping);1732return NULL;1733}1734// Clear the CRC, we've already added it to the mapping1735if (crc) {1736SDL_SetJoystickGUIDCRC(&jGUID, 0);1737}1738pGamepadMapping->guid = jGUID;1739pGamepadMapping->name = pchName;1740pGamepadMapping->mapping = pchMapping;1741pGamepadMapping->next = NULL;1742pGamepadMapping->priority = priority;17431744if (s_pSupportedGamepads) {1745// Add the mapping to the end of the list1746GamepadMapping_t *pCurrMapping, *pPrevMapping;17471748for (pPrevMapping = s_pSupportedGamepads, pCurrMapping = pPrevMapping->next;1749pCurrMapping;1750pPrevMapping = pCurrMapping, pCurrMapping = pCurrMapping->next) {1751// continue;1752}1753pPrevMapping->next = pGamepadMapping;1754} else {1755s_pSupportedGamepads = pGamepadMapping;1756}1757if (existing) {1758*existing = false;1759}1760}17611762PopMappingChangeTracking();17631764return pGamepadMapping;1765}17661767/*1768* Helper function to determine pre-calculated offset to certain joystick mappings1769*/1770static GamepadMapping_t *SDL_PrivateGetGamepadMappingForNameAndGUID(const char *name, SDL_GUID guid)1771{1772GamepadMapping_t *mapping;17731774SDL_AssertJoysticksLocked();17751776mapping = SDL_PrivateGetGamepadMappingForGUID(guid, false);17771778return mapping;1779}17801781static void SDL_PrivateAppendToMappingString(char *mapping_string,1782size_t mapping_string_len,1783const char *input_name,1784SDL_InputMapping *mapping)1785{1786char buffer[16];1787if (mapping->kind == EMappingKind_None) {1788return;1789}17901791SDL_strlcat(mapping_string, input_name, mapping_string_len);1792SDL_strlcat(mapping_string, ":", mapping_string_len);1793switch (mapping->kind) {1794case EMappingKind_Button:1795(void)SDL_snprintf(buffer, sizeof(buffer), "b%u", mapping->target);1796break;1797case EMappingKind_Axis:1798(void)SDL_snprintf(buffer, sizeof(buffer), "%sa%u%s",1799mapping->half_axis_positive ? "+" :1800mapping->half_axis_negative ? "-" : "",1801mapping->target,1802mapping->axis_reversed ? "~" : "");1803break;1804case EMappingKind_Hat:1805(void)SDL_snprintf(buffer, sizeof(buffer), "h%i.%i", mapping->target >> 4, mapping->target & 0x0F);1806break;1807default:1808SDL_assert(false);1809}18101811SDL_strlcat(mapping_string, buffer, mapping_string_len);1812SDL_strlcat(mapping_string, ",", mapping_string_len);1813}18141815static GamepadMapping_t *SDL_PrivateGenerateAutomaticGamepadMapping(const char *name,1816SDL_GUID guid,1817SDL_GamepadMapping *raw_map)1818{1819bool existing;1820char name_string[128];1821char mapping[1024];18221823// Remove the CRC from the GUID1824// We already know that this GUID doesn't have a mapping without the CRC, and we want newly1825// added mappings without a CRC to override this mapping.1826SDL_SetJoystickGUIDCRC(&guid, 0);18271828// Remove any commas in the name1829SDL_strlcpy(name_string, name, sizeof(name_string));1830{1831char *spot;1832for (spot = name_string; *spot; ++spot) {1833if (*spot == ',') {1834*spot = ' ';1835}1836}1837}1838(void)SDL_snprintf(mapping, sizeof(mapping), "none,%s,", name_string);1839SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "a", &raw_map->a);1840SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "b", &raw_map->b);1841SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "x", &raw_map->x);1842SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "y", &raw_map->y);1843SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "back", &raw_map->back);1844SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "guide", &raw_map->guide);1845SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "start", &raw_map->start);1846SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftstick", &raw_map->leftstick);1847SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightstick", &raw_map->rightstick);1848SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftshoulder", &raw_map->leftshoulder);1849SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightshoulder", &raw_map->rightshoulder);1850SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpup", &raw_map->dpup);1851SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpdown", &raw_map->dpdown);1852SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpleft", &raw_map->dpleft);1853SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpright", &raw_map->dpright);1854SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc1", &raw_map->misc1);1855SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc2", &raw_map->misc2);1856SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc3", &raw_map->misc3);1857SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc4", &raw_map->misc4);1858SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc5", &raw_map->misc5);1859SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc6", &raw_map->misc6);1860/* Keep using paddle1-4 in the generated mapping so that it can be1861* reused with SDL2 */1862SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle1", &raw_map->right_paddle1);1863SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle2", &raw_map->left_paddle1);1864SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle3", &raw_map->right_paddle2);1865SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle4", &raw_map->left_paddle2);1866SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftx", &raw_map->leftx);1867SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefty", &raw_map->lefty);1868SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightx", &raw_map->rightx);1869SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righty", &raw_map->righty);1870SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefttrigger", &raw_map->lefttrigger);1871SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "righttrigger", &raw_map->righttrigger);1872SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "touchpad", &raw_map->touchpad);18731874return SDL_PrivateAddMappingForGUID(guid, mapping, &existing, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);1875}18761877static GamepadMapping_t *SDL_PrivateGetGamepadMapping(SDL_JoystickID instance_id, bool create_mapping)1878{1879const char *name;1880SDL_GUID guid;1881GamepadMapping_t *mapping;18821883SDL_AssertJoysticksLocked();18841885name = SDL_GetJoystickNameForID(instance_id);1886guid = SDL_GetJoystickGUIDForID(instance_id);1887mapping = SDL_PrivateGetGamepadMappingForNameAndGUID(name, guid);1888if (!mapping && create_mapping) {1889SDL_GamepadMapping raw_map;18901891SDL_zero(raw_map);1892if (SDL_PrivateJoystickGetAutoGamepadMapping(instance_id, &raw_map)) {1893mapping = SDL_PrivateGenerateAutomaticGamepadMapping(name, guid, &raw_map);1894}1895}18961897if (!mapping) {1898mapping = s_pDefaultMapping;1899}1900return mapping;1901}19021903/*1904* Add or update an entry into the Mappings Database1905*/1906int SDL_AddGamepadMappingsFromIO(SDL_IOStream *src, bool closeio)1907{1908const char *platform = SDL_GetPlatform();1909int gamepads = 0;1910char *buf, *line, *line_end, *tmp, *comma, line_platform[64];1911size_t db_size;1912size_t platform_len;19131914buf = (char *)SDL_LoadFile_IO(src, &db_size, closeio);1915if (!buf) {1916SDL_SetError("Could not allocate space to read DB into memory");1917return -1;1918}1919line = buf;19201921SDL_LockJoysticks();19221923PushMappingChangeTracking();19241925while (line < buf + db_size) {1926line_end = SDL_strchr(line, '\n');1927if (line_end) {1928*line_end = '\0';1929} else {1930line_end = buf + db_size;1931}19321933// Extract and verify the platform1934tmp = SDL_strstr(line, SDL_GAMEPAD_PLATFORM_FIELD);1935if (tmp) {1936tmp += SDL_GAMEPAD_PLATFORM_FIELD_SIZE;1937comma = SDL_strchr(tmp, ',');1938if (comma) {1939platform_len = comma - tmp + 1;1940if (platform_len + 1 < SDL_arraysize(line_platform)) {1941SDL_strlcpy(line_platform, tmp, platform_len);1942if (SDL_strncasecmp(line_platform, platform, platform_len) == 0 &&1943SDL_AddGamepadMapping(line) > 0) {1944gamepads++;1945}1946}1947}1948}19491950line = line_end + 1;1951}19521953PopMappingChangeTracking();19541955SDL_UnlockJoysticks();19561957SDL_free(buf);1958return gamepads;1959}19601961int SDL_AddGamepadMappingsFromFile(const char *file)1962{1963return SDL_AddGamepadMappingsFromIO(SDL_IOFromFile(file, "rb"), true);1964}19651966bool SDL_ReloadGamepadMappings(void)1967{1968SDL_Gamepad *gamepad;19691970SDL_LockJoysticks();19711972PushMappingChangeTracking();19731974for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {1975AddMappingChangeTracking(gamepad->mapping);1976}19771978SDL_QuitGamepadMappings();1979SDL_InitGamepadMappings();19801981PopMappingChangeTracking();19821983SDL_UnlockJoysticks();19841985return true;1986}19871988static char *SDL_ConvertMappingToPositional(const char *mapping)1989{1990// Add space for '!' and null terminator1991size_t length = SDL_strlen(mapping) + 1 + 1;1992char *remapped = (char *)SDL_malloc(length);1993if (remapped) {1994char *button_A;1995char *button_B;1996char *button_X;1997char *button_Y;1998char *hint;19992000SDL_strlcpy(remapped, mapping, length);2001button_A = SDL_strstr(remapped, "a:");2002button_B = SDL_strstr(remapped, "b:");2003button_X = SDL_strstr(remapped, "x:");2004button_Y = SDL_strstr(remapped, "y:");2005hint = SDL_strstr(remapped, "hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS");20062007if (button_A) {2008*button_A = 'b';2009}2010if (button_B) {2011*button_B = 'a';2012}2013if (button_X) {2014*button_X = 'y';2015}2016if (button_Y) {2017*button_Y = 'x';2018}2019if (hint) {2020hint += 5;2021SDL_memmove(hint + 1, hint, SDL_strlen(hint) + 1);2022*hint = '!';2023}2024}2025return remapped;2026}20272028/*2029* Add or update an entry into the Mappings Database with a priority2030*/2031static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMappingPriority priority)2032{2033char *remapped = NULL;2034char *pchGUID;2035SDL_GUID jGUID;2036bool is_default_mapping = false;2037bool is_xinput_mapping = false;2038bool existing = false;2039GamepadMapping_t *pGamepadMapping;2040int result = -1;20412042SDL_AssertJoysticksLocked();20432044if (!mappingString) {2045SDL_InvalidParamError("mappingString");2046return -1;2047}20482049{ // Extract and verify the hint field2050const char *tmp;20512052tmp = SDL_strstr(mappingString, SDL_GAMEPAD_HINT_FIELD);2053if (tmp) {2054bool default_value, value, negate;2055int len;2056char hint[128];20572058tmp += SDL_GAMEPAD_HINT_FIELD_SIZE;20592060if (*tmp == '!') {2061negate = true;2062++tmp;2063} else {2064negate = false;2065}20662067len = 0;2068while (*tmp && *tmp != ',' && *tmp != ':' && len < (sizeof(hint) - 1)) {2069hint[len++] = *tmp++;2070}2071hint[len] = '\0';20722073if (tmp[0] == ':' && tmp[1] == '=') {2074tmp += 2;2075default_value = SDL_atoi(tmp);2076} else {2077default_value = false;2078}20792080if (SDL_strcmp(hint, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS") == 0) {2081// This hint is used to signal whether the mapping uses positional buttons or not2082if (negate) {2083// This mapping uses positional buttons, we can use it as-is2084} else {2085// This mapping uses labeled buttons, we need to swap them to positional2086remapped = SDL_ConvertMappingToPositional(mappingString);2087if (!remapped) {2088goto done;2089}2090mappingString = remapped;2091}2092} else {2093value = SDL_GetHintBoolean(hint, default_value);2094if (negate) {2095value = !value;2096}2097if (!value) {2098result = 0;2099goto done;2100}2101}2102}2103}21042105#ifdef ANDROID2106{ // Extract and verify the SDK version2107const char *tmp;21082109tmp = SDL_strstr(mappingString, SDL_GAMEPAD_SDKGE_FIELD);2110if (tmp) {2111tmp += SDL_GAMEPAD_SDKGE_FIELD_SIZE;2112if (!(SDL_GetAndroidSDKVersion() >= SDL_atoi(tmp))) {2113SDL_SetError("SDK version %d < minimum version %d", SDL_GetAndroidSDKVersion(), SDL_atoi(tmp));2114goto done;2115}2116}2117tmp = SDL_strstr(mappingString, SDL_GAMEPAD_SDKLE_FIELD);2118if (tmp) {2119tmp += SDL_GAMEPAD_SDKLE_FIELD_SIZE;2120if (!(SDL_GetAndroidSDKVersion() <= SDL_atoi(tmp))) {2121SDL_SetError("SDK version %d > maximum version %d", SDL_GetAndroidSDKVersion(), SDL_atoi(tmp));2122goto done;2123}2124}2125}2126#endif21272128pchGUID = SDL_PrivateGetGamepadGUIDFromMappingString(mappingString);2129if (!pchGUID) {2130SDL_SetError("Couldn't parse GUID from %s", mappingString);2131goto done;2132}2133if (!SDL_strcasecmp(pchGUID, "default")) {2134is_default_mapping = true;2135} else if (!SDL_strcasecmp(pchGUID, "xinput")) {2136is_xinput_mapping = true;2137}2138jGUID = SDL_StringToGUID(pchGUID);2139SDL_free(pchGUID);21402141pGamepadMapping = SDL_PrivateAddMappingForGUID(jGUID, mappingString, &existing, priority);2142if (!pGamepadMapping) {2143goto done;2144}21452146if (existing) {2147result = 0;2148} else {2149if (is_default_mapping) {2150s_pDefaultMapping = pGamepadMapping;2151} else if (is_xinput_mapping) {2152s_pXInputMapping = pGamepadMapping;2153}2154result = 1;2155}2156done:2157if (remapped) {2158SDL_free(remapped);2159}2160return result;2161}21622163/*2164* Add or update an entry into the Mappings Database2165*/2166int SDL_AddGamepadMapping(const char *mapping)2167{2168int result;21692170SDL_LockJoysticks();2171{2172result = SDL_PrivateAddGamepadMapping(mapping, SDL_GAMEPAD_MAPPING_PRIORITY_API);2173}2174SDL_UnlockJoysticks();21752176return result;2177}21782179/*2180* Create a mapping string for a mapping2181*/2182static char *CreateMappingString(GamepadMapping_t *mapping, SDL_GUID guid)2183{2184char *pMappingString, *pPlatformString;2185char pchGUID[33];2186size_t needed;2187bool need_platform = false;2188const char *platform = NULL;21892190SDL_AssertJoysticksLocked();21912192SDL_GUIDToString(guid, pchGUID, sizeof(pchGUID));21932194// allocate enough memory for GUID + ',' + name + ',' + mapping + \02195needed = SDL_strlen(pchGUID) + 1 + SDL_strlen(mapping->name) + 1 + SDL_strlen(mapping->mapping) + 1;21962197if (!SDL_strstr(mapping->mapping, SDL_GAMEPAD_PLATFORM_FIELD)) {2198// add memory for ',' + platform:PLATFORM2199need_platform = true;2200if (mapping->mapping[SDL_strlen(mapping->mapping) - 1] != ',') {2201needed += 1;2202}2203platform = SDL_GetPlatform();2204needed += SDL_GAMEPAD_PLATFORM_FIELD_SIZE + SDL_strlen(platform) + 1;2205}22062207pMappingString = (char *)SDL_malloc(needed);2208if (!pMappingString) {2209return NULL;2210}22112212(void)SDL_snprintf(pMappingString, needed, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping);22132214if (need_platform) {2215if (mapping->mapping[SDL_strlen(mapping->mapping) - 1] != ',') {2216SDL_strlcat(pMappingString, ",", needed);2217}2218SDL_strlcat(pMappingString, SDL_GAMEPAD_PLATFORM_FIELD, needed);2219SDL_strlcat(pMappingString, platform, needed);2220SDL_strlcat(pMappingString, ",", needed);2221}22222223// Make sure multiple platform strings haven't made their way into the mapping2224pPlatformString = SDL_strstr(pMappingString, SDL_GAMEPAD_PLATFORM_FIELD);2225if (pPlatformString) {2226pPlatformString = SDL_strstr(pPlatformString + 1, SDL_GAMEPAD_PLATFORM_FIELD);2227if (pPlatformString) {2228*pPlatformString = '\0';2229}2230}2231return pMappingString;2232}22332234char **SDL_GetGamepadMappings(int *count)2235{2236int num_mappings = 0;2237char **result = NULL;2238char **mappings = NULL;22392240if (count) {2241*count = 0;2242}22432244SDL_LockJoysticks();22452246for (GamepadMapping_t *mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) {2247if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) {2248continue;2249}2250num_mappings++;2251}22522253size_t final_allocation = sizeof (char *); // for the NULL terminator element.2254bool failed = false;2255mappings = (char **) SDL_calloc(num_mappings + 1, sizeof (char *));2256if (!mappings) {2257failed = true;2258} else {2259int i = 0;2260for (GamepadMapping_t *mapping = s_pSupportedGamepads; mapping; mapping = mapping->next) {2261if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) {2262continue;2263}22642265char *mappingstr = CreateMappingString(mapping, mapping->guid);2266if (!mappingstr) {2267failed = true;2268break; // error string is already set.2269}22702271SDL_assert(i < num_mappings);2272mappings[i++] = mappingstr;22732274final_allocation += SDL_strlen(mappingstr) + 1 + sizeof (char *);2275}2276}22772278SDL_UnlockJoysticks();22792280if (!failed) {2281result = (char **) SDL_malloc(final_allocation);2282if (result) {2283final_allocation -= (sizeof (char *) * num_mappings + 1);2284char *strptr = (char *) (result + (num_mappings + 1));2285for (int i = 0; i < num_mappings; i++) {2286result[i] = strptr;2287const size_t slen = SDL_strlcpy(strptr, mappings[i], final_allocation) + 1;2288SDL_assert(final_allocation >= slen);2289final_allocation -= slen;2290strptr += slen;2291}2292result[num_mappings] = NULL;22932294if (count) {2295*count = num_mappings;2296}2297}2298}22992300if (mappings) {2301for (int i = 0; i < num_mappings; i++) {2302SDL_free(mappings[i]);2303}2304SDL_free(mappings);2305}23062307return result;2308}23092310/*2311* Get the mapping string for this GUID2312*/2313char *SDL_GetGamepadMappingForGUID(SDL_GUID guid)2314{2315char *result;23162317SDL_LockJoysticks();2318{2319GamepadMapping_t *mapping = SDL_PrivateGetGamepadMappingForGUID(guid, false);2320if (mapping) {2321result = CreateMappingString(mapping, guid);2322} else {2323SDL_SetError("Mapping not available");2324result = NULL;2325}2326}2327SDL_UnlockJoysticks();23282329return result;2330}23312332/*2333* Get the mapping string for this device2334*/2335char *SDL_GetGamepadMapping(SDL_Gamepad *gamepad)2336{2337char *result;23382339SDL_LockJoysticks();2340{2341CHECK_GAMEPAD_MAGIC(gamepad, NULL);23422343result = CreateMappingString(gamepad->mapping, gamepad->joystick->guid);2344}2345SDL_UnlockJoysticks();23462347return result;2348}23492350/*2351* Set the mapping string for this device2352*/2353bool SDL_SetGamepadMapping(SDL_JoystickID instance_id, const char *mapping)2354{2355SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id);2356bool result = false;23572358if (SDL_memcmp(&guid, &s_zeroGUID, sizeof(guid)) == 0) {2359return SDL_InvalidParamError("instance_id");2360}23612362if (!mapping) {2363mapping = "*,*,";2364}23652366SDL_LockJoysticks();2367{2368if (SDL_PrivateAddMappingForGUID(guid, mapping, NULL, SDL_GAMEPAD_MAPPING_PRIORITY_API)) {2369result = true;2370}2371}2372SDL_UnlockJoysticks();23732374return result;2375}23762377static void SDL_LoadGamepadHints(void)2378{2379const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG);2380if (hint && hint[0]) {2381char *pTempMappings = SDL_strdup(hint);2382char *pUserMappings = pTempMappings;23832384PushMappingChangeTracking();23852386while (pUserMappings) {2387char *pchNewLine = NULL;23882389pchNewLine = SDL_strchr(pUserMappings, '\n');2390if (pchNewLine) {2391*pchNewLine = '\0';2392}23932394SDL_PrivateAddGamepadMapping(pUserMappings, SDL_GAMEPAD_MAPPING_PRIORITY_USER);23952396if (pchNewLine) {2397pUserMappings = pchNewLine + 1;2398} else {2399pUserMappings = NULL;2400}2401}24022403PopMappingChangeTracking();24042405SDL_free(pTempMappings);2406}2407}24082409/*2410* Fill the given buffer with the expected gamepad mapping filepath.2411* Usually this will just be SDL_HINT_GAMECONTROLLERCONFIG_FILE, but for2412* Android, we want to get the internal storage path.2413*/2414static bool SDL_GetGamepadMappingFilePath(char *path, size_t size)2415{2416const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG_FILE);2417if (hint && *hint) {2418return SDL_strlcpy(path, hint, size) < size;2419}24202421#ifdef SDL_PLATFORM_ANDROID2422return SDL_snprintf(path, size, "%s/gamepad_map.txt", SDL_GetAndroidInternalStoragePath()) < size;2423#else2424return false;2425#endif2426}24272428/*2429* Initialize the gamepad system, mostly load our DB of gamepad config mappings2430*/2431bool SDL_InitGamepadMappings(void)2432{2433char szGamepadMapPath[1024];2434int i = 0;2435const char *pMappingString = NULL;24362437SDL_AssertJoysticksLocked();24382439PushMappingChangeTracking();24402441pMappingString = s_GamepadMappings[i];2442while (pMappingString) {2443SDL_PrivateAddGamepadMapping(pMappingString, SDL_GAMEPAD_MAPPING_PRIORITY_DEFAULT);24442445i++;2446pMappingString = s_GamepadMappings[i];2447}24482449if (SDL_GetGamepadMappingFilePath(szGamepadMapPath, sizeof(szGamepadMapPath))) {2450SDL_AddGamepadMappingsFromFile(szGamepadMapPath);2451}24522453// load in any user supplied config2454SDL_LoadGamepadHints();24552456SDL_LoadVIDPIDList(&SDL_allowed_gamepads);2457SDL_LoadVIDPIDList(&SDL_ignored_gamepads);24582459PopMappingChangeTracking();24602461return true;2462}24632464bool SDL_InitGamepads(void)2465{2466int i;2467SDL_JoystickID *joysticks;24682469SDL_gamepads_initialized = true;24702471// Watch for joystick events and fire gamepad ones if needed2472SDL_AddEventWatch(SDL_GamepadEventWatcher, NULL);24732474// Send added events for gamepads currently attached2475joysticks = SDL_GetJoysticks(NULL);2476if (joysticks) {2477for (i = 0; joysticks[i]; ++i) {2478if (SDL_IsGamepad(joysticks[i])) {2479SDL_PrivateGamepadAdded(joysticks[i]);2480}2481}2482SDL_free(joysticks);2483}24842485return true;2486}24872488bool SDL_HasGamepad(void)2489{2490int num_joysticks = 0;2491int num_gamepads = 0;2492SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks);2493if (joysticks) {2494int i;2495for (i = num_joysticks - 1; i >= 0 && num_gamepads == 0; --i) {2496if (SDL_IsGamepad(joysticks[i])) {2497++num_gamepads;2498}2499}2500SDL_free(joysticks);2501}2502if (num_gamepads > 0) {2503return true;2504}2505return false;2506}25072508SDL_JoystickID *SDL_GetGamepads(int *count)2509{2510int num_joysticks = 0;2511int num_gamepads = 0;2512SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks);2513if (joysticks) {2514int i;2515for (i = num_joysticks - 1; i >= 0; --i) {2516if (SDL_IsGamepad(joysticks[i])) {2517++num_gamepads;2518} else {2519SDL_memmove(&joysticks[i], &joysticks[i+1], (num_gamepads + 1) * sizeof(joysticks[i]));2520}2521}2522}2523if (count) {2524*count = num_gamepads;2525}2526return joysticks;2527}25282529const char *SDL_GetGamepadNameForID(SDL_JoystickID instance_id)2530{2531const char *result = NULL;25322533SDL_LockJoysticks();2534{2535GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true);2536if (mapping) {2537if (SDL_strcmp(mapping->name, "*") == 0) {2538result = SDL_GetJoystickNameForID(instance_id);2539} else {2540result = SDL_GetPersistentString(mapping->name);2541}2542}2543}2544SDL_UnlockJoysticks();25452546return result;2547}25482549const char *SDL_GetGamepadPathForID(SDL_JoystickID instance_id)2550{2551return SDL_GetJoystickPathForID(instance_id);2552}25532554int SDL_GetGamepadPlayerIndexForID(SDL_JoystickID instance_id)2555{2556return SDL_GetJoystickPlayerIndexForID(instance_id);2557}25582559SDL_GUID SDL_GetGamepadGUIDForID(SDL_JoystickID instance_id)2560{2561return SDL_GetJoystickGUIDForID(instance_id);2562}25632564Uint16 SDL_GetGamepadVendorForID(SDL_JoystickID instance_id)2565{2566return SDL_GetJoystickVendorForID(instance_id);2567}25682569Uint16 SDL_GetGamepadProductForID(SDL_JoystickID instance_id)2570{2571return SDL_GetJoystickProductForID(instance_id);2572}25732574Uint16 SDL_GetGamepadProductVersionForID(SDL_JoystickID instance_id)2575{2576return SDL_GetJoystickProductVersionForID(instance_id);2577}25782579SDL_GamepadType SDL_GetGamepadTypeForID(SDL_JoystickID instance_id)2580{2581SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;25822583SDL_LockJoysticks();2584{2585GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true);2586if (mapping) {2587char *type_string, *comma;25882589type_string = SDL_strstr(mapping->mapping, SDL_GAMEPAD_TYPE_FIELD);2590if (type_string) {2591type_string += SDL_GAMEPAD_TYPE_FIELD_SIZE;2592comma = SDL_strchr(type_string, ',');2593if (comma) {2594*comma = '\0';2595type = SDL_GetGamepadTypeFromString(type_string);2596*comma = ',';2597}2598}2599}2600}2601SDL_UnlockJoysticks();26022603if (type != SDL_GAMEPAD_TYPE_UNKNOWN) {2604return type;2605}2606return SDL_GetRealGamepadTypeForID(instance_id);2607}26082609SDL_GamepadType SDL_GetRealGamepadTypeForID(SDL_JoystickID instance_id)2610{2611SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;2612const SDL_SteamVirtualGamepadInfo *info;26132614SDL_LockJoysticks();2615{2616info = SDL_GetJoystickVirtualGamepadInfoForID(instance_id);2617if (info) {2618type = info->type;2619} else {2620type = SDL_GetGamepadTypeFromGUID(SDL_GetJoystickGUIDForID(instance_id), SDL_GetJoystickNameForID(instance_id));2621}2622}2623SDL_UnlockJoysticks();26242625return type;2626}26272628char *SDL_GetGamepadMappingForID(SDL_JoystickID instance_id)2629{2630char *result = NULL;26312632SDL_LockJoysticks();2633{2634GamepadMapping_t *mapping = SDL_PrivateGetGamepadMapping(instance_id, true);2635if (mapping) {2636char pchGUID[33];2637SDL_GUID guid = SDL_GetJoystickGUIDForID(instance_id);2638SDL_GUIDToString(guid, pchGUID, sizeof(pchGUID));2639SDL_asprintf(&result, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping);2640}2641}2642SDL_UnlockJoysticks();26432644return result;2645}26462647/*2648* Return 1 if the joystick with this name and GUID is a supported gamepad2649*/2650bool SDL_IsGamepadNameAndGUID(const char *name, SDL_GUID guid)2651{2652bool result;26532654SDL_LockJoysticks();2655{2656if (s_pDefaultMapping || SDL_PrivateGetGamepadMappingForNameAndGUID(name, guid) != NULL) {2657result = true;2658} else {2659result = false;2660}2661}2662SDL_UnlockJoysticks();26632664return result;2665}26662667/*2668* Return 1 if the joystick at this device index is a supported gamepad2669*/2670bool SDL_IsGamepad(SDL_JoystickID instance_id)2671{2672bool result;26732674SDL_LockJoysticks();2675{2676const void *value;2677if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) {2678result = (bool)(uintptr_t)value;2679} else {2680if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) {2681result = true;2682} else {2683result = false;2684}26852686if (!s_gamepadInstanceIDs) {2687s_gamepadInstanceIDs = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, NULL, NULL);2688}2689SDL_InsertIntoHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, (void *)(uintptr_t)result, true);2690}2691}2692SDL_UnlockJoysticks();26932694return result;2695}26962697/*2698* Return 1 if the gamepad should be ignored by SDL2699*/2700bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)2701{2702int i;2703for (i = 0; i < SDL_arraysize(SDL_gamepad_blacklist_words); i++) {2704const struct SDL_GamepadBlacklistWords *blacklist_word = &SDL_gamepad_blacklist_words[i];27052706switch (blacklist_word->pos) {2707case GAMEPAD_BLACKLIST_BEGIN:2708if (SDL_startswith(name, blacklist_word->str)) {2709return true;2710}2711break;27122713case GAMEPAD_BLACKLIST_END:2714if (SDL_endswith(name, blacklist_word->str)) {2715return true;2716}2717break;27182719case GAMEPAD_BLACKLIST_ANYWHERE:2720if (SDL_strstr(name, blacklist_word->str) != NULL) {2721return true;2722}2723break;2724}2725}27262727#ifdef SDL_PLATFORM_WIN322728if (SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false) &&2729WIN_IsWine()) {2730// We are launched by Steam and running under Proton or Wine2731// We can't tell whether this controller is a Steam Virtual Gamepad,2732// so assume that is doing the appropriate filtering of controllers2733// and anything we see here is fine to use.2734return false;2735}2736#endif // SDL_PLATFORM_WIN3227372738if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {2739return !SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false);2740}27412742if (SDL_allowed_gamepads.num_included_entries > 0) {2743if (SDL_VIDPIDInList(vendor_id, product_id, &SDL_allowed_gamepads)) {2744return false;2745}2746return true;2747} else {2748if (SDL_VIDPIDInList(vendor_id, product_id, &SDL_ignored_gamepads)) {2749return true;2750}2751return false;2752}2753}27542755/*2756* Open a gamepad for use2757*2758* This function returns a gamepad identifier, or NULL if an error occurred.2759*/2760SDL_Gamepad *SDL_OpenGamepad(SDL_JoystickID instance_id)2761{2762SDL_Gamepad *gamepad;2763SDL_Gamepad *gamepadlist;2764GamepadMapping_t *pSupportedGamepad = NULL;27652766SDL_LockJoysticks();27672768gamepadlist = SDL_gamepads;2769// If the gamepad is already open, return it2770while (gamepadlist) {2771if (instance_id == gamepadlist->joystick->instance_id) {2772gamepad = gamepadlist;2773++gamepad->ref_count;2774SDL_UnlockJoysticks();2775return gamepad;2776}2777gamepadlist = gamepadlist->next;2778}27792780// Find a gamepad mapping2781pSupportedGamepad = SDL_PrivateGetGamepadMapping(instance_id, true);2782if (!pSupportedGamepad) {2783SDL_SetError("Couldn't find mapping for device (%" SDL_PRIu32 ")", instance_id);2784SDL_UnlockJoysticks();2785return NULL;2786}27872788// Create and initialize the gamepad2789gamepad = (SDL_Gamepad *)SDL_calloc(1, sizeof(*gamepad));2790if (!gamepad) {2791SDL_UnlockJoysticks();2792return NULL;2793}2794SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, true);27952796gamepad->joystick = SDL_OpenJoystick(instance_id);2797if (!gamepad->joystick) {2798SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false);2799SDL_free(gamepad);2800SDL_UnlockJoysticks();2801return NULL;2802}28032804if (gamepad->joystick->naxes) {2805gamepad->last_match_axis = (SDL_GamepadBinding **)SDL_calloc(gamepad->joystick->naxes, sizeof(*gamepad->last_match_axis));2806if (!gamepad->last_match_axis) {2807SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false);2808SDL_CloseJoystick(gamepad->joystick);2809SDL_free(gamepad);2810SDL_UnlockJoysticks();2811return NULL;2812}2813}2814if (gamepad->joystick->nhats) {2815gamepad->last_hat_mask = (Uint8 *)SDL_calloc(gamepad->joystick->nhats, sizeof(*gamepad->last_hat_mask));2816if (!gamepad->last_hat_mask) {2817SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false);2818SDL_CloseJoystick(gamepad->joystick);2819SDL_free(gamepad->last_match_axis);2820SDL_free(gamepad);2821SDL_UnlockJoysticks();2822return NULL;2823}2824}28252826SDL_PrivateLoadButtonMapping(gamepad, pSupportedGamepad);28272828// Add the gamepad to list2829++gamepad->ref_count;2830// Link the gamepad in the list2831gamepad->next = SDL_gamepads;2832SDL_gamepads = gamepad;28332834SDL_UnlockJoysticks();28352836return gamepad;2837}28382839/*2840* Manually pump for gamepad updates.2841*/2842void SDL_UpdateGamepads(void)2843{2844// Just for API completeness; the joystick API does all the work.2845SDL_UpdateJoysticks();2846}28472848/**2849* Return whether a gamepad has a given axis2850*/2851bool SDL_GamepadHasAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)2852{2853bool result = false;28542855SDL_LockJoysticks();2856{2857int i;28582859CHECK_GAMEPAD_MAGIC(gamepad, false);28602861for (i = 0; i < gamepad->num_bindings; ++i) {2862const SDL_GamepadBinding *binding = &gamepad->bindings[i];2863if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS && binding->output.axis.axis == axis) {2864result = true;2865break;2866}2867}2868}2869SDL_UnlockJoysticks();28702871return result;2872}28732874/*2875* Get the current state of an axis control on a gamepad2876*/2877Sint16 SDL_GetGamepadAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)2878{2879Sint16 result = 0;28802881SDL_LockJoysticks();2882{2883int i;28842885CHECK_GAMEPAD_MAGIC(gamepad, 0);28862887for (i = 0; i < gamepad->num_bindings; ++i) {2888const SDL_GamepadBinding *binding = &gamepad->bindings[i];2889if (binding->output_type == SDL_GAMEPAD_BINDTYPE_AXIS && binding->output.axis.axis == axis) {2890int value = 0;2891bool valid_input_range;2892bool valid_output_range;28932894if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) {2895value = SDL_GetJoystickAxis(gamepad->joystick, binding->input.axis.axis);2896if (binding->input.axis.axis_min < binding->input.axis.axis_max) {2897valid_input_range = (value >= binding->input.axis.axis_min && value <= binding->input.axis.axis_max);2898} else {2899valid_input_range = (value >= binding->input.axis.axis_max && value <= binding->input.axis.axis_min);2900}2901if (valid_input_range) {2902if (binding->input.axis.axis_min != binding->output.axis.axis_min || binding->input.axis.axis_max != binding->output.axis.axis_max) {2903float normalized_value = (float)(value - binding->input.axis.axis_min) / (binding->input.axis.axis_max - binding->input.axis.axis_min);2904value = binding->output.axis.axis_min + (int)(normalized_value * (binding->output.axis.axis_max - binding->output.axis.axis_min));2905}2906} else {2907value = 0;2908}2909} else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON) {2910if (SDL_GetJoystickButton(gamepad->joystick, binding->input.button)) {2911value = binding->output.axis.axis_max;2912}2913} else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT) {2914int hat_mask = SDL_GetJoystickHat(gamepad->joystick, binding->input.hat.hat);2915if (hat_mask & binding->input.hat.hat_mask) {2916value = binding->output.axis.axis_max;2917}2918}29192920if (binding->output.axis.axis_min < binding->output.axis.axis_max) {2921valid_output_range = (value >= binding->output.axis.axis_min && value <= binding->output.axis.axis_max);2922} else {2923valid_output_range = (value >= binding->output.axis.axis_max && value <= binding->output.axis.axis_min);2924}2925// If the value is zero, there might be another binding that makes it non-zero2926if (value != 0 && valid_output_range) {2927result = (Sint16)value;2928break;2929}2930}2931}2932}2933SDL_UnlockJoysticks();29342935return result;2936}29372938/**2939* Return whether a gamepad has a given button2940*/2941bool SDL_GamepadHasButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)2942{2943bool result = false;29442945SDL_LockJoysticks();2946{2947int i;29482949CHECK_GAMEPAD_MAGIC(gamepad, false);29502951for (i = 0; i < gamepad->num_bindings; ++i) {2952const SDL_GamepadBinding *binding = &gamepad->bindings[i];2953if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && binding->output.button == button) {2954result = true;2955break;2956}2957}2958}2959SDL_UnlockJoysticks();29602961return result;2962}29632964/*2965* Get the current state of a button on a gamepad2966*/2967bool SDL_GetGamepadButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)2968{2969bool result = false;29702971SDL_LockJoysticks();2972{2973int i;29742975CHECK_GAMEPAD_MAGIC(gamepad, false);29762977for (i = 0; i < gamepad->num_bindings; ++i) {2978const SDL_GamepadBinding *binding = &gamepad->bindings[i];2979if (binding->output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && binding->output.button == button) {2980if (binding->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) {2981bool valid_input_range;29822983int value = SDL_GetJoystickAxis(gamepad->joystick, binding->input.axis.axis);2984int threshold = binding->input.axis.axis_min + (binding->input.axis.axis_max - binding->input.axis.axis_min) / 2;2985if (binding->input.axis.axis_min < binding->input.axis.axis_max) {2986valid_input_range = (value >= binding->input.axis.axis_min && value <= binding->input.axis.axis_max);2987if (valid_input_range) {2988result |= (value >= threshold);2989}2990} else {2991valid_input_range = (value >= binding->input.axis.axis_max && value <= binding->input.axis.axis_min);2992if (valid_input_range) {2993result |= (value <= threshold);2994}2995}2996} else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_BUTTON) {2997result |= SDL_GetJoystickButton(gamepad->joystick, binding->input.button);2998} else if (binding->input_type == SDL_GAMEPAD_BINDTYPE_HAT) {2999int hat_mask = SDL_GetJoystickHat(gamepad->joystick, binding->input.hat.hat);3000result |= ((hat_mask & binding->input.hat.hat_mask) != 0);3001}3002}3003}3004}3005SDL_UnlockJoysticks();30063007return result;3008}30093010/**3011* Get the label of a button on a gamepad.3012*/3013static SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForFaceStyle(SDL_GamepadFaceStyle face_style, SDL_GamepadButton button)3014{3015SDL_GamepadButtonLabel label = SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN;30163017switch (face_style) {3018case SDL_GAMEPAD_FACE_STYLE_ABXY:3019switch (button) {3020case SDL_GAMEPAD_BUTTON_SOUTH:3021label = SDL_GAMEPAD_BUTTON_LABEL_A;3022break;3023case SDL_GAMEPAD_BUTTON_EAST:3024label = SDL_GAMEPAD_BUTTON_LABEL_B;3025break;3026case SDL_GAMEPAD_BUTTON_WEST:3027label = SDL_GAMEPAD_BUTTON_LABEL_X;3028break;3029case SDL_GAMEPAD_BUTTON_NORTH:3030label = SDL_GAMEPAD_BUTTON_LABEL_Y;3031break;3032default:3033break;3034}3035break;3036case SDL_GAMEPAD_FACE_STYLE_BAYX:3037switch (button) {3038case SDL_GAMEPAD_BUTTON_SOUTH:3039label = SDL_GAMEPAD_BUTTON_LABEL_B;3040break;3041case SDL_GAMEPAD_BUTTON_EAST:3042label = SDL_GAMEPAD_BUTTON_LABEL_A;3043break;3044case SDL_GAMEPAD_BUTTON_WEST:3045label = SDL_GAMEPAD_BUTTON_LABEL_Y;3046break;3047case SDL_GAMEPAD_BUTTON_NORTH:3048label = SDL_GAMEPAD_BUTTON_LABEL_X;3049break;3050default:3051break;3052}3053break;3054case SDL_GAMEPAD_FACE_STYLE_SONY:3055switch (button) {3056case SDL_GAMEPAD_BUTTON_SOUTH:3057label = SDL_GAMEPAD_BUTTON_LABEL_CROSS;3058break;3059case SDL_GAMEPAD_BUTTON_EAST:3060label = SDL_GAMEPAD_BUTTON_LABEL_CIRCLE;3061break;3062case SDL_GAMEPAD_BUTTON_WEST:3063label = SDL_GAMEPAD_BUTTON_LABEL_SQUARE;3064break;3065case SDL_GAMEPAD_BUTTON_NORTH:3066label = SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE;3067break;3068default:3069break;3070}3071break;3072default:3073break;3074}3075return label;3076}30773078/**3079* Get the label of a button on a gamepad.3080*/3081SDL_GamepadButtonLabel SDL_GetGamepadButtonLabelForType(SDL_GamepadType type, SDL_GamepadButton button)3082{3083return SDL_GetGamepadButtonLabelForFaceStyle(SDL_GetGamepadFaceStyleForGamepadType(type), button);3084}30853086/**3087* Get the label of a button on a gamepad.3088*/3089SDL_GamepadButtonLabel SDL_GetGamepadButtonLabel(SDL_Gamepad *gamepad, SDL_GamepadButton button)3090{3091SDL_GamepadFaceStyle face_style;30923093SDL_LockJoysticks();3094{3095CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN);30963097face_style = gamepad->face_style;3098}3099SDL_UnlockJoysticks();31003101return SDL_GetGamepadButtonLabelForFaceStyle(face_style, button);3102}31033104/**3105* Get the number of touchpads on a gamepad.3106*/3107int SDL_GetNumGamepadTouchpads(SDL_Gamepad *gamepad)3108{3109int result = 0;31103111SDL_LockJoysticks();3112{3113SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);3114if (joystick) {3115result = joystick->ntouchpads;3116}3117}3118SDL_UnlockJoysticks();31193120return result;3121}31223123/**3124* Get the number of supported simultaneous fingers on a touchpad on a gamepad.3125*/3126int SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepad, int touchpad)3127{3128int result = 0;31293130SDL_LockJoysticks();3131{3132SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);3133if (joystick) {3134if (touchpad >= 0 && touchpad < joystick->ntouchpads) {3135result = joystick->touchpads[touchpad].nfingers;3136}3137}3138}3139SDL_UnlockJoysticks();31403141return result;3142}31433144/**3145* Get the current state of a finger on a touchpad on a gamepad.3146*/3147bool SDL_GetGamepadTouchpadFinger(SDL_Gamepad *gamepad, int touchpad, int finger, bool *down, float *x, float *y, float *pressure)3148{3149bool result = false;31503151SDL_LockJoysticks();3152{3153SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);3154if (joystick) {3155if (touchpad >= 0 && touchpad < joystick->ntouchpads) {3156SDL_JoystickTouchpadInfo *touchpad_info = &joystick->touchpads[touchpad];3157if (finger >= 0 && finger < touchpad_info->nfingers) {3158SDL_JoystickTouchpadFingerInfo *info = &touchpad_info->fingers[finger];31593160if (down) {3161*down = info->down;3162}3163if (x) {3164*x = info->x;3165}3166if (y) {3167*y = info->y;3168}3169if (pressure) {3170*pressure = info->pressure;3171}3172result = true;3173} else {3174result = SDL_InvalidParamError("finger");3175}3176} else {3177result = SDL_InvalidParamError("touchpad");3178}3179}3180}3181SDL_UnlockJoysticks();31823183return result;3184}31853186/**3187* Return whether a gamepad has a particular sensor.3188*/3189bool SDL_GamepadHasSensor(SDL_Gamepad *gamepad, SDL_SensorType type)3190{3191bool result = false;31923193SDL_LockJoysticks();3194{3195SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);3196if (joystick) {3197int i;3198for (i = 0; i < joystick->nsensors; ++i) {3199if (joystick->sensors[i].type == type) {3200result = true;3201break;3202}3203}3204}3205}3206SDL_UnlockJoysticks();32073208return result;3209}32103211/*3212* Set whether data reporting for a gamepad sensor is enabled3213*/3214bool SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type, bool enabled)3215{3216SDL_LockJoysticks();3217{3218SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);3219if (joystick) {3220int i;3221for (i = 0; i < joystick->nsensors; ++i) {3222SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];32233224if (sensor->type == type) {3225if (sensor->enabled == (enabled != false)) {3226SDL_UnlockJoysticks();3227return true;3228}32293230if (type == SDL_SENSOR_ACCEL && joystick->accel_sensor) {3231if (enabled) {3232joystick->accel = SDL_OpenSensor(joystick->accel_sensor);3233if (!joystick->accel) {3234SDL_UnlockJoysticks();3235return false;3236}3237} else {3238if (joystick->accel) {3239SDL_CloseSensor(joystick->accel);3240joystick->accel = NULL;3241}3242}3243} else if (type == SDL_SENSOR_GYRO && joystick->gyro_sensor) {3244if (enabled) {3245joystick->gyro = SDL_OpenSensor(joystick->gyro_sensor);3246if (!joystick->gyro) {3247SDL_UnlockJoysticks();3248return false;3249}3250} else {3251if (joystick->gyro) {3252SDL_CloseSensor(joystick->gyro);3253joystick->gyro = NULL;3254}3255}3256} else {3257if (enabled) {3258if (joystick->nsensors_enabled == 0) {3259if (!joystick->driver->SetSensorsEnabled(joystick, true)) {3260SDL_UnlockJoysticks();3261return false;3262}3263}3264++joystick->nsensors_enabled;3265} else {3266if (joystick->nsensors_enabled == 1) {3267if (!joystick->driver->SetSensorsEnabled(joystick, false)) {3268SDL_UnlockJoysticks();3269return false;3270}3271}3272--joystick->nsensors_enabled;3273}3274}32753276sensor->enabled = enabled;3277SDL_UnlockJoysticks();3278return true;3279}3280}3281}3282}3283SDL_UnlockJoysticks();32843285return SDL_Unsupported();3286}32873288/*3289* Query whether sensor data reporting is enabled for a gamepad3290*/3291bool SDL_GamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type)3292{3293bool result = false;32943295SDL_LockJoysticks();3296{3297SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);3298if (joystick) {3299int i;3300for (i = 0; i < joystick->nsensors; ++i) {3301if (joystick->sensors[i].type == type) {3302result = joystick->sensors[i].enabled;3303break;3304}3305}3306}3307}3308SDL_UnlockJoysticks();33093310return result;3311}33123313/*3314* Get the data rate of a gamepad sensor.3315*/3316float SDL_GetGamepadSensorDataRate(SDL_Gamepad *gamepad, SDL_SensorType type)3317{3318float result = 0.0f;33193320SDL_LockJoysticks();3321{3322SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);3323if (joystick) {3324int i;3325for (i = 0; i < joystick->nsensors; ++i) {3326SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];33273328if (sensor->type == type) {3329result = sensor->rate;3330break;3331}3332}3333}3334}3335SDL_UnlockJoysticks();33363337return result;3338}33393340/*3341* Get the current state of a gamepad sensor.3342*/3343bool SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values)3344{3345SDL_LockJoysticks();3346{3347SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);3348if (joystick) {3349int i;3350for (i = 0; i < joystick->nsensors; ++i) {3351SDL_JoystickSensorInfo *sensor = &joystick->sensors[i];33523353if (sensor->type == type) {3354num_values = SDL_min(num_values, SDL_arraysize(sensor->data));3355SDL_memcpy(data, sensor->data, num_values * sizeof(*data));3356SDL_UnlockJoysticks();3357return true;3358}3359}3360}3361}3362SDL_UnlockJoysticks();33633364return SDL_Unsupported();3365}33663367SDL_JoystickID SDL_GetGamepadID(SDL_Gamepad *gamepad)3368{3369SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);33703371if (!joystick) {3372return 0;3373}3374return SDL_GetJoystickID(joystick);3375}33763377SDL_PropertiesID SDL_GetGamepadProperties(SDL_Gamepad *gamepad)3378{3379SDL_PropertiesID result = 0;33803381SDL_LockJoysticks();3382{3383CHECK_GAMEPAD_MAGIC(gamepad, 0);33843385result = SDL_GetJoystickProperties(gamepad->joystick);3386}3387SDL_UnlockJoysticks();33883389return result;3390}33913392const char *SDL_GetGamepadName(SDL_Gamepad *gamepad)3393{3394const char *result = NULL;33953396SDL_LockJoysticks();3397{3398CHECK_GAMEPAD_MAGIC(gamepad, NULL);33993400if (SDL_strcmp(gamepad->name, "*") == 0 ||3401gamepad->joystick->steam_handle != 0) {3402result = SDL_GetJoystickName(gamepad->joystick);3403} else {3404result = SDL_GetPersistentString(gamepad->name);3405}3406}3407SDL_UnlockJoysticks();34083409return result;3410}34113412const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad)3413{3414SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);34153416if (!joystick) {3417return NULL;3418}3419return SDL_GetJoystickPath(joystick);3420}34213422SDL_GamepadType SDL_GetGamepadType(SDL_Gamepad *gamepad)3423{3424SDL_GamepadType type;3425const SDL_SteamVirtualGamepadInfo *info;34263427SDL_LockJoysticks();3428{3429CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_TYPE_UNKNOWN);34303431info = SDL_GetJoystickVirtualGamepadInfoForID(gamepad->joystick->instance_id);3432if (info) {3433type = info->type;3434} else {3435type = gamepad->type;3436}3437}3438SDL_UnlockJoysticks();34393440return type;3441}34423443SDL_GamepadType SDL_GetRealGamepadType(SDL_Gamepad *gamepad)3444{3445SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);34463447if (!joystick) {3448return SDL_GAMEPAD_TYPE_UNKNOWN;3449}3450return SDL_GetGamepadTypeFromGUID(SDL_GetJoystickGUID(joystick), SDL_GetJoystickName(joystick));3451}34523453int SDL_GetGamepadPlayerIndex(SDL_Gamepad *gamepad)3454{3455SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);34563457if (!joystick) {3458return -1;3459}3460return SDL_GetJoystickPlayerIndex(joystick);3461}34623463/**3464* Set the player index of an opened gamepad3465*/3466bool SDL_SetGamepadPlayerIndex(SDL_Gamepad *gamepad, int player_index)3467{3468SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);34693470if (!joystick) {3471// SDL_SetError() will have been called already by SDL_GetGamepadJoystick()3472return false;3473}3474return SDL_SetJoystickPlayerIndex(joystick, player_index);3475}34763477Uint16 SDL_GetGamepadVendor(SDL_Gamepad *gamepad)3478{3479SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);34803481if (!joystick) {3482return 0;3483}3484return SDL_GetJoystickVendor(joystick);3485}34863487Uint16 SDL_GetGamepadProduct(SDL_Gamepad *gamepad)3488{3489SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);34903491if (!joystick) {3492return 0;3493}3494return SDL_GetJoystickProduct(joystick);3495}34963497Uint16 SDL_GetGamepadProductVersion(SDL_Gamepad *gamepad)3498{3499SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);35003501if (!joystick) {3502return 0;3503}3504return SDL_GetJoystickProductVersion(joystick);3505}35063507Uint16 SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepad)3508{3509SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);35103511if (!joystick) {3512return 0;3513}3514return SDL_GetJoystickFirmwareVersion(joystick);3515}35163517const char * SDL_GetGamepadSerial(SDL_Gamepad *gamepad)3518{3519SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);35203521if (!joystick) {3522return NULL;3523}3524return SDL_GetJoystickSerial(joystick);35253526}35273528Uint64 SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad)3529{3530Uint64 handle = 0;35313532SDL_LockJoysticks();3533{3534CHECK_GAMEPAD_MAGIC(gamepad, 0);35353536handle = gamepad->joystick->steam_handle;3537}3538SDL_UnlockJoysticks();35393540return handle;3541}35423543SDL_JoystickConnectionState SDL_GetGamepadConnectionState(SDL_Gamepad *gamepad)3544{3545SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);35463547if (!joystick) {3548return SDL_JOYSTICK_CONNECTION_INVALID;3549}3550return SDL_GetJoystickConnectionState(joystick);3551}35523553SDL_PowerState SDL_GetGamepadPowerInfo(SDL_Gamepad *gamepad, int *percent)3554{3555SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);35563557if (percent) {3558*percent = -1;3559}3560if (!joystick) {3561return SDL_POWERSTATE_ERROR;3562}3563return SDL_GetJoystickPowerInfo(joystick, percent);3564}35653566/*3567* Return if the gamepad in question is currently attached to the system,3568* \return 0 if not plugged in, 1 if still present.3569*/3570bool SDL_GamepadConnected(SDL_Gamepad *gamepad)3571{3572SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);35733574if (!joystick) {3575return false;3576}3577return SDL_JoystickConnected(joystick);3578}35793580/*3581* Get the joystick for this gamepad3582*/3583SDL_Joystick *SDL_GetGamepadJoystick(SDL_Gamepad *gamepad)3584{3585SDL_Joystick *joystick;35863587SDL_LockJoysticks();3588{3589CHECK_GAMEPAD_MAGIC(gamepad, NULL);35903591joystick = gamepad->joystick;3592}3593SDL_UnlockJoysticks();35943595return joystick;3596}35973598/*3599* Return the SDL_Gamepad associated with an instance id.3600*/3601SDL_Gamepad *SDL_GetGamepadFromID(SDL_JoystickID joyid)3602{3603SDL_Gamepad *gamepad;36043605SDL_LockJoysticks();3606gamepad = SDL_gamepads;3607while (gamepad) {3608if (gamepad->joystick->instance_id == joyid) {3609SDL_UnlockJoysticks();3610return gamepad;3611}3612gamepad = gamepad->next;3613}3614SDL_UnlockJoysticks();3615return NULL;3616}36173618/**3619* Return the SDL_Gamepad associated with a player index.3620*/3621SDL_Gamepad *SDL_GetGamepadFromPlayerIndex(int player_index)3622{3623SDL_Gamepad *result = NULL;36243625SDL_LockJoysticks();3626{3627SDL_Joystick *joystick = SDL_GetJoystickFromPlayerIndex(player_index);3628if (joystick) {3629result = SDL_GetGamepadFromID(joystick->instance_id);3630}3631}3632SDL_UnlockJoysticks();36333634return result;3635}36363637/*3638* Get the SDL joystick layer bindings for this gamepad3639*/3640SDL_GamepadBinding **SDL_GetGamepadBindings(SDL_Gamepad *gamepad, int *count)3641{3642SDL_GamepadBinding **bindings = NULL;36433644if (count) {3645*count = 0;3646}36473648SDL_LockJoysticks();3649{3650CHECK_GAMEPAD_MAGIC(gamepad, NULL);36513652size_t pointers_size = ((gamepad->num_bindings + 1) * sizeof(SDL_GamepadBinding *));3653size_t elements_size = (gamepad->num_bindings * sizeof(SDL_GamepadBinding));3654bindings = (SDL_GamepadBinding **)SDL_malloc(pointers_size + elements_size);3655if (bindings) {3656SDL_GamepadBinding *binding = (SDL_GamepadBinding *)((Uint8 *)bindings + pointers_size);3657int i;3658for (i = 0; i < gamepad->num_bindings; ++i, ++binding) {3659bindings[i] = binding;3660SDL_copyp(binding, &gamepad->bindings[i]);3661}3662bindings[i] = NULL;36633664if (count) {3665*count = gamepad->num_bindings;3666}3667}3668}3669SDL_UnlockJoysticks();36703671return bindings;3672}36733674bool SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)3675{3676SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);36773678if (!joystick) {3679return false;3680}3681return SDL_RumbleJoystick(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms);3682}36833684bool SDL_RumbleGamepadTriggers(SDL_Gamepad *gamepad, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms)3685{3686SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);36873688if (!joystick) {3689return false;3690}3691return SDL_RumbleJoystickTriggers(joystick, left_rumble, right_rumble, duration_ms);3692}36933694bool SDL_SetGamepadLED(SDL_Gamepad *gamepad, Uint8 red, Uint8 green, Uint8 blue)3695{3696SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);36973698if (!joystick) {3699return false;3700}3701return SDL_SetJoystickLED(joystick, red, green, blue);3702}37033704bool SDL_SendGamepadEffect(SDL_Gamepad *gamepad, const void *data, int size)3705{3706SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);37073708if (!joystick) {3709return false;3710}3711return SDL_SendJoystickEffect(joystick, data, size);3712}37133714void SDL_CloseGamepad(SDL_Gamepad *gamepad)3715{3716SDL_Gamepad *gamepadlist, *gamepadlistprev;37173718SDL_LockJoysticks();37193720if (!SDL_ObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD)) {3721SDL_UnlockJoysticks();3722return;3723}37243725// First decrement ref count3726if (--gamepad->ref_count > 0) {3727SDL_UnlockJoysticks();3728return;3729}37303731SDL_CloseJoystick(gamepad->joystick);37323733gamepadlist = SDL_gamepads;3734gamepadlistprev = NULL;3735while (gamepadlist) {3736if (gamepad == gamepadlist) {3737if (gamepadlistprev) {3738// unlink this entry3739gamepadlistprev->next = gamepadlist->next;3740} else {3741SDL_gamepads = gamepad->next;3742}3743break;3744}3745gamepadlistprev = gamepadlist;3746gamepadlist = gamepadlist->next;3747}37483749SDL_SetObjectValid(gamepad, SDL_OBJECT_TYPE_GAMEPAD, false);3750SDL_free(gamepad->bindings);3751SDL_free(gamepad->last_match_axis);3752SDL_free(gamepad->last_hat_mask);3753SDL_free(gamepad);37543755SDL_UnlockJoysticks();3756}37573758/*3759* Quit the gamepad subsystem3760*/3761void SDL_QuitGamepads(void)3762{3763SDL_Gamepad *gamepad;37643765SDL_LockJoysticks();37663767for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {3768SDL_PrivateGamepadRemoved(gamepad->joystick->instance_id);3769}37703771SDL_gamepads_initialized = false;37723773SDL_RemoveEventWatch(SDL_GamepadEventWatcher, NULL);37743775while (SDL_gamepads) {3776SDL_gamepads->ref_count = 1;3777SDL_CloseGamepad(SDL_gamepads);3778}37793780SDL_UnlockJoysticks();3781}37823783void SDL_QuitGamepadMappings(void)3784{3785GamepadMapping_t *pGamepadMap;37863787SDL_AssertJoysticksLocked();37883789while (s_pSupportedGamepads) {3790pGamepadMap = s_pSupportedGamepads;3791s_pSupportedGamepads = s_pSupportedGamepads->next;3792SDL_free(pGamepadMap->name);3793SDL_free(pGamepadMap->mapping);3794SDL_free(pGamepadMap);3795}37963797SDL_FreeVIDPIDList(&SDL_allowed_gamepads);3798SDL_FreeVIDPIDList(&SDL_ignored_gamepads);37993800if (s_gamepadInstanceIDs) {3801SDL_DestroyHashTable(s_gamepadInstanceIDs);3802s_gamepadInstanceIDs = NULL;3803}3804}38053806/*3807* Event filter to transform joystick events into appropriate gamepad ones3808*/3809static void SDL_SendGamepadAxis(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadAxis axis, Sint16 value)3810{3811SDL_AssertJoysticksLocked();38123813// translate the event, if desired3814if (SDL_EventEnabled(SDL_EVENT_GAMEPAD_AXIS_MOTION)) {3815SDL_Event event;3816event.type = SDL_EVENT_GAMEPAD_AXIS_MOTION;3817event.common.timestamp = timestamp;3818event.gaxis.which = gamepad->joystick->instance_id;3819event.gaxis.axis = axis;3820event.gaxis.value = value;3821SDL_PushEvent(&event);3822}3823}38243825static void SDL_SendGamepadButton(Uint64 timestamp, SDL_Gamepad *gamepad, SDL_GamepadButton button, bool down)3826{3827SDL_Event event;38283829SDL_AssertJoysticksLocked();38303831if (button == SDL_GAMEPAD_BUTTON_INVALID) {3832return;3833}38343835if (down) {3836event.type = SDL_EVENT_GAMEPAD_BUTTON_DOWN;3837} else {3838event.type = SDL_EVENT_GAMEPAD_BUTTON_UP;3839}38403841if (button == SDL_GAMEPAD_BUTTON_GUIDE) {3842Uint64 now = SDL_GetTicks();3843if (down) {3844gamepad->guide_button_down = now;38453846if (gamepad->joystick->delayed_guide_button) {3847// Skip duplicate press3848return;3849}3850} else {3851if (now < (gamepad->guide_button_down + SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS)) {3852gamepad->joystick->delayed_guide_button = true;3853return;3854}3855gamepad->joystick->delayed_guide_button = false;3856}3857}38583859// translate the event, if desired3860if (SDL_EventEnabled(event.type)) {3861event.common.timestamp = timestamp;3862event.gbutton.which = gamepad->joystick->instance_id;3863event.gbutton.button = button;3864event.gbutton.down = down;3865SDL_PushEvent(&event);3866}3867}38683869static const Uint32 SDL_gamepad_event_list[] = {3870SDL_EVENT_GAMEPAD_AXIS_MOTION,3871SDL_EVENT_GAMEPAD_BUTTON_DOWN,3872SDL_EVENT_GAMEPAD_BUTTON_UP,3873SDL_EVENT_GAMEPAD_ADDED,3874SDL_EVENT_GAMEPAD_REMOVED,3875SDL_EVENT_GAMEPAD_REMAPPED,3876SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN,3877SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION,3878SDL_EVENT_GAMEPAD_TOUCHPAD_UP,3879SDL_EVENT_GAMEPAD_SENSOR_UPDATE,3880};38813882void SDL_SetGamepadEventsEnabled(bool enabled)3883{3884unsigned int i;38853886for (i = 0; i < SDL_arraysize(SDL_gamepad_event_list); ++i) {3887SDL_SetEventEnabled(SDL_gamepad_event_list[i], enabled);3888}3889}38903891bool SDL_GamepadEventsEnabled(void)3892{3893bool enabled = false;3894unsigned int i;38953896for (i = 0; i < SDL_arraysize(SDL_gamepad_event_list); ++i) {3897enabled = SDL_EventEnabled(SDL_gamepad_event_list[i]);3898if (enabled) {3899break;3900}3901}3902return enabled;3903}39043905void SDL_GamepadHandleDelayedGuideButton(SDL_Joystick *joystick)3906{3907SDL_Gamepad *gamepad;39083909SDL_AssertJoysticksLocked();39103911for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {3912if (gamepad->joystick == joystick) {3913SDL_SendGamepadButton(0, gamepad, SDL_GAMEPAD_BUTTON_GUIDE, false);39143915// Make sure we send an update complete event for this change3916if (!gamepad->joystick->update_complete) {3917gamepad->joystick->update_complete = SDL_GetTicksNS();3918}3919break;3920}3921}3922}39233924const char *SDL_GetGamepadAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)3925{3926const char *result = NULL;3927#ifdef SDL_JOYSTICK_MFI3928const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button);39293930SDL_LockJoysticks();3931{3932CHECK_GAMEPAD_MAGIC(gamepad, NULL);39333934result = IOS_GetAppleSFSymbolsNameForButton(gamepad, button);3935}3936SDL_UnlockJoysticks();3937#endif3938return result;3939}39403941const char *SDL_GetGamepadAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)3942{3943const char *result = NULL;3944#ifdef SDL_JOYSTICK_MFI3945const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis);39463947SDL_LockJoysticks();3948{3949CHECK_GAMEPAD_MAGIC(gamepad, NULL);39503951result = IOS_GetAppleSFSymbolsNameForAxis(gamepad, axis);3952}3953SDL_UnlockJoysticks();3954#endif3955return result;3956}395739583959