foxygit / doom Log in
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;
         }