diff options
Diffstat (limited to 'source/3rd-party/SDL2/src/joystick/SDL_gamecontroller.c')
-rw-r--r-- | source/3rd-party/SDL2/src/joystick/SDL_gamecontroller.c | 2075 |
1 files changed, 2075 insertions, 0 deletions
diff --git a/source/3rd-party/SDL2/src/joystick/SDL_gamecontroller.c b/source/3rd-party/SDL2/src/joystick/SDL_gamecontroller.c new file mode 100644 index 0000000..eb1ef06 --- /dev/null +++ b/source/3rd-party/SDL2/src/joystick/SDL_gamecontroller.c @@ -0,0 +1,2075 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../SDL_internal.h" + +/* This is the game controller API for Simple DirectMedia Layer */ + +#include "SDL_events.h" +#include "SDL_assert.h" +#include "SDL_hints.h" +#include "SDL_timer.h" +#include "SDL_sysjoystick.h" +#include "SDL_joystick_c.h" +#include "SDL_gamecontrollerdb.h" + +#if !SDL_EVENTS_DISABLED +#include "../events/SDL_events_c.h" +#endif + +#if defined(__ANDROID__) +#include "SDL_system.h" +#endif + + +/* Many controllers turn the center button into an instantaneous button press */ +#define SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS 250 + +#define SDL_CONTROLLER_PLATFORM_FIELD "platform:" + +/* a list of currently opened game controllers */ +static SDL_GameController *SDL_gamecontrollers = NULL; + +typedef struct +{ + SDL_GameControllerBindType inputType; + union + { + int button; + + struct { + int axis; + int axis_min; + int axis_max; + } axis; + + struct { + int hat; + int hat_mask; + } hat; + + } input; + + SDL_GameControllerBindType outputType; + union + { + SDL_GameControllerButton button; + + struct { + SDL_GameControllerAxis axis; + int axis_min; + int axis_max; + } axis; + + } output; + +} SDL_ExtendedGameControllerBind; + +/* our hard coded list of mapping support */ +typedef enum +{ + SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT, + SDL_CONTROLLER_MAPPING_PRIORITY_API, + SDL_CONTROLLER_MAPPING_PRIORITY_USER, +} SDL_ControllerMappingPriority; + +typedef struct _ControllerMapping_t +{ + SDL_JoystickGUID guid; + char *name; + char *mapping; + SDL_ControllerMappingPriority priority; + struct _ControllerMapping_t *next; +} ControllerMapping_t; + +static SDL_JoystickGUID s_zeroGUID; +static ControllerMapping_t *s_pSupportedControllers = NULL; +static ControllerMapping_t *s_pDefaultMapping = NULL; +static ControllerMapping_t *s_pHIDAPIMapping = NULL; +static ControllerMapping_t *s_pXInputMapping = NULL; + +/* The SDL game controller structure */ +struct _SDL_GameController +{ + SDL_Joystick *joystick; /* underlying joystick device */ + int ref_count; + + const char *name; + int num_bindings; + SDL_ExtendedGameControllerBind *bindings; + SDL_ExtendedGameControllerBind **last_match_axis; + Uint8 *last_hat_mask; + Uint32 guide_button_down; + + struct _SDL_GameController *next; /* pointer to next game controller we have allocated */ +}; + + +typedef struct +{ + int num_entries; + int max_entries; + Uint32 *entries; +} SDL_vidpid_list; + +static SDL_vidpid_list SDL_allowed_controllers; +static SDL_vidpid_list SDL_ignored_controllers; + +static void +SDL_LoadVIDPIDListFromHint(const char *hint, SDL_vidpid_list *list) +{ + Uint32 entry; + char *spot; + char *file = NULL; + + list->num_entries = 0; + + if (hint && *hint == '@') { + spot = file = (char *)SDL_LoadFile(hint+1, NULL); + } else { + spot = (char *)hint; + } + + if (!spot) { + return; + } + + while ((spot = SDL_strstr(spot, "0x")) != NULL) { + entry = (Uint16)SDL_strtol(spot, &spot, 0); + entry <<= 16; + spot = SDL_strstr(spot, "0x"); + if (!spot) { + break; + } + entry |= (Uint16)SDL_strtol(spot, &spot, 0); + + if (list->num_entries == list->max_entries) { + int max_entries = list->max_entries + 16; + Uint32 *entries = (Uint32 *)SDL_realloc(list->entries, max_entries*sizeof(*list->entries)); + if (entries == NULL) { + /* Out of memory, go with what we have already */ + break; + } + list->entries = entries; + list->max_entries = max_entries; + } + list->entries[list->num_entries++] = entry; + } + + if (file) { + SDL_free(file); + } +} + +static void SDLCALL +SDL_GameControllerIgnoreDevicesChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_LoadVIDPIDListFromHint(hint, &SDL_ignored_controllers); +} + +static void SDLCALL +SDL_GameControllerIgnoreDevicesExceptChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_LoadVIDPIDListFromHint(hint, &SDL_allowed_controllers); +} + +static int SDL_PrivateGameControllerAxis(SDL_GameController * gamecontroller, SDL_GameControllerAxis axis, Sint16 value); +static int SDL_PrivateGameControllerButton(SDL_GameController * gamecontroller, SDL_GameControllerButton button, Uint8 state); + +/* + * If there is an existing add event in the queue, it needs to be modified + * to have the right value for which, because the number of controllers in + * the system is now one less. + */ +static void UpdateEventsForDeviceRemoval() +{ + int i, num_events; + SDL_Event *events; + + num_events = SDL_PeepEvents(NULL, 0, SDL_PEEKEVENT, SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEADDED); + if (num_events <= 0) { + return; + } + + events = SDL_stack_alloc(SDL_Event, num_events); + if (!events) { + return; + } + + num_events = SDL_PeepEvents(events, num_events, SDL_GETEVENT, SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEADDED); + for (i = 0; i < num_events; ++i) { + --events[i].cdevice.which; + } + SDL_PeepEvents(events, num_events, SDL_ADDEVENT, 0, 0); + + SDL_stack_free(events); +} + +static SDL_bool HasSameOutput(SDL_ExtendedGameControllerBind *a, SDL_ExtendedGameControllerBind *b) +{ + if (a->outputType != b->outputType) { + return SDL_FALSE; + } + + if (a->outputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + return (a->output.axis.axis == b->output.axis.axis); + } else { + return (a->output.button == b->output.button); + } +} + +static void ResetOutput(SDL_GameController *gamecontroller, SDL_ExtendedGameControllerBind *bind) +{ + if (bind->outputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + SDL_PrivateGameControllerAxis(gamecontroller, bind->output.axis.axis, 0); + } else { + SDL_PrivateGameControllerButton(gamecontroller, bind->output.button, SDL_RELEASED); + } +} + +static void HandleJoystickAxis(SDL_GameController *gamecontroller, int axis, int value) +{ + int i; + SDL_ExtendedGameControllerBind *last_match = gamecontroller->last_match_axis[axis]; + SDL_ExtendedGameControllerBind *match = NULL; + + for (i = 0; i < gamecontroller->num_bindings; ++i) { + SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i]; + if (binding->inputType == SDL_CONTROLLER_BINDTYPE_AXIS && + axis == binding->input.axis.axis) { + if (binding->input.axis.axis_min < binding->input.axis.axis_max) { + if (value >= binding->input.axis.axis_min && + value <= binding->input.axis.axis_max) { + match = binding; + break; + } + } else { + if (value >= binding->input.axis.axis_max && + value <= binding->input.axis.axis_min) { + match = binding; + break; + } + } + } + } + + if (last_match && (!match || !HasSameOutput(last_match, match))) { + /* Clear the last input that this axis generated */ + ResetOutput(gamecontroller, last_match); + } + + if (match) { + if (match->outputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + if (match->input.axis.axis_min != match->output.axis.axis_min || match->input.axis.axis_max != match->output.axis.axis_max) { + float normalized_value = (float)(value - match->input.axis.axis_min) / (match->input.axis.axis_max - match->input.axis.axis_min); + value = match->output.axis.axis_min + (int)(normalized_value * (match->output.axis.axis_max - match->output.axis.axis_min)); + } + SDL_PrivateGameControllerAxis(gamecontroller, match->output.axis.axis, (Sint16)value); + } else { + Uint8 state; + int threshold = match->input.axis.axis_min + (match->input.axis.axis_max - match->input.axis.axis_min) / 2; + if (match->input.axis.axis_max < match->input.axis.axis_min) { + state = (value <= threshold) ? SDL_PRESSED : SDL_RELEASED; + } else { + state = (value >= threshold) ? SDL_PRESSED : SDL_RELEASED; + } + SDL_PrivateGameControllerButton(gamecontroller, match->output.button, state); + } + } + gamecontroller->last_match_axis[axis] = match; +} + +static void HandleJoystickButton(SDL_GameController *gamecontroller, int button, Uint8 state) +{ + int i; + + for (i = 0; i < gamecontroller->num_bindings; ++i) { + SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i]; + if (binding->inputType == SDL_CONTROLLER_BINDTYPE_BUTTON && + button == binding->input.button) { + if (binding->outputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + int value = state ? binding->output.axis.axis_max : binding->output.axis.axis_min; + SDL_PrivateGameControllerAxis(gamecontroller, binding->output.axis.axis, (Sint16)value); + } else { + SDL_PrivateGameControllerButton(gamecontroller, binding->output.button, state); + } + break; + } + } +} + +static void HandleJoystickHat(SDL_GameController *gamecontroller, int hat, Uint8 value) +{ + int i; + Uint8 last_mask = gamecontroller->last_hat_mask[hat]; + Uint8 changed_mask = (last_mask ^ value); + + for (i = 0; i < gamecontroller->num_bindings; ++i) { + SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i]; + if (binding->inputType == SDL_CONTROLLER_BINDTYPE_HAT && hat == binding->input.hat.hat) { + if ((changed_mask & binding->input.hat.hat_mask) != 0) { + if (value & binding->input.hat.hat_mask) { + if (binding->outputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + SDL_PrivateGameControllerAxis(gamecontroller, binding->output.axis.axis, (Sint16)binding->output.axis.axis_max); + } else { + SDL_PrivateGameControllerButton(gamecontroller, binding->output.button, SDL_PRESSED); + } + } else { + ResetOutput(gamecontroller, binding); + } + } + } + } + gamecontroller->last_hat_mask[hat] = value; +} + +/* + * Event filter to fire controller events from joystick ones + */ +static int SDLCALL SDL_GameControllerEventWatcher(void *userdata, SDL_Event * event) +{ + switch(event->type) { + case SDL_JOYAXISMOTION: + { + SDL_GameController *controllerlist = SDL_gamecontrollers; + while (controllerlist) { + if (controllerlist->joystick->instance_id == event->jaxis.which) { + HandleJoystickAxis(controllerlist, event->jaxis.axis, event->jaxis.value); + break; + } + controllerlist = controllerlist->next; + } + } + break; + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + { + SDL_GameController *controllerlist = SDL_gamecontrollers; + while (controllerlist) { + if (controllerlist->joystick->instance_id == event->jbutton.which) { + HandleJoystickButton(controllerlist, event->jbutton.button, event->jbutton.state); + break; + } + controllerlist = controllerlist->next; + } + } + break; + case SDL_JOYHATMOTION: + { + SDL_GameController *controllerlist = SDL_gamecontrollers; + while (controllerlist) { + if (controllerlist->joystick->instance_id == event->jhat.which) { + HandleJoystickHat(controllerlist, event->jhat.hat, event->jhat.value); + break; + } + controllerlist = controllerlist->next; + } + } + break; + case SDL_JOYDEVICEADDED: + { + if (SDL_IsGameController(event->jdevice.which)) { + SDL_Event deviceevent; + deviceevent.type = SDL_CONTROLLERDEVICEADDED; + deviceevent.cdevice.which = event->jdevice.which; + SDL_PushEvent(&deviceevent); + } + } + break; + case SDL_JOYDEVICEREMOVED: + { + SDL_GameController *controllerlist = SDL_gamecontrollers; + while (controllerlist) { + if (controllerlist->joystick->instance_id == event->jdevice.which) { + SDL_Event deviceevent; + + deviceevent.type = SDL_CONTROLLERDEVICEREMOVED; + deviceevent.cdevice.which = event->jdevice.which; + SDL_PushEvent(&deviceevent); + + UpdateEventsForDeviceRemoval(); + break; + } + controllerlist = controllerlist->next; + } + } + break; + default: + break; + } + + return 1; +} + +/* + * Helper function to scan the mappings database for a controller with the specified GUID + */ +static ControllerMapping_t *SDL_PrivateGetControllerMappingForGUID(SDL_JoystickGUID *guid, SDL_bool exact_match) +{ + ControllerMapping_t *pSupportedController = s_pSupportedControllers; + while (pSupportedController) { + if (SDL_memcmp(guid, &pSupportedController->guid, sizeof(*guid)) == 0) { + return pSupportedController; + } + pSupportedController = pSupportedController->next; + } + if (!exact_match) { + if (SDL_IsJoystickHIDAPI(*guid)) { + /* This is a HIDAPI device */ + return s_pHIDAPIMapping; + } +#if SDL_JOYSTICK_XINPUT + if (SDL_IsJoystickXInput(*guid)) { + /* This is an XInput device */ + return s_pXInputMapping; + } +#endif + } + return NULL; +} + +static const char* map_StringForControllerAxis[] = { + "leftx", + "lefty", + "rightx", + "righty", + "lefttrigger", + "righttrigger", + NULL +}; + +/* + * convert a string to its enum equivalent + */ +SDL_GameControllerAxis SDL_GameControllerGetAxisFromString(const char *pchString) +{ + int entry; + + if (pchString && (*pchString == '+' || *pchString == '-')) { + ++pchString; + } + + if (!pchString || !pchString[0]) { + return SDL_CONTROLLER_AXIS_INVALID; + } + + for (entry = 0; map_StringForControllerAxis[entry]; ++entry) { + if (!SDL_strcasecmp(pchString, map_StringForControllerAxis[entry])) + return (SDL_GameControllerAxis) entry; + } + return SDL_CONTROLLER_AXIS_INVALID; +} + +/* + * convert an enum to its string equivalent + */ +const char* SDL_GameControllerGetStringForAxis(SDL_GameControllerAxis axis) +{ + if (axis > SDL_CONTROLLER_AXIS_INVALID && axis < SDL_CONTROLLER_AXIS_MAX) { + return map_StringForControllerAxis[axis]; + } + return NULL; +} + +static const char* map_StringForControllerButton[] = { + "a", + "b", + "x", + "y", + "back", + "guide", + "start", + "leftstick", + "rightstick", + "leftshoulder", + "rightshoulder", + "dpup", + "dpdown", + "dpleft", + "dpright", + NULL +}; + +/* + * convert a string to its enum equivalent + */ +SDL_GameControllerButton SDL_GameControllerGetButtonFromString(const char *pchString) +{ + int entry; + if (!pchString || !pchString[0]) + return SDL_CONTROLLER_BUTTON_INVALID; + + for (entry = 0; map_StringForControllerButton[entry]; ++entry) { + if (SDL_strcasecmp(pchString, map_StringForControllerButton[entry]) == 0) + return (SDL_GameControllerButton) entry; + } + return SDL_CONTROLLER_BUTTON_INVALID; +} + +/* + * convert an enum to its string equivalent + */ +const char* SDL_GameControllerGetStringForButton(SDL_GameControllerButton axis) +{ + if (axis > SDL_CONTROLLER_BUTTON_INVALID && axis < SDL_CONTROLLER_BUTTON_MAX) { + return map_StringForControllerButton[axis]; + } + return NULL; +} + +/* + * given a controller button name and a joystick name update our mapping structure with it + */ +static void SDL_PrivateGameControllerParseElement(SDL_GameController *gamecontroller, const char *szGameButton, const char *szJoystickButton) +{ + SDL_ExtendedGameControllerBind bind; + SDL_GameControllerButton button; + SDL_GameControllerAxis axis; + SDL_bool invert_input = SDL_FALSE; + char half_axis_input = 0; + char half_axis_output = 0; + + if (*szGameButton == '+' || *szGameButton == '-') { + half_axis_output = *szGameButton++; + } + + axis = SDL_GameControllerGetAxisFromString(szGameButton); + button = SDL_GameControllerGetButtonFromString(szGameButton); + if (axis != SDL_CONTROLLER_AXIS_INVALID) { + bind.outputType = SDL_CONTROLLER_BINDTYPE_AXIS; + bind.output.axis.axis = axis; + if (axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT || axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + bind.output.axis.axis_min = 0; + bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } else { + if (half_axis_output == '+') { + bind.output.axis.axis_min = 0; + bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } else if (half_axis_output == '-') { + bind.output.axis.axis_min = 0; + bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MIN; + } else { + bind.output.axis.axis_min = SDL_JOYSTICK_AXIS_MIN; + bind.output.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } + } + } else if (button != SDL_CONTROLLER_BUTTON_INVALID) { + bind.outputType = SDL_CONTROLLER_BINDTYPE_BUTTON; + bind.output.button = button; + } else { + SDL_SetError("Unexpected controller element %s", szGameButton); + return; + } + + if (*szJoystickButton == '+' || *szJoystickButton == '-') { + half_axis_input = *szJoystickButton++; + } + if (szJoystickButton[SDL_strlen(szJoystickButton) - 1] == '~') { + invert_input = SDL_TRUE; + } + + if (szJoystickButton[0] == 'a' && SDL_isdigit(szJoystickButton[1])) { + bind.inputType = SDL_CONTROLLER_BINDTYPE_AXIS; + bind.input.axis.axis = SDL_atoi(&szJoystickButton[1]); + if (half_axis_input == '+') { + bind.input.axis.axis_min = 0; + bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } else if (half_axis_input == '-') { + bind.input.axis.axis_min = 0; + bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MIN; + } else { + bind.input.axis.axis_min = SDL_JOYSTICK_AXIS_MIN; + bind.input.axis.axis_max = SDL_JOYSTICK_AXIS_MAX; + } + if (invert_input) { + int tmp = bind.input.axis.axis_min; + bind.input.axis.axis_min = bind.input.axis.axis_max; + bind.input.axis.axis_max = tmp; + } + } else if (szJoystickButton[0] == 'b' && SDL_isdigit(szJoystickButton[1])) { + bind.inputType = SDL_CONTROLLER_BINDTYPE_BUTTON; + bind.input.button = SDL_atoi(&szJoystickButton[1]); + } else if (szJoystickButton[0] == 'h' && SDL_isdigit(szJoystickButton[1]) && + szJoystickButton[2] == '.' && SDL_isdigit(szJoystickButton[3])) { + int hat = SDL_atoi(&szJoystickButton[1]); + int mask = SDL_atoi(&szJoystickButton[3]); + bind.inputType = SDL_CONTROLLER_BINDTYPE_HAT; + bind.input.hat.hat = hat; + bind.input.hat.hat_mask = mask; + } else { + SDL_SetError("Unexpected joystick element: %s", szJoystickButton); + return; + } + + ++gamecontroller->num_bindings; + gamecontroller->bindings = (SDL_ExtendedGameControllerBind *)SDL_realloc(gamecontroller->bindings, gamecontroller->num_bindings * sizeof(*gamecontroller->bindings)); + if (!gamecontroller->bindings) { + gamecontroller->num_bindings = 0; + SDL_OutOfMemory(); + return; + } + gamecontroller->bindings[gamecontroller->num_bindings - 1] = bind; +} + + +/* + * given a controller mapping string update our mapping object + */ +static void +SDL_PrivateGameControllerParseControllerConfigString(SDL_GameController *gamecontroller, const char *pchString) +{ + char szGameButton[20]; + char szJoystickButton[20]; + SDL_bool bGameButton = SDL_TRUE; + int i = 0; + const char *pchPos = pchString; + + SDL_zero(szGameButton); + SDL_zero(szJoystickButton); + + while (pchPos && *pchPos) { + if (*pchPos == ':') { + i = 0; + bGameButton = SDL_FALSE; + } else if (*pchPos == ' ') { + + } else if (*pchPos == ',') { + i = 0; + bGameButton = SDL_TRUE; + SDL_PrivateGameControllerParseElement(gamecontroller, szGameButton, szJoystickButton); + SDL_zero(szGameButton); + SDL_zero(szJoystickButton); + + } else if (bGameButton) { + if (i >= sizeof(szGameButton)) { + SDL_SetError("Button name too large: %s", szGameButton); + return; + } + szGameButton[i] = *pchPos; + i++; + } else { + if (i >= sizeof(szJoystickButton)) { + SDL_SetError("Joystick button name too large: %s", szJoystickButton); + return; + } + szJoystickButton[i] = *pchPos; + i++; + } + pchPos++; + } + + SDL_PrivateGameControllerParseElement(gamecontroller, szGameButton, szJoystickButton); + +} + +/* + * Make a new button mapping struct + */ +static void SDL_PrivateLoadButtonMapping(SDL_GameController *gamecontroller, const char *pchName, const char *pchMapping) +{ + int i; + + gamecontroller->name = pchName; + gamecontroller->num_bindings = 0; + SDL_memset(gamecontroller->last_match_axis, 0, gamecontroller->joystick->naxes * sizeof(*gamecontroller->last_match_axis)); + + SDL_PrivateGameControllerParseControllerConfigString(gamecontroller, pchMapping); + + /* Set the zero point for triggers */ + for (i = 0; i < gamecontroller->num_bindings; ++i) { + SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i]; + if (binding->inputType == SDL_CONTROLLER_BINDTYPE_AXIS && + binding->outputType == SDL_CONTROLLER_BINDTYPE_AXIS && + (binding->output.axis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT || + binding->output.axis.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) { + if (binding->input.axis.axis < gamecontroller->joystick->naxes) { + gamecontroller->joystick->axes[binding->input.axis.axis].value = + gamecontroller->joystick->axes[binding->input.axis.axis].zero = (Sint16)binding->input.axis.axis_min; + } + } + } +} + + +/* + * grab the guid string from a mapping string + */ +static char *SDL_PrivateGetControllerGUIDFromMappingString(const char *pMapping) +{ + const char *pFirstComma = SDL_strchr(pMapping, ','); + if (pFirstComma) { + char *pchGUID = SDL_malloc(pFirstComma - pMapping + 1); + if (!pchGUID) { + SDL_OutOfMemory(); + return NULL; + } + SDL_memcpy(pchGUID, pMapping, pFirstComma - pMapping); + pchGUID[pFirstComma - pMapping] = '\0'; + + /* Convert old style GUIDs to the new style in 2.0.5 */ +#if __WIN32__ + if (SDL_strlen(pchGUID) == 32 && + SDL_memcmp(&pchGUID[20], "504944564944", 12) == 0) { + SDL_memcpy(&pchGUID[20], "000000000000", 12); + SDL_memcpy(&pchGUID[16], &pchGUID[4], 4); + SDL_memcpy(&pchGUID[8], &pchGUID[0], 4); + SDL_memcpy(&pchGUID[0], "03000000", 8); + } +#elif __MACOSX__ + if (SDL_strlen(pchGUID) == 32 && + SDL_memcmp(&pchGUID[4], "000000000000", 12) == 0 && + SDL_memcmp(&pchGUID[20], "000000000000", 12) == 0) { + SDL_memcpy(&pchGUID[20], "000000000000", 12); + SDL_memcpy(&pchGUID[8], &pchGUID[0], 4); + SDL_memcpy(&pchGUID[0], "03000000", 8); + } +#endif + return pchGUID; + } + return NULL; +} + + +/* + * grab the name string from a mapping string + */ +static char *SDL_PrivateGetControllerNameFromMappingString(const char *pMapping) +{ + const char *pFirstComma, *pSecondComma; + char *pchName; + + pFirstComma = SDL_strchr(pMapping, ','); + if (!pFirstComma) + return NULL; + + pSecondComma = SDL_strchr(pFirstComma + 1, ','); + if (!pSecondComma) + return NULL; + + pchName = SDL_malloc(pSecondComma - pFirstComma); + if (!pchName) { + SDL_OutOfMemory(); + return NULL; + } + SDL_memcpy(pchName, pFirstComma + 1, pSecondComma - pFirstComma); + pchName[pSecondComma - pFirstComma - 1] = 0; + return pchName; +} + + +/* + * grab the button mapping string from a mapping string + */ +static char *SDL_PrivateGetControllerMappingFromMappingString(const char *pMapping) +{ + const char *pFirstComma, *pSecondComma; + + pFirstComma = SDL_strchr(pMapping, ','); + if (!pFirstComma) + return NULL; + + pSecondComma = SDL_strchr(pFirstComma + 1, ','); + if (!pSecondComma) + return NULL; + + return SDL_strdup(pSecondComma + 1); /* mapping is everything after the 3rd comma */ +} + +/* + * Helper function to refresh a mapping + */ +static void SDL_PrivateGameControllerRefreshMapping(ControllerMapping_t *pControllerMapping) +{ + SDL_GameController *gamecontrollerlist = SDL_gamecontrollers; + while (gamecontrollerlist) { + if (!SDL_memcmp(&gamecontrollerlist->joystick->guid, &pControllerMapping->guid, sizeof(pControllerMapping->guid))) { + /* Not really threadsafe. Should this lock access within SDL_GameControllerEventWatcher? */ + SDL_PrivateLoadButtonMapping(gamecontrollerlist, pControllerMapping->name, pControllerMapping->mapping); + + { + SDL_Event event; + event.type = SDL_CONTROLLERDEVICEREMAPPED; + event.cdevice.which = gamecontrollerlist->joystick->instance_id; + SDL_PushEvent(&event); + } + } + + gamecontrollerlist = gamecontrollerlist->next; + } +} + +/* + * Helper function to add a mapping for a guid + */ +static ControllerMapping_t * +SDL_PrivateAddMappingForGUID(SDL_JoystickGUID jGUID, const char *mappingString, SDL_bool *existing, SDL_ControllerMappingPriority priority) +{ + char *pchName; + char *pchMapping; + ControllerMapping_t *pControllerMapping; + + pchName = SDL_PrivateGetControllerNameFromMappingString(mappingString); + if (!pchName) { + SDL_SetError("Couldn't parse name from %s", mappingString); + return NULL; + } + + pchMapping = SDL_PrivateGetControllerMappingFromMappingString(mappingString); + if (!pchMapping) { + SDL_free(pchName); + SDL_SetError("Couldn't parse %s", mappingString); + return NULL; + } + + pControllerMapping = SDL_PrivateGetControllerMappingForGUID(&jGUID, SDL_TRUE); + if (pControllerMapping) { + /* Only overwrite the mapping if the priority is the same or higher. */ + if (pControllerMapping->priority <= priority) { + /* Update existing mapping */ + SDL_free(pControllerMapping->name); + pControllerMapping->name = pchName; + SDL_free(pControllerMapping->mapping); + pControllerMapping->mapping = pchMapping; + pControllerMapping->priority = priority; + /* refresh open controllers */ + SDL_PrivateGameControllerRefreshMapping(pControllerMapping); + } else { + SDL_free(pchName); + SDL_free(pchMapping); + } + *existing = SDL_TRUE; + } else { + pControllerMapping = SDL_malloc(sizeof(*pControllerMapping)); + if (!pControllerMapping) { + SDL_free(pchName); + SDL_free(pchMapping); + SDL_OutOfMemory(); + return NULL; + } + pControllerMapping->guid = jGUID; + pControllerMapping->name = pchName; + pControllerMapping->mapping = pchMapping; + pControllerMapping->next = NULL; + pControllerMapping->priority = priority; + + if (s_pSupportedControllers) { + /* Add the mapping to the end of the list */ + ControllerMapping_t *pCurrMapping, *pPrevMapping; + + for ( pPrevMapping = s_pSupportedControllers, pCurrMapping = pPrevMapping->next; + pCurrMapping; + pPrevMapping = pCurrMapping, pCurrMapping = pCurrMapping->next ) { + continue; + } + pPrevMapping->next = pControllerMapping; + } else { + s_pSupportedControllers = pControllerMapping; + } + *existing = SDL_FALSE; + } + return pControllerMapping; +} + +#ifdef __ANDROID__ +/* + * Helper function to guess at a mapping based on the elements reported for this controller + */ +static ControllerMapping_t *SDL_CreateMappingForAndroidController(const char *name, SDL_JoystickGUID guid) +{ + SDL_bool existing; + char name_string[128]; + char mapping_string[1024]; + int button_mask; + int axis_mask; + + button_mask = SDL_SwapLE16(*(Uint16*)(&guid.data[sizeof(guid.data)-4])); + axis_mask = SDL_SwapLE16(*(Uint16*)(&guid.data[sizeof(guid.data)-2])); + if (!button_mask && !axis_mask) { + /* Accelerometer, shouldn't have a game controller mapping */ + return NULL; + } + + /* Remove any commas in the name */ + SDL_strlcpy(name_string, name, sizeof(name_string)); + { + char *spot; + for (spot = name_string; *spot; ++spot) { + if (*spot == ',') { + *spot = ' '; + } + } + } + SDL_snprintf(mapping_string, sizeof(mapping_string), "none,%s,", name_string); + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_A)) { + SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_B)) { + SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string)); + } else if (button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) { + /* Use the back button as "B" for easy UI navigation with TV remotes */ + SDL_strlcat(mapping_string, "b:b4,", sizeof(mapping_string)); + button_mask &= ~(1 << SDL_CONTROLLER_BUTTON_BACK); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_X)) { + SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_Y)) { + SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) { + SDL_strlcat(mapping_string, "back:b4,", sizeof(mapping_string)); + } +#if 0 /* The guide button generally isn't functional (or acts as a home button) on most Android controllers */ + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_GUIDE)) { + SDL_strlcat(mapping_string, "guide:b5,", sizeof(mapping_string)); +#if 0 /* Actually this will be done in Steam */ + } else if (button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) { + /* The guide button doesn't exist, use the start button instead, + so you can do Steam guide button chords and open the Steam overlay. + */ + SDL_strlcat(mapping_string, "guide:b6,", sizeof(mapping_string)); + button_mask &= ~(1 << SDL_CONTROLLER_BUTTON_START); +#endif + } +#endif + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) { + SDL_strlcat(mapping_string, "start:b6,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) { + SDL_strlcat(mapping_string, "leftstick:b7,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) { + SDL_strlcat(mapping_string, "rightstick:b8,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) { + SDL_strlcat(mapping_string, "leftshoulder:b9,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)) { + SDL_strlcat(mapping_string, "rightshoulder:b10,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_DPAD_UP)) { + SDL_strlcat(mapping_string, "dpup:b11,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_DPAD_DOWN)) { + SDL_strlcat(mapping_string, "dpdown:b12,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_DPAD_LEFT)) { + SDL_strlcat(mapping_string, "dpleft:b13,", sizeof(mapping_string)); + } + if (button_mask & (1 << SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) { + SDL_strlcat(mapping_string, "dpright:b14,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_CONTROLLER_AXIS_LEFTX)) { + SDL_strlcat(mapping_string, "leftx:a0,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_CONTROLLER_AXIS_LEFTY)) { + SDL_strlcat(mapping_string, "lefty:a1,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_CONTROLLER_AXIS_RIGHTX)) { + SDL_strlcat(mapping_string, "rightx:a2,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_CONTROLLER_AXIS_RIGHTY)) { + SDL_strlcat(mapping_string, "righty:a3,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_CONTROLLER_AXIS_TRIGGERLEFT)) { + SDL_strlcat(mapping_string, "lefttrigger:a4,", sizeof(mapping_string)); + } + if (axis_mask & (1 << SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) { + SDL_strlcat(mapping_string, "righttrigger:a5,", sizeof(mapping_string)); + } + return SDL_PrivateAddMappingForGUID(guid, mapping_string, + &existing, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT); +} +#endif /* __ANDROID__ */ + + +/* + * Helper function to determine pre-calculated offset to certain joystick mappings + */ +static ControllerMapping_t *SDL_PrivateGetControllerMappingForNameAndGUID(const char *name, SDL_JoystickGUID guid) +{ + ControllerMapping_t *mapping; + + mapping = SDL_PrivateGetControllerMappingForGUID(&guid, SDL_FALSE); +#ifdef __LINUX__ + if (!mapping && name) { + if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) { + /* The Linux driver xpad.c maps the wireless dpad to buttons */ + SDL_bool existing; + mapping = SDL_PrivateAddMappingForGUID(guid, +"none,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,", + &existing, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT); + } + } +#endif /* __LINUX__ */ + + if (!mapping && name) { + if (SDL_strstr(name, "Xbox") || SDL_strstr(name, "X-Box") || SDL_strstr(name, "XBOX")) { + mapping = s_pXInputMapping; + } + } +#ifdef __ANDROID__ + if (!mapping && name && !SDL_IsJoystickHIDAPI(guid)) { + mapping = SDL_CreateMappingForAndroidController(name, guid); + } +#endif + if (!mapping) { + mapping = s_pDefaultMapping; + } + return mapping; +} + +static ControllerMapping_t *SDL_PrivateGetControllerMapping(int device_index) +{ + const char *name; + SDL_JoystickGUID guid; + ControllerMapping_t *mapping; + + SDL_LockJoysticks(); + + if ((device_index < 0) || (device_index >= SDL_NumJoysticks())) { + SDL_SetError("There are %d joysticks available", SDL_NumJoysticks()); + SDL_UnlockJoysticks(); + return (NULL); + } + + name = SDL_JoystickNameForIndex(device_index); + guid = SDL_JoystickGetDeviceGUID(device_index); + mapping = SDL_PrivateGetControllerMappingForNameAndGUID(name, guid); + SDL_UnlockJoysticks(); + return mapping; +} + +/* + * Add or update an entry into the Mappings Database + */ +int +SDL_GameControllerAddMappingsFromRW(SDL_RWops * rw, int freerw) +{ + const char *platform = SDL_GetPlatform(); + int controllers = 0; + char *buf, *line, *line_end, *tmp, *comma, line_platform[64]; + size_t db_size, platform_len; + + if (rw == NULL) { + return SDL_SetError("Invalid RWops"); + } + db_size = (size_t)SDL_RWsize(rw); + + buf = (char *)SDL_malloc(db_size + 1); + if (buf == NULL) { + if (freerw) { + SDL_RWclose(rw); + } + return SDL_SetError("Could not allocate space to read DB into memory"); + } + + if (SDL_RWread(rw, buf, db_size, 1) != 1) { + if (freerw) { + SDL_RWclose(rw); + } + SDL_free(buf); + return SDL_SetError("Could not read DB"); + } + + if (freerw) { + SDL_RWclose(rw); + } + + buf[db_size] = '\0'; + line = buf; + + while (line < buf + db_size) { + line_end = SDL_strchr(line, '\n'); + if (line_end != NULL) { + *line_end = '\0'; + } else { + line_end = buf + db_size; + } + + /* Extract and verify the platform */ + tmp = SDL_strstr(line, SDL_CONTROLLER_PLATFORM_FIELD); + if (tmp != NULL) { + tmp += SDL_strlen(SDL_CONTROLLER_PLATFORM_FIELD); + comma = SDL_strchr(tmp, ','); + if (comma != NULL) { + platform_len = comma - tmp + 1; + if (platform_len + 1 < SDL_arraysize(line_platform)) { + SDL_strlcpy(line_platform, tmp, platform_len); + if (SDL_strncasecmp(line_platform, platform, platform_len) == 0 && + SDL_GameControllerAddMapping(line) > 0) { + controllers++; + } + } + } + } + + line = line_end + 1; + } + + SDL_free(buf); + return controllers; +} + +/* + * Add or update an entry into the Mappings Database with a priority + */ +static int +SDL_PrivateGameControllerAddMapping(const char *mappingString, SDL_ControllerMappingPriority priority) +{ + char *pchGUID; + SDL_JoystickGUID jGUID; + SDL_bool is_default_mapping = SDL_FALSE; + SDL_bool is_hidapi_mapping = SDL_FALSE; + SDL_bool is_xinput_mapping = SDL_FALSE; + SDL_bool existing = SDL_FALSE; + ControllerMapping_t *pControllerMapping; + + if (!mappingString) { + return SDL_InvalidParamError("mappingString"); + } + + pchGUID = SDL_PrivateGetControllerGUIDFromMappingString(mappingString); + if (!pchGUID) { + return SDL_SetError("Couldn't parse GUID from %s", mappingString); + } + if (!SDL_strcasecmp(pchGUID, "default")) { + is_default_mapping = SDL_TRUE; + } else if (!SDL_strcasecmp(pchGUID, "hidapi")) { + is_hidapi_mapping = SDL_TRUE; + } else if (!SDL_strcasecmp(pchGUID, "xinput")) { + is_xinput_mapping = SDL_TRUE; + } + jGUID = SDL_JoystickGetGUIDFromString(pchGUID); + SDL_free(pchGUID); + + pControllerMapping = SDL_PrivateAddMappingForGUID(jGUID, mappingString, &existing, priority); + if (!pControllerMapping) { + return -1; + } + + if (existing) { + return 0; + } else { + if (is_default_mapping) { + s_pDefaultMapping = pControllerMapping; + } else if (is_hidapi_mapping) { + s_pHIDAPIMapping = pControllerMapping; + } else if (is_xinput_mapping) { + s_pXInputMapping = pControllerMapping; + } + return 1; + } +} + +/* + * Add or update an entry into the Mappings Database + */ +int +SDL_GameControllerAddMapping(const char *mappingString) +{ + return SDL_PrivateGameControllerAddMapping(mappingString, SDL_CONTROLLER_MAPPING_PRIORITY_API); +} + +/* + * Get the number of mappings installed + */ +int +SDL_GameControllerNumMappings(void) +{ + int num_mappings = 0; + ControllerMapping_t *mapping; + + for (mapping = s_pSupportedControllers; mapping; mapping = mapping->next) { + if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) { + continue; + } + ++num_mappings; + } + return num_mappings; +} + +/* + * Get the mapping at a particular index. + */ +char * +SDL_GameControllerMappingForIndex(int mapping_index) +{ + ControllerMapping_t *mapping; + + for (mapping = s_pSupportedControllers; mapping; mapping = mapping->next) { + if (SDL_memcmp(&mapping->guid, &s_zeroGUID, sizeof(mapping->guid)) == 0) { + continue; + } + if (mapping_index == 0) { + char *pMappingString; + char pchGUID[33]; + size_t needed; + + SDL_JoystickGetGUIDString(mapping->guid, pchGUID, sizeof(pchGUID)); + /* allocate enough memory for GUID + ',' + name + ',' + mapping + \0 */ + needed = SDL_strlen(pchGUID) + 1 + SDL_strlen(mapping->name) + 1 + SDL_strlen(mapping->mapping) + 1; + pMappingString = SDL_malloc(needed); + if (!pMappingString) { + SDL_OutOfMemory(); + return NULL; + } + SDL_snprintf(pMappingString, needed, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping); + return pMappingString; + } + --mapping_index; + } + return NULL; +} + +/* + * Get the mapping string for this GUID + */ +char * +SDL_GameControllerMappingForGUID(SDL_JoystickGUID guid) +{ + char *pMappingString = NULL; + ControllerMapping_t *mapping = SDL_PrivateGetControllerMappingForGUID(&guid, SDL_FALSE); + if (mapping) { + char pchGUID[33]; + size_t needed; + SDL_JoystickGetGUIDString(guid, pchGUID, sizeof(pchGUID)); + /* allocate enough memory for GUID + ',' + name + ',' + mapping + \0 */ + needed = SDL_strlen(pchGUID) + 1 + SDL_strlen(mapping->name) + 1 + SDL_strlen(mapping->mapping) + 1; + pMappingString = SDL_malloc(needed); + if (!pMappingString) { + SDL_OutOfMemory(); + return NULL; + } + SDL_snprintf(pMappingString, needed, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping); + } + return pMappingString; +} + +/* + * Get the mapping string for this device + */ +char * +SDL_GameControllerMapping(SDL_GameController * gamecontroller) +{ + if (!gamecontroller) { + return NULL; + } + + return SDL_GameControllerMappingForGUID(gamecontroller->joystick->guid); +} + +static void +SDL_GameControllerLoadHints() +{ + const char *hint = SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG); + if (hint && hint[0]) { + size_t nchHints = SDL_strlen(hint); + char *pUserMappings = SDL_malloc(nchHints + 1); + char *pTempMappings = pUserMappings; + SDL_memcpy(pUserMappings, hint, nchHints); + pUserMappings[nchHints] = '\0'; + while (pUserMappings) { + char *pchNewLine = NULL; + + pchNewLine = SDL_strchr(pUserMappings, '\n'); + if (pchNewLine) + *pchNewLine = '\0'; + + SDL_PrivateGameControllerAddMapping(pUserMappings, SDL_CONTROLLER_MAPPING_PRIORITY_USER); + + if (pchNewLine) { + pUserMappings = pchNewLine + 1; + } else { + pUserMappings = NULL; + } + } + SDL_free(pTempMappings); + } +} + +/* + * Fill the given buffer with the expected controller mapping filepath. + * Usually this will just be CONTROLLER_MAPPING_FILE, but for Android, + * we want to get the internal storage path. + */ +static SDL_bool SDL_GetControllerMappingFilePath(char *path, size_t size) +{ +#ifdef CONTROLLER_MAPPING_FILE +#define STRING(X) SDL_STRINGIFY_ARG(X) + return SDL_strlcpy(path, STRING(CONTROLLER_MAPPING_FILE), size) < size; +#elif defined(__ANDROID__) + return SDL_snprintf(path, size, "%s/controller_map.txt", SDL_AndroidGetInternalStoragePath()) < size; +#else + return SDL_FALSE; +#endif +} + +/* + * Initialize the game controller system, mostly load our DB of controller config mappings + */ +int +SDL_GameControllerInitMappings(void) +{ + char szControllerMapPath[1024]; + int i = 0; + const char *pMappingString = NULL; + pMappingString = s_ControllerMappings[i]; + while (pMappingString) { + SDL_PrivateGameControllerAddMapping(pMappingString, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT); + + i++; + pMappingString = s_ControllerMappings[i]; + } + + if (SDL_GetControllerMappingFilePath(szControllerMapPath, sizeof(szControllerMapPath))) { + SDL_GameControllerAddMappingsFromFile(szControllerMapPath); + } + + /* load in any user supplied config */ + SDL_GameControllerLoadHints(); + + SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, + SDL_GameControllerIgnoreDevicesChanged, NULL); + SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, + SDL_GameControllerIgnoreDevicesExceptChanged, NULL); + + return (0); +} + +int +SDL_GameControllerInit(void) +{ + int i; + + /* watch for joy events and fire controller ones if needed */ + SDL_AddEventWatch(SDL_GameControllerEventWatcher, NULL); + + /* Send added events for controllers currently attached */ + for (i = 0; i < SDL_NumJoysticks(); ++i) { + if (SDL_IsGameController(i)) { + SDL_Event deviceevent; + deviceevent.type = SDL_CONTROLLERDEVICEADDED; + deviceevent.cdevice.which = i; + SDL_PushEvent(&deviceevent); + } + } + + return (0); +} + + +/* + * Get the implementation dependent name of a controller + */ +const char * +SDL_GameControllerNameForIndex(int device_index) +{ + ControllerMapping_t *pSupportedController = SDL_PrivateGetControllerMapping(device_index); + if (pSupportedController) { + if (SDL_strcmp(pSupportedController->name, "*") == 0) { + return SDL_JoystickNameForIndex(device_index); + } else { + return pSupportedController->name; + } + } + return NULL; +} + + +/** + * Get the mapping of a game controller. + * This can be called before any controllers are opened. + * If no mapping can be found, this function returns NULL. + */ +char * +SDL_GameControllerMappingForDeviceIndex(int joystick_index) +{ + char *pMappingString = NULL; + ControllerMapping_t *mapping; + + SDL_LockJoysticks(); + mapping = SDL_PrivateGetControllerMapping(joystick_index); + if (mapping) { + SDL_JoystickGUID guid; + char pchGUID[33]; + size_t needed; + guid = SDL_JoystickGetDeviceGUID(joystick_index); + SDL_JoystickGetGUIDString(guid, pchGUID, sizeof(pchGUID)); + /* allocate enough memory for GUID + ',' + name + ',' + mapping + \0 */ + needed = SDL_strlen(pchGUID) + 1 + SDL_strlen(mapping->name) + 1 + SDL_strlen(mapping->mapping) + 1; + pMappingString = SDL_malloc(needed); + if (!pMappingString) { + SDL_OutOfMemory(); + SDL_UnlockJoysticks(); + return NULL; + } + SDL_snprintf(pMappingString, needed, "%s,%s,%s", pchGUID, mapping->name, mapping->mapping); + } + SDL_UnlockJoysticks(); + return pMappingString; +} + + +/* + * Return 1 if the joystick with this name and GUID is a supported controller + */ +SDL_bool +SDL_IsGameControllerNameAndGUID(const char *name, SDL_JoystickGUID guid) +{ + ControllerMapping_t *pSupportedController = SDL_PrivateGetControllerMappingForNameAndGUID(name, guid); + if (pSupportedController) { + return SDL_TRUE; + } + return SDL_FALSE; +} + +/* + * Return 1 if the joystick at this device index is a supported controller + */ +SDL_bool +SDL_IsGameController(int device_index) +{ + ControllerMapping_t *pSupportedController = SDL_PrivateGetControllerMapping(device_index); + if (pSupportedController) { + return SDL_TRUE; + } + return SDL_FALSE; +} + +/* + * Return 1 if the game controller should be ignored by SDL + */ +SDL_bool SDL_ShouldIgnoreGameController(const char *name, SDL_JoystickGUID guid) +{ + int i; + Uint16 vendor; + Uint16 product; + Uint16 version; + Uint32 vidpid; + + if (SDL_allowed_controllers.num_entries == 0 && + SDL_ignored_controllers.num_entries == 0) { + return SDL_FALSE; + } + + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, &version); + + if (SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", SDL_FALSE)) { + /* We shouldn't ignore Steam's virtual gamepad since it's using the hints to filter out the real controllers so it can remap input for the virtual controller */ + SDL_bool bSteamVirtualGamepad = SDL_FALSE; +#if defined(__LINUX__) + bSteamVirtualGamepad = (vendor == 0x28DE && product == 0x11FF); +#elif defined(__MACOSX__) + bSteamVirtualGamepad = (vendor == 0x045E && product == 0x028E && version == 1); +#elif defined(__WIN32__) + /* We can't tell on Windows, but Steam will block others in input hooks */ + bSteamVirtualGamepad = SDL_TRUE; +#endif + if (bSteamVirtualGamepad) { + return SDL_FALSE; + } + } + + vidpid = MAKE_VIDPID(vendor, product); + + if (SDL_allowed_controllers.num_entries > 0) { + for (i = 0; i < SDL_allowed_controllers.num_entries; ++i) { + if (vidpid == SDL_allowed_controllers.entries[i]) { + return SDL_FALSE; + } + } + return SDL_TRUE; + } else { + for (i = 0; i < SDL_ignored_controllers.num_entries; ++i) { + if (vidpid == SDL_ignored_controllers.entries[i]) { + return SDL_TRUE; + } + } + return SDL_FALSE; + } +} + +/* + * Open a controller for use - the index passed as an argument refers to + * the N'th controller on the system. This index is the value which will + * identify this controller in future controller events. + * + * This function returns a controller identifier, or NULL if an error occurred. + */ +SDL_GameController * +SDL_GameControllerOpen(int device_index) +{ + SDL_JoystickID instance_id; + SDL_GameController *gamecontroller; + SDL_GameController *gamecontrollerlist; + ControllerMapping_t *pSupportedController = NULL; + + SDL_LockJoysticks(); + + gamecontrollerlist = SDL_gamecontrollers; + /* If the controller is already open, return it */ + instance_id = SDL_JoystickGetDeviceInstanceID(device_index); + while (gamecontrollerlist) { + if (instance_id == gamecontrollerlist->joystick->instance_id) { + gamecontroller = gamecontrollerlist; + ++gamecontroller->ref_count; + SDL_UnlockJoysticks(); + return (gamecontroller); + } + gamecontrollerlist = gamecontrollerlist->next; + } + + /* Find a controller mapping */ + pSupportedController = SDL_PrivateGetControllerMapping(device_index); + if (!pSupportedController) { + SDL_SetError("Couldn't find mapping for device (%d)", device_index); + SDL_UnlockJoysticks(); + return NULL; + } + + /* Create and initialize the controller */ + gamecontroller = (SDL_GameController *) SDL_calloc(1, sizeof(*gamecontroller)); + if (gamecontroller == NULL) { + SDL_OutOfMemory(); + SDL_UnlockJoysticks(); + return NULL; + } + + gamecontroller->joystick = SDL_JoystickOpen(device_index); + if (!gamecontroller->joystick) { + SDL_free(gamecontroller); + SDL_UnlockJoysticks(); + return NULL; + } + + if (gamecontroller->joystick->naxes) { + gamecontroller->last_match_axis = (SDL_ExtendedGameControllerBind **)SDL_calloc(gamecontroller->joystick->naxes, sizeof(*gamecontroller->last_match_axis)); + if (!gamecontroller->last_match_axis) { + SDL_OutOfMemory(); + SDL_JoystickClose(gamecontroller->joystick); + SDL_free(gamecontroller); + SDL_UnlockJoysticks(); + return NULL; + } + } + if (gamecontroller->joystick->nhats) { + gamecontroller->last_hat_mask = (Uint8 *)SDL_calloc(gamecontroller->joystick->nhats, sizeof(*gamecontroller->last_hat_mask)); + if (!gamecontroller->last_hat_mask) { + SDL_OutOfMemory(); + SDL_JoystickClose(gamecontroller->joystick); + SDL_free(gamecontroller->last_match_axis); + SDL_free(gamecontroller); + SDL_UnlockJoysticks(); + return NULL; + } + } + + SDL_PrivateLoadButtonMapping(gamecontroller, pSupportedController->name, pSupportedController->mapping); + + /* Add the controller to list */ + ++gamecontroller->ref_count; + /* Link the controller in the list */ + gamecontroller->next = SDL_gamecontrollers; + SDL_gamecontrollers = gamecontroller; + + SDL_UnlockJoysticks(); + + return (gamecontroller); +} + +/* + * Manually pump for controller updates. + */ +void +SDL_GameControllerUpdate(void) +{ + /* Just for API completeness; the joystick API does all the work. */ + SDL_JoystickUpdate(); +} + +/* + * Get the current state of an axis control on a controller + */ +Sint16 +SDL_GameControllerGetAxis(SDL_GameController * gamecontroller, SDL_GameControllerAxis axis) +{ + int i; + + if (!gamecontroller) + return 0; + + for (i = 0; i < gamecontroller->num_bindings; ++i) { + SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i]; + if (binding->outputType == SDL_CONTROLLER_BINDTYPE_AXIS && binding->output.axis.axis == axis) { + int value = 0; + SDL_bool valid_input_range; + SDL_bool valid_output_range; + + if (binding->inputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + value = SDL_JoystickGetAxis(gamecontroller->joystick, binding->input.axis.axis); + if (binding->input.axis.axis_min < binding->input.axis.axis_max) { + valid_input_range = (value >= binding->input.axis.axis_min && value <= binding->input.axis.axis_max); + } else { + valid_input_range = (value >= binding->input.axis.axis_max && value <= binding->input.axis.axis_min); + } + if (valid_input_range) { + if (binding->input.axis.axis_min != binding->output.axis.axis_min || binding->input.axis.axis_max != binding->output.axis.axis_max) { + float normalized_value = (float)(value - binding->input.axis.axis_min) / (binding->input.axis.axis_max - binding->input.axis.axis_min); + value = binding->output.axis.axis_min + (int)(normalized_value * (binding->output.axis.axis_max - binding->output.axis.axis_min)); + } + } + } else if (binding->inputType == SDL_CONTROLLER_BINDTYPE_BUTTON) { + value = SDL_JoystickGetButton(gamecontroller->joystick, binding->input.button); + if (value == SDL_PRESSED) { + value = binding->output.axis.axis_max; + } + } else if (binding->inputType == SDL_CONTROLLER_BINDTYPE_HAT) { + int hat_mask = SDL_JoystickGetHat(gamecontroller->joystick, binding->input.hat.hat); + if (hat_mask & binding->input.hat.hat_mask) { + value = binding->output.axis.axis_max; + } + } + + if (binding->output.axis.axis_min < binding->output.axis.axis_max) { + valid_output_range = (value >= binding->output.axis.axis_min && value <= binding->output.axis.axis_max); + } else { + valid_output_range = (value >= binding->output.axis.axis_max && value <= binding->output.axis.axis_min); + } + /* If the value is zero, there might be another binding that makes it non-zero */ + if (value != 0 && valid_output_range) { + return (Sint16)value; + } + } + } + return 0; +} + +/* + * Get the current state of a button on a controller + */ +Uint8 +SDL_GameControllerGetButton(SDL_GameController * gamecontroller, SDL_GameControllerButton button) +{ + int i; + + if (!gamecontroller) + return 0; + + for (i = 0; i < gamecontroller->num_bindings; ++i) { + SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i]; + if (binding->outputType == SDL_CONTROLLER_BINDTYPE_BUTTON && binding->output.button == button) { + if (binding->inputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + SDL_bool valid_input_range; + + int value = SDL_JoystickGetAxis(gamecontroller->joystick, binding->input.axis.axis); + int threshold = binding->input.axis.axis_min + (binding->input.axis.axis_max - binding->input.axis.axis_min) / 2; + if (binding->input.axis.axis_min < binding->input.axis.axis_max) { + valid_input_range = (value >= binding->input.axis.axis_min && value <= binding->input.axis.axis_max); + if (valid_input_range) { + return (value >= threshold) ? SDL_PRESSED : SDL_RELEASED; + } + } else { + valid_input_range = (value >= binding->input.axis.axis_max && value <= binding->input.axis.axis_min); + if (valid_input_range) { + return (value <= threshold) ? SDL_PRESSED : SDL_RELEASED; + } + } + } else if (binding->inputType == SDL_CONTROLLER_BINDTYPE_BUTTON) { + return SDL_JoystickGetButton(gamecontroller->joystick, binding->input.button); + } else if (binding->inputType == SDL_CONTROLLER_BINDTYPE_HAT) { + int hat_mask = SDL_JoystickGetHat(gamecontroller->joystick, binding->input.hat.hat); + return (hat_mask & binding->input.hat.hat_mask) ? SDL_PRESSED : SDL_RELEASED; + } + } + } + return SDL_RELEASED; +} + +const char * +SDL_GameControllerName(SDL_GameController * gamecontroller) +{ + if (!gamecontroller) + return NULL; + + if (SDL_strcmp(gamecontroller->name, "*") == 0) { + return SDL_JoystickName(SDL_GameControllerGetJoystick(gamecontroller)); + } else { + return gamecontroller->name; + } +} + +int +SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller) +{ + return SDL_JoystickGetPlayerIndex(SDL_GameControllerGetJoystick(gamecontroller)); +} + +Uint16 +SDL_GameControllerGetVendor(SDL_GameController * gamecontroller) +{ + return SDL_JoystickGetVendor(SDL_GameControllerGetJoystick(gamecontroller)); +} + +Uint16 +SDL_GameControllerGetProduct(SDL_GameController * gamecontroller) +{ + return SDL_JoystickGetProduct(SDL_GameControllerGetJoystick(gamecontroller)); +} + +Uint16 +SDL_GameControllerGetProductVersion(SDL_GameController * gamecontroller) +{ + return SDL_JoystickGetProductVersion(SDL_GameControllerGetJoystick(gamecontroller)); +} + +/* + * Return if the controller in question is currently attached to the system, + * \return 0 if not plugged in, 1 if still present. + */ +SDL_bool +SDL_GameControllerGetAttached(SDL_GameController * gamecontroller) +{ + if (!gamecontroller) + return SDL_FALSE; + + return SDL_JoystickGetAttached(gamecontroller->joystick); +} + +/* + * Get the joystick for this controller + */ +SDL_Joystick *SDL_GameControllerGetJoystick(SDL_GameController * gamecontroller) +{ + if (!gamecontroller) + return NULL; + + return gamecontroller->joystick; +} + + +/* + * Find the SDL_GameController that owns this instance id + */ +SDL_GameController * +SDL_GameControllerFromInstanceID(SDL_JoystickID joyid) +{ + SDL_GameController *gamecontroller; + + SDL_LockJoysticks(); + gamecontroller = SDL_gamecontrollers; + while (gamecontroller) { + if (gamecontroller->joystick->instance_id == joyid) { + SDL_UnlockJoysticks(); + return gamecontroller; + } + gamecontroller = gamecontroller->next; + } + SDL_UnlockJoysticks(); + return NULL; +} + + +/* + * Get the SDL joystick layer binding for this controller axis mapping + */ +SDL_GameControllerButtonBind SDL_GameControllerGetBindForAxis(SDL_GameController * gamecontroller, SDL_GameControllerAxis axis) +{ + int i; + SDL_GameControllerButtonBind bind; + SDL_zero(bind); + + if (!gamecontroller || axis == SDL_CONTROLLER_AXIS_INVALID) + return bind; + + for (i = 0; i < gamecontroller->num_bindings; ++i) { + SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i]; + if (binding->outputType == SDL_CONTROLLER_BINDTYPE_AXIS && binding->output.axis.axis == axis) { + bind.bindType = binding->inputType; + if (binding->inputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + /* FIXME: There might be multiple axes bound now that we have axis ranges... */ + bind.value.axis = binding->input.axis.axis; + } else if (binding->inputType == SDL_CONTROLLER_BINDTYPE_BUTTON) { + bind.value.button = binding->input.button; + } else if (binding->inputType == SDL_CONTROLLER_BINDTYPE_HAT) { + bind.value.hat.hat = binding->input.hat.hat; + bind.value.hat.hat_mask = binding->input.hat.hat_mask; + } + break; + } + } + return bind; +} + + +/* + * Get the SDL joystick layer binding for this controller button mapping + */ +SDL_GameControllerButtonBind SDL_GameControllerGetBindForButton(SDL_GameController * gamecontroller, SDL_GameControllerButton button) +{ + int i; + SDL_GameControllerButtonBind bind; + SDL_zero(bind); + + if (!gamecontroller || button == SDL_CONTROLLER_BUTTON_INVALID) + return bind; + + for (i = 0; i < gamecontroller->num_bindings; ++i) { + SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i]; + if (binding->outputType == SDL_CONTROLLER_BINDTYPE_BUTTON && binding->output.button == button) { + bind.bindType = binding->inputType; + if (binding->inputType == SDL_CONTROLLER_BINDTYPE_AXIS) { + bind.value.axis = binding->input.axis.axis; + } else if (binding->inputType == SDL_CONTROLLER_BINDTYPE_BUTTON) { + bind.value.button = binding->input.button; + } else if (binding->inputType == SDL_CONTROLLER_BINDTYPE_HAT) { + bind.value.hat.hat = binding->input.hat.hat; + bind.value.hat.hat_mask = binding->input.hat.hat_mask; + } + break; + } + } + return bind; +} + + +int +SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) +{ + return SDL_JoystickRumble(SDL_GameControllerGetJoystick(gamecontroller), low_frequency_rumble, high_frequency_rumble, duration_ms); +} + +void +SDL_GameControllerClose(SDL_GameController * gamecontroller) +{ + SDL_GameController *gamecontrollerlist, *gamecontrollerlistprev; + + if (!gamecontroller) + return; + + SDL_LockJoysticks(); + + /* First decrement ref count */ + if (--gamecontroller->ref_count > 0) { + SDL_UnlockJoysticks(); + return; + } + + SDL_JoystickClose(gamecontroller->joystick); + + gamecontrollerlist = SDL_gamecontrollers; + gamecontrollerlistprev = NULL; + while (gamecontrollerlist) { + if (gamecontroller == gamecontrollerlist) { + if (gamecontrollerlistprev) { + /* unlink this entry */ + gamecontrollerlistprev->next = gamecontrollerlist->next; + } else { + SDL_gamecontrollers = gamecontroller->next; + } + break; + } + gamecontrollerlistprev = gamecontrollerlist; + gamecontrollerlist = gamecontrollerlist->next; + } + + SDL_free(gamecontroller->bindings); + SDL_free(gamecontroller->last_match_axis); + SDL_free(gamecontroller->last_hat_mask); + SDL_free(gamecontroller); + + SDL_UnlockJoysticks(); +} + + +/* + * Quit the controller subsystem + */ +void +SDL_GameControllerQuit(void) +{ + SDL_LockJoysticks(); + while (SDL_gamecontrollers) { + SDL_gamecontrollers->ref_count = 1; + SDL_GameControllerClose(SDL_gamecontrollers); + } + SDL_UnlockJoysticks(); +} + +void +SDL_GameControllerQuitMappings(void) +{ + ControllerMapping_t *pControllerMap; + + while (s_pSupportedControllers) { + pControllerMap = s_pSupportedControllers; + s_pSupportedControllers = s_pSupportedControllers->next; + SDL_free(pControllerMap->name); + SDL_free(pControllerMap->mapping); + SDL_free(pControllerMap); + } + + SDL_DelEventWatch(SDL_GameControllerEventWatcher, NULL); + + SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, + SDL_GameControllerIgnoreDevicesChanged, NULL); + SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, + SDL_GameControllerIgnoreDevicesExceptChanged, NULL); + + if (SDL_allowed_controllers.entries) { + SDL_free(SDL_allowed_controllers.entries); + SDL_zero(SDL_allowed_controllers); + } + if (SDL_ignored_controllers.entries) { + SDL_free(SDL_ignored_controllers.entries); + SDL_zero(SDL_ignored_controllers); + } +} + +/* + * Event filter to transform joystick events into appropriate game controller ones + */ +static int +SDL_PrivateGameControllerAxis(SDL_GameController * gamecontroller, SDL_GameControllerAxis axis, Sint16 value) +{ + int posted; + + /* translate the event, if desired */ + posted = 0; +#if !SDL_EVENTS_DISABLED + if (SDL_GetEventState(SDL_CONTROLLERAXISMOTION) == SDL_ENABLE) { + SDL_Event event; + event.type = SDL_CONTROLLERAXISMOTION; + event.caxis.which = gamecontroller->joystick->instance_id; + event.caxis.axis = axis; + event.caxis.value = value; + posted = SDL_PushEvent(&event) == 1; + } +#endif /* !SDL_EVENTS_DISABLED */ + return (posted); +} + + +/* + * Event filter to transform joystick events into appropriate game controller ones + */ +static int +SDL_PrivateGameControllerButton(SDL_GameController * gamecontroller, SDL_GameControllerButton button, Uint8 state) +{ + int posted; +#if !SDL_EVENTS_DISABLED + SDL_Event event; + + if (button == SDL_CONTROLLER_BUTTON_INVALID) + return (0); + + switch (state) { + case SDL_PRESSED: + event.type = SDL_CONTROLLERBUTTONDOWN; + break; + case SDL_RELEASED: + event.type = SDL_CONTROLLERBUTTONUP; + break; + default: + /* Invalid state -- bail */ + return (0); + } +#endif /* !SDL_EVENTS_DISABLED */ + + if (button == SDL_CONTROLLER_BUTTON_GUIDE) { + Uint32 now = SDL_GetTicks(); + if (state == SDL_PRESSED) { + gamecontroller->guide_button_down = now; + + if (gamecontroller->joystick->delayed_guide_button) { + /* Skip duplicate press */ + return (0); + } + } else { + if (!SDL_TICKS_PASSED(now, gamecontroller->guide_button_down+SDL_MINIMUM_GUIDE_BUTTON_DELAY_MS) && !gamecontroller->joystick->force_recentering) { + gamecontroller->joystick->delayed_guide_button = SDL_TRUE; + return (0); + } + gamecontroller->joystick->delayed_guide_button = SDL_FALSE; + } + } + + /* translate the event, if desired */ + posted = 0; +#if !SDL_EVENTS_DISABLED + if (SDL_GetEventState(event.type) == SDL_ENABLE) { + event.cbutton.which = gamecontroller->joystick->instance_id; + event.cbutton.button = button; + event.cbutton.state = state; + posted = SDL_PushEvent(&event) == 1; + } +#endif /* !SDL_EVENTS_DISABLED */ + return (posted); +} + +/* + * Turn off controller events + */ +int +SDL_GameControllerEventState(int state) +{ +#if SDL_EVENTS_DISABLED + return SDL_IGNORE; +#else + const Uint32 event_list[] = { + SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERBUTTONDOWN, SDL_CONTROLLERBUTTONUP, + SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMOVED, SDL_CONTROLLERDEVICEREMAPPED, + }; + unsigned int i; + + switch (state) { + case SDL_QUERY: + state = SDL_IGNORE; + for (i = 0; i < SDL_arraysize(event_list); ++i) { + state = SDL_EventState(event_list[i], SDL_QUERY); + if (state == SDL_ENABLE) { + break; + } + } + break; + default: + for (i = 0; i < SDL_arraysize(event_list); ++i) { + SDL_EventState(event_list[i], state); + } + break; + } + return (state); +#endif /* SDL_EVENTS_DISABLED */ +} + +void +SDL_GameControllerHandleDelayedGuideButton(SDL_Joystick *joystick) +{ + SDL_GameController *controllerlist = SDL_gamecontrollers; + while (controllerlist) { + if (controllerlist->joystick == joystick) { + SDL_PrivateGameControllerButton(controllerlist, SDL_CONTROLLER_BUTTON_GUIDE, SDL_RELEASED); + break; + } + controllerlist = controllerlist->next; + } +} + +/* vi: set ts=4 sw=4 expandtab: */ |