diff io.c @ 1207:9d6f155732ed

Basic support for mapping an analog axis to functionality
author Michael Pavone <pavone@retrodev.com>
date Thu, 26 Jan 2017 23:49:13 -0800
parents a6ae693974e0
children 95f5253e75c7
line wrap: on
line diff
--- a/io.c	Thu Jan 26 20:30:33 2017 -0800
+++ b/io.c	Thu Jan 26 23:49:13 2017 -0800
@@ -95,10 +95,18 @@
 } joydpad;
 
 typedef struct {
+	keybinding positive;
+	keybinding negative;
+	int16_t    value;
+} joyaxis;
+
+typedef struct {
 	keybinding *buttons;
 	joydpad    *dpads;
+	joyaxis    *axes;
 	uint32_t   num_buttons; //number of entries in the buttons array, not necessarily the number of buttons on the device
 	uint32_t   num_dpads;   //number of entries in the dpads array, not necessarily the number of dpads on the device
+	uint32_t   num_axes;    //number of entries in the axes array, not necessarily the number of dpads on the device
 } joystick;
 
 typedef struct {
@@ -116,6 +124,14 @@
 static io_port *keyboard_port;
 const uint8_t dpadbits[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
 
+static void do_bind(keybinding *binding, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+{
+	binding->bind_type = bind_type;
+	binding->subtype_a = subtype_a;
+	binding->subtype_b = subtype_b;
+	binding->value = value;
+}
+
 void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
 {
 	int bucket = keycode >> 15 & 0xFFFF;
@@ -124,10 +140,7 @@
 		memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000);
 	}
 	int idx = keycode & 0x7FFF;
-	bindings[bucket][idx].bind_type = bind_type;
-	bindings[bucket][idx].subtype_a = subtype_a;
-	bindings[bucket][idx].subtype_b = subtype_b;
-	bindings[bucket][idx].value = value;
+	do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b, value);
 }
 
 void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
@@ -144,10 +157,7 @@
 		joysticks[joystick].buttons = realloc(joysticks[joystick].buttons, sizeof(keybinding) * joysticks[joystick].num_buttons);
 		memset(joysticks[joystick].buttons + old_capacity, 0, joysticks[joystick].num_buttons - old_capacity);
 	}
-	joysticks[joystick].buttons[button].bind_type = bind_type;
-	joysticks[joystick].buttons[button].subtype_a = subtype_a;
-	joysticks[joystick].buttons[button].subtype_b = subtype_b;
-	joysticks[joystick].buttons[button].value = value;
+	do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b, value);
 }
 
 void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
@@ -156,26 +166,45 @@
 		return;
 	}
 	if (!joysticks[joystick].dpads) {
-		//multiple D-pads hats are not common, so don't allocate any extra space
+		//multiple D-pads/hats are not common, so don't allocate any extra space
 		joysticks[joystick].dpads = calloc(dpad+1, sizeof(joydpad));
 		joysticks[joystick].num_dpads = dpad+1;
 	} else if (joysticks[joystick].num_dpads <= dpad) {
 		uint32_t old_capacity = joysticks[joystick].num_dpads;
 		joysticks[joystick].num_dpads *= 2;
 		joysticks[joystick].dpads = realloc(joysticks[joystick].dpads, sizeof(joydpad) * joysticks[joystick].num_dpads);
-		memset(joysticks[joystick].dpads + old_capacity, 0, joysticks[joystick].num_dpads - old_capacity);
+		memset(joysticks[joystick].dpads + old_capacity, 0, (joysticks[joystick].num_dpads - old_capacity) * sizeof(joydpad));
 	}
 	for (int i = 0; i < 4; i ++) {
 		if (dpadbits[i] & direction) {
-			joysticks[joystick].dpads[dpad].bindings[i].bind_type = bind_type;
-			joysticks[joystick].dpads[dpad].bindings[i].subtype_a = subtype_a;
-			joysticks[joystick].dpads[dpad].bindings[i].subtype_b = subtype_b;
-			joysticks[joystick].dpads[dpad].bindings[i].value = value;
+			do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b, value);
 			break;
 		}
 	}
 }
 
