shithub: choc

Download patch

ref: 206603577aa81b4184a36ae38d5cc1307d6971ac
parent: 79d38c7d1ef051aad898eb45e965f3660bffc763
author: Simon Howard <fraggle@soulsphere.org>
date: Sun May 8 13:06:09 EDT 2016

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.

--- a/src/i_joystick.c
+++ b/src/i_joystick.c
@@ -44,8 +44,8 @@
 
 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 @@
     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,9 +153,13 @@
         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;
     }
@@ -126,12 +166,11 @@
 
     // 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 @@
      || !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 @@
     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);
--- a/src/m_config.c
+++ b/src/m_config.c
@@ -911,8 +911,16 @@
 #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),
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -52,8 +52,9 @@
 
 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_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 @@
     }
 }
 
-// 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,7 +603,6 @@
 static int OpenAllJoysticks(void)
 {
     int i;
-    int num_joysticks;
     int result;
 
     InitJoystick();
@@ -562,12 +609,12 @@
 
     // 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 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 @@
     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)
     {
-        if (joystick == all_joysticks[i])
+        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 (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 @@
         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 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 @@
     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 @@
     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);