changeset 1583:430dd12e4010

Refactor to split device bindings from IO emulation code
author Michael Pavone <pavone@retrodev.com>
date Sat, 05 May 2018 23:30:40 -0700
parents a74db49fa6b1
children e01adbe1a75b
files Makefile bindings.c bindings.h blastem.c genesis.c io.c io.h render_sdl.c sms.c system.h
diffstat 10 files changed, 1324 insertions(+), 1319 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed May 02 09:41:05 2018 -0700
+++ b/Makefile	Sat May 05 23:30:40 2018 -0700
@@ -149,7 +149,7 @@
 
 MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
 	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
-	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o 
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o
 	
 ifdef NONUKLEAR
 CFLAGS+= -DDISABLE_NUKLEAR
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings.c	Sat May 05 23:30:40 2018 -0700
@@ -0,0 +1,1000 @@
+#include "render.h"
+#include "system.h"
+#include "io.h"
+#include "blastem.h"
+#include "saves.h"
+#include "util.h"
+#include "genesis.h"
+#include "menu.h"
+#include "bindings.h"
+#ifndef DISABLE_NUKLEAR
+#include "nuklear_ui/blastem_nuklear.h"
+#endif
+
+enum {
+	BIND_NONE,
+	BIND_UI,
+	BIND_GAMEPAD,
+	BIND_MOUSE
+};
+
+typedef enum {
+	UI_DEBUG_MODE_INC,
+	UI_DEBUG_PAL_INC,
+	UI_ENTER_DEBUGGER,
+	UI_SAVE_STATE,
+	UI_SET_SPEED,
+	UI_NEXT_SPEED,
+	UI_PREV_SPEED,
+	UI_RELEASE_MOUSE,
+	UI_TOGGLE_KEYBOARD_CAPTURE,
+	UI_TOGGLE_FULLSCREEN,
+	UI_SOFT_RESET,
+	UI_RELOAD,
+	UI_SMS_PAUSE,
+	UI_SCREENSHOT,
+	UI_EXIT
+} ui_action;
+
+typedef struct {
+	uint8_t bind_type;
+	uint8_t subtype_a;
+	uint8_t subtype_b;
+} keybinding;
+
+typedef struct {
+	keybinding bindings[4];
+	uint8_t    state;
+} 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 {
+	keybinding buttons[MAX_MOUSE_BUTTONS];
+	keybinding motion;
+} mousebinding;
+
+#define DEFAULT_JOYBUTTON_ALLOC 12
+static keybinding *bindings[0x10000];
+static joystick joysticks[MAX_JOYSTICKS];
+static mousebinding mice[MAX_MICE];
+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)
+{
+	binding->bind_type = bind_type;
+	binding->subtype_a = subtype_a;
+	binding->subtype_b = subtype_b;
+}
+
+void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	int bucket = keycode >> 15 & 0xFFFF;
+	if (!bindings[bucket]) {
+		bindings[bucket] = malloc(sizeof(keybinding) * 0x8000);
+		memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000);
+	}
+	int idx = keycode & 0x7FFF;
+	do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b);
+}
+
+void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (!joysticks[joystick].buttons) {
+		joysticks[joystick].num_buttons = button < DEFAULT_JOYBUTTON_ALLOC ? DEFAULT_JOYBUTTON_ALLOC : button + 1;
+		joysticks[joystick].buttons = calloc(joysticks[joystick].num_buttons, sizeof(keybinding));
+	} else if (joysticks[joystick].num_buttons <= button) {
+		uint32_t old_capacity = joysticks[joystick].num_buttons;
+		joysticks[joystick].num_buttons *= 2;
+		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);
+	}
+	do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b);
+}
+
+void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (!joysticks[joystick].dpads) {
+		//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) * sizeof(joydpad));
+	}
+	for (int i = 0; i < 4; i ++) {
+		if (dpadbits[i] & direction) {
+			do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b);
+			break;
+		}
+	}
+}
+
+void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	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);
+	} else {
+		do_bind(&joysticks[joystick].axes[axis].negative, bind_type, subtype_a, subtype_b);
+	}
+}
+
+void reset_joystick_bindings(int joystick)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (joysticks[joystick].buttons) {
+		for (int i = 0; i < joysticks[joystick].num_buttons; i++)
+		{
+			joysticks[joystick].buttons[i].bind_type = BIND_NONE;
+		}
+	}
+	if (joysticks[joystick].dpads) {
+		for (int i = 0; i < joysticks[joystick].num_dpads; i++)
+		{
+			for (int dir = 0; dir < 4; dir++)
+			{
+				joysticks[joystick].dpads[i].bindings[dir].bind_type = BIND_NONE;
+			}
+		}
+	}
+	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;
+		}
+	}
+}
+
+void handle_binding_down(keybinding * binding)
+{
+	if (!current_system) {
+		return;
+	}
+	if (binding->bind_type == BIND_GAMEPAD && current_system && current_system->gamepad_down)
+	{
+		current_system->gamepad_down(current_system, binding->subtype_a, binding->subtype_b);
+	}
+	else if (binding->bind_type == BIND_MOUSE && current_system && current_system->mouse_down)
+	{
+		current_system->mouse_down(current_system, binding->subtype_a, binding->subtype_b);
+	}
+}
+
+static uint8_t keyboard_captured;
+void handle_keydown(int keycode, uint8_t scancode)
+{
+	int bucket = keycode >> 15 & 0xFFFF;
+	int idx = keycode & 0x7FFF;
+	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
+	if (binding && (!keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
+		handle_binding_down(binding);
+	} else if (keyboard_captured && current_system && current_system->keyboard_down) {
+		current_system->keyboard_down(current_system, scancode);
+	}
+}
+
+void handle_joydown(int joystick, int button)
+{
+	if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) {
+		return;
+	}
+	keybinding * binding = joysticks[joystick].buttons + button;
+	handle_binding_down(binding);
+}
+
+static uint8_t mouse_mode = MOUSE_NONE;
+static uint8_t mouse_captured;
+void handle_mousedown(int mouse, int button)
+{
+	if (mouse_mode == MOUSE_CAPTURE && !mouse_captured) {
+		mouse_captured = 1;
+		render_relative_mouse(1);
+		return;
+	}
+	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
+		return;
+	}
+	keybinding * binding = mice[mouse].buttons + button - 1;
+	handle_binding_down(binding);
+}
+
+static uint8_t ui_debug_mode = 0;
+static uint8_t ui_debug_pal = 0;
+
+static int current_speed = 0;
+static int num_speeds = 1;
+static uint32_t * speeds = NULL;
+
+static uint8_t mouse_captured;
+
+#ifdef _WIN32
+#define localtime_r(a,b) localtime(a)
+#endif
+
+void handle_binding_up(keybinding * binding)
+{
+	switch(binding->bind_type)
+	{
+	case BIND_GAMEPAD:
+		if (current_system && current_system->gamepad_up) {
+			current_system->gamepad_up(current_system, binding->subtype_a, binding->subtype_b);
+		}
+		break;
+	case BIND_MOUSE:
+		if (current_system && current_system->mouse_up) {
+			current_system->mouse_up(current_system, binding->subtype_a, binding->subtype_b);
+		}
+		break;
+	case BIND_UI:
+		switch (binding->subtype_a)
+		{
+		case UI_DEBUG_MODE_INC:
+			current_system->inc_debug_mode(current_system);
+			break;
+		case UI_DEBUG_PAL_INC:
+			current_system->inc_debug_pal(current_system);
+			break;
+		case UI_ENTER_DEBUGGER:
+			current_system->enter_debugger = 1;
+			break;
+		case UI_SAVE_STATE:
+			current_system->save_state = QUICK_SAVE_SLOT+1;
+			break;
+		case UI_NEXT_SPEED:
+			current_speed++;
+			if (current_speed >= num_speeds) {
+				current_speed = 0;
+			}
+			printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
+			current_system->set_speed_percent(current_system, speeds[current_speed]);
+			break;
+		case UI_PREV_SPEED:
+			current_speed--;
+			if (current_speed < 0) {
+				current_speed = num_speeds - 1;
+			}
+			printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
+			current_system->set_speed_percent(current_system, speeds[current_speed]);
+			break;
+		case UI_SET_SPEED:
+			if (binding->subtype_b < num_speeds) {
+				current_speed = binding->subtype_b;
+				printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
+				current_system->set_speed_percent(current_system, speeds[current_speed]);
+			} else {
+				printf("Setting speed to %d\n", speeds[current_speed]);
+				current_system->set_speed_percent(current_system, speeds[current_speed]);
+			}
+			break;
+		case UI_RELEASE_MOUSE:
+			if (mouse_captured) {
+				mouse_captured = 0;
+				render_relative_mouse(0);
+			}
+			break;
+		case UI_TOGGLE_KEYBOARD_CAPTURE:
+			if (current_system && current_system->has_keyboard) {
+				keyboard_captured = !keyboard_captured;
+			}
+			break;
+		case UI_TOGGLE_FULLSCREEN:
+			render_toggle_fullscreen();
+			break;
+		case UI_SOFT_RESET:
+			current_system->soft_reset(current_system);
+			break;
+		case UI_RELOAD:
+			reload_media();
+			break;
+		case UI_SMS_PAUSE:
+			if (current_system && current_system->gamepad_down) {
+				current_system->gamepad_down(current_system, GAMEPAD_MAIN_UNIT, MAIN_UNIT_PAUSE);
+			}
+			break;
+		case UI_SCREENSHOT: {
+			char *screenshot_base = tern_find_path(config, "ui\0screenshot_path\0", TVAL_PTR).ptrval;
+			if (!screenshot_base) {
+				screenshot_base = "$HOME";
+			}
+			tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
+			vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
+			screenshot_base = replace_vars(screenshot_base, vars, 1);
+			tern_free(vars);
+			time_t now = time(NULL);
+			struct tm local_store;
+			char fname_part[256];
+			char *template = tern_find_path(config, "ui\0screenshot_template\0", TVAL_PTR).ptrval;
+			if (!template) {
+				template = "blastem_%c.ppm";
+			}
+			strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
+			char const *parts[] = {screenshot_base, PATH_SEP, fname_part};
+			char *path = alloc_concat_m(3, parts);
+			free(screenshot_base);
+			render_save_screenshot(path);
+			break;
+		}
+		case UI_EXIT:
+#ifndef DISABLE_NUKLEAR
+			if (is_nuklear_active()) {
+				show_pause_menu();
+			} else {
+#endif
+			current_system->request_exit(current_system);
+			if (current_system->type == SYSTEM_GENESIS) {
+				genesis_context *gen = (genesis_context *)current_system;
+				if (gen->extra) {
+					//TODO: More robust mechanism for detecting menu
+					menu_context *menu = gen->extra;
+					menu->external_game_load = 1;
+				}
+			}
+#ifndef DISABLE_NUKLEAR
+			}
+#endif
+			break;
+		}
+		break;
+	}
+}
+
+void handle_keyup(int keycode, uint8_t scancode)
+{
+	int bucket = keycode >> 15 & 0xFFFF;
+	int idx = keycode & 0x7FFF;
+	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
+	if (binding && (!keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
+		handle_binding_up(binding);
+	} else if (keyboard_captured && current_system && current_system->keyboard_up) {
+		current_system->keyboard_up(current_system, scancode);
+	}
+}
+
+void handle_joyup(int joystick, int button)
+{
+	if (joystick >= MAX_JOYSTICKS  || button >= joysticks[joystick].num_buttons) {
+		return;
+	}
+	keybinding * binding = joysticks[joystick].buttons + button;
+	handle_binding_up(binding);
+}
+
+void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
+{
+	if (joystick >= MAX_JOYSTICKS  || dpadnum >= joysticks[joystick].num_dpads) {
+		return;
+	}
+	joydpad * dpad = joysticks[joystick].dpads + dpadnum;
+	uint8_t newdown = (value ^ dpad->state) & value;
+	uint8_t newup = ((~value) ^ (~dpad->state)) & (~value);
+	dpad->state = value;
+	for (int i = 0; i < 4; i++) {
+		if (newdown & dpadbits[i]) {
+			handle_binding_down(dpad->bindings + i);
+		} else if(newup & dpadbits[i]) {
+			handle_binding_up(dpad->bindings + i);
+		}
+	}
+}
+
+#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_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay)
+{
+	if (mouse >= MAX_MICE || !current_system) {
+		return;
+	}
+	if (mice[mouse].motion.bind_type == BIND_MOUSE && mice[mouse].motion.subtype_b == PSEUDO_BUTTON_MOTION) {
+		uint8_t target_mouse = mice[mouse].motion.subtype_a;
+		switch(mouse_mode)
+		{
+		case MOUSE_NONE:
+			break;
+		case MOUSE_ABSOLUTE: {
+			if (current_system->mouse_motion_absolute) {
+				float scale_x = (render_emulated_width() * 2.0f) / ((float)render_width());
+				float scale_y = (render_emulated_height() * 2.0f) / ((float)render_height());
+				int32_t adj_x = x * scale_x + 2 * render_overscan_left() - 2 * BORDER_LEFT;
+				int32_t adj_y = y * scale_y + 2 * render_overscan_top() - 4;
+				
+				current_system->mouse_motion_absolute(current_system, target_mouse, adj_x, adj_y);
+			}
+			break;
+		}
+		case MOUSE_RELATIVE: {
+			if (current_system->mouse_motion_relative) {
+				current_system->mouse_motion_relative(current_system, target_mouse, deltax, deltay);
+			}
+			break;
+		}
+		case MOUSE_CAPTURE: {
+			if (mouse_captured && current_system->mouse_motion_relative) {
+				current_system->mouse_motion_relative(current_system, target_mouse, deltax, deltay);
+			}
+			break;
+		}
+		}
+	} else {
+		handle_binding_up(&mice[mouse].motion);
+	}
+}
+
+void handle_mouseup(int mouse, int button)
+{
+	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
+		return;
+	}
+	keybinding * binding = mice[mouse].buttons + button - 1;
+	handle_binding_up(binding);
+}
+
+void bindings_release_capture(void)
+{
+	if (mouse_mode == MOUSE_RELATIVE || (mouse_mode == MOUSE_CAPTURE && mouse_captured)) {
+		render_relative_mouse(0);
+	}
+	keyboard_captured = 0;
+}
+
+void bindings_reacquire_capture(void)
+{
+	if (mouse_mode == MOUSE_RELATIVE || (mouse_mode == MOUSE_CAPTURE && mouse_captured)) {
+		render_relative_mouse(1);
+	}
+}
+
+int parse_binding_target(char * target, tern_node * padbuttons, tern_node *mousebuttons, uint8_t * subtype_a, uint8_t * subtype_b)
+{
+	const int gpadslen = strlen("gamepads.");
+	const int mouselen = strlen("mouse.");
+	if (!strncmp(target, "gamepads.", gpadslen)) {
+		if (target[gpadslen] >= '1' && target[gpadslen] <= '8') {
+			int padnum = target[gpadslen] - '0';
+			int button = tern_find_int(padbuttons, target + gpadslen + 1, 0);
+			if (button) {
+				*subtype_a = padnum;
+				*subtype_b = button;
+				return BIND_GAMEPAD;
+			} else {
+				if (target[gpadslen+1]) {
+					warning("Gamepad mapping string '%s' refers to an invalid button '%s'\n", target, target + gpadslen + 1);
+				} else {
+					warning("Gamepad mapping string '%s' has no button component\n", target);
+				}
+			}
+		} else {
+			warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]);
+		}
+	} else if(!strncmp(target, "mouse.", mouselen)) {
+		if (target[mouselen] >= '1' && target[mouselen] <= '8') {
+			int mousenum = target[mouselen] - '0';
+			int button = tern_find_int(mousebuttons, target + mouselen + 1, 0);
+			if (button) {
+				*subtype_a = mousenum;
+				*subtype_b = button;
+				return BIND_MOUSE;
+			} else {
+				if (target[mouselen+1]) {
+					warning("Mouse mapping string '%s' refers to an invalid button '%s'\n", target, target + mouselen + 1);
+				} else {
+					warning("Mouse mapping string '%s' has no button component\n", target);
+				}
+			}
+		} else {
+			warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]);
+		}
+	} else if(!strncmp(target, "ui.", strlen("ui."))) {
+		if (!strcmp(target + 3, "vdp_debug_mode")) {
+			*subtype_a = UI_DEBUG_MODE_INC;
+		} else if(!strcmp(target + 3, "vdp_debug_pal")) {
+			*subtype_a = UI_DEBUG_PAL_INC;
+		} else if(!strcmp(target + 3, "enter_debugger")) {
+			*subtype_a = UI_ENTER_DEBUGGER;
+		} else if(!strcmp(target + 3, "save_state")) {
+			*subtype_a = UI_SAVE_STATE;
+		} else if(!strncmp(target + 3, "set_speed.", strlen("set_speed."))) {
+			*subtype_a = UI_SET_SPEED;
+			*subtype_b = atoi(target + 3 + strlen("set_speed."));
+		} else if(!strcmp(target + 3, "next_speed")) {
+			*subtype_a = UI_NEXT_SPEED;
+		} else if(!strcmp(target + 3, "prev_speed")) {
+			*subtype_a = UI_PREV_SPEED;
+		} else if(!strcmp(target + 3, "release_mouse")) {
+			*subtype_a = UI_RELEASE_MOUSE;
+		} else if(!strcmp(target + 3, "toggle_keyboard_captured")) {
+			*subtype_a = UI_TOGGLE_KEYBOARD_CAPTURE;
+		} else if (!strcmp(target + 3, "toggle_fullscreen")) {
+			*subtype_a = UI_TOGGLE_FULLSCREEN;
+		} else if (!strcmp(target + 3, "soft_reset")) {
+			*subtype_a = UI_SOFT_RESET;
+		} else if (!strcmp(target + 3, "reload")) {
+			*subtype_a = UI_RELOAD;
+		} else if (!strcmp(target + 3, "sms_pause")) {
+			*subtype_a = UI_SMS_PAUSE;
+		} else if (!strcmp(target + 3, "screenshot")) {
+			*subtype_a = UI_SCREENSHOT;
+		} else if(!strcmp(target + 3, "exit")) {
+			*subtype_a = UI_EXIT;
+		} else {
+			warning("Unreconized UI binding type %s\n", target);
+			return 0;
+		}
+		return BIND_UI;
+	} else {
+		warning("Unrecognized binding type %s\n", target);
+	}
+	return 0;
+}
+
+void process_keys(tern_node * cur, tern_node * special, tern_node * padbuttons, tern_node *mousebuttons, char * prefix)
+{
+	char * curstr = NULL;
+	int len;
+	if (!cur) {
+		return;
+	}
+	char onec[2];
+	if (prefix) {
+		len = strlen(prefix);
+		curstr = malloc(len + 2);
+		memcpy(curstr, prefix, len);
+	} else {
+		curstr = onec;
+		len = 0;
+	}
+	curstr[len] = cur->el;
+	curstr[len+1] = 0;
+	if (cur->el) {
+		process_keys(cur->straight.next, special, padbuttons, mousebuttons, curstr);
+	} else {
+		int keycode = tern_find_int(special, curstr, 0);
+		if (!keycode) {
+			keycode = curstr[0];
+			if (curstr[1] != 0) {
+				warning("%s is not recognized as a key identifier, truncating to %c\n", curstr, curstr[0]);
+			}
+		}
+		char * target = cur->straight.value.ptrval;
+		uint8_t subtype_a = 0, subtype_b = 0;
+		int bindtype = parse_binding_target(target, padbuttons, mousebuttons, &subtype_a, &subtype_b);
+		bind_key(keycode, bindtype, subtype_a, subtype_b);
+	}
+	process_keys(cur->left, special, padbuttons, mousebuttons, prefix);
+	process_keys(cur->right, special, padbuttons, mousebuttons, prefix);
+	if (curstr && len) {
+		free(curstr);
+	}
+}
+
+void process_speeds(tern_node * cur, char * prefix)
+{
+	char * curstr = NULL;
+	int len;
+	if (!cur) {
+		return;
+	}
+	char onec[2];
+	if (prefix) {
+		len = strlen(prefix);
+		curstr = malloc(len + 2);
+		memcpy(curstr, prefix, len);
+	} else {
+		curstr = onec;
+		len = 0;
+	}
+	curstr[len] = cur->el;
+	curstr[len+1] = 0;
+	if (cur->el) {
+		process_speeds(cur->straight.next, curstr);
+	} else {
+		char *end;
+		long speed_index = strtol(curstr, &end, 10);
+		if (speed_index < 0 || end == curstr || *end) {
+			warning("%s is not a valid speed index", curstr);
+		} else {
+			if (speed_index >= num_speeds) {
+				speeds = realloc(speeds, sizeof(uint32_t) * (speed_index+1));
+				for(; num_speeds < speed_index + 1; num_speeds++) {
+					speeds[num_speeds] = 0;
+				}
+			}
+			speeds[speed_index] = atoi(cur->straight.value.ptrval);
+			if (speeds[speed_index] < 1) {
+				warning("%s is not a valid speed percentage, setting speed %d to 100", cur->straight.value.ptrval, speed_index);
+				speeds[speed_index] = 100;
+			}
+		}
+	}
+	process_speeds(cur->left, prefix);
+	process_speeds(cur->right, prefix);
+	if (curstr && len) {
+		free(curstr);
+	}
+}
+
+typedef struct {
+	tern_node *padbuttons;
+	tern_node *mousebuttons;
+	int       mouseidx;
+} pmb_state;
+
+void process_mouse_button(char *buttonstr, tern_val value, uint8_t valtype, void *data)
+{
+	pmb_state *state = data;
+	int buttonnum = atoi(buttonstr);
+	if (buttonnum < 1 || buttonnum > MAX_MOUSE_BUTTONS) {
+		warning("Mouse button %s is out of the supported range of 1-8\n", buttonstr);
+		return;
+	}
+	if (valtype != TVAL_PTR) {
+		warning("Mouse button %s is not a scalar value!\n", buttonstr);
+		return;
+	}
+	buttonnum--;
+	uint8_t subtype_a = 0, subtype_b = 0;
+	int bindtype = parse_binding_target(value.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b);
+	mice[state->mouseidx].buttons[buttonnum].bind_type = bindtype;
+	mice[state->mouseidx].buttons[buttonnum].subtype_a = subtype_a;
+	mice[state->mouseidx].buttons[buttonnum].subtype_b = subtype_b;
+}
+
+void process_mouse(char *mousenum, tern_val value, uint8_t valtype, void *data)
+{
+	tern_node **buttonmaps = data;
+	if (valtype != TVAL_NODE) {
+		warning("Binding for mouse %s is a scalar!\n", mousenum);
+		return;
+	}
+	tern_node *mousedef = value.ptrval;
+	tern_node *padbuttons = buttonmaps[0];
+	tern_node *mousebuttons = buttonmaps[1];
+
+	int mouseidx = atoi(mousenum);
+	if (mouseidx < 0 || mouseidx >= MAX_MICE) {
+		warning("Mouse numbers must be between 0 and %d, but %d is not\n", MAX_MICE, mouseidx);
+		return;
+	}
+	char *motion = tern_find_ptr(mousedef, "motion");
+	if (motion) {
+		uint8_t subtype_a = 0, subtype_b = 0;
+		int bindtype = parse_binding_target(motion, padbuttons, mousebuttons, &subtype_a, &subtype_b);
+		mice[mouseidx].motion.bind_type = bindtype;
+		mice[mouseidx].motion.subtype_a = subtype_a;
+		mice[mouseidx].motion.subtype_b = subtype_b;
+	}
+	tern_node *buttons = tern_find_path(mousedef, "buttons\0\0", TVAL_NODE).ptrval;
+	if (buttons) {
+		pmb_state state = {padbuttons, mousebuttons, mouseidx};
+		tern_foreach(buttons, process_mouse_button, &state);
+	}
+}
+
+typedef struct {
+	int       padnum;
+	tern_node *padbuttons;
+	tern_node *mousebuttons;
+} pad_button_state;
+
+
+static long map_warning_pad = -1;
+void process_pad_button(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	pad_button_state *state = data;
+	int hostpadnum = state->padnum;
+	if (valtype != TVAL_PTR) {
+		warning("Pad button %s has a non-scalar value\n", key);
+		return;
+	}
+	uint8_t subtype_a = 0, subtype_b = 0;
+	int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b);
+	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, 0);
+		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 && hostpadnum != map_warning_pad) {
+				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
+				map_warning_pad = hostpadnum;
+			}
+			return;
+		}
+		if (hostbutton & RENDER_DPAD_BIT) {
+			bind_dpad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b);
+			return;
+		} else if (hostbutton & RENDER_AXIS_BIT) {
+			bind_axis(hostpadnum, render_axis_part(hostbutton), 1, bindtype, subtype_a, subtype_b);
+			return;
+		}
+	}
+	bind_button(hostpadnum, hostbutton, bindtype, subtype_a, subtype_b);
+}
+
+void process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	key = strdup(key);
+	pad_button_state *state = data;
+	int hostpadnum = state->padnum;
+	if (valtype != TVAL_PTR) {
+		warning("Mapping for axis %s has a non-scalar value", key);
+		return;
+	}
+	uint8_t subtype_a = 0, subtype_b = 0;
+	int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b);
+	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 && hostpadnum != map_warning_pad) {
+				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
+				map_warning_pad = hostpadnum;
+			}
+			goto done;
+		}
+		if (axis & RENDER_DPAD_BIT) {
+			bind_dpad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), bindtype, subtype_a, subtype_b);
+			goto done;
+		} else if (axis & RENDER_AXIS_BIT) {
+			axis = render_axis_part(axis);
+		} else {
+			bind_button(hostpadnum, axis, bindtype, subtype_a, subtype_b);
+			goto done;
+		}
+	}
+	bind_axis(hostpadnum, axis, positive, bindtype, subtype_a, subtype_b);
+done:
+	free(key);
+	return;
+}
+
+static tern_node *get_pad_buttons()
+{
+	static tern_node *padbuttons;
+	if (!padbuttons) {
+		padbuttons = tern_insert_int(NULL, ".up", DPAD_UP);
+		padbuttons = tern_insert_int(padbuttons, ".down", DPAD_DOWN);
+		padbuttons = tern_insert_int(padbuttons, ".left", DPAD_LEFT);
+		padbuttons = tern_insert_int(padbuttons, ".right", DPAD_RIGHT);
+		padbuttons = tern_insert_int(padbuttons, ".a", BUTTON_A);
+		padbuttons = tern_insert_int(padbuttons, ".b", BUTTON_B);
+		padbuttons = tern_insert_int(padbuttons, ".c", BUTTON_C);
+		padbuttons = tern_insert_int(padbuttons, ".x", BUTTON_X);
+		padbuttons = tern_insert_int(padbuttons, ".y", BUTTON_Y);
+		padbuttons = tern_insert_int(padbuttons, ".z", BUTTON_Z);
+		padbuttons = tern_insert_int(padbuttons, ".start", BUTTON_START);
+		padbuttons = tern_insert_int(padbuttons, ".mode", BUTTON_MODE);
+	}
+	return padbuttons;
+}
+
+static tern_node *get_mouse_buttons()
+{
+	static tern_node *mousebuttons;
+	if (!mousebuttons) {
+		mousebuttons = tern_insert_int(NULL, ".left", MOUSE_LEFT);
+		mousebuttons = tern_insert_int(mousebuttons, ".middle", MOUSE_MIDDLE);
+		mousebuttons = tern_insert_int(mousebuttons, ".right", MOUSE_RIGHT);
+		mousebuttons = tern_insert_int(mousebuttons, ".start", MOUSE_START);
+		mousebuttons = tern_insert_int(mousebuttons, ".motion", PSEUDO_BUTTON_MOTION);
+	}
+	return mousebuttons;
+}
+
+void handle_joy_added(int joystick)
+{
+	if (joystick > MAX_JOYSTICKS) {
+		return;
+	}
+	tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
+	if (pads) {
+		char numstr[11];
+		sprintf(numstr, "%d", joystick);
+		tern_node * pad = tern_find_node(pads, numstr);
+		if (pad) {
+			tern_node * dpad_node = tern_find_node(pad, "dpads");
+			if (dpad_node) {
+				for (int dpad = 0; dpad < 10; dpad++)
+				{
+					numstr[0] = dpad + '0';
+					numstr[1] = 0;
+					tern_node * pad_dpad = tern_find_node(dpad_node, numstr);
+					char * dirs[] = {"up", "down", "left", "right"};
+					//TODO: Support controllers that have d-pads implemented as analog axes or buttons
+					int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
+					for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) {
+						char * target = tern_find_ptr(pad_dpad, dirs[dir]);
+						if (target) {
+							uint8_t subtype_a = 0, subtype_b = 0;
+							int bindtype = parse_binding_target(target, get_pad_buttons(), get_mouse_buttons(), &subtype_a, &subtype_b);
+							bind_dpad(joystick, dpad, dirnums[dir], bindtype, subtype_a, subtype_b);
+						}
+					}
+				}
+			}
+			tern_node *button_node = tern_find_node(pad, "buttons");
+			if (button_node) {
+				pad_button_state state = {
+					.padnum = joystick,
+					.padbuttons = get_pad_buttons(),
+					.mousebuttons = get_mouse_buttons()
+				};
+				tern_foreach(button_node, process_pad_button, &state);
+			}
+			tern_node *axes_node = tern_find_node(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);
+			}
+		}
+	}
+	
+}
+
+//only handles keyboards and mice as gamepads are handled on hotplug events
+void set_bindings(void)
+{
+	tern_node * special = tern_insert_int(NULL, "up", RENDERKEY_UP);
+	special = tern_insert_int(special, "down", RENDERKEY_DOWN);
+	special = tern_insert_int(special, "left", RENDERKEY_LEFT);
+	special = tern_insert_int(special, "right", RENDERKEY_RIGHT);
+	special = tern_insert_int(special, "enter", '\r');
+	special = tern_insert_int(special, "space", ' ');
+	special = tern_insert_int(special, "tab", '\t');
+	special = tern_insert_int(special, "backspace", '\b');
+	special = tern_insert_int(special, "esc", RENDERKEY_ESC);
+	special = tern_insert_int(special, "delete", RENDERKEY_DEL);
+	special = tern_insert_int(special, "lshift", RENDERKEY_LSHIFT);
+	special = tern_insert_int(special, "rshift", RENDERKEY_RSHIFT);
+	special = tern_insert_int(special, "lctrl", RENDERKEY_LCTRL);
+	special = tern_insert_int(special, "rctrl", RENDERKEY_RCTRL);
+	special = tern_insert_int(special, "lalt", RENDERKEY_LALT);
+	special = tern_insert_int(special, "ralt", RENDERKEY_RALT);
+	special = tern_insert_int(special, "home", RENDERKEY_HOME);
+	special = tern_insert_int(special, "end", RENDERKEY_END);
+	special = tern_insert_int(special, "pageup", RENDERKEY_PAGEUP);
+	special = tern_insert_int(special, "pagedown", RENDERKEY_PAGEDOWN);
+	special = tern_insert_int(special, "f1", RENDERKEY_F1);
+	special = tern_insert_int(special, "f2", RENDERKEY_F2);
+	special = tern_insert_int(special, "f3", RENDERKEY_F3);
+	special = tern_insert_int(special, "f4", RENDERKEY_F4);
+	special = tern_insert_int(special, "f5", RENDERKEY_F5);
+	special = tern_insert_int(special, "f6", RENDERKEY_F6);
+	special = tern_insert_int(special, "f7", RENDERKEY_F7);
+	special = tern_insert_int(special, "f8", RENDERKEY_F8);
+	special = tern_insert_int(special, "f9", RENDERKEY_F9);
+	special = tern_insert_int(special, "f10", RENDERKEY_F10);
+	special = tern_insert_int(special, "f11", RENDERKEY_F11);
+	special = tern_insert_int(special, "f12", RENDERKEY_F12);
+	special = tern_insert_int(special, "select", RENDERKEY_SELECT);
+	special = tern_insert_int(special, "play", RENDERKEY_PLAY);
+	special = tern_insert_int(special, "search", RENDERKEY_SEARCH);
+	special = tern_insert_int(special, "back", RENDERKEY_BACK);
+	special = tern_insert_int(special, "np0", RENDERKEY_NP0);
+	special = tern_insert_int(special, "np1", RENDERKEY_NP1);
+	special = tern_insert_int(special, "np2", RENDERKEY_NP2);
+	special = tern_insert_int(special, "np3", RENDERKEY_NP3);
+	special = tern_insert_int(special, "np4", RENDERKEY_NP4);
+	special = tern_insert_int(special, "np5", RENDERKEY_NP5);
+	special = tern_insert_int(special, "np6", RENDERKEY_NP6);
+	special = tern_insert_int(special, "np7", RENDERKEY_NP7);
+	special = tern_insert_int(special, "np8", RENDERKEY_NP8);
+	special = tern_insert_int(special, "np9", RENDERKEY_NP9);
+	special = tern_insert_int(special, "np/", RENDERKEY_NP_DIV);
+	special = tern_insert_int(special, "np*", RENDERKEY_NP_MUL);
+	special = tern_insert_int(special, "np-", RENDERKEY_NP_MIN);
+	special = tern_insert_int(special, "np+", RENDERKEY_NP_PLUS);
+	special = tern_insert_int(special, "npenter", RENDERKEY_NP_ENTER);
+	special = tern_insert_int(special, "np.", RENDERKEY_NP_STOP);
+
+	tern_node *padbuttons = get_pad_buttons();
+
+	tern_node *mousebuttons = get_mouse_buttons();
+	
+	tern_node * keys = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
+	process_keys(keys, special, padbuttons, mousebuttons, NULL);
+	
+	memset(mice, 0, sizeof(mice));
+	tern_node * mice = tern_find_path(config, "bindings\0mice\0", TVAL_NODE).ptrval;
+	if (mice) {
+		tern_node *buttonmaps[2] = {padbuttons, mousebuttons};
+		tern_foreach(mice, process_mouse, buttonmaps);
+	}
+	tern_node * speed_nodes = tern_find_path(config, "clocks\0speeds\0", TVAL_NODE).ptrval;
+	speeds = malloc(sizeof(uint32_t));
+	speeds[0] = 100;
+	process_speeds(speed_nodes, NULL);
+	for (int i = 0; i < num_speeds; i++)
+	{
+		if (!speeds[i]) {
+			warning("Speed index %d was not set to a valid percentage!", i);
+			speeds[i] = 100;
+		}
+	}
+}
+
+void bindings_set_mouse_mode(uint8_t mode)
+{
+	mouse_mode = mode;
+	if (mode == MOUSE_RELATIVE) {
+		render_relative_mouse(1);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings.h	Sat May 05 23:30:40 2018 -0700
@@ -0,0 +1,28 @@
+#ifndef BINDINGS_H_
+#define BINDINGS_H_
+#include <stdint.h>
+
+typedef enum {
+	MOUSE_NONE,     //mouse is ignored
+	MOUSE_ABSOLUTE, //really only useful for menu ROM
+	MOUSE_RELATIVE, //for full screen
+	MOUSE_CAPTURE   //for windowed mode
+} mouse_modes;
+
+void set_bindings(void);
+void bindings_set_mouse_mode(uint8_t mode);
+void handle_keydown(int keycode, uint8_t scancode);
+void handle_keyup(int keycode, uint8_t scancode);
+void handle_joydown(int joystick, int button);
+void handle_joyup(int joystick, int button);
+void handle_joy_dpad(int joystick, int dpad, uint8_t state);
+void handle_joy_axis(int joystick, int axis, int16_t value);
+void handle_joy_added(int joystick);
+void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay);
+void handle_mousedown(int mouse, int button);
+void handle_mouseup(int mouse, int button);
+
+void bindings_release_capture(void);
+void bindings_reacquire_capture(void);
+
+#endif //BINDINGS_H_
--- a/blastem.c	Wed May 02 09:41:05 2018 -0700
+++ b/blastem.c	Sat May 05 23:30:40 2018 -0700
@@ -23,6 +23,7 @@
 #include "terminal.h"
 #include "arena.h"
 #include "config.h"
+#include "bindings.h"
 #include "menu.h"
 #include "zip.h"
 #ifndef DISABLE_NUKLEAR
@@ -562,6 +563,7 @@
 		render_init(width, height, "BlastEm", fullscreen);
 		render_set_drag_drop_handler(on_drag_drop);
 	}
