Path: blob/master/thirdparty/sdl/haptic/windows/SDL_dinputhaptic.c
21745 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*/33extern HWND SDL_HelperWindow;3435/*36* Internal stuff.37*/38static bool coinitialized = false;39static LPDIRECTINPUT8 dinput = NULL;4041/*42* Like SDL_SetError but for DX error codes.43*/44static bool DI_SetError(const char *str, HRESULT err)45{46return SDL_SetError("Haptic error %s", str);47}4849/*50* Callback to find the haptic devices.51*/52static BOOL CALLBACK EnumHapticsCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext)53{54(void)pContext;55SDL_DINPUT_HapticMaybeAddDevice(pdidInstance);56return DIENUM_CONTINUE; // continue enumerating57}5859bool SDL_DINPUT_HapticInit(void)60{61HRESULT ret;62HINSTANCE instance;63DWORD devClass;6465if (dinput != NULL) { // Already open.66return SDL_SetError("Haptic: SubSystem already open.");67}6869if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true)) {70// In some environments, IDirectInput8_Initialize / _EnumDevices can take a minute even with no controllers.71return true;72}7374ret = WIN_CoInitialize();75if (FAILED(ret)) {76return DI_SetError("Coinitialize", ret);77}7879coinitialized = true;8081ret = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER,82&IID_IDirectInput8, (LPVOID *)&dinput);83if (FAILED(ret)) {84SDL_SYS_HapticQuit();85return DI_SetError("CoCreateInstance", ret);86}8788// Because we used CoCreateInstance, we need to Initialize it, first.89instance = GetModuleHandle(NULL);90if (!instance) {91SDL_SYS_HapticQuit();92return SDL_SetError("GetModuleHandle() failed with error code %lu.",93GetLastError());94}95ret = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION);96if (FAILED(ret)) {97SDL_SYS_HapticQuit();98return DI_SetError("Initializing DirectInput device", ret);99}100101// Look for haptic devices.102for (devClass = DI8DEVCLASS_DEVICE; devClass <= DI8DEVCLASS_GAMECTRL; devClass++) {103if (devClass == DI8DEVCLASS_GAMECTRL && SDL_WasInit(SDL_INIT_JOYSTICK)) {104// The joystick subsystem will manage adding DInput joystick haptic devices105continue;106}107108ret = IDirectInput8_EnumDevices(dinput,109devClass,110EnumHapticsCallback,111NULL,112DIEDFL_FORCEFEEDBACK |113DIEDFL_ATTACHEDONLY);114if (FAILED(ret)) {115SDL_SYS_HapticQuit();116return DI_SetError("Enumerating DirectInput devices", ret);117}118}119120return true;121}122123bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)124{125HRESULT ret;126LPDIRECTINPUTDEVICE8 device;127const DWORD needflags = DIDC_ATTACHED | DIDC_FORCEFEEDBACK;128DIDEVCAPS capabilities;129SDL_hapticlist_item *item = NULL;130131if (!dinput) {132return false; // not initialized. We'll pick these up on enumeration if we init later.133}134135// Make sure we don't already have it136for (item = SDL_hapticlist; item; item = item->next) {137if (SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) {138return false; // Already added139}140}141142// Open the device143ret = IDirectInput8_CreateDevice(dinput, &pdidInstance->guidInstance, &device, NULL);144if (FAILED(ret)) {145// DI_SetError("Creating DirectInput device",ret);146return false;147}148149// Get capabilities.150SDL_zero(capabilities);151capabilities.dwSize = sizeof(DIDEVCAPS);152ret = IDirectInputDevice8_GetCapabilities(device, &capabilities);153IDirectInputDevice8_Release(device);154if (FAILED(ret)) {155// DI_SetError("Getting device capabilities",ret);156return false;157}158159if ((capabilities.dwFlags & needflags) != needflags) {160return false; // not a device we can use.161}162163item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));164if (!item) {165return false;166}167168item->instance_id = SDL_GetNextObjectID();169item->name = WIN_StringToUTF8(pdidInstance->tszProductName);170if (!item->name) {171SDL_free(item);172return false;173}174175// Copy the instance over, useful for creating devices.176SDL_memcpy(&item->instance, pdidInstance, sizeof(DIDEVICEINSTANCE));177SDL_memcpy(&item->capabilities, &capabilities, sizeof(capabilities));178179return SDL_SYS_AddHapticDevice(item);180}181182bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)183{184SDL_hapticlist_item *item;185SDL_hapticlist_item *prev = NULL;186187if (!dinput) {188return false; // not initialized, ignore this.189}190191for (item = SDL_hapticlist; item; item = item->next) {192if (SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) {193// found it, remove it.194return SDL_SYS_RemoveHapticDevice(prev, item);195}196prev = item;197}198return false;199}200201/*202* Callback to get supported axes.203*/204static BOOL CALLBACK DI_DeviceObjectCallback(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID pvRef)205{206SDL_Haptic *haptic = (SDL_Haptic *)pvRef;207208if ((dev->dwType & DIDFT_AXIS) && (dev->dwFlags & DIDOI_FFACTUATOR)) {209const GUID *guid = &dev->guidType;210DWORD offset = 0;211if (WIN_IsEqualGUID(guid, &GUID_XAxis)) {212offset = DIJOFS_X;213} else if (WIN_IsEqualGUID(guid, &GUID_YAxis)) {214offset = DIJOFS_Y;215} else if (WIN_IsEqualGUID(guid, &GUID_ZAxis)) {216offset = DIJOFS_Z;217} else if (WIN_IsEqualGUID(guid, &GUID_RxAxis)) {218offset = DIJOFS_RX;219} else if (WIN_IsEqualGUID(guid, &GUID_RyAxis)) {220offset = DIJOFS_RY;221} else if (WIN_IsEqualGUID(guid, &GUID_RzAxis)) {222offset = DIJOFS_RZ;223} else {224return DIENUM_CONTINUE; // can't use this, go on.225}226227haptic->hwdata->axes[haptic->naxes] = offset;228haptic->naxes++;229230// Currently using the artificial limit of 3 axes.231if (haptic->naxes >= 3) {232return DIENUM_STOP;233}234}235236return DIENUM_CONTINUE;237}238239/*240* Callback to get all supported effects.241*/242#define EFFECT_TEST(e, s) \243if (WIN_IsEqualGUID(&pei->guid, &(e))) \244haptic->supported |= (s)245static BOOL CALLBACK DI_EffectCallback(LPCDIEFFECTINFO pei, LPVOID pv)246{247// Prepare the haptic device.248SDL_Haptic *haptic = (SDL_Haptic *)pv;249250// Get supported.251EFFECT_TEST(GUID_Spring, SDL_HAPTIC_SPRING);252EFFECT_TEST(GUID_Damper, SDL_HAPTIC_DAMPER);253EFFECT_TEST(GUID_Inertia, SDL_HAPTIC_INERTIA);254EFFECT_TEST(GUID_Friction, SDL_HAPTIC_FRICTION);255EFFECT_TEST(GUID_ConstantForce, SDL_HAPTIC_CONSTANT);256EFFECT_TEST(GUID_CustomForce, SDL_HAPTIC_CUSTOM);257EFFECT_TEST(GUID_Sine, SDL_HAPTIC_SINE);258EFFECT_TEST(GUID_Square, SDL_HAPTIC_SQUARE);259EFFECT_TEST(GUID_Triangle, SDL_HAPTIC_TRIANGLE);260EFFECT_TEST(GUID_SawtoothUp, SDL_HAPTIC_SAWTOOTHUP);261EFFECT_TEST(GUID_SawtoothDown, SDL_HAPTIC_SAWTOOTHDOWN);262EFFECT_TEST(GUID_RampForce, SDL_HAPTIC_RAMP);263264// Check for more.265return DIENUM_CONTINUE;266}267268/*269* Opens the haptic device.270*271* Steps:272* - Set cooperative level.273* - Set data format.274* - Acquire exclusiveness.275* - Reset actuators.276* - Get supported features.277*/278static bool SDL_DINPUT_HapticOpenFromDevice(SDL_Haptic *haptic, LPDIRECTINPUTDEVICE8 device8, bool is_joystick)279{280HRESULT ret;281DIPROPDWORD dipdw;282283// Allocate the hwdata284haptic->hwdata = (struct haptic_hwdata *)SDL_calloc(1, sizeof(*haptic->hwdata));285if (!haptic->hwdata) {286return false;287}288289// We'll use the device8 from now on.290haptic->hwdata->device = device8;291haptic->hwdata->is_joystick = is_joystick;292293/* !!! FIXME: opening a haptic device here first will make an attempt to294!!! FIXME: SDL_OpenJoystick() that same device fail later, since we295!!! FIXME: have it open in exclusive mode. But this will allow296!!! FIXME: SDL_OpenJoystick() followed by SDL_OpenHapticFromJoystick()297!!! FIXME: to work, and that's probably the common case. Still,298!!! FIXME: ideally, We need to unify the opening code. */299300if (!is_joystick) { // if is_joystick, we already set this up elsewhere.301// Grab it exclusively to use force feedback stuff.302ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device,303SDL_HelperWindow,304DISCL_EXCLUSIVE |305DISCL_BACKGROUND);306if (FAILED(ret)) {307DI_SetError("Setting cooperative level to exclusive", ret);308goto acquire_err;309}310311// Set data format.312ret = IDirectInputDevice8_SetDataFormat(haptic->hwdata->device,313&SDL_c_dfDIJoystick2);314if (FAILED(ret)) {315DI_SetError("Setting data format", ret);316goto acquire_err;317}318319// Acquire the device.320ret = IDirectInputDevice8_Acquire(haptic->hwdata->device);321if (FAILED(ret)) {322DI_SetError("Acquiring DirectInput device", ret);323goto acquire_err;324}325}326327// Get number of axes.328ret = IDirectInputDevice8_EnumObjects(haptic->hwdata->device,329DI_DeviceObjectCallback,330haptic, DIDFT_AXIS);331if (FAILED(ret)) {332DI_SetError("Getting device axes", ret);333goto acquire_err;334}335336// Reset all actuators - just in case.337ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,338DISFFC_RESET);339if (FAILED(ret)) {340DI_SetError("Resetting device", ret);341goto acquire_err;342}343344// Enabling actuators.345ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,346DISFFC_SETACTUATORSON);347if (FAILED(ret)) {348DI_SetError("Enabling actuators", ret);349goto acquire_err;350}351352// Get supported effects.353ret = IDirectInputDevice8_EnumEffects(haptic->hwdata->device,354DI_EffectCallback, haptic,355DIEFT_ALL);356if (FAILED(ret)) {357DI_SetError("Enumerating supported effects", ret);358goto acquire_err;359}360if (haptic->supported == 0) { // Error since device supports nothing.361SDL_SetError("Haptic: Internal error on finding supported effects.");362goto acquire_err;363}364365// Check autogain and autocenter.366dipdw.diph.dwSize = sizeof(DIPROPDWORD);367dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);368dipdw.diph.dwObj = 0;369dipdw.diph.dwHow = DIPH_DEVICE;370dipdw.dwData = 10000;371ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,372DIPROP_FFGAIN, &dipdw.diph);373if (!FAILED(ret)) { // Gain is supported.374haptic->supported |= SDL_HAPTIC_GAIN;375}376dipdw.diph.dwObj = 0;377dipdw.diph.dwHow = DIPH_DEVICE;378dipdw.dwData = DIPROPAUTOCENTER_OFF;379ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,380DIPROP_AUTOCENTER, &dipdw.diph);381if (!FAILED(ret)) { // Autocenter is supported.382haptic->supported |= SDL_HAPTIC_AUTOCENTER;383}384385// Status is always supported.386haptic->supported |= SDL_HAPTIC_STATUS | SDL_HAPTIC_PAUSE;387388// Check maximum effects.389haptic->neffects = 128; /* This is not actually supported as thus under windows,390there is no way to tell the number of EFFECTS that a391device can hold, so we'll just use a "random" number392instead and put warnings in SDL_haptic.h */393haptic->nplaying = 128; // Even more impossible to get this then neffects.394395// Prepare effects memory.396haptic->effects = (struct haptic_effect *)397SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects);398if (!haptic->effects) {399goto acquire_err;400}401// Clear the memory402SDL_memset(haptic->effects, 0,403sizeof(struct haptic_effect) * haptic->neffects);404405return true;406407// Error handling408acquire_err:409IDirectInputDevice8_Unacquire(haptic->hwdata->device);410return false;411}412413bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)414{415HRESULT ret;416LPDIRECTINPUTDEVICE8 device;417418// Open the device419ret = IDirectInput8_CreateDevice(dinput, &item->instance.guidInstance,420&device, NULL);421if (FAILED(ret)) {422DI_SetError("Creating DirectInput device", ret);423return false;424}425426if (!SDL_DINPUT_HapticOpenFromDevice(haptic, device, false)) {427IDirectInputDevice8_Release(device);428return false;429}430return true;431}432433bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)434{435HRESULT ret;436DIDEVICEINSTANCE hap_instance, joy_instance;437438hap_instance.dwSize = sizeof(DIDEVICEINSTANCE);439joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);440441// Get the device instances.442ret = IDirectInputDevice8_GetDeviceInfo(haptic->hwdata->device,443&hap_instance);444if (FAILED(ret)) {445return false;446}447ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice,448&joy_instance);449if (FAILED(ret)) {450return false;451}452453return (WIN_IsEqualGUID(&hap_instance.guidInstance, &joy_instance.guidInstance) == TRUE);454}455456bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)457{458SDL_hapticlist_item *item;459HRESULT ret;460DIDEVICEINSTANCE joy_instance;461462joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);463ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, &joy_instance);464if (FAILED(ret)) {465return false;466}467468// Since it comes from a joystick we have to try to match it with a haptic device on our haptic list.469for (item = SDL_hapticlist; item; item = item->next) {470if (WIN_IsEqualGUID(&item->instance.guidInstance, &joy_instance.guidInstance)) {471haptic->instance_id = item->instance_id;472haptic->name = SDL_strdup(item->name);473return SDL_DINPUT_HapticOpenFromDevice(haptic, joystick->hwdata->InputDevice, true);474}475}476477return SDL_SetError("Couldn't find joystick in haptic device list");478}479480void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)481{482IDirectInputDevice8_Unacquire(haptic->hwdata->device);483484// Only release if isn't grabbed by a joystick.485if (haptic->hwdata->is_joystick == 0) {486IDirectInputDevice8_Release(haptic->hwdata->device);487}488}489490void SDL_DINPUT_HapticQuit(void)491{492if (dinput != NULL) {493IDirectInput8_Release(dinput);494dinput = NULL;495}496497if (coinitialized) {498WIN_CoUninitialize();499coinitialized = false;500}501}502503/*504* Converts an SDL trigger button to an DIEFFECT trigger button.505*/506static DWORD DIGetTriggerButton(Uint16 button)507{508DWORD dwTriggerButton;509510dwTriggerButton = DIEB_NOTRIGGER;511512if (button != 0) {513dwTriggerButton = DIJOFS_BUTTON(button - 1);514}515516return dwTriggerButton;517}518519/*520* Sets the direction.521*/522static bool SDL_SYS_SetDirection(DIEFFECT *effect, const SDL_HapticDirection *dir, int naxes)523{524LONG *rglDir;525526// Handle no axes a part.527if (naxes == 0) {528effect->dwFlags |= DIEFF_SPHERICAL; // Set as default.529effect->rglDirection = NULL;530return true;531}532533// Has axes.534rglDir = (LONG *)SDL_malloc(sizeof(LONG) * naxes);535if (!rglDir) {536return false;537}538SDL_memset(rglDir, 0, sizeof(LONG) * naxes);539effect->rglDirection = rglDir;540541switch (dir->type) {542case SDL_HAPTIC_POLAR:543effect->dwFlags |= DIEFF_POLAR;544rglDir[0] = dir->dir[0];545return true;546case SDL_HAPTIC_CARTESIAN:547effect->dwFlags |= DIEFF_CARTESIAN;548rglDir[0] = dir->dir[0];549if (naxes > 1) {550rglDir[1] = dir->dir[1];551}552if (naxes > 2) {553rglDir[2] = dir->dir[2];554}555return true;556case SDL_HAPTIC_SPHERICAL:557effect->dwFlags |= DIEFF_SPHERICAL;558rglDir[0] = dir->dir[0];559if (naxes > 1) {560rglDir[1] = dir->dir[1];561}562if (naxes > 2) {563rglDir[2] = dir->dir[2];564}565return true;566case SDL_HAPTIC_STEERING_AXIS:567effect->dwFlags |= DIEFF_CARTESIAN;568rglDir[0] = 0;569return true;570571default:572return SDL_SetError("Haptic: Unknown direction type.");573}574}575576// Clamps and converts.577#define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)578// Just converts.579#define CONVERT(x) (((x)*10000) / 0x7FFF)580/*581* Creates the DIEFFECT from a SDL_HapticEffect.582*/583static bool SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest,584const SDL_HapticEffect *src)585{586int i;587DICONSTANTFORCE *constant;588DIPERIODIC *periodic;589DICONDITION *condition; // Actually an array of conditions - one per axis.590DIRAMPFORCE *ramp;591DICUSTOMFORCE *custom;592DIENVELOPE *envelope;593const SDL_HapticConstant *hap_constant;594const SDL_HapticPeriodic *hap_periodic;595const SDL_HapticCondition *hap_condition;596const SDL_HapticRamp *hap_ramp;597const SDL_HapticCustom *hap_custom;598DWORD *axes;599600// Set global stuff.601SDL_memset(dest, 0, sizeof(DIEFFECT));602dest->dwSize = sizeof(DIEFFECT); // Set the structure size.603dest->dwSamplePeriod = 0; // Not used by us.604dest->dwGain = 10000; // Gain is set globally, not locally.605dest->dwFlags = DIEFF_OBJECTOFFSETS; // Seems obligatory.606607// Envelope.608envelope = (DIENVELOPE *)SDL_calloc(1, sizeof(DIENVELOPE));609if (!envelope) {610return false;611}612dest->lpEnvelope = envelope;613envelope->dwSize = sizeof(DIENVELOPE); // Always should be this.614615// Axes.616if (src->constant.direction.type == SDL_HAPTIC_STEERING_AXIS) {617dest->cAxes = 1;618} else {619dest->cAxes = haptic->naxes;620}621if (dest->cAxes > 0) {622axes = (DWORD *)SDL_malloc(sizeof(DWORD) * dest->cAxes);623if (!axes) {624return false;625}626axes[0] = haptic->hwdata->axes[0]; // Always at least one axis.627if (dest->cAxes > 1) {628axes[1] = haptic->hwdata->axes[1];629}630if (dest->cAxes > 2) {631axes[2] = haptic->hwdata->axes[2];632}633dest->rgdwAxes = axes;634}635636// The big type handling switch, even bigger than Linux's version.637switch (src->type) {638case SDL_HAPTIC_CONSTANT:639hap_constant = &src->constant;640constant = (DICONSTANTFORCE *)SDL_calloc(1, sizeof(DICONSTANTFORCE));641if (!constant) {642return false;643}644645// Specifics646constant->lMagnitude = CONVERT(hap_constant->level);647dest->cbTypeSpecificParams = sizeof(DICONSTANTFORCE);648dest->lpvTypeSpecificParams = constant;649650// Generics651dest->dwDuration = hap_constant->length * 1000UL; // In microseconds.652dest->dwTriggerButton = DIGetTriggerButton(hap_constant->button);653dest->dwTriggerRepeatInterval = hap_constant->interval;654dest->dwStartDelay = hap_constant->delay * 1000UL; // In microseconds.655656// Direction.657if (!SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes)) {658return false;659}660661// Envelope662if ((hap_constant->attack_length == 0) && (hap_constant->fade_length == 0)) {663SDL_free(dest->lpEnvelope);664dest->lpEnvelope = NULL;665} else {666envelope->dwAttackLevel = CCONVERT(hap_constant->attack_level);667envelope->dwAttackTime = hap_constant->attack_length * 1000UL;668envelope->dwFadeLevel = CCONVERT(hap_constant->fade_level);669envelope->dwFadeTime = hap_constant->fade_length * 1000UL;670}671672break;673674case SDL_HAPTIC_SINE:675case SDL_HAPTIC_SQUARE:676case SDL_HAPTIC_TRIANGLE:677case SDL_HAPTIC_SAWTOOTHUP:678case SDL_HAPTIC_SAWTOOTHDOWN:679hap_periodic = &src->periodic;680periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(DIPERIODIC));681if (!periodic) {682return false;683}684685// Specifics686periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude));687periodic->lOffset = CONVERT(hap_periodic->offset);688periodic->dwPhase =689(hap_periodic->phase + (hap_periodic->magnitude < 0 ? 18000 : 0)) % 36000;690periodic->dwPeriod = hap_periodic->period * 1000;691dest->cbTypeSpecificParams = sizeof(DIPERIODIC);692dest->lpvTypeSpecificParams = periodic;693694// Generics695dest->dwDuration = hap_periodic->length * 1000UL; // In microseconds.696dest->dwTriggerButton = DIGetTriggerButton(hap_periodic->button);697dest->dwTriggerRepeatInterval = hap_periodic->interval;698dest->dwStartDelay = hap_periodic->delay * 1000UL; // In microseconds.699700// Direction.701if (!SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes)) {702return false;703}704705// Envelope706if ((hap_periodic->attack_length == 0) && (hap_periodic->fade_length == 0)) {707SDL_free(dest->lpEnvelope);708dest->lpEnvelope = NULL;709} else {710envelope->dwAttackLevel = CCONVERT(hap_periodic->attack_level);711envelope->dwAttackTime = hap_periodic->attack_length * 1000UL;712envelope->dwFadeLevel = CCONVERT(hap_periodic->fade_level);713envelope->dwFadeTime = hap_periodic->fade_length * 1000UL;714}715716break;717718case SDL_HAPTIC_SPRING:719case SDL_HAPTIC_DAMPER:720case SDL_HAPTIC_INERTIA:721case SDL_HAPTIC_FRICTION:722hap_condition = &src->condition;723condition = (DICONDITION *)SDL_calloc(dest->cAxes, sizeof(DICONDITION));724if (!condition) {725return false;726}727728// Specifics729for (i = 0; i < (int)dest->cAxes; i++) {730condition[i].lOffset = CONVERT(hap_condition->center[i]);731condition[i].lPositiveCoefficient =732CONVERT(hap_condition->right_coeff[i]);733condition[i].lNegativeCoefficient =734CONVERT(hap_condition->left_coeff[i]);735condition[i].dwPositiveSaturation =736CCONVERT(hap_condition->right_sat[i] / 2);737condition[i].dwNegativeSaturation =738CCONVERT(hap_condition->left_sat[i] / 2);739condition[i].lDeadBand = CCONVERT(hap_condition->deadband[i] / 2);740}741dest->cbTypeSpecificParams = sizeof(DICONDITION) * dest->cAxes;742dest->lpvTypeSpecificParams = condition;743744// Generics745dest->dwDuration = hap_condition->length * 1000UL; // In microseconds.746dest->dwTriggerButton = DIGetTriggerButton(hap_condition->button);747dest->dwTriggerRepeatInterval = hap_condition->interval;748dest->dwStartDelay = hap_condition->delay * 1000UL; // In microseconds.749750// Direction.751if (!SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes)) {752return false;753}754755// Envelope - Not actually supported by most CONDITION implementations.756SDL_free(dest->lpEnvelope);757dest->lpEnvelope = NULL;758759break;760761case SDL_HAPTIC_RAMP:762hap_ramp = &src->ramp;763ramp = (DIRAMPFORCE *)SDL_calloc(1, sizeof(DIRAMPFORCE));764if (!ramp) {765return false;766}767768// Specifics769ramp->lStart = CONVERT(hap_ramp->start);770ramp->lEnd = CONVERT(hap_ramp->end);771dest->cbTypeSpecificParams = sizeof(DIRAMPFORCE);772dest->lpvTypeSpecificParams = ramp;773774// Generics775dest->dwDuration = hap_ramp->length * 1000UL; // In microseconds.776dest->dwTriggerButton = DIGetTriggerButton(hap_ramp->button);777dest->dwTriggerRepeatInterval = hap_ramp->interval;778dest->dwStartDelay = hap_ramp->delay * 1000UL; // In microseconds.779780// Direction.781if (!SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes)) {782return false;783}784785// Envelope786if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) {787SDL_free(dest->lpEnvelope);788dest->lpEnvelope = NULL;789} else {790envelope->dwAttackLevel = CCONVERT(hap_ramp->attack_level);791envelope->dwAttackTime = hap_ramp->attack_length * 1000UL;792envelope->dwFadeLevel = CCONVERT(hap_ramp->fade_level);793envelope->dwFadeTime = hap_ramp->fade_length * 1000UL;794}795796break;797798case SDL_HAPTIC_CUSTOM:799hap_custom = &src->custom;800custom = (DICUSTOMFORCE *)SDL_calloc(1, sizeof(DICUSTOMFORCE));801if (!custom) {802return false;803}804805// Specifics806custom->cChannels = hap_custom->channels;807custom->dwSamplePeriod = hap_custom->period * 1000UL;808custom->cSamples = hap_custom->samples;809custom->rglForceData = (LPLONG)SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels);810for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) { // Copy data.811custom->rglForceData[i] = CCONVERT(hap_custom->data[i]);812}813dest->cbTypeSpecificParams = sizeof(DICUSTOMFORCE);814dest->lpvTypeSpecificParams = custom;815816// Generics817dest->dwDuration = hap_custom->length * 1000UL; // In microseconds.818dest->dwTriggerButton = DIGetTriggerButton(hap_custom->button);819dest->dwTriggerRepeatInterval = hap_custom->interval;820dest->dwStartDelay = hap_custom->delay * 1000UL; // In microseconds.821822// Direction.823if (!SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes)) {824return false;825}826827// Envelope828if ((hap_custom->attack_length == 0) && (hap_custom->fade_length == 0)) {829SDL_free(dest->lpEnvelope);830dest->lpEnvelope = NULL;831} else {832envelope->dwAttackLevel = CCONVERT(hap_custom->attack_level);833envelope->dwAttackTime = hap_custom->attack_length * 1000UL;834envelope->dwFadeLevel = CCONVERT(hap_custom->fade_level);835envelope->dwFadeTime = hap_custom->fade_length * 1000UL;836}837838break;839840default:841return SDL_SetError("Haptic: Unknown effect type.");842}843844return true;845}846847/*848* Frees an DIEFFECT allocated by SDL_SYS_ToDIEFFECT.849*/850static void SDL_SYS_HapticFreeDIEFFECT(DIEFFECT *effect, int type)851{852DICUSTOMFORCE *custom;853854SDL_free(effect->lpEnvelope);855effect->lpEnvelope = NULL;856SDL_free(effect->rgdwAxes);857effect->rgdwAxes = NULL;858if (effect->lpvTypeSpecificParams) {859if (type == SDL_HAPTIC_CUSTOM) { // Must free the custom data.860custom = (DICUSTOMFORCE *)effect->lpvTypeSpecificParams;861SDL_free(custom->rglForceData);862custom->rglForceData = NULL;863}864SDL_free(effect->lpvTypeSpecificParams);865effect->lpvTypeSpecificParams = NULL;866}867SDL_free(effect->rglDirection);868effect->rglDirection = NULL;869}870871/*872* Gets the effect type from the generic SDL haptic effect wrapper.873*/874// NOLINTNEXTLINE(readability-const-return-type): Can't fix Windows' headers875static REFGUID SDL_SYS_HapticEffectType(const SDL_HapticEffect *effect)876{877switch (effect->type) {878case SDL_HAPTIC_CONSTANT:879return &GUID_ConstantForce;880881case SDL_HAPTIC_RAMP:882return &GUID_RampForce;883884case SDL_HAPTIC_SQUARE:885return &GUID_Square;886887case SDL_HAPTIC_SINE:888return &GUID_Sine;889890case SDL_HAPTIC_TRIANGLE:891return &GUID_Triangle;892893case SDL_HAPTIC_SAWTOOTHUP:894return &GUID_SawtoothUp;895896case SDL_HAPTIC_SAWTOOTHDOWN:897return &GUID_SawtoothDown;898899case SDL_HAPTIC_SPRING:900return &GUID_Spring;901902case SDL_HAPTIC_DAMPER:903return &GUID_Damper;904905case SDL_HAPTIC_INERTIA:906return &GUID_Inertia;907908case SDL_HAPTIC_FRICTION:909return &GUID_Friction;910911case SDL_HAPTIC_CUSTOM:912return &GUID_CustomForce;913914default:915return NULL;916}917}918bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)919{920HRESULT ret;921REFGUID type = SDL_SYS_HapticEffectType(base);922923if (!type) {924return SDL_SetError("Haptic: Unknown effect type.");925}926927// Get the effect.928if (!SDL_SYS_ToDIEFFECT(haptic, &effect->hweffect->effect, base)) {929goto err_effectdone;930}931932// Create the actual effect.933ret = IDirectInputDevice8_CreateEffect(haptic->hwdata->device, type,934&effect->hweffect->effect,935&effect->hweffect->ref, NULL);936if (FAILED(ret)) {937DI_SetError("Unable to create effect", ret);938goto err_effectdone;939}940941return true;942943err_effectdone:944SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, base->type);945return false;946}947948bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)949{950HRESULT ret;951DWORD flags;952DIEFFECT temp;953954// Get the effect.955SDL_memset(&temp, 0, sizeof(DIEFFECT));956if (!SDL_SYS_ToDIEFFECT(haptic, &temp, data)) {957goto err_update;958}959960/* Set the flags. Might be worthwhile to diff temp with loaded effect and961* only change those parameters. */962flags = DIEP_DIRECTION |963DIEP_DURATION |964DIEP_ENVELOPE |965DIEP_STARTDELAY |966DIEP_TRIGGERBUTTON |967DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;968969// Create the actual effect.970ret =971IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags);972if (ret == DIERR_NOTEXCLUSIVEACQUIRED) {973IDirectInputDevice8_Unacquire(haptic->hwdata->device);974ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device, SDL_HelperWindow, DISCL_EXCLUSIVE | DISCL_BACKGROUND);975if (SUCCEEDED(ret)) {976ret = DIERR_NOTACQUIRED;977}978}979if (ret == DIERR_INPUTLOST || ret == DIERR_NOTACQUIRED) {980ret = IDirectInputDevice8_Acquire(haptic->hwdata->device);981if (SUCCEEDED(ret)) {982ret = IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags);983}984}985if (FAILED(ret)) {986DI_SetError("Unable to update effect", ret);987goto err_update;988}989990// Copy it over.991SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, data->type);992SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(DIEFFECT));993994return true;995996err_update:997SDL_SYS_HapticFreeDIEFFECT(&temp, data->type);998return false;999}10001001bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)1002{1003HRESULT ret;1004DWORD iter;10051006// Check if it's infinite.1007if (iterations == SDL_HAPTIC_INFINITY) {1008iter = INFINITE;1009} else {1010iter = iterations;1011}10121013// Run the effect.1014ret = IDirectInputEffect_Start(effect->hweffect->ref, iter, 0);1015if (FAILED(ret)) {1016return DI_SetError("Running the effect", ret);1017}1018return true;1019}10201021bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1022{1023HRESULT ret;10241025ret = IDirectInputEffect_Stop(effect->hweffect->ref);1026if (FAILED(ret)) {1027return DI_SetError("Unable to stop effect", ret);1028}1029return true;1030}10311032void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1033{1034HRESULT ret;10351036ret = IDirectInputEffect_Unload(effect->hweffect->ref);1037if (FAILED(ret)) {1038DI_SetError("Removing effect from the device", ret);1039}1040SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, effect->effect.type);1041}10421043int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)1044{1045HRESULT ret;1046DWORD status;10471048ret = IDirectInputEffect_GetEffectStatus(effect->hweffect->ref, &status);1049if (FAILED(ret)) {1050DI_SetError("Getting effect status", ret);1051return -1;1052}10531054if (status == 0) {1055return 0;1056}1057return 1;1058}10591060bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)1061{1062HRESULT ret;1063DIPROPDWORD dipdw;10641065// Create the weird structure thingy.1066dipdw.diph.dwSize = sizeof(DIPROPDWORD);1067dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);1068dipdw.diph.dwObj = 0;1069dipdw.diph.dwHow = DIPH_DEVICE;1070dipdw.dwData = (DWORD)gain * 100; // 0 to 10,00010711072// Try to set the autocenter.1073ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,1074DIPROP_FFGAIN, &dipdw.diph);1075if (FAILED(ret)) {1076return DI_SetError("Setting gain", ret);1077}1078return true;1079}10801081bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)1082{1083HRESULT ret;1084DIPROPDWORD dipdw;10851086// Create the weird structure thingy.1087dipdw.diph.dwSize = sizeof(DIPROPDWORD);1088dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);1089dipdw.diph.dwObj = 0;1090dipdw.diph.dwHow = DIPH_DEVICE;1091dipdw.dwData = (autocenter == 0) ? DIPROPAUTOCENTER_OFF : DIPROPAUTOCENTER_ON;10921093// Try to set the autocenter.1094ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,1095DIPROP_AUTOCENTER, &dipdw.diph);1096if (FAILED(ret)) {1097return DI_SetError("Setting autocenter", ret);1098}1099return true;1100}11011102bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)1103{1104HRESULT ret;11051106// Pause the device.1107ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,1108DISFFC_PAUSE);1109if (FAILED(ret)) {1110return DI_SetError("Pausing the device", ret);1111}1112return true;1113}11141115bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)1116{1117HRESULT ret;11181119// Unpause the device.1120ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,1121DISFFC_CONTINUE);1122if (FAILED(ret)) {1123return DI_SetError("Pausing the device", ret);1124}1125return true;1126}11271128bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)1129{1130HRESULT ret;11311132// Try to stop the effects.1133ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,1134DISFFC_STOPALL);1135if (FAILED(ret)) {1136return DI_SetError("Stopping the device", ret);1137}1138return true;1139}11401141#else // !SDL_HAPTIC_DINPUT11421143typedef struct DIDEVICEINSTANCE DIDEVICEINSTANCE;1144typedef struct SDL_hapticlist_item SDL_hapticlist_item;11451146bool SDL_DINPUT_HapticInit(void)1147{1148return true;1149}11501151bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)1152{1153return SDL_Unsupported();1154}11551156bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)1157{1158return SDL_Unsupported();1159}11601161bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)1162{1163return SDL_Unsupported();1164}11651166bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)1167{1168return false;1169}11701171bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)1172{1173return SDL_Unsupported();1174}11751176void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)1177{1178}11791180void SDL_DINPUT_HapticQuit(void)1181{1182}11831184bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)1185{1186return SDL_Unsupported();1187}11881189bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)1190{1191return SDL_Unsupported();1192}11931194bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)1195{1196return SDL_Unsupported();1197}11981199bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1200{1201return SDL_Unsupported();1202}12031204void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1205{1206}12071208int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)1209{1210SDL_Unsupported();1211return -1;1212}12131214bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)1215{1216return SDL_Unsupported();1217}12181219bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)1220{1221return SDL_Unsupported();1222}12231224bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)1225{1226return SDL_Unsupported();1227}12281229bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)1230{1231return SDL_Unsupported();1232}12331234bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)1235{1236return SDL_Unsupported();1237}12381239#endif // SDL_HAPTIC_DINPUT124012411242