src/haptic/darwin/SDL_syshaptic.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 25 Aug 2008 09:55:03 +0000
changeset 2713 0906692aa6a4
child 2729 6c8bc4d87282
permissions -rw-r--r--
Final merge of Google Summer of Code 2008 work...

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