diff --git a/.clang-format b/.clang-format index 7a183161..3a0e0e33 100644 --- a/.clang-format +++ b/.clang-format @@ -83,7 +83,7 @@ IncludeCategories: SortPriority: 0 IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' -IndentCaseLabels: false +IndentCaseLabels: true IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None diff --git a/CMakeLists.txt b/CMakeLists.txt index b992c114..dc25f85b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ option(CMAKE_FIND_PACKAGE_PREFER_CONFIG option(ENABLE_SDL2_NET "Enable SDL2_net" On) option(ENABLE_SDL2_MIXER "Enable SDL2_mixer" On) -find_package(SDL2 2.0.7) +find_package(SDL2 2.0.14) if(ENABLE_SDL2_MIXER) find_package(SDL2_mixer 2.0.2) else() diff --git a/Makefile.am b/Makefile.am index 8c81ed37..9a2e5231 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,7 +9,7 @@ CMAKE_FILES= \ cmake/FindSDL2_net.cmake \ cmake/FindFluidSynth.cmake \ cmake/Findm.cmake \ - cmake/Findsamplerate.cmake \ + cmake/FindSampleRate.cmake \ cmake/config.h.cin DOC_FILES= \ diff --git a/NEWS.md b/NEWS.md index f94f386b..8969e023 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,6 +30,10 @@ * Add native support for the FluidSynth midi synthesizer. * It's now possible to play back a demo file by drag-and-dropping it on the executable (Fabian). + * Add improved gamepad support via the SDL\_GameController interface. This + includes support for analog triggers, modern dual-stick default bindings + (based on Unity Doom), descriptive button names for common controller types + and configurable dead zones for stick axes. (Michael Day) ### Refactorings * CMake project files have been added, replacing the Microsoft Visual diff --git a/cmake/Findsamplerate.cmake b/cmake/FindSampleRate.cmake similarity index 100% rename from cmake/Findsamplerate.cmake rename to cmake/FindSampleRate.cmake diff --git a/configure.ac b/configure.ac index fa11ae80..f08168ae 100644 --- a/configure.ac +++ b/configure.ac @@ -31,7 +31,7 @@ then CFLAGS="-O$OPT_LEVEL -g $WARNINGS $orig_CFLAGS" fi -PKG_CHECK_MODULES(SDL, [sdl2 >= 2.0.7]) +PKG_CHECK_MODULES(SDL, [sdl2 >= 2.0.14]) # Check for SDL2_mixer AC_ARG_ENABLE([sdl2mixer], AS_HELP_STRING([--disable-sdl2mixer], [Disable SDL2_mixer support]) diff --git a/src/i_joystick.c b/src/i_joystick.c index d29c2fe6..abf2177c 100644 --- a/src/i_joystick.c +++ b/src/i_joystick.c @@ -18,6 +18,7 @@ #include "SDL.h" #include "SDL_joystick.h" +#include "SDL_gamecontroller.h" #include #include @@ -31,11 +32,7 @@ #include "m_config.h" #include "m_misc.h" -// When an axis is within the dead zone, it is set to zero. -// This is 5% of the full range: - -#define DEAD_ZONE (32768 / 3) - +static SDL_GameController *gamepad = NULL; static SDL_Joystick *joystick = NULL; // Configuration variables: @@ -44,6 +41,12 @@ static SDL_Joystick *joystick = NULL; static int usejoystick = 0; +// Use SDL_gamecontroller interface for the selected device +static int use_gamepad = 0; + +// SDL_GameControllerType of gamepad +static int gamepad_type = 0; + // SDL GUID and index of the joystick to use. static char *joystick_guid = ""; static int joystick_index = -1; @@ -70,12 +73,260 @@ static int joystick_strafe_invert = 0; static int joystick_look_axis = -1; static int joystick_look_invert = 0; +// Configurable dead zone for each axis, specified as a percentage of the axis +// max value. +static int joystick_x_dead_zone = 33; +static int joystick_y_dead_zone = 33; +static int joystick_strafe_dead_zone = 33; +static int joystick_look_dead_zone = 33; + // Virtual to physical button joystick button mapping. By default this // is a straight mapping. static int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; +void I_ShutdownGamepad(void) +{ + if (gamepad != NULL) + { + SDL_GameControllerClose(gamepad); + gamepad = NULL; + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + } +} + +static int FindFirstGamepad(void) +{ + int i; + int gamepadindex = -1; + + for (i = 0; i < SDL_NumJoysticks(); ++i) + { + if (SDL_IsGameController(i)) + { + gamepadindex = i; + break; + } + } + + return gamepadindex; +} + +static int FindSpecificGamepad(SDL_JoystickGUID guid) +{ + SDL_JoystickGUID dev_guid; + int i; + int gamepadindex = -1; + + for (i = 0; i < SDL_NumJoysticks(); ++i) + { + dev_guid = SDL_JoystickGetDeviceGUID(i); + if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID))) + { + gamepadindex = i; + break; + } + } + + return gamepadindex; +} + +static int DeviceIndexGamepad(void) +{ + SDL_JoystickGUID guid, dev_guid; + int index = -1; + + if (strcmp(joystick_guid, "")) + { + guid = SDL_JoystickGetGUIDFromString(joystick_guid); + + // First, look for the gamepad at the previously-used index. + if (joystick_index >= 0 && joystick_index < SDL_NumJoysticks()) + { + dev_guid = SDL_JoystickGetDeviceGUID(joystick_index); + if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID))) + { + return joystick_index; + } + } + + // Maybe the index has moved? + index = FindSpecificGamepad(guid); + } + + // If the previous gamepad isn't present, see if a different one is + // available. + if (index < 0) + { + index = FindFirstGamepad(); + } + + return index; +} + +void I_InitGamepad(void) +{ + SDL_JoystickGUID guid; + int index; + + if (!use_gamepad) + { + return; + } + + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) + { + return; + } + + index = DeviceIndexGamepad(); + + if (index < 0) + { + printf("I_InitGamepad: No gamepad found.\n"); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + return; + } + + gamepad = SDL_GameControllerOpen(index); + + if (gamepad == NULL) + { + printf("I_InitGamepad: Failed to open gamepad: %s\n", SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + return; + } + + joystick_index = index; + gamepad_type = SDL_GameControllerTypeForIndex(index); + + if (strcmp(joystick_guid, "")) + { + joystick_guid = malloc(GUID_STRING_BUF_SIZE); + } + + guid = SDL_JoystickGetDeviceGUID(joystick_index); + SDL_JoystickGetGUIDString(guid, joystick_guid, GUID_STRING_BUF_SIZE); + + // GameController events do not fire if Joystick events are disabled. + SDL_JoystickEventState(SDL_ENABLE); + SDL_GameControllerEventState(SDL_ENABLE); + + printf("I_InitGamepad: %s\n", SDL_GameControllerName(gamepad)); + I_AtExit(I_ShutdownGamepad, true); +} + +static int GetTriggerStateGamepad(SDL_GameControllerAxis trigger) +{ + return (SDL_GameControllerGetAxis(gamepad, trigger) > TRIGGER_THRESHOLD); +} + +// Get the state of the given virtual button. + +static int ReadButtonStateGamepad(int vbutton) +{ + int physbutton, state; + + // Map from virtual button to physical (SDL) button. + if (vbutton < NUM_VIRTUAL_BUTTONS) + { + physbutton = joystick_physical_buttons[vbutton]; + } + else + { + physbutton = vbutton; + } + + switch (physbutton) + { + case GAMEPAD_BUTTON_TRIGGERLEFT: + state = GetTriggerStateGamepad(SDL_CONTROLLER_AXIS_TRIGGERLEFT); + break; + + case GAMEPAD_BUTTON_TRIGGERRIGHT: + state = GetTriggerStateGamepad(SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + break; + + default: + state = SDL_GameControllerGetButton(gamepad, physbutton); + break; + } + + return state; +} + +// Get a bitmask of all currently-pressed buttons + +static int GetButtonsStateGamepad(void) +{ + int i; + int result = 0; + + for (i = 0; i < MAX_VIRTUAL_BUTTONS; ++i) + { + if (ReadButtonStateGamepad(i)) + { + result |= 1u << i; + } + } + + return result; +} + +// Read the state of an axis, inverting if necessary. + +static int GetAxisStateGamepad(int axis, int invert, int dead_zone) +{ + int result; + + // Axis -1 means disabled. + + if (axis < 0) + { + return 0; + } + + // Dead zone is expressed as percentage of axis max value + dead_zone = 32768 * dead_zone / 100; + + result = SDL_GameControllerGetAxis(gamepad, axis); + + if (result < dead_zone && result > -dead_zone) + { + result = 0; + } + + if (invert) + { + result = -result; + } + + return result; +} + +void I_UpdateGamepad(void) +{ + if (gamepad != NULL) + { + event_t ev; + + ev.type = ev_joystick; + ev.data1 = GetButtonsStateGamepad(); + ev.data2 = GetAxisStateGamepad(joystick_x_axis, joystick_x_invert, + joystick_x_dead_zone); + ev.data3 = GetAxisStateGamepad(joystick_y_axis, joystick_y_invert, + joystick_y_dead_zone); + ev.data4 = + GetAxisStateGamepad(joystick_strafe_axis, joystick_strafe_invert, + joystick_strafe_dead_zone); + ev.data5 = GetAxisStateGamepad(joystick_look_axis, joystick_look_invert, + joystick_look_dead_zone); + + D_PostEvent(&ev); + } +} + void I_ShutdownJoystick(void) { if (joystick != NULL) @@ -153,7 +404,13 @@ void I_InitJoystick(void) return; } - if (SDL_Init(SDL_INIT_JOYSTICK) < 0) + if (use_gamepad) + { + I_InitGamepad(); + return; + } + + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { return; } @@ -175,7 +432,8 @@ void I_InitJoystick(void) if (joystick == NULL) { - printf("I_InitJoystick: Failed to open joystick #%i\n", index); + printf("I_InitJoystick: Failed to open joystick #%i: %s\n", index, + SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); return; } @@ -270,11 +528,9 @@ static int ReadButtonState(int vbutton) static int GetButtonsState(void) { int i; - int result; + int result = 0; - result = 0; - - for (i = 0; i < 20; ++i) + for (i = 0; i < MAX_VIRTUAL_BUTTONS; ++i) { if (ReadButtonState(i)) { @@ -287,7 +543,7 @@ static int GetButtonsState(void) // Read the state of an axis, inverting if necessary. -static int GetAxisState(int axis, int invert) +static int GetAxisState(int axis, int invert, int dead_zone) { int result; @@ -346,7 +602,10 @@ static int GetAxisState(int axis, int invert) { result = SDL_JoystickGetAxis(joystick, axis); - if (result < DEAD_ZONE && result > -DEAD_ZONE) + // Dead zone is expressed as percentage of axis max value + dead_zone = 32768 * dead_zone / 100; + + if (result < dead_zone && result > -dead_zone) { result = 0; } @@ -362,16 +621,26 @@ static int GetAxisState(int axis, int invert) void I_UpdateJoystick(void) { + if (use_gamepad) + { + I_UpdateGamepad(); + return; + } + if (joystick != NULL) { event_t ev; ev.type = ev_joystick; ev.data1 = GetButtonsState(); - ev.data2 = GetAxisState(joystick_x_axis, joystick_x_invert); - ev.data3 = GetAxisState(joystick_y_axis, joystick_y_invert); - ev.data4 = GetAxisState(joystick_strafe_axis, joystick_strafe_invert); - ev.data5 = GetAxisState(joystick_look_axis, joystick_look_invert); + ev.data2 = GetAxisState(joystick_x_axis, joystick_x_invert, + joystick_x_dead_zone); + ev.data3 = GetAxisState(joystick_y_axis, joystick_y_invert, + joystick_y_dead_zone); + ev.data4 = GetAxisState(joystick_strafe_axis, joystick_strafe_invert, + joystick_strafe_dead_zone); + ev.data5 = GetAxisState(joystick_look_axis, joystick_look_invert, + joystick_look_dead_zone); D_PostEvent(&ev); } @@ -382,6 +651,8 @@ void I_BindJoystickVariables(void) int i; M_BindIntVariable("use_joystick", &usejoystick); + M_BindIntVariable("use_gamepad", &use_gamepad); + M_BindIntVariable("gamepad_type", &gamepad_type); M_BindStringVariable("joystick_guid", &joystick_guid); M_BindIntVariable("joystick_index", &joystick_index); M_BindIntVariable("joystick_x_axis", &joystick_x_axis); @@ -392,6 +663,10 @@ void I_BindJoystickVariables(void) M_BindIntVariable("joystick_strafe_invert",&joystick_strafe_invert); M_BindIntVariable("joystick_look_axis", &joystick_look_axis); M_BindIntVariable("joystick_look_invert", &joystick_look_invert); + M_BindIntVariable("joystick_x_dead_zone", &joystick_x_dead_zone); + M_BindIntVariable("joystick_y_dead_zone", &joystick_y_dead_zone); + M_BindIntVariable("joystick_strafe_dead_zone", &joystick_strafe_dead_zone); + M_BindIntVariable("joystick_look_dead_zone", &joystick_look_dead_zone); for (i = 0; i < NUM_VIRTUAL_BUTTONS; ++i) { diff --git a/src/i_joystick.h b/src/i_joystick.h index 1c9744f5..b99b8d24 100644 --- a/src/i_joystick.h +++ b/src/i_joystick.h @@ -19,11 +19,17 @@ #ifndef __I_JOYSTICK__ #define __I_JOYSTICK__ +#include "SDL_gamecontroller.h" + // Number of "virtual" joystick buttons defined in configuration files. // This needs to be at least as large as the number of different key // bindings supported by the higher-level game code (joyb* variables). #define NUM_VIRTUAL_BUTTONS 11 +// Max allowed number of virtual mappings. Chosen to be less than joybspeed +// autorun value. +#define MAX_VIRTUAL_BUTTONS 20 + // If this bit is set in a configuration file axis value, the axis is // not actually a joystick axis, but instead is a "button axis". This // means that instead of reading an SDL joystick axis, we read the @@ -60,6 +66,22 @@ #define HAT_AXIS_HORIZONTAL 1 #define HAT_AXIS_VERTICAL 2 +// When a trigger reads greater than this, consider it to be pressed. 30 comes +// from XINPUT_GAMEPAD_TRIGGER_THRESHOLD in xinput.h, and is scaled here for +// the SDL_GameController trigger max value. +#define TRIGGER_THRESHOLD (30 * 32767 / 255) + +// To be used with SDL_JoystickGetGUIDString; see SDL_joystick.h +#define GUID_STRING_BUF_SIZE 33 + +// Extend the SDL_GameControllerButton enum to include the triggers. +enum +{ + GAMEPAD_BUTTON_TRIGGERLEFT = SDL_CONTROLLER_BUTTON_MAX, + GAMEPAD_BUTTON_TRIGGERRIGHT, + GAMEPAD_BUTTON_MAX +}; + void I_InitJoystick(void); void I_ShutdownJoystick(void); void I_UpdateJoystick(void); diff --git a/src/m_config.c b/src/m_config.c index be1e1aea..54ab4b26 100644 --- a/src/m_config.c +++ b/src/m_config.c @@ -1498,6 +1498,47 @@ static default_t extra_defaults_list[] = CONFIG_VARIABLE_INT(joystick_physical_button10), + //! + // If non-zero, use the SDL_GameController interface instead of the + // SDL_Joystick interface. + // + + CONFIG_VARIABLE_INT(use_gamepad), + + //! + // Stores the SDL_GameControllerType of the last configured gamepad. + // + + CONFIG_VARIABLE_INT(gamepad_type), + + //! + // Joystick x axis dead zone, specified as a percentage of the axis max + // value. + // + + CONFIG_VARIABLE_INT(joystick_x_dead_zone), + + //! + // Joystick y axis dead zone, specified as a percentage of the axis max + // value. + // + + CONFIG_VARIABLE_INT(joystick_y_dead_zone), + + //! + // Joystick strafe axis dead zone, specified as a percentage of the axis + // max value. + // + + CONFIG_VARIABLE_INT(joystick_strafe_dead_zone), + + //! + // Joystick look axis dead zone, specified as a percentage of the axis max + // value. + // + + CONFIG_VARIABLE_INT(joystick_look_dead_zone), + //! // Joystick virtual button to make the player strafe left. // diff --git a/src/setup/joystick.c b/src/setup/joystick.c index 1be86dd4..f07ebdc2 100644 --- a/src/setup/joystick.c +++ b/src/setup/joystick.c @@ -85,6 +85,13 @@ static int joystick_strafe_invert = 0; static int joystick_look_axis = -1; static int joystick_look_invert = 0; +// Configurable dead zone for each axis, specified as a percentage of the axis +// max value. +static int joystick_x_dead_zone = 33; +static int joystick_y_dead_zone = 33; +static int joystick_strafe_dead_zone = 33; +static int joystick_look_dead_zone = 33; + // Virtual to physical mapping. int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 @@ -596,6 +603,63 @@ static const known_joystick_t known_joysticks[] = }, }; +// Use SDL_GameController interface +int use_gamepad = 0; + +// SDL_GameControllerType of gamepad +int gamepad_type = 0; + +// Based on Unity Doom mapping +static const joystick_config_t modern_gamepad[] = +{ + {"joystick_x_axis", SDL_CONTROLLER_AXIS_RIGHTX}, + {"joystick_y_axis", SDL_CONTROLLER_AXIS_LEFTY}, + {"joystick_strafe_axis", SDL_CONTROLLER_AXIS_LEFTX}, + {"joystick_look_axis", SDL_CONTROLLER_AXIS_RIGHTY}, + {"joyb_fire", GAMEPAD_BUTTON_TRIGGERRIGHT}, + {"joyb_speed", GAMEPAD_BUTTON_TRIGGERLEFT}, + {"joyb_use", SDL_CONTROLLER_BUTTON_B}, + {"joyb_jump", SDL_CONTROLLER_BUTTON_A}, + {"joyb_prevweapon", SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {"joyb_nextweapon", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {"joyb_menu_activate", SDL_CONTROLLER_BUTTON_START}, + {"joyb_toggle_automap", SDL_CONTROLLER_BUTTON_Y}, + {NULL, 0}, +}; + +// Based on the SNES Doom mapping +static const joystick_config_t classic_gamepad[] = +{ + {"joystick_x_axis", SDL_CONTROLLER_AXIS_LEFTX}, + {"joystick_y_axis", SDL_CONTROLLER_AXIS_LEFTY}, + {"joyb_fire", SDL_CONTROLLER_BUTTON_X}, // SNES Y + {"joyb_speed", SDL_CONTROLLER_BUTTON_A}, // SNES B + {"joyb_use", SDL_CONTROLLER_BUTTON_B}, // SNES A + {"joyb_strafeleft", SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, // SNES L + {"joyb_straferight", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, // SNES R + {"joyb_nextweapon", SDL_CONTROLLER_BUTTON_Y}, // SNES X + {"joyb_menu_activate", SDL_CONTROLLER_BUTTON_START}, // SNES Start + {"joyb_toggle_automap", SDL_CONTROLLER_BUTTON_BACK}, // SNES Select + {NULL, 0}, +}; + +// SNES Doom mapping with extra shoulder buttons +static const joystick_config_t classic_gamepad_plus[] = +{ + {"joystick_x_axis", SDL_CONTROLLER_AXIS_LEFTX}, + {"joystick_y_axis", SDL_CONTROLLER_AXIS_LEFTY}, + {"joyb_fire", SDL_CONTROLLER_BUTTON_X}, // SNES Y + {"joyb_speed", SDL_CONTROLLER_BUTTON_A}, // SNES B + {"joyb_use", SDL_CONTROLLER_BUTTON_B}, // SNES A + {"joyb_strafeleft", SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, // L1 + {"joyb_straferight", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, // R1 + {"joyb_prevweapon", GAMEPAD_BUTTON_TRIGGERLEFT}, // L2 + {"joyb_nextweapon", GAMEPAD_BUTTON_TRIGGERRIGHT}, // R2 + {"joyb_menu_activate", SDL_CONTROLLER_BUTTON_START}, // SNES Start + {"joyb_toggle_automap", SDL_CONTROLLER_BUTTON_BACK}, // SNES Select + {NULL, 0}, +}; + static const known_joystick_t *GetJoystickType(int index) { SDL_Joystick *joystick; @@ -665,7 +729,8 @@ static void LoadConfigurationSet(const joystick_config_t *configs) config = &configs[i]; // Don't overwrite autorun if it is set. - if (!strcmp(config->name, "joyb_speed") && joybspeed >= 20) + if (!strcmp(config->name, "joyb_speed") && + joybspeed >= MAX_VIRTUAL_BUTTONS) { continue; } @@ -705,7 +770,7 @@ static void InitJoystick(void) { if (!joystick_initted) { - joystick_initted = SDL_Init(SDL_INIT_JOYSTICK) >= 0; + joystick_initted = SDL_InitSubSystem(SDL_INIT_JOYSTICK) >= 0; } } @@ -867,9 +932,11 @@ static boolean SetJoystickGUID(SDL_JoystickID joy_id) if (SDL_JoystickInstanceID(all_joysticks[i]) == joy_id) { guid = SDL_JoystickGetGUID(all_joysticks[i]); - joystick_guid = malloc(33); - SDL_JoystickGetGUIDString(guid, joystick_guid, 33); + joystick_guid = malloc(GUID_STRING_BUF_SIZE); + SDL_JoystickGetGUIDString(guid, joystick_guid, + GUID_STRING_BUF_SIZE); joystick_index = i; + return true; } } @@ -877,6 +944,36 @@ static boolean SetJoystickGUID(SDL_JoystickID joy_id) return false; } +static void GetGamepadDefaultConfig(void) +{ + boolean have_four_shoulder, have_dual_sticks; + char *mapping; + SDL_JoystickGUID guid; + + guid = SDL_JoystickGetGUID(all_joysticks[joystick_index]); + mapping = SDL_GameControllerMappingForGUID(guid); + have_four_shoulder = + strstr(mapping, "leftshoulder") && strstr(mapping, "rightshoulder") && + strstr(mapping, "lefttrigger") && strstr(mapping, "righttrigger"); + have_dual_sticks = strstr(mapping, "leftx") && strstr(mapping, "rightx"); + SDL_free(mapping); + + LoadConfigurationSet(empty_defaults); + + if (have_four_shoulder && have_dual_sticks) + { + LoadConfigurationSet(modern_gamepad); + } + else if (have_four_shoulder) + { + LoadConfigurationSet(classic_gamepad_plus); + } + else + { + LoadConfigurationSet(classic_gamepad); + } +} + static int CalibrationEventCallback(SDL_Event *event, void *user_data) { if (event->type != SDL_JOYBUTTONDOWN) @@ -889,10 +986,23 @@ static int CalibrationEventCallback(SDL_Event *event, void *user_data) return 0; } + if (SDL_IsGameController(joystick_index)) + { + usejoystick = 1; + use_gamepad = 1; + gamepad_type = SDL_GameControllerTypeForIndex(joystick_index); + LoadConfigurationSet(empty_defaults); + GetGamepadDefaultConfig(); + TXT_CloseWindow(calibration_window); + return 1; + } + // At this point, we have a button press. // In the first "center" stage, we're just trying to work out which // joystick is being configured and which button the user is pressing. usejoystick = 1; + use_gamepad = 0; + gamepad_type = SDL_CONTROLLER_TYPE_UNKNOWN; calibrate_button = event->jbutton.button; // If the joystick is a known one, auto-load default @@ -923,19 +1033,35 @@ static void NoJoystick(void) "some drivers or otherwise configure it."); usejoystick = 0; + use_gamepad = 0; joystick_index = -1; SetJoystickButtonLabel(); } -static void CalibrateWindowClosed(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused)) +static void RefreshJoystickWindow(TXT_UNCAST_ARG(widget), + TXT_UNCAST_ARG(unused)) { + ConfigJoystick(NULL, NULL); +} + +static void CalibrateWindowClosed(TXT_UNCAST_ARG(widget), + TXT_UNCAST_ARG(joystick_window)) +{ + TXT_CAST_ARG(txt_window_t, joystick_window); TXT_SDL_SetEventCallback(NULL, NULL); SetJoystickButtonLabel(); CloseAllJoysticks(); + + // Refresh Joystick window to update button and axis widgets. + TXT_SignalConnect(joystick_window, "closed", RefreshJoystickWindow, NULL); + TXT_CloseWindow(joystick_window); } -static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused)) +static void CalibrateJoystick(TXT_UNCAST_ARG(widget), + TXT_UNCAST_ARG(joystick_window)) { + TXT_CAST_ARG(txt_window_t, joystick_window); + // Try to open all available joysticks. If none are opened successfully, // bomb out with an error. @@ -961,11 +1087,10 @@ static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused)) TXT_SDL_SetEventCallback(CalibrationEventCallback, NULL); - TXT_SignalConnect(calibration_window, "closed", CalibrateWindowClosed, NULL); + TXT_SignalConnect(calibration_window, "closed", CalibrateWindowClosed, + joystick_window); // Start calibration - usejoystick = 0; - joystick_index = -1; } // @@ -986,6 +1111,28 @@ static void AddJoystickControl(TXT_UNCAST_ARG(table), const char *label, int *va NULL); } +static void SwapLRSticks(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused)) +{ + // Single pad/stick controllers don't get a joystick_strafe_axis value + if (joystick_strafe_axis >= 0) + { + if (joystick_x_axis == SDL_CONTROLLER_AXIS_LEFTX) + { + joystick_x_axis = SDL_CONTROLLER_AXIS_RIGHTX; + joystick_y_axis = SDL_CONTROLLER_AXIS_LEFTY; + joystick_strafe_axis = SDL_CONTROLLER_AXIS_LEFTX; + joystick_look_axis = SDL_CONTROLLER_AXIS_RIGHTY; + } + else + { + joystick_x_axis = SDL_CONTROLLER_AXIS_LEFTX; + joystick_y_axis = SDL_CONTROLLER_AXIS_RIGHTY; + joystick_strafe_axis = SDL_CONTROLLER_AXIS_RIGHTX; + joystick_look_axis = SDL_CONTROLLER_AXIS_LEFTY; + } + } +} + void ConfigJoystick(TXT_UNCAST_ARG(widget), void *user_data) { txt_window_t *window; @@ -1004,6 +1151,7 @@ void ConfigJoystick(TXT_UNCAST_ARG(widget), void *user_data) TXT_NewLabel("Forward/backward"), y_axis_widget = TXT_NewJoystickAxis(&joystick_y_axis, &joystick_y_invert, + &joystick_y_dead_zone, JOYSTICK_AXIS_VERTICAL), TXT_TABLE_OVERFLOW_RIGHT, TXT_TABLE_OVERFLOW_RIGHT, @@ -1014,6 +1162,7 @@ void ConfigJoystick(TXT_UNCAST_ARG(widget), void *user_data) x_axis_widget = TXT_NewJoystickAxis(&joystick_x_axis, &joystick_x_invert, + &joystick_x_dead_zone, JOYSTICK_AXIS_HORIZONTAL), TXT_TABLE_OVERFLOW_RIGHT, TXT_TABLE_OVERFLOW_RIGHT, @@ -1023,6 +1172,7 @@ void ConfigJoystick(TXT_UNCAST_ARG(widget), void *user_data) TXT_NewLabel("Strafe left/right"), TXT_NewJoystickAxis(&joystick_strafe_axis, &joystick_strafe_invert, + &joystick_strafe_dead_zone, JOYSTICK_AXIS_HORIZONTAL), TXT_TABLE_OVERFLOW_RIGHT, TXT_TABLE_OVERFLOW_RIGHT, @@ -1036,6 +1186,7 @@ void ConfigJoystick(TXT_UNCAST_ARG(widget), void *user_data) TXT_NewLabel("Look up/down"), TXT_NewJoystickAxis(&joystick_look_axis, &joystick_look_invert, + &joystick_look_dead_zone, JOYSTICK_AXIS_VERTICAL), TXT_TABLE_OVERFLOW_RIGHT, TXT_TABLE_OVERFLOW_RIGHT, @@ -1044,6 +1195,17 @@ void ConfigJoystick(TXT_UNCAST_ARG(widget), void *user_data) NULL); } + TXT_AddWidget(window, + TXT_NewConditional(&use_gamepad, 1, + TXT_MakeTable(6, + TXT_NewButton2("Swap L and R sticks", SwapLRSticks, NULL), + TXT_TABLE_OVERFLOW_RIGHT, + TXT_TABLE_OVERFLOW_RIGHT, + TXT_TABLE_EMPTY, + TXT_TABLE_EMPTY, + TXT_TABLE_EMPTY, + NULL))); + TXT_AddWidget(window, TXT_NewSeparator("Buttons")); AddJoystickControl(window, "Fire/Attack", &joybfire); @@ -1061,7 +1223,7 @@ void ConfigJoystick(TXT_UNCAST_ARG(widget), void *user_data) // trick in Vanilla Doom. If this has been enabled, not only is the // joybspeed value meaningless, but the control itself is useless. - if (joybspeed < 20) + if (joybspeed < MAX_VIRTUAL_BUTTONS) { AddJoystickControl(window, "Run", &joybspeed); } @@ -1075,7 +1237,7 @@ void ConfigJoystick(TXT_UNCAST_ARG(widget), void *user_data) AddJoystickControl(window, "Toggle Automap", &joybautomap); - TXT_SignalConnect(joystick_button, "pressed", CalibrateJoystick, NULL); + TXT_SignalConnect(joystick_button, "pressed", CalibrateJoystick, window); TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction()); InitJoystick(); @@ -1089,6 +1251,8 @@ void BindJoystickVariables(void) int i; M_BindIntVariable("use_joystick", &usejoystick); + M_BindIntVariable("use_gamepad", &use_gamepad); + M_BindIntVariable("gamepad_type", &gamepad_type); M_BindStringVariable("joystick_guid", &joystick_guid); M_BindIntVariable("joystick_index", &joystick_index); M_BindIntVariable("joystick_x_axis", &joystick_x_axis); @@ -1099,6 +1263,10 @@ void BindJoystickVariables(void) M_BindIntVariable("joystick_strafe_invert", &joystick_strafe_invert); M_BindIntVariable("joystick_look_axis", &joystick_look_axis); M_BindIntVariable("joystick_look_invert", &joystick_look_invert); + M_BindIntVariable("joystick_x_dead_zone", &joystick_x_dead_zone); + M_BindIntVariable("joystick_y_dead_zone", &joystick_y_dead_zone); + M_BindIntVariable("joystick_strafe_dead_zone", &joystick_strafe_dead_zone); + M_BindIntVariable("joystick_look_dead_zone", &joystick_look_dead_zone); for (i = 0; i < NUM_VIRTUAL_BUTTONS; ++i) { diff --git a/src/setup/joystick.h b/src/setup/joystick.h index 57d4664b..dd17b7d9 100644 --- a/src/setup/joystick.h +++ b/src/setup/joystick.h @@ -21,6 +21,8 @@ extern int joystick_index; extern int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS]; +extern int use_gamepad; +extern int gamepad_type; void ConfigJoystick(void *widget, void *user_data); diff --git a/src/setup/txt_joyaxis.c b/src/setup/txt_joyaxis.c index a863704d..620c0bfa 100644 --- a/src/setup/txt_joyaxis.c +++ b/src/setup/txt_joyaxis.c @@ -359,7 +359,7 @@ void TXT_ConfigureJoystickAxis(txt_joystick_axis_t *joystick_axis, txt_joystick_axis_callback_t callback) { // Open the joystick first. - if (SDL_Init(SDL_INIT_JOYSTICK) < 0) + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { return; } @@ -413,6 +413,30 @@ void TXT_ConfigureJoystickAxis(txt_joystick_axis_t *joystick_axis, joystick_axis->callback = callback; } +void TXT_ConfigureGamepadAxis(txt_joystick_axis_t *joystick_axis, + int using_button, + txt_joystick_axis_callback_t callback) +{ + // Build the prompt window. + + joystick_axis->config_window = TXT_NewWindow("Configure axis"); + TXT_SetTableColumns(joystick_axis->config_window, 2); + TXT_SetColumnWidths(joystick_axis->config_window, 10, 5); + TXT_AddWidgets(joystick_axis->config_window, + TXT_NewCheckBox("Invert", joystick_axis->invert), + TXT_TABLE_EMPTY, + TXT_NewLabel("Dead zone"), + TXT_NewSpinControl(joystick_axis->dead_zone, 10, 90), + NULL); + + TXT_SetWindowAction(joystick_axis->config_window, TXT_HORIZ_LEFT, NULL); + TXT_SetWindowAction( + joystick_axis->config_window, TXT_HORIZ_CENTER, + TXT_NewWindowEscapeAction(joystick_axis->config_window)); + TXT_SetWindowAction(joystick_axis->config_window, TXT_HORIZ_RIGHT, NULL); + TXT_SetWidgetAlign(joystick_axis->config_window, TXT_HORIZ_CENTER); +} + static void TXT_JoystickAxisSizeCalc(TXT_UNCAST_ARG(joystick_axis)) { TXT_CAST_ARG(txt_joystick_axis_t, joystick_axis); @@ -467,6 +491,55 @@ static void TXT_JoystickAxisDrawer(TXT_UNCAST_ARG(joystick_axis)) } } +static void GetAxisDescription(int axis, char *buf, size_t buf_len) +{ + switch (axis) + { + case SDL_CONTROLLER_AXIS_INVALID: + M_StringCopy(buf, "(none)", sizeof(buf)); + break; + + case SDL_CONTROLLER_AXIS_LEFTX: + M_StringCopy(buf, "Left X", sizeof(buf)); + break; + + case SDL_CONTROLLER_AXIS_LEFTY: + M_StringCopy(buf, "Left Y", sizeof(buf)); + break; + + case SDL_CONTROLLER_AXIS_RIGHTX: + M_StringCopy(buf, "Right X", sizeof(buf)); + break; + + case SDL_CONTROLLER_AXIS_RIGHTY: + M_StringCopy(buf, "Right Y", sizeof(buf)); + break; + + default: + M_StringCopy(buf, "(unknown)", sizeof(buf)); + break; + } +} + +static void TXT_GamepadAxisDrawer(TXT_UNCAST_ARG(joystick_axis)) +{ + TXT_CAST_ARG(txt_joystick_axis_t, joystick_axis); + char buf[JOYSTICK_AXIS_WIDTH + 1]; + int i; + + GetAxisDescription(*joystick_axis->axis, buf, sizeof(buf)); + + TXT_SetWidgetBG(joystick_axis); + TXT_FGColor(TXT_COLOR_BRIGHT_WHITE); + + TXT_DrawString(buf); + + for (i = TXT_UTF8_Strlen(buf); i < joystick_axis->widget.w; ++i) + { + TXT_DrawString(" "); + } +} + static void TXT_JoystickAxisDestructor(TXT_UNCAST_ARG(joystick_axis)) { } @@ -489,6 +562,24 @@ static int TXT_JoystickAxisKeyPress(TXT_UNCAST_ARG(joystick_axis), int key) return 0; } +static int TXT_GamepadAxisKeyPress(TXT_UNCAST_ARG(joystick_axis), int key) +{ + TXT_CAST_ARG(txt_joystick_axis_t, joystick_axis); + + if (key == KEY_ENTER) + { + TXT_ConfigureGamepadAxis(joystick_axis, -1, NULL); + return 1; + } + + if (key == KEY_BACKSPACE || key == KEY_DEL) + { + *joystick_axis->axis = -1; + } + + return 0; +} + static void TXT_JoystickAxisMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b) { @@ -502,6 +593,19 @@ static void TXT_JoystickAxisMousePress(TXT_UNCAST_ARG(widget), } } +static void TXT_GamepadAxisMousePress(TXT_UNCAST_ARG(widget), int x, int y, + int b) +{ + TXT_CAST_ARG(txt_joystick_axis_t, widget); + + // Clicking is like pressing enter + + if (b == TXT_MOUSE_LEFT) + { + TXT_GamepadAxisKeyPress(widget, KEY_ENTER); + } +} + txt_widget_class_t txt_joystick_axis_class = { TXT_AlwaysSelectable, @@ -513,16 +617,35 @@ txt_widget_class_t txt_joystick_axis_class = NULL, }; -txt_joystick_axis_t *TXT_NewJoystickAxis(int *axis, int *invert, +txt_widget_class_t txt_gamepad_axis_class = +{ + TXT_AlwaysSelectable, + TXT_JoystickAxisSizeCalc, + TXT_GamepadAxisDrawer, + TXT_GamepadAxisKeyPress, + TXT_JoystickAxisDestructor, + TXT_GamepadAxisMousePress, + NULL, +}; + +txt_joystick_axis_t *TXT_NewJoystickAxis(int *axis, int *invert, int *dead_zone, txt_joystick_axis_direction_t dir) { txt_joystick_axis_t *joystick_axis; joystick_axis = malloc(sizeof(txt_joystick_axis_t)); - TXT_InitWidget(joystick_axis, &txt_joystick_axis_class); + if (use_gamepad) + { + TXT_InitWidget(joystick_axis, &txt_gamepad_axis_class); + } + else + { + TXT_InitWidget(joystick_axis, &txt_joystick_axis_class); + } joystick_axis->axis = axis; joystick_axis->invert = invert; + joystick_axis->dead_zone = dead_zone; joystick_axis->dir = dir; joystick_axis->bad_axis = NULL; diff --git a/src/setup/txt_joyaxis.h b/src/setup/txt_joyaxis.h index 1f41d0e8..f0527c5b 100644 --- a/src/setup/txt_joyaxis.h +++ b/src/setup/txt_joyaxis.h @@ -45,7 +45,7 @@ typedef void (*txt_joystick_axis_callback_t)(void); struct txt_joystick_axis_s { txt_widget_t widget; - int *axis, *invert; + int *axis, *invert, *dead_zone; txt_joystick_axis_direction_t dir; // Only used when configuring: @@ -75,7 +75,7 @@ struct txt_joystick_axis_s txt_joystick_axis_callback_t callback; }; -txt_joystick_axis_t *TXT_NewJoystickAxis(int *axis, int *invert, +txt_joystick_axis_t *TXT_NewJoystickAxis(int *axis, int *invert, int *dead_zone, txt_joystick_axis_direction_t dir); // Configure a joystick axis widget. diff --git a/src/setup/txt_joybinput.c b/src/setup/txt_joybinput.c index 9c248ef6..0cb651ac 100644 --- a/src/setup/txt_joybinput.c +++ b/src/setup/txt_joybinput.c @@ -17,6 +17,7 @@ #include #include "SDL_joystick.h" +#include "SDL_gamecontroller.h" #include "doomkeys.h" #include "joystick.h" @@ -59,6 +60,199 @@ static int *all_joystick_buttons[NUM_VIRTUAL_BUTTONS] = &joybautomap, }; +// For indirection so that we're not dependent on item ordering in the +// SDL_GameControllerButton enum. +static const int gamepad_buttons[GAMEPAD_BUTTON_MAX] = +{ + SDL_CONTROLLER_BUTTON_A, + SDL_CONTROLLER_BUTTON_B, + SDL_CONTROLLER_BUTTON_X, + SDL_CONTROLLER_BUTTON_Y, + SDL_CONTROLLER_BUTTON_BACK, + SDL_CONTROLLER_BUTTON_GUIDE, + SDL_CONTROLLER_BUTTON_START, + SDL_CONTROLLER_BUTTON_LEFTSTICK, + SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_CONTROLLER_BUTTON_MISC1, + SDL_CONTROLLER_BUTTON_PADDLE1, + SDL_CONTROLLER_BUTTON_PADDLE2, + SDL_CONTROLLER_BUTTON_PADDLE3, + SDL_CONTROLLER_BUTTON_PADDLE4, + SDL_CONTROLLER_BUTTON_TOUCHPAD, + GAMEPAD_BUTTON_TRIGGERLEFT, + GAMEPAD_BUTTON_TRIGGERRIGHT, +}; + +// Items in the following button lists are ordered according to gamepad_buttons +// above. +static const char *xbox360_buttons[GAMEPAD_BUTTON_MAX] = +{ + "A", + "B", + "X", + "Y", + "BACK", + "GUIDE", + "START", + "LSB", + "RSB", + "LB", + "RB", + "DPAD U", + "DPAD D", + "DPAD L", + "DPAD R", + "", + "", + "", + "", + "", + "", + "LT", + "RT", +}; + +static const char *xboxone_buttons[GAMEPAD_BUTTON_MAX] = +{ + "A", + "B", + "X", + "Y", + "VIEW", + "XBOX", + "MENU", + "LSB", + "RSB", + "LB", + "RB", + "DPAD U", + "DPAD D", + "DPAD L", + "DPAD R", + "PROFILE", + "P1", + "P2", + "P3", + "P4", + "", + "LT", + "RT", +}; + +static const char *ps3_buttons[GAMEPAD_BUTTON_MAX] = +{ + "X", + "CIRCLE", + "SQUARE", + "TRIANGLE", + "SELECT", + "PS", + "START", + "L3", + "R3", + "L1", + "R1", + "DPAD U", + "DPAD D", + "DPAD L", + "DPAD R", + "", + "", + "", + "", + "", + "", + "L2", + "R2", +}; + +static const char *ps4_buttons[GAMEPAD_BUTTON_MAX] = +{ + "X", + "CIRCLE", + "SQUARE", + "TRIANGLE", + "SHARE", + "PS", + "OPTIONS", + "L3", + "R3", + "L1", + "R1", + "DPAD U", + "DPAD D", + "DPAD L", + "DPAD R", + "", + "", + "", + "", + "", + "TOUCH", + "L2", + "R2", +}; + +static const char *ps5_buttons[GAMEPAD_BUTTON_MAX] = +{ + "X", + "CIRCLE", + "SQUARE", + "TRIANGLE", + "SHARE", + "PS", + "OPTIONS", + "L3", + "R3", + "L1", + "R1", + "DPAD U", + "DPAD D", + "DPAD L", + "DPAD R", + "MUTE", + "", + "", + "", + "", + "TOUCH", + "L2", + "R2", +}; + +static const char *switchpro_buttons[GAMEPAD_BUTTON_MAX] = +{ + "B", + "A", + "Y", + "X", + "MINUS", + "HOME", + "PLUS", + "LSB", + "RSB", + "L", + "R", + "DPAD U", + "DPAD D", + "DPAD L", + "DPAD R", + "CAPTURE", + "", + "", + "", + "", + "", + "ZL", + "ZR", +}; + static int PhysicalForVirtualButton(int vbutton) { if (vbutton < NUM_VIRTUAL_BUTTONS) @@ -104,8 +298,9 @@ static void CanonicalizeButtons(void) // Don't remap the speed key if it's bound to "always run". // Also preserve "unbound" variables. - if ((all_joystick_buttons[i] == &joybspeed && vbutton >= 20) - || vbutton < 0) + if ((all_joystick_buttons[i] == &joybspeed && + vbutton >= MAX_VIRTUAL_BUTTONS) || + vbutton < 0) { new_mapping[i] = i; } @@ -175,6 +370,64 @@ static int EventCallback(SDL_Event *event, TXT_UNCAST_ARG(joystick_input)) return 0; } +static int EventCallbackGamepad(SDL_Event *event, + TXT_UNCAST_ARG(joystick_input)) +{ + TXT_CAST_ARG(txt_joystick_input_t, joystick_input); + + // Got the joystick button press? + + if (event->type == SDL_CONTROLLERBUTTONDOWN || + event->type == SDL_CONTROLLERAXISMOTION) + { + int vbutton, physbutton, axis; + + // Before changing anything, remap button configuration into + // canonical form, to avoid conflicts. + CanonicalizeButtons(); + + vbutton = VirtualButtonForVariable(joystick_input->variable); + axis = event->caxis.axis; + + if (event->type == SDL_CONTROLLERAXISMOTION && + (axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT || + axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) && + event->caxis.value > TRIGGER_THRESHOLD) + { + if (axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) + { + physbutton = GAMEPAD_BUTTON_TRIGGERLEFT; + } + else + { + physbutton = GAMEPAD_BUTTON_TRIGGERRIGHT; + } + } + else if (event->type == SDL_CONTROLLERBUTTONDOWN) + { + physbutton = event->cbutton.button; + } + else + { + return 0; + } + + if (joystick_input->check_conflicts) + { + ClearVariablesUsingButton(physbutton); + } + + // Set mapping. + *joystick_input->variable = vbutton; + joystick_physical_buttons[vbutton] = physbutton; + + TXT_CloseWindow(joystick_input->prompt_window); + return 1; + } + + return 0; +} + // When the prompt window is closed, disable the event callback function; // we are no longer interested in receiving notification of events. @@ -188,6 +441,18 @@ static void PromptWindowClosed(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(joystick)) SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } +static void PromptWindowClosedGamepad(TXT_UNCAST_ARG(widget), + TXT_UNCAST_ARG(joystick)) +{ + TXT_CAST_ARG(SDL_GameController, joystick); + + SDL_GameControllerClose(joystick); + TXT_SDL_SetEventCallback(NULL, NULL); + SDL_JoystickEventState(SDL_DISABLE); + SDL_GameControllerEventState(SDL_DISABLE); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); +} + static void OpenErrorWindow(void) { TXT_MessageBox(NULL, "Please configure a controller first!"); @@ -202,7 +467,7 @@ static void OpenPromptWindow(txt_joystick_input_t *joystick_input) joystick_input->check_conflicts = !TXT_GetModifierState(TXT_MOD_SHIFT); - if (SDL_Init(SDL_INIT_JOYSTICK) < 0) + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) { return; } @@ -228,6 +493,43 @@ static void OpenPromptWindow(txt_joystick_input_t *joystick_input) SDL_JoystickEventState(SDL_ENABLE); } +static void OpenPromptWindowGamepad(txt_joystick_input_t *joystick_input) +{ + txt_window_t *window; + SDL_GameController *gamepad; + + // Silently update when the shift button is held down. + + joystick_input->check_conflicts = !TXT_GetModifierState(TXT_MOD_SHIFT); + + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) + { + return; + } + + // Check the current joystick is valid + + gamepad = SDL_GameControllerOpen(joystick_index); + + if (gamepad == NULL) + { + OpenErrorWindow(); + return; + } + + // Open the prompt window + + window = TXT_MessageBox(NULL, "Press the new button on the controller..."); + + TXT_SDL_SetEventCallback(EventCallbackGamepad, joystick_input); + TXT_SignalConnect(window, "closed", PromptWindowClosedGamepad, gamepad); + joystick_input->prompt_window = window; + + // GameController events do not fire if Joystick events are disabled. + SDL_JoystickEventState(SDL_ENABLE); + SDL_GameControllerEventState(SDL_ENABLE); +} + static void TXT_JoystickInputSizeCalc(TXT_UNCAST_ARG(joystick_input)) { TXT_CAST_ARG(txt_joystick_input_t, joystick_input); @@ -272,6 +574,93 @@ static void TXT_JoystickInputDrawer(TXT_UNCAST_ARG(joystick_input)) } } +static int GetGamepadButtonIndex(int button) +{ + int i; + + for (i = 0; i < arrlen(gamepad_buttons); ++i) + { + if (button == gamepad_buttons[i]) + { + return i; + } + } + + return -1; +} + +static void GetGamepadButtonDescription(int vbutton, char *buf, size_t buf_len) +{ + int index; + + index = GetGamepadButtonIndex(PhysicalForVirtualButton(vbutton)); + + if (index < 0) + { + M_StringCopy(buf, "(unknown)", buf_len); + return; + } + + switch (gamepad_type) + { + case SDL_CONTROLLER_TYPE_XBOX360: + M_snprintf(buf, buf_len, "%s", xbox360_buttons[index]); + break; + + case SDL_CONTROLLER_TYPE_XBOXONE: + M_snprintf(buf, buf_len, "%s", xboxone_buttons[index]); + break; + + case SDL_CONTROLLER_TYPE_PS3: + M_snprintf(buf, buf_len, "%s", ps3_buttons[index]); + break; + + case SDL_CONTROLLER_TYPE_PS4: + M_snprintf(buf, buf_len, "%s", ps4_buttons[index]); + break; + + case SDL_CONTROLLER_TYPE_PS5: + M_snprintf(buf, buf_len, "%s", ps5_buttons[index]); + break; + + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: + M_snprintf(buf, buf_len, "%s", switchpro_buttons[index]); + break; + + default: + M_snprintf(buf, buf_len, "BUTTON #%i", + PhysicalForVirtualButton(vbutton) + 1); + break; + } +} + +static void TXT_GamepadInputDrawer(TXT_UNCAST_ARG(joystick_input)) +{ + TXT_CAST_ARG(txt_joystick_input_t, joystick_input); + char buf[20]; // Need to fit "BUTTON #XX" + int i; + + if (*joystick_input->variable < 0) + { + M_StringCopy(buf, "(none)", sizeof(buf)); + } + else + { + GetGamepadButtonDescription(*joystick_input->variable, buf, + sizeof(buf)); + } + + TXT_SetWidgetBG(joystick_input); + TXT_FGColor(TXT_COLOR_BRIGHT_WHITE); + + TXT_DrawString(buf); + + for (i = TXT_UTF8_Strlen(buf); i < JOYSTICK_INPUT_WIDTH; ++i) + { + TXT_DrawString(" "); + } +} + static void TXT_JoystickInputDestructor(TXT_UNCAST_ARG(joystick_input)) { } @@ -283,7 +672,6 @@ static int TXT_JoystickInputKeyPress(TXT_UNCAST_ARG(joystick_input), int key) if (key == KEY_ENTER) { // Open a window to prompt for the new joystick press - OpenPromptWindow(joystick_input); return 1; @@ -297,6 +685,26 @@ static int TXT_JoystickInputKeyPress(TXT_UNCAST_ARG(joystick_input), int key) return 0; } +static int TXT_GamepadInputKeyPress(TXT_UNCAST_ARG(joystick_input), int key) +{ + TXT_CAST_ARG(txt_joystick_input_t, joystick_input); + + if (key == KEY_ENTER) + { + // Open a window to prompt for the new joystick press + OpenPromptWindowGamepad(joystick_input); + + return 1; + } + + if (key == KEY_BACKSPACE || key == KEY_DEL) + { + *joystick_input->variable = -1; + } + + return 0; +} + static void TXT_JoystickInputMousePress(TXT_UNCAST_ARG(widget), int x, int y, int b) { @@ -310,6 +718,19 @@ static void TXT_JoystickInputMousePress(TXT_UNCAST_ARG(widget), } } +static void TXT_GamepadInputMousePress(TXT_UNCAST_ARG(widget), int x, int y, + int b) +{ + TXT_CAST_ARG(txt_joystick_input_t, widget); + + // Clicking is like pressing enter + + if (b == TXT_MOUSE_LEFT) + { + TXT_GamepadInputKeyPress(widget, KEY_ENTER); + } +} + txt_widget_class_t txt_joystick_input_class = { TXT_AlwaysSelectable, @@ -321,13 +742,31 @@ txt_widget_class_t txt_joystick_input_class = NULL, }; +txt_widget_class_t txt_gamepad_input_class = +{ + TXT_AlwaysSelectable, + TXT_JoystickInputSizeCalc, + TXT_GamepadInputDrawer, + TXT_GamepadInputKeyPress, + TXT_JoystickInputDestructor, + TXT_GamepadInputMousePress, + NULL, +}; + txt_joystick_input_t *TXT_NewJoystickInput(int *variable) { txt_joystick_input_t *joystick_input; joystick_input = malloc(sizeof(txt_joystick_input_t)); - TXT_InitWidget(joystick_input, &txt_joystick_input_class); + if (use_gamepad) + { + TXT_InitWidget(joystick_input, &txt_gamepad_input_class); + } + else + { + TXT_InitWidget(joystick_input, &txt_joystick_input_class); + } joystick_input->variable = variable; return joystick_input;