Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/haptic/darwin/SDL_syshaptic.c
9913 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
#ifdef SDL_HAPTIC_IOKIT
24
25
#include "../SDL_syshaptic.h"
26
#include "../../joystick/SDL_sysjoystick.h" // For the real SDL_Joystick
27
#include "../../joystick/darwin/SDL_iokitjoystick_c.h" // For joystick hwdata
28
#include "SDL_syshaptic_c.h"
29
30
#include <IOKit/IOKitLib.h>
31
#include <IOKit/hid/IOHIDKeys.h>
32
#include <IOKit/hid/IOHIDUsageTables.h>
33
#include <ForceFeedback/ForceFeedback.h>
34
#include <ForceFeedback/ForceFeedbackConstants.h>
35
36
#ifndef IO_OBJECT_NULL
37
#define IO_OBJECT_NULL ((io_service_t)0)
38
#endif
39
40
/*
41
* List of available haptic devices.
42
*/
43
typedef struct SDL_hapticlist_item
44
{
45
SDL_HapticID instance_id;
46
char name[256]; // Name of the device.
47
48
io_service_t dev; // Node we use to create the device.
49
SDL_Haptic *haptic; // Haptic currently associated with it.
50
51
// Usage pages for determining if it's a mouse or not.
52
long usage;
53
long usagePage;
54
55
struct SDL_hapticlist_item *next;
56
} SDL_hapticlist_item;
57
58
/*
59
* Haptic system hardware data.
60
*/
61
struct haptic_hwdata
62
{
63
FFDeviceObjectReference device; // Hardware device.
64
UInt8 axes[3];
65
};
66
67
/*
68
* Haptic system effect data.
69
*/
70
struct haptic_hweffect
71
{
72
FFEffectObjectReference ref; // Reference.
73
struct FFEFFECT effect; // Hardware effect.
74
};
75
76
/*
77
* Prototypes.
78
*/
79
static void SDL_SYS_HapticFreeFFEFFECT(FFEFFECT *effect, int type);
80
static bool HIDGetDeviceProduct(io_service_t dev, char *name);
81
82
static SDL_hapticlist_item *SDL_hapticlist = NULL;
83
static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;
84
static int numhaptics = -1;
85
86
/*
87
* Like strerror but for force feedback errors.
88
*/
89
static const char *FFStrError(unsigned int err)
90
{
91
switch (err) {
92
case FFERR_DEVICEFULL:
93
return "device full";
94
// This should be valid, but for some reason isn't defined...
95
/* case FFERR_DEVICENOTREG:
96
return "device not registered"; */
97
case FFERR_DEVICEPAUSED:
98
return "device paused";
99
case FFERR_DEVICERELEASED:
100
return "device released";
101
case FFERR_EFFECTPLAYING:
102
return "effect playing";
103
case FFERR_EFFECTTYPEMISMATCH:
104
return "effect type mismatch";
105
case FFERR_EFFECTTYPENOTSUPPORTED:
106
return "effect type not supported";
107
case FFERR_GENERIC:
108
return "undetermined error";
109
case FFERR_HASEFFECTS:
110
return "device has effects";
111
case FFERR_INCOMPLETEEFFECT:
112
return "incomplete effect";
113
case FFERR_INTERNAL:
114
return "internal fault";
115
case FFERR_INVALIDDOWNLOADID:
116
return "invalid download id";
117
case FFERR_INVALIDPARAM:
118
return "invalid parameter";
119
case FFERR_MOREDATA:
120
return "more data";
121
case FFERR_NOINTERFACE:
122
return "interface not supported";
123
case FFERR_NOTDOWNLOADED:
124
return "effect is not downloaded";
125
case FFERR_NOTINITIALIZED:
126
return "object has not been initialized";
127
case FFERR_OUTOFMEMORY:
128
return "out of memory";
129
case FFERR_UNPLUGGED:
130
return "device is unplugged";
131
case FFERR_UNSUPPORTED:
132
return "function call unsupported";
133
case FFERR_UNSUPPORTEDAXIS:
134
return "axis unsupported";
135
136
default:
137
return "unknown error";
138
}
139
}
140
141
/*
142
* Initializes the haptic subsystem.
143
*/
144
bool SDL_SYS_HapticInit(void)
145
{
146
IOReturn result;
147
io_iterator_t iter;
148
CFDictionaryRef match;
149
io_service_t device;
150
151
if (numhaptics != -1) {
152
return SDL_SetError("Haptic subsystem already initialized!");
153
}
154
numhaptics = 0;
155
156
// Get HID devices.
157
match = IOServiceMatching(kIOHIDDeviceKey);
158
if (!match) {
159
return SDL_SetError("Haptic: Failed to get IOServiceMatching.");
160
}
161
162
// Now search I/O Registry for matching devices.
163
result = IOServiceGetMatchingServices(kIOMainPortDefault, match, &iter);
164
if (result != kIOReturnSuccess) {
165
return SDL_SetError("Haptic: Couldn't create a HID object iterator.");
166
}
167
// IOServiceGetMatchingServices consumes dictionary.
168
169
if (!IOIteratorIsValid(iter)) { // No iterator.
170
return true;
171
}
172
173
while ((device = IOIteratorNext(iter)) != IO_OBJECT_NULL) {
174
MacHaptic_MaybeAddDevice(device);
175
// always release as the AddDevice will retain IF it's a forcefeedback device
176
IOObjectRelease(device);
177
}
178
IOObjectRelease(iter);
179
180
return true;
181
}
182
183
int SDL_SYS_NumHaptics(void)
184
{
185
return numhaptics;
186
}
187
188
static SDL_hapticlist_item *HapticByDevIndex(int device_index)
189
{
190
SDL_hapticlist_item *item = SDL_hapticlist;
191
192
if ((device_index < 0) || (device_index >= numhaptics)) {
193
return NULL;
194
}
195
196
while (device_index > 0) {
197
SDL_assert(item != NULL);
198
--device_index;
199
item = item->next;
200
}
201
202
return item;
203
}
204
205
static SDL_hapticlist_item *HapticByInstanceID(SDL_HapticID instance_id)
206
{
207
SDL_hapticlist_item *item;
208
for (item = SDL_hapticlist; item; item = item->next) {
209
if (instance_id == item->instance_id) {
210
return item;
211
}
212
}
213
return NULL;
214
}
215
216
bool MacHaptic_MaybeAddDevice(io_object_t device)
217
{
218
IOReturn result;
219
CFMutableDictionaryRef hidProperties;
220
CFTypeRef refCF;
221
SDL_hapticlist_item *item;
222
223
if (numhaptics == -1) {
224
return false; // not initialized. We'll pick these up on enumeration if we init later.
225
}
226
227
// Check for force feedback.
228
if (FFIsForceFeedback(device) != FF_OK) {
229
return false;
230
}
231
232
// Make sure we don't already have it
233
for (item = SDL_hapticlist; item; item = item->next) {
234
if (IOObjectIsEqualTo((io_object_t)item->dev, device)) {
235
// Already added
236
return false;
237
}
238
}
239
240
item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));
241
if (!item) {
242
return SDL_SetError("Could not allocate haptic storage");
243
}
244
item->instance_id = SDL_GetNextObjectID();
245
246
// retain it as we are going to keep it around a while
247
IOObjectRetain(device);
248
249
// Set basic device data.
250
HIDGetDeviceProduct(device, item->name);
251
item->dev = device;
252
253
// Set usage pages.
254
hidProperties = 0;
255
refCF = 0;
256
result = IORegistryEntryCreateCFProperties(device,
257
&hidProperties,
258
kCFAllocatorDefault,
259
kNilOptions);
260
if ((result == KERN_SUCCESS) && hidProperties) {
261
refCF = CFDictionaryGetValue(hidProperties,
262
CFSTR(kIOHIDPrimaryUsagePageKey));
263
if (refCF) {
264
if (!CFNumberGetValue(refCF, kCFNumberLongType, &item->usagePage)) {
265
SDL_SetError("Haptic: Receiving device's usage page.");
266
}
267
refCF = CFDictionaryGetValue(hidProperties,
268
CFSTR(kIOHIDPrimaryUsageKey));
269
if (refCF) {
270
if (!CFNumberGetValue(refCF, kCFNumberLongType, &item->usage)) {
271
SDL_SetError("Haptic: Receiving device's usage.");
272
}
273
}
274
}
275
CFRelease(hidProperties);
276
}
277
278
if (!SDL_hapticlist_tail) {
279
SDL_hapticlist = SDL_hapticlist_tail = item;
280
} else {
281
SDL_hapticlist_tail->next = item;
282
SDL_hapticlist_tail = item;
283
}
284
285
// Device has been added.
286
++numhaptics;
287
288
return true;
289
}
290
291
bool MacHaptic_MaybeRemoveDevice(io_object_t device)
292
{
293
SDL_hapticlist_item *item;
294
SDL_hapticlist_item *prev = NULL;
295
296
if (numhaptics == -1) {
297
return false; // not initialized. ignore this.
298
}
299
300
for (item = SDL_hapticlist; item; item = item->next) {
301
// found it, remove it.
302
if (IOObjectIsEqualTo((io_object_t)item->dev, device)) {
303
bool result = item->haptic ? true : false;
304
305
if (prev) {
306
prev->next = item->next;
307
} else {
308
SDL_assert(SDL_hapticlist == item);
309
SDL_hapticlist = item->next;
310
}
311
if (item == SDL_hapticlist_tail) {
312
SDL_hapticlist_tail = prev;
313
}
314
315
// Need to decrement the haptic count
316
--numhaptics;
317
// !!! TODO: Send a haptic remove event?
318
319
IOObjectRelease(item->dev);
320
SDL_free(item);
321
return result;
322
}
323
prev = item;
324
}
325
326
return false;
327
}
328
329
SDL_HapticID SDL_SYS_HapticInstanceID(int index)
330
{
331
SDL_hapticlist_item *item;
332
item = HapticByDevIndex(index);
333
if (item) {
334
return item->instance_id;
335
}
336
return 0;
337
}
338
339
/*
340
* Return the name of a haptic device, does not need to be opened.
341
*/
342
const char *SDL_SYS_HapticName(int index)
343
{
344
SDL_hapticlist_item *item;
345
item = HapticByDevIndex(index);
346
if (item) {
347
return item->name;
348
}
349
return NULL;
350
}
351
352
/*
353
* Gets the device's product name.
354
*/
355
static bool HIDGetDeviceProduct(io_service_t dev, char *name)
356
{
357
CFMutableDictionaryRef hidProperties, usbProperties;
358
io_registry_entry_t parent1, parent2;
359
kern_return_t ret;
360
361
hidProperties = usbProperties = 0;
362
363
ret = IORegistryEntryCreateCFProperties(dev, &hidProperties,
364
kCFAllocatorDefault, kNilOptions);
365
if ((ret != KERN_SUCCESS) || !hidProperties) {
366
return SDL_SetError("Haptic: Unable to create CFProperties.");
367
}
368
369
/* macOS currently is not mirroring all USB properties to HID page so need to look at USB device page also
370
* get dictionary for USB properties: step up two levels and get CF dictionary for USB properties
371
*/
372
if ((KERN_SUCCESS ==
373
IORegistryEntryGetParentEntry(dev, kIOServicePlane, &parent1)) &&
374
(KERN_SUCCESS ==
375
IORegistryEntryGetParentEntry(parent1, kIOServicePlane, &parent2)) &&
376
(KERN_SUCCESS ==
377
IORegistryEntryCreateCFProperties(parent2, &usbProperties,
378
kCFAllocatorDefault,
379
kNilOptions))) {
380
if (usbProperties) {
381
CFTypeRef refCF = 0;
382
/* get device info
383
* try hid dictionary first, if fail then go to USB dictionary
384
*/
385
386
// Get product name
387
refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductKey));
388
if (!refCF) {
389
refCF = CFDictionaryGetValue(usbProperties,
390
CFSTR("USB Product Name"));
391
}
392
if (refCF) {
393
if (!CFStringGetCString(refCF, name, 256,
394
CFStringGetSystemEncoding())) {
395
return SDL_SetError("Haptic: CFStringGetCString error retrieving pDevice->product.");
396
}
397
}
398
399
CFRelease(usbProperties);
400
} else {
401
return SDL_SetError("Haptic: IORegistryEntryCreateCFProperties failed to create usbProperties.");
402
}
403
404
// Release stuff.
405
if (kIOReturnSuccess != IOObjectRelease(parent2)) {
406
SDL_SetError("Haptic: IOObjectRelease error with parent2.");
407
}
408
if (kIOReturnSuccess != IOObjectRelease(parent1)) {
409
SDL_SetError("Haptic: IOObjectRelease error with parent1.");
410
}
411
} else {
412
return SDL_SetError("Haptic: Error getting registry entries.");
413
}
414
415
return true;
416
}
417
418
#define FF_TEST(ff, s) \
419
if (features.supportedEffects & (ff)) \
420
supported |= (s)
421
/*
422
* Gets supported features.
423
*/
424
static bool GetSupportedFeatures(SDL_Haptic *haptic)
425
{
426
HRESULT ret;
427
FFDeviceObjectReference device;
428
FFCAPABILITIES features;
429
unsigned int supported;
430
Uint32 val;
431
432
device = haptic->hwdata->device;
433
434
ret = FFDeviceGetForceFeedbackCapabilities(device, &features);
435
if (ret != FF_OK) {
436
return SDL_SetError("Haptic: Unable to get device's supported features.");
437
}
438
439
supported = 0;
440
441
// Get maximum effects.
442
haptic->neffects = features.storageCapacity;
443
haptic->nplaying = features.playbackCapacity;
444
445
// Test for effects.
446
FF_TEST(FFCAP_ET_CONSTANTFORCE, SDL_HAPTIC_CONSTANT);
447
FF_TEST(FFCAP_ET_RAMPFORCE, SDL_HAPTIC_RAMP);
448
FF_TEST(FFCAP_ET_SQUARE, SDL_HAPTIC_SQUARE);
449
FF_TEST(FFCAP_ET_SINE, SDL_HAPTIC_SINE);
450
FF_TEST(FFCAP_ET_TRIANGLE, SDL_HAPTIC_TRIANGLE);
451
FF_TEST(FFCAP_ET_SAWTOOTHUP, SDL_HAPTIC_SAWTOOTHUP);
452
FF_TEST(FFCAP_ET_SAWTOOTHDOWN, SDL_HAPTIC_SAWTOOTHDOWN);
453
FF_TEST(FFCAP_ET_SPRING, SDL_HAPTIC_SPRING);
454
FF_TEST(FFCAP_ET_DAMPER, SDL_HAPTIC_DAMPER);
455
FF_TEST(FFCAP_ET_INERTIA, SDL_HAPTIC_INERTIA);
456
FF_TEST(FFCAP_ET_FRICTION, SDL_HAPTIC_FRICTION);
457
FF_TEST(FFCAP_ET_CUSTOMFORCE, SDL_HAPTIC_CUSTOM);
458
459
// Check if supports gain.
460
ret = FFDeviceGetForceFeedbackProperty(device, FFPROP_FFGAIN,
461
&val, sizeof(val));
462
if (ret == FF_OK) {
463
supported |= SDL_HAPTIC_GAIN;
464
} else if (ret != FFERR_UNSUPPORTED) {
465
return SDL_SetError("Haptic: Unable to get if device supports gain: %s.",
466
FFStrError(ret));
467
}
468
469
// Checks if supports autocenter.
470
ret = FFDeviceGetForceFeedbackProperty(device, FFPROP_AUTOCENTER,
471
&val, sizeof(val));
472
if (ret == FF_OK) {
473
supported |= SDL_HAPTIC_AUTOCENTER;
474
} else if (ret != FFERR_UNSUPPORTED) {
475
return SDL_SetError("Haptic: Unable to get if device supports autocenter: %s.",
476
FFStrError(ret));
477
}
478
479
// Check for axes, we have an artificial limit on axes
480
haptic->naxes = ((features.numFfAxes) > 3) ? 3 : features.numFfAxes;
481
// Actually store the axes we want to use
482
SDL_memcpy(haptic->hwdata->axes, features.ffAxes,
483
haptic->naxes * sizeof(Uint8));
484
485
// Always supported features.
486
supported |= SDL_HAPTIC_STATUS | SDL_HAPTIC_PAUSE;
487
488
haptic->supported = supported;
489
return true;
490
}
491
492
/*
493
* Opens the haptic device from the file descriptor.
494
*/
495
static bool SDL_SYS_HapticOpenFromService(SDL_Haptic *haptic, io_service_t service)
496
{
497
HRESULT ret;
498
499
// Allocate the hwdata
500
haptic->hwdata = (struct haptic_hwdata *) SDL_calloc(1, sizeof(*haptic->hwdata));
501
if (!haptic->hwdata) {
502
goto creat_err;
503
}
504
505
// Open the device
506
ret = FFCreateDevice(service, &haptic->hwdata->device);
507
if (ret != FF_OK) {
508
SDL_SetError("Haptic: Unable to create device from service: %s.", FFStrError(ret));
509
goto creat_err;
510
}
511
512
// Get supported features.
513
if (!GetSupportedFeatures(haptic)) {
514
goto open_err;
515
}
516
517
// Reset and then enable actuators.
518
ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,
519
FFSFFC_RESET);
520
if (ret != FF_OK) {
521
SDL_SetError("Haptic: Unable to reset device: %s.", FFStrError(ret));
522
goto open_err;
523
}
524
ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,
525
FFSFFC_SETACTUATORSON);
526
if (ret != FF_OK) {
527
SDL_SetError("Haptic: Unable to enable actuators: %s.",
528
FFStrError(ret));
529
goto open_err;
530
}
531
532
// Allocate effects memory.
533
haptic->effects = (struct haptic_effect *)
534
SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects);
535
if (!haptic->effects) {
536
goto open_err;
537
}
538
// Clear the memory
539
SDL_memset(haptic->effects, 0,
540
sizeof(struct haptic_effect) * haptic->neffects);
541
542
return true;
543
544
// Error handling
545
open_err:
546
FFReleaseDevice(haptic->hwdata->device);
547
creat_err:
548
if (haptic->hwdata) {
549
SDL_free(haptic->hwdata);
550
haptic->hwdata = NULL;
551
}
552
return false;
553
}
554
555
/*
556
* Opens a haptic device for usage.
557
*/
558
bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)
559
{
560
SDL_hapticlist_item *item;
561
item = HapticByInstanceID(haptic->instance_id);
562
563
return SDL_SYS_HapticOpenFromService(haptic, item->dev);
564
}
565
566
/*
567
* Opens a haptic device from first mouse it finds for usage.
568
*/
569
int SDL_SYS_HapticMouse(void)
570
{
571
int device_index = 0;
572
SDL_hapticlist_item *item;
573
574
for (item = SDL_hapticlist; item; item = item->next) {
575
if ((item->usagePage == kHIDPage_GenericDesktop) &&
576
(item->usage == kHIDUsage_GD_Mouse)) {
577
return device_index;
578
}
579
++device_index;
580
}
581
582
return 0;
583
}
584
585
/*
586
* Checks to see if a joystick has haptic features.
587
*/
588
bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)
589
{
590
#ifdef SDL_JOYSTICK_IOKIT
591
if (joystick->driver != &SDL_DARWIN_JoystickDriver) {
592
return false;
593
}
594
if (joystick->hwdata->ffservice != 0) {
595
return true;
596
}
597
#endif
598
return false;
599
}
600
601
/*
602
* Checks to see if the haptic device and joystick are in reality the same.
603
*/
604
bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
605
{
606
#ifdef SDL_JOYSTICK_IOKIT
607
if (joystick->driver != &SDL_DARWIN_JoystickDriver) {
608
return false;
609
}
610
if (IOObjectIsEqualTo((io_object_t)((size_t)haptic->hwdata->device),
611
joystick->hwdata->ffservice)) {
612
return true;
613
}
614
#endif
615
return false;
616
}
617
618
/*
619
* Opens a SDL_Haptic from a SDL_Joystick.
620
*/
621
bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
622
{
623
#ifdef SDL_JOYSTICK_IOKIT
624
SDL_hapticlist_item *item;
625
626
if (joystick->driver != &SDL_DARWIN_JoystickDriver) {
627
return false;
628
}
629
for (item = SDL_hapticlist; item; item = item->next) {
630
if (IOObjectIsEqualTo((io_object_t)item->dev,
631
joystick->hwdata->ffservice)) {
632
haptic->instance_id = item->instance_id;
633
break;
634
}
635
}
636
637
if (joystick->name) {
638
haptic->name = SDL_strdup(joystick->name);
639
}
640
641
return SDL_SYS_HapticOpenFromService(haptic, joystick->hwdata->ffservice);
642
#else
643
return false;
644
#endif
645
}
646
647
/*
648
* Closes the haptic device.
649
*/
650
void SDL_SYS_HapticClose(SDL_Haptic *haptic)
651
{
652
if (haptic->hwdata) {
653
654
// Free Effects.
655
SDL_free(haptic->effects);
656
haptic->effects = NULL;
657
haptic->neffects = 0;
658
659
// Clean up
660
FFReleaseDevice(haptic->hwdata->device);
661
662
// Free
663
SDL_free(haptic->hwdata);
664
haptic->hwdata = NULL;
665
}
666
}
667
668
/*
669
* Clean up after system specific haptic stuff
670
*/
671
void SDL_SYS_HapticQuit(void)
672
{
673
SDL_hapticlist_item *item;
674
SDL_hapticlist_item *next = NULL;
675
676
for (item = SDL_hapticlist; item; item = next) {
677
next = item->next;
678
/* Opened and not closed haptics are leaked, this is on purpose.
679
* Close your haptic devices after usage. */
680
681
// Free the io_service_t
682
IOObjectRelease(item->dev);
683
SDL_free(item);
684
}
685
686
numhaptics = -1;
687
SDL_hapticlist = NULL;
688
SDL_hapticlist_tail = NULL;
689
}
690
691
/*
692
* Converts an SDL trigger button to an FFEFFECT trigger button.
693
*/
694
static DWORD FFGetTriggerButton(Uint16 button)
695
{
696
DWORD dwTriggerButton;
697
698
dwTriggerButton = FFEB_NOTRIGGER;
699
700
if (button != 0) {
701
dwTriggerButton = FFJOFS_BUTTON(button - 1);
702
}
703
704
return dwTriggerButton;
705
}
706
707
/*
708
* Sets the direction.
709
*/
710
static bool SDL_SYS_SetDirection(FFEFFECT *effect, const SDL_HapticDirection *dir, int naxes)
711
{
712
LONG *rglDir;
713
714
// Handle no axes a part.
715
if (naxes == 0) {
716
effect->dwFlags |= FFEFF_SPHERICAL; // Set as default.
717
effect->rglDirection = NULL;
718
return true;
719
}
720
721
// Has axes.
722
rglDir = SDL_malloc(sizeof(LONG) * naxes);
723
if (!rglDir) {
724
return false;
725
}
726
SDL_memset(rglDir, 0, sizeof(LONG) * naxes);
727
effect->rglDirection = rglDir;
728
729
switch (dir->type) {
730
case SDL_HAPTIC_POLAR:
731
effect->dwFlags |= FFEFF_POLAR;
732
rglDir[0] = dir->dir[0];
733
return true;
734
case SDL_HAPTIC_CARTESIAN:
735
effect->dwFlags |= FFEFF_CARTESIAN;
736
rglDir[0] = dir->dir[0];
737
if (naxes > 1) {
738
rglDir[1] = dir->dir[1];
739
}
740
if (naxes > 2) {
741
rglDir[2] = dir->dir[2];
742
}
743
return true;
744
case SDL_HAPTIC_SPHERICAL:
745
effect->dwFlags |= FFEFF_SPHERICAL;
746
rglDir[0] = dir->dir[0];
747
if (naxes > 1) {
748
rglDir[1] = dir->dir[1];
749
}
750
if (naxes > 2) {
751
rglDir[2] = dir->dir[2];
752
}
753
return true;
754
case SDL_HAPTIC_STEERING_AXIS:
755
effect->dwFlags |= FFEFF_CARTESIAN;
756
rglDir[0] = 0;
757
return true;
758
759
default:
760
return SDL_SetError("Haptic: Unknown direction type.");
761
}
762
}
763
764
// Clamps and converts.
765
#define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)
766
// Just converts.
767
#define CONVERT(x) (((x)*10000) / 0x7FFF)
768
/*
769
* Creates the FFEFFECT from a SDL_HapticEffect.
770
*/
771
static bool SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, const SDL_HapticEffect *src)
772
{
773
int i;
774
FFCONSTANTFORCE *constant = NULL;
775
FFPERIODIC *periodic = NULL;
776
FFCONDITION *condition = NULL; // Actually an array of conditions - one per axis.
777
FFRAMPFORCE *ramp = NULL;
778
FFCUSTOMFORCE *custom = NULL;
779
FFENVELOPE *envelope = NULL;
780
const SDL_HapticConstant *hap_constant = NULL;
781
const SDL_HapticPeriodic *hap_periodic = NULL;
782
const SDL_HapticCondition *hap_condition = NULL;
783
const SDL_HapticRamp *hap_ramp = NULL;
784
const SDL_HapticCustom *hap_custom = NULL;
785
DWORD *axes = NULL;
786
787
// Set global stuff.
788
SDL_memset(dest, 0, sizeof(FFEFFECT));
789
dest->dwSize = sizeof(FFEFFECT); // Set the structure size.
790
dest->dwSamplePeriod = 0; // Not used by us.
791
dest->dwGain = 10000; // Gain is set globally, not locally.
792
dest->dwFlags = FFEFF_OBJECTOFFSETS; // Seems obligatory.
793
794
// Envelope.
795
envelope = SDL_calloc(1, sizeof(FFENVELOPE));
796
if (!envelope) {
797
return false;
798
}
799
dest->lpEnvelope = envelope;
800
envelope->dwSize = sizeof(FFENVELOPE); // Always should be this.
801
802
// Axes.
803
if (src->constant.direction.type == SDL_HAPTIC_STEERING_AXIS) {
804
dest->cAxes = 1;
805
} else {
806
dest->cAxes = haptic->naxes;
807
}
808
if (dest->cAxes > 0) {
809
axes = SDL_malloc(sizeof(DWORD) * dest->cAxes);
810
if (!axes) {
811
return false;
812
}
813
axes[0] = haptic->hwdata->axes[0]; // Always at least one axis.
814
if (dest->cAxes > 1) {
815
axes[1] = haptic->hwdata->axes[1];
816
}
817
if (dest->cAxes > 2) {
818
axes[2] = haptic->hwdata->axes[2];
819
}
820
dest->rgdwAxes = axes;
821
}
822
823
// The big type handling switch, even bigger then Linux's version.
824
switch (src->type) {
825
case SDL_HAPTIC_CONSTANT:
826
hap_constant = &src->constant;
827
constant = SDL_calloc(1, sizeof(FFCONSTANTFORCE));
828
if (!constant) {
829
return false;
830
}
831
832
// Specifics
833
constant->lMagnitude = CONVERT(hap_constant->level);
834
dest->cbTypeSpecificParams = sizeof(FFCONSTANTFORCE);
835
dest->lpvTypeSpecificParams = constant;
836
837
// Generics
838
dest->dwDuration = hap_constant->length * 1000; // In microseconds.
839
dest->dwTriggerButton = FFGetTriggerButton(hap_constant->button);
840
dest->dwTriggerRepeatInterval = hap_constant->interval;
841
dest->dwStartDelay = hap_constant->delay * 1000; // In microseconds.
842
843
// Direction.
844
if (!SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes)) {
845
return false;
846
}
847
848
// Envelope
849
if ((hap_constant->attack_length == 0) && (hap_constant->fade_length == 0)) {
850
SDL_free(envelope);
851
dest->lpEnvelope = NULL;
852
} else {
853
envelope->dwAttackLevel = CCONVERT(hap_constant->attack_level);
854
envelope->dwAttackTime = hap_constant->attack_length * 1000;
855
envelope->dwFadeLevel = CCONVERT(hap_constant->fade_level);
856
envelope->dwFadeTime = hap_constant->fade_length * 1000;
857
}
858
859
break;
860
861
case SDL_HAPTIC_SINE:
862
case SDL_HAPTIC_SQUARE:
863
case SDL_HAPTIC_TRIANGLE:
864
case SDL_HAPTIC_SAWTOOTHUP:
865
case SDL_HAPTIC_SAWTOOTHDOWN:
866
hap_periodic = &src->periodic;
867
periodic = SDL_calloc(1, sizeof(FFPERIODIC));
868
if (!periodic) {
869
return false;
870
}
871
872
// Specifics
873
periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude));
874
periodic->lOffset = CONVERT(hap_periodic->offset);
875
periodic->dwPhase =
876
(hap_periodic->phase + (hap_periodic->magnitude < 0 ? 18000 : 0)) % 36000;
877
periodic->dwPeriod = hap_periodic->period * 1000;
878
dest->cbTypeSpecificParams = sizeof(FFPERIODIC);
879
dest->lpvTypeSpecificParams = periodic;
880
881
// Generics
882
dest->dwDuration = hap_periodic->length * 1000; // In microseconds.
883
dest->dwTriggerButton = FFGetTriggerButton(hap_periodic->button);
884
dest->dwTriggerRepeatInterval = hap_periodic->interval;
885
dest->dwStartDelay = hap_periodic->delay * 1000; // In microseconds.
886
887
// Direction.
888
if (!SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes)) {
889
return false;
890
}
891
892
// Envelope
893
if ((hap_periodic->attack_length == 0) && (hap_periodic->fade_length == 0)) {
894
SDL_free(envelope);
895
dest->lpEnvelope = NULL;
896
} else {
897
envelope->dwAttackLevel = CCONVERT(hap_periodic->attack_level);
898
envelope->dwAttackTime = hap_periodic->attack_length * 1000;
899
envelope->dwFadeLevel = CCONVERT(hap_periodic->fade_level);
900
envelope->dwFadeTime = hap_periodic->fade_length * 1000;
901
}
902
903
break;
904
905
case SDL_HAPTIC_SPRING:
906
case SDL_HAPTIC_DAMPER:
907
case SDL_HAPTIC_INERTIA:
908
case SDL_HAPTIC_FRICTION:
909
hap_condition = &src->condition;
910
if (dest->cAxes > 0) {
911
condition = SDL_calloc(dest->cAxes, sizeof(FFCONDITION));
912
if (!condition) {
913
return false;
914
}
915
916
// Specifics
917
for (i = 0; i < dest->cAxes; i++) {
918
condition[i].lOffset = CONVERT(hap_condition->center[i]);
919
condition[i].lPositiveCoefficient =
920
CONVERT(hap_condition->right_coeff[i]);
921
condition[i].lNegativeCoefficient =
922
CONVERT(hap_condition->left_coeff[i]);
923
condition[i].dwPositiveSaturation =
924
CCONVERT(hap_condition->right_sat[i] / 2);
925
condition[i].dwNegativeSaturation =
926
CCONVERT(hap_condition->left_sat[i] / 2);
927
condition[i].lDeadBand = CCONVERT(hap_condition->deadband[i] / 2);
928
}
929
}
930
931
dest->cbTypeSpecificParams = sizeof(FFCONDITION) * dest->cAxes;
932
dest->lpvTypeSpecificParams = condition;
933
934
// Generics
935
dest->dwDuration = hap_condition->length * 1000; // In microseconds.
936
dest->dwTriggerButton = FFGetTriggerButton(hap_condition->button);
937
dest->dwTriggerRepeatInterval = hap_condition->interval;
938
dest->dwStartDelay = hap_condition->delay * 1000; // In microseconds.
939
940
// Direction.
941
if (!SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes)) {
942
return false;
943
}
944
945
// Envelope - Not actually supported by most CONDITION implementations.
946
SDL_free(dest->lpEnvelope);
947
dest->lpEnvelope = NULL;
948
949
break;
950
951
case SDL_HAPTIC_RAMP:
952
hap_ramp = &src->ramp;
953
ramp = SDL_calloc(1, sizeof(FFRAMPFORCE));
954
if (!ramp) {
955
return false;
956
}
957
958
// Specifics
959
ramp->lStart = CONVERT(hap_ramp->start);
960
ramp->lEnd = CONVERT(hap_ramp->end);
961
dest->cbTypeSpecificParams = sizeof(FFRAMPFORCE);
962
dest->lpvTypeSpecificParams = ramp;
963
964
// Generics
965
dest->dwDuration = hap_ramp->length * 1000; // In microseconds.
966
dest->dwTriggerButton = FFGetTriggerButton(hap_ramp->button);
967
dest->dwTriggerRepeatInterval = hap_ramp->interval;
968
dest->dwStartDelay = hap_ramp->delay * 1000; // In microseconds.
969
970
// Direction.
971
if (!SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes)) {
972
return false;
973
}
974
975
// Envelope
976
if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) {
977
SDL_free(envelope);
978
dest->lpEnvelope = NULL;
979
} else {
980
envelope->dwAttackLevel = CCONVERT(hap_ramp->attack_level);
981
envelope->dwAttackTime = hap_ramp->attack_length * 1000;
982
envelope->dwFadeLevel = CCONVERT(hap_ramp->fade_level);
983
envelope->dwFadeTime = hap_ramp->fade_length * 1000;
984
}
985
986
break;
987
988
case SDL_HAPTIC_CUSTOM:
989
hap_custom = &src->custom;
990
custom = SDL_calloc(1, sizeof(FFCUSTOMFORCE));
991
if (!custom) {
992
return false;
993
}
994
995
// Specifics
996
custom->cChannels = hap_custom->channels;
997
custom->dwSamplePeriod = hap_custom->period * 1000;
998
custom->cSamples = hap_custom->samples;
999
custom->rglForceData =
1000
SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels);
1001
for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) { // Copy data.
1002
custom->rglForceData[i] = CCONVERT(hap_custom->data[i]);
1003
}
1004
dest->cbTypeSpecificParams = sizeof(FFCUSTOMFORCE);
1005
dest->lpvTypeSpecificParams = custom;
1006
1007
// Generics
1008
dest->dwDuration = hap_custom->length * 1000; // In microseconds.
1009
dest->dwTriggerButton = FFGetTriggerButton(hap_custom->button);
1010
dest->dwTriggerRepeatInterval = hap_custom->interval;
1011
dest->dwStartDelay = hap_custom->delay * 1000; // In microseconds.
1012
1013
// Direction.
1014
if (!SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes)) {
1015
return false;
1016
}
1017
1018
// Envelope
1019
if ((hap_custom->attack_length == 0) && (hap_custom->fade_length == 0)) {
1020
SDL_free(envelope);
1021
dest->lpEnvelope = NULL;
1022
} else {
1023
envelope->dwAttackLevel = CCONVERT(hap_custom->attack_level);
1024
envelope->dwAttackTime = hap_custom->attack_length * 1000;
1025
envelope->dwFadeLevel = CCONVERT(hap_custom->fade_level);
1026
envelope->dwFadeTime = hap_custom->fade_length * 1000;
1027
}
1028
1029
break;
1030
1031
default:
1032
return SDL_SetError("Haptic: Unknown effect type.");
1033
}
1034
1035
return true;
1036
}
1037
1038
/*
1039
* Frees an FFEFFECT allocated by SDL_SYS_ToFFEFFECT.
1040
*/
1041
static void SDL_SYS_HapticFreeFFEFFECT(FFEFFECT *effect, int type)
1042
{
1043
FFCUSTOMFORCE *custom;
1044
1045
SDL_free(effect->lpEnvelope);
1046
effect->lpEnvelope = NULL;
1047
SDL_free(effect->rgdwAxes);
1048
effect->rgdwAxes = NULL;
1049
if (effect->lpvTypeSpecificParams) {
1050
if (type == SDL_HAPTIC_CUSTOM) { // Must free the custom data.
1051
custom = (FFCUSTOMFORCE *)effect->lpvTypeSpecificParams;
1052
SDL_free(custom->rglForceData);
1053
custom->rglForceData = NULL;
1054
}
1055
SDL_free(effect->lpvTypeSpecificParams);
1056
effect->lpvTypeSpecificParams = NULL;
1057
}
1058
SDL_free(effect->rglDirection);
1059
effect->rglDirection = NULL;
1060
}
1061
1062
/*
1063
* Gets the effect type from the generic SDL haptic effect wrapper.
1064
*/
1065
CFUUIDRef
1066
SDL_SYS_HapticEffectType(Uint16 type)
1067
{
1068
switch (type) {
1069
case SDL_HAPTIC_CONSTANT:
1070
return kFFEffectType_ConstantForce_ID;
1071
1072
case SDL_HAPTIC_RAMP:
1073
return kFFEffectType_RampForce_ID;
1074
1075
case SDL_HAPTIC_SQUARE:
1076
return kFFEffectType_Square_ID;
1077
1078
case SDL_HAPTIC_SINE:
1079
return kFFEffectType_Sine_ID;
1080
1081
case SDL_HAPTIC_TRIANGLE:
1082
return kFFEffectType_Triangle_ID;
1083
1084
case SDL_HAPTIC_SAWTOOTHUP:
1085
return kFFEffectType_SawtoothUp_ID;
1086
1087
case SDL_HAPTIC_SAWTOOTHDOWN:
1088
return kFFEffectType_SawtoothDown_ID;
1089
1090
case SDL_HAPTIC_SPRING:
1091
return kFFEffectType_Spring_ID;
1092
1093
case SDL_HAPTIC_DAMPER:
1094
return kFFEffectType_Damper_ID;
1095
1096
case SDL_HAPTIC_INERTIA:
1097
return kFFEffectType_Inertia_ID;
1098
1099
case SDL_HAPTIC_FRICTION:
1100
return kFFEffectType_Friction_ID;
1101
1102
case SDL_HAPTIC_CUSTOM:
1103
return kFFEffectType_CustomForce_ID;
1104
1105
default:
1106
SDL_SetError("Haptic: Unknown effect type.");
1107
return NULL;
1108
}
1109
}
1110
1111
/*
1112
* Creates a new haptic effect.
1113
*/
1114
bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
1115
const SDL_HapticEffect *base)
1116
{
1117
HRESULT ret;
1118
CFUUIDRef type;
1119
1120
// Alloc the effect.
1121
effect->hweffect = (struct haptic_hweffect *)
1122
SDL_calloc(1, sizeof(struct haptic_hweffect));
1123
if (!effect->hweffect) {
1124
goto err_hweffect;
1125
}
1126
1127
// Get the type.
1128
type = SDL_SYS_HapticEffectType(base->type);
1129
if (!type) {
1130
goto err_hweffect;
1131
}
1132
1133
// Get the effect.
1134
if (!SDL_SYS_ToFFEFFECT(haptic, &effect->hweffect->effect, base)) {
1135
goto err_effectdone;
1136
}
1137
1138
// Create the actual effect.
1139
ret = FFDeviceCreateEffect(haptic->hwdata->device, type,
1140
&effect->hweffect->effect,
1141
&effect->hweffect->ref);
1142
if (ret != FF_OK) {
1143
SDL_SetError("Haptic: Unable to create effect: %s.", FFStrError(ret));
1144
goto err_effectdone;
1145
}
1146
1147
return true;
1148
1149
err_effectdone:
1150
SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, base->type);
1151
err_hweffect:
1152
SDL_free(effect->hweffect);
1153
effect->hweffect = NULL;
1154
return false;
1155
}
1156
1157
/*
1158
* Updates an effect.
1159
*/
1160
bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,
1161
struct haptic_effect *effect,
1162
const SDL_HapticEffect *data)
1163
{
1164
HRESULT ret;
1165
FFEffectParameterFlag flags;
1166
FFEFFECT temp;
1167
1168
// Get the effect.
1169
SDL_memset(&temp, 0, sizeof(FFEFFECT));
1170
if (!SDL_SYS_ToFFEFFECT(haptic, &temp, data)) {
1171
goto err_update;
1172
}
1173
1174
/* Set the flags. Might be worthwhile to diff temp with loaded effect and
1175
* only change those parameters. */
1176
flags = FFEP_DIRECTION |
1177
FFEP_DURATION |
1178
FFEP_ENVELOPE |
1179
FFEP_STARTDELAY |
1180
FFEP_TRIGGERBUTTON |
1181
FFEP_TRIGGERREPEATINTERVAL | FFEP_TYPESPECIFICPARAMS;
1182
1183
// Create the actual effect.
1184
ret = FFEffectSetParameters(effect->hweffect->ref, &temp, flags);
1185
if (ret != FF_OK) {
1186
SDL_SetError("Haptic: Unable to update effect: %s.", FFStrError(ret));
1187
goto err_update;
1188
}
1189
1190
// Copy it over.
1191
SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, data->type);
1192
SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(FFEFFECT));
1193
1194
return true;
1195
1196
err_update:
1197
SDL_SYS_HapticFreeFFEFFECT(&temp, data->type);
1198
return false;
1199
}
1200
1201
/*
1202
* Runs an effect.
1203
*/
1204
bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
1205
Uint32 iterations)
1206
{
1207
HRESULT ret;
1208
Uint32 iter;
1209
1210
// Check if it's infinite.
1211
if (iterations == SDL_HAPTIC_INFINITY) {
1212
iter = FF_INFINITE;
1213
} else {
1214
iter = iterations;
1215
}
1216
1217
// Run the effect.
1218
ret = FFEffectStart(effect->hweffect->ref, iter, 0);
1219
if (ret != FF_OK) {
1220
return SDL_SetError("Haptic: Unable to run the effect: %s.",
1221
FFStrError(ret));
1222
}
1223
1224
return true;
1225
}
1226
1227
/*
1228
* Stops an effect.
1229
*/
1230
bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1231
{
1232
HRESULT ret;
1233
1234
ret = FFEffectStop(effect->hweffect->ref);
1235
if (ret != FF_OK) {
1236
return SDL_SetError("Haptic: Unable to stop the effect: %s.",
1237
FFStrError(ret));
1238
}
1239
1240
return true;
1241
}
1242
1243
/*
1244
* Frees the effect.
1245
*/
1246
void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
1247
{
1248
HRESULT ret;
1249
1250
ret = FFDeviceReleaseEffect(haptic->hwdata->device, effect->hweffect->ref);
1251
if (ret != FF_OK) {
1252
SDL_SetError("Haptic: Error removing the effect from the device: %s.",
1253
FFStrError(ret));
1254
}
1255
SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect,
1256
effect->effect.type);
1257
SDL_free(effect->hweffect);
1258
effect->hweffect = NULL;
1259
}
1260
1261
/*
1262
* Gets the status of a haptic effect.
1263
*/
1264
int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic,
1265
struct haptic_effect *effect)
1266
{
1267
HRESULT ret;
1268
FFEffectStatusFlag status;
1269
1270
ret = FFEffectGetEffectStatus(effect->hweffect->ref, &status);
1271
if (ret != FF_OK) {
1272
SDL_SetError("Haptic: Unable to get effect status: %s.", FFStrError(ret));
1273
return -1;
1274
}
1275
1276
if (status == 0) {
1277
return 0;
1278
}
1279
return 1; // Assume it's playing or emulated.
1280
}
1281
1282
/*
1283
* Sets the gain.
1284
*/
1285
bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)
1286
{
1287
HRESULT ret;
1288
Uint32 val;
1289
1290
val = gain * 100; // macOS uses 0 to 10,000
1291
ret = FFDeviceSetForceFeedbackProperty(haptic->hwdata->device,
1292
FFPROP_FFGAIN, &val);
1293
if (ret != FF_OK) {
1294
return SDL_SetError("Haptic: Error setting gain: %s.", FFStrError(ret));
1295
}
1296
1297
return true;
1298
}
1299
1300
/*
1301
* Sets the autocentering.
1302
*/
1303
bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
1304
{
1305
HRESULT ret;
1306
Uint32 val;
1307
1308
// macOS only has 0 (off) and 1 (on)
1309
if (autocenter == 0) {
1310
val = 0;
1311
} else {
1312
val = 1;
1313
}
1314
1315
ret = FFDeviceSetForceFeedbackProperty(haptic->hwdata->device,
1316
FFPROP_AUTOCENTER, &val);
1317
if (ret != FF_OK) {
1318
return SDL_SetError("Haptic: Error setting autocenter: %s.",
1319
FFStrError(ret));
1320
}
1321
1322
return true;
1323
}
1324
1325
/*
1326
* Pauses the device.
1327
*/
1328
bool SDL_SYS_HapticPause(SDL_Haptic *haptic)
1329
{
1330
HRESULT ret;
1331
1332
ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,
1333
FFSFFC_PAUSE);
1334
if (ret != FF_OK) {
1335
return SDL_SetError("Haptic: Error pausing device: %s.", FFStrError(ret));
1336
}
1337
1338
return true;
1339
}
1340
1341
/*
1342
* Unpauses the device.
1343
*/
1344
bool SDL_SYS_HapticResume(SDL_Haptic *haptic)
1345
{
1346
HRESULT ret;
1347
1348
ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,
1349
FFSFFC_CONTINUE);
1350
if (ret != FF_OK) {
1351
return SDL_SetError("Haptic: Error resuming device: %s.", FFStrError(ret));
1352
}
1353
1354
return true;
1355
}
1356
1357
/*
1358
* Stops all currently playing effects.
1359
*/
1360
bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)
1361
{
1362
HRESULT ret;
1363
1364
ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,
1365
FFSFFC_STOPALL);
1366
if (ret != FF_OK) {
1367
return SDL_SetError("Haptic: Error stopping device: %s.", FFStrError(ret));
1368
}
1369
1370
return true;
1371
}
1372
1373
#endif // SDL_HAPTIC_IOKIT
1374
1375