+	set_bindings();
 	
 	uint8_t menu = !loaded;
 	uint8_t use_nuklear = 0;
--- a/genesis.c	Wed May 02 09:41:05 2018 -0700
+++ b/genesis.c	Sat May 05 23:30:40 2018 -0700
@@ -16,6 +16,7 @@
 #include "debug.h"
 #include "gdb_remote.h"
 #include "saves.h"
+#include "bindings.h"
 #define MCLKS_NTSC 53693175
 #define MCLKS_PAL  53203395
 
@@ -1063,7 +1064,7 @@
 			resume_68k(gen->m68k);
 		}
 	}
-	io_release_capture(&gen->io);
+	bindings_release_capture();
 	vdp_release_framebuffer(gen->vdp);
 	render_pause_source(gen->ym->audio);
 	render_pause_source(gen->psg->audio);
@@ -1072,7 +1073,6 @@
 static void start_genesis(system_header *system, char *statefile)
 {
 	genesis_context *gen = (genesis_context *)system;
-	set_keybindings(&gen->io);
 	if (statefile) {
 		//first try loading as a native format savestate
 		deserialize_buffer state;
@@ -1110,9 +1110,8 @@
 static void resume_genesis(system_header *system)
 {
 	genesis_context *gen = (genesis_context *)system;
-	map_all_bindings(&gen->io);
 	render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC);
-	io_reacquire_capture(&gen->io);
+	bindings_reacquire_capture();
 	vdp_reacquire_framebuffer(gen->vdp);
 	render_resume_source(gen->ym->audio);
 	render_resume_source(gen->psg->audio);
@@ -1204,6 +1203,54 @@
 	free(gen);
 }
 
+static void gamepad_down(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_gamepad_down(&gen->io, gamepad_num, button);
+}
+
+static void gamepad_up(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_gamepad_up(&gen->io, gamepad_num, button);
+}
+
+static void mouse_down(system_header *system, uint8_t mouse_num, uint8_t button)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_mouse_down(&gen->io, mouse_num, button);
+}
+
+static void mouse_up(system_header *system, uint8_t mouse_num, uint8_t button)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_mouse_up(&gen->io, mouse_num, button);
+}
+
+static void mouse_motion_absolute(system_header *system, uint8_t mouse_num, uint16_t x, uint16_t y)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_mouse_motion_absolute(&gen->io, mouse_num, x, y);
+}
+
+static void mouse_motion_relative(system_header *system, uint8_t mouse_num, int32_t x, int32_t y)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_mouse_motion_relative(&gen->io, mouse_num, x, y);
+}
+
+static void keyboard_down(system_header *system, uint8_t scancode)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_keyboard_down(&gen->io, scancode);
+}
+
+static void keyboard_up(system_header *system, uint8_t scancode)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_keyboard_up(&gen->io, scancode);
+}
+
 genesis_context *alloc_init_genesis(rom_info *rom, void *main_rom, void *lock_on, uint32_t system_opts, uint8_t force_region)
 {
 	static memmap_chunk z80_map[] = {
@@ -1226,6 +1273,14 @@
 	gen->header.request_exit = request_exit;
 	gen->header.inc_debug_mode = inc_debug_mode;
 	gen->header.inc_debug_pal = inc_debug_pal;
+	gen->header.gamepad_down = gamepad_down;
+	gen->header.gamepad_up = gamepad_up;
+	gen->header.mouse_down = mouse_down;
+	gen->header.mouse_up = mouse_up;
+	gen->header.mouse_motion_absolute = mouse_motion_absolute;
+	gen->header.mouse_motion_relative = mouse_motion_relative;
+	gen->header.keyboard_down = keyboard_down;
+	gen->header.keyboard_up = keyboard_up;
 	gen->header.type = SYSTEM_GENESIS;
 	set_region(gen, rom, force_region);
 
@@ -1294,6 +1349,7 @@
 		}
 	}
 	setup_io_devices(config, rom, &gen->io);