+void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (!joysticks[joystick].axes) {
+		//typical gamepad has 4 axes
+		joysticks[joystick].num_axes = axis+1 > 4 ? axis+1 : 4;
+		joysticks[joystick].axes = calloc(joysticks[joystick].num_axes, sizeof(joyaxis));
+	} else if (joysticks[joystick].num_axes <= axis) {
+		uint32_t old_capacity = joysticks[joystick].num_axes;
+		joysticks[joystick].num_axes *= 2;
+		joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes);
+		memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis));
+	}
+	if (positive) {
+		do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b, value);
+	} else {
+		do_bind(&joysticks[joystick].axes[axis].negative, bind_type, subtype_a, subtype_b, value);
+	}
+}
+
 void reset_joystick_bindings(int joystick)
 {
 	if (joystick >= MAX_JOYSTICKS) {
@@ -196,6 +225,13 @@
 			}
 		}
 	}
+	if (joysticks[joystick].axes) {
+		for (int i = 0; i < joysticks[joystick].num_axes; i++)
+		{
+			joysticks[joystick].axes[i].positive.bind_type = BIND_NONE;
+			joysticks[joystick].axes[i].negative.bind_type = BIND_NONE;
+		}
+	}
 }
 
 #define GAMEPAD_BUTTON(PRI_SLOT, SEC_SLOT, VALUE)  (PRI_SLOT << 12 | SEC_SLOT << 8 | VALUE)
@@ -219,8 +255,6 @@
 #define MOUSE_MIDDLE         4
 #define MOUSE_START          8
 
-
-
 void bind_gamepad(int keycode, int gamepadnum, int button)
 {
 
@@ -249,6 +283,15 @@
 	bind_dpad(joystick, dpad, direction, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
 }
 
+void bind_axis_gamepad(int joystick, int axis, uint8_t positive, int gamepadnum, int button)
+{
+	if (gamepadnum < 1 || gamepadnum > 8) {
+		return;
+	}
+	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
+	bind_axis(joystick, axis, positive, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
+}
+
 void bind_ui(int keycode, ui_action action, uint8_t param)
 {
 	bind_key(keycode, BIND_UI, action, 0, param);
@@ -264,6 +307,11 @@
 	bind_dpad(joystick, dpad, direction, BIND_UI, action, 0, param);
 }
 
+void bind_axis_ui(int joystick, int axis, uint8_t positive, ui_action action, uint8_t param)
+{
+	bind_axis(joystick, axis, positive, BIND_UI, action, 0, param);
+}
+
 void handle_binding_down(keybinding * binding)
 {
 	if (binding->bind_type >= BIND_GAMEPAD1 && binding->bind_type <= BIND_GAMEPAD8)
@@ -466,6 +514,29 @@
 	}
 }
 
+#define JOY_AXIS_THRESHOLD 2000
+
+void handle_joy_axis(int joystick, int axis, int16_t value)
+{
+	if (joystick >= MAX_JOYSTICKS  || axis >= joysticks[joystick].num_axes) {
+		return;
+	}
+	joyaxis *jaxis = joysticks[joystick].axes + axis;
+	int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD;
+	int new_active = abs(value) > JOY_AXIS_THRESHOLD;
+	int old_pos = jaxis->value > 0;
+	int new_pos = value > 0;
+	jaxis->value = value;
+	if (old_active && (!new_active || old_pos != new_pos)) {
+		//previously activated direction is no longer active
+		handle_binding_up(old_pos ? &jaxis->positive : &jaxis->negative);
+	}
+	if (new_active && (!old_active || old_pos != new_pos)) {
+		//previously unactivated direction is now active
+		handle_binding_down(new_pos ? &jaxis->positive : &jaxis->negative);
+	}
+}
+
 void handle_mouseup(int mouse, int button)
 {
 	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
@@ -967,7 +1038,7 @@
 	long hostbutton = strtol(key, &end, 10);
 	if (*end) {
 		//key is not a valid base 10 integer
-		hostbutton = render_translate_input_name(hostpadnum, key);
+		hostbutton = render_translate_input_name(hostpadnum, key, 0);
 		if (hostbutton < 0) {
 			if (hostbutton == RENDER_INVALID_NAME) {
 				warning("%s is not a valid gamepad input name\n", key);
@@ -982,8 +1053,13 @@
 			} else {
 				bind_dpad_ui(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), ui_func, button);
 			}
+			return;
 		} else if (hostbutton & RENDER_AXIS_BIT) {
-			//TODO: support analog axes
+			if (bindtype == BIND_GAMEPAD1) {
+				bind_axis_gamepad(hostpadnum, render_axis_part(hostbutton), 1, padnum, button);
+			} else {
+				bind_axis_ui(hostpadnum, render_axis_part(hostbutton), 1, padnum, button);
+			}
 			return;
 		}
 	}
@@ -994,6 +1070,65 @@
 	}
 }
 
