foxygit / doom Log in
commit 206603577aa81b4184a36ae38d5cc1307d6971ac
Author:     Simon Howard <fraggle@soulsphere.org>
AuthorDate: Sun May 8 17:06:09 2016 -0400
Commit:     Simon Howard <fraggle@soulsphere.org>
CommitDate: Sun May 8 17:06:09 2016 -0400

    joystick: Use SDL GUID to identify joysticks.

    The current configuration system uses joystick_index to identify
    the joystick which is configured. This has the downside of assuming
    a static configuration; joystick indexes can change if devices are
    plugged or unplugged.

    SDL2 introduces the idea of Joystick GUIDs which can uniquely
    identify a class of device, so use this as the primary configuration
    variable instead; that way, if the number or ordering of joystick
    devices changes, we will still use the same device. As GUID can only
    identify a "class" of device (eg. "Xbox controller"), we still keep
    joystick_index around to try to differentiate between devices when
    there are multiple identical devices connected.
---
 src/i_joystick.c     |  61 +++++++++++++++++++-----
 src/m_config.c       |  12 ++++-
 src/setup/joystick.c | 129 +++++++++++++++++++++++++++++++++++++--------------
 3 files changed, 155 insertions(+), 47 deletions(-)

diff --git a/src/i_joystick.c b/src/i_joystick.c
index b7164275..3e4c7de2 100644
--- a/src/i_joystick.c
+++ b/src/i_joystick.c
@@ -44,8 +44,8 @@ static SDL_Joystick *joystick = NULL;

 static int usejoystick = 0;

-// Joystick to use, as an SDL joystick index:
-
+// SDL GUID and index of the joystick to use.
+static char *joystick_guid = "";
 static int joystick_index = -1;

 // Which joystick axis to use for horizontal movement, and whether to
@@ -105,9 +105,45 @@ static boolean IsValidAxis(int axis)
     return axis < num_axes;
 }

+static int DeviceIndex(void)
+{
+    SDL_JoystickGUID guid, dev_guid;
+    int i;
+
+    guid = SDL_JoystickGetGUIDFromString(joystick_guid);
+
+    // GUID identifies a class of device rather than a specific device.
+    // Check if joystick_index has the expected GUID, as this can act
+    // as a tie-breaker in case there are multiple identical devices.
+    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;
+        }
+    }
+
+    // Check all devices to look for one with the expected GUID.
+    for (i = 0; i < SDL_NumJoysticks(); ++i)
+    {
+        dev_guid = SDL_JoystickGetDeviceGUID(i);
+        if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID)))
+        {
+            printf("I_InitJoystick: Joystick moved to index %d.\n", i);
+            return i;
+        }
+    }
+
+    // No joystick found with the expected GUID.
+    return -1;
+}
+
 void I_InitJoystick(void)
 {
-    if (!usejoystick || joystick_index < 0)
+    int index;
+
+    if (!usejoystick || !strcmp(joystick_guid, ""))
     {
         return;
     }
@@ -117,21 +153,24 @@ void I_InitJoystick(void)
         return;
     }

-    if (joystick_index >= SDL_NumJoysticks())
+    index = DeviceIndex();
+
+    if (index < 0)
     {
-        printf("I_InitJoystick: Invalid joystick ID: %i\n", joystick_index);
+        printf("I_InitJoystick: Couldn't find joystick with GUID \"%s\": "
+               "device not found or not connected?\n",
+               joystick_guid);
         SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
         return;
     }

     // Open the joystick

-    joystick = SDL_JoystickOpen(joystick_index);
+    joystick = SDL_JoystickOpen(index);

     if (joystick == NULL)
     {
-        printf("I_InitJoystick: Failed to open joystick #%i\n",
-               joystick_index);
+        printf("I_InitJoystick: Failed to open joystick #%i\n", index);
         SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
         return;
     }
