commit 6ee220fe922caade74d2d8ed5794b2f9aa5b3a11
Author: Michael Day <contact@michaelcday.com>
AuthorDate: Sun Jul 30 23:37:03 2023 -0400
Commit: Turo Lamminen <turol@users.noreply.github.com>
CommitDate: Wed Aug 23 11:42:50 2023 +0300
Add support for SDL_GameController interface
Minimal functional implementation. When setup finds that a controller is
supported by the SDL_GameController interface, load a default mapping
for that gamepad based on either the Unity Doom or SNES control scheme.
(Depending of the capabilities of the attached gamepad.)
---
src/i_joystick.c | 261 +++++++++++++++++++++++++++++++++++++++++++++-
src/i_joystick.h | 22 ++++
src/m_config.c | 7 ++
src/setup/joystick.c | 108 ++++++++++++++++++-
src/setup/joystick.h | 1 +
src/setup/txt_joybinput.c | 5 +-
6 files changed, 393 insertions(+), 11 deletions(-)
diff --git a/src/i_joystick.c b/src/i_joystick.c
index d29c2fe6..eb471750 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 <stdlib.h>
#include <stdio.h>
@@ -36,6 +37,7 @@
#define DEAD_ZONE (32768 / 3)
+static SDL_GameController *gamepad = NULL;
static SDL_Joystick *joystick = NULL;
// Configuration variables:
@@ -44,6 +46,9 @@ static SDL_Joystick *joystick = NULL;
static int usejoystick = 0;
+// Use SDL_gamecontroller interface for the selected device
+static int use_gamepad = 0;
+
// SDL GUID and index of the joystick to use.
static char *joystick_guid = "";
static int joystick_index = -1;
@@ -76,6 +81,240 @@ 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_Init(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;
+
+ 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 result;
+
+ // Axis -1 means disabled.
+
+ if (axis < 0)
+ {
+ return 0;
+ }
+
+ 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);
+ ev.data3 = GetAxisStateGamepad(joystick_y_axis, joystick_y_invert);
+ ev.data4 =
+ GetAxisStateGamepad(joystick_strafe_axis, joystick_strafe_invert);
+ ev.data5 =
+ GetAxisStateGamepad(joystick_look_axis, joystick_look_invert);
+
+ D_PostEvent(&ev);
+ }
+}
+
void I_ShutdownJoystick(void)
{
if (joystick != NULL)
@@ -153,6 +392,12 @@ void I_InitJoystick(void)
return;
}
+ if (use_gamepad)
+ {
+ I_InitGamepad();
+ return;
+ }
+
if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
{
return;
@@ -175,7 +420,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 +516,9 @@ static int ReadButtonState(int vbutton)
static int GetButtonsState(void)
{
int i;
- int result;
-
- result = 0;
+ int result = 0;
- for (i = 0; i < 20; ++i)
+ for (i = 0; i < MAX_VIRTUAL_BUTTONS; ++i)
{
if (ReadButtonState(i))
{
@@ -362,6 +606,12 @@ static int GetAxisState(int axis, int invert)
void I_UpdateJoystick(void)
{
+ if (use_gamepad)
+ {
+ I_UpdateGamepad();
+ return;
+ }
+
if (joystick != NULL)
{
event_t ev;
@@ -382,6 +632,7 @@ void I_BindJoystickVariables(void)
int i;
M_BindIntVariable("use_joystick", &usejoystick);
+ M_BindIntVariable("use_gamepad", &use_gamepad);
M_BindStringVariable("joystick_guid", &joystick_guid);
M_BindIntVariable("joystick_index", &joystick_index);
M_BindIntVariable("joystick_x_axis", &joystick_x_axis);
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 79dcfe88..c38d5989 100644
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -1369,6 +1369,13 @@ 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),
+
//!
// Joystick virtual button to make the player strafe left.
//
diff --git a/src/setup/joystick.c b/src/setup/joystick.c
index d140c58b..86279925 100644
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -596,6 +596,60 @@ static const known_joystick_t known_joysticks[] =
},
};
+// Use SDL_GameController interface
+int use_gamepad = 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 +719,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;
}
@@ -867,9 +922,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 +934,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,6 +976,16 @@ static int CalibrationEventCallback(SDL_Event *event, void *user_data)
return 0;
}
+ if (SDL_IsGameController(joystick_index))
+ {
+ usejoystick = 1;
+ use_gamepad = 1;
+ 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.
@@ -923,6 +1020,7 @@ static void NoJoystick(void)
"some drivers or otherwise configure it.");
usejoystick = 0;
+ use_gamepad = 0;
joystick_index = -1;
SetJoystickButtonLabel();
}
@@ -965,6 +1063,7 @@ static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
// Start calibration
usejoystick = 0;
+ use_gamepad = 0;
joystick_index = -1;
}
@@ -1061,7 +1160,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);
}
@@ -1089,6 +1188,7 @@ void BindJoystickVariables(void)
int i;
M_BindIntVariable("use_joystick", &usejoystick);
+ M_BindIntVariable("use_gamepad", &use_gamepad);
M_BindStringVariable("joystick_guid", &joystick_guid);
M_BindIntVariable("joystick_index", &joystick_index);
M_BindIntVariable("joystick_x_axis", &joystick_x_axis);
diff --git a/src/setup/joystick.h b/src/setup/joystick.h
index 57d4664b..3a9d1705 100644
--- a/src/setup/joystick.h
+++ b/src/setup/joystick.h
@@ -21,6 +21,7 @@
extern int joystick_index;
extern int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS];
+extern int use_gamepad;
void ConfigJoystick(void *widget, void *user_data);
diff --git a/src/setup/txt_joybinput.c b/src/setup/txt_joybinput.c
index 9c248ef6..fc0c7c39 100644
--- a/src/setup/txt_joybinput.c
+++ b/src/setup/txt_joybinput.c
@@ -104,8 +104,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;
}