+	gen->header.has_keyboard = io_has_keyboard(&gen->io);
 
 	gen->mapper_type = rom->mapper_type;
 	gen->save_type = rom->save_type;
--- a/io.c	Wed May 02 09:41:05 2018 -0700
+++ b/io.c	Sat May 05 23:30:40 2018 -0700
@@ -18,15 +18,9 @@
 #include "serialize.h"
 #include "io.h"
 #include "blastem.h"
-#include "genesis.h"
-#include "sms.h"
 #include "render.h"
 #include "util.h"
-#include "menu.h"
-#include "saves.h"
-#ifndef DISABLE_NUKLEAR
-#include "nuklear_ui/blastem_nuklear.h"
-#endif
+#include "bindings.h"
 
 #define CYCLE_NEVER 0xFFFFFFFF
 #define MIN_POLL_INTERVAL 6840
@@ -48,304 +42,137 @@
 	"None"
 };
 
-enum {
-	BIND_NONE,
-	BIND_UI,
-	BIND_GAMEPAD1,
-	BIND_GAMEPAD2,
-	BIND_GAMEPAD3,
-	BIND_GAMEPAD4,
-	BIND_GAMEPAD5,
-	BIND_GAMEPAD6,
-	BIND_GAMEPAD7,
-	BIND_GAMEPAD8,
-	BIND_MOUSE1,
-	BIND_MOUSE2,
-	BIND_MOUSE3,
-	BIND_MOUSE4,
-	BIND_MOUSE5,
-	BIND_MOUSE6,
-	BIND_MOUSE7,
-	BIND_MOUSE8
-};
+#define GAMEPAD_TH0 0
+#define GAMEPAD_TH1 1
+#define GAMEPAD_EXTRA 2
+#define GAMEPAD_NONE 0xF
 
