From e42d46b463e75efd0d6ce8facc2fdd93d94d7328 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 6 Feb 2014 07:37:20 -0500 Subject: [PATCH] Wired up haptic hotplugging for Windows DirectInput/XInput code. --- src/haptic/windows/SDL_syshaptic.c | 376 +++++++++++++++++++------- src/haptic/windows/SDL_syshaptic_c.h | 28 ++ src/joystick/windows/SDL_dxjoystick.c | 25 ++ 3 files changed, 334 insertions(+), 95 deletions(-) create mode 100644 src/haptic/windows/SDL_syshaptic_c.h diff --git a/src/haptic/windows/SDL_syshaptic.c b/src/haptic/windows/SDL_syshaptic.c index 34ce2de00..a68cd34f6 100644 --- a/src/haptic/windows/SDL_syshaptic.c +++ b/src/haptic/windows/SDL_syshaptic.c @@ -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 @@ static struct 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 @@ struct haptic_hweffect 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_HapticOpenFromInstance(SDL_Haptic * haptic, 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 @@ SDL_SYS_HapticInit(void) 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 @@ SDL_SYS_HapticInit(void) if (loaded_xinput) { DWORD i; - const SDL_bool bIs14OrLater = (SDL_XInputVersion >= ((1<<16)|4)); - - 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++; - } - } + for (i = 0; i < SDL_XINPUT_MAX_DEVICES; i++) { + XInputHaptic_MaybeAddDevice(i); } } - return SDL_numhaptics; + return 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 */ + } + } + + /* 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; - - /* 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; - } - - /* 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; - } - - /* 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; + (void) pContext; + DirectInputHaptic_MaybeAddDevice(pdidInstance); + return DIENUM_CONTINUE; /* continue enumerating */ } +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; + } + + while (device_index > 0) { + SDL_assert(item != NULL); + device_index--; + item = item->next; + } + + 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 @@ SDL_SYS_HapticOpenFromInstance(SDL_Haptic * haptic, DIDEVICEINSTANCE instance) } 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 @@ SDL_SYS_HapticOpenFromDevice8(SDL_Haptic * haptic, 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 @@ SDL_SYS_HapticOpen(SDL_Haptic * haptic) 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 @@ SDL_SYS_JoystickSameHaptic(SDL_Haptic * haptic, SDL_Joystick * joystick) 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; inext) { + 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; ihwdata->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 @@ SDL_SYS_HapticClose(SDL_Haptic * haptic) 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) { diff --git a/src/haptic/windows/SDL_syshaptic_c.h b/src/haptic/windows/SDL_syshaptic_c.h new file mode 100644 index 000000000..1e0fa190d --- /dev/null +++ b/src/haptic/windows/SDL_syshaptic_c.h @@ -0,0 +1,28 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2014 Sam Lantinga + + 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: */ + diff --git a/src/joystick/windows/SDL_dxjoystick.c b/src/joystick/windows/SDL_dxjoystick.c index 902121398..da96b6063 100644 --- a/src/joystick/windows/SDL_dxjoystick.c +++ b/src/joystick/windows/SDL_dxjoystick.c @@ -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 @@ void SDL_SYS_JoystickDetect() 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 @@ void SDL_SYS_JoystickDetect() SDL_PushEvent(&event); } } + } #endif /* !SDL_EVENTS_DISABLED */ pListNext = pCurList->pNext; @@ -855,7 +870,16 @@ void SDL_SYS_JoystickDetect() { 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 @@ void SDL_SYS_JoystickDetect() SDL_PushEvent(&event); } } + } #endif /* !SDL_EVENTS_DISABLED */ pNewJoystick->send_add_event = 0; }