changeset 1599:1fc61c844ec5

Allow selecting controller type when controllers have an SDL 2 mapping, but heuristics fail to idenify details
author Michael Pavone <pavone@retrodev.com>
date Fri, 27 Jul 2018 22:40:56 -0700
parents 5e2af89c3467
children 7f39c40b4b25
files Makefile config.c config.h controller_info.c controller_info.h nuklear_ui/blastem_nuklear.c
diffstat 6 files changed, 227 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Jul 25 09:38:40 2018 -0700
+++ b/Makefile	Fri Jul 27 22:40:56 2018 -0700
@@ -70,12 +70,8 @@
 endif #Windows
 
 ifdef DEBUG
-ifeq ($(OS),Darwin)
 OPT:=-g3 -O0
 else
-OPT:=-g3 -Og
-endif #Darwin
-else
 ifdef NOLTO
 OPT:=-O2
 else
--- a/config.c	Wed Jul 25 09:38:40 2018 -0700
+++ b/config.c	Fri Jul 27 22:40:56 2018 -0700
@@ -216,11 +216,33 @@
 	return ret;
 }
 
+tern_node *load_overrideable_config(char *name, char *bundled_name)
+{
+	char const *confdir = get_config_dir();
+	char *confpath = NULL;
+	tern_node *ret;
+	if (confdir) {
+		confpath = path_append(confdir, name);
+		ret = parse_config_file(confpath);
+		if (ret) {
+			free(confpath);
+			return ret;
+		}
+	}
+
+	ret = parse_bundled_config(bundled_name);
+	if (ret) {
+		free(confpath);
+		return ret;
+	}
+	return NULL;
+}
+
 tern_node *load_config()
 {
 	char const *confdir = get_config_dir();
 	char *confpath = NULL;
-	tern_node *ret;
+	tern_node *ret = load_overrideable_config("blastem.cfg", "default.cfg");
 	if (confdir) {
 		confpath = path_append(confdir, "blastem.cfg");
 		ret = parse_config_file(confpath);
@@ -236,8 +258,8 @@
 		return ret;
 	}
 
-	if (confpath) {
-		fatal_error("Failed to find a config file at %s or in the blastem executable directory\n", confpath);
+	if (get_config_dir()) {
+		fatal_error("Failed to find a config file at %s or in the blastem executable directory\n", get_config_dir());
 	} else {
 		fatal_error("Failed to find a config file in the BlastEm executable directory and the config directory path could not be determined\n");
 	}
@@ -245,20 +267,25 @@
 	return NULL;
 }
 
-void persist_config(tern_node *config)
+void persist_config_at(tern_node *config, char *fname)
 {
 	char const *confdir = get_config_dir();
 	if (!confdir) {
 		fatal_error("Failed to locate config file directory\n");
 	}
 	ensure_dir_exists(confdir);
-	char *confpath = path_append(confdir, "blastem.cfg");
+	char *confpath = path_append(confdir, fname);
 	if (!serialize_config_file(config, confpath)) {
 		fatal_error("Failed to write config to %s\n", confpath);
 	}
 	free(confpath);
 }
 
+void persist_config(tern_node *config)
+{
+	persist_config_at(config, "blastem.cfg");
+}
+
 char **get_extension_list(tern_node *config, uint32_t *num_exts_out)
 {
 	char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg"}, TVAL_PTR).ptrval);
--- a/config.h	Wed Jul 25 09:38:40 2018 -0700
+++ b/config.h	Fri Jul 27 22:40:56 2018 -0700
@@ -9,9 +9,11 @@
 
 tern_node *parse_config_file(char *config_path);
 tern_node *parse_bundled_config(char *config_name);
+tern_node *load_overrideable_config(char *name, char *bundled_name);
 tern_node *load_config();
 char *serialize_config(tern_node *config, uint32_t *size_out);
 uint8_t serialize_config_file(tern_node *config, char *path);
+void persist_config_at(tern_node *config, char *fname);
 void persist_config(tern_node *config);
 char **get_extension_list(tern_node *config, uint32_t *num_exts_out);
 uint32_t get_lowpass_cutoff(tern_node *config);
--- a/controller_info.c	Wed Jul 25 09:38:40 2018 -0700
+++ b/controller_info.c	Fri Jul 27 22:40:56 2018 -0700
@@ -1,13 +1,14 @@
 #include <string.h>
 #include "render_sdl.h"
 #include "controller_info.h"
+#include "config.h"
 
 typedef struct {
 	char const      *name;
 	controller_info info;
 } heuristic;
 
-heuristic heuristics[] = {
+static heuristic heuristics[] = {
 	//TODO: Add more heuristic rules
 	{"DualShock 4", {.type = TYPE_PSX, .subtype = SUBTYPE_PS4}},
 	{"PS4", {.type = TYPE_PSX, .subtype = SUBTYPE_PS4}},
@@ -24,15 +25,97 @@
 };
 const uint32_t num_heuristics = sizeof(heuristics)/sizeof(*heuristics);
 
+static tern_node *info_config;
+static uint8_t loaded;
+static const char *subtype_names[] = {
+	"unknown",
+	"xbox",
+	"xbox 360",
+	"xbone",
+	"ps2",
+	"ps3",
+	"ps4",
+	"wiiu",
+	"switch",
+	"genesis",
+	"saturn"
+};
+static const char *variant_names[] = {
+	"normal",
+	"6b bumpers",
+	"6b right"
+};
 controller_info get_controller_info(int joystick)
 {
+	if (!loaded) {
+		info_config = load_overrideable_config("controller_types.cfg", "controller_types.cfg");
+		loaded = 1;
+	}
+	char guid_string[33];
+	SDL_Joystick *stick = render_get_joystick(joystick);
 	SDL_GameController *control = render_get_controller(joystick);
+	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(stick), guid_string, sizeof(guid_string));
+	tern_node *info = tern_find_node(info_config, guid_string);
+	if (info) {
+		controller_info res;
+		char *subtype = tern_find_ptr(info, "subtype");
+		res.subtype = SUBTYPE_UNKNOWN;
+		if (subtype) {
+			for (int i = 0; i < SUBTYPE_NUM; i++)
+			{
+				if (!strcmp(subtype_names[i], subtype)) {
+					res.subtype = i;
+					break;
+				}
+			}
+		}
+		switch (res.subtype)
+		{
+		case SUBTYPE_XBOX:
+		case SUBTYPE_X360:
+		case SUBTYPE_XBONE:
+			res.type = TYPE_XBOX;
+			break;
+		case SUBTYPE_PS2:
+		case SUBTYPE_PS3:
+		case SUBTYPE_PS4:
+			res.type = TYPE_PSX;
+			break;
+		case SUBTYPE_WIIU:
+		case SUBTYPE_SWITCH:
+			res.type = TYPE_NINTENDO;
+			break;
+		case SUBTYPE_GENESIS:
+		case SUBTYPE_SATURN:
+			res.type = TYPE_SEGA;
+			break;
+		default:
+			res.type = TYPE_UNKNOWN;
+			break;
+		}
+		char *variant = tern_find_ptr(info, "variant");
+		res.variant = VARIANT_NORMAL;
+		if (variant) {
+			for (int i = 0; i < VARIANT_NUM; i++)
+			{
+				if (!strcmp(variant_names[i], variant)) {
+					res.variant = i;
+					break;
+				}
+			}
+		}
+		res.name = control ? SDL_GameControllerName(control) : SDL_JoystickName(stick);
+		if (control) {
+			SDL_GameControllerClose(control);
+		}
+		return res;
+	}
 	if (!control) {
 		return (controller_info) {
 			.type = TYPE_UNKNOWN,
 			.subtype = SUBTYPE_UNKNOWN,
 			.variant = VARIANT_NORMAL,
-			.name = SDL_JoystickName(render_get_joystick(joystick))
+			.name = SDL_JoystickName(stick)
 		};
 	}
 	const char *name = SDL_GameControllerName(control);
@@ -54,6 +137,18 @@
 	};
 }
 
+void save_controller_info(int joystick, controller_info *info)
+{
+	char guid_string[33];
+	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string));
+	tern_node *existing = tern_find_node(info_config, guid_string);
+	existing = tern_insert_ptr(existing, "subtype", (void *)subtype_names[info->subtype]);
+	existing = tern_insert_ptr(existing, "variant",  (void *)variant_names[info->variant]);
+	info_config = tern_insert_node(info_config, guid_string, existing);
+	persist_config_at(info_config, "controller_types.cfg");
+	
+}
+
 char const *labels_xbox[] = {
 	"A", "B", "X", "Y", "Back", NULL, "Start", "Click", "Click", "White", "Black", "LT", "RT"
 };
