src/joystick/darwin/SDL_sysjoystick.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Wed, 16 Nov 2016 22:08:51 +0100
changeset 10617 346c02ff71b6
parent 10598 85f444d7a73d
child 10714 a9c15922ad7b
permissions -rw-r--r--
Fixed empty parameter list in signatures of internal functions.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 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 <IOKit/hid/IOHIDLib.h>
    26 
    27 /* For force feedback testing. */
    28 #include <ForceFeedback/ForceFeedback.h>
    29 #include <ForceFeedback/ForceFeedbackConstants.h>
    30 
    31 #include "SDL_joystick.h"
    32 #include "../SDL_sysjoystick.h"
    33 #include "../SDL_joystick_c.h"
    34 #include "SDL_sysjoystick_c.h"
    35 #include "SDL_events.h"
    36 #include "../../haptic/darwin/SDL_syshaptic_c.h"    /* For haptic hot plugging */
    37 
    38 #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
    39 
    40 /* The base object of the HID Manager API */
    41 static IOHIDManagerRef hidman = NULL;
    42 
    43 /* Linked list of all available devices */
    44 static recDevice *gpDeviceList = NULL;
    45 
    46 /* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
    47 static int s_joystick_instance_id = -1;
    48 
    49 static recDevice *GetDeviceForIndex(int device_index)
    50 {
    51     recDevice *device = gpDeviceList;
    52     while (device) {
    53         if (!device->removed) {
    54             if (device_index == 0)
    55                 break;
    56 
    57             --device_index;
    58         }
    59         device = device->pNext;
    60     }
    61     return device;
    62 }
    63 
    64 static void
    65 FreeElementList(recElement *pElement)
    66 {
    67     while (pElement) {
    68         recElement *pElementNext = pElement->pNext;
    69         SDL_free(pElement);
    70         pElement = pElementNext;
    71     }
    72 }
    73 
    74 static recDevice *
    75 FreeDevice(recDevice *removeDevice)
    76 {
    77     recDevice *pDeviceNext = NULL;
    78     if (removeDevice) {
    79         if (removeDevice->deviceRef) {
    80             IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
    81             removeDevice->deviceRef = NULL;
    82         }
    83 
    84         /* save next device prior to disposing of this device */
    85         pDeviceNext = removeDevice->pNext;
    86 
    87         if ( gpDeviceList == removeDevice ) {
    88             gpDeviceList = pDeviceNext;
    89         } else {
    90             recDevice *device = gpDeviceList;
    91             while (device->pNext != removeDevice) {
    92                 device = device->pNext;
    93             }
    94             device->pNext = pDeviceNext;
    95         }
    96         removeDevice->pNext = NULL;
    97 
    98         /* free element lists */
    99         FreeElementList(removeDevice->firstAxis);
   100         FreeElementList(removeDevice->firstButton);
   101         FreeElementList(removeDevice->firstHat);
   102 
   103         SDL_free(removeDevice);
   104     }
   105     return pDeviceNext;
   106 }
   107 
   108 static SInt32
   109 GetHIDElementState(recDevice *pDevice, recElement *pElement)
   110 {
   111     SInt32 value = 0;
   112 
   113     if (pDevice && pElement) {
   114         IOHIDValueRef valueRef;
   115         if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
   116             value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
   117 
   118             /* record min and max for auto calibration */
   119             if (value < pElement->minReport) {
   120                 pElement->minReport = value;
   121             }
   122             if (value > pElement->maxReport) {
   123                 pElement->maxReport = value;
   124             }
   125         }
   126     }
   127 
   128     return value;
   129 }
   130 
   131 static SInt32
   132 GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max)
   133 {
   134     const float deviceScale = max - min;
   135     const float readScale = pElement->maxReport - pElement->minReport;
   136     const SInt32 value = GetHIDElementState(pDevice, pElement);
   137     if (readScale == 0) {
   138         return value;           /* no scaling at all */
   139     }
   140     return ((value - pElement->minReport) * deviceScale / readScale) + min;
   141 }
   142 
   143 
   144 static void
   145 JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
   146 {
   147     recDevice *device = (recDevice *) ctx;
   148     device->removed = SDL_TRUE;
   149     device->deviceRef = NULL; // deviceRef was invalidated due to the remove
   150 #if SDL_HAPTIC_IOKIT
   151     MacHaptic_MaybeRemoveDevice(device->ffservice);
   152 #endif
   153 
   154     SDL_PrivateJoystickRemoved(device->instance_id);
   155 }
   156 
   157 
   158 static void AddHIDElement(const void *value, void *parameter);
   159 
   160 /* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
   161 static void
   162 AddHIDElements(CFArrayRef array, recDevice *pDevice)
   163 {
   164     const CFRange range = { 0, CFArrayGetCount(array) };
   165     CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
   166 }
   167 
   168 static SDL_bool
   169 ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
   170     while (listitem) {
   171         if (listitem->cookie == cookie) {
   172             return SDL_TRUE;
   173         }
   174         listitem = listitem->pNext;
   175     }
   176     return SDL_FALSE;
   177 }
   178 
   179 /* See if we care about this HID element, and if so, note it in our recDevice. */
   180 static void
   181 AddHIDElement(const void *value, void *parameter)
   182 {
   183     recDevice *pDevice = (recDevice *) parameter;
   184     IOHIDElementRef refElement = (IOHIDElementRef) value;
   185     const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
   186 
   187     if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
   188         const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
   189         const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
   190         const uint32_t usage = IOHIDElementGetUsage(refElement);
   191         recElement *element = NULL;
   192         recElement **headElement = NULL;
   193 
   194         /* look at types of interest */
   195         switch (IOHIDElementGetType(refElement)) {
   196             case kIOHIDElementTypeInput_Misc:
   197             case kIOHIDElementTypeInput_Button:
   198             case kIOHIDElementTypeInput_Axis: {
   199                 switch (usagePage) {    /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
   200                     case kHIDPage_GenericDesktop:
   201                         switch (usage) {
   202                             case kHIDUsage_GD_X:
   203                             case kHIDUsage_GD_Y:
   204                             case kHIDUsage_GD_Z:
   205                             case kHIDUsage_GD_Rx:
   206                             case kHIDUsage_GD_Ry:
   207                             case kHIDUsage_GD_Rz:
   208                             case kHIDUsage_GD_Slider:
   209                             case kHIDUsage_GD_Dial:
   210                             case kHIDUsage_GD_Wheel:
   211                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
   212                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   213                                     if (element) {
   214                                         pDevice->axes++;
   215                                         headElement = &(pDevice->firstAxis);
   216                                     }
   217                                 }
   218                                 break;
   219 
   220                             case kHIDUsage_GD_Hatswitch:
   221                                 if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
   222                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   223                                     if (element) {
   224                                         pDevice->hats++;
   225                                         headElement = &(pDevice->firstHat);
   226                                     }
   227                                 }
   228                                 break;
   229                             case kHIDUsage_GD_DPadUp:
   230                             case kHIDUsage_GD_DPadDown:
   231                             case kHIDUsage_GD_DPadRight:
   232                             case kHIDUsage_GD_DPadLeft:
   233                             case kHIDUsage_GD_Start:
   234                             case kHIDUsage_GD_Select:
   235                             case kHIDUsage_GD_SystemMainMenu:
   236                                 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
   237                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   238                                     if (element) {
   239                                         pDevice->buttons++;
   240                                         headElement = &(pDevice->firstButton);
   241                                     }
   242                                 }
   243                                 break;
   244                         }
   245                         break;
   246 
   247                     case kHIDPage_Simulation:
   248                         switch (usage) {
   249                             case kHIDUsage_Sim_Rudder:
   250                             case kHIDUsage_Sim_Throttle:
   251                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
   252                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   253                                     if (element) {
   254                                         pDevice->axes++;
   255                                         headElement = &(pDevice->firstAxis);
   256                                     }
   257                                 }
   258                                 break;
   259 
   260                             default:
   261                                 break;
   262                         }
   263                         break;
   264 
   265                     case kHIDPage_Button:
   266                     case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
   267                         if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
   268                             element = (recElement *) SDL_calloc(1, sizeof (recElement));
   269                             if (element) {
   270                                 pDevice->buttons++;
   271                                 headElement = &(pDevice->firstButton);
   272                             }
   273                         }
   274                         break;
   275 
   276                     default:
   277                         break;
   278                 }
   279             }
   280             break;
   281 
   282             case kIOHIDElementTypeCollection: {
   283                 CFArrayRef array = IOHIDElementGetChildren(refElement);
   284                 if (array) {
   285                     AddHIDElements(array, pDevice);
   286                 }
   287             }
   288             break;
   289 
   290             default:
   291                 break;
   292         }
   293 
   294         if (element && headElement) {       /* add to list */
   295             recElement *elementPrevious = NULL;
   296             recElement *elementCurrent = *headElement;
   297             while (elementCurrent && usage >= elementCurrent->usage) {
   298                 elementPrevious = elementCurrent;
   299                 elementCurrent = elementCurrent->pNext;
   300             }
   301             if (elementPrevious) {
   302                 elementPrevious->pNext = element;
   303             } else {
   304                 *headElement = element;
   305             }
   306 
   307             element->elementRef = refElement;
   308             element->usagePage = usagePage;
   309             element->usage = usage;
   310             element->pNext = elementCurrent;
   311 
   312             element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
   313             element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
   314             element->cookie = IOHIDElementGetCookie(refElement);
   315 
   316             pDevice->elements++;
   317         }
   318     }
   319 }
   320 
   321 static SDL_bool
   322 GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
   323 {
   324     const Uint16 BUS_USB = 0x03;
   325     const Uint16 BUS_BLUETOOTH = 0x05;
   326     Sint32 vendor = 0;
   327     Sint32 product = 0;
   328     Sint32 version = 0;
   329     CFTypeRef refCF = NULL;
   330     CFArrayRef array = NULL;
   331     Uint16 *guid16 = (Uint16 *)pDevice->guid.data;
   332 
   333     /* get usage page and usage */
   334     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
   335     if (refCF) {
   336         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
   337     }
   338     if (pDevice->usagePage != kHIDPage_GenericDesktop) {
   339         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
   340     }
   341 
   342     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
   343     if (refCF) {
   344         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
   345     }
   346 
   347     if ((pDevice->usage != kHIDUsage_GD_Joystick &&
   348          pDevice->usage != kHIDUsage_GD_GamePad &&
   349          pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
   350         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
   351     }
   352 
   353     pDevice->deviceRef = hidDevice;
   354 
   355     /* get device name */
   356     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
   357     if (!refCF) {
   358         /* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
   359         refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
   360     }
   361     if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
   362         SDL_strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
   363     }
   364 
   365     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
   366     if (refCF) {
   367         CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
   368     }
   369 
   370     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
   371     if (refCF) {
   372         CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
   373     }
   374 
   375     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
   376     if (refCF) {
   377         CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
   378     }
   379 
   380     SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
   381 
   382     if (vendor && product) {
   383         *guid16++ = SDL_SwapLE16(BUS_USB);
   384         *guid16++ = 0;
   385         *guid16++ = SDL_SwapLE16((Uint16)vendor);
   386         *guid16++ = 0;
   387         *guid16++ = SDL_SwapLE16((Uint16)product);
   388         *guid16++ = 0;
   389         *guid16++ = SDL_SwapLE16((Uint16)version);
   390         *guid16++ = 0;
   391     } else {
   392         *guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
   393         *guid16++ = 0;
   394         SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
   395     }
   396 
   397     array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
   398     if (array) {
   399         AddHIDElements(array, pDevice);
   400         CFRelease(array);
   401     }
   402 
   403     return SDL_TRUE;
   404 }
   405 
   406 static SDL_bool
   407 JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
   408 {
   409     recDevice *i;
   410     for (i = gpDeviceList; i != NULL; i = i->pNext) {
   411         if (i->deviceRef == ioHIDDeviceObject) {
   412             return SDL_TRUE;
   413         }
   414     }
   415     return SDL_FALSE;
   416 }
   417 
   418 
   419 static void
   420 JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
   421 {
   422     recDevice *device;
   423     int device_index = 0;
   424     io_service_t ioservice;
   425 
   426     if (res != kIOReturnSuccess) {
   427         return;
   428     }
   429 
   430     if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
   431         return;  /* IOKit sent us a duplicate. */
   432     }
   433 
   434     device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
   435 
   436     if (!device) {
   437         SDL_OutOfMemory();
   438         return;
   439     }
   440 
   441     if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
   442         SDL_free(device);
   443         return;   /* not a device we care about, probably. */
   444     }
   445 
   446     /* Get notified when this device is disconnected. */
   447     IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
   448     IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   449 
   450     /* Allocate an instance ID for this device */
   451     device->instance_id = ++s_joystick_instance_id;
   452 
   453     /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
   454     ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
   455 #if SDL_HAPTIC_IOKIT
   456     if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
   457         device->ffservice = ioservice;
   458         MacHaptic_MaybeAddDevice(ioservice);
   459     }
   460 #endif
   461 
   462     /* Add device to the end of the list */
   463     if ( !gpDeviceList ) {
   464         gpDeviceList = device;
   465     } else {
   466         recDevice *curdevice;
   467 
   468         curdevice = gpDeviceList;
   469         while ( curdevice->pNext ) {
   470             ++device_index;
   471             curdevice = curdevice->pNext;
   472         }
   473         curdevice->pNext = device;
   474         ++device_index;  /* bump by one since we counted by pNext. */
   475     }
   476 
   477     SDL_PrivateJoystickAdded(device_index);
   478 }
   479 
   480 static SDL_bool
   481 ConfigHIDManager(CFArrayRef matchingArray)
   482 {
   483     CFRunLoopRef runloop = CFRunLoopGetCurrent();
   484 
   485     if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
   486         return SDL_FALSE;
   487     }
   488 
   489     IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
   490     IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
   491     IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
   492 
   493     while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
   494         /* no-op. Callback fires once per existing device. */
   495     }
   496 
   497     /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
   498 
   499     return SDL_TRUE;  /* good to go. */
   500 }
   501 
   502 
   503 static CFDictionaryRef
   504 CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
   505 {
   506     CFDictionaryRef retval = NULL;
   507     CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
   508     CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
   509     const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
   510     const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
   511 
   512     if (pageNumRef && usageNumRef) {
   513         retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
   514     }
   515 
   516     if (pageNumRef) {
   517         CFRelease(pageNumRef);
   518     }
   519     if (usageNumRef) {
   520         CFRelease(usageNumRef);
   521     }
   522 
   523     if (!retval) {
   524         *okay = 0;
   525     }
   526 
   527     return retval;
   528 }
   529 
   530 static SDL_bool
   531 CreateHIDManager(void)
   532 {
   533     SDL_bool retval = SDL_FALSE;
   534     int okay = 1;
   535     const void *vals[] = {
   536         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
   537         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
   538         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
   539     };
   540     const size_t numElements = SDL_arraysize(vals);
   541     CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
   542     size_t i;
   543 
   544     for (i = 0; i < numElements; i++) {
   545         if (vals[i]) {
   546             CFRelease((CFTypeRef) vals[i]);
   547         }
   548     }
   549 
   550     if (array) {
   551         hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
   552         if (hidman != NULL) {
   553             retval = ConfigHIDManager(array);
   554         }
   555         CFRelease(array);
   556     }
   557 
   558     return retval;
   559 }
   560 
   561 
   562 /* Function to scan the system for joysticks.
   563  * Joystick 0 should be the system default joystick.
   564  * This function should return the number of available joysticks, or -1
   565  * on an unrecoverable fatal error.
   566  */
   567 int
   568 SDL_SYS_JoystickInit(void)
   569 {
   570     if (gpDeviceList) {
   571         return SDL_SetError("Joystick: Device list already inited.");
   572     }
   573 
   574     if (!CreateHIDManager()) {
   575         return SDL_SetError("Joystick: Couldn't initialize HID Manager");
   576     }
   577 
   578     return SDL_SYS_NumJoysticks();
   579 }
   580 
   581 /* Function to return the number of joystick devices plugged in right now */
   582 int
   583 SDL_SYS_NumJoysticks(void)
   584 {
   585     recDevice *device = gpDeviceList;
   586     int nJoySticks = 0;
   587 
   588     while (device) {
   589         if (!device->removed) {
   590             nJoySticks++;
   591         }
   592         device = device->pNext;
   593     }
   594 
   595     return nJoySticks;
   596 }
   597 
   598 /* Function to cause any queued joystick insertions to be processed
   599  */
   600 void
   601 SDL_SYS_JoystickDetect(void)
   602 {
   603     recDevice *device = gpDeviceList;
   604     while (device) {
   605         if (device->removed) {
   606             device = FreeDevice(device);
   607         } else {
   608             device = device->pNext;
   609         }
   610     }
   611 
   612 	// run this after the checks above so we don't set device->removed and delete the device before
   613 	// SDL_SYS_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device
   614 	while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
   615 		/* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
   616 	}
   617 }
   618 
   619 /* Function to get the device-dependent name of a joystick */
   620 const char *
   621 SDL_SYS_JoystickNameForDeviceIndex(int device_index)
   622 {
   623     recDevice *device = GetDeviceForIndex(device_index);
   624     return device ? device->product : "UNKNOWN";
   625 }
   626 
   627 /* Function to return the instance id of the joystick at device_index
   628  */
   629 SDL_JoystickID
   630 SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
   631 {
   632     recDevice *device = GetDeviceForIndex(device_index);
   633     return device ? device->instance_id : 0;
   634 }
   635 
   636 /* Function to open a joystick for use.
   637  * The joystick to open is specified by the device index.
   638  * This should fill the nbuttons and naxes fields of the joystick structure.
   639  * It returns 0, or -1 if there is an error.
   640  */
   641 int
   642 SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
   643 {
   644     recDevice *device = GetDeviceForIndex(device_index);
   645 
   646     joystick->instance_id = device->instance_id;
   647     joystick->hwdata = device;
   648     joystick->name = device->product;
   649 
   650     joystick->naxes = device->axes;
   651     joystick->nhats = device->hats;
   652     joystick->nballs = 0;
   653     joystick->nbuttons = device->buttons;
   654     return 0;
   655 }
   656 
   657 /* Function to query if the joystick is currently attached
   658  * It returns SDL_TRUE if attached, SDL_FALSE otherwise.
   659  */
   660 SDL_bool
   661 SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
   662 {
   663     return joystick->hwdata != NULL;
   664 }
   665 
   666 /* Function to update the state of a joystick - called as a device poll.
   667  * This function shouldn't update the joystick structure directly,
   668  * but instead should call SDL_PrivateJoystick*() to deliver events
   669  * and update joystick device state.
   670  */
   671 void
   672 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   673 {
   674     recDevice *device = joystick->hwdata;
   675     recElement *element;
   676     SInt32 value, range;
   677     int i;
   678 
   679     if (!device) {
   680         return;
   681     }
   682 
   683     if (device->removed) {      /* device was unplugged; ignore it. */
   684         if (joystick->hwdata) {
   685             joystick->force_recentering = SDL_TRUE;
   686             joystick->hwdata = NULL;
   687         }
   688         return;
   689     }
   690 
   691     element = device->firstAxis;
   692     i = 0;
   693     while (element) {
   694         value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
   695         if (value != joystick->axes[i]) {
   696             SDL_PrivateJoystickAxis(joystick, i, value);
   697         }
   698         element = element->pNext;
   699         ++i;
   700     }
   701 
   702     element = device->firstButton;
   703     i = 0;
   704     while (element) {
   705         value = GetHIDElementState(device, element);
   706         if (value > 1) {          /* handle pressure-sensitive buttons */
   707             value = 1;
   708         }
   709         if (value != joystick->buttons[i]) {
   710             SDL_PrivateJoystickButton(joystick, i, value);
   711         }
   712         element = element->pNext;
   713         ++i;
   714     }
   715 
   716     element = device->firstHat;
   717     i = 0;
   718     while (element) {
   719         Uint8 pos = 0;
   720 
   721         range = (element->max - element->min + 1);
   722         value = GetHIDElementState(device, element) - element->min;
   723         if (range == 4) {         /* 4 position hatswitch - scale up value */
   724             value *= 2;
   725         } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
   726             value = -1;
   727         }
   728         switch (value) {
   729         case 0:
   730             pos = SDL_HAT_UP;
   731             break;
   732         case 1:
   733             pos = SDL_HAT_RIGHTUP;
   734             break;
   735         case 2:
   736             pos = SDL_HAT_RIGHT;
   737             break;
   738         case 3:
   739             pos = SDL_HAT_RIGHTDOWN;
   740             break;
   741         case 4:
   742             pos = SDL_HAT_DOWN;
   743             break;
   744         case 5:
   745             pos = SDL_HAT_LEFTDOWN;
   746             break;
   747         case 6:
   748             pos = SDL_HAT_LEFT;
   749             break;
   750         case 7:
   751             pos = SDL_HAT_LEFTUP;
   752             break;
   753         default:
   754             /* Every other value is mapped to center. We do that because some
   755              * joysticks use 8 and some 15 for this value, and apparently
   756              * there are even more variants out there - so we try to be generous.
   757              */
   758             pos = SDL_HAT_CENTERED;
   759             break;
   760         }
   761 
   762         if (pos != joystick->hats[i]) {
   763             SDL_PrivateJoystickHat(joystick, i, pos);
   764         }
   765 
   766         element = element->pNext;
   767         ++i;
   768     }
   769 }
   770 
   771 /* Function to close a joystick after use */
   772 void
   773 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   774 {
   775 }
   776 
   777 /* Function to perform any system-specific joystick related cleanup */
   778 void
   779 SDL_SYS_JoystickQuit(void)
   780 {
   781     while (FreeDevice(gpDeviceList)) {
   782         /* spin */
   783     }
   784 
   785     if (hidman) {
   786         IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   787         IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
   788         CFRelease(hidman);
   789         hidman = NULL;
   790     }
   791 }
   792 
   793 
   794 SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
   795 {
   796     recDevice *device = GetDeviceForIndex(device_index);
   797     SDL_JoystickGUID guid;
   798     if (device) {
   799         guid = device->guid;
   800     } else {
   801         SDL_zero(guid);
   802     }
   803     return guid;
   804 }
   805 
   806 SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick *joystick)
   807 {
   808     return joystick->hwdata->guid;
   809 }
   810 
   811 #endif /* SDL_JOYSTICK_IOKIT */
   812 
   813 /* vi: set ts=4 sw=4 expandtab: */