changeset 1187:6a4503fad67e

Initial support for using SDL2 game controller mapping functionality
author Michael Pavone <pavone@retrodev.com>
date Sun, 22 Jan 2017 16:23:59 -0800
parents 110251ea369e
children 448ce87b87fc
files default.cfg io.c render.h render_sdl.c
diffstat 4 files changed, 144 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/default.cfg	Sun Jan 22 16:13:02 2017 -0800
+++ b/default.cfg	Sun Jan 22 16:23:59 2017 -0800
@@ -42,14 +42,15 @@
 				}
 			}
 			buttons {
-				0 gamepads.1.a
-				1 gamepads.1.b
-				2 gamepads.1.c
-				3 gamepads.1.x
-				4 gamepads.1.y
-				5 gamepads.1.z
-				6 gamepads.1.mode
-				7 gamepads.1.start
+				a gamepads.1.a
+				b gamepads.1.b
+				rightshoulder gamepads.1.c
+				x gamepads.1.x
+				y gamepads.1.y
+				leftshoulder gamepads.1.z
+				back gamepads.1.mode
+				start gamepads.1.start
+				guide ui.exit
 			}
 		}
 		1 {
@@ -62,14 +63,16 @@
 				}
 			}
 			buttons {
-				0 gamepads.2.a
-				1 gamepads.2.b
-				2 gamepads.2.c
-				3 gamepads.2.x
-				4 gamepads.2.y
-				5 gamepads.2.z
-				6 gamepads.2.mode
-				7 gamepads.2.start
+				#this is exactly the same mapping as above, but with PS4 style names
+				cross gamepads.2.a
+				circle gamepads.2.b
+				r1 gamepads.2.c
+				square gamepads.2.x
+				triangle gamepads.2.y
+				l1 gamepads.2.z
+				share gamepads.2.mode
+				options gamepads.2.start
+				guide ui.exit
 			}
 		}
 	}
--- a/io.c	Sun Jan 22 16:13:02 2017 -0800
+++ b/io.c	Sun Jan 22 16:23:59 2017 -0800
@@ -923,6 +923,49 @@
 	}
 }
 
+typedef struct {
+	int       padnum;
+	tern_node *padbuttons;
+	tern_node *mousebuttons;
+} pad_button_state;
+
+void process_pad_button(char *key, tern_val val, void *data)
+{
+	pad_button_state *state = data;
+	int hostpadnum = state->padnum;
+	int ui_func, padnum, button;
+	int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &padnum, &button);
+	char *end;
+	long hostbutton = strtol(key, &end, 10);
+	if (*end) {
+		//key is not a valid base 10 integer
+		hostbutton = render_translate_input_name(hostpadnum, key);
+		if (hostbutton < 0) {
+			if (hostbutton == RENDER_INVALID_NAME) {
+				warning("%s is not a valid gamepad input name\n", key);
+			} else if (hostbutton == RENDER_NOT_MAPPED) {
+				warning("No mapping exists for input %s on gamepad %d\n", key, hostpadnum);
+			}
+			return;
+		}
+		if (hostbutton & RENDER_DPAD_BIT) {
+			if (bindtype == BIND_GAMEPAD1) {
+				bind_dpad_gamepad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), padnum, button);
+			} else {
+				bind_dpad_ui(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), ui_func, button);
+			}
+		} else if (hostbutton & RENDER_AXIS_BIT) {
+			//TODO: support analog axes
+			return;
+		}
+	}
+	if (bindtype == BIND_GAMEPAD1) {
+		bind_button_gamepad(hostpadnum, hostbutton, padnum, button);
+	} else if (bindtype == BIND_UI) {
+		bind_button_ui(hostpadnum, hostbutton, ui_func, button);
+	}
+}
+
 void set_keybindings(sega_io *io)
 {
 	static uint8_t already_done;
@@ -988,6 +1031,9 @@
 	mousebuttons = tern_insert_int(mousebuttons, ".start", MOUSE_START);
 	mousebuttons = tern_insert_int(mousebuttons, ".motion", PSEUDO_BUTTON_MOTION);
 
+	//pump event loop, so initial joystick insertion events are processed
+	process_events();
+	
 	tern_node * keys = tern_get_node(tern_find_path(config, "bindings\0keys\0"));
 	process_keys(keys, special, padbuttons, mousebuttons, NULL);
 	char numstr[] = "00";
@@ -1030,26 +1076,13 @@
 				}
 				tern_node *button_node = tern_find_ptr(pad, "buttons");
 				if (button_node) {
-					for (int but = 0; but < 30; but++)
-					{
-						if (but < 10) {
-							numstr[0] = but + '0';
-							numstr[1] = 0;
-						} else {
-							numstr[0] = but/10 + '0';
-							numstr[1] = but%10 + '0';
-						}
-						char * target = tern_find_ptr(button_node, numstr);
-						if (target) {
-							int ui_func, padnum, button;
-							int bindtype = parse_binding_target(target, padbuttons, mousebuttons, &ui_func, &padnum, &button);
-							if (bindtype == BIND_GAMEPAD1) {
-								bind_button_gamepad(i, but, padnum, button);
-							} else if (bindtype == BIND_UI) {
-								bind_button_ui(i, but, ui_func, button);
-							}
-						}
-					}
+					pad_button_state state = {
+						.padnum = i,
+						.padbuttons = padbuttons,
+						.mousebuttons = mousebuttons
+					};
+					tern_foreach(button_node, process_pad_button, &state);
+					
 				}
 			}
 		}