--- a/controller_info.h	Wed Jul 25 09:38:40 2018 -0700
+++ b/controller_info.h	Fri Jul 27 22:40:56 2018 -0700
@@ -22,13 +22,15 @@
 	SUBTYPE_WIIU,
 	SUBTYPE_SWITCH,
 	SUBTYPE_GENESIS,
-	SUBTYPE_SATURN
+	SUBTYPE_SATURN,
+	SUBTYPE_NUM
 };
 
 enum {
 	VARIANT_NORMAL,
 	VARIANT_6B_BUMPERS, //C and Z positions are RB and LB respectively
-	VARIANT_6B_RIGHT //C and Z positions are RT and RB respectively
+	VARIANT_6B_RIGHT, //C and Z positions are RT and RB respectively
+	VARIANT_NUM
 };
 
 typedef struct {
@@ -41,5 +43,6 @@
 controller_info get_controller_info(int index);
 const char *get_button_label(controller_info *info, int button);
 const char *get_axis_label(controller_info *info, int axis);
+void save_controller_info(int joystick, controller_info *info);
 
 #endif //CONTROLLER_INFO_H_
\ No newline at end of file
--- a/nuklear_ui/blastem_nuklear.c	Wed Jul 25 09:38:40 2018 -0700
+++ b/nuklear_ui/blastem_nuklear.c	Fri Jul 27 22:40:56 2018 -0700
@@ -516,16 +516,33 @@
 enum {
 	UP,DOWN,LEFT,RIGHT
 };
-void binding_box(struct nk_context *context, char *name, float x, float y, float width, int num_binds, int *binds)
+
+static char * config_ps_names[] = {
+	[SDL_CONTROLLER_BUTTON_A] = "cross",
+	[SDL_CONTROLLER_BUTTON_B] = "circle",
+	[SDL_CONTROLLER_BUTTON_X] = "square",
+	[SDL_CONTROLLER_BUTTON_Y] = "triangle",
+	[SDL_CONTROLLER_BUTTON_BACK] = "share",
+	[SDL_CONTROLLER_BUTTON_START] = "options",
+	[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = "l1",
+	[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = "r1",
+	[SDL_CONTROLLER_BUTTON_LEFTSTICK] = "l3",
+	[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = "r3",
+};
+
+static void binding_box(struct nk_context *context, char *name, float x, float y, float width, int num_binds, int *binds)
 {
 	const struct nk_user_font *font = context->style.font;
 	float row_height = font->height * 2;
 	
-	nk_layout_space_push(context, nk_rect(x, y, width, num_binds * (row_height + 4) + 4));
-	nk_group_begin(context, name, NK_WINDOW_BORDER | NK_WINDOW_NO_SCROLLBAR);
+	char const **labels = calloc(sizeof(char *), num_binds);
+	char **conf_keys = calloc(sizeof(char *), num_binds);
+	float max_width = 0.0f;
 	
-	char const **labels = calloc(sizeof(char *), num_binds);
-	float max_width = 0.0f;
+	static const char base_path[] = "bindings\0pads";
+	static const char buttons[] = "buttons";
+	char padkey[] = {'0' + selected_controller, 0};
+	int skipped = 0;
 	for (int i = 0; i < num_binds; i++)
 	{
 		if (binds[i] & AXIS) {
@@ -535,16 +552,37 @@
 			labels[i] = dirs[binds[i] & 3];
 		} else {
 			labels[i] = get_button_label(&selected_controller_info, binds[i]);
+			char template[] = "bindings\0pads\00\0buttons\0";
+			const char *but_name = SDL_GameControllerGetStringForButton(binds[i]);
+			size_t namelen = strlen(but_name);
+			conf_keys[i] = malloc(sizeof(base_path) + sizeof(padkey) + sizeof(buttons) + namelen + 2);
+			memcpy(conf_keys[i], base_path, sizeof(base_path));
+			memcpy(conf_keys[i] + sizeof(base_path), padkey, sizeof(padkey));
+			memcpy(conf_keys[i] + sizeof(base_path) + sizeof(padkey), buttons, sizeof(buttons));
+			
+			memcpy(conf_keys[i] + sizeof(base_path) + sizeof(padkey) + sizeof(buttons), but_name, namelen+1);
+			conf_keys[i][sizeof(base_path) + sizeof(padkey) + sizeof(buttons) + namelen + 1] = 0;
+		}
+		if (!labels[i]) {
+			skipped++;
+			continue;
 		}
 		float lb_width = font->width(font->userdata, font->height, labels[i], strlen(labels[i]));
 		max_width = max_width < lb_width ? lb_width : max_width;
 	}
+	nk_layout_space_push(context, nk_rect(x, y, width, (num_binds - skipped) * (row_height + 4) + 4));
+	nk_group_begin(context, name, NK_WINDOW_BORDER | NK_WINDOW_NO_SCROLLBAR);
+	
 	float widths[] = {max_width + 3, width - (max_width + 6)};
 	nk_layout_row(context, NK_STATIC, row_height, 2, widths);
 	for (int i = 0; i < num_binds; i++)
 	{
+		if (!labels[i]) {
+			continue;
+		}
 		nk_label(context, labels[i], NK_TEXT_LEFT);
 		nk_button_label(context, i & 1 ? "Internal Screenshot" : "A");
+		free(conf_keys[i]);
 	}
 	nk_group_end(context);
 }
@@ -655,6 +693,43 @@
 	}
 }
 
+void controller_type_group(struct nk_context *context, char *name, int type_id, int first_subtype_id, const char **types, uint32_t num_types)
+{
+	nk_layout_row_static(context, (context->style.font->height + 3) * num_types + context->style.font->height, render_width() - 80, 1);
+	if (nk_group_begin(context, name, NK_WINDOW_TITLE)) {
+		nk_layout_row_static(context, context->style.font->height, render_width()/2 - 80, 2);
+		for (int i = 0; i < num_types; i++)
+		{
+			if (nk_button_label(context, types[i])) {
+				selected_controller_info.subtype = first_subtype_id + i;
+				save_controller_info(selected_controller, &selected_controller_info);
+				pop_view();
+				push_view(view_controller_bindings);
+			}
+		}
+		nk_group_end(context);
+	}
+}
+
+void view_controller_type(struct nk_context *context)
+{
+	if (nk_begin(context, "Controller Type", nk_rect(0, 0, render_width(), render_height()), 0)) {
+		controller_type_group(context, "Xbox", TYPE_XBOX, SUBTYPE_XBOX, (const char *[]){
+			"Original", "Xbox 360", "Xbox One"
+		}, 3);
+		controller_type_group(context, "Playstation", TYPE_PSX, SUBTYPE_PS3, (const char *[]){
+			"PS3", "PS4"
+		}, 2);
+		controller_type_group(context, "Sega", TYPE_SEGA, SUBTYPE_GENESIS, (const char *[]){
+			"Genesis", "Saturn"
+		}, 2);
+		controller_type_group(context, "Nintendo", TYPE_NINTENDO, SUBTYPE_WIIU, (const char *[]){
+			"WiiU", "Switch"
+		}, 2);
+		nk_end(context);
+	}
+}
+
 void view_controllers(struct nk_context *context)
 {
 	if (nk_begin(context, "Controllers", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
@@ -667,12 +742,21 @@
 				controller_info info = get_controller_info(i);
 				nk_layout_row_begin(context, NK_STATIC, height, 2);
 				nk_layout_row_push(context, image_width);
-				nk_image(context, controller_360_image);
+				if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) {
+					nk_label(context, "?", NK_TEXT_CENTERED);
+				} else {
+					nk_image(context, controller_360_image);
+				}
 				nk_layout_row_push(context, render_width() - image_width - 2 * context->style.font->height);
 				if (nk_button_label(context, info.name)) {
 					selected_controller = i;
 					selected_controller_info = info;
-					push_view(view_controller_bindings);
+					if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) {
+						push_view(view_controller_type);
+					} else {
+						push_view(view_controller_bindings);
+					}
+					
 				}
 				nk_layout_row_end(context);
 			}