src/haptic/darwin/SDL_syshaptic.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 19 Sep 2019 16:50:49 -0700
changeset 13078 ae4f9911321a
parent 12503 806492103856
permissions -rw-r--r--
The PS4 is ignoring the volume values in the report, so we don't need to fill them in.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2019 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(void)
   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: */