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