Path: blob/master/thirdparty/sdl/haptic/windows/SDL_dinputhaptic.c
9912 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#include "../SDL_syshaptic.h"2324#ifdef SDL_HAPTIC_DINPUT2526#include "SDL_windowshaptic_c.h"27#include "SDL_dinputhaptic_c.h"28#include "../../joystick/windows/SDL_windowsjoystick_c.h"2930/*31* External stuff.32*/33#ifdef SDL_VIDEO_DRIVER_WINDOWS34extern HWND SDL_HelperWindow;35#else36static const HWND SDL_HelperWindow = NULL;37#endif3839/*40* Internal stuff.41*/42static bool coinitialized = false;43static LPDIRECTINPUT8 dinput = NULL;4445/*46* Like SDL_SetError but for DX error codes.47*/48static bool DI_SetError(const char *str, HRESULT err)49{50return SDL_SetError("Haptic error %s", str);51}5253/*54* Callback to find the haptic devices.55*/56static BOOL CALLBACK EnumHapticsCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext)57{58(void)pContext;59SDL_DINPUT_HapticMaybeAddDevice(pdidInstance);60return DIENUM_CONTINUE; // continue enumerating61}6263bool SDL_DINPUT_HapticInit(void)64{65HRESULT ret;66HINSTANCE instance;67DWORD devClass;6869if (dinput != NULL) { // Already open.70return SDL_SetError("Haptic: SubSystem already open.");71}7273if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true)) {74// In some environments, IDirectInput8_Initialize / _EnumDevices can take a minute even with no controllers.75return true;76}7778ret = WIN_CoInitialize();79if (FAILED(ret)) {80return DI_SetError("Coinitialize", ret);81}8283coinitialized = true;8485ret = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER,86&IID_IDirectInput8, (LPVOID *)&dinput);87if (FAILED(ret)) {88SDL_SYS_HapticQuit();89return DI_SetError("CoCreateInstance", ret);90}9192// Because we used CoCreateInstance, we need to Initialize it, first.93instance = GetModuleHandle(NULL);94if (!instance) {95SDL_SYS_HapticQuit();96return SDL_SetError("GetModuleHandle() failed with error code %lu.",97GetLastError());98}99ret = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION);100if (FAILED(ret)) {101SDL_SYS_HapticQuit();102return DI_SetError("Initializing DirectInput device", ret);103}104105// Look for haptic devices.106for (devClass = DI8DEVCLASS_DEVICE; devClass <= DI8DEVCLASS_GAMECTRL; devClass++) {107if (devClass == DI8DEVCLASS_GAMECTRL && SDL_WasInit(SDL_INIT_JOYSTICK)) {108// The joystick subsystem will manage adding DInput joystick haptic devices109continue;110}111112ret = IDirectInput8_EnumDevices(dinput,113devClass,114EnumHapticsCallback,115NULL,116DIEDFL_FORCEFEEDBACK |117DIEDFL_ATTACHEDONLY);118if (FAILED(ret)) {119SDL_SYS_HapticQuit();120return DI_SetError("Enumerating DirectInput devices", ret);121}122}123124return true;125}126127bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)128{129HRESULT ret;130LPDIRECTINPUTDEVICE8 device;131const DWORD needflags = DIDC_ATTACHED | DIDC_FORCEFEEDBACK;132DIDEVCAPS capabilities;133SDL_hapticlist_item *item = NULL;134135if (!dinput) {136return false; // not initialized. We'll pick these up on enumeration if we init later.137}138139// Make sure we don't already have it140for (item = SDL_hapticlist; item; item = item->next) {141if (SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) {142return false; // Already added143}144}145146// Open the device147ret = IDirectInput8_CreateDevice(dinput, &pdidInstance->guidInstance, &device, NULL);148if (FAILED(ret)) {149// DI_SetError("Creating DirectInput device",ret);150return false;151}152153// Get capabilities.154SDL_zero(capabilities);155capabilities.dwSize = sizeof(DIDEVCAPS);156ret = IDirectInputDevice8_GetCapabilities(device, &capabilities);157IDirectInputDevice8_Release(device);158if (FAILED(ret)) {159// DI_SetError("Getting device capabilities",ret);160return false;161}162163if ((capabilities.dwFlags & needflags) != needflags) {164return false; // not a device we can use.165}166167item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));168if (!item) {169return false;170}171172item->instance_id = SDL_GetNextObjectID();173item->name = WIN_StringToUTF8(pdidInstance->tszProductName);174if (!item->name) {175SDL_free(item);176return false;177}178179// Copy the instance over, useful for creating devices.180SDL_memcpy(&item->instance, pdidInstance, sizeof(DIDEVICEINSTANCE));181SDL_memcpy(&item->capabilities, &capabilities, sizeof(capabilities));182183return SDL_SYS_AddHapticDevice(item);184}185186bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)187{188SDL_hapticlist_item *item;189SDL_hapticlist_item *prev = NULL;190191if (!dinput) {192return false; // not initialized, ignore this.193}194195for (item = SDL_hapticlist; item; item = item->next) {196if (SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) {197// found it, remove it.198return SDL_SYS_RemoveHapticDevice(prev, item);199}200prev = item;201}202return false;203}204205/*206* Callback to get supported axes.207*/208static BOOL CALLBACK DI_DeviceObjectCallback(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID pvRef)209{210SDL_Haptic *haptic = (SDL_Haptic *)pvRef;211212if ((dev->dwType & DIDFT_AXIS) && (dev->dwFlags & DIDOI_FFACTUATOR)) {213const GUID *guid = &dev->guidType;214DWORD offset = 0;215if (WIN_IsEqualGUID(guid, &GUID_XAxis)) {216offset = DIJOFS_X;217} else if (WIN_IsEqualGUID(guid, &GUID_YAxis)) {218offset = DIJOFS_Y;219} else if (WIN_IsEqualGUID(guid, &GUID_ZAxis)) {220offset = DIJOFS_Z;221} else if (WIN_IsEqualGUID(guid, &GUID_RxAxis)) {222offset = DIJOFS_RX;223} else if (WIN_IsEqualGUID(guid, &GUID_RyAxis)) {224offset = DIJOFS_RY;225} else if (WIN_IsEqualGUID(guid, &GUID_RzAxis)) {226offset = DIJOFS_RZ;227} else {228return DIENUM_CONTINUE; // can't use this, go on.229}230231haptic->hwdata->axes[haptic->naxes] = offset;232haptic->naxes++;233234// Currently using the artificial limit of 3 axes.235if (haptic->naxes >= 3) {236return DIENUM_STOP;237}238}239240return DIENUM_CONTINUE;241}242243/*244* Callback to get all supported effects.245*/246#define EFFECT_TEST(e, s) \247if (WIN_IsEqualGUID(&pei->guid, &(e))) \248haptic->supported |= (s)249static BOOL CALLBACK DI_EffectCallback(LPCDIEFFECTINFO pei, LPVOID pv)250{251// Prepare the haptic device.252SDL_Haptic *haptic = (SDL_Haptic *)pv;253254// Get supported.255EFFECT_TEST(GUID_Spring, SDL_HAPTIC_SPRING);256EFFECT_TEST(GUID_Damper, SDL_HAPTIC_DAMPER);257EFFECT_TEST(GUID_Inertia, SDL_HAPTIC_INERTIA);258EFFECT_TEST(GUID_Friction, SDL_HAPTIC_FRICTION);259EFFECT_TEST(GUID_ConstantForce, SDL_HAPTIC_CONSTANT);260EFFECT_TEST(GUID_CustomForce, SDL_HAPTIC_CUSTOM);261EFFECT_TEST(GUID_Sine, SDL_HAPTIC_SINE);262EFFECT_TEST(GUID_Square, SDL_HAPTIC_SQUARE);263EFFECT_TEST(GUID_Triangle, SDL_HAPTIC_TRIANGLE);264EFFECT_TEST(GUID_SawtoothUp, SDL_HAPTIC_SAWTOOTHUP);265EFFECT_TEST(GUID_SawtoothDown, SDL_HAPTIC_SAWTOOTHDOWN);266EFFECT_TEST(GUID_RampForce, SDL_HAPTIC_RAMP);267268// Check for more.269return DIENUM_CONTINUE;270}271272/*273* Opens the haptic device.274*275* Steps:276* - Set cooperative level.277* - Set data format.278* - Acquire exclusiveness.279* - Reset actuators.280* - Get supported features.281*/282static bool SDL_DINPUT_HapticOpenFromDevice(SDL_Haptic *haptic, LPDIRECTINPUTDEVICE8 device8, bool is_joystick)283{284HRESULT ret;285DIPROPDWORD dipdw;286287// Allocate the hwdata288haptic->hwdata = (struct haptic_hwdata *)SDL_calloc(1, sizeof(*haptic->hwdata));289if (!haptic->hwdata) {290return false;291}292293// We'll use the device8 from now on.294haptic->hwdata->device = device8;295haptic->hwdata->is_joystick = is_joystick;296297/* !!! FIXME: opening a haptic device here first will make an attempt to298!!! FIXME: SDL_OpenJoystick() that same device fail later, since we299!!! FIXME: have it open in exclusive mode. But this will allow300!!! FIXME: SDL_OpenJoystick() followed by SDL_OpenHapticFromJoystick()301!!! FIXME: to work, and that's probably the common case. Still,302!!! FIXME: ideally, We need to unify the opening code. */303304if (!is_joystick) { // if is_joystick, we already set this up elsewhere.305// Grab it exclusively to use force feedback stuff.306ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device,307SDL_HelperWindow,308DISCL_EXCLUSIVE |309DISCL_BACKGROUND);310if (FAILED(ret)) {311DI_SetError("Setting cooperative level to exclusive", ret);312goto acquire_err;313}314315// Set data format.316ret = IDirectInputDevice8_SetDataFormat(haptic->hwdata->device,317&SDL_c_dfDIJoystick2);318if (FAILED(ret)) {319DI_SetError("Setting data format", ret);320goto acquire_err;321}322323// Acquire the device.324ret = IDirectInputDevice8_Acquire(haptic->hwdata->device);325if (FAILED(ret)) {326DI_SetError("Acquiring DirectInput device", ret);327goto acquire_err;328}329}330331// Get number of axes.332ret = IDirectInputDevice8_EnumObjects(haptic->hwdata->device,333DI_DeviceObjectCallback,334haptic, DIDFT_AXIS);335if (FAILED(ret)) {336DI_SetError("Getting device axes", ret);337goto acquire_err;338}339340// Reset all actuators - just in case.341ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,342DISFFC_RESET);343if (FAILED(ret)) {344DI_SetError("Resetting device", ret);345goto acquire_err;346}347348// Enabling actuators.349ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,350DISFFC_SETACTUATORSON);351if (FAILED(ret)) {352DI_SetError("Enabling actuators", ret);353goto acquire_err;354}355356// Get supported effects.357ret = IDirectInputDevice8_EnumEffects(haptic->hwdata->device,358DI_EffectCallback, haptic,359DIEFT_ALL);360if (FAILED(ret)) {361DI_SetError("Enumerating supported effects", ret);362goto acquire_err;363}364if (haptic->supported == 0) { // Error since device supports nothing.365SDL_SetError("Haptic: Internal error on finding supported effects.");366goto acquire_err;367}368369// Check autogain and autocenter.370dipdw.diph.dwSize = sizeof(DIPROPDWORD);371dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);372dipdw.diph.dwObj = 0;373dipdw.diph.dwHow = DIPH_DEVICE;374dipdw.dwData = 10000;375ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,376DIPROP_FFGAIN, &dipdw.diph);377if (!FAILED(ret)) { // Gain is supported.378haptic->supported |= SDL_HAPTIC_GAIN;379}380dipdw.diph.dwObj = 0;381dipdw.diph.dwHow = DIPH_DEVICE;382dipdw.dwData = DIPROPAUTOCENTER_OFF;383ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,384DIPROP_AUTOCENTER, &dipdw.diph);385if (!FAILED(ret)) { // Autocenter is supported.386haptic->supported |= SDL_HAPTIC_AUTOCENTER;387}388389// Status is always supported.390haptic->supported |= SDL_HAPTIC_STATUS | SDL_HAPTIC_PAUSE;391392// Check maximum effects.393haptic->neffects = 128; /* This is not actually supported as thus under windows,394there is no way to tell the number of EFFECTS that a395device can hold, so we'll just use a "random" number396instead and put warnings in SDL_haptic.h */397haptic->nplaying = 128; // Even more impossible to get this then neffects.398399// Prepare effects memory.400haptic->effects = (struct haptic_effect *)401SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects);402if (!haptic->effects) {403goto acquire_err;404}405// Clear the memory406SDL_memset(haptic->effects, 0,407sizeof(struct haptic_effect) * haptic->neffects);408409return true;410411// Error handling412acquire_err:413IDirectInputDevice8_Unacquire(haptic->hwdata->device);414return false;415}416417bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)418{419HRESULT ret;420LPDIRECTINPUTDEVICE8 device;421422// Open the device423ret = IDirectInput8_CreateDevice(dinput, &item->instance.guidInstance,424&device, NULL);425if (FAILED(ret)) {426DI_SetError("Creating DirectInput device", ret);427return false;428}429430if (!SDL_DINPUT_HapticOpenFromDevice(haptic, device, false)) {431IDirectInputDevice8_Release(device);432return false;433}434return true;435}436437bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)438{439HRESULT ret;440DIDEVICEINSTANCE hap_instance, joy_instance;441442hap_instance.dwSize = sizeof(DIDEVICEINSTANCE);443joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);444445// Get the device instances.446ret = IDirectInputDevice8_GetDeviceInfo(haptic->hwdata->device,447&hap_instance);448if (FAILED(ret)) {449return false;450}451ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice,452&joy_instance);453if (FAILED(ret)) {454return false;455}456457return (WIN_IsEqualGUID(&hap_instance.guidInstance, &joy_instance.guidInstance) == TRUE);458}459460bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)461{462SDL_hapticlist_item *item;463HRESULT ret;464DIDEVICEINSTANCE joy_instance;465466joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);467ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, &joy_instance);468if (FAILED(ret)) {469return false;470}471472// Since it comes from a joystick we have to try to match it with a haptic device on our haptic list.473for (item = SDL_hapticlist; item; item = item->next) {474if (WIN_IsEqualGUID(&item->instance.guidInstance, &joy_instance.guidInstance)) {475haptic->instance_id = item->instance_id;476haptic->name = SDL_strdup(item->name);477return SDL_DINPUT_HapticOpenFromDevice(haptic, joystick->hwdata->InputDevice, true);478}479}480481return SDL_SetError("Couldn't find joystick in haptic device list");482}483484void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)485{486IDirectInputDevice8_Unacquire(haptic->hwdata->device);487488// Only release if isn't grabbed by a joystick.489if (haptic->hwdata->is_joystick == 0) {490IDirectInputDevice8_Release(haptic->hwdata->device);491}492}493494void SDL_DINPUT_HapticQuit(void)495{496if (dinput != NULL) {497IDirectInput8_Release(dinput);498dinput = NULL;499}500501if (coinitialized) {502WIN_CoUninitialize();503coinitialized = false;504}505}506507/*508* Converts an SDL trigger button to an DIEFFECT trigger button.509*/510static DWORD DIGetTriggerButton(Uint16 button)511{512DWORD dwTriggerButton;513514dwTriggerButton = DIEB_NOTRIGGER;515516if (button != 0) {517dwTriggerButton = DIJOFS_BUTTON(button - 1);518}519520return dwTriggerButton;521}522523/*524* Sets the direction.525*/526static bool SDL_SYS_SetDirection(DIEFFECT *effect, const SDL_HapticDirection *dir, int naxes)527{528LONG *rglDir;529530// Handle no axes a part.531if (naxes == 0) {532effect->dwFlags |= DIEFF_SPHERICAL; // Set as default.533effect->rglDirection = NULL;534return true;535}536537// Has axes.538rglDir = (LONG *)SDL_malloc(sizeof(LONG) * naxes);539if (!rglDir) {540return false;541}542SDL_memset(rglDir, 0, sizeof(LONG) * naxes);543effect->rglDirection = rglDir;544545switch (dir->type) {546case SDL_HAPTIC_POLAR:547effect->dwFlags |= DIEFF_POLAR;548rglDir[0] = dir->dir[0];549return true;550case SDL_HAPTIC_CARTESIAN:551effect->dwFlags |= DIEFF_CARTESIAN;552rglDir[0] = dir->dir[0];553if (naxes > 1) {554rglDir[1] = dir->dir[1];555}556if (naxes > 2) {557rglDir[2] = dir->dir[2];558}559return true;560case SDL_HAPTIC_SPHERICAL:561effect->dwFlags |= DIEFF_SPHERICAL;562rglDir[0] = dir->dir[0];563if (naxes > 1) {564rglDir[1] = dir->dir[1];565}566if (naxes > 2) {567rglDir[2] = dir->dir[2];568}569return true;570case SDL_HAPTIC_STEERING_AXIS:571effect->dwFlags |= DIEFF_CARTESIAN;572rglDir[0] = 0;573return true;574575default:576return SDL_SetError("Haptic: Unknown direction type.");577}578}579580// Clamps and converts.581#define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)582// Just converts.583#define CONVERT(x) (((x)*10000) / 0x7FFF)584/*585* Creates the DIEFFECT from a SDL_HapticEffect.586*/587static bool SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest,588const SDL_HapticEffect *src)589{590int i;591DICONSTANTFORCE *constant;592DIPERIODIC *periodic;593DICONDITION *condition; // Actually an array of conditions - one per axis.594DIRAMPFORCE *ramp;595DICUSTOMFORCE *custom;596DIENVELOPE *envelope;597const SDL_HapticConstant *hap_constant;598const SDL_HapticPeriodic *hap_periodic;599const SDL_HapticCondition *hap_condition;600const SDL_HapticRamp *hap_ramp;601const SDL_HapticCustom *hap_custom;602DWORD *axes;603604// Set global stuff.605SDL_memset(dest, 0, sizeof(DIEFFECT));606dest->dwSize = sizeof(DIEFFECT); // Set the structure size.607dest->dwSamplePeriod = 0; // Not used by us.608dest->dwGain = 10000; // Gain is set globally, not locally.609dest->dwFlags = DIEFF_OBJECTOFFSETS; // Seems obligatory.610611// Envelope.612envelope = (DIENVELOPE *)SDL_calloc(1, sizeof(DIENVELOPE));613if (!envelope) {614return false;615}616dest->lpEnvelope = envelope;617envelope->dwSize = sizeof(DIENVELOPE); // Always should be this.618619// Axes.620if (src->constant.direction.type == SDL_HAPTIC_STEERING_AXIS) {621dest->cAxes = 1;622} else {623dest->cAxes = haptic->naxes;624}625if (dest->cAxes > 0) {626axes = (DWORD *)SDL_malloc(sizeof(DWORD) * dest->cAxes);627if (!axes) {628return false;629}630axes[0] = haptic->hwdata->axes[0]; // Always at least one axis.631if (dest->cAxes > 1) {632axes[1] = haptic->hwdata->axes[1];633}634if (dest->cAxes > 2) {635axes[2] = haptic->hwdata->axes[2];636}637dest->rgdwAxes = axes;638}639640// The big type handling switch, even bigger than Linux's version.641switch (src->type) {642case SDL_HAPTIC_CONSTANT:643hap_constant = &src->constant;644constant = (DICONSTANTFORCE *)SDL_calloc(1, sizeof(DICONSTANTFORCE));645if (!constant) {646return false;647}648649// Specifics650constant->lMagnitude = CONVERT(hap_constant->level);651dest->cbTypeSpecificParams = sizeof(DICONSTANTFORCE);652dest->lpvTypeSpecificParams = constant;653654// Generics655dest->dwDuration = hap_constant->length * 1000UL; // In microseconds.656dest->dwTriggerButton = DIGetTriggerButton(hap_constant->button);657dest->dwTriggerRepeatInterval = hap_constant->interval;658dest->dwStartDelay = hap_constant->delay * 1000UL; // In microseconds.659660// Direction.661if (!SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes)) {662return false;663}664665// Envelope666if ((hap_constant->attack_length == 0) && (hap_constant->fade_length == 0)) {667SDL_free(dest->lpEnvelope);668dest->lpEnvelope = NULL;669} else {670envelope->dwAttackLevel = CCONVERT(hap_constant->attack_level);671envelope->dwAttackTime = hap_constant->attack_length * 1000UL;672envelope->dwFadeLevel = CCONVERT(hap_constant->fade_level);673envelope->dwFadeTime = hap_constant->fade_length * 1000UL;674}675676break;677678case SDL_HAPTIC_SINE:679case SDL_HAPTIC_SQUARE:680case SDL_HAPTIC_TRIANGLE:681case SDL_HAPTIC_SAWTOOTHUP:682case SDL_HAPTIC_SAWTOOTHDOWN:683hap_periodic = &src->periodic;684periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(DIPERIODIC));685if (!periodic) {686return false;687}688689// Specifics690periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude));691periodic->lOffset = CONVERT(hap_periodic->offset);692periodic->dwPhase =693(hap_periodic->phase + (hap_periodic->magnitude < 0 ? 18000 : 0)) % 36000;694periodic->dwPeriod = hap_periodic->period * 1000;695dest->cbTypeSpecificParams = sizeof(DIPERIODIC);696dest->lpvTypeSpecificParams = periodic;697698// Generics699dest->dwDuration = hap_periodic->length * 1000UL; // In microseconds.700dest->dwTriggerButton = DIGetTriggerButton(hap_periodic->button);701dest->dwTriggerRepeatInterval = hap_periodic->interval;702dest->dwStartDelay = hap_periodic->delay * 1000UL; // In microseconds.703704// Direction.705if (!SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes)) {706return false;707}708709// Envelope710if ((hap_periodic->attack_length == 0) && (hap_periodic->fade_length == 0)) {711SDL_free(dest->lpEnvelope);712dest->lpEnvelope = NULL;713} else {714envelope->dwAttackLevel = CCONVERT(hap_periodic->attack_level);715envelope->dwAttackTime = hap_periodic->attack_length * 1000UL;716envelope->dwFadeLevel = CCONVERT(hap_periodic->fade_level);717envelope->dwFadeTime = hap_periodic->fade_length * 1000UL;718}719720break;721722case SDL_HAPTIC_SPRING:723case SDL_HAPTIC_DAMPER:724case SDL_HAPTIC_INERTIA:725case SDL_HAPTIC_FRICTION:726hap_condition = &src->condition;727condition = (DICONDITION *)SDL_calloc(dest->cAxes, sizeof(DICONDITION));728if (!condition) {729return false;730}731732// Specifics733for (i = 0; i < (int)dest->cAxes; i++) {734condition[i].lOffset = CONVERT(hap_condition->center[i]);735condition[i].lPositiveCoefficient =736CONVERT(hap_condition->right_coeff[i]);737condition[i].lNegativeCoefficient =738CONVERT(hap_condition->left_coeff[i]);739condition[i].dwPositiveSaturation =740CCONVERT(hap_condition->right_sat[i] / 2);741condition[i].dwNegativeSaturation =742CCONVERT(hap_condition->left_sat[i] / 2);743condition[i].lDeadBand = CCONVERT(hap_condition->deadband[i] / 2);744}745dest->cbTypeSpecificParams = sizeof(DICONDITION) * dest->cAxes;746dest->lpvTypeSpecificParams = condition;747748// Generics749dest->dwDuration = hap_condition->length * 1000UL; // In microseconds.750dest->dwTriggerButton = DIGetTriggerButton(hap_condition->button);751dest->dwTriggerRepeatInterval = hap_condition->interval;752dest->dwStartDelay = hap_condition->delay * 1000UL; // In microseconds.753754// Direction.755if (!SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes)) {756return false;757}758759// Envelope - Not actually supported by most CONDITION implementations.760SDL_free(dest->lpEnvelope);761dest->lpEnvelope = NULL;762763break;764765case SDL_HAPTIC_RAMP:766hap_ramp = &src->ramp;767ramp = (DIRAMPFORCE *)SDL_calloc(1, sizeof(DIRAMPFORCE));768if (!ramp) {769return false;770}771772// Specifics773ramp->lStart = CONVERT(hap_ramp->start);774ramp->lEnd = CONVERT(hap_ramp->end);775dest->cbTypeSpecificParams = sizeof(DIRAMPFORCE);776dest->lpvTypeSpecificParams = ramp;777778// Generics779dest->dwDuration = hap_ramp->length * 1000UL; // In microseconds.780dest->dwTriggerButton = DIGetTriggerButton(hap_ramp->button);781dest->dwTriggerRepeatInterval = hap_ramp->interval;782dest->dwStartDelay = hap_ramp->delay * 1000UL; // In microseconds.783784// Direction.785if (!SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes)) {786return false;787}788789// Envelope790if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) {791SDL_free(dest->lpEnvelope);792dest->lpEnvelope = NULL;793} else {794envelope->dwAttackLevel = CCONVERT(hap_ramp->attack_level);795envelope->dwAttackTime = hap_ramp->attack_length * 1000UL;796envelope->dwFadeLevel = CCONVERT(hap_ramp->fade_level);797envelope->dwFadeTime = hap_ramp->fade_length * 1000UL;798}799800break;801802case SDL_HAPTIC_CUSTOM:803hap_custom = &src->custom;804custom = (DICUSTOMFORCE *)SDL_calloc(1, sizeof(DICUSTOMFORCE));805if (!custom) {806return false;807}808809// Specifics810custom->cChannels = hap_custom->channels;811custom->dwSamplePeriod = hap_custom->period * 1000UL;812custom->cSamples = hap_custom->samples;813custom->rglForceData = (LPLONG)SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels);814for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) { // Copy data.815custom->rglForceData[i] = CCONVERT(hap_custom->data[i]);816}817dest->cbTypeSpecificParams = sizeof(DICUSTOMFORCE);818dest->lpvTypeSpecificParams = custom;819820// Generics821dest->dwDuration = hap_custom->length * 1000UL; // In microseconds.822dest->dwTriggerButton = DIGetTriggerButton(hap_custom->button);823dest->dwTriggerRepeatInterval = hap_custom->interval;824dest->dwStartDelay = hap_custom->delay * 1000UL; // In microseconds.825826// Direction.827if (!SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes)) {828return false;829}830831// Envelope832if ((hap_custom->attack_length == 0) && (hap_custom->fade_length == 0)) {833SDL_free(dest->lpEnvelope);834dest->lpEnvelope = NULL;835} else {836envelope->dwAttackLevel = CCONVERT(hap_custom->attack_level);837envelope->dwAttackTime = hap_custom->attack_length * 1000UL;838envelope->dwFadeLevel = CCONVERT(hap_custom->fade_level);839envelope->dwFadeTime = hap_custom->fade_length * 1000UL;840}841842break;843844default:845return SDL_SetError("Haptic: Unknown effect type.");846}847848return true;849}850851/*852* Frees an DIEFFECT allocated by SDL_SYS_ToDIEFFECT.853*/854static void SDL_SYS_HapticFreeDIEFFECT(DIEFFECT *effect, int type)855{856DICUSTOMFORCE *custom;857858SDL_free(effect->lpEnvelope);859effect->lpEnvelope = NULL;860SDL_free(effect->rgdwAxes);861effect->rgdwAxes = NULL;862if (effect->lpvTypeSpecificParams) {863if (type == SDL_HAPTIC_CUSTOM) { // Must free the custom data.864custom = (DICUSTOMFORCE *)effect->lpvTypeSpecificParams;865SDL_free(custom->rglForceData);866custom->rglForceData = NULL;867}868SDL_free(effect->lpvTypeSpecificParams);869effect->lpvTypeSpecificParams = NULL;870}871SDL_free(effect->rglDirection);872effect->rglDirection = NULL;873}874875/*876* Gets the effect type from the generic SDL haptic effect wrapper.877*/878// NOLINTNEXTLINE(readability-const-return-type): Can't fix Windows' headers879static REFGUID SDL_SYS_HapticEffectType(const SDL_HapticEffect *effect)880{881switch (effect->type) {882case SDL_HAPTIC_CONSTANT:883return &GUID_ConstantForce;884885case SDL_HAPTIC_RAMP:886return &GUID_RampForce;887888case SDL_HAPTIC_SQUARE:889return &GUID_Square;890891case SDL_HAPTIC_SINE:892return &GUID_Sine;893894case SDL_HAPTIC_TRIANGLE:895return &GUID_Triangle;896897case SDL_HAPTIC_SAWTOOTHUP:898return &GUID_SawtoothUp;899900case SDL_HAPTIC_SAWTOOTHDOWN:901return &GUID_SawtoothDown;902903case SDL_HAPTIC_SPRING:904return &GUID_Spring;905906case SDL_HAPTIC_DAMPER:907return &GUID_Damper;908909case SDL_HAPTIC_INERTIA:910return &GUID_Inertia;911912case SDL_HAPTIC_FRICTION:913return &GUID_Friction;914915case SDL_HAPTIC_CUSTOM:916return &GUID_CustomForce;917918default:919return NULL;920}921}922bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)923{924HRESULT ret;925REFGUID type = SDL_SYS_HapticEffectType(base);926927if (!type) {928return SDL_SetError("Haptic: Unknown effect type.");929}930931// Get the effect.932if (!SDL_SYS_ToDIEFFECT(haptic, &effect->hweffect->effect, base)) {933goto err_effectdone;934}935936// Create the actual effect.937ret = IDirectInputDevice8_CreateEffect(haptic->hwdata->device, type,938&effect->hweffect->effect,939&effect->hweffect->ref, NULL);940if (FAILED(ret)) {941DI_SetError("Unable to create effect", ret);942goto err_effectdone;943}944945return true;946947err_effectdone:948SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, base->type);949return false;950}951952bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)953{954HRESULT ret;955DWORD flags;956DIEFFECT temp;957958// Get the effect.959SDL_memset(&temp, 0, sizeof(DIEFFECT));960if (!SDL_SYS_ToDIEFFECT(haptic, &temp, data)) {961goto err_update;962}963964/* Set the flags. Might be worthwhile to diff temp with loaded effect and965* only change those parameters. */966flags = DIEP_DIRECTION |967DIEP_DURATION |968DIEP_ENVELOPE |969DIEP_STARTDELAY |970DIEP_TRIGGERBUTTON |971DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;972973// Create the actual effect.974ret =975IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags);976if (ret == DIERR_NOTEXCLUSIVEACQUIRED) {977IDirectInputDevice8_Unacquire(haptic->hwdata->device);978ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device, SDL_HelperWindow, DISCL_EXCLUSIVE | DISCL_BACKGROUND);979if (SUCCEEDED(ret)) {980ret = DIERR_NOTACQUIRED;981}982}983if (ret == DIERR_INPUTLOST || ret == DIERR_NOTACQUIRED) {984ret = IDirectInputDevice8_Acquire(haptic->hwdata->device);985if (SUCCEEDED(ret)) {986ret = IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags);987}988}989if (FAILED(ret)) {990DI_SetError("Unable to update effect", ret);991goto err_update;992}993994// Copy it over.995SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, data->type);996SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(DIEFFECT));997998return true;9991000err_update:1001SDL_SYS_HapticFreeDIEFFECT(&temp, data->type);1002return false;1003}10041005bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)1006{1007HRESULT ret;1008DWORD iter;10091010// Check if it's infinite.1011if (iterations == SDL_HAPTIC_INFINITY) {1012iter = INFINITE;1013} else {1014iter = iterations;1015}10161017// Run the effect.1018ret = IDirectInputEffect_Start(effect->hweffect->ref, iter, 0);1019if (FAILED(ret)) {1020return DI_SetError("Running the effect", ret);1021}1022return true;1023}10241025bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1026{1027HRESULT ret;10281029ret = IDirectInputEffect_Stop(effect->hweffect->ref);1030if (FAILED(ret)) {1031return DI_SetError("Unable to stop effect", ret);1032}1033return true;1034}10351036void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1037{1038HRESULT ret;10391040ret = IDirectInputEffect_Unload(effect->hweffect->ref);1041if (FAILED(ret)) {1042DI_SetError("Removing effect from the device", ret);1043}1044SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, effect->effect.type);1045}10461047int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)1048{1049HRESULT ret;1050DWORD status;10511052ret = IDirectInputEffect_GetEffectStatus(effect->hweffect->ref, &status);1053if (FAILED(ret)) {1054DI_SetError("Getting effect status", ret);1055return -1;1056}10571058if (status == 0) {1059return 0;1060}1061return 1;1062}10631064bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)1065{1066HRESULT ret;1067DIPROPDWORD dipdw;10681069// Create the weird structure thingy.1070dipdw.diph.dwSize = sizeof(DIPROPDWORD);1071dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);1072dipdw.diph.dwObj = 0;1073dipdw.diph.dwHow = DIPH_DEVICE;1074dipdw.dwData = (DWORD)gain * 100; // 0 to 10,00010751076// Try to set the autocenter.1077ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,1078DIPROP_FFGAIN, &dipdw.diph);1079if (FAILED(ret)) {1080return DI_SetError("Setting gain", ret);1081}1082return true;1083}10841085bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)1086{1087HRESULT ret;1088DIPROPDWORD dipdw;10891090// Create the weird structure thingy.1091dipdw.diph.dwSize = sizeof(DIPROPDWORD);1092dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);1093dipdw.diph.dwObj = 0;1094dipdw.diph.dwHow = DIPH_DEVICE;1095dipdw.dwData = (autocenter == 0) ? DIPROPAUTOCENTER_OFF : DIPROPAUTOCENTER_ON;10961097// Try to set the autocenter.1098ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,1099DIPROP_AUTOCENTER, &dipdw.diph);1100if (FAILED(ret)) {1101return DI_SetError("Setting autocenter", ret);1102}1103return true;1104}11051106bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)1107{1108HRESULT ret;11091110// Pause the device.1111ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,1112DISFFC_PAUSE);1113if (FAILED(ret)) {1114return DI_SetError("Pausing the device", ret);1115}1116return true;1117}11181119bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)1120{1121HRESULT ret;11221123// Unpause the device.1124ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,1125DISFFC_CONTINUE);1126if (FAILED(ret)) {1127return DI_SetError("Pausing the device", ret);1128}1129return true;1130}11311132bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)1133{1134HRESULT ret;11351136// Try to stop the effects.1137ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,1138DISFFC_STOPALL);1139if (FAILED(ret)) {1140return DI_SetError("Stopping the device", ret);1141}1142return true;1143}11441145#else // !SDL_HAPTIC_DINPUT11461147typedef struct DIDEVICEINSTANCE DIDEVICEINSTANCE;1148typedef struct SDL_hapticlist_item SDL_hapticlist_item;11491150bool SDL_DINPUT_HapticInit(void)1151{1152return true;1153}11541155bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)1156{1157return SDL_Unsupported();1158}11591160bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)1161{1162return SDL_Unsupported();1163}11641165bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)1166{1167return SDL_Unsupported();1168}11691170bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)1171{1172return false;1173}11741175bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)1176{1177return SDL_Unsupported();1178}11791180void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)1181{1182}11831184void SDL_DINPUT_HapticQuit(void)1185{1186}11871188bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)1189{1190return SDL_Unsupported();1191}11921193bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)1194{1195return SDL_Unsupported();1196}11971198bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)1199{1200return SDL_Unsupported();1201}12021203bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1204{1205return SDL_Unsupported();1206}12071208void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1209{1210}12111212int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)1213{1214SDL_Unsupported();1215return -1;1216}12171218bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)1219{1220return SDL_Unsupported();1221}12221223bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)1224{1225return SDL_Unsupported();1226}12271228bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)1229{1230return SDL_Unsupported();1231}12321233bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)1234{1235return SDL_Unsupported();1236}12371238bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)1239{1240return SDL_Unsupported();1241}12421243#endif // SDL_HAPTIC_DINPUT124412451246