-typedef enum {
-	UI_DEBUG_MODE_INC,
-	UI_DEBUG_PAL_INC,
-	UI_ENTER_DEBUGGER,
-	UI_SAVE_STATE,
-	UI_SET_SPEED,
-	UI_NEXT_SPEED,
-	UI_PREV_SPEED,
-	UI_RELEASE_MOUSE,
-	UI_TOGGLE_KEYBOARD_CAPTURE,
-	UI_TOGGLE_FULLSCREEN,
-	UI_SOFT_RESET,
-	UI_RELOAD,
-	UI_SMS_PAUSE,
-	UI_SCREENSHOT,
-	UI_EXIT
-} ui_action;
+#define IO_TH0 0
+#define IO_TH1 1
+#define IO_STATE 2
 
-typedef enum {
-	MOUSE_NONE,     //mouse is ignored
-	MOUSE_ABSOLUTE, //really only useful for menu ROM
-	MOUSE_RELATIVE, //for full screen
-	MOUSE_CAPTURE   //for windowed mode
-} mouse_modes;
-
-
-typedef struct {
-	io_port *port;
-	uint8_t bind_type;
-	uint8_t subtype_a;
-	uint8_t subtype_b;
-	uint8_t value;
-} keybinding;
-
-typedef struct {
-	keybinding bindings[4];
-	uint8_t    state;
-} joydpad;
-
-typedef struct {
-	keybinding positive;
-	keybinding negative;
-	int16_t    value;
-} joyaxis;
+enum {
+	IO_WRITE_PENDING,
+	IO_WRITTEN,
+	IO_READ_PENDING,
+	IO_READ
+};
 
 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;
+	uint8_t states[2], value;
+} gp_button_def;
 
-typedef struct {
-	io_port    *motion_port;
-	keybinding buttons[MAX_MOUSE_BUTTONS];
-	uint8_t    bind_type;
-} mousebinding;
 
-#define DEFAULT_JOYBUTTON_ALLOC 12
+static gp_button_def button_defs[NUM_GAMEPAD_BUTTONS] = {
+	[DPAD_UP] = {.states = {GAMEPAD_TH0, GAMEPAD_TH1}, .value = 0x1},
+	[DPAD_DOWN] = {.states = {GAMEPAD_TH0, GAMEPAD_TH1}, .value = 0x2},
+	[DPAD_LEFT] = {.states = {GAMEPAD_TH1, GAMEPAD_NONE}, .value = 0x4},
+	[DPAD_RIGHT] = {.states = {GAMEPAD_TH1, GAMEPAD_NONE}, .value = 0x8},
+	[BUTTON_A] = {.states = {GAMEPAD_TH0, GAMEPAD_NONE}, .value = 0x10},
+	[BUTTON_B] = {.states = {GAMEPAD_TH1, GAMEPAD_NONE}, .value = 0x10},
+	[BUTTON_C] = {.states = {GAMEPAD_TH1, GAMEPAD_NONE}, .value = 0x20},
+	[BUTTON_START] = {.states = {GAMEPAD_TH0, GAMEPAD_NONE}, .value = 0x20},
+	[BUTTON_X] = {.states = {GAMEPAD_EXTRA, GAMEPAD_NONE}, .value = 0x4},
+	[BUTTON_Y] = {.states = {GAMEPAD_EXTRA, GAMEPAD_NONE}, .value = 0x2},
+	[BUTTON_Z] = {.states = {GAMEPAD_EXTRA, GAMEPAD_NONE}, .value = 0x1},
+	[BUTTON_MODE] = {.states = {GAMEPAD_EXTRA, GAMEPAD_NONE}, .value = 0x8},
+};
 
-static sega_io *current_io;
-static keybinding *bindings[0x10000];
-static joystick joysticks[MAX_JOYSTICKS];
-static mousebinding mice[MAX_MICE];
-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)
+static io_port *find_gamepad(sega_io *io, uint8_t gamepad_num)
 {
-	binding->bind_type = bind_type;
-	binding->subtype_a = subtype_a;
-	binding->subtype_b = subtype_b;
-	binding->value = value;
+	for (int i = 0; i < 3; i++)
+	{
+		io_port *port = io->ports + i;
+		if (port->device_type < IO_MOUSE && port->device.pad.gamepad_num == gamepad_num) {
+			return port;
+		}
+	}
+	return NULL;
 }
 
-void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+static io_port *find_mouse(sega_io *io, uint8_t mouse_num)
 {
-	int bucket = keycode >> 15 & 0xFFFF;
-	if (!bindings[bucket]) {
-		bindings[bucket] = malloc(sizeof(keybinding) * 0x8000);
-		memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000);
+	for (int i = 0; i < 3; i++)
+	{
+		io_port *port = io->ports + i;
+		if (port->device_type == IO_MOUSE && port->device.mouse.mouse_num == mouse_num) {
+			return port;
+		}
 	}
-	int idx = keycode & 0x7FFF;
-	do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b, value);
+	return NULL;
 }
 
-void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+static io_port *find_keyboard(sega_io *io)
 {
-	if (joystick >= MAX_JOYSTICKS) {
-		return;
+	for (int i = 0; i < 3; i++)
+	{
+		io_port *port = io->ports + i;
+		if (port->device_type == IO_SATURN_KEYBOARD || port->device_type == IO_XBAND_KEYBOARD) {
+			return port;
+		}
 	}
-	if (!joysticks[joystick].buttons) {
-		joysticks[joystick].num_buttons = button < DEFAULT_JOYBUTTON_ALLOC ? DEFAULT_JOYBUTTON_ALLOC : button + 1;
-		joysticks[joystick].buttons = calloc(joysticks[joystick].num_buttons, sizeof(keybinding));
-	} else if (joysticks[joystick].num_buttons <= button) {
-		uint32_t old_capacity = joysticks[joystick].num_buttons;
-		joysticks[joystick].num_buttons *= 2;
-		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);
-	}
-	do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b, value);
+	return NULL;
 }
 
-void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+void io_gamepad_down(sega_io *io, uint8_t gamepad_num, uint8_t button)
 {
-	if (joystick >= MAX_JOYSTICKS) {
-		return;
-	}
-	if (!joysticks[joystick].dpads) {
-		//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) * sizeof(joydpad));
-	}
-	for (int i = 0; i < 4; i ++) {
-		if (dpadbits[i] & direction) {
-			do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b, value);
-			break;
+	io_port *port = find_gamepad(io, gamepad_num);
+	if (port) {
+		gp_button_def *def = button_defs + button;
+		port->input[def->states[0]] |= def->value;
+		if (def->states[1] != GAMEPAD_NONE) {
+			port->input[def->states[1]] |= def->value;
 		}
 	}
 }
 
-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)
+void io_gamepad_up(sega_io *io, uint8_t gamepad_num, uint8_t button)
 {
-	if (joystick >= MAX_JOYSTICKS) {
-		return;
-	}
-	if (joysticks[joystick].buttons) {
-		for (int i = 0; i < joysticks[joystick].num_buttons; i++)
-		{
-			joysticks[joystick].buttons[i].bind_type = BIND_NONE;
-		}
-	}
-	if (joysticks[joystick].dpads) {
-		for (int i = 0; i < joysticks[joystick].num_dpads; i++)
-		{
-			for (int dir = 0; dir < 4; dir++)
-			{
-				joysticks[joystick].dpads[i].bindings[dir].bind_type = BIND_NONE;
-			}
-		}
-	}
-	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;
+	io_port *port = find_gamepad(io, gamepad_num);
+	if (port) {
+		gp_button_def *def = button_defs + button;
+		port->input[def->states[0]] &= ~def->value;
+		if (def->states[1] != GAMEPAD_NONE) {
+			port->input[def->states[1]] &= ~def->value;
 		}
 	}
 }
 
