# HG changeset patch # User Michael Pavone # Date 1485503353 28800 # Node ID 9d6f155732ed60f2fe8771744433789f861b3605 # Parent 32265f6b79e96a407f82eca36023fd0d8b6167a4 Basic support for mapping an analog axis to functionality diff -r 32265f6b79e9 -r 9d6f155732ed default.cfg --- a/default.cfg Thu Jan 26 20:30:33 2017 -0800 +++ b/default.cfg Thu Jan 26 23:49:13 2017 -0800 @@ -54,6 +54,14 @@ guide ui.exit leftstick ui.save_state } + axes { + lefty.positive gamepads.1.down + lefty.negative gamepads.1.up + leftx.positive gamepads.1.right + leftx.negative gamepads.1.left + lefttrigger ui.prev_speed + righttrigger ui.next_speed + } } 1 { dpads { @@ -77,6 +85,14 @@ guide ui.exit l3 ui.save_state } + axes { + lefty.positive gamepads.2.down + lefty.negative gamepads.2.up + leftx.positive gamepads.2.right + leftx.negative gamepads.2.left + l2 ui.prev_speed + r2 ui.next_speed + } } } mice { diff -r 32265f6b79e9 -r 9d6f155732ed io.c --- a/io.c Thu Jan 26 20:30:33 2017 -0800 +++ b/io.c Thu Jan 26 23:49:13 2017 -0800 @@ -95,10 +95,18 @@ } joydpad; typedef struct { + keybinding positive; + keybinding negative; + int16_t value; +} joyaxis; + +typedef struct { keybinding *buttons; joydpad *dpads; + joyaxis *axes; uint32_t num_buttons; //number of entries in the buttons array, not necessarily the number of buttons on the device uint32_t num_dpads; //number of entries in the dpads array, not necessarily the number of dpads on the device + uint32_t num_axes; //number of entries in the axes array, not necessarily the number of dpads on the device } joystick; typedef struct { @@ -116,6 +124,14 @@ static io_port *keyboard_port; const uint8_t dpadbits[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT}; +static void do_bind(keybinding *binding, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value) +{ + binding->bind_type = bind_type; + binding->subtype_a = subtype_a; + binding->subtype_b = subtype_b; + binding->value = value; +} + void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value) { int bucket = keycode >> 15 & 0xFFFF; @@ -124,10 +140,7 @@ memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000); } int idx = keycode & 0x7FFF; - bindings[bucket][idx].bind_type = bind_type; - bindings[bucket][idx].subtype_a = subtype_a; - bindings[bucket][idx].subtype_b = subtype_b; - bindings[bucket][idx].value = value; + do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b, value); } void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value) @@ -144,10 +157,7 @@ joysticks[joystick].buttons = realloc(joysticks[joystick].buttons, sizeof(keybinding) * joysticks[joystick].num_buttons); memset(joysticks[joystick].buttons + old_capacity, 0, joysticks[joystick].num_buttons - old_capacity); } - joysticks[joystick].buttons[button].bind_type = bind_type; - joysticks[joystick].buttons[button].subtype_a = subtype_a; - joysticks[joystick].buttons[button].subtype_b = subtype_b; - joysticks[joystick].buttons[button].value = value; + do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b, value); } void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value) @@ -156,26 +166,45 @@ return; } if (!joysticks[joystick].dpads) { - //multiple D-pads hats are not common, so don't allocate any extra space + //multiple D-pads/hats are not common, so don't allocate any extra space joysticks[joystick].dpads = calloc(dpad+1, sizeof(joydpad)); joysticks[joystick].num_dpads = dpad+1; } else if (joysticks[joystick].num_dpads <= dpad) { uint32_t old_capacity = joysticks[joystick].num_dpads; joysticks[joystick].num_dpads *= 2; joysticks[joystick].dpads = realloc(joysticks[joystick].dpads, sizeof(joydpad) * joysticks[joystick].num_dpads); - memset(joysticks[joystick].dpads + old_capacity, 0, joysticks[joystick].num_dpads - old_capacity); + memset(joysticks[joystick].dpads + old_capacity, 0, (joysticks[joystick].num_dpads - old_capacity) * sizeof(joydpad)); } for (int i = 0; i < 4; i ++) { if (dpadbits[i] & direction) { - joysticks[joystick].dpads[dpad].bindings[i].bind_type = bind_type; - joysticks[joystick].dpads[dpad].bindings[i].subtype_a = subtype_a; - joysticks[joystick].dpads[dpad].bindings[i].subtype_b = subtype_b; - joysticks[joystick].dpads[dpad].bindings[i].value = value; + do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b, value); break; } } } +void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value) +{ + if (joystick >= MAX_JOYSTICKS) { + return; + } + if (!joysticks[joystick].axes) { + //typical gamepad has 4 axes + joysticks[joystick].num_axes = axis+1 > 4 ? axis+1 : 4; + joysticks[joystick].axes = calloc(joysticks[joystick].num_axes, sizeof(joyaxis)); + } else if (joysticks[joystick].num_axes <= axis) { + uint32_t old_capacity = joysticks[joystick].num_axes; + joysticks[joystick].num_axes *= 2; + joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes); + memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis)); + } + if (positive) { + do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b, value); + } else { + do_bind(&joysticks[joystick].axes[axis].negative, bind_type, subtype_a, subtype_b, value); + } +} + void reset_joystick_bindings(int joystick) { if (joystick >= MAX_JOYSTICKS) { @@ -196,6 +225,13 @@ } } } + if (joysticks[joystick].axes) { + for (int i = 0; i < joysticks[joystick].num_axes; i++) + { + joysticks[joystick].axes[i].positive.bind_type = BIND_NONE; + joysticks[joystick].axes[i].negative.bind_type = BIND_NONE; + } + } } #define GAMEPAD_BUTTON(PRI_SLOT, SEC_SLOT, VALUE) (PRI_SLOT << 12 | SEC_SLOT << 8 | VALUE) @@ -219,8 +255,6 @@ #define MOUSE_MIDDLE 4 #define MOUSE_START 8 - - void bind_gamepad(int keycode, int gamepadnum, int button) { @@ -249,6 +283,15 @@ bind_dpad(joystick, dpad, direction, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF); } +void bind_axis_gamepad(int joystick, int axis, uint8_t positive, int gamepadnum, int button) +{ + if (gamepadnum < 1 || gamepadnum > 8) { + return; + } + uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1; + bind_axis(joystick, axis, positive, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF); +} + void bind_ui(int keycode, ui_action action, uint8_t param) { bind_key(keycode, BIND_UI, action, 0, param); @@ -264,6 +307,11 @@ bind_dpad(joystick, dpad, direction, BIND_UI, action, 0, param); } +void bind_axis_ui(int joystick, int axis, uint8_t positive, ui_action action, uint8_t param) +{ + bind_axis(joystick, axis, positive, BIND_UI, action, 0, param); +} + void handle_binding_down(keybinding * binding) { if (binding->bind_type >= BIND_GAMEPAD1 && binding->bind_type <= BIND_GAMEPAD8) @@ -466,6 +514,29 @@ } } +#define JOY_AXIS_THRESHOLD 2000 + +void handle_joy_axis(int joystick, int axis, int16_t value) +{ + if (joystick >= MAX_JOYSTICKS || axis >= joysticks[joystick].num_axes) { + return; + } + joyaxis *jaxis = joysticks[joystick].axes + axis; + int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD; + int new_active = abs(value) > JOY_AXIS_THRESHOLD; + int old_pos = jaxis->value > 0; + int new_pos = value > 0; + jaxis->value = value; + if (old_active && (!new_active || old_pos != new_pos)) { + //previously activated direction is no longer active + handle_binding_up(old_pos ? &jaxis->positive : &jaxis->negative); + } + if (new_active && (!old_active || old_pos != new_pos)) { + //previously unactivated direction is now active + handle_binding_down(new_pos ? &jaxis->positive : &jaxis->negative); + } +} + void handle_mouseup(int mouse, int button) { if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) { @@ -967,7 +1038,7 @@ long hostbutton = strtol(key, &end, 10); if (*end) { //key is not a valid base 10 integer - hostbutton = render_translate_input_name(hostpadnum, key); + hostbutton = render_translate_input_name(hostpadnum, key, 0); if (hostbutton < 0) { if (hostbutton == RENDER_INVALID_NAME) { warning("%s is not a valid gamepad input name\n", key); @@ -982,8 +1053,13 @@ } else { bind_dpad_ui(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), ui_func, button); } + return; } else if (hostbutton & RENDER_AXIS_BIT) { - //TODO: support analog axes + if (bindtype == BIND_GAMEPAD1) { + bind_axis_gamepad(hostpadnum, render_axis_part(hostbutton), 1, padnum, button); + } else { + bind_axis_ui(hostpadnum, render_axis_part(hostbutton), 1, padnum, button); + } return; } } @@ -994,6 +1070,65 @@ } } +void process_pad_axis(char *key, tern_val val, void *data) +{ + key = strdup(key); + pad_button_state *state = data; + int hostpadnum = state->padnum; + int ui_func, padnum, button; + int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &padnum, &button); + char *modifier = strchr(key, '.'); + int positive = 1; + if (modifier) { + *modifier = 0; + modifier++; + if (!strcmp("negative", modifier)) { + positive = 0; + } else if(strcmp("positive", modifier)) { + warning("Invalid axis modifier %s for axis %s on pad %d\n", modifier, key, hostpadnum); + } + } + char *end; + long axis = strtol(key, &end, 10); + if (*end) { + //key is not a valid base 10 integer + axis = render_translate_input_name(hostpadnum, key, 1); + if (axis < 0) { + if (axis == RENDER_INVALID_NAME) { + warning("%s is not a valid gamepad input name\n", key); + } else if (axis == RENDER_NOT_MAPPED) { + warning("No mapping exists for input %s on gamepad %d\n", key, hostpadnum); + } + goto done; + } + if (axis & RENDER_DPAD_BIT) { + if (bindtype == BIND_GAMEPAD1) { + bind_dpad_gamepad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), padnum, button); + } else { + bind_dpad_ui(hostpadnum, render_dpad_part(axis), render_direction_part(axis), ui_func, button); + } + goto done; + } else if (axis & RENDER_AXIS_BIT) { + axis = render_axis_part(axis); + } else { + if (bindtype == BIND_GAMEPAD1) { + bind_button_gamepad(hostpadnum, axis, padnum, button); + } else if (bindtype == BIND_UI) { + bind_button_ui(hostpadnum, axis, ui_func, button); + } + goto done; + } + } + if (bindtype == BIND_GAMEPAD1) { + bind_axis_gamepad(hostpadnum, axis, positive, padnum, button); + } else { + bind_axis_ui(hostpadnum, axis, positive, ui_func, button); + } +done: + free(key); + return; +} + static tern_node *get_pad_buttons() { static tern_node *padbuttons; @@ -1070,16 +1205,33 @@ }; tern_foreach(button_node, process_pad_button, &state); } + tern_node *axes_node = tern_find_ptr(pad, "axes"); + if (axes_node) { + pad_button_state state = { + .padnum = joystick, + .padbuttons = get_pad_buttons(), + .mousebuttons = get_mouse_buttons() + }; + tern_foreach(axes_node, process_pad_axis, &state); + } if (current_io) { if (joysticks[joystick].buttons) { map_bindings(current_io->ports, joysticks[joystick].buttons, joysticks[joystick].num_buttons); } if (joysticks[joystick].dpads) { - for (uint32_t i = 0; i < joysticks[joystick].num_dpads; i++) { + for (uint32_t i = 0; i < joysticks[joystick].num_dpads; i++) + { map_bindings(current_io->ports, joysticks[joystick].dpads[i].bindings, 4); } } + if (joysticks[joystick].axes) { + for (uint32_t i = 0; i < joysticks[joystick].num_axes; i++) + { + map_bindings(current_io->ports, &joysticks[joystick].axes[i].positive, 1); + map_bindings(current_io->ports, &joysticks[joystick].axes[i].negative, 1); + } + } } } } @@ -1193,10 +1345,16 @@ } if (joysticks[stick].dpads) { - for (uint32_t i = 0; i < joysticks[stick].num_dpads; i++) { + for (uint32_t i = 0; i < joysticks[stick].num_dpads; i++) + { map_bindings(ports, joysticks[stick].dpads[i].bindings, 4); } } + for (uint32_t i = 0; i < joysticks[stick].num_axes; i++) + { + map_bindings(current_io->ports, &joysticks[stick].axes[i].positive, 1); + map_bindings(current_io->ports, &joysticks[stick].axes[i].negative, 1); + } } for (int mouse = 0; mouse < MAX_MICE; mouse++) { diff -r 32265f6b79e9 -r 9d6f155732ed io.h --- a/io.h Thu Jan 26 20:30:33 2017 -0800 +++ b/io.h Thu Jan 26 23:49:13 2017 -0800 @@ -95,6 +95,7 @@ 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); diff -r 32265f6b79e9 -r 9d6f155732ed render.h --- a/render.h Thu Jan 26 20:30:33 2017 -0800 +++ b/render.h Thu Jan 26 23:49:13 2017 -0800 @@ -88,8 +88,9 @@ int render_height(); int render_fullscreen(); void process_events(); -int32_t render_translate_input_name(int32_t controller, char *name); +int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis); int32_t render_dpad_part(int32_t input); +int32_t render_axis_part(int32_t input); uint8_t render_direction_part(int32_t input); void render_errorbox(char *title, char *message); void render_warnbox(char *title, char *message); diff -r 32265f6b79e9 -r 9d6f155732ed render_sdl.c --- a/render_sdl.c Thu Jan 26 20:30:33 2017 -0800 +++ b/render_sdl.c Thu Jan 26 23:49:13 2017 -0800 @@ -656,9 +656,9 @@ return -1; } -int32_t render_translate_input_name(int32_t controller, char *name) +int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis) { - static tern_node *button_lookup; + static tern_node *button_lookup, *axis_lookup; if (controller > MAX_JOYSTICKS || !joysticks[controller]) { return RENDER_NOT_PLUGGED_IN; } @@ -666,35 +666,55 @@ if (!SDL_IsGameController(joystick_sdl_index[controller])) { return RENDER_NOT_MAPPED; } - - if (!button_lookup) { - for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) - { - button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i); - } - //alternative Playstation-style names - button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A); - button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B); - button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X); - button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y); - button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK); - button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK); - button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START); - button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER); - button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); - button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK); - button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK); - } - intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID); - if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) { - return RENDER_INVALID_NAME; - } SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]); if (!control) { warning("Failed to open game controller %d: %s\n", controller, SDL_GetError()); return RENDER_NOT_PLUGGED_IN; } - SDL_GameControllerButtonBind cbind = SDL_GameControllerGetBindForButton(control, sdl_button); + + SDL_GameControllerButtonBind cbind; + if (is_axis) { + if (!axis_lookup) { + for (int i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) + { + axis_lookup = tern_insert_int(axis_lookup, SDL_GameControllerGetStringForAxis(i), i); + } + //alternative Playstation-style names + axis_lookup = tern_insert_int(axis_lookup, "l2", SDL_CONTROLLER_AXIS_TRIGGERLEFT); + axis_lookup = tern_insert_int(axis_lookup, "r2", SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + } + intptr_t sdl_axis = tern_find_int(axis_lookup, name, SDL_CONTROLLER_AXIS_INVALID); + if (sdl_axis == SDL_CONTROLLER_AXIS_INVALID) { + SDL_GameControllerClose(control); + return RENDER_INVALID_NAME; + } + cbind = SDL_GameControllerGetBindForAxis(control, sdl_axis); + } else { + if (!button_lookup) { + for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) + { + button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i); + } + //alternative Playstation-style names + button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A); + button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B); + button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X); + button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y); + button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK); + button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK); + button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START); + button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER); + button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); + button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK); + button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK); + } + intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID); + if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) { + SDL_GameControllerClose(control); + return RENDER_INVALID_NAME; + } + cbind = SDL_GameControllerGetBindForButton(control, sdl_button); + } SDL_GameControllerClose(control); switch (cbind.bindType) { @@ -710,7 +730,7 @@ int32_t render_dpad_part(int32_t input) { - return input >> 4 & 0x7FFFFF; + return input >> 4 & 0xFFFFFF; } uint8_t render_direction_part(int32_t input) @@ -718,6 +738,11 @@ return input & 0xF; } +int32_t render_axis_part(int32_t input) +{ + return input & 0xFFFFFFF; +} + static uint8_t scancode_map[SDL_NUM_SCANCODES] = { [SDL_SCANCODE_A] = 0x1C, [SDL_SCANCODE_B] = 0x32, @@ -840,6 +865,9 @@ case SDL_JOYHATMOTION: handle_joy_dpad(find_joystick_index(event->jbutton.which), event->jhat.hat, event->jhat.value); break; + case SDL_JOYAXISMOTION: + handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value); + break; case SDL_JOYDEVICEADDED: if (event->jdevice.which < MAX_JOYSTICKS) { int index = lowest_unused_joystick_index(); diff -r 32265f6b79e9 -r 9d6f155732ed todo.txt --- a/todo.txt Thu Jan 26 20:30:33 2017 -0800 +++ b/todo.txt Thu Jan 26 23:49:13 2017 -0800 @@ -20,7 +20,6 @@ Horizontal border emulation Integrate Jaguar emulation into main executable Realtec mapper support -Gamepad analog axis mapping 64-bit Windows Build Future Releases (no particular order)