src/joystick/darwin/SDL_sysjoystick.c
author Ozkan Sezer <sezeroz@gmail.com>
Fri, 23 Aug 2019 03:01:10 +0300
changeset 13033 70bf772a32d7
parent 12503 806492103856
permissions -rw-r--r--
fix permissions
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../../SDL_internal.h"
    22 
    23 #ifdef SDL_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 int
   704 DARWIN_JoystickGetDevicePlayerIndex(int device_index)
   705 {
   706     return -1;
   707 }
   708 
   709 static SDL_JoystickGUID
   710 DARWIN_JoystickGetDeviceGUID( int device_index )
   711 {
   712     recDevice *device = GetDeviceForIndex(device_index);
   713     SDL_JoystickGUID guid;
   714     if (device) {
   715         guid = device->guid;
   716     } else {
   717         SDL_zero(guid);
   718     }
   719     return guid;
   720 }
   721 
   722 static SDL_JoystickID
   723 DARWIN_JoystickGetDeviceInstanceID(int device_index)
   724 {
   725     recDevice *device = GetDeviceForIndex(device_index);
   726     return device ? device->instance_id : 0;
   727 }
   728 
   729 static int
   730 DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index)
   731 {
   732     recDevice *device = GetDeviceForIndex(device_index);
   733 
   734     joystick->instance_id = device->instance_id;
   735     joystick->hwdata = device;
   736     joystick->name = device->product;
   737 
   738     joystick->naxes = device->axes;
   739     joystick->nhats = device->hats;
   740     joystick->nballs = 0;
   741     joystick->nbuttons = device->buttons;
   742     return 0;
   743 }
   744 
   745 /*
   746  * Like strerror but for force feedback errors.
   747  */
   748 static const char *
   749 FFStrError(unsigned int err)
   750 {
   751     switch (err) {
   752     case FFERR_DEVICEFULL:
   753         return "device full";
   754     /* This should be valid, but for some reason isn't defined... */
   755     /* case FFERR_DEVICENOTREG:
   756         return "device not registered"; */
   757     case FFERR_DEVICEPAUSED:
   758         return "device paused";
   759     case FFERR_DEVICERELEASED:
   760         return "device released";
   761     case FFERR_EFFECTPLAYING:
   762         return "effect playing";
   763     case FFERR_EFFECTTYPEMISMATCH:
   764         return "effect type mismatch";
   765     case FFERR_EFFECTTYPENOTSUPPORTED:
   766         return "effect type not supported";
   767     case FFERR_GENERIC:
   768         return "undetermined error";
   769     case FFERR_HASEFFECTS:
   770         return "device has effects";
   771     case FFERR_INCOMPLETEEFFECT:
   772         return "incomplete effect";
   773     case FFERR_INTERNAL:
   774         return "internal fault";
   775     case FFERR_INVALIDDOWNLOADID:
   776         return "invalid download id";
   777     case FFERR_INVALIDPARAM:
   778         return "invalid parameter";
   779     case FFERR_MOREDATA:
   780         return "more data";
   781     case FFERR_NOINTERFACE:
   782         return "interface not supported";
   783     case FFERR_NOTDOWNLOADED:
   784         return "effect is not downloaded";
   785     case FFERR_NOTINITIALIZED:
   786         return "object has not been initialized";
   787     case FFERR_OUTOFMEMORY:
   788         return "out of memory";
   789     case FFERR_UNPLUGGED:
   790         return "device is unplugged";
   791     case FFERR_UNSUPPORTED:
   792         return "function call unsupported";
   793     case FFERR_UNSUPPORTEDAXIS:
   794         return "axis unsupported";
   795 
   796     default:
   797         return "unknown error";
   798     }
   799 }
   800 
   801 static int
   802 DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude, Uint32 duration_ms)
   803 {
   804     HRESULT result;
   805 
   806     if (!device->ffdevice) {
   807         result = FFCreateDevice(device->ffservice, &device->ffdevice);
   808         if (result != FF_OK) {
   809             return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));
   810         }
   811     }
   812 
   813     /* Reset and then enable actuators */
   814     result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);
   815     if (result != FF_OK) {
   816         return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));
   817     }
   818 
   819     result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);
   820     if (result != FF_OK) {
   821         return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));
   822     }
   823 
   824     /* Create the effect */
   825     device->ffeffect = CreateRumbleEffectData(magnitude, duration_ms);
   826     if (!device->ffeffect) {
   827         return SDL_OutOfMemory();
   828     }
   829 
   830     result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,
   831                                device->ffeffect, &device->ffeffect_ref);
   832     if (result != FF_OK) {
   833         return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));
   834     }
   835     return 0;
   836 }
   837 
   838 static int
   839 DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
   840 {
   841     HRESULT result;
   842     recDevice *device = joystick->hwdata;
   843 
   844     /* Scale and average the two rumble strengths */
   845     Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
   846 
   847     if (!device->ffservice) {
   848         return SDL_Unsupported();
   849     }
   850 
   851     if (device->ff_initialized) {
   852         FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
   853         device->ffeffect->dwDuration = duration_ms * 1000; /* In microseconds. */
   854         periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
   855 
   856         result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
   857                                     (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
   858         if (result != FF_OK) {
   859             return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
   860         }
   861     } else {
   862         if (DARWIN_JoystickInitRumble(device, magnitude, duration_ms) < 0) {
   863             return -1;
   864         }
   865         device->ff_initialized = SDL_TRUE;
   866     }
   867 
   868     result = FFEffectStart(device->ffeffect_ref, 1, 0);
   869     if (result != FF_OK) {
   870         return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
   871     }
   872     return 0;
   873 }
   874 
   875 static void
   876 DARWIN_JoystickUpdate(SDL_Joystick * joystick)
   877 {
   878     recDevice *device = joystick->hwdata;
   879     recElement *element;
   880     SInt32 value, range;
   881     int i;
   882 
   883     if (!device) {
   884         return;
   885     }
   886 
   887     if (device->removed) {      /* device was unplugged; ignore it. */
   888         if (joystick->hwdata) {
   889             joystick->force_recentering = SDL_TRUE;
   890             joystick->hwdata = NULL;
   891         }
   892         return;
   893     }
   894 
   895     element = device->firstAxis;
   896     i = 0;
   897 
   898     int goodRead = SDL_FALSE;
   899     while (element) {
   900         goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);
   901         if (goodRead) {
   902             SDL_PrivateJoystickAxis(joystick, i, value);
   903         }
   904 
   905         element = element->pNext;
   906         ++i;
   907     }
   908 
   909     element = device->firstButton;
   910     i = 0;
   911     while (element) {
   912         goodRead = GetHIDElementState(device, element, &value);
   913         if (goodRead) {
   914             if (value > 1) {          /* handle pressure-sensitive buttons */
   915                 value = 1;
   916             }
   917             SDL_PrivateJoystickButton(joystick, i, value);
   918         }
   919 
   920         element = element->pNext;
   921         ++i;
   922     }
   923 
   924     element = device->firstHat;
   925     i = 0;
   926     
   927     while (element) {
   928         Uint8 pos = 0;
   929 
   930         range = (element->max - element->min + 1);
   931         goodRead = GetHIDElementState(device, element, &value);
   932         if (goodRead) {
   933             value -= element->min;
   934             if (range == 4) {         /* 4 position hatswitch - scale up value */
   935                 value *= 2;
   936             } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
   937                 value = -1;
   938             }
   939             switch (value) {
   940             case 0:
   941                 pos = SDL_HAT_UP;
   942                 break;
   943             case 1:
   944                 pos = SDL_HAT_RIGHTUP;
   945                 break;
   946             case 2:
   947                 pos = SDL_HAT_RIGHT;
   948                 break;
   949             case 3:
   950                 pos = SDL_HAT_RIGHTDOWN;
   951                 break;
   952             case 4:
   953                 pos = SDL_HAT_DOWN;
   954                 break;
   955             case 5:
   956                 pos = SDL_HAT_LEFTDOWN;
   957                 break;
   958             case 6:
   959                 pos = SDL_HAT_LEFT;
   960                 break;
   961             case 7:
   962                 pos = SDL_HAT_LEFTUP;
   963                 break;
   964             default:
   965                 /* Every other value is mapped to center. We do that because some
   966                  * joysticks use 8 and some 15 for this value, and apparently
   967                  * there are even more variants out there - so we try to be generous.
   968                  */
   969                 pos = SDL_HAT_CENTERED;
   970                 break;
   971             }
   972 
   973             SDL_PrivateJoystickHat(joystick, i, pos);
   974         }
   975         
   976         element = element->pNext;
   977         ++i;
   978     }
   979 }
   980 
   981 static void
   982 DARWIN_JoystickClose(SDL_Joystick * joystick)
   983 {
   984 }
   985 
   986 static void
   987 DARWIN_JoystickQuit(void)
   988 {
   989     while (FreeDevice(gpDeviceList)) {
   990         /* spin */
   991     }
   992 
   993     if (hidman) {
   994         IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   995         IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
   996         CFRelease(hidman);
   997         hidman = NULL;
   998     }
   999 }
  1000 
  1001 SDL_JoystickDriver SDL_DARWIN_JoystickDriver =
  1002 {
  1003     DARWIN_JoystickInit,
  1004     DARWIN_JoystickGetCount,
  1005     DARWIN_JoystickDetect,
  1006     DARWIN_JoystickGetDeviceName,
  1007     DARWIN_JoystickGetDevicePlayerIndex,
  1008     DARWIN_JoystickGetDeviceGUID,
  1009     DARWIN_JoystickGetDeviceInstanceID,
  1010     DARWIN_JoystickOpen,
  1011     DARWIN_JoystickRumble,
  1012     DARWIN_JoystickUpdate,
  1013     DARWIN_JoystickClose,
  1014     DARWIN_JoystickQuit,
  1015 };
  1016 
  1017 #endif /* SDL_JOYSTICK_IOKIT */
  1018 
  1019 /* vi: set ts=4 sw=4 expandtab: */