-#define GAMEPAD_BUTTON(PRI_SLOT, SEC_SLOT, VALUE)  (PRI_SLOT << 12 | SEC_SLOT << 8 | VALUE)
-
-#define DPAD_UP      GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_TH1, 0x01)
-#define BUTTON_Z     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x01)
-#define DPAD_DOWN    GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_TH1, 0x02)
-#define BUTTON_Y     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x02)
-#define DPAD_LEFT    GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x04)
-#define BUTTON_X     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x04)
-#define DPAD_RIGHT   GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x08)
-#define BUTTON_MODE  GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x08)
-#define BUTTON_A     GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_NONE, 0x10)
-#define BUTTON_B     GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x10)
-#define BUTTON_START GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_NONE, 0x20)
-#define BUTTON_C     GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x20)
-
-#define PSEUDO_BUTTON_MOTION 0xFFFF
-#define MOUSE_LEFT           1
-#define MOUSE_RIGHT          2
-#define MOUSE_MIDDLE         4
-#define MOUSE_START          8
-
-void bind_gamepad(int keycode, int gamepadnum, int button)
+void io_mouse_down(sega_io *io, uint8_t mouse_num, uint8_t button)
 {
-
-	if (gamepadnum < 1 || gamepadnum > 8) {
-		return;
+	io_port *port = find_mouse(io, mouse_num);
+	if (port) {
+		port->input[0] |= button;
 	}
-	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
-	bind_key(keycode, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
-}
-
-void bind_button_gamepad(int joystick, int joybutton, int gamepadnum, int padbutton)
-{
-	if (gamepadnum < 1 || gamepadnum > 8) {
-		return;
-	}
-	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
-	bind_button(joystick, joybutton, bind_type, padbutton >> 12, padbutton >> 8 & 0xF, padbutton & 0xFF);
 }
 
-void bind_dpad_gamepad(int joystick, int dpad, uint8_t direction, int gamepadnum, int button)
-{
-	if (gamepadnum < 1 || gamepadnum > 8) {
-		return;
-	}
-	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
-	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);
-}
-
-void bind_button_ui(int joystick, int joybutton, ui_action action, uint8_t param)
+void io_mouse_up(sega_io *io, uint8_t mouse_num, uint8_t button)
 {
-	bind_button(joystick, joybutton, BIND_UI, action, 0, param);
-}
-
-void bind_dpad_ui(int joystick, int dpad, uint8_t direction, ui_action action, uint8_t param)
-{
-	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)
-	{
-		if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
-			binding->port->input[binding->subtype_a] |= binding->value;
-		}
-		if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
-			binding->port->input[binding->subtype_b] |= binding->value;
-		}
-	}
-	else if (binding->bind_type >= BIND_MOUSE1 && binding->bind_type <= BIND_MOUSE8)
-	{
-		if (binding->port) {
-			binding->port->input[0] |= binding->value;
-		}
+	io_port *port = find_mouse(io, mouse_num);
+	if (port) {
+		port->input[0] &= ~button;
 	}
 }
 
-void store_key_event(uint16_t code)
+void io_mouse_motion_absolute(sega_io *io, uint8_t mouse_num, uint16_t x, uint16_t y)
+{
+	io_port *port = find_mouse(io, mouse_num);
+	if (port) {
+		port->device.mouse.cur_x = x;
+		port->device.mouse.cur_y = y;
+	}
+}
+
+void io_mouse_motion_relative(sega_io *io, uint8_t mouse_num, int32_t x, int32_t y)
+{
+	io_port *port = find_mouse(io, mouse_num);
+	if (port) {
+		port->device.mouse.cur_x += x;
+		port->device.mouse.cur_y += y;
+	}
+}
+
+void store_key_event(io_port *keyboard_port, uint16_t code)
 {
 	if (keyboard_port && keyboard_port->device.keyboard.write_pos != keyboard_port->device.keyboard.read_pos) {
 		//there's room in the buffer, record this event
@@ -358,509 +185,19 @@
 	}
 }
 
-void handle_keydown(int keycode, uint8_t scancode)
-{
-	if (!current_io) {
-		return;
-	}
-	int bucket = keycode >> 15 & 0xFFFF;
-	int idx = keycode & 0x7FFF;
-	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
-	if (binding && (!current_io->keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
-		handle_binding_down(binding);
-	} else if (current_io->keyboard_captured) {
-		store_key_event(scancode);
-	}
-}
-
-void handle_joydown(int joystick, int button)
-{
-	if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) {
-		return;
-	}
-	keybinding * binding = joysticks[joystick].buttons + button;
-	handle_binding_down(binding);
-}
-
-void handle_mousedown(int mouse, int button)
-{
-	if (!current_io) {
-		return;
-	}
-	if (current_io->mouse_mode == MOUSE_CAPTURE && !current_io->mouse_captured) {
-		current_io->mouse_captured = 1;
-		render_relative_mouse(1);
-		return;
-	}
-	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
-		return;
-	}
-	keybinding * binding = mice[mouse].buttons + button - 1;
-	handle_binding_down(binding);
-}
-
-uint8_t ui_debug_mode = 0;
-uint8_t ui_debug_pal = 0;
-
-int current_speed = 0;
-int num_speeds = 1;
-uint32_t * speeds = NULL;
-
-uint8_t is_keyboard(io_port *port)
-{
-	return port->device_type == IO_SATURN_KEYBOARD || port->device_type == IO_XBAND_KEYBOARD;
-}
-
-uint8_t keyboard_connected(sega_io *io)
-{
-	return is_keyboard(io->ports) || is_keyboard(io->ports+1) || is_keyboard(io->ports+2);
-}
-
-#ifdef _WIN32
-#define localtime_r(a,b) localtime(a)
-#endif
-
-void handle_binding_up(keybinding * binding)
+void io_keyboard_down(sega_io *io, uint8_t scancode)
 {
-	switch(binding->bind_type)
-	{
-	case BIND_GAMEPAD1:
-	case BIND_GAMEPAD2:
-	case BIND_GAMEPAD3:
-	case BIND_GAMEPAD4:
-	case BIND_GAMEPAD5:
-	case BIND_GAMEPAD6:
-	case BIND_GAMEPAD7:
-	case BIND_GAMEPAD8:
-		if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
-			binding->port->input[binding->subtype_a] &= ~binding->value;
-		}
-		if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
-			binding->port->input[binding->subtype_b] &= ~binding->value;
-		}
-		break;
-	case BIND_MOUSE1:
-	case BIND_MOUSE2:
-	case BIND_MOUSE3:
-	case BIND_MOUSE4:
-	case BIND_MOUSE5:
-	case BIND_MOUSE6:
-	case BIND_MOUSE7:
-	case BIND_MOUSE8:
-		if (binding->port) {
-			binding->port->input[0] &= ~binding->value;
-		}
-		break;
-	case BIND_UI:
-		switch (binding->subtype_a)
-		{
-		case UI_DEBUG_MODE_INC:
-			current_system->inc_debug_mode(current_system);
-			break;
-		case UI_DEBUG_PAL_INC:
-			current_system->inc_debug_pal(current_system);
-			break;
-		case UI_ENTER_DEBUGGER:
-			current_system->enter_debugger = 1;
-			break;
-		case UI_SAVE_STATE:
-			current_system->save_state = QUICK_SAVE_SLOT+1;
-			break;
-		case UI_NEXT_SPEED:
-			current_speed++;
-			if (current_speed >= num_speeds) {
-				current_speed = 0;
-			}
-			printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
-			current_system->set_speed_percent(current_system, speeds[current_speed]);
-			break;
-		case UI_PREV_SPEED:
-			current_speed--;
-			if (current_speed < 0) {
-				current_speed = num_speeds - 1;
-			}
-			printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
-			current_system->set_speed_percent(current_system, speeds[current_speed]);
-			break;
-		case UI_SET_SPEED:
-			if (binding->value < num_speeds) {
-				current_speed = binding->value;
-				printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
-				current_system->set_speed_percent(current_system, speeds[current_speed]);
-			} else {
-				printf("Setting speed to %d\n", speeds[current_speed]);
-				current_system->set_speed_percent(current_system, speeds[current_speed]);
-			}
-			break;
-		case UI_RELEASE_MOUSE:
-			if (current_io->mouse_captured) {
-				current_io->mouse_captured = 0;
-				render_relative_mouse(0);
-			}
-			break;
-		case UI_TOGGLE_KEYBOARD_CAPTURE:
-			if (keyboard_connected(current_io)) {
-				current_io->keyboard_captured = !current_io->keyboard_captured;
-			}
-			break;
-		case UI_TOGGLE_FULLSCREEN:
-			render_toggle_fullscreen();
-			break;
-		case UI_SOFT_RESET:
-			current_system->soft_reset(current_system);
-			break;
-		case UI_RELOAD:
-			reload_media();
-			break;
-		case UI_SMS_PAUSE:
-			if (current_system->type == SYSTEM_SMS) {
-				sms_context *sms = (sms_context *)current_system;
-				vdp_pbc_pause(sms->vdp);
-			}
-			break;
-		case UI_SCREENSHOT: {
-			char *screenshot_base = tern_find_path(config, "ui\0screenshot_path\0", TVAL_PTR).ptrval;
-			if (!screenshot_base) {
-				screenshot_base = "$HOME";
-			}
-			tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
-			vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
-			screenshot_base = replace_vars(screenshot_base, vars, 1);
-			tern_free(vars);
-			time_t now = time(NULL);
-			struct tm local_store;
-			char fname_part[256];
-			char *template = tern_find_path(config, "ui\0screenshot_template\0", TVAL_PTR).ptrval;
-			if (!template) {
-				template = "blastem_%c.ppm";
-			}
-			strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
-			char const *parts[] = {screenshot_base, PATH_SEP, fname_part};
-			char *path = alloc_concat_m(3, parts);
-			free(screenshot_base);
-			render_save_screenshot(path);
-			break;
-		}
-		case UI_EXIT:
-#ifndef DISABLE_NUKLEAR
-			if (is_nuklear_active()) {
-				show_pause_menu();
-			} else {
-#endif
-			current_system->request_exit(current_system);
-			if (current_system->type == SYSTEM_GENESIS) {
-				genesis_context *gen = (genesis_context *)current_system;
-				if (gen->extra) {
-					//TODO: More robust mechanism for detecting menu
-					menu_context *menu = gen->extra;
-					menu->external_game_load = 1;
-				}
-			}
-#ifndef DISABLE_NUKLEAR
-			}
-#endif
-			break;
-		}
-		break;
-	}
-}
-
-void handle_keyup(int keycode, uint8_t scancode)
-{
-	if (!current_io) {
-		return;
-	}
-	int bucket = keycode >> 15 & 0xFFFF;
-	int idx = keycode & 0x7FFF;
-	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
-	if (binding && (!current_io->keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
-		handle_binding_up(binding);
-	} else if (current_io->keyboard_captured) {
-		store_key_event(0xF000 | scancode);
-	}
-}
-
-void handle_joyup(int joystick, int button)
-{
-	if (joystick >= MAX_JOYSTICKS  || button >= joysticks[joystick].num_buttons) {
-		return;
-	}
-	keybinding * binding = joysticks[joystick].buttons + button;
-	handle_binding_up(binding);
-}
-
-void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
-{
-	if (joystick >= MAX_JOYSTICKS  || dpadnum >= joysticks[joystick].num_dpads) {
-		return;
-	}
-	joydpad * dpad = joysticks[joystick].dpads + dpadnum;
-	uint8_t newdown = (value ^ dpad->state) & value;
-	uint8_t newup = ((~value) ^ (~dpad->state)) & (~value);
-	dpad->state = value;
-	for (int i = 0; i < 4; i++) {
-		if (newdown & dpadbits[i]) {
-			handle_binding_down(dpad->bindings + i);
-		} else if(newup & dpadbits[i]) {
-			handle_binding_up(dpad->bindings + i);
-		}
-	}
+	store_key_event(find_keyboard(io), scancode);
 }
 
-#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) {
-		return;
-	}
-	keybinding * binding = mice[mouse].buttons + button - 1;
-	handle_binding_up(binding);
-}
-
-void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay)
+void io_keyboard_up(sega_io *io, uint8_t scancode)
 {
-	if (mouse >= MAX_MICE || !mice[mouse].motion_port) {
-		return;
-	}
-	switch(current_io->mouse_mode)
-	{
-	case MOUSE_NONE:
-		break;
-	case MOUSE_ABSOLUTE: {
-		float scale_x = (render_emulated_width() * 2.0f) / ((float)render_width());
-		float scale_y = (render_emulated_height() * 2.0f) / ((float)render_height());
-		int32_t adj_x = x * scale_x + 2 * render_overscan_left() - 2 * BORDER_LEFT;
-		int32_t adj_y = y * scale_y + 2 * render_overscan_top() - 4;
-		if (adj_x >= 0 && adj_y >= 0) {
-			mice[mouse].motion_port->device.mouse.cur_x = adj_x;
-			mice[mouse].motion_port->device.mouse.cur_y = adj_y;
-		}
-		break;
-	}
-	case MOUSE_RELATIVE: {
-		mice[mouse].motion_port->device.mouse.cur_x += deltax;
-		mice[mouse].motion_port->device.mouse.cur_y += deltay;
-		break;
-	}
-	case MOUSE_CAPTURE: {
-		if (current_io->mouse_captured) {
-			mice[mouse].motion_port->device.mouse.cur_x += deltax;
-			mice[mouse].motion_port->device.mouse.cur_y += deltay;
-		}
-		break;
-	}
-	}
-}
-
-void io_release_capture(sega_io *io)
-{
-	if (io->mouse_mode == MOUSE_RELATIVE || (io->mouse_mode == MOUSE_CAPTURE && io->mouse_captured)) {
-		render_relative_mouse(0);
-	}
-	current_io->keyboard_captured = 0;
-}
-
-void io_reacquire_capture(sega_io *io)
-{
-	if (io->mouse_mode == MOUSE_RELATIVE || (io->mouse_mode == MOUSE_CAPTURE && io->mouse_captured)) {
-		render_relative_mouse(1);
-	}
+	store_key_event(find_keyboard(io), 0xF000 | scancode);
 }
 
