Wired up haptic hotplugging for Windows DirectInput/XInput code.
authorRyan C. Gordon <icculus@icculus.org>
Thu, 06 Feb 2014 07:37:20 -0500
changeset 8179 48c90b62af13
parent 8178 d74974cacb9b
child 8180 16afd5b764b3
Wired up haptic hotplugging for Windows DirectInput/XInput code.
src/haptic/windows/SDL_syshaptic.c
src/haptic/windows/SDL_syshaptic_c.h
src/joystick/windows/SDL_dxjoystick.c
--- a/src/haptic/windows/SDL_syshaptic.c	Wed Feb 05 20:07:25 2014 -0500
+++ b/src/haptic/windows/SDL_syshaptic.c	Thu Feb 06 07:37:20 2014 -0500
@@ -33,12 +33,12 @@
 #include "../../joystick/SDL_sysjoystick.h"     /* For the real SDL_Joystick */
 #include "../../joystick/windows/SDL_dxjoystick_c.h"      /* For joystick hwdata */
 
-#define MAX_HAPTICS  32
+#include "SDL_syshaptic_c.h"
 
 /*
  * List of available haptic devices.
  */
-static struct
+typedef struct SDL_hapticlist_item
 {
     DIDEVICEINSTANCE instance;
     char *name;
@@ -46,7 +46,8 @@
     DIDEVCAPS capabilities;
     Uint8 bXInputHaptic; /* Supports force feedback via XInput. */
     Uint8 userid; /* XInput userid index for this joystick */
-} SDL_hapticlist[MAX_HAPTICS];
+    struct SDL_hapticlist_item *next;
+} SDL_hapticlist_item;
 
 
 /*
@@ -83,7 +84,9 @@
 static SDL_bool coinitialized = SDL_FALSE;
 static LPDIRECTINPUT8 dinput = NULL;
 static SDL_bool loaded_xinput = SDL_FALSE;
-
+static SDL_hapticlist_item *SDL_hapticlist = NULL;
+static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;
+static int numhaptics = 0;
 
 /*
  * External stuff.
@@ -101,7 +104,7 @@
 static int SDL_SYS_HapticOpenFromDevice8(SDL_Haptic * haptic,
                                          LPDIRECTINPUTDEVICE8 device8,
                                          SDL_bool is_joystick);
-static int SDL_SYS_HapticOpenFromXInput(SDL_Haptic * haptic, Uint8 userid);
+static int SDL_SYS_HapticOpenFromXInput(SDL_Haptic * haptic, const Uint8 userid);
 static DWORD DIGetTriggerButton(Uint16 button);
 static int SDL_SYS_SetDirection(DIEFFECT * effect, SDL_HapticDirection * dir,
                                 int naxes);
@@ -155,11 +158,6 @@
         return SDL_SetError("Haptic: SubSystem already open.");
     }
 
-    /* Clear all the memory. */
-    SDL_memset(SDL_hapticlist, 0, sizeof(SDL_hapticlist));
-
-    SDL_numhaptics = 0;
-
     ret = WIN_CoInitialize();
     if (FAILED(ret)) {
         return DI_SetError("Coinitialize", ret);
@@ -205,81 +203,260 @@
 
     if (loaded_xinput) {
         DWORD i;
-        const SDL_bool bIs14OrLater = (SDL_XInputVersion >= ((1<<16)|4));
+        for (i = 0; i < SDL_XINPUT_MAX_DEVICES; i++) {
+            XInputHaptic_MaybeAddDevice(i);
+        }
+    }
+
+    return numhaptics;
+}
+
 
