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