-int parse_binding_target(char * target, tern_node * padbuttons, tern_node *mousebuttons, int * ui_out, int * padnum_out, int * padbutton_out)
-{
-	const int gpadslen = strlen("gamepads.");
-	const int mouselen = strlen("mouse.");
-	if (!strncmp(target, "gamepads.", gpadslen)) {
-		if (target[gpadslen] >= '1' && target[gpadslen] <= '8') {
-			int padnum = target[gpadslen] - '0';
-			int button = tern_find_int(padbuttons, target + gpadslen + 1, 0);
-			if (button) {
-				*padnum_out = padnum;
-				*padbutton_out = button;
-				return BIND_GAMEPAD1;
-			} else {
-				if (target[gpadslen+1]) {
-					warning("Gamepad mapping string '%s' refers to an invalid button '%s'\n", target, target + gpadslen + 1);
-				} else {
-					warning("Gamepad mapping string '%s' has no button component\n", target);
-				}
-			}
-		} else {
-			warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]);
-		}
-	} else if(!strncmp(target, "mouse.", mouselen)) {
-		if (target[mouselen] >= '1' && target[mouselen] <= '8') {
-			int mousenum = target[mouselen] - '0';
-			int button = tern_find_int(mousebuttons, target + mouselen + 1, 0);
-			if (button) {
-				*padnum_out = mousenum;
-				*padbutton_out = button;
-				return BIND_MOUSE1;
-			} else {
-				if (target[mouselen+1]) {
-					warning("Mouse mapping string '%s' refers to an invalid button '%s'\n", target, target + mouselen + 1);
-				} else {
-					warning("Mouse mapping string '%s' has no button component\n", target);
-				}
-			}
-		} else {
-			warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]);
-		}
-	} else if(!strncmp(target, "ui.", strlen("ui."))) {
-		*padbutton_out = 0;
-		if (!strcmp(target + 3, "vdp_debug_mode")) {
-			*ui_out = UI_DEBUG_MODE_INC;
-		} else if(!strcmp(target + 3, "vdp_debug_pal")) {
-			*ui_out = UI_DEBUG_PAL_INC;
-		} else if(!strcmp(target + 3, "enter_debugger")) {
-			*ui_out = UI_ENTER_DEBUGGER;
-		} else if(!strcmp(target + 3, "save_state")) {
-			*ui_out = UI_SAVE_STATE;
-		} else if(!strncmp(target + 3, "set_speed.", strlen("set_speed."))) {
-			*ui_out = UI_SET_SPEED;
-			*padbutton_out = atoi(target + 3 + strlen("set_speed."));
-		} else if(!strcmp(target + 3, "next_speed")) {
-			*ui_out = UI_NEXT_SPEED;
-		} else if(!strcmp(target + 3, "prev_speed")) {
-			*ui_out = UI_PREV_SPEED;
-		} else if(!strcmp(target + 3, "release_mouse")) {
-			*ui_out = UI_RELEASE_MOUSE;
-		} else if(!strcmp(target + 3, "toggle_keyboard_captured")) {
-			*ui_out = UI_TOGGLE_KEYBOARD_CAPTURE;
-		} else if (!strcmp(target + 3, "toggle_fullscreen")) {
-			*ui_out = UI_TOGGLE_FULLSCREEN;
-		} else if (!strcmp(target + 3, "soft_reset")) {
-			*ui_out = UI_SOFT_RESET;
-		} else if (!strcmp(target + 3, "reload")) {
-			*ui_out = UI_RELOAD;
-		} else if (!strcmp(target + 3, "sms_pause")) {
-			*ui_out = UI_SMS_PAUSE;
-		} else if (!strcmp(target + 3, "screenshot")) {
-			*ui_out = UI_SCREENSHOT;
-		} else if(!strcmp(target + 3, "exit")) {
-			*ui_out = UI_EXIT;
-		} else {
-			warning("Unreconized UI binding type %s\n", target);
-			return 0;
-		}
-		return BIND_UI;
-	} else {
-		warning("Unrecognized binding type %s\n", target);
-	}
-	return 0;
-}
-
-void process_keys(tern_node * cur, tern_node * special, tern_node * padbuttons, tern_node *mousebuttons, char * prefix)
+uint8_t io_has_keyboard(sega_io *io)
 {
-	char * curstr = NULL;
-	int len;
-	if (!cur) {
-		return;
-	}
-	char onec[2];
-	if (prefix) {
-		len = strlen(prefix);
-		curstr = malloc(len + 2);
-		memcpy(curstr, prefix, len);
-	} else {
-		curstr = onec;
-		len = 0;
-	}
-	curstr[len] = cur->el;
-	curstr[len+1] = 0;
-	if (cur->el) {
-		process_keys(cur->straight.next, special, padbuttons, mousebuttons, curstr);
-	} else {
-		int keycode = tern_find_int(special, curstr, 0);
-		if (!keycode) {
-			keycode = curstr[0];
-			if (curstr[1] != 0) {
-				warning("%s is not recognized as a key identifier, truncating to %c\n", curstr, curstr[0]);
-			}
-		}
-		char * target = cur->straight.value.ptrval;
-		int ui_func, padnum, button;
-		int bindtype = parse_binding_target(target, padbuttons, mousebuttons, &ui_func, &padnum, &button);
-		if (bindtype == BIND_GAMEPAD1) {
-			bind_gamepad(keycode, padnum, button);
-		} else if(bindtype == BIND_UI) {
-			bind_ui(keycode, ui_func, button);
-		}
-	}
-	process_keys(cur->left, special, padbuttons, mousebuttons, prefix);
-	process_keys(cur->right, special, padbuttons, mousebuttons, prefix);
-	if (curstr && len) {
-		free(curstr);
-	}
-}
-
-void process_speeds(tern_node * cur, char * prefix)
-{
-	char * curstr = NULL;
-	int len;
-	if (!cur) {
-		return;
-	}
-	char onec[2];
-	if (prefix) {
-		len = strlen(prefix);
-		curstr = malloc(len + 2);
-		memcpy(curstr, prefix, len);
-	} else {
-		curstr = onec;
-		len = 0;
-	}
-	curstr[len] = cur->el;
-	curstr[len+1] = 0;
-	if (cur->el) {
-		process_speeds(cur->straight.next, curstr);
-	} else {
-		char *end;
-		long speed_index = strtol(curstr, &end, 10);
-		if (speed_index < 0 || end == curstr || *end) {
-			warning("%s is not a valid speed index", curstr);
-		} else {
-			if (speed_index >= num_speeds) {
-				speeds = realloc(speeds, sizeof(uint32_t) * (speed_index+1));
-				for(; num_speeds < speed_index + 1; num_speeds++) {
-					speeds[num_speeds] = 0;
-				}
-			}
-			speeds[speed_index] = atoi(cur->straight.value.ptrval);
-			if (speeds[speed_index] < 1) {
-				warning("%s is not a valid speed percentage, setting speed %d to 100", cur->straight.value.ptrval, speed_index);
-				speeds[speed_index] = 100;
-			}
-		}
-	}
-	process_speeds(cur->left, prefix);
-	process_speeds(cur->right, prefix);
-	if (curstr && len) {
-		free(curstr);
-	}
+	return find_keyboard(io) != NULL;
 }
 
 void process_device(char * device_type, io_port * port)
@@ -888,10 +225,10 @@
 		} else {
 			port->device_type = IO_GAMEPAD6;
 		}
