summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c')
-rw-r--r--contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c1244
1 files changed, 1244 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c b/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c
new file mode 100644
index 0000000..255aac0
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/windows/SDL_dinputhaptic.c
@@ -0,0 +1,1244 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "../SDL_syshaptic.h"
24
25#ifdef SDL_HAPTIC_DINPUT
26
27#include "SDL_windowshaptic_c.h"
28#include "SDL_dinputhaptic_c.h"
29#include "../../joystick/windows/SDL_windowsjoystick_c.h"
30
31/*
32 * External stuff.
33 */
34#ifdef SDL_VIDEO_DRIVER_WINDOWS
35extern HWND SDL_HelperWindow;
36#else
37static const HWND SDL_HelperWindow = NULL;
38#endif
39
40/*
41 * Internal stuff.
42 */
43static bool coinitialized = false;
44static LPDIRECTINPUT8 dinput = NULL;
45
46/*
47 * Like SDL_SetError but for DX error codes.
48 */
49static bool DI_SetError(const char *str, HRESULT err)
50{
51 return SDL_SetError("Haptic error %s", str);
52}
53
54/*
55 * Callback to find the haptic devices.
56 */
57static BOOL CALLBACK EnumHapticsCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext)
58{
59 (void)pContext;
60 SDL_DINPUT_HapticMaybeAddDevice(pdidInstance);
61 return DIENUM_CONTINUE; // continue enumerating
62}
63
64bool SDL_DINPUT_HapticInit(void)
65{
66 HRESULT ret;
67 HINSTANCE instance;
68 DWORD devClass;
69
70 if (dinput != NULL) { // Already open.
71 return SDL_SetError("Haptic: SubSystem already open.");
72 }
73
74 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_DIRECTINPUT, true)) {
75 // In some environments, IDirectInput8_Initialize / _EnumDevices can take a minute even with no controllers.
76 return true;
77 }
78
79 ret = WIN_CoInitialize();
80 if (FAILED(ret)) {
81 return DI_SetError("Coinitialize", ret);
82 }
83
84 coinitialized = true;
85
86 ret = CoCreateInstance(&CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER,
87 &IID_IDirectInput8, (LPVOID *)&dinput);
88 if (FAILED(ret)) {
89 SDL_SYS_HapticQuit();
90 return DI_SetError("CoCreateInstance", ret);
91 }
92
93 // Because we used CoCreateInstance, we need to Initialize it, first.
94 instance = GetModuleHandle(NULL);
95 if (!instance) {
96 SDL_SYS_HapticQuit();
97 return SDL_SetError("GetModuleHandle() failed with error code %lu.",
98 GetLastError());
99 }
100 ret = IDirectInput8_Initialize(dinput, instance, DIRECTINPUT_VERSION);
101 if (FAILED(ret)) {
102 SDL_SYS_HapticQuit();
103 return DI_SetError("Initializing DirectInput device", ret);
104 }
105
106 // Look for haptic devices.
107 for (devClass = DI8DEVCLASS_DEVICE; devClass <= DI8DEVCLASS_GAMECTRL; devClass++) {
108 if (devClass == DI8DEVCLASS_GAMECTRL && SDL_WasInit(SDL_INIT_JOYSTICK)) {
109 // The joystick subsystem will manage adding DInput joystick haptic devices
110 continue;
111 }
112
113 ret = IDirectInput8_EnumDevices(dinput,
114 devClass,
115 EnumHapticsCallback,
116 NULL,
117 DIEDFL_FORCEFEEDBACK |
118 DIEDFL_ATTACHEDONLY);
119 if (FAILED(ret)) {
120 SDL_SYS_HapticQuit();
121 return DI_SetError("Enumerating DirectInput devices", ret);
122 }
123 }
124
125 return true;
126}
127
128bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)
129{
130 HRESULT ret;
131 LPDIRECTINPUTDEVICE8 device;
132 const DWORD needflags = DIDC_ATTACHED | DIDC_FORCEFEEDBACK;
133 DIDEVCAPS capabilities;
134 SDL_hapticlist_item *item = NULL;
135
136 if (!dinput) {
137 return false; // not initialized. We'll pick these up on enumeration if we init later.
138 }
139
140 // Make sure we don't already have it
141 for (item = SDL_hapticlist; item; item = item->next) {
142 if (SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) {
143 return false; // Already added
144 }
145 }
146
147 // Open the device
148 ret = IDirectInput8_CreateDevice(dinput, &pdidInstance->guidInstance, &device, NULL);
149 if (FAILED(ret)) {
150 // DI_SetError("Creating DirectInput device",ret);
151 return false;
152 }
153
154 // Get capabilities.
155 SDL_zero(capabilities);
156 capabilities.dwSize = sizeof(DIDEVCAPS);
157 ret = IDirectInputDevice8_GetCapabilities(device, &capabilities);
158 IDirectInputDevice8_Release(device);
159 if (FAILED(ret)) {
160 // DI_SetError("Getting device capabilities",ret);
161 return false;
162 }
163
164 if ((capabilities.dwFlags & needflags) != needflags) {
165 return false; // not a device we can use.
166 }
167
168 item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));
169 if (!item) {
170 return false;
171 }
172
173 item->instance_id = SDL_GetNextObjectID();
174 item->name = WIN_StringToUTF8(pdidInstance->tszProductName);
175 if (!item->name) {
176 SDL_free(item);
177 return false;
178 }
179
180 // Copy the instance over, useful for creating devices.
181 SDL_memcpy(&item->instance, pdidInstance, sizeof(DIDEVICEINSTANCE));
182 SDL_memcpy(&item->capabilities, &capabilities, sizeof(capabilities));
183
184 return SDL_SYS_AddHapticDevice(item);
185}
186
187bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)
188{
189 SDL_hapticlist_item *item;
190 SDL_hapticlist_item *prev = NULL;
191
192 if (!dinput) {
193 return false; // not initialized, ignore this.
194 }
195
196 for (item = SDL_hapticlist; item; item = item->next) {
197 if (SDL_memcmp(&item->instance, pdidInstance, sizeof(*pdidInstance)) == 0) {
198 // found it, remove it.
199 return SDL_SYS_RemoveHapticDevice(prev, item);
200 }
201 prev = item;
202 }
203 return false;
204}
205
206/*
207 * Callback to get supported axes.
208 */
209static BOOL CALLBACK DI_DeviceObjectCallback(LPCDIDEVICEOBJECTINSTANCE dev, LPVOID pvRef)
210{
211 SDL_Haptic *haptic = (SDL_Haptic *)pvRef;
212
213 if ((dev->dwType & DIDFT_AXIS) && (dev->dwFlags & DIDOI_FFACTUATOR)) {
214 const GUID *guid = &dev->guidType;
215 DWORD offset = 0;
216 if (WIN_IsEqualGUID(guid, &GUID_XAxis)) {
217 offset = DIJOFS_X;
218 } else if (WIN_IsEqualGUID(guid, &GUID_YAxis)) {
219 offset = DIJOFS_Y;
220 } else if (WIN_IsEqualGUID(guid, &GUID_ZAxis)) {
221 offset = DIJOFS_Z;
222 } else if (WIN_IsEqualGUID(guid, &GUID_RxAxis)) {
223 offset = DIJOFS_RX;
224 } else if (WIN_IsEqualGUID(guid, &GUID_RyAxis)) {
225 offset = DIJOFS_RY;
226 } else if (WIN_IsEqualGUID(guid, &GUID_RzAxis)) {
227 offset = DIJOFS_RZ;
228 } else {
229 return DIENUM_CONTINUE; // can't use this, go on.
230 }
231
232 haptic->hwdata->axes[haptic->naxes] = offset;
233 haptic->naxes++;
234
235 // Currently using the artificial limit of 3 axes.
236 if (haptic->naxes >= 3) {
237 return DIENUM_STOP;
238 }
239 }
240
241 return DIENUM_CONTINUE;
242}
243
244/*
245 * Callback to get all supported effects.
246 */
247#define EFFECT_TEST(e, s) \
248 if (WIN_IsEqualGUID(&pei->guid, &(e))) \
249 haptic->supported |= (s)
250static BOOL CALLBACK DI_EffectCallback(LPCDIEFFECTINFO pei, LPVOID pv)
251{
252 // Prepare the haptic device.
253 SDL_Haptic *haptic = (SDL_Haptic *)pv;
254
255 // Get supported.
256 EFFECT_TEST(GUID_Spring, SDL_HAPTIC_SPRING);
257 EFFECT_TEST(GUID_Damper, SDL_HAPTIC_DAMPER);
258 EFFECT_TEST(GUID_Inertia, SDL_HAPTIC_INERTIA);
259 EFFECT_TEST(GUID_Friction, SDL_HAPTIC_FRICTION);
260 EFFECT_TEST(GUID_ConstantForce, SDL_HAPTIC_CONSTANT);
261 EFFECT_TEST(GUID_CustomForce, SDL_HAPTIC_CUSTOM);
262 EFFECT_TEST(GUID_Sine, SDL_HAPTIC_SINE);
263 EFFECT_TEST(GUID_Square, SDL_HAPTIC_SQUARE);
264 EFFECT_TEST(GUID_Triangle, SDL_HAPTIC_TRIANGLE);
265 EFFECT_TEST(GUID_SawtoothUp, SDL_HAPTIC_SAWTOOTHUP);
266 EFFECT_TEST(GUID_SawtoothDown, SDL_HAPTIC_SAWTOOTHDOWN);
267 EFFECT_TEST(GUID_RampForce, SDL_HAPTIC_RAMP);
268
269 // Check for more.
270 return DIENUM_CONTINUE;
271}
272
273/*
274 * Opens the haptic device.
275 *
276 * Steps:
277 * - Set cooperative level.
278 * - Set data format.
279 * - Acquire exclusiveness.
280 * - Reset actuators.
281 * - Get supported features.
282 */
283static bool SDL_DINPUT_HapticOpenFromDevice(SDL_Haptic *haptic, LPDIRECTINPUTDEVICE8 device8, bool is_joystick)
284{
285 HRESULT ret;
286 DIPROPDWORD dipdw;
287
288 // Allocate the hwdata
289 haptic->hwdata = (struct haptic_hwdata *)SDL_calloc(1, sizeof(*haptic->hwdata));
290 if (!haptic->hwdata) {
291 return false;
292 }
293
294 // We'll use the device8 from now on.
295 haptic->hwdata->device = device8;
296 haptic->hwdata->is_joystick = is_joystick;
297
298 /* !!! FIXME: opening a haptic device here first will make an attempt to
299 !!! FIXME: SDL_OpenJoystick() that same device fail later, since we
300 !!! FIXME: have it open in exclusive mode. But this will allow
301 !!! FIXME: SDL_OpenJoystick() followed by SDL_OpenHapticFromJoystick()
302 !!! FIXME: to work, and that's probably the common case. Still,
303 !!! FIXME: ideally, We need to unify the opening code. */
304
305 if (!is_joystick) { // if is_joystick, we already set this up elsewhere.
306 // Grab it exclusively to use force feedback stuff.
307 ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device,
308 SDL_HelperWindow,
309 DISCL_EXCLUSIVE |
310 DISCL_BACKGROUND);
311 if (FAILED(ret)) {
312 DI_SetError("Setting cooperative level to exclusive", ret);
313 goto acquire_err;
314 }
315
316 // Set data format.
317 ret = IDirectInputDevice8_SetDataFormat(haptic->hwdata->device,
318 &SDL_c_dfDIJoystick2);
319 if (FAILED(ret)) {
320 DI_SetError("Setting data format", ret);
321 goto acquire_err;
322 }
323
324 // Acquire the device.
325 ret = IDirectInputDevice8_Acquire(haptic->hwdata->device);
326 if (FAILED(ret)) {
327 DI_SetError("Acquiring DirectInput device", ret);
328 goto acquire_err;
329 }
330 }
331
332 // Get number of axes.
333 ret = IDirectInputDevice8_EnumObjects(haptic->hwdata->device,
334 DI_DeviceObjectCallback,
335 haptic, DIDFT_AXIS);
336 if (FAILED(ret)) {
337 DI_SetError("Getting device axes", ret);
338 goto acquire_err;
339 }
340
341 // Reset all actuators - just in case.
342 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
343 DISFFC_RESET);
344 if (FAILED(ret)) {
345 DI_SetError("Resetting device", ret);
346 goto acquire_err;
347 }
348
349 // Enabling actuators.
350 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
351 DISFFC_SETACTUATORSON);
352 if (FAILED(ret)) {
353 DI_SetError("Enabling actuators", ret);
354 goto acquire_err;
355 }
356
357 // Get supported effects.
358 ret = IDirectInputDevice8_EnumEffects(haptic->hwdata->device,
359 DI_EffectCallback, haptic,
360 DIEFT_ALL);
361 if (FAILED(ret)) {
362 DI_SetError("Enumerating supported effects", ret);
363 goto acquire_err;
364 }
365 if (haptic->supported == 0) { // Error since device supports nothing.
366 SDL_SetError("Haptic: Internal error on finding supported effects.");
367 goto acquire_err;
368 }
369
370 // Check autogain and autocenter.
371 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
372 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
373 dipdw.diph.dwObj = 0;
374 dipdw.diph.dwHow = DIPH_DEVICE;
375 dipdw.dwData = 10000;
376 ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,
377 DIPROP_FFGAIN, &dipdw.diph);
378 if (!FAILED(ret)) { // Gain is supported.
379 haptic->supported |= SDL_HAPTIC_GAIN;
380 }
381 dipdw.diph.dwObj = 0;
382 dipdw.diph.dwHow = DIPH_DEVICE;
383 dipdw.dwData = DIPROPAUTOCENTER_OFF;
384 ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,
385 DIPROP_AUTOCENTER, &dipdw.diph);
386 if (!FAILED(ret)) { // Autocenter is supported.
387 haptic->supported |= SDL_HAPTIC_AUTOCENTER;
388 }
389
390 // Status is always supported.
391 haptic->supported |= SDL_HAPTIC_STATUS | SDL_HAPTIC_PAUSE;
392
393 // Check maximum effects.
394 haptic->neffects = 128; /* This is not actually supported as thus under windows,
395 there is no way to tell the number of EFFECTS that a
396 device can hold, so we'll just use a "random" number
397 instead and put warnings in SDL_haptic.h */
398 haptic->nplaying = 128; // Even more impossible to get this then neffects.
399
400 // Prepare effects memory.
401 haptic->effects = (struct haptic_effect *)
402 SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects);
403 if (!haptic->effects) {
404 goto acquire_err;
405 }
406 // Clear the memory
407 SDL_memset(haptic->effects, 0,
408 sizeof(struct haptic_effect) * haptic->neffects);
409
410 return true;
411
412 // Error handling
413acquire_err:
414 IDirectInputDevice8_Unacquire(haptic->hwdata->device);
415 return false;
416}
417
418bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)
419{
420 HRESULT ret;
421 LPDIRECTINPUTDEVICE8 device;
422
423 // Open the device
424 ret = IDirectInput8_CreateDevice(dinput, &item->instance.guidInstance,
425 &device, NULL);
426 if (FAILED(ret)) {
427 DI_SetError("Creating DirectInput device", ret);
428 return false;
429 }
430
431 if (!SDL_DINPUT_HapticOpenFromDevice(haptic, device, false)) {
432 IDirectInputDevice8_Release(device);
433 return false;
434 }
435 return true;
436}
437
438bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
439{
440 HRESULT ret;
441 DIDEVICEINSTANCE hap_instance, joy_instance;
442
443 hap_instance.dwSize = sizeof(DIDEVICEINSTANCE);
444 joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);
445
446 // Get the device instances.
447 ret = IDirectInputDevice8_GetDeviceInfo(haptic->hwdata->device,
448 &hap_instance);
449 if (FAILED(ret)) {
450 return false;
451 }
452 ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice,
453 &joy_instance);
454 if (FAILED(ret)) {
455 return false;
456 }
457
458 return (WIN_IsEqualGUID(&hap_instance.guidInstance, &joy_instance.guidInstance) == TRUE);
459}
460
461bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
462{
463 SDL_hapticlist_item *item;
464 HRESULT ret;
465 DIDEVICEINSTANCE joy_instance;
466
467 joy_instance.dwSize = sizeof(DIDEVICEINSTANCE);
468 ret = IDirectInputDevice8_GetDeviceInfo(joystick->hwdata->InputDevice, &joy_instance);
469 if (FAILED(ret)) {
470 return false;
471 }
472
473 // Since it comes from a joystick we have to try to match it with a haptic device on our haptic list.
474 for (item = SDL_hapticlist; item; item = item->next) {
475 if (WIN_IsEqualGUID(&item->instance.guidInstance, &joy_instance.guidInstance)) {
476 haptic->instance_id = item->instance_id;
477 haptic->name = SDL_strdup(item->name);
478 return SDL_DINPUT_HapticOpenFromDevice(haptic, joystick->hwdata->InputDevice, true);
479 }
480 }
481
482 return SDL_SetError("Couldn't find joystick in haptic device list");
483}
484
485void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)
486{
487 IDirectInputDevice8_Unacquire(haptic->hwdata->device);
488
489 // Only release if isn't grabbed by a joystick.
490 if (haptic->hwdata->is_joystick == 0) {
491 IDirectInputDevice8_Release(haptic->hwdata->device);
492 }
493}
494
495void SDL_DINPUT_HapticQuit(void)
496{
497 if (dinput != NULL) {
498 IDirectInput8_Release(dinput);
499 dinput = NULL;
500 }
501
502 if (coinitialized) {
503 WIN_CoUninitialize();
504 coinitialized = false;
505 }
506}
507
508/*
509 * Converts an SDL trigger button to an DIEFFECT trigger button.
510 */
511static DWORD DIGetTriggerButton(Uint16 button)
512{
513 DWORD dwTriggerButton;
514
515 dwTriggerButton = DIEB_NOTRIGGER;
516
517 if (button != 0) {
518 dwTriggerButton = DIJOFS_BUTTON(button - 1);
519 }
520
521 return dwTriggerButton;
522}
523
524/*
525 * Sets the direction.
526 */
527static bool SDL_SYS_SetDirection(DIEFFECT *effect, const SDL_HapticDirection *dir, int naxes)
528{
529 LONG *rglDir;
530
531 // Handle no axes a part.
532 if (naxes == 0) {
533 effect->dwFlags |= DIEFF_SPHERICAL; // Set as default.
534 effect->rglDirection = NULL;
535 return true;
536 }
537
538 // Has axes.
539 rglDir = (LONG *)SDL_malloc(sizeof(LONG) * naxes);
540 if (!rglDir) {
541 return false;
542 }
543 SDL_memset(rglDir, 0, sizeof(LONG) * naxes);
544 effect->rglDirection = rglDir;
545
546 switch (dir->type) {
547 case SDL_HAPTIC_POLAR:
548 effect->dwFlags |= DIEFF_POLAR;
549 rglDir[0] = dir->dir[0];
550 return true;
551 case SDL_HAPTIC_CARTESIAN:
552 effect->dwFlags |= DIEFF_CARTESIAN;
553 rglDir[0] = dir->dir[0];
554 if (naxes > 1) {
555 rglDir[1] = dir->dir[1];
556 }
557 if (naxes > 2) {
558 rglDir[2] = dir->dir[2];
559 }
560 return true;
561 case SDL_HAPTIC_SPHERICAL:
562 effect->dwFlags |= DIEFF_SPHERICAL;
563 rglDir[0] = dir->dir[0];
564 if (naxes > 1) {
565 rglDir[1] = dir->dir[1];
566 }
567 if (naxes > 2) {
568 rglDir[2] = dir->dir[2];
569 }
570 return true;
571 case SDL_HAPTIC_STEERING_AXIS:
572 effect->dwFlags |= DIEFF_CARTESIAN;
573 rglDir[0] = 0;
574 return true;
575
576 default:
577 return SDL_SetError("Haptic: Unknown direction type.");
578 }
579}
580
581// Clamps and converts.
582#define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)
583// Just converts.
584#define CONVERT(x) (((x)*10000) / 0x7FFF)
585/*
586 * Creates the DIEFFECT from a SDL_HapticEffect.
587 */
588static bool SDL_SYS_ToDIEFFECT(SDL_Haptic *haptic, DIEFFECT *dest,
589 const SDL_HapticEffect *src)
590{
591 int i;
592 DICONSTANTFORCE *constant;
593 DIPERIODIC *periodic;
594 DICONDITION *condition; // Actually an array of conditions - one per axis.
595 DIRAMPFORCE *ramp;
596 DICUSTOMFORCE *custom;
597 DIENVELOPE *envelope;
598 const SDL_HapticConstant *hap_constant;
599 const SDL_HapticPeriodic *hap_periodic;
600 const SDL_HapticCondition *hap_condition;
601 const SDL_HapticRamp *hap_ramp;
602 const SDL_HapticCustom *hap_custom;
603 DWORD *axes;
604
605 // Set global stuff.
606 SDL_memset(dest, 0, sizeof(DIEFFECT));
607 dest->dwSize = sizeof(DIEFFECT); // Set the structure size.
608 dest->dwSamplePeriod = 0; // Not used by us.
609 dest->dwGain = 10000; // Gain is set globally, not locally.
610 dest->dwFlags = DIEFF_OBJECTOFFSETS; // Seems obligatory.
611
612 // Envelope.
613 envelope = (DIENVELOPE *)SDL_calloc(1, sizeof(DIENVELOPE));
614 if (!envelope) {
615 return false;
616 }
617 dest->lpEnvelope = envelope;
618 envelope->dwSize = sizeof(DIENVELOPE); // Always should be this.
619
620 // Axes.
621 if (src->constant.direction.type == SDL_HAPTIC_STEERING_AXIS) {
622 dest->cAxes = 1;
623 } else {
624 dest->cAxes = haptic->naxes;
625 }
626 if (dest->cAxes > 0) {
627 axes = (DWORD *)SDL_malloc(sizeof(DWORD) * dest->cAxes);
628 if (!axes) {
629 return false;
630 }
631 axes[0] = haptic->hwdata->axes[0]; // Always at least one axis.
632 if (dest->cAxes > 1) {
633 axes[1] = haptic->hwdata->axes[1];
634 }
635 if (dest->cAxes > 2) {
636 axes[2] = haptic->hwdata->axes[2];
637 }
638 dest->rgdwAxes = axes;
639 }
640
641 // The big type handling switch, even bigger than Linux's version.
642 switch (src->type) {
643 case SDL_HAPTIC_CONSTANT:
644 hap_constant = &src->constant;
645 constant = (DICONSTANTFORCE *)SDL_calloc(1, sizeof(DICONSTANTFORCE));
646 if (!constant) {
647 return false;
648 }
649
650 // Specifics
651 constant->lMagnitude = CONVERT(hap_constant->level);
652 dest->cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
653 dest->lpvTypeSpecificParams = constant;
654
655 // Generics
656 dest->dwDuration = hap_constant->length * 1000UL; // In microseconds.
657 dest->dwTriggerButton = DIGetTriggerButton(hap_constant->button);
658 dest->dwTriggerRepeatInterval = hap_constant->interval;
659 dest->dwStartDelay = hap_constant->delay * 1000UL; // In microseconds.
660
661 // Direction.
662 if (!SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes)) {
663 return false;
664 }
665
666 // Envelope
667 if ((hap_constant->attack_length == 0) && (hap_constant->fade_length == 0)) {
668 SDL_free(dest->lpEnvelope);
669 dest->lpEnvelope = NULL;
670 } else {
671 envelope->dwAttackLevel = CCONVERT(hap_constant->attack_level);
672 envelope->dwAttackTime = hap_constant->attack_length * 1000UL;
673 envelope->dwFadeLevel = CCONVERT(hap_constant->fade_level);
674 envelope->dwFadeTime = hap_constant->fade_length * 1000UL;
675 }
676
677 break;
678
679 case SDL_HAPTIC_SINE:
680 case SDL_HAPTIC_SQUARE:
681 case SDL_HAPTIC_TRIANGLE:
682 case SDL_HAPTIC_SAWTOOTHUP:
683 case SDL_HAPTIC_SAWTOOTHDOWN:
684 hap_periodic = &src->periodic;
685 periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(DIPERIODIC));
686 if (!periodic) {
687 return false;
688 }
689
690 // Specifics
691 periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude));
692 periodic->lOffset = CONVERT(hap_periodic->offset);
693 periodic->dwPhase =
694 (hap_periodic->phase + (hap_periodic->magnitude < 0 ? 18000 : 0)) % 36000;
695 periodic->dwPeriod = hap_periodic->period * 1000;
696 dest->cbTypeSpecificParams = sizeof(DIPERIODIC);
697 dest->lpvTypeSpecificParams = periodic;
698
699 // Generics
700 dest->dwDuration = hap_periodic->length * 1000UL; // In microseconds.
701 dest->dwTriggerButton = DIGetTriggerButton(hap_periodic->button);
702 dest->dwTriggerRepeatInterval = hap_periodic->interval;
703 dest->dwStartDelay = hap_periodic->delay * 1000UL; // In microseconds.
704
705 // Direction.
706 if (!SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes)) {
707 return false;
708 }
709
710 // Envelope
711 if ((hap_periodic->attack_length == 0) && (hap_periodic->fade_length == 0)) {
712 SDL_free(dest->lpEnvelope);
713 dest->lpEnvelope = NULL;
714 } else {
715 envelope->dwAttackLevel = CCONVERT(hap_periodic->attack_level);
716 envelope->dwAttackTime = hap_periodic->attack_length * 1000UL;
717 envelope->dwFadeLevel = CCONVERT(hap_periodic->fade_level);
718 envelope->dwFadeTime = hap_periodic->fade_length * 1000UL;
719 }
720
721 break;
722
723 case SDL_HAPTIC_SPRING:
724 case SDL_HAPTIC_DAMPER:
725 case SDL_HAPTIC_INERTIA:
726 case SDL_HAPTIC_FRICTION:
727 hap_condition = &src->condition;
728 condition = (DICONDITION *)SDL_calloc(dest->cAxes, sizeof(DICONDITION));
729 if (!condition) {
730 return false;
731 }
732
733 // Specifics
734 for (i = 0; i < (int)dest->cAxes; i++) {
735 condition[i].lOffset = CONVERT(hap_condition->center[i]);
736 condition[i].lPositiveCoefficient =
737 CONVERT(hap_condition->right_coeff[i]);
738 condition[i].lNegativeCoefficient =
739 CONVERT(hap_condition->left_coeff[i]);
740 condition[i].dwPositiveSaturation =
741 CCONVERT(hap_condition->right_sat[i] / 2);
742 condition[i].dwNegativeSaturation =
743 CCONVERT(hap_condition->left_sat[i] / 2);
744 condition[i].lDeadBand = CCONVERT(hap_condition->deadband[i] / 2);
745 }
746 dest->cbTypeSpecificParams = sizeof(DICONDITION) * dest->cAxes;
747 dest->lpvTypeSpecificParams = condition;
748
749 // Generics
750 dest->dwDuration = hap_condition->length * 1000UL; // In microseconds.
751 dest->dwTriggerButton = DIGetTriggerButton(hap_condition->button);
752 dest->dwTriggerRepeatInterval = hap_condition->interval;
753 dest->dwStartDelay = hap_condition->delay * 1000UL; // In microseconds.
754
755 // Direction.
756 if (!SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes)) {
757 return false;
758 }
759
760 // Envelope - Not actually supported by most CONDITION implementations.
761 SDL_free(dest->lpEnvelope);
762 dest->lpEnvelope = NULL;
763
764 break;
765
766 case SDL_HAPTIC_RAMP:
767 hap_ramp = &src->ramp;
768 ramp = (DIRAMPFORCE *)SDL_calloc(1, sizeof(DIRAMPFORCE));
769 if (!ramp) {
770 return false;
771 }
772
773 // Specifics
774 ramp->lStart = CONVERT(hap_ramp->start);
775 ramp->lEnd = CONVERT(hap_ramp->end);
776 dest->cbTypeSpecificParams = sizeof(DIRAMPFORCE);
777 dest->lpvTypeSpecificParams = ramp;
778
779 // Generics
780 dest->dwDuration = hap_ramp->length * 1000UL; // In microseconds.
781 dest->dwTriggerButton = DIGetTriggerButton(hap_ramp->button);
782 dest->dwTriggerRepeatInterval = hap_ramp->interval;
783 dest->dwStartDelay = hap_ramp->delay * 1000UL; // In microseconds.
784
785 // Direction.
786 if (!SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes)) {
787 return false;
788 }
789
790 // Envelope
791 if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) {
792 SDL_free(dest->lpEnvelope);
793 dest->lpEnvelope = NULL;
794 } else {
795 envelope->dwAttackLevel = CCONVERT(hap_ramp->attack_level);
796 envelope->dwAttackTime = hap_ramp->attack_length * 1000UL;
797 envelope->dwFadeLevel = CCONVERT(hap_ramp->fade_level);
798 envelope->dwFadeTime = hap_ramp->fade_length * 1000UL;
799 }
800
801 break;
802
803 case SDL_HAPTIC_CUSTOM:
804 hap_custom = &src->custom;
805 custom = (DICUSTOMFORCE *)SDL_calloc(1, sizeof(DICUSTOMFORCE));
806 if (!custom) {
807 return false;
808 }
809
810 // Specifics
811 custom->cChannels = hap_custom->channels;
812 custom->dwSamplePeriod = hap_custom->period * 1000UL;
813 custom->cSamples = hap_custom->samples;
814 custom->rglForceData = (LPLONG)SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels);
815 for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) { // Copy data.
816 custom->rglForceData[i] = CCONVERT(hap_custom->data[i]);
817 }
818 dest->cbTypeSpecificParams = sizeof(DICUSTOMFORCE);
819 dest->lpvTypeSpecificParams = custom;
820
821 // Generics
822 dest->dwDuration = hap_custom->length * 1000UL; // In microseconds.
823 dest->dwTriggerButton = DIGetTriggerButton(hap_custom->button);
824 dest->dwTriggerRepeatInterval = hap_custom->interval;
825 dest->dwStartDelay = hap_custom->delay * 1000UL; // In microseconds.
826
827 // Direction.
828 if (!SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes)) {
829 return false;
830 }
831
832 // Envelope
833 if ((hap_custom->attack_length == 0) && (hap_custom->fade_length == 0)) {
834 SDL_free(dest->lpEnvelope);
835 dest->lpEnvelope = NULL;
836 } else {
837 envelope->dwAttackLevel = CCONVERT(hap_custom->attack_level);
838 envelope->dwAttackTime = hap_custom->attack_length * 1000UL;
839 envelope->dwFadeLevel = CCONVERT(hap_custom->fade_level);
840 envelope->dwFadeTime = hap_custom->fade_length * 1000UL;
841 }
842
843 break;
844
845 default:
846 return SDL_SetError("Haptic: Unknown effect type.");
847 }
848
849 return true;
850}
851
852/*
853 * Frees an DIEFFECT allocated by SDL_SYS_ToDIEFFECT.
854 */
855static void SDL_SYS_HapticFreeDIEFFECT(DIEFFECT *effect, int type)
856{
857 DICUSTOMFORCE *custom;
858
859 SDL_free(effect->lpEnvelope);
860 effect->lpEnvelope = NULL;
861 SDL_free(effect->rgdwAxes);
862 effect->rgdwAxes = NULL;
863 if (effect->lpvTypeSpecificParams) {
864 if (type == SDL_HAPTIC_CUSTOM) { // Must free the custom data.
865 custom = (DICUSTOMFORCE *)effect->lpvTypeSpecificParams;
866 SDL_free(custom->rglForceData);
867 custom->rglForceData = NULL;
868 }
869 SDL_free(effect->lpvTypeSpecificParams);
870 effect->lpvTypeSpecificParams = NULL;
871 }
872 SDL_free(effect->rglDirection);
873 effect->rglDirection = NULL;
874}
875
876/*
877 * Gets the effect type from the generic SDL haptic effect wrapper.
878 */
879// NOLINTNEXTLINE(readability-const-return-type): Can't fix Windows' headers
880static REFGUID SDL_SYS_HapticEffectType(const SDL_HapticEffect *effect)
881{
882 switch (effect->type) {
883 case SDL_HAPTIC_CONSTANT:
884 return &GUID_ConstantForce;
885
886 case SDL_HAPTIC_RAMP:
887 return &GUID_RampForce;
888
889 case SDL_HAPTIC_SQUARE:
890 return &GUID_Square;
891
892 case SDL_HAPTIC_SINE:
893 return &GUID_Sine;
894
895 case SDL_HAPTIC_TRIANGLE:
896 return &GUID_Triangle;
897
898 case SDL_HAPTIC_SAWTOOTHUP:
899 return &GUID_SawtoothUp;
900
901 case SDL_HAPTIC_SAWTOOTHDOWN:
902 return &GUID_SawtoothDown;
903
904 case SDL_HAPTIC_SPRING:
905 return &GUID_Spring;
906
907 case SDL_HAPTIC_DAMPER:
908 return &GUID_Damper;
909
910 case SDL_HAPTIC_INERTIA:
911 return &GUID_Inertia;
912
913 case SDL_HAPTIC_FRICTION:
914 return &GUID_Friction;
915
916 case SDL_HAPTIC_CUSTOM:
917 return &GUID_CustomForce;
918
919 default:
920 return NULL;
921 }
922}
923bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)
924{
925 HRESULT ret;
926 REFGUID type = SDL_SYS_HapticEffectType(base);
927
928 if (!type) {
929 return SDL_SetError("Haptic: Unknown effect type.");
930 }
931
932 // Get the effect.
933 if (!SDL_SYS_ToDIEFFECT(haptic, &effect->hweffect->effect, base)) {
934 goto err_effectdone;
935 }
936
937 // Create the actual effect.
938 ret = IDirectInputDevice8_CreateEffect(haptic->hwdata->device, type,
939 &effect->hweffect->effect,
940 &effect->hweffect->ref, NULL);
941 if (FAILED(ret)) {
942 DI_SetError("Unable to create effect", ret);
943 goto err_effectdone;
944 }
945
946 return true;
947
948err_effectdone:
949 SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, base->type);
950 return false;
951}
952
953bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
954{
955 HRESULT ret;
956 DWORD flags;
957 DIEFFECT temp;
958
959 // Get the effect.
960 SDL_memset(&temp, 0, sizeof(DIEFFECT));
961 if (!SDL_SYS_ToDIEFFECT(haptic, &temp, data)) {
962 goto err_update;
963 }
964
965 /* Set the flags. Might be worthwhile to diff temp with loaded effect and
966 * only change those parameters. */
967 flags = DIEP_DIRECTION |
968 DIEP_DURATION |
969 DIEP_ENVELOPE |
970 DIEP_STARTDELAY |
971 DIEP_TRIGGERBUTTON |
972 DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
973
974 // Create the actual effect.
975 ret =
976 IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags);
977 if (ret == DIERR_NOTEXCLUSIVEACQUIRED) {
978 IDirectInputDevice8_Unacquire(haptic->hwdata->device);
979 ret = IDirectInputDevice8_SetCooperativeLevel(haptic->hwdata->device, SDL_HelperWindow, DISCL_EXCLUSIVE | DISCL_BACKGROUND);
980 if (SUCCEEDED(ret)) {
981 ret = DIERR_NOTACQUIRED;
982 }
983 }
984 if (ret == DIERR_INPUTLOST || ret == DIERR_NOTACQUIRED) {
985 ret = IDirectInputDevice8_Acquire(haptic->hwdata->device);
986 if (SUCCEEDED(ret)) {
987 ret = IDirectInputEffect_SetParameters(effect->hweffect->ref, &temp, flags);
988 }
989 }
990 if (FAILED(ret)) {
991 DI_SetError("Unable to update effect", ret);
992 goto err_update;
993 }
994
995 // Copy it over.
996 SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, data->type);
997 SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(DIEFFECT));
998
999 return true;
1000
1001err_update:
1002 SDL_SYS_HapticFreeDIEFFECT(&temp, data->type);
1003 return false;
1004}
1005
1006bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)
1007{
1008 HRESULT ret;
1009 DWORD iter;
1010
1011 // Check if it's infinite.
1012 if (iterations == SDL_HAPTIC_INFINITY) {
1013 iter = INFINITE;
1014 } else {
1015 iter = iterations;
1016 }
1017
1018 // Run the effect.
1019 ret = IDirectInputEffect_Start(effect->hweffect->ref, iter, 0);
1020 if (FAILED(ret)) {
1021 return DI_SetError("Running the effect", ret);
1022 }
1023 return true;
1024}
1025
1026bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1027{
1028 HRESULT ret;
1029
1030 ret = IDirectInputEffect_Stop(effect->hweffect->ref);
1031 if (FAILED(ret)) {
1032 return DI_SetError("Unable to stop effect", ret);
1033 }
1034 return true;
1035}
1036
1037void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1038{
1039 HRESULT ret;
1040
1041 ret = IDirectInputEffect_Unload(effect->hweffect->ref);
1042 if (FAILED(ret)) {
1043 DI_SetError("Removing effect from the device", ret);
1044 }
1045 SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, effect->effect.type);
1046}
1047
1048int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)
1049{
1050 HRESULT ret;
1051 DWORD status;
1052
1053 ret = IDirectInputEffect_GetEffectStatus(effect->hweffect->ref, &status);
1054 if (FAILED(ret)) {
1055 DI_SetError("Getting effect status", ret);
1056 return -1;
1057 }
1058
1059 if (status == 0) {
1060 return 0;
1061 }
1062 return 1;
1063}
1064
1065bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)
1066{
1067 HRESULT ret;
1068 DIPROPDWORD dipdw;
1069
1070 // Create the weird structure thingy.
1071 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
1072 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
1073 dipdw.diph.dwObj = 0;
1074 dipdw.diph.dwHow = DIPH_DEVICE;
1075 dipdw.dwData = (DWORD)gain * 100; // 0 to 10,000
1076
1077 // Try to set the autocenter.
1078 ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,
1079 DIPROP_FFGAIN, &dipdw.diph);
1080 if (FAILED(ret)) {
1081 return DI_SetError("Setting gain", ret);
1082 }
1083 return true;
1084}
1085
1086bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
1087{
1088 HRESULT ret;
1089 DIPROPDWORD dipdw;
1090
1091 // Create the weird structure thingy.
1092 dipdw.diph.dwSize = sizeof(DIPROPDWORD);
1093 dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
1094 dipdw.diph.dwObj = 0;
1095 dipdw.diph.dwHow = DIPH_DEVICE;
1096 dipdw.dwData = (autocenter == 0) ? DIPROPAUTOCENTER_OFF : DIPROPAUTOCENTER_ON;
1097
1098 // Try to set the autocenter.
1099 ret = IDirectInputDevice8_SetProperty(haptic->hwdata->device,
1100 DIPROP_AUTOCENTER, &dipdw.diph);
1101 if (FAILED(ret)) {
1102 return DI_SetError("Setting autocenter", ret);
1103 }
1104 return true;
1105}
1106
1107bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)
1108{
1109 HRESULT ret;
1110
1111 // Pause the device.
1112 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
1113 DISFFC_PAUSE);
1114 if (FAILED(ret)) {
1115 return DI_SetError("Pausing the device", ret);
1116 }
1117 return true;
1118}
1119
1120bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)
1121{
1122 HRESULT ret;
1123
1124 // Unpause the device.
1125 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
1126 DISFFC_CONTINUE);
1127 if (FAILED(ret)) {
1128 return DI_SetError("Pausing the device", ret);
1129 }
1130 return true;
1131}
1132
1133bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)
1134{
1135 HRESULT ret;
1136
1137 // Try to stop the effects.
1138 ret = IDirectInputDevice8_SendForceFeedbackCommand(haptic->hwdata->device,
1139 DISFFC_STOPALL);
1140 if (FAILED(ret)) {
1141 return DI_SetError("Stopping the device", ret);
1142 }
1143 return true;
1144}
1145
1146#else // !SDL_HAPTIC_DINPUT
1147
1148typedef struct DIDEVICEINSTANCE DIDEVICEINSTANCE;
1149typedef struct SDL_hapticlist_item SDL_hapticlist_item;
1150
1151bool SDL_DINPUT_HapticInit(void)
1152{
1153 return true;
1154}
1155
1156bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)
1157{
1158 return SDL_Unsupported();
1159}
1160
1161bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)
1162{
1163 return SDL_Unsupported();
1164}
1165
1166bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)
1167{
1168 return SDL_Unsupported();
1169}
1170
1171bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
1172{
1173 return false;
1174}
1175
1176bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
1177{
1178 return SDL_Unsupported();
1179}
1180
1181void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)
1182{
1183}
1184
1185void SDL_DINPUT_HapticQuit(void)
1186{
1187}
1188
1189bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)
1190{
1191 return SDL_Unsupported();
1192}
1193
1194bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
1195{
1196 return SDL_Unsupported();
1197}
1198
1199bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)
1200{
1201 return SDL_Unsupported();
1202}
1203
1204bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1205{
1206 return SDL_Unsupported();
1207}
1208
1209void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1210{
1211}
1212
1213int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)
1214{
1215 SDL_Unsupported();
1216 return -1;
1217}
1218
1219bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)
1220{
1221 return SDL_Unsupported();
1222}
1223
1224bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
1225{
1226 return SDL_Unsupported();
1227}
1228
1229bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)
1230{
1231 return SDL_Unsupported();
1232}
1233
1234bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)
1235{
1236 return SDL_Unsupported();
1237}
1238
1239bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)
1240{
1241 return SDL_Unsupported();
1242}
1243
1244#endif // SDL_HAPTIC_DINPUT