Wired up haptic hotplugging for Windows DirectInput/XInput code.

This commit is contained in:
Ryan C. Gordon 2014-02-06 07:37:20 -05:00
parent 9cf8c49c39
commit e42d46b463
3 changed files with 334 additions and 95 deletions

View File

@ -33,12 +33,12 @@
#include "../../joystick/SDL_sysjoystick.h" /* For the real SDL_Joystick */ #include "../../joystick/SDL_sysjoystick.h" /* For the real SDL_Joystick */
#include "../../joystick/windows/SDL_dxjoystick_c.h" /* For joystick hwdata */ #include "../../joystick/windows/SDL_dxjoystick_c.h" /* For joystick hwdata */
#define MAX_HAPTICS 32 #include "SDL_syshaptic_c.h"
/* /*
* List of available haptic devices. * List of available haptic devices.
*/ */
static struct typedef struct SDL_hapticlist_item
{ {
DIDEVICEINSTANCE instance; DIDEVICEINSTANCE instance;
char *name; char *name;
@ -46,7 +46,8 @@ static struct
DIDEVCAPS capabilities; DIDEVCAPS capabilities;
Uint8 bXInputHaptic; /* Supports force feedback via XInput. */ Uint8 bXInputHaptic; /* Supports force feedback via XInput. */
Uint8 userid; /* XInput userid index for this joystick */ 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 SDL_bool coinitialized = SDL_FALSE;
static LPDIRECTINPUT8 dinput = NULL; static LPDIRECTINPUT8 dinput = NULL;
static SDL_bool loaded_xinput = SDL_FALSE; 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. * External stuff.
@ -101,7 +104,7 @@ static int SDL_SYS_HapticOpenFromInstance(SDL_Haptic * haptic,
static int SDL_SYS_HapticOpenFromDevice8(SDL_Haptic * haptic, static int SDL_SYS_HapticOpenFromDevice8(SDL_Haptic * haptic,
LPDIRECTINPUTDEVICE8 device8, LPDIRECTINPUTDEVICE8 device8,
SDL_bool is_joystick); 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 DWORD DIGetTriggerButton(Uint16 button);
static int SDL_SYS_SetDirection(DIEFFECT * effect, SDL_HapticDirection * dir, static int SDL_SYS_SetDirection(DIEFFECT * effect, SDL_HapticDirection * dir,
int naxes); int naxes);
@ -155,11 +158,6 @@ SDL_SYS_HapticInit(void)
return SDL_SetError("Haptic: SubSystem already open."); 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(); ret = WIN_CoInitialize();
if (FAILED(ret)) { if (FAILED(ret)) {
return DI_SetError("Coinitialize", ret); return DI_SetError("Coinitialize", ret);
@ -205,81 +203,260 @@ SDL_SYS_HapticInit(void)
if (loaded_xinput) { if (loaded_xinput) {
DWORD i; DWORD i;
const SDL_bool bIs14OrLater = (SDL_XInputVersion >= ((1<<16)|4)); for (i = 0; i < SDL_XINPUT_MAX_DEVICES; i++) {
XInputHaptic_MaybeAddDevice(i);
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++;
}
}
} }
} }
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. * Callback to find the haptic devices.
*/ */
static BOOL CALLBACK static BOOL CALLBACK
EnumHapticsCallback(const DIDEVICEINSTANCE * pdidInstance, VOID * pContext) EnumHapticsCallback(const DIDEVICEINSTANCE * pdidInstance, VOID * pContext)
{ {
HRESULT ret; (void) pContext;
LPDIRECTINPUTDEVICE8 device; 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;
}
/* 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;
} }
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. * Return the name of a haptic device, does not need to be opened.
*/ */
const char * const char *
SDL_SYS_HapticName(int index) 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 static int
SDL_SYS_HapticOpenFromXInput(SDL_Haptic * haptic, Uint8 userid) SDL_SYS_HapticOpenFromXInput(SDL_Haptic *haptic, const Uint8 userid)
{ {
char threadName[32]; char threadName[32];
XINPUT_VIBRATION vibration = { 0, 0 }; /* stop any current vibration */ XINPUT_VIBRATION vibration = { 0, 0 }; /* stop any current vibration */
@ -595,11 +772,8 @@ SDL_SYS_HapticOpenFromDevice8(SDL_Haptic * haptic,
int int
SDL_SYS_HapticOpen(SDL_Haptic * haptic) SDL_SYS_HapticOpen(SDL_Haptic * haptic)
{ {
if (SDL_hapticlist[haptic->index].bXInputHaptic) { SDL_hapticlist_item *item = HapticByDevIndex(haptic->index);
return SDL_SYS_HapticOpenFromXInput(haptic, SDL_hapticlist[haptic->index].userid); return (item->bXInputHaptic) ? SDL_SYS_HapticOpenFromXInput(haptic, item->userid) : SDL_SYS_HapticOpenFromInstance(haptic, item->instance);
}
return SDL_SYS_HapticOpenFromInstance(haptic, SDL_hapticlist[haptic->index].instance);
} }
@ -609,13 +783,16 @@ SDL_SYS_HapticOpen(SDL_Haptic * haptic)
int int
SDL_SYS_HapticMouse(void) SDL_SYS_HapticMouse(void)
{ {
int i; SDL_hapticlist_item *item;
int index = numhaptics-1;
/* Grab the first mouse haptic device we find. */ /* Grab the first mouse haptic device we find. */
for (i = 0; i < SDL_numhaptics; i++) { for (item = SDL_hapticlist; item != NULL; item = item->next) {
if (SDL_hapticlist[i].capabilities.dwDevType == DI8DEVCLASS_POINTER ) { SDL_assert(index >= 0);
return i; if (item->capabilities.dwDevType == DI8DEVCLASS_POINTER ) {
return index;
} }
index--;
} }
return -1; return -1;
@ -677,34 +854,39 @@ SDL_SYS_JoystickSameHaptic(SDL_Haptic * haptic, SDL_Joystick * joystick)
int int
SDL_SYS_HapticOpenFromJoystick(SDL_Haptic * haptic, SDL_Joystick * joystick) SDL_SYS_HapticOpenFromJoystick(SDL_Haptic * haptic, SDL_Joystick * joystick)
{ {
int i; SDL_hapticlist_item *item;
HRESULT idret; int index = numhaptics-1;
DIDEVICEINSTANCE joy_instance;
joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);
/* Since it comes from a joystick we have to try to match it with a haptic device on our haptic list. */ /* 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) { if (joystick->hwdata->bXInputDevice) {
const Uint8 userid = joystick->hwdata->userid; const Uint8 userid = joystick->hwdata->userid;
for (i=0; i<SDL_numhaptics; i++) { for (item = SDL_hapticlist; item != NULL; item = item->next) {
if ((SDL_hapticlist[i].bXInputHaptic) && (SDL_hapticlist[i].userid == userid)) { if ((item->bXInputHaptic) && (item->userid == userid)) {
SDL_assert(joystick->hwdata->bXInputHaptic); SDL_assert(joystick->hwdata->bXInputHaptic);
haptic->index = i; haptic->index = index;
return SDL_SYS_HapticOpenFromXInput(haptic, SDL_hapticlist[haptic->index].userid); return SDL_SYS_HapticOpenFromXInput(haptic, userid);
} }
index--;
} }
} else { } else {
for (i=0; i<SDL_numhaptics; i++) { HRESULT idret;
idret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, &joy_instance); DIDEVICEINSTANCE joy_instance;
if (FAILED(idret)) {
return -1; joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);
} idret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, &joy_instance);
if (DI_GUIDIsSame(&SDL_hapticlist[i].instance.guidInstance, if (FAILED(idret)) {
&joy_instance.guidInstance)) { return -1;
haptic->index = i; }
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); return SDL_SYS_HapticOpenFromDevice8(haptic, joystick->hwdata->InputDevice, SDL_TRUE);
} }
index--;
} }
} }
/* No match to our haptic list */ /* No match to our haptic list */
return -1; return -1;
} }
@ -749,16 +931,20 @@ SDL_SYS_HapticClose(SDL_Haptic * haptic)
void void
SDL_SYS_HapticQuit(void) SDL_SYS_HapticQuit(void)
{ {
int i; SDL_hapticlist_item *item;
SDL_hapticlist_item *next = NULL;
if (loaded_xinput) { if (loaded_xinput) {
WIN_UnloadXInputDLL(); WIN_UnloadXInputDLL();
loaded_xinput = SDL_FALSE; loaded_xinput = SDL_FALSE;
} }
for (i = 0; i < SDL_arraysize(SDL_hapticlist); ++i) { for (item = SDL_hapticlist; item; item = next) {
SDL_free(SDL_hapticlist[i].name); /* Opened and not closed haptics are leaked, this is on purpose.
SDL_hapticlist[i].name = NULL; * Close your haptic devices after usage. */
next = item->next;
SDL_free(item->name);
SDL_free(item);
} }
if (dinput != NULL) { if (dinput != NULL) {

View File

@ -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: */

View File

@ -49,6 +49,10 @@
#define INITGUID /* Only set here, if set twice will cause mingw32 to break. */ #define INITGUID /* Only set here, if set twice will cause mingw32 to break. */
#include "SDL_dxjoystick_c.h" #include "SDL_dxjoystick_c.h"
#if SDL_HAPTIC_DINPUT
#include "../../haptic/windows/SDL_syshaptic_c.h" /* For haptic hot plugging */
#endif
#ifndef DIDFT_OPTIONAL #ifndef DIDFT_OPTIONAL
#define DIDFT_OPTIONAL 0x80000000 #define DIDFT_OPTIONAL 0x80000000
#endif #endif
@ -824,7 +828,17 @@ void SDL_SYS_JoystickDetect()
while ( pCurList ) while ( pCurList )
{ {
JoyStick_DeviceData *pListNext = NULL; 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 #if !SDL_EVENTS_DISABLED
{
SDL_Event event; SDL_Event event;
event.type = SDL_JOYDEVICEREMOVED; event.type = SDL_JOYDEVICEREMOVED;
@ -835,6 +849,7 @@ void SDL_SYS_JoystickDetect()
SDL_PushEvent(&event); SDL_PushEvent(&event);
} }
} }
}
#endif /* !SDL_EVENTS_DISABLED */ #endif /* !SDL_EVENTS_DISABLED */
pListNext = pCurList->pNext; pListNext = pCurList->pNext;
@ -855,7 +870,16 @@ void SDL_SYS_JoystickDetect()
{ {
if ( pNewJoystick->send_add_event ) 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 #if !SDL_EVENTS_DISABLED
{
SDL_Event event; SDL_Event event;
event.type = SDL_JOYDEVICEADDED; event.type = SDL_JOYDEVICEADDED;
@ -866,6 +890,7 @@ void SDL_SYS_JoystickDetect()
SDL_PushEvent(&event); SDL_PushEvent(&event);
} }
} }
}
#endif /* !SDL_EVENTS_DISABLED */ #endif /* !SDL_EVENTS_DISABLED */
pNewJoystick->send_add_event = 0; pNewJoystick->send_add_event = 0;
} }