Skip to content

Commit 016d525

Browse files
authored
Add support for game controllers with auto key binding and enable controller support for android (#788)
* Add support for game controllers with auto key binding and enable controller support for android Fixes #787 * Fixes * More fixes, removing leftover augustus code
1 parent 4e1e740 commit 016d525

1 file changed

Lines changed: 174 additions & 32 deletions

File tree

src/platform/joystick.c

Lines changed: 174 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@
77
#include <stdio.h>
88
#include <string.h>
99

10+
/*
11+
* Stores the SDL game controller bindings associated with a mapping action.
12+
*
13+
* Unlike mapping_element, which describes a generic joystick input in terms of
14+
* this project's JOYSTICK_ELEMENT_* representation, controller_mapping keeps
15+
* the SDL_GameController-derived binding information returned by SDL for each
16+
* action so it can be translated into the internal mapping format.
17+
*/
18+
typedef struct {
19+
/* Action that the listed controller bindings should trigger. */
20+
mapping_action action;
21+
struct {
22+
/* SDL binding kind for this entry (button, axis, hat, etc.). */
23+
SDL_GameControllerBindType type;
24+
/* SDL identifier for the bound control, such as axis/button index. */
25+
int id;
26+
/* Direction or position associated with the binding when applicable. */
27+
int position;
28+
} element[JOYSTICK_MAPPING_ELEMENTS_MAX];
29+
} controller_mapping;
30+
1031
#ifdef __vita__
1132
#include "platform/vita/pad.h"
1233

@@ -98,48 +119,165 @@ static mapping_element default_mapping[] = {
98119
};
99120
#endif
100121

122+
static controller_mapping controller_mappings[] = {
123+
{MAPPING_ACTION_MOUSE_CURSOR_UP, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTY, JOYSTICK_AXIS_NEGATIVE}}},
124+
{MAPPING_ACTION_MOUSE_CURSOR_LEFT, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTX, JOYSTICK_AXIS_NEGATIVE}}},
125+
{MAPPING_ACTION_MOUSE_CURSOR_DOWN, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTY, JOYSTICK_AXIS_POSITIVE}}},
126+
{MAPPING_ACTION_MOUSE_CURSOR_RIGHT, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_LEFTX, JOYSTICK_AXIS_POSITIVE}}},
127+
{MAPPING_ACTION_FASTER_MOUSE_CURSOR_SPEED, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_TRIGGERLEFT, JOYSTICK_AXIS_POSITIVE}}},
128+
{MAPPING_ACTION_SLOWER_MOUSE_CURSOR_SPEED, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, JOYSTICK_AXIS_POSITIVE}}},
129+
{MAPPING_ACTION_LEFT_MOUSE_BUTTON, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_A}}},
130+
{MAPPING_ACTION_RIGHT_MOUSE_BUTTON, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_B}}},
131+
{MAPPING_ACTION_SCROLL_WINDOW_UP, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, JOYSTICK_AXIS_NEGATIVE}}},
132+
{MAPPING_ACTION_SCROLL_WINDOW_DOWN, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, JOYSTICK_AXIS_POSITIVE}}},
133+
{MAPPING_ACTION_SCROLL_MAP_UP, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, JOYSTICK_AXIS_NEGATIVE}}},
134+
{MAPPING_ACTION_SCROLL_MAP_LEFT, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTX, JOYSTICK_AXIS_NEGATIVE}}},
135+
{MAPPING_ACTION_SCROLL_MAP_DOWN, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTY, JOYSTICK_AXIS_POSITIVE}}},
136+
{MAPPING_ACTION_SCROLL_MAP_RIGHT, {{SDL_CONTROLLER_BINDTYPE_AXIS, SDL_CONTROLLER_AXIS_RIGHTX, JOYSTICK_AXIS_POSITIVE}}},
137+
{MAPPING_ACTION_ROTATE_MAP_LEFT, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}}},
138+
{MAPPING_ACTION_ROTATE_MAP_RIGHT, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}}},
139+
{MAPPING_ACTION_INCREASE_GAME_SPEED, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_Y}}},
140+
{MAPPING_ACTION_DECREASE_GAME_SPEED, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_X}}},
141+
{MAPPING_ACTION_MOUSE_CURSOR_UP, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_UP}}},
142+
{MAPPING_ACTION_MOUSE_CURSOR_LEFT, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_LEFT}}},
143+
{MAPPING_ACTION_MOUSE_CURSOR_DOWN, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_DOWN}}},
144+
{MAPPING_ACTION_MOUSE_CURSOR_RIGHT, {{SDL_CONTROLLER_BINDTYPE_BUTTON, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}}}
145+
};
146+
147+
#define NUM_CONTROLLER_MAPPINGS (sizeof(controller_mappings) / sizeof(controller_mapping))
148+
101149
static int use_joystick(void)
102150
{
103-
#if defined(__vita__) || defined(__SWITCH__)
151+
#if defined(__vita__) || defined(__SWITCH__) || defined(__ANDROID__)
104152
return 1;
105153
#else
106154
return 0;
107155
#endif
108156
}
109157

