summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c')
-rw-r--r--contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c1373
1 files changed, 1373 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c b/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c
new file mode 100644
index 0000000..b48a3fe
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/haptic/darwin/SDL_syshaptic.c
@@ -0,0 +1,1373 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#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 */
43typedef 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 */
61struct haptic_hwdata
62{
63 FFDeviceObjectReference device; // Hardware device.
64 UInt8 axes[3];
65};
66
67/*
68 * Haptic system effect data.
69 */
70struct haptic_hweffect
71{
72 FFEffectObjectReference ref; // Reference.
73 struct FFEFFECT effect; // Hardware effect.
74};
75
76/*
77 * Prototypes.
78 */
79static void SDL_SYS_HapticFreeFFEFFECT(FFEFFECT *effect, int type);
80static bool HIDGetDeviceProduct(io_service_t dev, char *name);
81
82static SDL_hapticlist_item *SDL_hapticlist = NULL;
83static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;
84static int numhaptics = -1;
85
86/*
87 * Like strerror but for force feedback errors.
88 */
89static 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 */
144bool 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
183int SDL_SYS_NumHaptics(void)
184{
185 return numhaptics;
186}
187
188static 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
205static 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
216bool 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
291bool 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
329SDL_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 */
342const 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 */
355static 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 */
424static 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 */
495static 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
545open_err:
546 FFReleaseDevice(haptic->hwdata->device);
547creat_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 */
558bool 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 */
569int 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 */
588bool 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 */
604bool 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 */
621bool 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 */
650void 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 */
671void 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 */
694static 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 */
710static 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 */
771static 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 */
1041static 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 */
1065CFUUIDRef
1066SDL_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 */
1114bool 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
1149err_effectdone:
1150 SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, base->type);
1151err_hweffect:
1152 SDL_free(effect->hweffect);
1153 effect->hweffect = NULL;
1154 return false;
1155}
1156
1157/*
1158 * Updates an effect.
1159 */
1160bool 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
1196err_update:
1197 SDL_SYS_HapticFreeFFEFFECT(&temp, data->type);
1198 return false;
1199}
1200
1201/*
1202 * Runs an effect.
1203 */
1204bool 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 */
1230bool 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 */
1246void 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 */
1264int 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 */
1285bool 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 */
1303bool 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 */
1328bool 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 */
1344bool 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 */
1360bool 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