src/joystick/darwin/SDL_sysjoystick.c
author Sam Lantinga <slouken@libsdl.org>
Wed, 15 Aug 2018 19:53:34 -0700
changeset 12115 0d9277d27e2d
parent 12090 c3209fca27b2
child 12359 691c32a30fb9
permissions -rw-r--r--
Fixed input from the Steam Virtual Gamepad on Mac OS X
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2018 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_JOYSTICK_IOKIT
    24 
    25 #include "SDL_events.h"
    26 #include "SDL_joystick.h"
    27 #include "../SDL_sysjoystick.h"
    28 #include "../SDL_joystick_c.h"
    29 #include "SDL_sysjoystick_c.h"
    30 #include "../hidapi/SDL_hidapijoystick_c.h"
    31 #include "../../haptic/darwin/SDL_syshaptic_c.h"    /* For haptic hot plugging */
    32 
    33 
    34 #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
    35 
    36 #define CONVERT_MAGNITUDE(x)    (((x)*10000) / 0x7FFF)
    37 
    38 /* The base object of the HID Manager API */
    39 static IOHIDManagerRef hidman = NULL;
    40 
    41 /* Linked list of all available devices */
    42 static recDevice *gpDeviceList = NULL;
    43 
    44 void FreeRumbleEffectData(FFEFFECT *effect)
    45 {
    46     if (!effect) {
    47         return;
    48     }
    49     SDL_free(effect->rgdwAxes);
    50     SDL_free(effect->rglDirection);
    51     SDL_free(effect->lpvTypeSpecificParams);
    52     SDL_free(effect);
    53 }
    54 
    55 FFEFFECT *CreateRumbleEffectData(Sint16 magnitude, Uint32 duration_ms)
    56 {
    57     FFEFFECT *effect;
    58     FFPERIODIC *periodic;
    59 
    60     /* Create the effect */
    61     effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect));
    62     if (!effect) {
    63         return NULL;
    64     }
    65     effect->dwSize = sizeof(*effect);
    66     effect->dwGain = 10000;
    67     effect->dwFlags = FFEFF_OBJECTOFFSETS;
    68     effect->dwDuration = duration_ms * 1000; /* In microseconds. */
    69     effect->dwTriggerButton = FFEB_NOTRIGGER;
    70 
    71     effect->cAxes = 2;
    72     effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
    73     if (!effect->rgdwAxes) {
    74         FreeRumbleEffectData(effect);
    75         return NULL;
    76     }
    77 
    78     effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
    79     if (!effect->rglDirection) {
    80         FreeRumbleEffectData(effect);
    81         return NULL;
    82     }
    83     effect->dwFlags |= FFEFF_CARTESIAN;
    84 
    85     periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic));
    86     if (!periodic) {
    87         FreeRumbleEffectData(effect);
    88         return NULL;
    89     }
    90     periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
    91     periodic->dwPeriod = 1000000;
    92 
    93     effect->cbTypeSpecificParams = sizeof(*periodic);
    94     effect->lpvTypeSpecificParams = periodic;
    95 
    96     return effect;
    97 }
    98 
    99 static recDevice *GetDeviceForIndex(int device_index)
   100 {
   101     recDevice *device = gpDeviceList;
   102     while (device) {
   103         if (!device->removed) {
   104             if (device_index == 0)
   105                 break;
   106 
   107             --device_index;
   108         }
   109         device = device->pNext;
   110     }
   111     return device;
   112 }
   113 
   114 static void
   115 FreeElementList(recElement *pElement)
   116 {
   117     while (pElement) {
   118         recElement *pElementNext = pElement->pNext;
   119         SDL_free(pElement);
   120         pElement = pElementNext;
   121     }
   122 }
   123 
   124 static recDevice *
   125 FreeDevice(recDevice *removeDevice)
   126 {
   127     recDevice *pDeviceNext = NULL;
   128     if (removeDevice) {
   129         if (removeDevice->deviceRef) {
   130             IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   131             removeDevice->deviceRef = NULL;
   132         }
   133 
   134         /* save next device prior to disposing of this device */
   135         pDeviceNext = removeDevice->pNext;
   136 
   137         if ( gpDeviceList == removeDevice ) {
   138             gpDeviceList = pDeviceNext;
   139         } else {
   140             recDevice *device = gpDeviceList;
   141             while (device->pNext != removeDevice) {
   142                 device = device->pNext;
   143             }
   144             device->pNext = pDeviceNext;
   145         }
   146         removeDevice->pNext = NULL;
   147 
   148         /* free element lists */
   149         FreeElementList(removeDevice->firstAxis);
   150         FreeElementList(removeDevice->firstButton);
   151         FreeElementList(removeDevice->firstHat);
   152 
   153         SDL_free(removeDevice);
   154     }
   155     return pDeviceNext;
   156 }
   157 
   158 static SDL_bool
   159 GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue)
   160 {
   161     SInt32 value = 0;
   162     int returnValue = SDL_FALSE;
   163 
   164     if (pDevice && pElement) {
   165         IOHIDValueRef valueRef;
   166         if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
   167             value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
   168 
   169             /* record min and max for auto calibration */
   170             if (value < pElement->minReport) {
   171                 pElement->minReport = value;
   172             }
   173             if (value > pElement->maxReport) {
   174                 pElement->maxReport = value;
   175             }
   176             *pValue = value;
   177 
   178             returnValue = SDL_TRUE;
   179         }
   180     }
   181     return returnValue;
   182 }
   183 
   184 static SDL_bool
   185 GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max, SInt32 *pValue)
   186 {
   187     const float deviceScale = max - min;
   188     const float readScale = pElement->maxReport - pElement->minReport;
   189     int returnValue = SDL_FALSE;
   190     if (GetHIDElementState(pDevice, pElement, pValue))
   191     {
   192         if (readScale == 0) {
   193             returnValue = SDL_TRUE;           /* no scaling at all */
   194         }
   195         else
   196         {
   197             *pValue = ((*pValue - pElement->minReport) * deviceScale / readScale) + min;
   198             returnValue = SDL_TRUE;
   199         }
   200     } 
   201     return returnValue;
   202 }
   203 
   204 static void
   205 JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
   206 {
   207     recDevice *device = (recDevice *) ctx;
   208     device->removed = SDL_TRUE;
   209     device->deviceRef = NULL; // deviceRef was invalidated due to the remove
   210     if (device->ffeffect_ref) {
   211         FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref);
   212         device->ffeffect_ref = NULL;
   213     }
   214     if (device->ffeffect) {
   215         FreeRumbleEffectData(device->ffeffect);
   216         device->ffeffect = NULL;
   217     }
   218     if (device->ffdevice) {
   219         FFReleaseDevice(device->ffdevice);
   220         device->ffdevice = NULL;
   221         device->ff_initialized = SDL_FALSE;
   222     }
   223 #if SDL_HAPTIC_IOKIT
   224     MacHaptic_MaybeRemoveDevice(device->ffservice);
   225 #endif
   226 
   227     SDL_PrivateJoystickRemoved(device->instance_id);
   228 }
   229 
   230 
   231 static void AddHIDElement(const void *value, void *parameter);
   232 
   233 /* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
   234 static void
   235 AddHIDElements(CFArrayRef array, recDevice *pDevice)
   236 {
   237     const CFRange range = { 0, CFArrayGetCount(array) };
   238     CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
   239 }
   240 
   241 static SDL_bool
   242 ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
   243     while (listitem) {
   244         if (listitem->cookie == cookie) {
   245             return SDL_TRUE;
   246         }
   247         listitem = listitem->pNext;
   248     }
   249     return SDL_FALSE;
   250 }
   251 
   252 /* See if we care about this HID element, and if so, note it in our recDevice. */
   253 static void
   254 AddHIDElement(const void *value, void *parameter)
   255 {
   256     recDevice *pDevice = (recDevice *) parameter;
   257     IOHIDElementRef refElement = (IOHIDElementRef) value;
   258     const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
   259 
   260     if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
   261         const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
   262         const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
   263         const uint32_t usage = IOHIDElementGetUsage(refElement);
   264         recElement *element = NULL;
   265         recElement **headElement = NULL;
   266 
   267         /* look at types of interest */
   268         switch (IOHIDElementGetType(refElement)) {
   269             case kIOHIDElementTypeInput_Misc:
   270             case kIOHIDElementTypeInput_Button:
   271             case kIOHIDElementTypeInput_Axis: {
   272                 switch (usagePage) {    /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
   273                     case kHIDPage_GenericDesktop:
   274                         switch (usage) {
   275                             case kHIDUsage_GD_X:
   276                             case kHIDUsage_GD_Y:
   277                             case kHIDUsage_GD_Z:
   278                             case kHIDUsage_GD_Rx:
   279                             case kHIDUsage_GD_Ry:
   280                             case kHIDUsage_GD_Rz:
   281                             case kHIDUsage_GD_Slider:
   282                             case kHIDUsage_GD_Dial:
   283                             case kHIDUsage_GD_Wheel:
   284                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
   285                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   286                                     if (element) {
   287                                         pDevice->axes++;
   288                                         headElement = &(pDevice->firstAxis);
   289                                     }
   290                                 }
   291                                 break;
   292 
   293                             case kHIDUsage_GD_Hatswitch:
   294                                 if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
   295                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   296                                     if (element) {
   297                                         pDevice->hats++;
   298                                         headElement = &(pDevice->firstHat);
   299                                     }
   300                                 }
   301                                 break;
   302                             case kHIDUsage_GD_DPadUp:
   303                             case kHIDUsage_GD_DPadDown:
   304                             case kHIDUsage_GD_DPadRight:
   305                             case kHIDUsage_GD_DPadLeft:
   306                             case kHIDUsage_GD_Start:
   307                             case kHIDUsage_GD_Select:
   308                             case kHIDUsage_GD_SystemMainMenu:
   309                                 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
   310                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   311                                     if (element) {
   312                                         pDevice->buttons++;
   313                                         headElement = &(pDevice->firstButton);
   314                                     }
   315                                 }
   316                                 break;
   317                         }
   318                         break;
   319 
   320                     case kHIDPage_Simulation:
   321                         switch (usage) {
   322                             case kHIDUsage_Sim_Rudder:
   323                             case kHIDUsage_Sim_Throttle:
   324                             case kHIDUsage_Sim_Accelerator:
   325                             case kHIDUsage_Sim_Brake:
   326                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
   327                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   328                                     if (element) {
   329                                         pDevice->axes++;
   330                                         headElement = &(pDevice->firstAxis);
   331                                     }
   332                                 }
   333                                 break;
   334 
   335                             default:
   336                                 break;
   337                         }
   338                         break;
   339 
   340                     case kHIDPage_Button:
   341                     case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
   342                         if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
   343                             element = (recElement *) SDL_calloc(1, sizeof (recElement));
   344                             if (element) {
   345                                 pDevice->buttons++;
   346                                 headElement = &(pDevice->firstButton);
   347                             }
   348                         }
   349                         break;
   350 
   351                     default:
   352                         break;
   353                 }
   354             }
   355             break;
   356 
   357             case kIOHIDElementTypeCollection: {
   358                 CFArrayRef array = IOHIDElementGetChildren(refElement);
   359                 if (array) {
   360                     AddHIDElements(array, pDevice);
   361                 }
   362             }
   363             break;
   364 
   365             default:
   366                 break;
   367         }
   368 
   369         if (element && headElement) {       /* add to list */
   370             recElement *elementPrevious = NULL;
   371             recElement *elementCurrent = *headElement;
   372             while (elementCurrent && usage >= elementCurrent->usage) {
   373                 elementPrevious = elementCurrent;
   374                 elementCurrent = elementCurrent->pNext;
   375             }
   376             if (elementPrevious) {
   377                 elementPrevious->pNext = element;
   378             } else {
   379                 *headElement = element;
   380             }
   381 
   382             element->elementRef = refElement;
   383             element->usagePage = usagePage;
   384             element->usage = usage;
   385             element->pNext = elementCurrent;
   386 
   387             element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
   388             element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
   389             element->cookie = IOHIDElementGetCookie(refElement);
   390 
   391             pDevice->elements++;
   392         }
   393     }
   394 }
   395 
   396 static SDL_bool
   397 GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
   398 {
   399     Sint32 vendor = 0;
   400     Sint32 product = 0;
   401     Sint32 version = 0;
   402     CFTypeRef refCF = NULL;
   403     CFArrayRef array = NULL;
   404     Uint16 *guid16 = (Uint16 *)pDevice->guid.data;
   405 
   406     /* get usage page and usage */
   407     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
   408     if (refCF) {
   409         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
   410     }
   411     if (pDevice->usagePage != kHIDPage_GenericDesktop) {
   412         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
   413     }
   414 
   415     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
   416     if (refCF) {
   417         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
   418     }
   419 
   420     if ((pDevice->usage != kHIDUsage_GD_Joystick &&
   421          pDevice->usage != kHIDUsage_GD_GamePad &&
   422          pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
   423         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
   424     }
   425 
   426     pDevice->deviceRef = hidDevice;
   427 
   428     /* get device name */
   429     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
   430     if (!refCF) {
   431         /* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
   432         refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
   433     }
   434     if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
   435         SDL_strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
   436     }
   437 
   438     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
   439     if (refCF) {
   440         CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
   441     }
   442 
   443     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
   444     if (refCF) {
   445         CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
   446     }
   447 
   448     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
   449     if (refCF) {
   450         CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
   451     }
   452 
   453 #ifdef SDL_JOYSTICK_HIDAPI
   454     if (HIDAPI_IsDevicePresent(vendor, product, version)) {
   455         /* The HIDAPI driver is taking care of this device */
   456         return 0;
   457     }
   458 #endif
   459 
   460     SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
   461 
   462     if (vendor && product) {
   463         *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB);
   464         *guid16++ = 0;
   465         *guid16++ = SDL_SwapLE16((Uint16)vendor);
   466         *guid16++ = 0;
   467         *guid16++ = SDL_SwapLE16((Uint16)product);
   468         *guid16++ = 0;
   469         *guid16++ = SDL_SwapLE16((Uint16)version);
   470         *guid16++ = 0;
   471     } else {
   472         *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
   473         *guid16++ = 0;
   474         SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
   475     }
   476 
   477     array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
   478     if (array) {
   479         AddHIDElements(array, pDevice);
   480         CFRelease(array);
   481     }
   482 
   483     return SDL_TRUE;
   484 }
   485 
   486 static SDL_bool
   487 JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
   488 {
   489     recDevice *i;
   490     for (i = gpDeviceList; i != NULL; i = i->pNext) {
   491         if (i->deviceRef == ioHIDDeviceObject) {
   492             return SDL_TRUE;
   493         }
   494     }
   495     return SDL_FALSE;
   496 }
   497 
   498 
   499 static void
   500 JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
   501 {
   502     recDevice *device;
   503     int device_index = 0;
   504     io_service_t ioservice;
   505 
   506     if (res != kIOReturnSuccess) {
   507         return;
   508     }
   509 
   510     if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
   511         return;  /* IOKit sent us a duplicate. */
   512     }
   513 
   514     device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
   515     if (!device) {
   516         SDL_OutOfMemory();
   517         return;
   518     }
   519 
   520     if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
   521         SDL_free(device);
   522         return;   /* not a device we care about, probably. */
   523     }
   524 
   525     if (SDL_ShouldIgnoreJoystick(device->product, device->guid)) {
   526         SDL_free(device);
   527         return;
   528     }
   529 
   530     /* Get notified when this device is disconnected. */
   531     IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
   532     IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   533 
   534     /* Allocate an instance ID for this device */
   535     device->instance_id = SDL_GetNextJoystickInstanceID();
   536 
   537     /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
   538     ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
   539     if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
   540         device->ffservice = ioservice;
   541 #if SDL_HAPTIC_IOKIT
   542         MacHaptic_MaybeAddDevice(ioservice);
   543 #endif
   544     }
   545 
   546     /* Add device to the end of the list */
   547     if ( !gpDeviceList ) {
   548         gpDeviceList = device;
   549     } else {
   550         recDevice *curdevice;
   551 
   552         curdevice = gpDeviceList;
   553         while ( curdevice->pNext ) {
   554             ++device_index;
   555             curdevice = curdevice->pNext;
   556         }
   557         curdevice->pNext = device;
   558         ++device_index;  /* bump by one since we counted by pNext. */
   559     }
   560 
   561     SDL_PrivateJoystickAdded(device->instance_id);
   562 }
   563 
   564 static SDL_bool
   565 ConfigHIDManager(CFArrayRef matchingArray)
   566 {
   567     CFRunLoopRef runloop = CFRunLoopGetCurrent();
   568 
   569     if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
   570         return SDL_FALSE;
   571     }
   572 
   573     IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
   574     IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
   575     IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
   576 
   577     while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
   578         /* no-op. Callback fires once per existing device. */
   579     }
   580 
   581     /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
   582 
   583     return SDL_TRUE;  /* good to go. */
   584 }
   585 
   586 
   587 static CFDictionaryRef
   588 CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
   589 {
   590     CFDictionaryRef retval = NULL;
   591     CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
   592     CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
   593     const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
   594     const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
   595 
   596     if (pageNumRef && usageNumRef) {
   597         retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
   598     }
   599 
   600     if (pageNumRef) {
   601         CFRelease(pageNumRef);
   602     }
   603     if (usageNumRef) {
   604         CFRelease(usageNumRef);
   605     }
   606 
   607     if (!retval) {
   608         *okay = 0;
   609     }
   610 
   611     return retval;
   612 }
   613 
   614 static SDL_bool
   615 CreateHIDManager(void)
   616 {
   617     SDL_bool retval = SDL_FALSE;
   618     int okay = 1;
   619     const void *vals[] = {
   620         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
   621         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
   622         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
   623     };
   624     const size_t numElements = SDL_arraysize(vals);
   625     CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
   626     size_t i;
   627 
   628     for (i = 0; i < numElements; i++) {
   629         if (vals[i]) {
   630             CFRelease((CFTypeRef) vals[i]);
   631         }
   632     }
   633 
   634     if (array) {
   635         hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
   636         if (hidman != NULL) {
   637             retval = ConfigHIDManager(array);
   638         }
   639         CFRelease(array);
   640     }
   641 
   642     return retval;
   643 }
   644 
   645 
   646 static int
   647 DARWIN_JoystickInit(void)
   648 {
   649     if (gpDeviceList) {
   650         return SDL_SetError("Joystick: Device list already inited.");
   651     }
   652 
   653     if (!CreateHIDManager()) {
   654         return SDL_SetError("Joystick: Couldn't initialize HID Manager");
   655     }
   656 
   657     return 0;
   658 }
   659 
   660 static int
   661 DARWIN_JoystickGetCount(void)
   662 {
   663     recDevice *device = gpDeviceList;
   664     int nJoySticks = 0;
   665 
   666     while (device) {
   667         if (!device->removed) {
   668             nJoySticks++;
   669         }
   670         device = device->pNext;
   671     }
   672 
   673     return nJoySticks;
   674 }
   675 
   676 static void
   677 DARWIN_JoystickDetect(void)
   678 {
   679     recDevice *device = gpDeviceList;
   680     while (device) {
   681         if (device->removed) {
   682             device = FreeDevice(device);
   683         } else {
   684             device = device->pNext;
   685         }
   686     }
   687 
   688     /* run this after the checks above so we don't set device->removed and delete the device before
   689        DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
   690     while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
   691         /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
   692     }
   693 }
   694 
   695 /* Function to get the device-dependent name of a joystick */
   696 const char *
   697 DARWIN_JoystickGetDeviceName(int device_index)
   698 {
   699     recDevice *device = GetDeviceForIndex(device_index);
   700     return device ? device->product : "UNKNOWN";
   701 }
   702 
   703 static SDL_JoystickGUID
   704 DARWIN_JoystickGetDeviceGUID( int device_index )
   705 {
   706     recDevice *device = GetDeviceForIndex(device_index);
   707     SDL_JoystickGUID guid;
   708     if (device) {
   709         guid = device->guid;
   710     } else {
   711         SDL_zero(guid);
   712     }
   713     return guid;
   714 }
   715 
   716 static SDL_JoystickID
   717 DARWIN_JoystickGetDeviceInstanceID(int device_index)
   718 {
   719     recDevice *device = GetDeviceForIndex(device_index);
   720     return device ? device->instance_id : 0;
   721 }
   722 
   723 static int
   724 DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index)
   725 {
   726     recDevice *device = GetDeviceForIndex(device_index);
   727 
   728     joystick->instance_id = device->instance_id;
   729     joystick->hwdata = device;
   730     joystick->name = device->product;
   731 
   732     joystick->naxes = device->axes;
   733     joystick->nhats = device->hats;
   734     joystick->nballs = 0;
   735     joystick->nbuttons = device->buttons;
   736     return 0;
   737 }
   738 
   739 /*
   740  * Like strerror but for force feedback errors.
   741  */
   742 static const char *
   743 FFStrError(unsigned int err)
   744 {
   745     switch (err) {
   746     case FFERR_DEVICEFULL:
   747         return "device full";
   748     /* This should be valid, but for some reason isn't defined... */
   749     /* case FFERR_DEVICENOTREG:
   750         return "device not registered"; */
   751     case FFERR_DEVICEPAUSED:
   752         return "device paused";
   753     case FFERR_DEVICERELEASED:
   754         return "device released";
   755     case FFERR_EFFECTPLAYING:
   756         return "effect playing";
   757     case FFERR_EFFECTTYPEMISMATCH:
   758         return "effect type mismatch";
   759     case FFERR_EFFECTTYPENOTSUPPORTED:
   760         return "effect type not supported";
   761     case FFERR_GENERIC:
   762         return "undetermined error";
   763     case FFERR_HASEFFECTS:
   764         return "device has effects";
   765     case FFERR_INCOMPLETEEFFECT:
   766         return "incomplete effect";
   767     case FFERR_INTERNAL:
   768         return "internal fault";
   769     case FFERR_INVALIDDOWNLOADID:
   770         return "invalid download id";
   771     case FFERR_INVALIDPARAM:
   772         return "invalid parameter";
   773     case FFERR_MOREDATA:
   774         return "more data";
   775     case FFERR_NOINTERFACE:
   776         return "interface not supported";
   777     case FFERR_NOTDOWNLOADED:
   778         return "effect is not downloaded";
   779     case FFERR_NOTINITIALIZED:
   780         return "object has not been initialized";
   781     case FFERR_OUTOFMEMORY:
   782         return "out of memory";
   783     case FFERR_UNPLUGGED:
   784         return "device is unplugged";
   785     case FFERR_UNSUPPORTED:
   786         return "function call unsupported";
   787     case FFERR_UNSUPPORTEDAXIS:
   788         return "axis unsupported";
   789 
   790     default:
   791         return "unknown error";
   792     }
   793 }
   794 
   795 static int
   796 DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude, Uint32 duration_ms)
   797 {
   798     HRESULT result;
   799 
   800     if (!device->ffdevice) {
   801         result = FFCreateDevice(device->ffservice, &device->ffdevice);
   802         if (result != FF_OK) {
   803             return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));
   804         }
   805     }
   806 
   807     /* Reset and then enable actuators */
   808     result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);
   809     if (result != FF_OK) {
   810         return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));
   811     }
   812 
   813     result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);
   814     if (result != FF_OK) {
   815         return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));
   816     }
   817 
   818     /* Create the effect */
   819     device->ffeffect = CreateRumbleEffectData(magnitude, duration_ms);
   820     if (!device->ffeffect) {
   821         return SDL_OutOfMemory();
   822     }
   823 
   824     result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,
   825                                device->ffeffect, &device->ffeffect_ref);
   826     if (result != FF_OK) {
   827         return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));
   828     }
   829     return 0;
   830 }
   831 
   832 static int
   833 DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
   834 {
   835     HRESULT result;
   836     recDevice *device = joystick->hwdata;
   837 
   838     /* Scale and average the two rumble strengths */
   839     Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
   840 
   841     if (!device->ffservice) {
   842         return SDL_Unsupported();
   843     }
   844 
   845     if (device->ff_initialized) {
   846         FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
   847         device->ffeffect->dwDuration = duration_ms * 1000; /* In microseconds. */
   848         periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
   849 
   850         result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
   851                                     (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
   852         if (result != FF_OK) {
   853             return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
   854         }
   855     } else {
   856         if (DARWIN_JoystickInitRumble(device, magnitude, duration_ms) < 0) {
   857             return -1;
   858         }
   859         device->ff_initialized = SDL_TRUE;
   860     }
   861 
   862     result = FFEffectStart(device->ffeffect_ref, 1, 0);
   863     if (result != FF_OK) {
   864         return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
   865     }
   866     return 0;
   867 }
   868 
   869 static void
   870 DARWIN_JoystickUpdate(SDL_Joystick * joystick)
   871 {
   872     recDevice *device = joystick->hwdata;
   873     recElement *element;
   874     SInt32 value, range;
   875     int i;
   876 
   877     if (!device) {
   878         return;
   879     }
   880 
   881     if (device->removed) {      /* device was unplugged; ignore it. */
   882         if (joystick->hwdata) {
   883             joystick->force_recentering = SDL_TRUE;
   884             joystick->hwdata = NULL;
   885         }
   886         return;
   887     }
   888 
   889     element = device->firstAxis;
   890     i = 0;
   891 
   892     int goodRead = SDL_FALSE;
   893     while (element) {
   894         goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);
   895         if (goodRead) {
   896             SDL_PrivateJoystickAxis(joystick, i, value);
   897         }
   898 
   899         element = element->pNext;
   900         ++i;
   901     }
   902 
   903     element = device->firstButton;
   904     i = 0;
   905     while (element) {
   906         goodRead = GetHIDElementState(device, element, &value);
   907         if (goodRead) {
   908             if (value > 1) {          /* handle pressure-sensitive buttons */
   909                 value = 1;
   910             }
   911             SDL_PrivateJoystickButton(joystick, i, value);
   912         }
   913 
   914         element = element->pNext;
   915         ++i;
   916     }
   917 
   918     element = device->firstHat;
   919     i = 0;
   920     
   921     while (element) {
   922         Uint8 pos = 0;
   923 
   924         range = (element->max - element->min + 1);
   925         goodRead = GetHIDElementState(device, element, &value);
   926         if (goodRead) {
   927             value -= element->min;
   928             if (range == 4) {         /* 4 position hatswitch - scale up value */
   929                 value *= 2;
   930             } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
   931                 value = -1;
   932             }
   933             switch (value) {
   934             case 0:
   935                 pos = SDL_HAT_UP;
   936                 break;
   937             case 1:
   938                 pos = SDL_HAT_RIGHTUP;
   939                 break;
   940             case 2:
   941                 pos = SDL_HAT_RIGHT;
   942                 break;
   943             case 3:
   944                 pos = SDL_HAT_RIGHTDOWN;
   945                 break;
   946             case 4:
   947                 pos = SDL_HAT_DOWN;
   948                 break;
   949             case 5:
   950                 pos = SDL_HAT_LEFTDOWN;
   951                 break;
   952             case 6:
   953                 pos = SDL_HAT_LEFT;
   954                 break;
   955             case 7:
   956                 pos = SDL_HAT_LEFTUP;
   957                 break;
   958             default:
   959                 /* Every other value is mapped to center. We do that because some
   960                  * joysticks use 8 and some 15 for this value, and apparently
   961                  * there are even more variants out there - so we try to be generous.
   962                  */
   963                 pos = SDL_HAT_CENTERED;
   964                 break;
   965             }
   966 
   967             SDL_PrivateJoystickHat(joystick, i, pos);
   968         }
   969         
   970         element = element->pNext;
   971         ++i;
   972     }
   973 }
   974 
   975 static void
   976 DARWIN_JoystickClose(SDL_Joystick * joystick)
   977 {
   978 }
   979 
   980 static void
   981 DARWIN_JoystickQuit(void)
   982 {
   983     while (FreeDevice(gpDeviceList)) {
   984         /* spin */
   985     }
   986 
   987     if (hidman) {
   988         IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   989         IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
   990         CFRelease(hidman);
   991         hidman = NULL;
   992     }
   993 }
   994 
   995 SDL_JoystickDriver SDL_DARWIN_JoystickDriver =
   996 {
   997     DARWIN_JoystickInit,
   998     DARWIN_JoystickGetCount,
   999     DARWIN_JoystickDetect,
  1000     DARWIN_JoystickGetDeviceName,
  1001     DARWIN_JoystickGetDeviceGUID,
  1002     DARWIN_JoystickGetDeviceInstanceID,
  1003     DARWIN_JoystickOpen,
  1004     DARWIN_JoystickRumble,
  1005     DARWIN_JoystickUpdate,
  1006     DARWIN_JoystickClose,
  1007     DARWIN_JoystickQuit,
  1008 };
  1009 
  1010 #endif /* SDL_JOYSTICK_IOKIT */
  1011 
  1012 /* vi: set ts=4 sw=4 expandtab: */