110-
static void create_new_model(const char *guid, const char *name, int instance_id)
158+
static joystick_hat_position convert_hat_position(int value)
159+
{
160+
joystick_hat_position position = JOYSTICK_HAT_CENTERED;
161+
if (value & SDL_HAT_UP) {
162+
position |= JOYSTICK_HAT_UP;
163+
}
164+
if (value & SDL_HAT_DOWN) {
165+
position |= JOYSTICK_HAT_DOWN;
166+
}
167+
if (value & SDL_HAT_LEFT) {
168+
position |= JOYSTICK_HAT_LEFT;
169+
}
170+
if (value & SDL_HAT_RIGHT) {
171+
position |= JOYSTICK_HAT_RIGHT;
172+
}
173+
return position;
174+
}
175+
176+
static void set_default_controller_mapping(joystick_model *model, SDL_GameController *controller)
177+
{
178+
for (int i = 0; i < NUM_CONTROLLER_MAPPINGS; i++) {
179+
const controller_mapping *mapping = &controller_mappings[i];
180+
SDL_GameControllerButtonBind binds[JOYSTICK_MAPPING_ELEMENTS_MAX] = { 0 };
181+
int bind_failed = 0;
182+
for (int j = 0; j < JOYSTICK_MAPPING_ELEMENTS_MAX; j++) {
183+
int has_bind = 0;
184+
if (mapping->element[j].type == SDL_CONTROLLER_BINDTYPE_AXIS) {
185+
has_bind = 1;
186+
binds[j] = SDL_GameControllerGetBindForAxis(controller, mapping->element[j].id);
187+
} else if (mapping->element[j].type == SDL_CONTROLLER_BINDTYPE_BUTTON) {
188+
has_bind = 1;
189+
binds[j] = SDL_GameControllerGetBindForButton(controller, mapping->element[j].id);
190+
}
191+
if (has_bind && binds[j].bindType == SDL_CONTROLLER_BINDTYPE_NONE) {
192+
bind_failed = 1;
193+
break;
194+
}
195+
}
196+
if (bind_failed) {
197+
continue;
198+
}
199+
mapping_element *action = &model->mapping[model->num_mappings];
200+
action->action = mapping->action;
201+
for (int j = 0; j < JOYSTICK_MAPPING_ELEMENTS_MAX; j++) {
202+
switch (binds[j].bindType) {
203+
case SDL_CONTROLLER_BINDTYPE_AXIS:
204+
action->element[j].type = JOYSTICK_ELEMENT_AXIS;
205+
action->element[j].id = binds[j].value.axis;
206+
action->element[j].position = mapping->element[j].position;
207+
break;
208+
case SDL_CONTROLLER_BINDTYPE_BUTTON:
209+
action->element[j].type = JOYSTICK_ELEMENT_BUTTON;
210+
action->element[j].id = binds[j].value.button;
211+
break;
212+
case SDL_CONTROLLER_BINDTYPE_HAT:
213+
action->element[j].type = JOYSTICK_ELEMENT_HAT;
214+
action->element[j].id = binds[j].value.hat.hat;
215+
action->element[j].position = convert_hat_position(binds[j].value.hat.hat_mask);
216+
break;
217+
default:
218+
break;
219+
}
220+
}
221+
model->num_mappings++;
222+
}
223+
}
224+
225+
static void create_new_model(const char *guid, const char *name, int instance_id, SDL_GameController *controller)
111226
{
112227
joystick_model model;
113228
memset(&model, 0, sizeof(model));
114-
strncpy(model.guid, guid, JOYSTICK_MAX_GUID);
229+
snprintf(model.guid, JOYSTICK_MAX_GUID, "%s", guid);
115230
if (!name) {
116231
snprintf(model.name, JOYSTICK_MAX_NAME, "Joystick %d", instance_id);
117232
} else {
118-
strncpy(model.name, name, JOYSTICK_MAX_NAME - 1);
233+
snprintf(model.name, JOYSTICK_MAX_NAME, "Joystick %s", name);
119234
}
120-
int size = sizeof(default_mapping);
121-
if (size > sizeof(model.mapping)) {
122-
size = sizeof(model.mapping);
235+
if (controller) {
236+
set_default_controller_mapping(&model, controller);
237+
} else {
238+
size_t size = sizeof(default_mapping);
239+
if (size > sizeof(model.mapping)) {
240+
size = sizeof(model.mapping);
241+
}
242+
memcpy(model.mapping, default_mapping, size);
243+
model.num_mappings = (int) (size / sizeof(mapping_element));
123244
}
124-
memcpy(model.mapping, default_mapping, size);
125-
model.num_mappings = size / sizeof(mapping_element);
126245
joystick_add_model(&model);
127246
}
128247

129248
static void add_joystick(int index)
130249
{
131-
SDL_Joystick *joystick = SDL_JoystickOpen(index);
250+
SDL_GameController *controller = 0;
251+
SDL_Joystick *joystick = 0;
252+
253+
if (SDL_IsGameController(index)) {
254+
controller = SDL_GameControllerOpen(index);
255+
joystick = SDL_GameControllerGetJoystick(controller);
256+
SDL_Log("Game controller found. Setting default gamepad mapping.");
257+
} else {
258+
joystick = SDL_JoystickOpen(index);
259+
}
132260
int instance_id = SDL_JoystickInstanceID(joystick);
133261
if (joystick_is_active(instance_id)) {
262+
if (controller) {
263+
SDL_GameControllerClose(controller);
264+
} else {
265+
SDL_JoystickClose(joystick);
266+
}
134267
return;
135268
}
269+
136270
static char guid[JOYSTICK_MAX_GUID];
137271
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(joystick), guid, JOYSTICK_MAX_GUID);
138272
if (!joystick_has_model(guid)) {
139-
create_new_model(guid, SDL_JoystickName(joystick), instance_id);
273+
create_new_model(guid, SDL_JoystickName(joystick), instance_id, controller);
140274
}
141275
if (!joystick_add(instance_id, guid)) {
142-
SDL_JoystickClose(joystick);
276+
if (controller) {
277+
SDL_GameControllerClose(controller);
278+
} else {
279+
SDL_JoystickClose(joystick);
280+
}
143281
}
144282
}
145283