--- a/render.h	Sun Jan 22 16:13:02 2017 -0800
+++ b/render.h	Sun Jan 22 16:23:59 2017 -0800
@@ -63,6 +63,12 @@
 	NUM_VID_STD
 } vid_std;
 
+#define RENDER_DPAD_BIT 0x40000000
+#define RENDER_AXIS_BIT 0x20000000
+#define RENDER_INVALID_NAME -1
+#define RENDER_NOT_MAPPED -2
+#define RENDER_NOT_PLUGGED_IN -3
+
 uint32_t render_map_color(uint8_t r, uint8_t g, uint8_t b);
 uint32_t *render_get_framebuffer(uint8_t which, int *pitch);
 void render_framebuffer_updated(uint8_t which, int width);
@@ -81,6 +87,9 @@
 int render_height();
 int render_fullscreen();
 void process_events();
+int32_t render_translate_input_name(int32_t controller, char *name);
+int32_t render_dpad_part(int32_t input);
+uint8_t render_direction_part(int32_t input);
 void render_errorbox(char *title, char *message);
 void render_warnbox(char *title, char *message);
 void render_infobox(char *title, char *message);
--- a/render_sdl.c	Sun Jan 22 16:13:02 2017 -0800
+++ b/render_sdl.c	Sun Jan 22 16:23:59 2017 -0800
@@ -111,6 +111,7 @@
 }
 
 static SDL_Joystick * joysticks[MAX_JOYSTICKS];
+static int joystick_sdl_index[MAX_JOYSTICKS];
 
 int render_width()
 {
@@ -271,7 +272,7 @@
 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"};
 void render_init(int width, int height, char * title, uint8_t fullscreen)
 {
-	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0) {
+	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
 		fatal_error("Unable to init SDL: %s\n", SDL_GetError());
 	}
 	atexit(SDL_Quit);
@@ -626,6 +627,66 @@
 	return -1;
 }
 
+int32_t render_translate_input_name(int32_t controller, char *name)
+{
+	static tern_node *button_lookup;
+	if (controller > MAX_JOYSTICKS || !joysticks[controller]) {
+		return RENDER_NOT_PLUGGED_IN;
+	}
+	
+	if (!SDL_IsGameController(joystick_sdl_index[controller])) {
+		return RENDER_NOT_MAPPED;
+	}
+	
+	if (!button_lookup) {
+		for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++)
+		{
+			button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i);
+		}
+		//alternative Playstation-style names
+		button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A);
+		button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B);
+		button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X);
+		button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y);
+		button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK);
+		button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK);
+		button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START);
+		button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
+		button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
+	}
+	intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID);
+	if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) {
+		return RENDER_INVALID_NAME;
+	}
+	SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]);
+	if (!control) {
+		warning("Failed to open game controller %d: %s\n", controller, SDL_GetError());
+		return RENDER_NOT_PLUGGED_IN;
+	}
+	SDL_GameControllerButtonBind cbind = SDL_GameControllerGetBindForButton(control, sdl_button);
+	SDL_GameControllerClose(control);
+	switch (cbind.bindType)
+	{
+	case SDL_CONTROLLER_BINDTYPE_BUTTON:
+		return cbind.value.button;
+	case SDL_CONTROLLER_BINDTYPE_AXIS:
+		return RENDER_AXIS_BIT | cbind.value.axis;
+	case SDL_CONTROLLER_BINDTYPE_HAT:
+		return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask;
+	}
+	return RENDER_NOT_MAPPED;
+}
+
+int32_t render_dpad_part(int32_t input)
+{
+	return input >> 4 & 0x7FFFFF;
+}
+
+uint8_t render_direction_part(int32_t input)
+{
+	return input & 0xF;
+}
+
 static uint8_t scancode_map[SDL_NUM_SCANCODES] = {
 	[SDL_SCANCODE_A] = 0x1C,
 	[SDL_SCANCODE_B] = 0x32,
@@ -753,6 +814,7 @@
 			int index = lowest_unused_joystick_index();
 			if (index >= 0) {
 				SDL_Joystick * joy = joysticks[index] = SDL_JoystickOpen(event->jdevice.which);
+				joystick_sdl_index[index] = event->jdevice.which;
 				if (joy) {
 					printf("Joystick %d added: %s\n", index, SDL_JoystickName(joy));
 					printf("\tNum Axes: %d\n\tNum Buttons: %d\n\tNum Hats: %d\n", SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumHats(joy));