-		port->device.pad.gamepad_num = device_type[gamepad_len+2] - '1';
+		port->device.pad.gamepad_num = device_type[gamepad_len+2] - '0';
 	} else if(!strncmp(device_type, "mouse", mouse_len)) {
 		port->device_type = IO_MOUSE;
-		port->device.mouse.mouse_num = device_type[mouse_len+1] - '1';
+		port->device.mouse.mouse_num = device_type[mouse_len+1] - '0';
 		port->device.mouse.last_read_x = 0;
 		port->device.mouse.last_read_y = 0;
 		port->device.mouse.cur_x = 0;
@@ -942,8 +279,7 @@
 
 void setup_io_devices(tern_node * config, rom_info *rom, sega_io *io)
 {
-	current_io = io;
-	io_port * ports = current_io->ports;
+	io_port * ports = io->ports;
 	tern_node *io_nodes = tern_find_path(config, "io\0devices\0", TVAL_NODE).ptrval;
 	char * io_1 = rom->port1_override ? rom->port1_override : io_nodes ? tern_find_ptr(io_nodes, "1") : NULL;
 	char * io_2 = rom->port2_override ? rom->port2_override : io_nodes ? tern_find_ptr(io_nodes, "2") : NULL;
@@ -953,20 +289,21 @@
 	process_device(io_2, ports+1);
 	process_device(io_ext, ports+2);
 
+	uint8_t mouse_mode;
 	if (ports[0].device_type == IO_MOUSE || ports[1].device_type == IO_MOUSE || ports[2].device_type == IO_MOUSE) {
 		if (render_fullscreen()) {
-				current_io->mouse_mode = MOUSE_RELATIVE;
-				render_relative_mouse(1);
+				mouse_mode = MOUSE_RELATIVE;
 		} else {
 			if (rom->mouse_mode && !strcmp(rom->mouse_mode, "absolute")) {
-				current_io->mouse_mode = MOUSE_ABSOLUTE;
+				mouse_mode = MOUSE_ABSOLUTE;
 			} else {
-				current_io->mouse_mode = MOUSE_CAPTURE;
+				mouse_mode = MOUSE_CAPTURE;
 			}
 		}
 	} else {
-		current_io->mouse_mode = MOUSE_NONE;
+		mouse_mode = MOUSE_NONE;
 	}
+	bindings_set_mouse_mode(mouse_mode);
 
 	for (int i = 0; i < 3; i++)
 	{
@@ -1042,512 +379,6 @@
 	}
 }
 
-void map_bindings(io_port *ports, keybinding *bindings, int numbindings)
-{
-	for (int i = 0; i < numbindings; i++)
-	{
-		if (bindings[i].bind_type >= BIND_GAMEPAD1 && bindings[i].bind_type <= BIND_GAMEPAD8)
-		{
-			int num = bindings[i].bind_type - BIND_GAMEPAD1;
-			for (int j = 0; j < 3; j++)
-			{
-				if ((ports[j].device_type == IO_GAMEPAD3
-					 || ports[j].device_type == IO_GAMEPAD6
-					 || ports[j].device_type == IO_GAMEPAD2)
-					 && ports[j].device.pad.gamepad_num == num
-				)
-				{
-					memset(ports[j].input, 0, sizeof(ports[j].input));
-					bindings[i].port = ports + j;
-					break;
-				}
-			}
-		}
-		else if (bindings[i].bind_type >= BIND_MOUSE1 && bindings[i].bind_type <= BIND_MOUSE8)
-		{
-			int num = bindings[i].bind_type - BIND_MOUSE1;
-			for (int j = 0; j < 3; j++)
-			{
-				if (ports[j].device_type == IO_MOUSE && ports[j].device.mouse.mouse_num == num)
-				{
-					memset(ports[j].input, 0, sizeof(ports[j].input));
-					bindings[i].port = ports + j;
-					break;
-				}
-			}
-		}
-	}
-}
-
-typedef struct {
-	tern_node *padbuttons;
-	tern_node *mousebuttons;
-	int       mouseidx;
-} pmb_state;
-
-void process_mouse_button(char *buttonstr, tern_val value, uint8_t valtype, void *data)
-{
-	pmb_state *state = data;
-	int buttonnum = atoi(buttonstr);
-	if (buttonnum < 1 || buttonnum > MAX_MOUSE_BUTTONS) {
-		warning("Mouse button %s is out of the supported range of 1-8\n", buttonstr);
-		return;
-	}
-	if (valtype != TVAL_PTR) {
-		warning("Mouse button %s is not a scalar value!\n", buttonstr);
-		return;
-	}
-	buttonnum--;
-	int ui_func, devicenum, button;
-	int bindtype = parse_binding_target(value.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &devicenum, &button);
-	switch (bindtype)
-	{
-	case BIND_UI:
-		mice[state->mouseidx].buttons[buttonnum].subtype_a = ui_func;
-		break;
-	case BIND_GAMEPAD1:
-		mice[state->mouseidx].buttons[buttonnum].subtype_a = button >> 12;
-		mice[state->mouseidx].buttons[buttonnum].subtype_b = button >> 8 & 0xF;
-		mice[state->mouseidx].buttons[buttonnum].value = button & 0xFF;
-		break;
-	case BIND_MOUSE1:
-		mice[state->mouseidx].buttons[buttonnum].value = button & 0xFF;
-		break;
-	}
-	if (bindtype != BIND_UI) {
-		bindtype += devicenum-1;
-	}
-	mice[state->mouseidx].buttons[buttonnum].bind_type = bindtype;
-
-}
-
-void process_mouse(char *mousenum, tern_val value, uint8_t valtype, void *data)
-{
-	tern_node **buttonmaps = data;
-	if (valtype != TVAL_NODE) {
-		warning("Binding for mouse %s is a scalar!\n", mousenum);
-		return;
-	}
-	tern_node *mousedef = value.ptrval;
-	tern_node *padbuttons = buttonmaps[0];
-	tern_node *mousebuttons = buttonmaps[1];
-
-	int mouseidx = atoi(mousenum);
-	if (mouseidx < 0 || mouseidx >= MAX_MICE) {
-		warning("Mouse numbers must be between 0 and %d, but %d is not\n", MAX_MICE, mouseidx);
-		return;
-	}
-	char *motion = tern_find_ptr(mousedef, "motion");
-	if (motion) {
-		int ui_func,devicenum,button;
-		int bindtype = parse_binding_target(motion, padbuttons, mousebuttons, &ui_func, &devicenum, &button);
-		if (bindtype != BIND_UI) {
-			bindtype += devicenum-1;
-		}
-		if (button == PSEUDO_BUTTON_MOTION) {
-			mice[mouseidx].bind_type = bindtype;
-		} else {
-			warning("Mouse motion can't be bound to target %s\n", motion);
-		}
-	}
-	tern_node *buttons = tern_find_path(mousedef, "buttons\0\0", TVAL_NODE).ptrval;
-	if (buttons) {
-		pmb_state state = {padbuttons, mousebuttons, mouseidx};
-		tern_foreach(buttons, process_mouse_button, &state);
-	}
-}
-
-typedef struct {
-	int       padnum;
-	tern_node *padbuttons;
-	tern_node *mousebuttons;
-} pad_button_state;
-
-
-static long map_warning_pad = -1;
-void process_pad_button(char *key, tern_val val, uint8_t valtype, void *data)
-{
-	pad_button_state *state = data;
-	int hostpadnum = state->padnum;
-	int ui_func, padnum, button;
-	if (valtype != TVAL_PTR) {
-		warning("Pad button %s has a non-scalar value\n", key);
-		return;
-	}
-	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, 0);
-		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 && hostpadnum != map_warning_pad) {
-				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
-				map_warning_pad = 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);
-			}
-			return;
-		} else if (hostbutton & RENDER_AXIS_BIT) {
-			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;
-		}
-	}
-	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 process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data)
-{
-	key = strdup(key);
-	pad_button_state *state = data;
-	int hostpadnum = state->padnum;
-	int ui_func, padnum, button;
-	if (valtype != TVAL_PTR) {
-		warning("Mapping for axis %s has a non-scalar value", key);
-		return;
-	}
-	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 && hostpadnum != map_warning_pad) {
-				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
-				map_warning_pad = 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;
-	if (!padbuttons) {
-		padbuttons = tern_insert_int(NULL, ".up", DPAD_UP);
-		padbuttons = tern_insert_int(padbuttons, ".down", DPAD_DOWN);
-		padbuttons = tern_insert_int(padbuttons, ".left", DPAD_LEFT);
-		padbuttons = tern_insert_int(padbuttons, ".right", DPAD_RIGHT);
-		padbuttons = tern_insert_int(padbuttons, ".a", BUTTON_A);
-		padbuttons = tern_insert_int(padbuttons, ".b", BUTTON_B);
-		padbuttons = tern_insert_int(padbuttons, ".c", BUTTON_C);
-		padbuttons = tern_insert_int(padbuttons, ".x", BUTTON_X);
-		padbuttons = tern_insert_int(padbuttons, ".y", BUTTON_Y);
-		padbuttons = tern_insert_int(padbuttons, ".z", BUTTON_Z);
-		padbuttons = tern_insert_int(padbuttons, ".start", BUTTON_START);
-		padbuttons = tern_insert_int(padbuttons, ".mode", BUTTON_MODE);
-	}
-	return padbuttons;
-}
-
-static tern_node *get_mouse_buttons()
-{
-	static tern_node *mousebuttons;
-	if (!mousebuttons) {
-		mousebuttons = tern_insert_int(NULL, ".left", MOUSE_LEFT);
-		mousebuttons = tern_insert_int(mousebuttons, ".middle", MOUSE_MIDDLE);
-		mousebuttons = tern_insert_int(mousebuttons, ".right", MOUSE_RIGHT);
-		mousebuttons = tern_insert_int(mousebuttons, ".start", MOUSE_START);
-		mousebuttons = tern_insert_int(mousebuttons, ".motion", PSEUDO_BUTTON_MOTION);
-	}
-	return mousebuttons;
-}
-
-void handle_joy_added(int joystick)
-{
-	if (joystick > MAX_JOYSTICKS) {
-		return;
-	}
-	tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
-	if (pads) {
-		char numstr[11];
-		sprintf(numstr, "%d", joystick);
-		tern_node * pad = tern_find_node(pads, numstr);
-		if (pad) {
-			tern_node * dpad_node = tern_find_node(pad, "dpads");
-			if (dpad_node) {
-				for (int dpad = 0; dpad < 10; dpad++)
-				{
-					numstr[0] = dpad + '0';
-					numstr[1] = 0;
-					tern_node * pad_dpad = tern_find_node(dpad_node, numstr);
-					char * dirs[] = {"up", "down", "left", "right"};
-					int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
-					for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) {
-						char * target = tern_find_ptr(pad_dpad, dirs[dir]);
-						if (target) {
-							int ui_func, padnum, button;
-							int bindtype = parse_binding_target(target, get_pad_buttons(), get_mouse_buttons(), &ui_func, &padnum, &button);
-							if (bindtype == BIND_GAMEPAD1) {
-								bind_dpad_gamepad(joystick, dpad, dirnums[dir], padnum, button);
-							} else if (bindtype == BIND_UI) {
-								bind_dpad_ui(joystick, dpad, dirnums[dir], ui_func, button);
-							}
-						}
-					}
-				}
-			}
-			tern_node *button_node = tern_find_node(pad, "buttons");
-			if (button_node) {
-				pad_button_state state = {
-					.padnum = joystick,
-					.padbuttons = get_pad_buttons(),
-					.mousebuttons = get_mouse_buttons()
-				};
-				tern_foreach(button_node, process_pad_button, &state);
-			}
-			tern_node *axes_node = tern_find_node(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++)
-					{
-						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);
-					}
-				}
-			}
-		}
-	}
-	
-}
-
-void set_keybindings(sega_io *io)
-{
-	static uint8_t already_done;
-	if (already_done) {
-		map_all_bindings(io);
-		return;
-	}
-	already_done = 1;
-	io_port *ports = io->ports;
-	tern_node * special = tern_insert_int(NULL, "up", RENDERKEY_UP);
-	special = tern_insert_int(special, "down", RENDERKEY_DOWN);
-	special = tern_insert_int(special, "left", RENDERKEY_LEFT);
-	special = tern_insert_int(special, "right", RENDERKEY_RIGHT);
-	special = tern_insert_int(special, "enter", '\r');
-	special = tern_insert_int(special, "space", ' ');
-	special = tern_insert_int(special, "tab", '\t');
-	special = tern_insert_int(special, "backspace", '\b');
-	special = tern_insert_int(special, "esc", RENDERKEY_ESC);
-	special = tern_insert_int(special, "delete", RENDERKEY_DEL);
-	special = tern_insert_int(special, "lshift", RENDERKEY_LSHIFT);
-	special = tern_insert_int(special, "rshift", RENDERKEY_RSHIFT);
-	special = tern_insert_int(special, "lctrl", RENDERKEY_LCTRL);
-	special = tern_insert_int(special, "rctrl", RENDERKEY_RCTRL);
-	special = tern_insert_int(special, "lalt", RENDERKEY_LALT);
-	special = tern_insert_int(special, "ralt", RENDERKEY_RALT);
-	special = tern_insert_int(special, "home", RENDERKEY_HOME);
-	special = tern_insert_int(special, "end", RENDERKEY_END);
-	special = tern_insert_int(special, "pageup", RENDERKEY_PAGEUP);
-	special = tern_insert_int(special, "pagedown", RENDERKEY_PAGEDOWN);
-	special = tern_insert_int(special, "f1", RENDERKEY_F1);
-	special = tern_insert_int(special, "f2", RENDERKEY_F2);
-	special = tern_insert_int(special, "f3", RENDERKEY_F3);
-	special = tern_insert_int(special, "f4", RENDERKEY_F4);
-	special = tern_insert_int(special, "f5", RENDERKEY_F5);
-	special = tern_insert_int(special, "f6", RENDERKEY_F6);
-	special = tern_insert_int(special, "f7", RENDERKEY_F7);
-	special = tern_insert_int(special, "f8", RENDERKEY_F8);
-	special = tern_insert_int(special, "f9", RENDERKEY_F9);
-	special = tern_insert_int(special, "f10", RENDERKEY_F10);
-	special = tern_insert_int(special, "f11", RENDERKEY_F11);
-	special = tern_insert_int(special, "f12", RENDERKEY_F12);
-	special = tern_insert_int(special, "select", RENDERKEY_SELECT);
-	special = tern_insert_int(special, "play", RENDERKEY_PLAY);
-	special = tern_insert_int(special, "search", RENDERKEY_SEARCH);
-	special = tern_insert_int(special, "back", RENDERKEY_BACK);
-	special = tern_insert_int(special, "np0", RENDERKEY_NP0);
-	special = tern_insert_int(special, "np1", RENDERKEY_NP1);
-	special = tern_insert_int(special, "np2", RENDERKEY_NP2);
-	special = tern_insert_int(special, "np3", RENDERKEY_NP3);
-	special = tern_insert_int(special, "np4", RENDERKEY_NP4);
-	special = tern_insert_int(special, "np5", RENDERKEY_NP5);
-	special = tern_insert_int(special, "np6", RENDERKEY_NP6);
-	special = tern_insert_int(special, "np7", RENDERKEY_NP7);
-	special = tern_insert_int(special, "np8", RENDERKEY_NP8);
-	special = tern_insert_int(special, "np9", RENDERKEY_NP9);
-	special = tern_insert_int(special, "np/", RENDERKEY_NP_DIV);
-	special = tern_insert_int(special, "np*", RENDERKEY_NP_MUL);
-	special = tern_insert_int(special, "np-", RENDERKEY_NP_MIN);
-	special = tern_insert_int(special, "np+", RENDERKEY_NP_PLUS);
-	special = tern_insert_int(special, "npenter", RENDERKEY_NP_ENTER);
-	special = tern_insert_int(special, "np.", RENDERKEY_NP_STOP);
-
-	tern_node *padbuttons = get_pad_buttons();
-
-	tern_node *mousebuttons = get_mouse_buttons();
-	
-	tern_node * keys = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
-	process_keys(keys, special, padbuttons, mousebuttons, NULL);
-	char numstr[] = "00";
-	tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
-	if (pads) {
-		for (int i = 0; i < MAX_JOYSTICKS; i++)
-		{
-
-			if (i < 10) {
-				numstr[0] = i + '0';
-				numstr[1] = 0;
-			} else {
-				numstr[0] = i/10 + '0';
-				numstr[1] = i%10 + '0';
-			}
-			
-		}
-	}
-	memset(mice, 0, sizeof(mice));
-	tern_node * mice = tern_find_path(config, "bindings\0mice\0", TVAL_NODE).ptrval;
-	if (mice) {
-		tern_node *buttonmaps[2] = {padbuttons, mousebuttons};
-		tern_foreach(mice, process_mouse, buttonmaps);
-	}
-	tern_node * speed_nodes = tern_find_path(config, "clocks\0speeds\0", TVAL_NODE).ptrval;
-	speeds = malloc(sizeof(uint32_t));
-	speeds[0] = 100;
-	process_speeds(speed_nodes, NULL);
-	for (int i = 0; i < num_speeds; i++)
-	{
-		if (!speeds[i]) {
-			warning("Speed index %d was not set to a valid percentage!", i);
-			speeds[i] = 100;
-		}
-	}
-	map_all_bindings(io);
-}
-
-void map_all_bindings(sega_io *io)
-{
-	current_io = io;
-	io_port *ports = io->ports;
-	
-	for (int bucket = 0; bucket < 0x10000; bucket++)
-	{
-		if (bindings[bucket])
-		{
-			map_bindings(ports, bindings[bucket], 0x8000);
-		}
-	}
-	for (int stick = 0; stick < MAX_JOYSTICKS; stick++)
-	{
-		if (joysticks[stick].buttons) {
-			map_bindings(ports, joysticks[stick].buttons, joysticks[stick].num_buttons);
-		}
-		if (joysticks[stick].dpads)
-		{
-			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++)
-	{
-		if (mice[mouse].bind_type >= BIND_MOUSE1 && mice[mouse].bind_type <= BIND_MOUSE8) {
-			int num = mice[mouse].bind_type - BIND_MOUSE1;
-			for (int j = 0; j < 3; j++)
-			{
-				if (ports[j].device_type == IO_MOUSE && ports[j].device.mouse.mouse_num == num)
-				{
-					memset(ports[j].input, 0, sizeof(ports[j].input));
-					mice[mouse].motion_port = ports + j;
-					break;
-				}
-			}
-		}
-		map_bindings(ports, mice[mouse].buttons, MAX_MOUSE_BUTTONS);
-	}
-	keyboard_port = NULL;
-	for (int i = 0; i < 3; i++)
-	{
-		if (ports[i].device_type == IO_SATURN_KEYBOARD || ports[i].device_type == IO_XBAND_KEYBOARD) {
-			keyboard_port = ports + i;
-			break;
-		}
-	}
-	//not really related to the intention of this function, but the best place to do this currently
-	if (speeds[0] != 100) {
-		current_system->set_speed_percent(current_system, speeds[0]);
-	}
-}
 
 #define TH 0x40
 #define TR 0x20
@@ -1561,6 +392,7 @@
 		if (port->device.mouse.tr_counter == 3) {
 			port->device.mouse.latched_x = port->device.mouse.cur_x;
 			port->device.mouse.latched_y = port->device.mouse.cur_y;
+			/* FIXME mouse mode owned by bindings now
 			if (current_io->mouse_mode == MOUSE_ABSOLUTE) {
 				//avoid overflow in absolute mode
 				int deltax = port->device.mouse.latched_x - port->device.mouse.last_read_x;
@@ -1571,7 +403,7 @@
 				if (abs(deltay) > 255) {
 					port->device.mouse.latched_y = port->device.mouse.last_read_y + (deltay > 0 ? 255 : -255);
 				}
-			}
+			}*/
 		}
 	}
 }
