| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2014 Sam Lantinga <[email protected]> |
| |
| 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. |
| */ |
| #include "../../SDL_internal.h" |
| |
| #ifdef SDL_HAPTIC_DINPUT |
| |
| #include "SDL_assert.h" |
| #include "SDL_thread.h" |
| #include "SDL_mutex.h" |
| #include "SDL_timer.h" |
| #include "SDL_hints.h" |
| #include "SDL_haptic.h" |
| #include "../SDL_syshaptic.h" |
| #include "SDL_joystick.h" |
| #include "../../joystick/SDL_sysjoystick.h" /* For the real SDL_Joystick */ |
| #include "../../joystick/windows/SDL_dxjoystick_c.h" /* For joystick hwdata */ |
| |
| #include "SDL_syshaptic_c.h" |
| |
| /* |
| * List of available haptic devices. |
| */ |
| typedef struct SDL_hapticlist_item |
| { |
| DIDEVICEINSTANCE instance; |
| char *name; |
| SDL_Haptic *haptic; |
| DIDEVCAPS capabilities; |
| Uint8 bXInputHaptic; /* Supports force feedback via XInput. */ |
| Uint8 userid; /* XInput userid index for this joystick */ |
| struct SDL_hapticlist_item *next; |
| } SDL_hapticlist_item; |
| |
| |
| /* |
| * Haptic system hardware data. |
| */ |
| struct haptic_hwdata |
| { |
| LPDIRECTINPUTDEVICE8 device; |
| DWORD axes[3]; /* Axes to use. */ |
| SDL_bool is_joystick; /* Device is loaded as joystick. */ |
| Uint8 bXInputHaptic; /* Supports force feedback via XInput. */ |
| Uint8 userid; /* XInput userid index for this joystick */ |
| SDL_Thread *thread; |
| SDL_mutex *mutex; |
| volatile Uint32 stopTicks; |
| volatile int stopThread; |
| }; |
| |
| |
| /* |
| * Haptic system effect data. |
| */ |
| struct haptic_hweffect |
| { |
| DIEFFECT effect; |
| LPDIRECTINPUTEFFECT ref; |
| XINPUT_VIBRATION vibration; |
| }; |
| |
| |
| /* |
| * Internal stuff. |
| */ |
| 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. |
| */ |
| extern HWND SDL_HelperWindow; |
| |
| |
| /* |
| * Prototypes. |
| */ |
| static int DI_SetError(const char *str, HRESULT err); |
| static int DI_GUIDIsSame(const GUID * a, const GUID * b); |
| static int SDL_SYS_HapticOpenFromInstance(SDL_Haptic * haptic, |
| DIDEVICEINSTANCE instance); |
| static int SDL_SYS_HapticOpenFromDevice8(SDL_Haptic * haptic, |
| LPDIRECTINPUTDEVICE8 device8, |
| SDL_bool is_joystick); |
| 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); |
| static int SDL_SYS_ToDIEFFECT(SDL_Haptic * haptic, DIEFFECT * dest, |
| SDL_HapticEffect * src); |
| static void SDL_SYS_HapticFreeDIEFFECT(DIEFFECT * effect, int type); |
| static REFGUID SDL_SYS_HapticEffectType(SDL_HapticEffect * effect); |
| static int SDLCALL SDL_RunXInputHaptic(void *arg); |
| |
| /* Callbacks. */ |
| static BOOL CALLBACK EnumHapticsCallback(const DIDEVICEINSTANCE * |
| pdidInstance, VOID * pContext); |
| static BOOL CALLBACK DI_EffectCallback(LPCDIEFFECTINFO pei, LPVOID pv); |
| |
| |
| /* |
| * Like SDL_SetError but for DX error codes. |
| */ |
| static int |
| DI_SetError(const char *str, HRESULT err) |
| { |
| /* |
| SDL_SetError("Haptic: %s - %s: %s", str, |
| DXGetErrorString8A(err), DXGetErrorDescription8A(err)); |
| */ |
| return SDL_SetError("Haptic error %s", str); |
| } |
| |
| |
| /* |
| * Checks to see if two GUID are the same. |
| */ |
| static int |
| DI_GUIDIsSame(const GUID * a, const GUID * b) |
| { |
| return (SDL_memcmp(a, b, sizeof (GUID)) == 0); |
| } |
| |
| |
| /* |
| * Initializes the haptic subsystem. |
| */ |
| int |
| SDL_SYS_HapticInit(void) |
| { |
| const char *env = SDL_GetHint(SDL_HINT_XINPUT_ENABLED); |
| HRESULT ret; |
| HINSTANCE instance; |
| |
| if (dinput != NULL) { /* Already open. */ |
| return SDL_SetError("Haptic: SubSystem already open."); |
| } |
| |
| ret = WIN_CoInitialize(); |
| if (FAILED(ret)) { |
| return DI_SetError("Coinitialize", ret); |
| } |
| |
| coinitialized = SDL_TRUE; |
| |
| ret = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER, |
| &IID_IDirectInput8, (LPVOID) & dinput); |
| if (FAILED(ret)) { |
| SDL_SYS_HapticQuit(); |
| return DI_SetError("CoCreateInstance", ret); |
| } |
| |
| /* Because we used CoCreateInstance, we need to Initialize it, first. */ |
| instance = GetModuleHandle(NULL); |
| if (instance == NULL) { |
| SDL_SYS_HapticQuit(); |
| return SDL_SetError("GetModuleHandle() failed with error code %d.", |
| GetLastError()); |
| } |
| ret = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION); |
| if (FAILED(ret)) { |
| SDL_SYS_HapticQuit(); |
| return DI_SetError("Initializing DirectInput device", ret); |
| } |
| |
| /* Look for haptic devices. */ |
| ret = IDirectInput8_EnumDevices(dinput, |
| 0, |
| EnumHapticsCallback, |
| NULL, |
| DIEDFL_FORCEFEEDBACK | |
| DIEDFL_ATTACHEDONLY); |
| if (FAILED(ret)) { |
| SDL_SYS_HapticQuit(); |
| return DI_SetError("Enumerating DirectInput devices", ret); |
| } |
| |
| if (!env || SDL_atoi(env)) { |
| loaded_xinput = (WIN_LoadXInputDLL() == 0); |
| } |
| |
| if (loaded_xinput) { |
| DWORD i; |
| for (i = 0; i < SDL_XINPUT_MAX_DEVICES; i++) { |
| XInputHaptic_MaybeAddDevice(i); |
| } |
| } |
| |
| 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; |
| |
| if (dinput == NULL) { |
| return -1; /* not initialized. We'll pick these up on enumeration if we init later. */ |
| } |
| |
| /* 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_calloc(1, sizeof(SDL_hapticlist_item)); |
| if (item == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| |
| 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; |
| |
| if (dinput == NULL) { |
| return -1; /* not initialized, ignore this. */ |
| } |
| |
| 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) |
| { |
| (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) |
| { |
| SDL_hapticlist_item *item = HapticByDevIndex(index); |
| return item->name; |
| } |
| |
| |
| /* |
| * Callback to get all supported effects. |
| */ |
| #define EFFECT_TEST(e,s) \ |
| if (DI_GUIDIsSame(&pei->guid, &(e))) \ |
| haptic->supported |= (s) |
| static BOOL CALLBACK |
| DI_EffectCallback(LPCDIEFFECTINFO pei, LPVOID pv) |
| { |
| /* Prepare the haptic device. */ |
| SDL_Haptic *haptic = (SDL_Haptic *) pv; |
| |
| /* Get supported. */ |
| EFFECT_TEST(GUID_Spring, SDL_HAPTIC_SPRING); |
| EFFECT_TEST(GUID_Damper, SDL_HAPTIC_DAMPER); |
| EFFECT_TEST(GUID_Inertia, SDL_HAPTIC_INERTIA); |
| EFFECT_TEST(GUID_Friction, SDL_HAPTIC_FRICTION); |
| EFFECT_TEST(GUID_ConstantForce, SDL_HAPTIC_CONSTANT); |
| EFFECT_TEST(GUID_CustomForce, SDL_HAPTIC_CUSTOM); |
| EFFECT_TEST(GUID_Sine, SDL_HAPTIC_SINE); |
| /* !!! FIXME: put this back when we have more bits in 2.1 */ |
| /* EFFECT_TEST(GUID_Square, SDL_HAPTIC_SQUARE); */ |
| EFFECT_TEST(GUID_Triangle, SDL_HAPTIC_TRIANGLE); |
| EFFECT_TEST(GUID_SawtoothUp, SDL_HAPTIC_SAWTOOTHUP); |
| EFFECT_TEST(GUID_SawtoothDown, SDL_HAPTIC_SAWTOOTHDOWN); |
| EFFECT_TEST(GUID_RampForce, SDL_HAPTIC_RAMP); |
| |
| /* Check for more. */ |
| return DIENUM_CONTINUE; |
| } |
| |
| |
| /* |
| * Callback to get supported axes. |
| */ |
| static BOOL CALLBACK |
| DI_DeviceObjectCallback(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID pvRef) |
| { |
| SDL_Haptic *haptic = (SDL_Haptic *) pvRef; |
| |
| if ((dev->dwType & DIDFT_AXIS) && (dev->dwFlags & DIDOI_FFACTUATOR)) { |
| |
| haptic->hwdata->axes[haptic->naxes] = dev->dwOfs; |
| haptic->naxes++; |
| |
| /* Currently using the artificial limit of 3 axes. */ |
| if (haptic->naxes >= 3) { |
| return DIENUM_STOP; |
| } |
| } |
| |
| return DIENUM_CONTINUE; |
| } |
| |
| |
| /* |
| * Opens the haptic device from the file descriptor. |
| * |
| * Steps: |
| * - Open temporary DirectInputDevice interface. |
| * - Create DirectInputDevice8 interface. |
| * - Release DirectInputDevice interface. |
| * - Call SDL_SYS_HapticOpenFromDevice8 |
| */ |
| static int |
| SDL_SYS_HapticOpenFromInstance(SDL_Haptic * haptic, DIDEVICEINSTANCE instance) |
| { |
| HRESULT ret; |
| int ret2; |
| LPDIRECTINPUTDEVICE8 device; |
| LPDIRECTINPUTDEVICE8 device8; |
| |
| /* Open the device */ |
| ret = IDirectInput8_CreateDevice(dinput, &instance.guidInstance, |
| &device, NULL); |
| if (FAILED(ret)) { |
| DI_SetError("Creating DirectInput device", ret); |
| return -1; |
| } |
| |
| /* Now get the IDirectInputDevice8 interface, instead. */ |
| ret = IDirectInputDevice8_QueryInterface(device, |
| &IID_IDirectInputDevice8, |
| (LPVOID *) &device8); |
| /* Done with the temporary one now. */ |
| IDirectInputDevice8_Release(device); |
| if (FAILED(ret)) { |
| DI_SetError("Querying DirectInput interface", ret); |
| return -1; |
| } |
| |
| ret2 = SDL_SYS_HapticOpenFromDevice8(haptic, device8, SDL_FALSE); |
| if (ret2 < 0) { |
| IDirectInputDevice8_Release(device8); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| SDL_SYS_HapticOpenFromXInput(SDL_Haptic *haptic, const Uint8 userid) |
| { |
| char threadName[32]; |
| XINPUT_VIBRATION vibration = { 0, 0 }; /* stop any current vibration */ |
| XINPUTSETSTATE(userid, &vibration); |
| |
| haptic->supported = SDL_HAPTIC_LEFTRIGHT; |
| |
| haptic->neffects = 1; |
| haptic->nplaying = 1; |
| |
| /* Prepare effects memory. */ |
| haptic->effects = (struct haptic_effect *) |
| SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); |
| if (haptic->effects == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| /* Clear the memory */ |
| SDL_memset(haptic->effects, 0, |
| sizeof(struct haptic_effect) * haptic->neffects); |
| |
| haptic->hwdata = (struct haptic_hwdata *) SDL_malloc(sizeof(*haptic->hwdata)); |
| if (haptic->hwdata == NULL) { |
| SDL_free(haptic->effects); |
| haptic->effects = NULL; |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(haptic->hwdata, 0, sizeof(*haptic->hwdata)); |
| |
| haptic->hwdata->bXInputHaptic = 1; |
| haptic->hwdata->userid = userid; |
| |
| haptic->hwdata->mutex = SDL_CreateMutex(); |
| if (haptic->hwdata->mutex == NULL) { |
| SDL_free(haptic->effects); |
| SDL_free(haptic->hwdata); |
| haptic->effects = NULL; |
| return SDL_SetError("Couldn't create XInput haptic mutex"); |
| } |
| |
| SDL_snprintf(threadName, sizeof (threadName), "SDLXInputDev%d", (int) userid); |
| |
| #if defined(__WIN32__) && !defined(HAVE_LIBC) /* !!! FIXME: this is nasty. */ |
| #undef SDL_CreateThread |
| #if SDL_DYNAMIC_API |
| haptic->hwdata->thread = SDL_CreateThread_REAL(SDL_RunXInputHaptic, threadName, haptic->hwdata, NULL, NULL); |
| #else |
| haptic->hwdata->thread = SDL_CreateThread(SDL_RunXInputHaptic, threadName, haptic->hwdata, NULL, NULL); |
| #endif |
| #else |
| haptic->hwdata->thread = SDL_CreateThread(SDL_RunXInputHaptic, threadName, haptic->hwdata); |
| #endif |
| if (haptic->hwdata->thread == NULL) { |
| SDL_DestroyMutex(haptic->hwdata->mutex); |
| SDL_free(haptic->effects); |
| SDL_free(haptic->hwdata); |
| haptic->effects = NULL; |
| return SDL_SetError("Couldn't create XInput haptic thread"); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Opens the haptic device from the file descriptor. |
| * |
| * Steps: |
| * - Set cooperative level. |
| * - Set data format. |
| * - Acquire exclusiveness. |
| * - Reset actuators. |
| * - Get supported features. |
| */ |
| static int |
| SDL_SYS_HapticOpenFromDevice8(SDL_Haptic * haptic, |
| LPDIRECTINPUTDEVICE8 device8, SDL_bool is_joystick) |
| { |
| HRESULT ret; |
| DIPROPDWORD dipdw; |
| |
| /* Allocate the hwdata */ |
| haptic->hwdata = (struct haptic_hwdata *)SDL_malloc(sizeof(*haptic->hwdata)); |
| if (haptic->hwdata == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(haptic->hwdata, 0, sizeof(*haptic->hwdata)); |
| |
| /* We'll use the device8 from now on. */ |
| haptic->hwdata->device = device8; |
| haptic->hwdata->is_joystick = is_joystick; |
| |
| /* Grab it exclusively to use force feedback stuff. */ |
| ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device, |
| SDL_HelperWindow, |
| DISCL_EXCLUSIVE | |
| DISCL_BACKGROUND); |
| if (FAILED(ret)) { |
| DI_SetError("Setting cooperative level to exclusive", ret); |
| goto acquire_err; |
| } |
| |
| /* Set data format. */ |
| ret = IDirectInputDevice8_SetDataFormat(haptic->hwdata->device, |
| &c_dfDIJoystick2); |
| if (FAILED(ret)) { |
| DI_SetError("Setting data format", ret); |
| goto acquire_err; |
| } |
| |
| /* Get number of axes. */ |
| ret = IDirectInputDevice8_EnumObjects(haptic->hwdata->device, |
| DI_DeviceObjectCallback, |
| haptic, DIDFT_AXIS); |
| if (FAILED(ret)) { |
| DI_SetError("Getting device axes", ret); |
| goto acquire_err; |
| } |
| |
| /* Acquire the device. */ |
| ret = IDirectInputDevice8_Acquire(haptic->hwdata->device); |
| if (FAILED(ret)) { |
| DI_SetError("Acquiring DirectInput device", ret); |
| goto acquire_err; |
| } |
| |
| /* Reset all actuators - just in case. */ |
| ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device, |
| DISFFC_RESET); |
| if (FAILED(ret)) { |
| DI_SetError("Resetting device", ret); |
| goto acquire_err; |
| } |
| |
| /* Enabling actuators. */ |
| ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device, |
| DISFFC_SETACTUATORSON); |
| if (FAILED(ret)) { |
| DI_SetError("Enabling actuators", ret); |
| goto acquire_err; |
| } |
| |
| /* Get supported effects. */ |
| ret = IDirectInputDevice8_EnumEffects(haptic->hwdata->device, |
| DI_EffectCallback, haptic, |
| DIEFT_ALL); |
| if (FAILED(ret)) { |
| DI_SetError("Enumerating supported effects", ret); |
| goto acquire_err; |
| } |
| if (haptic->supported == 0) { /* Error since device supports nothing. */ |
| SDL_SetError("Haptic: Internal error on finding supported effects."); |
| goto acquire_err; |
| } |
| |
| /* Check autogain and autocenter. */ |
| dipdw.diph.dwSize = sizeof(DIPROPDWORD); |
| dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); |
| dipdw.diph.dwObj = 0; |
| dipdw.diph.dwHow = DIPH_DEVICE; |
| dipdw.dwData = 10000; |
| ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device, |
| DIPROP_FFGAIN, &dipdw.diph); |
| if (!FAILED(ret)) { /* Gain is supported. */ |
| haptic->supported |= SDL_HAPTIC_GAIN; |
| } |
| dipdw.diph.dwObj = 0; |
| dipdw.diph.dwHow = DIPH_DEVICE; |
| dipdw.dwData = DIPROPAUTOCENTER_OFF; |
| ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device, |
| DIPROP_AUTOCENTER, &dipdw.diph); |
| if (!FAILED(ret)) { /* Autocenter is supported. */ |
| haptic->supported |= SDL_HAPTIC_AUTOCENTER; |
| } |
| |
| /* Status is always supported. */ |
| haptic->supported |= SDL_HAPTIC_STATUS | SDL_HAPTIC_PAUSE; |
| |
| /* Check maximum effects. */ |
| haptic->neffects = 128; /* This is not actually supported as thus under windows, |
| there is no way to tell the number of EFFECTS that a |
| device can hold, so we'll just use a "random" number |
| instead and put warnings in SDL_haptic.h */ |
| haptic->nplaying = 128; /* Even more impossible to get this then neffects. */ |
| |
| /* Prepare effects memory. */ |
| haptic->effects = (struct haptic_effect *) |
| SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); |
| if (haptic->effects == NULL) { |
| SDL_OutOfMemory(); |
| goto acquire_err; |
| } |
| /* Clear the memory */ |
| SDL_memset(haptic->effects, 0, |
| sizeof(struct haptic_effect) * haptic->neffects); |
| |
| return 0; |
| |
| /* Error handling */ |
| acquire_err: |
| IDirectInputDevice8_Unacquire(haptic->hwdata->device); |
| return -1; |
| |
| } |
| |
| |
| /* |
| * Opens a haptic device for usage. |
| */ |
| int |
| SDL_SYS_HapticOpen(SDL_Haptic * haptic) |
| { |
| SDL_hapticlist_item *item = HapticByDevIndex(haptic->index); |
| return (item->bXInputHaptic) ? SDL_SYS_HapticOpenFromXInput(haptic, item->userid) : SDL_SYS_HapticOpenFromInstance(haptic, item->instance); |
| } |
| |
| |
| /* |
| * Opens a haptic device from first mouse it finds for usage. |
| */ |
| int |
| SDL_SYS_HapticMouse(void) |
| { |
| SDL_hapticlist_item *item; |
| int index = 0; |
| |
| /* Grab the first mouse haptic device we find. */ |
| for (item = SDL_hapticlist; item != NULL; item = item->next) { |
| SDL_assert(index >= 0); |
| if (item->capabilities.dwDevType == DI8DEVCLASS_POINTER ) { |
| return index; |
| } |
| ++index; |
| } |
| |
| return -1; |
| } |
| |
| |
| /* |
| * Checks to see if a joystick has haptic features. |
| */ |
| int |
| SDL_SYS_JoystickIsHaptic(SDL_Joystick * joystick) |
| { |
| const struct joystick_hwdata *hwdata = joystick->hwdata; |
| return ( (hwdata->bXInputHaptic) || |
| ((hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) != 0) ); |
| } |
| |
| |
| /* |
| * Checks to see if the haptic device and joystick are in reality the same. |
| */ |
| int |
| SDL_SYS_JoystickSameHaptic(SDL_Haptic * haptic, SDL_Joystick * joystick) |
| { |
| if (joystick->hwdata->bXInputHaptic != haptic->hwdata->bXInputHaptic) { |
| return 0; /* one is XInput, one is not; not the same device. */ |
| } else if (joystick->hwdata->bXInputHaptic) { /* XInput */ |
| return (haptic->hwdata->userid == joystick->hwdata->userid); |
| } else { /* DirectInput */ |
| HRESULT ret; |
| DIDEVICEINSTANCE hap_instance, joy_instance; |
| |
| hap_instance.dwSize = sizeof(DIDEVICEINSTANCE); |
| joy_instance.dwSize = sizeof(DIDEVICEINSTANCE); |
| |
| /* Get the device instances. */ |
| ret = IDirectInputDevice8_GetDeviceInfo(haptic->hwdata->device, |
| &hap_instance); |
| if (FAILED(ret)) { |
| return 0; |
| } |
| ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, |
| &joy_instance); |
| if (FAILED(ret)) { |
| return 0; |
| } |
| |
| if (DI_GUIDIsSame(&hap_instance.guidInstance, &joy_instance.guidInstance)) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Opens a SDL_Haptic from a SDL_Joystick. |
| */ |
| int |
| SDL_SYS_HapticOpenFromJoystick(SDL_Haptic * haptic, SDL_Joystick * joystick) |
| { |
| SDL_hapticlist_item *item; |
| int index = 0; |
| |
| /* 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 (item = SDL_hapticlist; item != NULL; item = item->next) { |
| if ((item->bXInputHaptic) && (item->userid == userid)) { |
| SDL_assert(joystick->hwdata->bXInputHaptic); |
| haptic->index = index; |
| return SDL_SYS_HapticOpenFromXInput(haptic, userid); |
| } |
| ++index; |
| } |
| } else { |
| 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; |
| } |
| |
| |
| /* |
| * Closes the haptic device. |
| */ |
| void |
| SDL_SYS_HapticClose(SDL_Haptic * haptic) |
| { |
| if (haptic->hwdata) { |
| |
| /* Free effects. */ |
| SDL_free(haptic->effects); |
| haptic->effects = NULL; |
| haptic->neffects = 0; |
| |
| /* Clean up */ |
| if (haptic->hwdata->bXInputHaptic) { |
| haptic->hwdata->stopThread = 1; |
| SDL_WaitThread(haptic->hwdata->thread, NULL); |
| SDL_DestroyMutex(haptic->hwdata->mutex); |
| } else { |
| IDirectInputDevice8_Unacquire(haptic->hwdata->device); |
| /* Only release if isn't grabbed by a joystick. */ |
| if (haptic->hwdata->is_joystick == 0) { |
| IDirectInputDevice8_Release(haptic->hwdata->device); |
| } |
| } |
| |
| /* Free */ |
| SDL_free(haptic->hwdata); |
| haptic->hwdata = NULL; |
| } |
| } |
| |
| |
| /* |
| * Clean up after system specific haptic stuff |
| */ |
| void |
| SDL_SYS_HapticQuit(void) |
| { |
| SDL_hapticlist_item *item; |
| SDL_hapticlist_item *next = NULL; |
| SDL_Haptic *hapticitem = NULL; |
| |
| extern SDL_Haptic *SDL_haptics; |
| for (hapticitem = SDL_haptics; hapticitem; hapticitem = hapticitem->next) { |
| if ((hapticitem->hwdata->bXInputHaptic) && (hapticitem->hwdata->thread)) { |
| /* we _have_ to stop the thread before we free the XInput DLL! */ |
| hapticitem->hwdata->stopThread = 1; |
| SDL_WaitThread(hapticitem->hwdata->thread, NULL); |
| hapticitem->hwdata->thread = 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. */ |
| /* !!! FIXME: (...is leaking on purpose a good idea?) */ |
| next = item->next; |
| SDL_free(item->name); |
| SDL_free(item); |
| } |
| |
| if (loaded_xinput) { |
| WIN_UnloadXInputDLL(); |
| loaded_xinput = SDL_FALSE; |
| } |
| |
| if (dinput != NULL) { |
| IDirectInput8_Release(dinput); |
| dinput = NULL; |
| } |
| |
| if (coinitialized) { |
| WIN_CoUninitialize(); |
| coinitialized = SDL_FALSE; |
| } |
| } |
| |
| |
| /* |
| * Converts an SDL trigger button to an DIEFFECT trigger button. |
| */ |
| static DWORD |
| DIGetTriggerButton(Uint16 button) |
| { |
| DWORD dwTriggerButton; |
| |
| dwTriggerButton = DIEB_NOTRIGGER; |
| |
| if (button != 0) { |
| dwTriggerButton = DIJOFS_BUTTON(button - 1); |
| } |
| |
| return dwTriggerButton; |
| } |
| |
| |
| /* |
| * Sets the direction. |
| */ |
| static int |
| SDL_SYS_SetDirection(DIEFFECT * effect, SDL_HapticDirection * dir, int naxes) |
| { |
| LONG *rglDir; |
| |
| /* Handle no axes a part. */ |
| if (naxes == 0) { |
| effect->dwFlags |= DIEFF_SPHERICAL; /* Set as default. */ |
| effect->rglDirection = NULL; |
| return 0; |
| } |
| |
| /* Has axes. */ |
| rglDir = SDL_malloc(sizeof(LONG) * naxes); |
| if (rglDir == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(rglDir, 0, sizeof(LONG) * naxes); |
| effect->rglDirection = rglDir; |
| |
| switch (dir->type) { |
| case SDL_HAPTIC_POLAR: |
| effect->dwFlags |= DIEFF_POLAR; |
| rglDir[0] = dir->dir[0]; |
| return 0; |
| case SDL_HAPTIC_CARTESIAN: |
| effect->dwFlags |= DIEFF_CARTESIAN; |
| rglDir[0] = dir->dir[0]; |
| if (naxes > 1) |
| rglDir[1] = dir->dir[1]; |
| if (naxes > 2) |
| rglDir[2] = dir->dir[2]; |
| return 0; |
| case SDL_HAPTIC_SPHERICAL: |
| effect->dwFlags |= DIEFF_SPHERICAL; |
| rglDir[0] = dir->dir[0]; |
| if (naxes > 1) |
| rglDir[1] = dir->dir[1]; |
| if (naxes > 2) |
| rglDir[2] = dir->dir[2]; |
| return 0; |
| |
| default: |
| return SDL_SetError("Haptic: Unknown direction type."); |
| } |
| } |
| |
| #define CONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF) |
| /* |
| * Creates the DIEFFECT from a SDL_HapticEffect. |
| */ |
| static int |
| SDL_SYS_ToDIEFFECT(SDL_Haptic * haptic, DIEFFECT * dest, |
| SDL_HapticEffect * src) |
| { |
| int i; |
| DICONSTANTFORCE *constant; |
| DIPERIODIC *periodic; |
| DICONDITION *condition; /* Actually an array of conditions - one per axis. */ |
| DIRAMPFORCE *ramp; |
| DICUSTOMFORCE *custom; |
| DIENVELOPE *envelope; |
| SDL_HapticConstant *hap_constant; |
| SDL_HapticPeriodic *hap_periodic; |
| SDL_HapticCondition *hap_condition; |
| SDL_HapticRamp *hap_ramp; |
| SDL_HapticCustom *hap_custom; |
| DWORD *axes; |
| |
| /* Set global stuff. */ |
| SDL_memset(dest, 0, sizeof(DIEFFECT)); |
| dest->dwSize = sizeof(DIEFFECT); /* Set the structure size. */ |
| dest->dwSamplePeriod = 0; /* Not used by us. */ |
| dest->dwGain = 10000; /* Gain is set globally, not locally. */ |
| dest->dwFlags = DIEFF_OBJECTOFFSETS; /* Seems obligatory. */ |
| |
| /* Envelope. */ |
| envelope = SDL_malloc(sizeof(DIENVELOPE)); |
| if (envelope == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(envelope, 0, sizeof(DIENVELOPE)); |
| dest->lpEnvelope = envelope; |
| envelope->dwSize = sizeof(DIENVELOPE); /* Always should be this. */ |
| |
| /* Axes. */ |
| dest->cAxes = haptic->naxes; |
| if (dest->cAxes > 0) { |
| axes = SDL_malloc(sizeof(DWORD) * dest->cAxes); |
| if (axes == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| axes[0] = haptic->hwdata->axes[0]; /* Always at least one axis. */ |
| if (dest->cAxes > 1) { |
| axes[1] = haptic->hwdata->axes[1]; |
| } |
| if (dest->cAxes > 2) { |
| axes[2] = haptic->hwdata->axes[2]; |
| } |
| dest->rgdwAxes = axes; |
| } |
| |
| |
| /* The big type handling switch, even bigger then Linux's version. */ |
| switch (src->type) { |
| case SDL_HAPTIC_CONSTANT: |
| hap_constant = &src->constant; |
| constant = SDL_malloc(sizeof(DICONSTANTFORCE)); |
| if (constant == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(constant, 0, sizeof(DICONSTANTFORCE)); |
| |
| /* Specifics */ |
| constant->lMagnitude = CONVERT(hap_constant->level); |
| dest->cbTypeSpecificParams = sizeof(DICONSTANTFORCE); |
| dest->lpvTypeSpecificParams = constant; |
| |
| /* Generics */ |
| dest->dwDuration = hap_constant->length * 1000; /* In microseconds. */ |
| dest->dwTriggerButton = DIGetTriggerButton(hap_constant->button); |
| dest->dwTriggerRepeatInterval = hap_constant->interval; |
| dest->dwStartDelay = hap_constant->delay * 1000; /* In microseconds. */ |
| |
| /* Direction. */ |
| if (SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes) |
| < 0) { |
| return -1; |
| } |
| |
| /* Envelope */ |
| if ((hap_constant->attack_length == 0) |
| && (hap_constant->fade_length == 0)) { |
| SDL_free(dest->lpEnvelope); |
| dest->lpEnvelope = NULL; |
| } else { |
| envelope->dwAttackLevel = CONVERT(hap_constant->attack_level); |
| envelope->dwAttackTime = hap_constant->attack_length * 1000; |
| envelope->dwFadeLevel = CONVERT(hap_constant->fade_level); |
| envelope->dwFadeTime = hap_constant->fade_length * 1000; |
| } |
| |
| break; |
| |
| case SDL_HAPTIC_SINE: |
| /* !!! FIXME: put this back when we have more bits in 2.1 */ |
| /* case SDL_HAPTIC_SQUARE: */ |
| case SDL_HAPTIC_TRIANGLE: |
| case SDL_HAPTIC_SAWTOOTHUP: |
| case SDL_HAPTIC_SAWTOOTHDOWN: |
| hap_periodic = &src->periodic; |
| periodic = SDL_malloc(sizeof(DIPERIODIC)); |
| if (periodic == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(periodic, 0, sizeof(DIPERIODIC)); |
| |
| /* Specifics */ |
| periodic->dwMagnitude = CONVERT(hap_periodic->magnitude); |
| periodic->lOffset = CONVERT(hap_periodic->offset); |
| periodic->dwPhase = hap_periodic->phase; |
| periodic->dwPeriod = hap_periodic->period * 1000; |
| dest->cbTypeSpecificParams = sizeof(DIPERIODIC); |
| dest->lpvTypeSpecificParams = periodic; |
| |
| /* Generics */ |
| dest->dwDuration = hap_periodic->length * 1000; /* In microseconds. */ |
| dest->dwTriggerButton = DIGetTriggerButton(hap_periodic->button); |
| dest->dwTriggerRepeatInterval = hap_periodic->interval; |
| dest->dwStartDelay = hap_periodic->delay * 1000; /* In microseconds. */ |
| |
| /* Direction. */ |
| if (SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes) |
| < 0) { |
| return -1; |
| } |
| |
| /* Envelope */ |
| if ((hap_periodic->attack_length == 0) |
| && (hap_periodic->fade_length == 0)) { |
| SDL_free(dest->lpEnvelope); |
| dest->lpEnvelope = NULL; |
| } else { |
| envelope->dwAttackLevel = CONVERT(hap_periodic->attack_level); |
| envelope->dwAttackTime = hap_periodic->attack_length * 1000; |
| envelope->dwFadeLevel = CONVERT(hap_periodic->fade_level); |
| envelope->dwFadeTime = hap_periodic->fade_length * 1000; |
| } |
| |
| break; |
| |
| case SDL_HAPTIC_SPRING: |
| case SDL_HAPTIC_DAMPER: |
| case SDL_HAPTIC_INERTIA: |
| case SDL_HAPTIC_FRICTION: |
| hap_condition = &src->condition; |
| condition = SDL_malloc(sizeof(DICONDITION) * dest->cAxes); |
| if (condition == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(condition, 0, sizeof(DICONDITION)); |
| |
| /* Specifics */ |
| for (i = 0; i < (int) dest->cAxes; i++) { |
| condition[i].lOffset = CONVERT(hap_condition->center[i]); |
| condition[i].lPositiveCoefficient = |
| CONVERT(hap_condition->right_coeff[i]); |
| condition[i].lNegativeCoefficient = |
| CONVERT(hap_condition->left_coeff[i]); |
| condition[i].dwPositiveSaturation = |
| CONVERT(hap_condition->right_sat[i]); |
| condition[i].dwNegativeSaturation = |
| CONVERT(hap_condition->left_sat[i]); |
| condition[i].lDeadBand = CONVERT(hap_condition->deadband[i]); |
| } |
| dest->cbTypeSpecificParams = sizeof(DICONDITION) * dest->cAxes; |
| dest->lpvTypeSpecificParams = condition; |
| |
| /* Generics */ |
| dest->dwDuration = hap_condition->length * 1000; /* In microseconds. */ |
| dest->dwTriggerButton = DIGetTriggerButton(hap_condition->button); |
| dest->dwTriggerRepeatInterval = hap_condition->interval; |
| dest->dwStartDelay = hap_condition->delay * 1000; /* In microseconds. */ |
| |
| /* Direction. */ |
| if (SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes) |
| < 0) { |
| return -1; |
| } |
| |
| /* Envelope - Not actually supported by most CONDITION implementations. */ |
| SDL_free(dest->lpEnvelope); |
| dest->lpEnvelope = NULL; |
| |
| break; |
| |
| case SDL_HAPTIC_RAMP: |
| hap_ramp = &src->ramp; |
| ramp = SDL_malloc(sizeof(DIRAMPFORCE)); |
| if (ramp == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(ramp, 0, sizeof(DIRAMPFORCE)); |
| |
| /* Specifics */ |
| ramp->lStart = CONVERT(hap_ramp->start); |
| ramp->lEnd = CONVERT(hap_ramp->end); |
| dest->cbTypeSpecificParams = sizeof(DIRAMPFORCE); |
| dest->lpvTypeSpecificParams = ramp; |
| |
| /* Generics */ |
| dest->dwDuration = hap_ramp->length * 1000; /* In microseconds. */ |
| dest->dwTriggerButton = DIGetTriggerButton(hap_ramp->button); |
| dest->dwTriggerRepeatInterval = hap_ramp->interval; |
| dest->dwStartDelay = hap_ramp->delay * 1000; /* In microseconds. */ |
| |
| /* Direction. */ |
| if (SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes) < 0) { |
| return -1; |
| } |
| |
| /* Envelope */ |
| if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) { |
| SDL_free(dest->lpEnvelope); |
| dest->lpEnvelope = NULL; |
| } else { |
| envelope->dwAttackLevel = CONVERT(hap_ramp->attack_level); |
| envelope->dwAttackTime = hap_ramp->attack_length * 1000; |
| envelope->dwFadeLevel = CONVERT(hap_ramp->fade_level); |
| envelope->dwFadeTime = hap_ramp->fade_length * 1000; |
| } |
| |
| break; |
| |
| case SDL_HAPTIC_CUSTOM: |
| hap_custom = &src->custom; |
| custom = SDL_malloc(sizeof(DICUSTOMFORCE)); |
| if (custom == NULL) { |
| return SDL_OutOfMemory(); |
| } |
| SDL_memset(custom, 0, sizeof(DICUSTOMFORCE)); |
| |
| /* Specifics */ |
| custom->cChannels = hap_custom->channels; |
| custom->dwSamplePeriod = hap_custom->period * 1000; |
| custom->cSamples = hap_custom->samples; |
| custom->rglForceData = |
| SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels); |
| for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) { /* Copy data. */ |
| custom->rglForceData[i] = CONVERT(hap_custom->data[i]); |
| } |
| dest->cbTypeSpecificParams = sizeof(DICUSTOMFORCE); |
| dest->lpvTypeSpecificParams = custom; |
| |
| /* Generics */ |
| dest->dwDuration = hap_custom->length * 1000; /* In microseconds. */ |
| dest->dwTriggerButton = DIGetTriggerButton(hap_custom->button); |
| dest->dwTriggerRepeatInterval = hap_custom->interval; |
| dest->dwStartDelay = hap_custom->delay * 1000; /* In microseconds. */ |
| |
| /* Direction. */ |
| if (SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes) < |
| 0) { |
| return -1; |
| } |
| |
| /* Envelope */ |
| if ((hap_custom->attack_length == 0) |
| && (hap_custom->fade_length == 0)) { |
| SDL_free(dest->lpEnvelope); |
| dest->lpEnvelope = NULL; |
| } else { |
| envelope->dwAttackLevel = CONVERT(hap_custom->attack_level); |
| envelope->dwAttackTime = hap_custom->attack_length * 1000; |
| envelope->dwFadeLevel = CONVERT(hap_custom->fade_level); |
| envelope->dwFadeTime = hap_custom->fade_length * 1000; |
| } |
| |
| break; |
| |
| |
| default: |
| return SDL_SetError("Haptic: Unknown effect type."); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Frees an DIEFFECT allocated by SDL_SYS_ToDIEFFECT. |
| */ |
| static void |
| SDL_SYS_HapticFreeDIEFFECT(DIEFFECT * effect, int type) |
| { |
| DICUSTOMFORCE *custom; |
| |
| SDL_free(effect->lpEnvelope); |
| effect->lpEnvelope = NULL; |
| SDL_free(effect->rgdwAxes); |
| effect->rgdwAxes = NULL; |
| if (effect->lpvTypeSpecificParams != NULL) { |
| if (type == SDL_HAPTIC_CUSTOM) { /* Must free the custom data. */ |
| custom = (DICUSTOMFORCE *) effect->lpvTypeSpecificParams; |
| SDL_free(custom->rglForceData); |
| custom->rglForceData = NULL; |
| } |
| SDL_free(effect->lpvTypeSpecificParams); |
| effect->lpvTypeSpecificParams = NULL; |
| } |
| SDL_free(effect->rglDirection); |
| effect->rglDirection = NULL; |
| } |
| |
| |
| /* |
| * Gets the effect type from the generic SDL haptic effect wrapper. |
| */ |
| static REFGUID |
| SDL_SYS_HapticEffectType(SDL_HapticEffect * effect) |
| { |
| switch (effect->type) { |
| case SDL_HAPTIC_CONSTANT: |
| return &GUID_ConstantForce; |
| |
| case SDL_HAPTIC_RAMP: |
| return &GUID_RampForce; |
| |
| /* !!! FIXME: put this back when we have more bits in 2.1 */ |
| /* case SDL_HAPTIC_SQUARE: |
| return &GUID_Square; */ |
| |
| case SDL_HAPTIC_SINE: |
| return &GUID_Sine; |
| |
| case SDL_HAPTIC_TRIANGLE: |
| return &GUID_Triangle; |
| |
| case SDL_HAPTIC_SAWTOOTHUP: |
| return &GUID_SawtoothUp; |
| |
| case SDL_HAPTIC_SAWTOOTHDOWN: |
| return &GUID_SawtoothDown; |
| |
| case SDL_HAPTIC_SPRING: |
| return &GUID_Spring; |
| |
| case SDL_HAPTIC_DAMPER: |
| return &GUID_Damper; |
| |
| case SDL_HAPTIC_INERTIA: |
| return &GUID_Inertia; |
| |
| case SDL_HAPTIC_FRICTION: |
| return &GUID_Friction; |
| |
| case SDL_HAPTIC_CUSTOM: |
| return &GUID_CustomForce; |
| |
| default: |
| return NULL; |
| } |
| } |
| |
| |
| /* |
| * Creates a new haptic effect. |
| */ |
| int |
| SDL_SYS_HapticNewEffect(SDL_Haptic * haptic, struct haptic_effect *effect, |
| SDL_HapticEffect * base) |
| { |
| HRESULT ret; |
| REFGUID type = SDL_SYS_HapticEffectType(base); |
| |
| if ((type == NULL) && (!haptic->hwdata->bXInputHaptic)) { |
| SDL_SetError("Haptic: Unknown effect type."); |
| goto err_hweffect; |
| } |
| |
| /* Alloc the effect. */ |
| effect->hweffect = (struct haptic_hweffect *) |
| SDL_malloc(sizeof(struct haptic_hweffect)); |
| if (effect->hweffect == NULL) { |
| SDL_OutOfMemory(); |
| goto err_hweffect; |
| } |
| |
| SDL_zerop(effect->hweffect); |
| |
| if (haptic->hwdata->bXInputHaptic) { |
| SDL_assert(base->type == SDL_HAPTIC_LEFTRIGHT); /* should catch this at higher level */ |
| return SDL_SYS_HapticUpdateEffect(haptic, effect, base); |
| } |
| |
| /* Get the effect. */ |
| if (SDL_SYS_ToDIEFFECT(haptic, &effect->hweffect->effect, base) < 0) { |
| goto err_effectdone; |
| } |
| |
| /* Create the actual effect. */ |
| ret = IDirectInputDevice8_CreateEffect(haptic->hwdata->device, type, |
| &effect->hweffect->effect, |
| &effect->hweffect->ref, NULL); |
| if (FAILED(ret)) { |
| DI_SetError("Unable to create effect", ret); |
| goto err_effectdone; |
| } |
| |
| return 0; |
| |
| err_effectdone: |
| SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, base->type); |
| err_hweffect: |
| SDL_free(effect->hweffect); |
| effect->hweffect = NULL; |
| return -1; |
| } |
| |
| |
| /* |
| * Updates an effect. |
| */ |
| int |
| SDL_SYS_HapticUpdateEffect(SDL_Haptic * haptic, |
| struct haptic_effect *effect, |
| SDL_HapticEffect * data) |
| { |
| HRESULT ret; |
| DWORD flags; |
| DIEFFECT temp; |
| |
| if (haptic->hwdata->bXInputHaptic) { |
| XINPUT_VIBRATION *vib = &effect->hweffect->vibration; |
| SDL_assert(data->type == SDL_HAPTIC_LEFTRIGHT); |
| vib->wLeftMotorSpeed = data->leftright.large_magnitude; |
| vib->wRightMotorSpeed = data->leftright.small_magnitude; |
| SDL_LockMutex(haptic->hwdata->mutex); |
| if (haptic->hwdata->stopTicks) { /* running right now? Update it. */ |
| XINPUTSETSTATE(haptic->hwdata->userid, vib); |
| } |
| SDL_UnlockMutex(haptic->hwdata->mutex); |
| return 0; |
| } |
| |
| /* Get the effect. */ |
| SDL_memset(&temp, 0, sizeof(DIEFFECT)); |
| if (SDL_SYS_ToDIEFFECT(haptic, &temp, data) < 0) { |
| goto err_update; |
| } |
| |
| /* Set the flags. Might be worthwhile to diff temp with loaded effect and |
| * only change those parameters. */ |
| flags = DIEP_DIRECTION | |
| DIEP_DURATION | |
| DIEP_ENVELOPE | |
| DIEP_STARTDELAY | |
| DIEP_TRIGGERBUTTON | |
| DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS; |
| |
| /* Create the actual effect. */ |
| ret = |
| IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags); |
| if (FAILED(ret)) { |
| DI_SetError("Unable to update effect", ret); |
| goto err_update; |
| } |
| |
| /* Copy it over. */ |
| SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, data->type); |
| SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(DIEFFECT)); |
| |
| return 0; |
| |
| err_update: |
| SDL_SYS_HapticFreeDIEFFECT(&temp, data->type); |
| return -1; |
| } |
| |
| |
| /* |
| * Runs an effect. |
| */ |
| int |
| SDL_SYS_HapticRunEffect(SDL_Haptic * haptic, struct haptic_effect *effect, |
| Uint32 iterations) |
| { |
| HRESULT ret; |
| DWORD iter; |
| |
| if (haptic->hwdata->bXInputHaptic) { |
| XINPUT_VIBRATION *vib = &effect->hweffect->vibration; |
| SDL_assert(effect->effect.type == SDL_HAPTIC_LEFTRIGHT); /* should catch this at higher level */ |
| SDL_LockMutex(haptic->hwdata->mutex); |
| if(effect->effect.leftright.length == SDL_HAPTIC_INFINITY || iterations == SDL_HAPTIC_INFINITY) { |
| haptic->hwdata->stopTicks = SDL_HAPTIC_INFINITY; |
| } else if ((!effect->effect.leftright.length) || (!iterations)) { |
| /* do nothing. Effect runs for zero milliseconds. */ |
| } else { |
| haptic->hwdata->stopTicks = SDL_GetTicks() + (effect->effect.leftright.length * iterations); |
| if ((haptic->hwdata->stopTicks == SDL_HAPTIC_INFINITY) || (haptic->hwdata->stopTicks == 0)) { |
| haptic->hwdata->stopTicks = 1; /* fix edge cases. */ |
| } |
| } |
| SDL_UnlockMutex(haptic->hwdata->mutex); |
| return (XINPUTSETSTATE(haptic->hwdata->userid, vib) == ERROR_SUCCESS) ? 0 : -1; |
| } |
| |
| /* Check if it's infinite. */ |
| if (iterations == SDL_HAPTIC_INFINITY) { |
| iter = INFINITE; |
| } else |
| iter = iterations; |
| |
| /* Run the effect. */ |
| ret = IDirectInputEffect_Start(effect->hweffect->ref, iter, 0); |
| if (FAILED(ret)) { |
| return DI_SetError("Running the effect", ret); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Stops an effect. |
| */ |
| int |
| SDL_SYS_HapticStopEffect(SDL_Haptic * haptic, struct haptic_effect *effect) |
| { |
| HRESULT ret; |
| |
| if (haptic->hwdata->bXInputHaptic) { |
| XINPUT_VIBRATION vibration = { 0, 0 }; |
| SDL_LockMutex(haptic->hwdata->mutex); |
| haptic->hwdata->stopTicks = 0; |
| SDL_UnlockMutex(haptic->hwdata->mutex); |
| return (XINPUTSETSTATE(haptic->hwdata->userid, &vibration) == ERROR_SUCCESS) ? 0 : -1; |
| } |
| |
| ret = IDirectInputEffect_Stop(effect->hweffect->ref); |
| if (FAILED(ret)) { |
| return DI_SetError("Unable to stop effect", ret); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Frees the effect. |
| */ |
| void |
| SDL_SYS_HapticDestroyEffect(SDL_Haptic * haptic, struct haptic_effect *effect) |
| { |
| HRESULT ret; |
| |
| if (haptic->hwdata->bXInputHaptic) { |
| SDL_SYS_HapticStopEffect(haptic, effect); |
| } else { |
| ret = IDirectInputEffect_Unload(effect->hweffect->ref); |
| if (FAILED(ret)) { |
| DI_SetError("Removing effect from the device", ret); |
| } |
| SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, |
| effect->effect.type); |
| } |
| SDL_free(effect->hweffect); |
| effect->hweffect = NULL; |
| } |
| |
| |
| /* |
| * Gets the status of a haptic effect. |
| */ |
| int |
| SDL_SYS_HapticGetEffectStatus(SDL_Haptic * haptic, |
| struct haptic_effect *effect) |
| { |
| HRESULT ret; |
| DWORD status; |
| |
| ret = IDirectInputEffect_GetEffectStatus(effect->hweffect->ref, &status); |
| if (FAILED(ret)) { |
| return DI_SetError("Getting effect status", ret); |
| } |
| |
| if (status == 0) |
| return SDL_FALSE; |
| return SDL_TRUE; |
| } |
| |
| |
| /* |
| * Sets the gain. |
| */ |
| int |
| SDL_SYS_HapticSetGain(SDL_Haptic * haptic, int gain) |
| { |
| HRESULT ret; |
| DIPROPDWORD dipdw; |
| |
| /* Create the weird structure thingy. */ |
| dipdw.diph.dwSize = sizeof(DIPROPDWORD); |
| dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); |
| dipdw.diph.dwObj = 0; |
| dipdw.diph.dwHow = DIPH_DEVICE; |
| dipdw.dwData = gain * 100; /* 0 to 10,000 */ |
| |
| /* Try to set the autocenter. */ |
| ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device, |
| DIPROP_FFGAIN, &dipdw.diph); |
| if (FAILED(ret)) { |
| return DI_SetError("Setting gain", ret); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Sets the autocentering. |
| */ |
| int |
| SDL_SYS_HapticSetAutocenter(SDL_Haptic * haptic, int autocenter) |
| { |
| HRESULT ret; |
| DIPROPDWORD dipdw; |
| |
| /* Create the weird structure thingy. */ |
| dipdw.diph.dwSize = sizeof(DIPROPDWORD); |
| dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); |
| dipdw.diph.dwObj = 0; |
| dipdw.diph.dwHow = DIPH_DEVICE; |
| dipdw.dwData = (autocenter == 0) ? DIPROPAUTOCENTER_OFF : |
| DIPROPAUTOCENTER_ON; |
| |
| /* Try to set the autocenter. */ |
| ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device, |
| DIPROP_AUTOCENTER, &dipdw.diph); |
| if (FAILED(ret)) { |
| return DI_SetError("Setting autocenter", ret); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Pauses the device. |
| */ |
| int |
| SDL_SYS_HapticPause(SDL_Haptic * haptic) |
| { |
| HRESULT ret; |
| |
| /* Pause the device. */ |
| ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device, |
| DISFFC_PAUSE); |
| if (FAILED(ret)) { |
| return DI_SetError("Pausing the device", ret); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Pauses the device. |
| */ |
| int |
| SDL_SYS_HapticUnpause(SDL_Haptic * haptic) |
| { |
| HRESULT ret; |
| |
| /* Unpause the device. */ |
| ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device, |
| DISFFC_CONTINUE); |
| if (FAILED(ret)) { |
| return DI_SetError("Pausing the device", ret); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Stops all the playing effects on the device. |
| */ |
| int |
| SDL_SYS_HapticStopAll(SDL_Haptic * haptic) |
| { |
| HRESULT ret; |
| |
| if (haptic->hwdata->bXInputHaptic) { |
| XINPUT_VIBRATION vibration = { 0, 0 }; |
| SDL_LockMutex(haptic->hwdata->mutex); |
| haptic->hwdata->stopTicks = 0; |
| SDL_UnlockMutex(haptic->hwdata->mutex); |
| return (XINPUTSETSTATE(haptic->hwdata->userid, &vibration) == ERROR_SUCCESS) ? 0 : -1; |
| } |
| |
| /* Try to stop the effects. */ |
| ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device, |
| DISFFC_STOPALL); |
| if (FAILED(ret)) { |
| return DI_SetError("Stopping the device", ret); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* !!! FIXME: this is a hack, remove this later. */ |
| /* Since XInput doesn't offer a way to vibrate for X time, we hook into |
| * SDL_PumpEvents() to check if it's time to stop vibrating with some |
| * frequency. |
| * In practice, this works for 99% of use cases. But in an ideal world, |
| * we do this in a separate thread so that: |
| * - we aren't bound to when the app chooses to pump the event queue. |
| * - we aren't adding more polling to the event queue |
| * - we can emulate all the haptic effects correctly (start on a delay, |
| * mix multiple effects, etc). |
| * |
| * Mostly, this is here to get rumbling to work, and all the other features |
| * are absent in the XInput path for now. :( |
| */ |
| static int SDLCALL |
| SDL_RunXInputHaptic(void *arg) |
| { |
| struct haptic_hwdata *hwdata = (struct haptic_hwdata *) arg; |
| |
| while (!hwdata->stopThread) { |
| SDL_Delay(50); |
| SDL_LockMutex(hwdata->mutex); |
| /* If we're currently running and need to stop... */ |
| if (hwdata->stopTicks) { |
| if ((hwdata->stopTicks != SDL_HAPTIC_INFINITY) && SDL_TICKS_PASSED(SDL_GetTicks(), hwdata->stopTicks)) { |
| XINPUT_VIBRATION vibration = { 0, 0 }; |
| hwdata->stopTicks = 0; |
| XINPUTSETSTATE(hwdata->userid, &vibration); |
| } |
| } |
| SDL_UnlockMutex(hwdata->mutex); |
| } |
| |
| return 0; |
| } |
| |
| #endif /* SDL_HAPTIC_DINPUT */ |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |