src/joystick/darwin/SDL_sysjoystick.c
author David Ludwig <dludwig@pobox.com>
Tue, 17 Mar 2020 02:31:47 -0400
changeset 13642 f4ae4b91cf38
parent 13638 ca87b62e4e17
child 13646 784ce9766fb9
permissions -rw-r--r--
Backout prior fix for Bug 5034, which needs more research

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