+void process_pad_axis(char *key, tern_val val, void *data)
+{
+	key = strdup(key);
+	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 *modifier = strchr(key, '.');
+	int positive = 1;
+	if (modifier) {
+		*modifier = 0;
+		modifier++;
+		if (!strcmp("negative", modifier)) {
+			positive = 0;
+		} else if(strcmp("positive", modifier)) {
+			warning("Invalid axis modifier %s for axis %s on pad %d\n", modifier, key, hostpadnum);
+		}
+	}
+	char *end;
+	long axis = strtol(key, &end, 10);
+	if (*end) {
+		//key is not a valid base 10 integer
+		axis = render_translate_input_name(hostpadnum, key, 1);
+		if (axis < 0) {
+			if (axis == RENDER_INVALID_NAME) {
+				warning("%s is not a valid gamepad input name\n", key);
+			} else if (axis == RENDER_NOT_MAPPED) {
+				warning("No mapping exists for input %s on gamepad %d\n", key, hostpadnum);
+			}
+			goto done;
+		}
+		if (axis & RENDER_DPAD_BIT) {
+			if (bindtype == BIND_GAMEPAD1) {
+				bind_dpad_gamepad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), padnum, button);
+			} else {
+				bind_dpad_ui(hostpadnum, render_dpad_part(axis), render_direction_part(axis), ui_func, button);
+			}
+			goto done;
+		} else if (axis & RENDER_AXIS_BIT) {
+			axis = render_axis_part(axis);
+		} else {
+			if (bindtype == BIND_GAMEPAD1) {
+				bind_button_gamepad(hostpadnum, axis, padnum, button);
+			} else if (bindtype == BIND_UI) {
+				bind_button_ui(hostpadnum, axis, ui_func, button);
+			}
+			goto done;
+		}
+	}
+	if (bindtype == BIND_GAMEPAD1) {
+		bind_axis_gamepad(hostpadnum, axis, positive, padnum, button);
+	} else {
+		bind_axis_ui(hostpadnum, axis, positive, ui_func, button);
+	}
+done:
+	free(key);
+	return;
+}
+
 static tern_node *get_pad_buttons()
 {
 	static tern_node *padbuttons;
@@ -1070,16 +1205,33 @@
 				};
 				tern_foreach(button_node, process_pad_button, &state);
 			}
+			tern_node *axes_node = tern_find_ptr(pad, "axes");
+			if (axes_node) {
+				pad_button_state state = {
+					.padnum = joystick,
+					.padbuttons = get_pad_buttons(),
+					.mousebuttons = get_mouse_buttons()
+				};
+				tern_foreach(axes_node, process_pad_axis, &state);
+			}
 			if (current_io) {
 				if (joysticks[joystick].buttons) {
 					map_bindings(current_io->ports, joysticks[joystick].buttons, joysticks[joystick].num_buttons);
 				}
 				if (joysticks[joystick].dpads)
 				{
-					for (uint32_t i = 0; i < joysticks[joystick].num_dpads; i++) {
+					for (uint32_t i = 0; i < joysticks[joystick].num_dpads; i++)
+					{
 						map_bindings(current_io->ports, joysticks[joystick].dpads[i].bindings, 4);
 					}
 				}
+				if (joysticks[joystick].axes) {
+					for (uint32_t i = 0; i < joysticks[joystick].num_axes; i++)
+					{
+						map_bindings(current_io->ports, &joysticks[joystick].axes[i].positive, 1);
+						map_bindings(current_io->ports, &joysticks[joystick].axes[i].negative, 1);
+					}
+				}
 			}
 		}
 	}
@@ -1193,10 +1345,16 @@
 		}
 		if (joysticks[stick].dpads)
 		{
-			for (uint32_t i = 0; i < joysticks[stick].num_dpads; i++) {
+			for (uint32_t i = 0; i < joysticks[stick].num_dpads; i++)
+			{
 				map_bindings(ports, joysticks[stick].dpads[i].bindings, 4);
 			}
 		}
+		for (uint32_t i = 0; i < joysticks[stick].num_axes; i++)
+		{
+			map_bindings(current_io->ports, &joysticks[stick].axes[i].positive, 1);
+			map_bindings(current_io->ports, &joysticks[stick].axes[i].negative, 1);
+		}
 	}
 	for (int mouse = 0; mouse < MAX_MICE; mouse++)
 	{