--- a/io.h	Wed May 02 09:41:05 2018 -0700
+++ b/io.h	Sat May 05 23:30:40 2018 -0700
@@ -70,48 +70,57 @@
 
 typedef struct {
 	io_port	ports[3];
-	uint8_t mouse_mode;
-	uint8_t mouse_captured;
-	uint8_t keyboard_captured;
 } sega_io;
 
-#define GAMEPAD_TH0 0
-#define GAMEPAD_TH1 1
-#define GAMEPAD_EXTRA 2
-#define GAMEPAD_NONE 0xF
-
-#define IO_TH0 0
-#define IO_TH1 1
-#define IO_STATE 2
+//pseudo gamepad for buttons on main console unit
+#define GAMEPAD_MAIN_UNIT 255
 
 enum {
-	IO_WRITE_PENDING,
-	IO_WRITTEN,
-	IO_READ_PENDING,
-	IO_READ
+	BUTTON_INVALID,
+	DPAD_UP,
+	DPAD_DOWN,
+	DPAD_LEFT,
+	DPAD_RIGHT,
+	BUTTON_A,
+	BUTTON_B,
+	BUTTON_C,
+	BUTTON_START,
+	BUTTON_X,
+	BUTTON_Y,
+	BUTTON_Z,
+	BUTTON_MODE,
+	NUM_GAMEPAD_BUTTONS
 };
 
-void set_keybindings(sega_io *io);
-void map_all_bindings(sega_io *io);
+enum {
+	MAIN_UNIT_PAUSE
+};
+
+enum {
+	MOUSE_LEFT = 1,
+	MOUSE_RIGHT = 2,
+	MOUSE_MIDDLE = 4,
+	MOUSE_START = 8,
+	PSEUDO_BUTTON_MOTION=0xFF
+};
+
 void setup_io_devices(tern_node * config, rom_info *rom, sega_io *io);
 void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction);
 void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle);
 void io_data_write(io_port * pad, uint8_t value, uint32_t current_cycle);
 uint8_t io_data_read(io_port * pad, uint32_t current_cycle);
-void handle_keydown(int keycode, uint8_t scancode);
-void handle_keyup(int keycode, uint8_t scancode);
-void handle_joydown(int joystick, int button);
-void handle_joyup(int joystick, int button);
-void handle_joy_dpad(int joystick, int dpad, uint8_t state);
-void handle_joy_axis(int joystick, int axis, int16_t value);
-void handle_joy_added(int joystick);
-void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay);
-void handle_mousedown(int mouse, int button);
-void handle_mouseup(int mouse, int button);
 void io_serialize(io_port *port, serialize_buffer *buf);
 void io_deserialize(deserialize_buffer *buf, void *vport);
-void io_release_capture(sega_io *io);
-void io_reacquire_capture(sega_io *io);
+
+void io_gamepad_down(sega_io *io, uint8_t gamepad_num, uint8_t button);
+void io_gamepad_up(sega_io *io, uint8_t gamepad_num, uint8_t button);
+void io_mouse_down(sega_io *io, uint8_t mouse_num, uint8_t button);
+void io_mouse_up(sega_io *io, uint8_t mouse_num, uint8_t button);
+void io_mouse_motion_absolute(sega_io *io, uint8_t mouse_num, uint16_t x, uint16_t y);
+void io_mouse_motion_relative(sega_io *io, uint8_t mouse_num, int32_t x, int32_t y);
+void io_keyboard_down(sega_io *io, uint8_t scancode);
+void io_keyboard_up(sega_io *io, uint8_t scancode);
+uint8_t io_has_keyboard(sega_io *io);
 
 extern const char * device_type_names[];
 
--- a/render_sdl.c	Wed May 02 09:41:05 2018 -0700
+++ b/render_sdl.c	Sat May 05 23:30:40 2018 -0700
@@ -11,7 +11,7 @@
 #include "render_sdl.h"
 #include "blastem.h"
 #include "genesis.h"
-#include "io.h"
+#include "bindings.h"
 #include "util.h"
 #include "ppm.h"
 #include "png.h"
--- a/sms.c	Wed May 02 09:41:05 2018 -0700
+++ b/sms.c	Sat May 05 23:30:40 2018 -0700
@@ -7,6 +7,7 @@
 #include "util.h"
 #include "debug.h"
 #include "saves.h"
+#include "bindings.h"
 
 static void *memory_io_write(uint32_t location, void *vcontext, uint8_t value)
 {
@@ -385,6 +386,7 @@
 			target_cycle -= adjust;
 		}
 	}
+	bindings_release_capture();
 	vdp_release_framebuffer(sms->vdp);
 	render_pause_source(sms->psg->audio);
 	sms->should_return = 0;
@@ -393,6 +395,7 @@
 static void resume_sms(system_header *system)
 {
 	sms_context *sms = (sms_context *)system;
+	bindings_reacquire_capture();
 	vdp_reacquire_framebuffer(sms->vdp);
 	render_resume_source(sms->psg->audio);
 	run_sms(system);
@@ -401,7 +404,6 @@
 static void start_sms(system_header *system, char *statefile)
 {
 	sms_context *sms = (sms_context *)system;
-	set_keybindings(&sms->io);
 	
 	z80_assert_reset(sms->z80, 0);
 	z80_clear_reset(sms->z80, 128*15);
@@ -474,6 +476,60 @@
 	//TODO: Implement me
 }
 
+static void gamepad_down(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	sms_context *sms = (sms_context *)system;
+	if (gamepad_num == GAMEPAD_MAIN_UNIT) {
+		if (button == MAIN_UNIT_PAUSE) {
+			vdp_pbc_pause(sms->vdp);
+		}
+	} else {
+		io_gamepad_down(&sms->io, gamepad_num, button);
+	}
+}
+
+static void gamepad_up(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	sms_context *sms = (sms_context *)system;
+	io_gamepad_up(&sms->io, gamepad_num, button);
+}
+
+static void mouse_down(system_header *system, uint8_t mouse_num, uint8_t button)
+{
+	sms_context *sms = (sms_context *)system;
+	io_mouse_down(&sms->io, mouse_num, button);
+}
+
+static void mouse_up(system_header *system, uint8_t mouse_num, uint8_t button)
+{
+	sms_context *sms = (sms_context *)system;
+	io_mouse_up(&sms->io, mouse_num, button);
+}
+
+static void mouse_motion_absolute(system_header *system, uint8_t mouse_num, uint16_t x, uint16_t y)
+{
+	sms_context *sms = (sms_context *)system;
+	io_mouse_motion_absolute(&sms->io, mouse_num, x, y);
+}
+
+static void mouse_motion_relative(system_header *system, uint8_t mouse_num, int32_t x, int32_t y)
+{
+	sms_context *sms = (sms_context *)system;
+	io_mouse_motion_relative(&sms->io, mouse_num, x, y);
+}
+
+static void keyboard_down(system_header *system, uint8_t scancode)
+{
+	sms_context *sms = (sms_context *)system;
+	io_keyboard_down(&sms->io, scancode);
+}
+
+static void keyboard_up(system_header *system, uint8_t scancode)
+{
+	sms_context *sms = (sms_context *)system;
+	io_keyboard_up(&sms->io, scancode);
+}
+
 sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info_out)
 {
 	memset(info_out, 0, sizeof(*info_out));
@@ -527,6 +583,7 @@
 	info_out->name = strdup(media->name);
 	
 	setup_io_devices(config, info_out, &sms->io);
+	sms->header.has_keyboard = io_has_keyboard(&sms->io);
 	
 	sms->header.set_speed_percent = set_speed_percent;
 	sms->header.start_context = start_sms;
@@ -540,6 +597,14 @@
 	sms->header.soft_reset = soft_reset;
 	sms->header.inc_debug_mode = inc_debug_mode;
 	sms->header.inc_debug_pal = inc_debug_pal;
+	sms->header.gamepad_down = gamepad_down;
+	sms->header.gamepad_up = gamepad_up;
+	sms->header.mouse_down = mouse_down;
+	sms->header.mouse_up = mouse_up;
+	sms->header.mouse_motion_absolute = mouse_motion_absolute;
+	sms->header.mouse_motion_relative = mouse_motion_relative;
+	sms->header.keyboard_down = keyboard_down;
+	sms->header.keyboard_up = keyboard_up;
 	sms->header.type = SYSTEM_SMS;
 	
 	return sms;
--- a/system.h	Wed May 02 09:41:05 2018 -0700
+++ b/system.h	Sat May 05 23:30:40 2018 -0700
@@ -21,8 +21,12 @@
 typedef uint16_t (*system_fun_r16)(system_header *);
 typedef void (*system_str_fun)(system_header *, char *);
 typedef uint8_t (*system_str_fun_r8)(system_header *, char *);
-typedef void (*speed_system_fun)(system_header *, uint32_t);
+typedef void (*system_u32_fun)(system_header *, uint32_t);
+typedef void (*system_u8_fun)(system_header *, uint8_t);
 typedef uint8_t (*system_u8_fun_r8)(system_header *, uint8_t);
+typedef void (*system_u8_u8_fun)(system_header *, uint8_t, uint8_t);
+typedef void (*system_mabs_fun)(system_header *, uint8_t, uint16_t, uint16_t);
+typedef void (*system_mrel_fun)(system_header *, uint8_t, int32_t, int32_t);
 
 #include "arena.h"
 #include "romdb.h"
@@ -38,9 +42,17 @@
 	system_fun        soft_reset;
 	system_fun        free_context;
 	system_fun_r16    get_open_bus_value;
-	speed_system_fun  set_speed_percent;
+	system_u32_fun    set_speed_percent;
 	system_fun        inc_debug_mode;
 	system_fun        inc_debug_pal;
+	system_u8_u8_fun  gamepad_down;
+	system_u8_u8_fun  gamepad_up;
+	system_u8_u8_fun  mouse_down;
+	system_u8_u8_fun  mouse_up;
+	system_mabs_fun   mouse_motion_absolute;
+	system_mrel_fun   mouse_motion_relative;
+	system_u8_fun     keyboard_down;
+	system_u8_fun     keyboard_up;
 	arena             *arena;
 	char              *next_rom;
 	char              *save_dir;
@@ -48,6 +60,7 @@
 	uint8_t           should_exit;
 	uint8_t           save_state;
 	uint8_t           delayed_load_slot;
+	uint8_t           has_keyboard;
 	debugger_type     debugger_type;
 	system_type       type;
 };