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