@@ -148,23 +286,40 @@ static void remove_joystick(int instance_id)
148286
if (!joystick_is_active(instance_id)) {
149287
return;
150288
}
289+
SDL_GameController *controller = 0;
151290
SDL_Joystick *joystick = 0;
152291
#if SDL_VERSION_ATLEAST(2, 0, 4)
153292
if (platform_sdl_version_at_least(2, 0, 4)) {
154-
joystick = SDL_JoystickFromInstanceID(instance_id);
293+
controller = SDL_GameControllerFromInstanceID(instance_id);
294+
if (!controller) {
295+
joystick = SDL_JoystickFromInstanceID(instance_id);
296+
}
155297
} else {
156298
#endif
157299
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
158-
SDL_Joystick *current_joystick = SDL_JoystickOpen(i);
159-
if (SDL_JoystickInstanceID(current_joystick) == instance_id) {
160-
joystick = current_joystick;
161-
break;
300+
if (SDL_IsGameController(i)) {
301+
SDL_GameController *current_controller = SDL_GameControllerOpen(i);
302+
SDL_Joystick *current_joystick = SDL_GameControllerGetJoystick(current_controller);
303+
if (SDL_JoystickInstanceID(current_joystick) == instance_id) {
304+
controller = current_controller;
305+
break;
306+
}
307+
SDL_GameControllerClose(current_controller);
308+
} else {
309+
SDL_Joystick *current_joystick = SDL_JoystickOpen(i);
310+
if (SDL_JoystickInstanceID(current_joystick) == instance_id) {
311+
joystick = current_joystick;
312+
break;
313+
}
314+
SDL_JoystickClose(current_joystick);
162315
}
163316
}
164317
#if SDL_VERSION_ATLEAST(2, 0, 4)
165318
}
166319
#endif
167-
if (joystick) {
320+
if (controller) {
321+
SDL_GameControllerClose(controller);
322+
} else if (joystick) {
168323
SDL_JoystickClose(joystick);
169324
}
170325
joystick_remove(instance_id);
@@ -216,20 +371,7 @@ void platform_joystick_handle_hat(SDL_JoyHatEvent *event)
216371
if (!use_joystick()) {
217372
return;
218373
}
219-
joystick_hat_position position = JOYSTICK_HAT_CENTERED;
220-
if (event->value & SDL_HAT_UP) {
221-
position |= JOYSTICK_HAT_UP;
222-
}
223-
if (event->value & SDL_HAT_DOWN) {
224-
position |= JOYSTICK_HAT_DOWN;
225-
}
226-
if (event->value & SDL_HAT_LEFT) {
227-
position |= JOYSTICK_HAT_LEFT;
228-
}
229-
if (event->value & SDL_HAT_RIGHT) {
230-
position |= JOYSTICK_HAT_RIGHT;
231-
}
232-
joystick_update_element(event->which, JOYSTICK_ELEMENT_HAT, event->hat, position, 0);
374+
joystick_update_element(event->which, JOYSTICK_ELEMENT_HAT, event->hat, convert_hat_position(event->value), 0);
233375
}
234376

235377
void platform_joystick_handle_button(SDL_JoyButtonEvent *event, int is_down)

0 commit comments

Comments
 (0)