Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/haptic/windows/SDL_dinputhaptic.c
9912 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
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
35
extern HWND SDL_HelperWindow;
36
#else
37
static const HWND SDL_HelperWindow = NULL;
38
#endif
39
40
/*
41
* Internal stuff.
42
*/
43
static bool coinitialized = false;
44
static LPDIRECTINPUT8 dinput = NULL;
45
46
/*
47
* Like SDL_SetError but for DX error codes.
48
*/
49
static 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
*/
57
static 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
64
bool 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
128
bool 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
187
bool 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
*/
209
static 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)
250
static 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
*/
283
static 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
413
acquire_err:
414
IDirectInputDevice8_Unacquire(haptic->hwdata->device);
415
return false;
416
}
417
418
bool 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
438
bool 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
461
bool 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
485
void 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
495
void 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
*/
511
static 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
*/
527
static 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
*/
588
static 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
*/
855
static 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
880
static 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
}
923
bool 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
948
err_effectdone:
949
SDL_SYS_HapticFreeDIEFFECT(&effect->hweffect->effect, base->type);
950
return false;
951
}
952
953
bool 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
1001
err_update:
1002
SDL_SYS_HapticFreeDIEFFECT(&temp, data->type);
1003
return false;
1004
}
1005
1006
bool 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
1026
bool 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
1037
void 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
1048
int 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
1065
bool 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
1086
bool 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
1107
bool 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
1120
bool 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
1133
bool 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
1148
typedef struct DIDEVICEINSTANCE DIDEVICEINSTANCE;
1149
typedef struct SDL_hapticlist_item SDL_hapticlist_item;
1150
1151
bool SDL_DINPUT_HapticInit(void)
1152
{
1153
return true;
1154
}
1155
1156
bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance)
1157
{
1158
return SDL_Unsupported();
1159
}
1160
1161
bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance)
1162
{
1163
return SDL_Unsupported();
1164
}
1165
1166
bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item)
1167
{
1168
return SDL_Unsupported();
1169
}
1170
1171
bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
1172
{
1173
return false;
1174
}
1175
1176
bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
1177
{
1178
return SDL_Unsupported();
1179
}
1180
1181
void SDL_DINPUT_HapticClose(SDL_Haptic *haptic)
1182
{
1183
}
1184
1185
void SDL_DINPUT_HapticQuit(void)
1186
{
1187
}
1188
1189
bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base)
1190
{
1191
return SDL_Unsupported();
1192
}
1193
1194
bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
1195
{
1196
return SDL_Unsupported();
1197
}
1198
1199
bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)
1200
{
1201
return SDL_Unsupported();
1202
}
1203
1204
bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1205
{
1206
return SDL_Unsupported();
1207
}
1208
1209
void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1210
{
1211
}
1212
1213
int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)
1214
{
1215
SDL_Unsupported();
1216
return -1;
1217
}
1218
1219
bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain)
1220
{
1221
return SDL_Unsupported();
1222
}
1223
1224
bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
1225
{
1226
return SDL_Unsupported();
1227
}
1228
1229
bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic)
1230
{
1231
return SDL_Unsupported();
1232
}
1233
1234
bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic)
1235
{
1236
return SDL_Unsupported();
1237
}
1238
1239
bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic)
1240
{
1241
return SDL_Unsupported();
1242
}
1243
1244
#endif // SDL_HAPTIC_DINPUT
1245
1246