@@ -140,9 +179,8 @@ void I_InitJoystick(void)
      || !IsValidAxis(joystick_y_axis)
      || !IsValidAxis(joystick_strafe_axis))
     {
-        printf("I_InitJoystick: Invalid joystick axis for joystick #%i "
-               "(run joystick setup again)\n",
-               joystick_index);
+        printf("I_InitJoystick: Invalid joystick axis for configured joystick "
+               "(run joystick setup again)\n");

         SDL_JoystickClose(joystick);
         joystick = NULL;
@@ -329,6 +367,7 @@ void I_BindJoystickVariables(void)
     int i;

     M_BindIntVariable("use_joystick",          &usejoystick);
+    M_BindStringVariable("joystick_guid",      &joystick_guid);
     M_BindIntVariable("joystick_index",        &joystick_index);
     M_BindIntVariable("joystick_x_axis",       &joystick_x_axis);
     M_BindIntVariable("joystick_y_axis",       &joystick_y_axis);
diff --git a/src/m_config.c b/src/m_config.c
index e66e5867..690aceb1 100644
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -911,8 +911,16 @@ static default_t extra_defaults_list[] =
 #endif

     //!
-    // Joystick number to use; '0' is the first joystick.  A negative
-    // value ('-1') indicates that no joystick is configured.
+    // SDL GUID string indicating the joystick to use. An empty string
+    // indicates that no joystick is configured.
+    //
+
+    CONFIG_VARIABLE_STRING(joystick_guid),
+
+    //!
+    // Index of SDL joystick to use; this is only used in the case where
+    // multiple identical joystick devices are connected which have the
+    // same GUID, to distinguish between devices.
     //

     CONFIG_VARIABLE_INT(joystick_index),
diff --git a/src/setup/joystick.c b/src/setup/joystick.c
index f2d63e80..2c94a237 100644
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -52,8 +52,9 @@ static int joystick_initted = 0;

 static int usejoystick = 0;

-// Joystick to use, as an SDL joystick index:
+// GUID and index of joystick to use.

+char *joystick_guid = "";
 int joystick_index = -1;

 // Calibration button. This is the button the user pressed at the
@@ -94,6 +95,7 @@ static txt_joystick_axis_t *y_axis_widget;

 static txt_window_t *calibration_window;
 static SDL_Joystick **all_joysticks = NULL;
+static int all_joysticks_len = 0;

 // Known controllers.
 // There are lots of game controllers on the market. Try to configure
@@ -529,25 +531,71 @@ static void UnInitJoystick(void)
     }
 }

-// Set the label showing the name of the currently selected joystick
-
-static void SetJoystickButtonLabel(void)
+// We identify joysticks using GUID where possible, but joystick_index
+// is used to distinguish between different devices. As the index can
+// change, UpdateJoystickIndex() checks to see if it is still valid and
+// updates it as appropriate.
+static void UpdateJoystickIndex(void)
 {
-    char *name;
+    SDL_JoystickGUID guid, dev_guid;
+    int i;

-    InitJoystick();
+    guid = SDL_JoystickGetGUIDFromString(joystick_guid);

-    name = "None set";
+    // Is joystick_index already correct?
+    if (joystick_index >= 0 && joystick_index < SDL_NumJoysticks())
+    {
+        dev_guid = SDL_JoystickGetDeviceGUID(joystick_index);
+        if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID)))
+        {
+            return;
+        }
+    }

-    if (joystick_initted
-     && joystick_index >= 0 && joystick_index < SDL_NumJoysticks())
+    // If index is not correct, look for the first device with the
+    // expected GUID. It may have moved to a different index.
+    for (i = 0; i < SDL_NumJoysticks(); ++i)
     {
-        name = (char *) SDL_JoystickNameForIndex(joystick_index);
+        dev_guid = SDL_JoystickGetDeviceGUID(i);
+        if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID)))
+        {
+            joystick_index = i;
+            return;
+        }
     }

-    TXT_SetButtonLabel(joystick_button, name);
+    // Not found; it's possible the device is disconnected. Do not
+    // reset joystick_guid or joystick_index in case they are
+    // reconnected later.
+}

-    UnInitJoystick();
+// Set the label showing the name of the currently selected joystick
+static void SetJoystickButtonLabel(void)
+{
+    SDL_JoystickGUID guid, dev_guid;
+    const char *name;
+
+    if (!usejoystick || !strcmp(joystick_guid, ""))
+    {
+        name = "None set";
+    }
+    else
+    {
+        name = "Not found (device disconnected?)";
+
+        // Use the device name if the GUID and index match.
+        if (joystick_index >= 0 && joystick_index < SDL_NumJoysticks())
+        {
+            guid = SDL_JoystickGetGUIDFromString(joystick_guid);
+            dev_guid = SDL_JoystickGetDeviceGUID(joystick_index);
+            if (!memcmp(&guid, &dev_guid, sizeof(SDL_JoystickGUID)))
+            {
+                name = SDL_JoystickNameForIndex(joystick_index);
+            }
+        }
+    }
+
+    TXT_SetButtonLabel(joystick_button, (char *) name);
 }

 // Try to open all joysticks visible to SDL.
