change SDL 1.2 to SDL 2.0
[platform/upstream/SDL.git] / src / haptic / darwin / SDL_syshaptic.c
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 *
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
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
192 SDL_SYS_NumHaptics()
193 {
194     return numhaptics;
195 }
196
197 static SDL_hapticlist_item *
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
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
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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: */