diff --git a/src/platform/joystick.c b/src/platform/joystick.c index 566a77f670..1e2f1fef22 100644 --- a/src/platform/joystick.c +++ b/src/platform/joystick.c @@ -7,6 +7,27 @@ #include #include +/* + * Stores the SDL game controller bindings associated with a mapping action. + * + * Unlike mapping_element, which describes a generic joystick input in terms of + * this project's JOYSTICK_ELEMENT_* representation, controller_mapping keeps + * the SDL_GameController-derived binding information returned by SDL for each + * action so it can be translated into the internal mapping format. + */ +typedef struct { + /* Action that the listed controller bindings should trigger. */ + mapping_action action; + struct { + /* SDL binding kind for this entry (button, axis, hat, etc.). */ + SDL_GameControllerBindType type; + /* SDL identifier for the bound control, such as axis/button index. */ + int id; + /* Direction or position associated with the binding when applicable. */ + int position; + } element[JOYSTICK_MAPPING_ELEMENTS_MAX]; +} controller_mapping; + #ifdef __vita__ #include "platform/vita/pad.h" @@ -98,48 +119,165 @@ static mapping_element default_mapping[] = { }; #endif +static controller_mapping controller_mappings[] = { + {MAPPING_ACTION_MOUSE_CURSOR_UP, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTY, JOYSTICK_AXIS_NEGATIVE}}}, + {MAPPING_ACTION_MOUSE_CURSOR_LEFT, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTX, JOYSTICK_AXIS_NEGATIVE}}}, + {MAPPING_ACTION_MOUSE_CURSOR_DOWN, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTY, JOYSTICK_AXIS_POSITIVE}}}, + {MAPPING_ACTION_MOUSE_CURSOR_RIGHT, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTX, JOYSTICK_AXIS_POSITIVE}}}, + {MAPPING_ACTION_FASTER_MOUSE_CURSOR_SPEED, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_TRIGGERLEFT, JOYSTICK_AXIS_POSITIVE}}}, + {MAPPING_ACTION_SLOWER_MOUSE_CURSOR_SPEED, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, JOYSTICK_AXIS_POSITIVE}}}, + {MAPPING_ACTION_LEFT_MOUSE_BUTTON, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_A}}}, + {MAPPING_ACTION_RIGHT_MOUSE_BUTTON, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_B}}}, + {MAPPING_ACTION_SCROLL_WINDOW_UP, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, JOYSTICK_AXIS_NEGATIVE}}}, + {MAPPING_ACTION_SCROLL_WINDOW_DOWN, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, JOYSTICK_AXIS_POSITIVE}}}, + {MAPPING_ACTION_SCROLL_MAP_UP, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, JOYSTICK_AXIS_NEGATIVE}}}, + {MAPPING_ACTION_SCROLL_MAP_LEFT, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTX, JOYSTICK_AXIS_NEGATIVE}}}, + {MAPPING_ACTION_SCROLL_MAP_DOWN, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, JOYSTICK_AXIS_POSITIVE}}}, + {MAPPING_ACTION_SCROLL_MAP_RIGHT, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTX, JOYSTICK_AXIS_POSITIVE}}}, + {MAPPING_ACTION_ROTATE_MAP_LEFT, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}}}, + {MAPPING_ACTION_ROTATE_MAP_RIGHT, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}}}, + {MAPPING_ACTION_INCREASE_GAME_SPEED, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_Y}}}, + {MAPPING_ACTION_DECREASE_GAME_SPEED, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_X}}}, + {MAPPING_ACTION_MOUSE_CURSOR_UP, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_UP}}}, + {MAPPING_ACTION_MOUSE_CURSOR_LEFT, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_LEFT}}}, + {MAPPING_ACTION_MOUSE_CURSOR_DOWN, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_DOWN}}}, + {MAPPING_ACTION_MOUSE_CURSOR_RIGHT, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}}} +}; + +#define NUM_CONTROLLER_MAPPINGS (sizeof(controller_mappings) / sizeof(controller_mapping)) + static int use_joystick(void) { -#if defined(__vita__) || defined(__SWITCH__) +#if defined(__vita__) || defined(__SWITCH__) || defined(__ANDROID__) return 1; #else return 0; #endif } -static void create_new_model(const char *guid, const char *name, int instance_id) +static joystick_hat_position convert_hat_position(int value) +{ + joystick_hat_position position = JOYSTICK_HAT_CENTERED; + if (value & SDL_HAT_UP) { + position |= JOYSTICK_HAT_UP; + } + if (value & SDL_HAT_DOWN) { + position |= JOYSTICK_HAT_DOWN; + } + if (value & SDL_HAT_LEFT) { + position |= JOYSTICK_HAT_LEFT; + } + if (value & SDL_HAT_RIGHT) { + position |= JOYSTICK_HAT_RIGHT; + } + return position; +} + +static void set_default_controller_mapping(joystick_model *model, SDL_GameController *controller) +{ + for (int i = 0; i < NUM_CONTROLLER_MAPPINGS; i++) { + const controller_mapping *mapping = &controller_mappings[i]; + SDL_GameControllerButtonBind binds[JOYSTICK_MAPPING_ELEMENTS_MAX] = { 0 }; + int bind_failed = 0; + for (int j = 0; j < JOYSTICK_MAPPING_ELEMENTS_MAX; j++) { + int has_bind = 0; + if (mapping->element[j].type == SDL_CONTROLLER_BINDTYPE_AXIS) { + has_bind = 1; + binds[j] = SDL_GameControllerGetBindForAxis(controller, mapping->element[j].id); + } else if (mapping->element[j].type == SDL_CONTROLLER_BINDTYPE_BUTTON) { + has_bind = 1; + binds[j] = SDL_GameControllerGetBindForButton(controller, mapping->element[j].id); + } + if (has_bind && binds[j].bindType == SDL_CONTROLLER_BINDTYPE_NONE) { + bind_failed = 1; + break; + } + } + if (bind_failed) { + continue; + } + mapping_element *action = &model->mapping[model->num_mappings]; + action->action = mapping->action; + for (int j = 0; j < JOYSTICK_MAPPING_ELEMENTS_MAX; j++) { + switch (binds[j].bindType) { + case SDL_CONTROLLER_BINDTYPE_AXIS: + action->element[j].type = JOYSTICK_ELEMENT_AXIS; + action->element[j].id = binds[j].value.axis; + action->element[j].position = mapping->element[j].position; + break; + case SDL_CONTROLLER_BINDTYPE_BUTTON: + action->element[j].type = JOYSTICK_ELEMENT_BUTTON; + action->element[j].id = binds[j].value.button; + break; + case SDL_CONTROLLER_BINDTYPE_HAT: + action->element[j].type = JOYSTICK_ELEMENT_HAT; + action->element[j].id = binds[j].value.hat.hat; + action->element[j].position = convert_hat_position(binds[j].value.hat.hat_mask); + break; + default: + break; + } + } + model->num_mappings++; + } +} + +static void create_new_model(const char *guid, const char *name, int instance_id, SDL_GameController *controller) { joystick_model model; memset(&model, 0, sizeof(model)); - strncpy(model.guid, guid, JOYSTICK_MAX_GUID); + snprintf(model.guid, JOYSTICK_MAX_GUID, "%s", guid); if (!name) { snprintf(model.name, JOYSTICK_MAX_NAME, "Joystick %d", instance_id); } else { - strncpy(model.name, name, JOYSTICK_MAX_NAME - 1); + snprintf(model.name, JOYSTICK_MAX_NAME, "Joystick %s", name); } - int size = sizeof(default_mapping); - if (size > sizeof(model.mapping)) { - size = sizeof(model.mapping); + if (controller) { + set_default_controller_mapping(&model, controller); + } else { + size_t size = sizeof(default_mapping); + if (size > sizeof(model.mapping)) { + size = sizeof(model.mapping); + } + memcpy(model.mapping, default_mapping, size); + model.num_mappings = (int) (size / sizeof(mapping_element)); } - memcpy(model.mapping, default_mapping, size); - model.num_mappings = size / sizeof(mapping_element); joystick_add_model(&model); } static void add_joystick(int index) { - SDL_Joystick *joystick = SDL_JoystickOpen(index); + SDL_GameController *controller = 0; + SDL_Joystick *joystick = 0; + + if (SDL_IsGameController(index)) { + controller = SDL_GameControllerOpen(index); + joystick = SDL_GameControllerGetJoystick(controller); + SDL_Log("Game controller found. Setting default gamepad mapping."); + } else { + joystick = SDL_JoystickOpen(index); + } int instance_id = SDL_JoystickInstanceID(joystick); if (joystick_is_active(instance_id)) { + if (controller) { + SDL_GameControllerClose(controller); + } else { + SDL_JoystickClose(joystick); + } return; } + static char guid[JOYSTICK_MAX_GUID]; SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joystick), guid, JOYSTICK_MAX_GUID); if (!joystick_has_model(guid)) { - create_new_model(guid, SDL_JoystickName(joystick), instance_id); + create_new_model(guid, SDL_JoystickName(joystick), instance_id, controller); } if (!joystick_add(instance_id, guid)) { - SDL_JoystickClose(joystick); + if (controller) { + SDL_GameControllerClose(controller); + } else { + SDL_JoystickClose(joystick); + } } } @@ -148,23 +286,40 @@ static void remove_joystick(int instance_id) if (!joystick_is_active(instance_id)) { return; } + SDL_GameController *controller = 0; SDL_Joystick *joystick = 0; #if SDL_VERSION_ATLEAST(2, 0, 4) if (platform_sdl_version_at_least(2, 0, 4)) { - joystick = SDL_JoystickFromInstanceID(instance_id); + controller = SDL_GameControllerFromInstanceID(instance_id); + if (!controller) { + joystick = SDL_JoystickFromInstanceID(instance_id); + } } else { #endif for (int i = 0; i < SDL_NumJoysticks(); ++i) { - SDL_Joystick *current_joystick = SDL_JoystickOpen(i); - if (SDL_JoystickInstanceID(current_joystick) == instance_id) { - joystick = current_joystick; - break; + if (SDL_IsGameController(i)) { + SDL_GameController *current_controller = SDL_GameControllerOpen(i); + SDL_Joystick *current_joystick = SDL_GameControllerGetJoystick(current_controller); + if (SDL_JoystickInstanceID(current_joystick) == instance_id) { + controller = current_controller; + break; + } + SDL_GameControllerClose(current_controller); + } else { + SDL_Joystick *current_joystick = SDL_JoystickOpen(i); + if (SDL_JoystickInstanceID(current_joystick) == instance_id) { + joystick = current_joystick; + break; + } + SDL_JoystickClose(current_joystick); } } #if SDL_VERSION_ATLEAST(2, 0, 4) } #endif - if (joystick) { + if (controller) { + SDL_GameControllerClose(controller); + } else if (joystick) { SDL_JoystickClose(joystick); } joystick_remove(instance_id); @@ -216,20 +371,7 @@ void platform_joystick_handle_hat(SDL_JoyHatEvent *event) if (!use_joystick()) { return; } - joystick_hat_position position = JOYSTICK_HAT_CENTERED; - if (event->value & SDL_HAT_UP) { - position |= JOYSTICK_HAT_UP; - } - if (event->value & SDL_HAT_DOWN) { - position |= JOYSTICK_HAT_DOWN; - } - if (event->value & SDL_HAT_LEFT) { - position |= JOYSTICK_HAT_LEFT; - } - if (event->value & SDL_HAT_RIGHT) { - position |= JOYSTICK_HAT_RIGHT; - } - joystick_update_element(event->which, JOYSTICK_ELEMENT_HAT, event->hat, position, 0); + joystick_update_element(event->which, JOYSTICK_ELEMENT_HAT, event->hat, convert_hat_position(event->value), 0); } void platform_joystick_handle_button(SDL_JoyButtonEvent *event, int is_down)