@@ -555,19 +603,18 @@ static void SetJoystickButtonLabel(void)
 static int OpenAllJoysticks(void)
 {
     int i;
-    int num_joysticks;
     int result;

     InitJoystick();

     // SDL_JoystickOpen() all joysticks.

-    num_joysticks = SDL_NumJoysticks();
-    all_joysticks = calloc(num_joysticks, sizeof(SDL_Joystick *));
+    all_joysticks_len = SDL_NumJoysticks();
+    all_joysticks = calloc(all_joysticks_len, sizeof(SDL_Joystick *));

     result = 0;

-    for (i = 0; i < num_joysticks; ++i)
+    for (i = 0; i < all_joysticks_len; ++i)
     {
         all_joysticks[i] = SDL_JoystickOpen(i);

@@ -599,11 +646,8 @@ static int OpenAllJoysticks(void)
 static void CloseAllJoysticks(void)
 {
     int i;
-    int num_joysticks;
-
-    num_joysticks = SDL_NumJoysticks();

-    for (i = 0; i < num_joysticks; ++i)
+    for (i = 0; i < all_joysticks_len; ++i)
     {
         if (all_joysticks[i] != NULL)
         {
@@ -624,20 +668,34 @@ static void CalibrateXAxis(void)
     TXT_ConfigureJoystickAxis(x_axis_widget, calibrate_button, NULL);
 }

-// TODO: Remove once we no longer use joystick_index in .cfg files.
-static int JoystickIDToIndex(int joy_id)
+// Given the SDL_JoystickID instance ID from a button event, set the
+// joystick_guid and joystick_index config variables.
+static boolean SetJoystickGUID(SDL_JoystickID joy_id)
 {
-    SDL_Joystick *joystick = SDL_JoystickFromInstanceID(joy_id);
+    SDL_Joystick *joystick;
+    SDL_JoystickGUID guid;
     int i;

-    for (i = 0; i < SDL_NumJoysticks(); ++i)
+    joystick = SDL_JoystickFromInstanceID(joy_id);
+    if (joystick == NULL)
+    {
+        return false;
+    }
+
+    guid = SDL_JoystickGetGUID(joystick);
+    joystick_guid = malloc(33);
+    SDL_JoystickGetGUIDString(guid, joystick_guid, 33);
+
+    for (i = 0; i < all_joysticks_len; ++i)
     {
-        if (joystick == all_joysticks[i])
+        if (all_joysticks[i] == joystick)
         {
-            return i;
+            joystick_index = i;
+            return true;
         }
     }
-    return -1;
+
+    return false;
 }

 static int CalibrationEventCallback(SDL_Event *event, void *user_data)
@@ -647,18 +705,17 @@ static int CalibrationEventCallback(SDL_Event *event, void *user_data)
         return 0;
     }

+    if (!SetJoystickGUID(event->jbutton.which))
+    {
+        return 0;
+    }
+
     // 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;
-    joystick_index = JoystickIDToIndex(event->jbutton.which);
     calibrate_button = event->jbutton.button;

-    if (joystick_index < 0)
-    {
-        return 0;
-    }
-
     // If the joystick is a known one, auto-load default
     // config for it. Otherwise, proceed with calibration.
     if (IsKnownJoystick(joystick_index))
@@ -693,9 +750,9 @@ static void NoJoystick(void)

 static void CalibrateWindowClosed(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
 {
-    CloseAllJoysticks();
     TXT_SDL_SetEventCallback(NULL, NULL);
     SetJoystickButtonLabel();
+    CloseAllJoysticks();
 }

 static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
@@ -826,7 +883,10 @@ void ConfigJoystick(void)
     TXT_SignalConnect(joystick_button, "pressed", CalibrateJoystick, NULL);
     TXT_SetWindowAction(window, TXT_HORIZ_CENTER, TestConfigAction());

+    InitJoystick();
+    UpdateJoystickIndex();
     SetJoystickButtonLabel();
+    UnInitJoystick();
 }

 void BindJoystickVariables(void)
@@ -834,6 +894,7 @@ void BindJoystickVariables(void)
     int i;

     M_BindIntVariable("use_joystick",           &usejoystick);
+    M_BindStringVariable("joystick_guid",       &joystick_guid);
     M_BindIntVariable("joystick_index",         &joystick_index);
     M_BindIntVariable("joystick_x_axis",        &joystick_x_axis);
     M_BindIntVariable("joystick_y_axis",        &joystick_y_axis);