-        for (i = 0; (i < SDL_XINPUT_MAX_DEVICES) && (SDL_numhaptics < MAX_HAPTICS); i++) {
-            XINPUT_CAPABILITIES caps;
-            if (XINPUTGETCAPABILITIES(i, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) {
-                if ((!bIs14OrLater) || (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED)) {
-                    /* !!! FIXME: I'm not bothering to query for a real name right now. */
-                    char buf[64];
-                    SDL_snprintf(buf, sizeof (buf), "XInput Controller #%u", i+1);
-                    SDL_hapticlist[SDL_numhaptics].name = SDL_strdup(buf);
-                    SDL_hapticlist[SDL_numhaptics].bXInputHaptic = 1;
-                    SDL_hapticlist[SDL_numhaptics].userid = (Uint8) i;
-                    SDL_numhaptics++;
-                }
-            }
+int
+DirectInputHaptic_MaybeAddDevice(const DIDEVICEINSTANCE * pdidInstance)
+{
+    HRESULT ret;
+    LPDIRECTINPUTDEVICE8 device;
+    const DWORD needflags = DIDC_ATTACHED | DIDC_FORCEFEEDBACK;
+    DIDEVCAPS capabilities;
+    SDL_hapticlist_item *item = NULL;
+
+    /* Make sure we don't already have it */
+    for (item = SDL_hapticlist; item; item = item->next) {
+        if ( (!item->bXInputHaptic) && (SDL_memcmp(&item->instance, pdidInstance, sizeof (*pdidInstance)) == 0) ) {
+            return -1;  /* Already added */
         }
     }
 
-    return SDL_numhaptics;
+    /* Open the device */
+    ret = IDirectInput8_CreateDevice(dinput, &pdidInstance->guidInstance, &device, NULL);
+    if (FAILED(ret)) {
+        /* DI_SetError("Creating DirectInput device",ret); */
+        return -1;
+    }
+
+    /* Get capabilities. */
+    SDL_zero(capabilities);
+    capabilities.dwSize = sizeof (DIDEVCAPS);
+    ret = IDirectInputDevice8_GetCapabilities(device, &capabilities);
+    IDirectInputDevice8_Release(device);
+    if (FAILED(ret)) {
+        /* DI_SetError("Getting device capabilities",ret); */
+        return -1;
+    }
+
+    if ((capabilities.dwFlags & needflags) != needflags) {
+        return -1;  /* not a device we can use. */
+    }
+
+    item = (SDL_hapticlist_item *)SDL_malloc( sizeof(SDL_hapticlist_item));
+    if (item == NULL) {
+        return SDL_OutOfMemory();
+    }
+
+    SDL_zerop(item);
+
+    item->name = WIN_StringToUTF8(pdidInstance->tszProductName);
+    if (!item->name) {
+        SDL_free(item);
+        return -1;
+    }
+
+    /* Copy the instance over, useful for creating devices. */
+    SDL_memcpy(&item->instance, pdidInstance, sizeof (DIDEVICEINSTANCE));
+    SDL_memcpy(&item->capabilities, &capabilities, sizeof (capabilities));
+
+    if (SDL_hapticlist_tail == NULL) {
+        SDL_hapticlist = SDL_hapticlist_tail = item;
+    } else {
+        SDL_hapticlist_tail->next = item;
+        SDL_hapticlist_tail = item;
+    }
+
+    /* Device has been added. */
+    ++numhaptics;
+
+    return numhaptics;
+}
+
+
+int
+DirectInputHaptic_MaybeRemoveDevice(const DIDEVICEINSTANCE * pdidInstance)
+{
+    SDL_hapticlist_item *item;
+    SDL_hapticlist_item *prev = NULL;
+
+    for (item = SDL_hapticlist; item != NULL; item = item->next) {
+        if ( (!item->bXInputHaptic) && (SDL_memcmp(&item->instance, pdidInstance, sizeof (*pdidInstance)) == 0) ) {
+            /* found it, remove it. */
+            const int retval = item->haptic ? item->haptic->index : -1;
+            if (prev != NULL) {
+                prev->next = item->next;
+            } else {
+                SDL_assert(SDL_hapticlist == item);
+                SDL_hapticlist = item->next;
+            }
+            if (item == SDL_hapticlist_tail) {
+                SDL_hapticlist_tail = prev;
+            }
+            --numhaptics;
+            /* !!! TODO: Send a haptic remove event? */
+            SDL_free(item);
+            return retval;
+        }
+        prev = item;
+    }
+
+    return -1;
 }
 
+
+int
+XInputHaptic_MaybeAddDevice(const DWORD dwUserid)
+{
+    const Uint8 userid = (Uint8) dwUserid;
+    XINPUT_CAPABILITIES caps;
+    const SDL_bool bIs14OrLater = (SDL_XInputVersion >= ((1<<16)|4));
+    SDL_hapticlist_item *item;
+
+    if ((!loaded_xinput) || (dwUserid >= SDL_XINPUT_MAX_DEVICES)) {
+        return -1;
+    }
+
+    /* Make sure we don't already have it */
+    for (item = SDL_hapticlist; item; item = item->next) {
+        if ((item->bXInputHaptic) && (item->userid == userid)) {
+            return -1;  /* Already added */
+        }
+    }
+
+    if (XINPUTGETCAPABILITIES(dwUserid, XINPUT_FLAG_GAMEPAD, &caps) != ERROR_SUCCESS) {
+        return -1;  /* maybe controller isn't plugged in. */
+    }
+
+    /* XInput < 1.4 is probably only for original XBox360 controllers,
+        which don't offer the flag, and always have force feedback */
+    if ( (bIs14OrLater) && ((caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) == 0) ) {
+        return -1;  /* no force feedback on this device. */
+    }
+
+    item = (SDL_hapticlist_item *)SDL_malloc( sizeof(SDL_hapticlist_item));
+    if (item == NULL) {
+        return SDL_OutOfMemory();
+    }
+
+    SDL_zerop(item);
+
+    /* !!! FIXME: I'm not bothering to query for a real name right now (can we even?) */
+    {
+        char buf[64];
+        SDL_snprintf(buf, sizeof (buf), "XInput Controller #%u", (unsigned int) (userid+1));
+        item->name = SDL_strdup(buf);
+    }
+
+    if (!item->name) {
+        SDL_free(item);
+        return -1;
+    }
+
+    /* Copy the instance over, useful for creating devices. */
+    item->bXInputHaptic = 1;
+    item->userid = userid;
+
+    if (SDL_hapticlist_tail == NULL) {
+        SDL_hapticlist = SDL_hapticlist_tail = item;
+    } else {
+        SDL_hapticlist_tail->next = item;
+        SDL_hapticlist_tail = item;
+    }
+
+    /* Device has been added. */
+    ++numhaptics;
+
+    return numhaptics;
+}
+
+
+int
+XInputHaptic_MaybeRemoveDevice(const DWORD dwUserid)
+{
+    const Uint8 userid = (Uint8) dwUserid;
+    SDL_hapticlist_item *item;
+    SDL_hapticlist_item *prev = NULL;
+
+    if ((!loaded_xinput) || (dwUserid >= SDL_XINPUT_MAX_DEVICES)) {
+        return -1;
+    }
+
+    for (item = SDL_hapticlist; item != NULL; item = item->next) {
+        if ((item->bXInputHaptic) && (item->userid == userid)) {
+            /* found it, remove it. */
+            const int retval = item->haptic ? item->haptic->index : -1;
+            if (prev != NULL) {
+                prev->next = item->next;
+            } else {
+                SDL_assert(SDL_hapticlist == item);
+                SDL_hapticlist = item->next;
+            }
+            if (item == SDL_hapticlist_tail) {
+                SDL_hapticlist_tail = prev;
+            }
+            --numhaptics;
+            /* !!! TODO: Send a haptic remove event? */
+            SDL_free(item);
+            return retval;
+        }
+        prev = item;
+    }
+
+    return -1;
+}
+
+
 /*
  * Callback to find the haptic devices.
  */
 static BOOL CALLBACK
 EnumHapticsCallback(const DIDEVICEINSTANCE * pdidInstance, VOID * pContext)
 {
-    HRESULT ret;
-    LPDIRECTINPUTDEVICE8 device;
+    (void) pContext;
+    DirectInputHaptic_MaybeAddDevice(pdidInstance);
+    return DIENUM_CONTINUE;  /* continue enumerating */
+}
 
-    /* Copy the instance over, useful for creating devices. */
-    SDL_memcpy(&SDL_hapticlist[SDL_numhaptics].instance, pdidInstance,
-               sizeof(DIDEVICEINSTANCE));
 
-    /* Open the device */
-    ret = IDirectInput8_CreateDevice(dinput, &pdidInstance->guidInstance,
-                                    &device, NULL);
-    if (FAILED(ret)) {
-        /* DI_SetError("Creating DirectInput device",ret); */
-        return DIENUM_CONTINUE;
+int
+SDL_SYS_NumHaptics()
+{
+    return numhaptics;
+}
+
+static SDL_hapticlist_item *
+HapticByDevIndex(int device_index)
+{
+    SDL_hapticlist_item *item = SDL_hapticlist;
+
+    if ((device_index < 0) || (device_index >= numhaptics)) {
+        return NULL;
     }
 
-    /* Get capabilities. */
-    SDL_hapticlist[SDL_numhaptics].capabilities.dwSize = sizeof(DIDEVCAPS);
-    ret = IDirectInputDevice8_GetCapabilities(device,
-                                             &SDL_hapticlist[SDL_numhaptics].
-                                             capabilities);
-    if (FAILED(ret)) {
-        /* DI_SetError("Getting device capabilities",ret); */
-        IDirectInputDevice8_Release(device);
-        return DIENUM_CONTINUE;
+    while (device_index > 0) {
+        SDL_assert(item != NULL);
+        device_index--;
+        item = item->next;
     }
 
-    /* Copy the name */
-    SDL_hapticlist[SDL_numhaptics].name = WIN_StringToUTF8(SDL_hapticlist[SDL_numhaptics].instance.tszProductName);
-
-    /* Close up device and count it. */
-    IDirectInputDevice8_Release(device);
-    SDL_numhaptics++;
-
-    /* Watch out for hard limit. */
-    if (SDL_numhaptics >= MAX_HAPTICS)
-        return DIENUM_STOP;
-
-    return DIENUM_CONTINUE;
+    return item;
 }
 
-
 /*
  * Return the name of a haptic device, does not need to be opened.
  */
 const char *
 SDL_SYS_HapticName(int index)
 {
-    return SDL_hapticlist[index].name;
+    SDL_hapticlist_item *item = HapticByDevIndex(index);
+    return item->name;
 }
 
 
@@ -384,7 +561,7 @@
 }
 
 static int
-SDL_SYS_HapticOpenFromXInput(SDL_Haptic * haptic, Uint8 userid)
+SDL_SYS_HapticOpenFromXInput(SDL_Haptic *haptic, const Uint8 userid)
 {
     char threadName[32];
     XINPUT_VIBRATION vibration = { 0, 0 };  /* stop any current vibration */
@@ -595,11 +772,8 @@
 int
 SDL_SYS_HapticOpen(SDL_Haptic * haptic)
 {
-    if (SDL_hapticlist[haptic->index].bXInputHaptic) {
-        return SDL_SYS_HapticOpenFromXInput(haptic, SDL_hapticlist[haptic->index].userid);
-    }
-
-    return SDL_SYS_HapticOpenFromInstance(haptic, SDL_hapticlist[haptic->index].instance);
+    SDL_hapticlist_item *item = HapticByDevIndex(haptic->index);
+    return (item->bXInputHaptic) ? SDL_SYS_HapticOpenFromXInput(haptic, item->userid) : SDL_SYS_HapticOpenFromInstance(haptic, item->instance);
 }
 
 
@@ -609,13 +783,16 @@
 int
 SDL_SYS_HapticMouse(void)
 {
-    int i;
+    SDL_hapticlist_item *item;
+    int index = numhaptics-1;
 
     /* Grab the first mouse haptic device we find. */
-    for (i = 0; i < SDL_numhaptics; i++) {
-        if (SDL_hapticlist[i].capabilities.dwDevType == DI8DEVCLASS_POINTER ) {
-            return i;
+    for (item = SDL_hapticlist; item != NULL; item = item->next) {
+        SDL_assert(index >= 0);
+        if (item->capabilities.dwDevType == DI8DEVCLASS_POINTER ) {
+            return index;
         }
+        index--;
     }
 
     return -1;
@@ -677,34 +854,39 @@
 int
 SDL_SYS_HapticOpenFromJoystick(SDL_Haptic * haptic, SDL_Joystick * joystick)
 {
-    int i;
-    HRESULT idret;
-    DIDEVICEINSTANCE joy_instance;
-    joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);
+    SDL_hapticlist_item *item;
+    int index = numhaptics-1;
 
     /* Since it comes from a joystick we have to try to match it with a haptic device on our haptic list. */
     if (joystick->hwdata->bXInputDevice) {
         const Uint8 userid = joystick->hwdata->userid;
-        for (i=0; i<SDL_numhaptics; i++) {
-            if ((SDL_hapticlist[i].bXInputHaptic) && (SDL_hapticlist[i].userid == userid)) {
+        for (item = SDL_hapticlist; item != NULL; item = item->next) {
+            if ((item->bXInputHaptic) && (item->userid == userid)) {
                 SDL_assert(joystick->hwdata->bXInputHaptic);
-                haptic->index = i;
-                return SDL_SYS_HapticOpenFromXInput(haptic, SDL_hapticlist[haptic->index].userid);
+                haptic->index = index;
+                return SDL_SYS_HapticOpenFromXInput(haptic, userid);
             }
+            index--;
         }
     } else {
-        for (i=0; i<SDL_numhaptics; i++) {
-            idret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, &joy_instance);
-            if (FAILED(idret)) {
-                return -1;
-            }
-            if (DI_GUIDIsSame(&SDL_hapticlist[i].instance.guidInstance,
-                              &joy_instance.guidInstance)) {
-                haptic->index = i;
+        HRESULT idret;
+        DIDEVICEINSTANCE joy_instance;
+
+        joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);
+        idret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, &joy_instance);
+        if (FAILED(idret)) {
+            return -1;
+        }
+
+        for (item = SDL_hapticlist; item != NULL; item = item->next) {
+            if (DI_GUIDIsSame(&item->instance.guidInstance, &joy_instance.guidInstance)) {
+                haptic->index = index;
                 return SDL_SYS_HapticOpenFromDevice8(haptic, joystick->hwdata->InputDevice, SDL_TRUE);
             }
+            index--;
         }
     }
+
     /* No match to our haptic list */
     return -1;
 }
@@ -749,16 +931,20 @@
 void
 SDL_SYS_HapticQuit(void)
 {
-    int i;
+    SDL_hapticlist_item *item;
+    SDL_hapticlist_item *next = NULL;
 
     if (loaded_xinput) {
         WIN_UnloadXInputDLL();
         loaded_xinput = SDL_FALSE;
     }
 
-    for (i = 0; i < SDL_arraysize(SDL_hapticlist); ++i) {
-        SDL_free(SDL_hapticlist[i].name);
-        SDL_hapticlist[i].name = NULL;
+    for (item = SDL_hapticlist; item; item = next) {
+        /* Opened and not closed haptics are leaked, this is on purpose.
+         * Close your haptic devices after usage. */
+        next = item->next;
+        SDL_free(item->name);
+        SDL_free(item);
     }
 
     if (dinput != NULL) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/haptic/windows/SDL_syshaptic_c.h	Thu Feb 06 07:37:20 2014 -0500
@@ -0,0 +1,28 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+extern int DirectInputHaptic_MaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance);
+extern int DirectInputHaptic_MaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance);
+extern int XInputHaptic_MaybeAddDevice(const DWORD dwUserid);
+extern int XInputHaptic_MaybeRemoveDevice(const DWORD dwUserid);
+
+/* vi: set ts=4 sw=4 expandtab: */
+
--- a/src/joystick/windows/SDL_dxjoystick.c	Wed Feb 05 20:07:25 2014 -0500
+++ b/src/joystick/windows/SDL_dxjoystick.c	Thu Feb 06 07:37:20 2014 -0500
@@ -49,6 +49,10 @@
 #define INITGUID /* Only set here, if set twice will cause mingw32 to break. */
 #include "SDL_dxjoystick_c.h"
 
+#if SDL_HAPTIC_DINPUT
+#include "../../haptic/windows/SDL_syshaptic_c.h"    /* For haptic hot plugging */
+#endif
+
 #ifndef DIDFT_OPTIONAL
 #define DIDFT_OPTIONAL      0x80000000
 #endif
@@ -824,7 +828,17 @@
         while ( pCurList )
         {
             JoyStick_DeviceData *pListNext = NULL;
+
+#if SDL_HAPTIC_DINPUT
+            if (pCurList->bXInputDevice) {
+                XInputHaptic_MaybeRemoveDevice(pCurList->XInputUserId);
+            } else {
+                DirectInputHaptic_MaybeRemoveDevice(&pCurList->dxdevice);
+            }
+#endif
+
 #if !SDL_EVENTS_DISABLED
+            {
             SDL_Event event;
             event.type = SDL_JOYDEVICEREMOVED;
 
@@ -835,6 +849,7 @@
                         SDL_PushEvent(&event);
                 }
             }
+            }
 #endif /* !SDL_EVENTS_DISABLED */
 
             pListNext = pCurList->pNext;
@@ -855,7 +870,16 @@
         {
             if ( pNewJoystick->send_add_event )
             {
+#if SDL_HAPTIC_DINPUT
+                if (pNewJoystick->bXInputDevice) {
+                    XInputHaptic_MaybeAddDevice(pNewJoystick->XInputUserId);
+                } else {
+                    DirectInputHaptic_MaybeAddDevice(&pNewJoystick->dxdevice);
+                }
+#endif
+
 #if !SDL_EVENTS_DISABLED
+                {
                 SDL_Event event;
                 event.type = SDL_JOYDEVICEADDED;
 
@@ -866,6 +890,7 @@
                             SDL_PushEvent(&event);
                     }
                 }
+                }
 #endif /* !SDL_EVENTS_DISABLED */
                 pNewJoystick->send_add_event = 0;
             }