src/haptic/darwin/SDL_syshaptic.c
author Sam Lantinga
Mon, 04 May 2020 13:17:43 -0700
changeset 13789 a359f4f93439
parent 13635 701fe6486077
child 13860 d16844f83bfd
permissions -rw-r--r--
Improvement for bug 3446 - The haptic API does not allow to select the direction axes

meyraud705

I see how the documentation is confusing. I think that the choice of the axis is an implementation detail. The documentation should state the goal of this value, so I propose this wording:

"Use this value to play an effect on the steering wheel axis. This provides
better compatibility across platforms and devices as SDL will guess the
correct axis."

Value could even be renamed 'SDL_HAPTIC_STEERING_AXIS'.

For Linux, sending an effect on the X axis with a Logitech wheel works. Others brands don't have driver for Linux as far as I know.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2020 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->driver != &SDL_DARWIN_JoystickDriver) {
   603         return SDL_FALSE;
   604     }
   605     if (joystick->hwdata->ffservice != 0) {
   606         return SDL_TRUE;
   607     }
   608     return SDL_FALSE;
   609 }
   610 
   611 
   612 /*
   613  * Checks to see if the haptic device and joystick are in reality the same.
   614  */
   615 int
   616 SDL_SYS_JoystickSameHaptic(SDL_Haptic * haptic, SDL_Joystick * joystick)
   617 {
   618     if (joystick->driver != &SDL_DARWIN_JoystickDriver) {
   619         return 0;
   620     }
   621     if (IOObjectIsEqualTo((io_object_t) ((size_t)haptic->hwdata->device),
   622                           joystick->hwdata->ffservice)) {
   623         return 1;
   624     }
   625     return 0;
   626 }
   627 
   628 
   629 /*
   630  * Opens a SDL_Haptic from a SDL_Joystick.
   631  */
   632 int
   633 SDL_SYS_HapticOpenFromJoystick(SDL_Haptic * haptic, SDL_Joystick * joystick)
   634 {
   635     int device_index = 0;
   636     SDL_hapticlist_item *item;
   637     
   638     if (joystick->driver != &SDL_DARWIN_JoystickDriver) {
   639         return -1;
   640     }
   641     for (item = SDL_hapticlist; item; item = item->next) {
   642         if (IOObjectIsEqualTo((io_object_t) item->dev,
   643                              joystick->hwdata->ffservice)) {
   644            haptic->index = device_index;
   645            break;
   646         }
   647         ++device_index;
   648     }
   649 
   650     return SDL_SYS_HapticOpenFromService(haptic, joystick->hwdata->ffservice);
   651 }
   652 
   653 
   654 /*
   655  * Closes the haptic device.
   656  */
   657 void
   658 SDL_SYS_HapticClose(SDL_Haptic * haptic)
   659 {
   660     if (haptic->hwdata) {
   661 
   662         /* Free Effects. */
   663         SDL_free(haptic->effects);
   664         haptic->effects = NULL;
   665         haptic->neffects = 0;
   666 
   667         /* Clean up */
   668         FFReleaseDevice(haptic->hwdata->device);
   669 
   670         /* Free */
   671         SDL_free(haptic->hwdata);
   672         haptic->hwdata = NULL;
   673     }
   674 }
   675 
   676 
   677 /*
   678  * Clean up after system specific haptic stuff
   679  */
   680 void
   681 SDL_SYS_HapticQuit(void)
   682 {
   683     SDL_hapticlist_item *item;
   684     SDL_hapticlist_item *next = NULL;
   685 
   686     for (item = SDL_hapticlist; item; item = next) {
   687         next = item->next;
   688         /* Opened and not closed haptics are leaked, this is on purpose.
   689          * Close your haptic devices after usage. */
   690 
   691         /* Free the io_service_t */
   692         IOObjectRelease(item->dev);
   693         SDL_free(item);
   694     }
   695 
   696     numhaptics = -1;
   697     SDL_hapticlist = NULL;
   698     SDL_hapticlist_tail = NULL;
   699 }
   700 
   701 
   702 /*
   703  * Converts an SDL trigger button to an FFEFFECT trigger button.
   704  */
   705 static DWORD
   706 FFGetTriggerButton(Uint16 button)
   707 {
   708     DWORD dwTriggerButton;
   709 
   710     dwTriggerButton = FFEB_NOTRIGGER;
   711 
   712     if (button != 0) {
   713         dwTriggerButton = FFJOFS_BUTTON(button - 1);
   714     }
   715 
   716     return dwTriggerButton;
   717 }
   718 
   719 
   720 /*
   721  * Sets the direction.
   722  */
   723 static int
   724 SDL_SYS_SetDirection(FFEFFECT * effect, SDL_HapticDirection * dir, int naxes)
   725 {
   726     LONG *rglDir;
   727 
   728     /* Handle no axes a part. */
   729     if (naxes == 0) {
   730         effect->dwFlags |= FFEFF_SPHERICAL;     /* Set as default. */
   731         effect->rglDirection = NULL;
   732         return 0;
   733     }
   734 
   735     /* Has axes. */
   736     rglDir = SDL_malloc(sizeof(LONG) * naxes);
   737     if (rglDir == NULL) {
   738         return SDL_OutOfMemory();
   739     }
   740     SDL_memset(rglDir, 0, sizeof(LONG) * naxes);
   741     effect->rglDirection = rglDir;
   742 
   743     switch (dir->type) {
   744     case SDL_HAPTIC_POLAR:
   745         effect->dwFlags |= FFEFF_POLAR;
   746         rglDir[0] = dir->dir[0];
   747         return 0;
   748     case SDL_HAPTIC_CARTESIAN:
   749         effect->dwFlags |= FFEFF_CARTESIAN;
   750         rglDir[0] = dir->dir[0];
   751         if (naxes > 1) {
   752             rglDir[1] = dir->dir[1];
   753         }
   754         if (naxes > 2) {
   755             rglDir[2] = dir->dir[2];
   756         }
   757         return 0;
   758     case SDL_HAPTIC_SPHERICAL:
   759         effect->dwFlags |= FFEFF_SPHERICAL;
   760         rglDir[0] = dir->dir[0];
   761         if (naxes > 1) {
   762             rglDir[1] = dir->dir[1];
   763         }
   764         if (naxes > 2) {
   765             rglDir[2] = dir->dir[2];
   766         }
   767         return 0;
   768     case SDL_HAPTIC_STEERING_AXIS:
   769         effect->dwFlags |= FFEFF_CARTESIAN;
   770         rglDir[0] = 0;
   771         return 0;
   772 
   773     default:
   774         return SDL_SetError("Haptic: Unknown direction type.");
   775     }
   776 }
   777 
   778 
   779 /* Clamps and converts. */
   780 #define CCONVERT(x)   (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)
   781 /* Just converts. */
   782 #define CONVERT(x)    (((x)*10000) / 0x7FFF)
   783 /*
   784  * Creates the FFEFFECT from a SDL_HapticEffect.
   785  */
   786 static int
   787 SDL_SYS_ToFFEFFECT(SDL_Haptic * haptic, FFEFFECT * dest, SDL_HapticEffect * src)
   788 {
   789     int i;
   790     FFCONSTANTFORCE *constant = NULL;
   791     FFPERIODIC *periodic = NULL;
   792     FFCONDITION *condition = NULL;     /* Actually an array of conditions - one per axis. */
   793     FFRAMPFORCE *ramp = NULL;
   794     FFCUSTOMFORCE *custom = NULL;
   795     FFENVELOPE *envelope = NULL;
   796     SDL_HapticConstant *hap_constant = NULL;
   797     SDL_HapticPeriodic *hap_periodic = NULL;
   798     SDL_HapticCondition *hap_condition = NULL;
   799     SDL_HapticRamp *hap_ramp = NULL;
   800     SDL_HapticCustom *hap_custom = NULL;
   801     DWORD *axes = NULL;
   802 
   803     /* Set global stuff. */
   804     SDL_memset(dest, 0, sizeof(FFEFFECT));
   805     dest->dwSize = sizeof(FFEFFECT);    /* Set the structure size. */
   806     dest->dwSamplePeriod = 0;   /* Not used by us. */
   807     dest->dwGain = 10000;       /* Gain is set globally, not locally. */
   808     dest->dwFlags = FFEFF_OBJECTOFFSETS;        /* Seems obligatory. */
   809 
   810     /* Envelope. */
   811     envelope = SDL_malloc(sizeof(FFENVELOPE));
   812     if (envelope == NULL) {
   813         return SDL_OutOfMemory();
   814     }
   815     SDL_memset(envelope, 0, sizeof(FFENVELOPE));
   816     dest->lpEnvelope = envelope;
   817     envelope->dwSize = sizeof(FFENVELOPE);      /* Always should be this. */
   818 
   819     /* Axes. */
   820     if (src->constant.direction.type == SDL_HAPTIC_STEERING_AXIS) {
   821         dest->cAxes = 1;
   822     } else {
   823         dest->cAxes = haptic->naxes;
   824     }
   825     if (dest->cAxes > 0) {
   826         axes = SDL_malloc(sizeof(DWORD) * dest->cAxes);
   827         if (axes == NULL) {
   828             return SDL_OutOfMemory();
   829         }
   830         axes[0] = haptic->hwdata->axes[0];      /* Always at least one axis. */
   831         if (dest->cAxes > 1) {
   832             axes[1] = haptic->hwdata->axes[1];
   833         }
   834         if (dest->cAxes > 2) {
   835             axes[2] = haptic->hwdata->axes[2];
   836         }
   837         dest->rgdwAxes = axes;
   838     }
   839 
   840 
   841     /* The big type handling switch, even bigger then Linux's version. */
   842     switch (src->type) {
   843     case SDL_HAPTIC_CONSTANT:
   844         hap_constant = &src->constant;
   845         constant = SDL_malloc(sizeof(FFCONSTANTFORCE));
   846         if (constant == NULL) {
   847             return SDL_OutOfMemory();
   848         }
   849         SDL_memset(constant, 0, sizeof(FFCONSTANTFORCE));
   850 
   851         /* Specifics */
   852         constant->lMagnitude = CONVERT(hap_constant->level);
   853         dest->cbTypeSpecificParams = sizeof(FFCONSTANTFORCE);
   854         dest->lpvTypeSpecificParams = constant;
   855 
   856         /* Generics */
   857         dest->dwDuration = hap_constant->length * 1000; /* In microseconds. */
   858         dest->dwTriggerButton = FFGetTriggerButton(hap_constant->button);
   859         dest->dwTriggerRepeatInterval = hap_constant->interval;
   860         dest->dwStartDelay = hap_constant->delay * 1000;        /* In microseconds. */
   861 
   862         /* Direction. */
   863         if (SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes)
   864             < 0) {
   865             return -1;
   866         }
   867 
   868         /* Envelope */
   869         if ((hap_constant->attack_length == 0)
   870             && (hap_constant->fade_length == 0)) {
   871             SDL_free(envelope);
   872             dest->lpEnvelope = NULL;
   873         } else {
   874             envelope->dwAttackLevel = CCONVERT(hap_constant->attack_level);
   875             envelope->dwAttackTime = hap_constant->attack_length * 1000;
   876             envelope->dwFadeLevel = CCONVERT(hap_constant->fade_level);
   877             envelope->dwFadeTime = hap_constant->fade_length * 1000;
   878         }
   879 
   880         break;
   881 
   882     case SDL_HAPTIC_SINE:
   883     /* !!! FIXME: put this back when we have more bits in 2.1 */
   884     /* case SDL_HAPTIC_SQUARE: */
   885     case SDL_HAPTIC_TRIANGLE:
   886     case SDL_HAPTIC_SAWTOOTHUP:
   887     case SDL_HAPTIC_SAWTOOTHDOWN:
   888         hap_periodic = &src->periodic;
   889         periodic = SDL_malloc(sizeof(FFPERIODIC));
   890         if (periodic == NULL) {
   891             return SDL_OutOfMemory();
   892         }
   893         SDL_memset(periodic, 0, sizeof(FFPERIODIC));
   894 
   895         /* Specifics */
   896         periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude));
   897         periodic->lOffset = CONVERT(hap_periodic->offset);
   898         periodic->dwPhase = 
   899                 (hap_periodic->phase + (hap_periodic->magnitude < 0 ? 18000 : 0)) % 36000;
   900         periodic->dwPeriod = hap_periodic->period * 1000;
   901         dest->cbTypeSpecificParams = sizeof(FFPERIODIC);
   902         dest->lpvTypeSpecificParams = periodic;
   903 
   904         /* Generics */
   905         dest->dwDuration = hap_periodic->length * 1000; /* In microseconds. */
   906         dest->dwTriggerButton = FFGetTriggerButton(hap_periodic->button);
   907         dest->dwTriggerRepeatInterval = hap_periodic->interval;
   908         dest->dwStartDelay = hap_periodic->delay * 1000;        /* In microseconds. */
   909 
   910         /* Direction. */
   911         if (SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes)
   912             < 0) {
   913             return -1;
   914         }
   915 
   916         /* Envelope */
   917         if ((hap_periodic->attack_length == 0)
   918             && (hap_periodic->fade_length == 0)) {
   919             SDL_free(envelope);
   920             dest->lpEnvelope = NULL;
   921         } else {
   922             envelope->dwAttackLevel = CCONVERT(hap_periodic->attack_level);
   923             envelope->dwAttackTime = hap_periodic->attack_length * 1000;
   924             envelope->dwFadeLevel = CCONVERT(hap_periodic->fade_level);
   925             envelope->dwFadeTime = hap_periodic->fade_length * 1000;
   926         }
   927 
   928         break;
   929 
   930     case SDL_HAPTIC_SPRING:
   931     case SDL_HAPTIC_DAMPER:
   932     case SDL_HAPTIC_INERTIA:
   933     case SDL_HAPTIC_FRICTION:
   934         hap_condition = &src->condition;
   935         if (dest->cAxes > 0) {
   936             condition = SDL_malloc(sizeof(FFCONDITION) * dest->cAxes);
   937             if (condition == NULL) {
   938                 return SDL_OutOfMemory();
   939             }
   940             SDL_memset(condition, 0, sizeof(FFCONDITION));
   941 
   942             /* Specifics */
   943             for (i = 0; i < dest->cAxes; i++) {
   944                 condition[i].lOffset = CONVERT(hap_condition->center[i]);
   945                 condition[i].lPositiveCoefficient =
   946                     CONVERT(hap_condition->right_coeff[i]);
   947                 condition[i].lNegativeCoefficient =
   948                     CONVERT(hap_condition->left_coeff[i]);
   949                 condition[i].dwPositiveSaturation =
   950                     CCONVERT(hap_condition->right_sat[i] / 2);
   951                 condition[i].dwNegativeSaturation =
   952                     CCONVERT(hap_condition->left_sat[i] / 2);
   953                 condition[i].lDeadBand = CCONVERT(hap_condition->deadband[i] / 2);
   954             }
   955         }
   956 
   957         dest->cbTypeSpecificParams = sizeof(FFCONDITION) * dest->cAxes;
   958         dest->lpvTypeSpecificParams = condition;
   959 
   960         /* Generics */
   961         dest->dwDuration = hap_condition->length * 1000;        /* In microseconds. */
   962         dest->dwTriggerButton = FFGetTriggerButton(hap_condition->button);
   963         dest->dwTriggerRepeatInterval = hap_condition->interval;
   964         dest->dwStartDelay = hap_condition->delay * 1000;       /* In microseconds. */
   965 
   966         /* Direction. */
   967         if (SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes)
   968             < 0) {
   969             return -1;
   970         }
   971 
   972         /* Envelope - Not actually supported by most CONDITION implementations. */
   973         SDL_free(dest->lpEnvelope);
   974         dest->lpEnvelope = NULL;
   975 
   976         break;
   977 
   978     case SDL_HAPTIC_RAMP:
   979         hap_ramp = &src->ramp;
   980         ramp = SDL_malloc(sizeof(FFRAMPFORCE));
   981         if (ramp == NULL) {
   982             return SDL_OutOfMemory();
   983         }
   984         SDL_memset(ramp, 0, sizeof(FFRAMPFORCE));
   985 
   986         /* Specifics */
   987         ramp->lStart = CONVERT(hap_ramp->start);
   988         ramp->lEnd = CONVERT(hap_ramp->end);
   989         dest->cbTypeSpecificParams = sizeof(FFRAMPFORCE);
   990         dest->lpvTypeSpecificParams = ramp;
   991 
   992         /* Generics */
   993         dest->dwDuration = hap_ramp->length * 1000;     /* In microseconds. */
   994         dest->dwTriggerButton = FFGetTriggerButton(hap_ramp->button);
   995         dest->dwTriggerRepeatInterval = hap_ramp->interval;
   996         dest->dwStartDelay = hap_ramp->delay * 1000;    /* In microseconds. */
   997 
   998         /* Direction. */
   999         if (SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes) < 0) {
  1000             return -1;
  1001         }
  1002 
  1003         /* Envelope */
  1004         if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) {
  1005             SDL_free(envelope);
  1006             dest->lpEnvelope = NULL;
  1007         } else {
  1008             envelope->dwAttackLevel = CCONVERT(hap_ramp->attack_level);
  1009             envelope->dwAttackTime = hap_ramp->attack_length * 1000;
  1010             envelope->dwFadeLevel = CCONVERT(hap_ramp->fade_level);
  1011             envelope->dwFadeTime = hap_ramp->fade_length * 1000;
  1012         }
  1013 
  1014         break;
  1015 
  1016     case SDL_HAPTIC_CUSTOM:
  1017         hap_custom = &src->custom;
  1018         custom = SDL_malloc(sizeof(FFCUSTOMFORCE));
  1019         if (custom == NULL) {
  1020             return SDL_OutOfMemory();
  1021         }
  1022         SDL_memset(custom, 0, sizeof(FFCUSTOMFORCE));
  1023 
  1024         /* Specifics */
  1025         custom->cChannels = hap_custom->channels;
  1026         custom->dwSamplePeriod = hap_custom->period * 1000;
  1027         custom->cSamples = hap_custom->samples;
  1028         custom->rglForceData =
  1029             SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels);
  1030         for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) {      /* Copy data. */
  1031             custom->rglForceData[i] = CCONVERT(hap_custom->data[i]);
  1032         }
  1033         dest->cbTypeSpecificParams = sizeof(FFCUSTOMFORCE);
  1034         dest->lpvTypeSpecificParams = custom;
  1035 
  1036         /* Generics */
  1037         dest->dwDuration = hap_custom->length * 1000;   /* In microseconds. */
  1038         dest->dwTriggerButton = FFGetTriggerButton(hap_custom->button);
  1039         dest->dwTriggerRepeatInterval = hap_custom->interval;
  1040         dest->dwStartDelay = hap_custom->delay * 1000;  /* In microseconds. */
  1041 
  1042         /* Direction. */
  1043         if (SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes) <
  1044             0) {
  1045             return -1;
  1046         }
  1047 
  1048         /* Envelope */
  1049         if ((hap_custom->attack_length == 0)
  1050             && (hap_custom->fade_length == 0)) {
  1051             SDL_free(envelope);
  1052             dest->lpEnvelope = NULL;
  1053         } else {
  1054             envelope->dwAttackLevel = CCONVERT(hap_custom->attack_level);
  1055             envelope->dwAttackTime = hap_custom->attack_length * 1000;
  1056             envelope->dwFadeLevel = CCONVERT(hap_custom->fade_level);
  1057             envelope->dwFadeTime = hap_custom->fade_length * 1000;
  1058         }
  1059 
  1060         break;
  1061 
  1062 
  1063     default:
  1064         return SDL_SetError("Haptic: Unknown effect type.");
  1065     }
  1066 
  1067     return 0;
  1068 }
  1069 
  1070 
  1071 /*
  1072  * Frees an FFEFFECT allocated by SDL_SYS_ToFFEFFECT.
  1073  */
  1074 static void
  1075 SDL_SYS_HapticFreeFFEFFECT(FFEFFECT * effect, int type)
  1076 {
  1077     FFCUSTOMFORCE *custom;
  1078 
  1079     SDL_free(effect->lpEnvelope);
  1080     effect->lpEnvelope = NULL;
  1081     SDL_free(effect->rgdwAxes);
  1082     effect->rgdwAxes = NULL;
  1083     if (effect->lpvTypeSpecificParams != NULL) {
  1084         if (type == SDL_HAPTIC_CUSTOM) {        /* Must free the custom data. */
  1085             custom = (FFCUSTOMFORCE *) effect->lpvTypeSpecificParams;
  1086             SDL_free(custom->rglForceData);
  1087             custom->rglForceData = NULL;
  1088         }
  1089         SDL_free(effect->lpvTypeSpecificParams);
  1090         effect->lpvTypeSpecificParams = NULL;
  1091     }
  1092     SDL_free(effect->rglDirection);
  1093     effect->rglDirection = NULL;
  1094 }
  1095 
  1096 
  1097 /*
  1098  * Gets the effect type from the generic SDL haptic effect wrapper.
  1099  */
  1100 CFUUIDRef
  1101 SDL_SYS_HapticEffectType(Uint16 type)
  1102 {
  1103     switch (type) {
  1104     case SDL_HAPTIC_CONSTANT:
  1105         return kFFEffectType_ConstantForce_ID;
  1106 
  1107     case SDL_HAPTIC_RAMP:
  1108         return kFFEffectType_RampForce_ID;
  1109 
  1110     /* !!! FIXME: put this back when we have more bits in 2.1 */
  1111     /* case SDL_HAPTIC_SQUARE:
  1112         return kFFEffectType_Square_ID; */
  1113 
  1114     case SDL_HAPTIC_SINE:
  1115         return kFFEffectType_Sine_ID;
  1116 
  1117     case SDL_HAPTIC_TRIANGLE:
  1118         return kFFEffectType_Triangle_ID;
  1119 
  1120     case SDL_HAPTIC_SAWTOOTHUP:
  1121         return kFFEffectType_SawtoothUp_ID;
  1122 
  1123     case SDL_HAPTIC_SAWTOOTHDOWN:
  1124         return kFFEffectType_SawtoothDown_ID;
  1125 
  1126     case SDL_HAPTIC_SPRING:
  1127         return kFFEffectType_Spring_ID;
  1128 
  1129     case SDL_HAPTIC_DAMPER:
  1130         return kFFEffectType_Damper_ID;
  1131 
  1132     case SDL_HAPTIC_INERTIA:
  1133         return kFFEffectType_Inertia_ID;
  1134 
  1135     case SDL_HAPTIC_FRICTION:
  1136         return kFFEffectType_Friction_ID;
  1137 
  1138     case SDL_HAPTIC_CUSTOM:
  1139         return kFFEffectType_CustomForce_ID;
  1140 
  1141     default:
  1142         SDL_SetError("Haptic: Unknown effect type.");
  1143         return NULL;
  1144     }
  1145 }
  1146 
  1147 
  1148 /*
  1149  * Creates a new haptic effect.
  1150  */
  1151 int
  1152 SDL_SYS_HapticNewEffect(SDL_Haptic * haptic, struct haptic_effect *effect,
  1153                         SDL_HapticEffect * base)
  1154 {
  1155     HRESULT ret;
  1156     CFUUIDRef type;
  1157 
  1158     /* Alloc the effect. */
  1159     effect->hweffect = (struct haptic_hweffect *)
  1160         SDL_malloc(sizeof(struct haptic_hweffect));
  1161     if (effect->hweffect == NULL) {
  1162         SDL_OutOfMemory();
  1163         goto err_hweffect;
  1164     }
  1165 
  1166     /* Get the type. */
  1167     type = SDL_SYS_HapticEffectType(base->type);
  1168     if (type == NULL) {
  1169         goto err_hweffect;
  1170     }
  1171 
  1172     /* Get the effect. */
  1173     if (SDL_SYS_ToFFEFFECT(haptic, &effect->hweffect->effect, base) < 0) {
  1174         goto err_effectdone;
  1175     }
  1176 
  1177     /* Create the actual effect. */
  1178     ret = FFDeviceCreateEffect(haptic->hwdata->device, type,
  1179                                &effect->hweffect->effect,
  1180                                &effect->hweffect->ref);
  1181     if (ret != FF_OK) {
  1182         SDL_SetError("Haptic: Unable to create effect: %s.", FFStrError(ret));
  1183         goto err_effectdone;
  1184     }
  1185 
  1186     return 0;
  1187 
  1188   err_effectdone:
  1189     SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, base->type);
  1190   err_hweffect:
  1191     SDL_free(effect->hweffect);
  1192     effect->hweffect = NULL;
  1193     return -1;
  1194 }
  1195 
  1196 
  1197 /*
  1198  * Updates an effect.
  1199  */
  1200 int
  1201 SDL_SYS_HapticUpdateEffect(SDL_Haptic * haptic,
  1202                            struct haptic_effect *effect,
  1203                            SDL_HapticEffect * data)
  1204 {
  1205     HRESULT ret;
  1206     FFEffectParameterFlag flags;
  1207     FFEFFECT temp;
  1208 
  1209     /* Get the effect. */
  1210     SDL_memset(&temp, 0, sizeof(FFEFFECT));
  1211     if (SDL_SYS_ToFFEFFECT(haptic, &temp, data) < 0) {
  1212         goto err_update;
  1213     }
  1214 
  1215     /* Set the flags.  Might be worthwhile to diff temp with loaded effect and
  1216      *  only change those parameters. */
  1217     flags = FFEP_DIRECTION |
  1218         FFEP_DURATION |
  1219         FFEP_ENVELOPE |
  1220         FFEP_STARTDELAY |
  1221         FFEP_TRIGGERBUTTON |
  1222         FFEP_TRIGGERREPEATINTERVAL | FFEP_TYPESPECIFICPARAMS;
  1223 
  1224     /* Create the actual effect. */
  1225     ret = FFEffectSetParameters(effect->hweffect->ref, &temp, flags);
  1226     if (ret != FF_OK) {
  1227         SDL_SetError("Haptic: Unable to update effect: %s.", FFStrError(ret));
  1228         goto err_update;
  1229     }
  1230 
  1231     /* Copy it over. */
  1232     SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, data->type);
  1233     SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(FFEFFECT));
  1234 
  1235     return 0;
  1236 
  1237   err_update:
  1238     SDL_SYS_HapticFreeFFEFFECT(&temp, data->type);
  1239     return -1;
  1240 }
  1241 
  1242 
  1243 /*
  1244  * Runs an effect.
  1245  */
  1246 int
  1247 SDL_SYS_HapticRunEffect(SDL_Haptic * haptic, struct haptic_effect *effect,
  1248                         Uint32 iterations)
  1249 {
  1250     HRESULT ret;
  1251     Uint32 iter;
  1252 
  1253     /* Check if it's infinite. */
  1254     if (iterations == SDL_HAPTIC_INFINITY) {
  1255         iter = FF_INFINITE;
  1256     } else
  1257         iter = iterations;
  1258 
  1259     /* Run the effect. */
  1260     ret = FFEffectStart(effect->hweffect->ref, iter, 0);
  1261     if (ret != FF_OK) {
  1262         return SDL_SetError("Haptic: Unable to run the effect: %s.",
  1263                             FFStrError(ret));
  1264     }
  1265 
  1266     return 0;
  1267 }
  1268 
  1269 
  1270 /*
  1271  * Stops an effect.
  1272  */
  1273 int
  1274 SDL_SYS_HapticStopEffect(SDL_Haptic * haptic, struct haptic_effect *effect)
  1275 {
  1276     HRESULT ret;
  1277 
  1278     ret = FFEffectStop(effect->hweffect->ref);
  1279     if (ret != FF_OK) {
  1280         return SDL_SetError("Haptic: Unable to stop the effect: %s.",
  1281                             FFStrError(ret));
  1282     }
  1283 
  1284     return 0;
  1285 }
  1286 
  1287 
  1288 /*
  1289  * Frees the effect.
  1290  */
  1291 void
  1292 SDL_SYS_HapticDestroyEffect(SDL_Haptic * haptic, struct haptic_effect *effect)
  1293 {
  1294     HRESULT ret;
  1295 
  1296     ret = FFDeviceReleaseEffect(haptic->hwdata->device, effect->hweffect->ref);
  1297     if (ret != FF_OK) {
  1298         SDL_SetError("Haptic: Error removing the effect from the device: %s.",
  1299                      FFStrError(ret));
  1300     }
  1301     SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect,
  1302                                effect->effect.type);
  1303     SDL_free(effect->hweffect);
  1304     effect->hweffect = NULL;
  1305 }
  1306 
  1307 
  1308 /*
  1309  * Gets the status of a haptic effect.
  1310  */
  1311 int
  1312 SDL_SYS_HapticGetEffectStatus(SDL_Haptic * haptic,
  1313                               struct haptic_effect *effect)
  1314 {
  1315     HRESULT ret;
  1316     FFEffectStatusFlag status;
  1317 
  1318     ret = FFEffectGetEffectStatus(effect->hweffect->ref, &status);
  1319     if (ret != FF_OK) {
  1320         SDL_SetError("Haptic: Unable to get effect status: %s.",
  1321                      FFStrError(ret));
  1322         return -1;
  1323     }
  1324 
  1325     if (status == 0) {
  1326         return SDL_FALSE;
  1327     }
  1328     return SDL_TRUE;            /* Assume it's playing or emulated. */
  1329 }
  1330 
  1331 
  1332 /*
  1333  * Sets the gain.
  1334  */
  1335 int
  1336 SDL_SYS_HapticSetGain(SDL_Haptic * haptic, int gain)
  1337 {
  1338     HRESULT ret;
  1339     Uint32 val;
  1340 
  1341     val = gain * 100;           /* Mac OS X uses 0 to 10,000 */
  1342     ret = FFDeviceSetForceFeedbackProperty(haptic->hwdata->device,
  1343                                            FFPROP_FFGAIN, &val);
  1344     if (ret != FF_OK) {
  1345         return SDL_SetError("Haptic: Error setting gain: %s.", FFStrError(ret));
  1346     }
  1347 
  1348     return 0;
  1349 }
  1350 
  1351 
  1352 /*
  1353  * Sets the autocentering.
  1354  */
  1355 int
  1356 SDL_SYS_HapticSetAutocenter(SDL_Haptic * haptic, int autocenter)
  1357 {
  1358     HRESULT ret;
  1359     Uint32 val;
  1360 
  1361     /* Mac OS X only has 0 (off) and 1 (on) */
  1362     if (autocenter == 0) {
  1363         val = 0;
  1364     } else {
  1365         val = 1;
  1366     }
  1367 
  1368     ret = FFDeviceSetForceFeedbackProperty(haptic->hwdata->device,
  1369                                            FFPROP_AUTOCENTER, &val);
  1370     if (ret != FF_OK) {
  1371         return SDL_SetError("Haptic: Error setting autocenter: %s.",
  1372                             FFStrError(ret));
  1373     }
  1374 
  1375     return 0;
  1376 }
  1377 
  1378 
  1379 /*
  1380  * Pauses the device.
  1381  */
  1382 int
  1383 SDL_SYS_HapticPause(SDL_Haptic * haptic)
  1384 {
  1385     HRESULT ret;
  1386 
  1387     ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,
  1388                                            FFSFFC_PAUSE);
  1389     if (ret != FF_OK) {
  1390         return SDL_SetError("Haptic: Error pausing device: %s.", FFStrError(ret));
  1391     }
  1392 
  1393     return 0;
  1394 }
  1395 
  1396 
  1397 /*
  1398  * Unpauses the device.
  1399  */
  1400 int
  1401 SDL_SYS_HapticUnpause(SDL_Haptic * haptic)
  1402 {
  1403     HRESULT ret;
  1404 
  1405     ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,
  1406                                            FFSFFC_CONTINUE);
  1407     if (ret != FF_OK) {
  1408         return SDL_SetError("Haptic: Error pausing device: %s.", FFStrError(ret));
  1409     }
  1410 
  1411     return 0;
  1412 }
  1413 
  1414 
  1415 /*
  1416  * Stops all currently playing effects.
  1417  */
  1418 int
  1419 SDL_SYS_HapticStopAll(SDL_Haptic * haptic)
  1420 {
  1421     HRESULT ret;
  1422 
  1423     ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,
  1424                                            FFSFFC_STOPALL);
  1425     if (ret != FF_OK) {
  1426         return SDL_SetError("Haptic: Error stopping device: %s.", FFStrError(ret));
  1427     }
  1428 
  1429     return 0;
  1430 }
  1431 
  1432 #endif /* SDL_HAPTIC_IOKIT */
  1433 
  1434 /* vi: set ts=4 sw=4 expandtab: */