src/joystick/darwin/SDL_sysjoystick.c
author Sam Lantinga
Fri, 27 Jan 2017 21:23:27 -0800
changeset 10861 71d8f9afb690
parent 10853 f4a771647ecd
child 11201 813a8510bd0c
permissions -rw-r--r--
Fixed bug 3569 - GL_UpdateViewport leaves PROJECTION matrix selected

Tom Seddon

GL_ActivateRenderer may call GL_UpdateViewport, which leaves the GL_PROJECTION matrix selected. But after GL_ResetState, the GL_MODELVIEW matrix is selected, suggesting that's the intended default state.

It seems at least like these should be consistent. Presumably GL_UpdateViewport should be doing a glMatrixMode(GL_MODELVIEW) before it finishes.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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                             case kHIDUsage_Sim_Accelerator:
   252                             case kHIDUsage_Sim_Brake:
   253                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
   254                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
   255                                     if (element) {
   256                                         pDevice->axes++;
   257                                         headElement = &(pDevice->firstAxis);
   258                                     }
   259                                 }
   260                                 break;
   261 
   262                             default:
   263                                 break;
   264                         }
   265                         break;
   266 
   267                     case kHIDPage_Button:
   268                     case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
   269                         if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
   270                             element = (recElement *) SDL_calloc(1, sizeof (recElement));
   271                             if (element) {
   272                                 pDevice->buttons++;
   273                                 headElement = &(pDevice->firstButton);
   274                             }
   275                         }
   276                         break;
   277 
   278                     default:
   279                         break;
   280                 }
   281             }
   282             break;
   283 
   284             case kIOHIDElementTypeCollection: {
   285                 CFArrayRef array = IOHIDElementGetChildren(refElement);
   286                 if (array) {
   287                     AddHIDElements(array, pDevice);
   288                 }
   289             }
   290             break;
   291 
   292             default:
   293                 break;
   294         }
   295 
   296         if (element && headElement) {       /* add to list */
   297             recElement *elementPrevious = NULL;
   298             recElement *elementCurrent = *headElement;
   299             while (elementCurrent && usage >= elementCurrent->usage) {
   300                 elementPrevious = elementCurrent;
   301                 elementCurrent = elementCurrent->pNext;
   302             }
   303             if (elementPrevious) {
   304                 elementPrevious->pNext = element;
   305             } else {
   306                 *headElement = element;
   307             }
   308 
   309             element->elementRef = refElement;
   310             element->usagePage = usagePage;
   311             element->usage = usage;
   312             element->pNext = elementCurrent;
   313 
   314             element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
   315             element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
   316             element->cookie = IOHIDElementGetCookie(refElement);
   317 
   318             pDevice->elements++;
   319         }
   320     }
   321 }
   322 
   323 static SDL_bool
   324 GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
   325 {
   326     const Uint16 BUS_USB = 0x03;
   327     const Uint16 BUS_BLUETOOTH = 0x05;
   328     Sint32 vendor = 0;
   329     Sint32 product = 0;
   330     Sint32 version = 0;
   331     CFTypeRef refCF = NULL;
   332     CFArrayRef array = NULL;
   333     Uint16 *guid16 = (Uint16 *)pDevice->guid.data;
   334 
   335     /* get usage page and usage */
   336     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
   337     if (refCF) {
   338         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
   339     }
   340     if (pDevice->usagePage != kHIDPage_GenericDesktop) {
   341         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
   342     }
   343 
   344     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
   345     if (refCF) {
   346         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
   347     }
   348 
   349     if ((pDevice->usage != kHIDUsage_GD_Joystick &&
   350          pDevice->usage != kHIDUsage_GD_GamePad &&
   351          pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
   352         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
   353     }
   354 
   355     pDevice->deviceRef = hidDevice;
   356 
   357     /* get device name */
   358     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
   359     if (!refCF) {
   360         /* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
   361         refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
   362     }
   363     if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
   364         SDL_strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
   365     }
   366 
   367     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
   368     if (refCF) {
   369         CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
   370     }
   371 
   372     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
   373     if (refCF) {
   374         CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
   375     }
   376 
   377     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
   378     if (refCF) {
   379         CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
   380     }
   381 
   382     SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
   383 
   384     if (vendor && product) {
   385         *guid16++ = SDL_SwapLE16(BUS_USB);
   386         *guid16++ = 0;
   387         *guid16++ = SDL_SwapLE16((Uint16)vendor);
   388         *guid16++ = 0;
   389         *guid16++ = SDL_SwapLE16((Uint16)product);
   390         *guid16++ = 0;
   391         *guid16++ = SDL_SwapLE16((Uint16)version);
   392         *guid16++ = 0;
   393     } else {
   394         *guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
   395         *guid16++ = 0;
   396         SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
   397     }
   398 
   399     array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
   400     if (array) {
   401         AddHIDElements(array, pDevice);
   402         CFRelease(array);
   403     }
   404 
   405     return SDL_TRUE;
   406 }
   407 
   408 static SDL_bool
   409 JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
   410 {
   411     recDevice *i;
   412     for (i = gpDeviceList; i != NULL; i = i->pNext) {
   413         if (i->deviceRef == ioHIDDeviceObject) {
   414             return SDL_TRUE;
   415         }
   416     }
   417     return SDL_FALSE;
   418 }
   419 
   420 
   421 static void
   422 JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
   423 {
   424     recDevice *device;
   425     int device_index = 0;
   426     io_service_t ioservice;
   427 
   428     if (res != kIOReturnSuccess) {
   429         return;
   430     }
   431 
   432     if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
   433         return;  /* IOKit sent us a duplicate. */
   434     }
   435 
   436     device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
   437 
   438     if (!device) {
   439         SDL_OutOfMemory();
   440         return;
   441     }
   442 
   443     if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
   444         SDL_free(device);
   445         return;   /* not a device we care about, probably. */
   446     }
   447 
   448     /* Get notified when this device is disconnected. */
   449     IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
   450     IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   451 
   452     /* Allocate an instance ID for this device */
   453     device->instance_id = ++s_joystick_instance_id;
   454 
   455     /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
   456     ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
   457 #if SDL_HAPTIC_IOKIT
   458     if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
   459         device->ffservice = ioservice;
   460         MacHaptic_MaybeAddDevice(ioservice);
   461     }
   462 #endif
   463 
   464     /* Add device to the end of the list */
   465     if ( !gpDeviceList ) {
   466         gpDeviceList = device;
   467     } else {
   468         recDevice *curdevice;
   469 
   470         curdevice = gpDeviceList;
   471         while ( curdevice->pNext ) {
   472             ++device_index;
   473             curdevice = curdevice->pNext;
   474         }
   475         curdevice->pNext = device;
   476         ++device_index;  /* bump by one since we counted by pNext. */
   477     }
   478 
   479     SDL_PrivateJoystickAdded(device_index);
   480 }
   481 
   482 static SDL_bool
   483 ConfigHIDManager(CFArrayRef matchingArray)
   484 {
   485     CFRunLoopRef runloop = CFRunLoopGetCurrent();
   486 
   487     if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
   488         return SDL_FALSE;
   489     }
   490 
   491     IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
   492     IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
   493     IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
   494 
   495     while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
   496         /* no-op. Callback fires once per existing device. */
   497     }
   498 
   499     /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
   500 
   501     return SDL_TRUE;  /* good to go. */
   502 }
   503 
   504 
   505 static CFDictionaryRef
   506 CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
   507 {
   508     CFDictionaryRef retval = NULL;
   509     CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
   510     CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
   511     const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
   512     const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
   513 
   514     if (pageNumRef && usageNumRef) {
   515         retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
   516     }
   517 
   518     if (pageNumRef) {
   519         CFRelease(pageNumRef);
   520     }
   521     if (usageNumRef) {
   522         CFRelease(usageNumRef);
   523     }
   524 
   525     if (!retval) {
   526         *okay = 0;
   527     }
   528 
   529     return retval;
   530 }
   531 
   532 static SDL_bool
   533 CreateHIDManager(void)
   534 {
   535     SDL_bool retval = SDL_FALSE;
   536     int okay = 1;
   537     const void *vals[] = {
   538         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
   539         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
   540         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
   541     };
   542     const size_t numElements = SDL_arraysize(vals);
   543     CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
   544     size_t i;
   545 
   546     for (i = 0; i < numElements; i++) {
   547         if (vals[i]) {
   548             CFRelease((CFTypeRef) vals[i]);
   549         }
   550     }
   551 
   552     if (array) {
   553         hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
   554         if (hidman != NULL) {
   555             retval = ConfigHIDManager(array);
   556         }
   557         CFRelease(array);
   558     }
   559 
   560     return retval;
   561 }
   562 
   563 
   564 /* Function to scan the system for joysticks.
   565  * Joystick 0 should be the system default joystick.
   566  * This function should return the number of available joysticks, or -1
   567  * on an unrecoverable fatal error.
   568  */
   569 int
   570 SDL_SYS_JoystickInit(void)
   571 {
   572     if (gpDeviceList) {
   573         return SDL_SetError("Joystick: Device list already inited.");
   574     }
   575 
   576     if (!CreateHIDManager()) {
   577         return SDL_SetError("Joystick: Couldn't initialize HID Manager");
   578     }
   579 
   580     return SDL_SYS_NumJoysticks();
   581 }
   582 
   583 /* Function to return the number of joystick devices plugged in right now */
   584 int
   585 SDL_SYS_NumJoysticks(void)
   586 {
   587     recDevice *device = gpDeviceList;
   588     int nJoySticks = 0;
   589 
   590     while (device) {
   591         if (!device->removed) {
   592             nJoySticks++;
   593         }
   594         device = device->pNext;
   595     }
   596 
   597     return nJoySticks;
   598 }
   599 
   600 /* Function to cause any queued joystick insertions to be processed
   601  */
   602 void
   603 SDL_SYS_JoystickDetect(void)
   604 {
   605     recDevice *device = gpDeviceList;
   606     while (device) {
   607         if (device->removed) {
   608             device = FreeDevice(device);
   609         } else {
   610             device = device->pNext;
   611         }
   612     }
   613 
   614 	/* run this after the checks above so we don't set device->removed and delete the device before
   615 	   SDL_SYS_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
   616 	while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
   617 		/* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
   618 	}
   619 }
   620 
   621 /* Function to get the device-dependent name of a joystick */
   622 const char *
   623 SDL_SYS_JoystickNameForDeviceIndex(int device_index)
   624 {
   625     recDevice *device = GetDeviceForIndex(device_index);
   626     return device ? device->product : "UNKNOWN";
   627 }
   628 
   629 /* Function to return the instance id of the joystick at device_index
   630  */
   631 SDL_JoystickID
   632 SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
   633 {
   634     recDevice *device = GetDeviceForIndex(device_index);
   635     return device ? device->instance_id : 0;
   636 }
   637 
   638 /* Function to open a joystick for use.
   639  * The joystick to open is specified by the device index.
   640  * This should fill the nbuttons and naxes fields of the joystick structure.
   641  * It returns 0, or -1 if there is an error.
   642  */
   643 int
   644 SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
   645 {
   646     recDevice *device = GetDeviceForIndex(device_index);
   647 
   648     joystick->instance_id = device->instance_id;
   649     joystick->hwdata = device;
   650     joystick->name = device->product;
   651 
   652     joystick->naxes = device->axes;
   653     joystick->nhats = device->hats;
   654     joystick->nballs = 0;
   655     joystick->nbuttons = device->buttons;
   656     return 0;
   657 }
   658 
   659 /* Function to query if the joystick is currently attached
   660  * It returns SDL_TRUE if attached, SDL_FALSE otherwise.
   661  */
   662 SDL_bool
   663 SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
   664 {
   665     return joystick->hwdata != NULL;
   666 }
   667 
   668 /* Function to update the state of a joystick - called as a device poll.
   669  * This function shouldn't update the joystick structure directly,
   670  * but instead should call SDL_PrivateJoystick*() to deliver events
   671  * and update joystick device state.
   672  */
   673 void
   674 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   675 {
   676     recDevice *device = joystick->hwdata;
   677     recElement *element;
   678     SInt32 value, range;
   679     int i;
   680 
   681     if (!device) {
   682         return;
   683     }
   684 
   685     if (device->removed) {      /* device was unplugged; ignore it. */
   686         if (joystick->hwdata) {
   687             joystick->force_recentering = SDL_TRUE;
   688             joystick->hwdata = NULL;
   689         }
   690         return;
   691     }
   692 
   693     element = device->firstAxis;
   694     i = 0;
   695     while (element) {
   696         value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
   697         SDL_PrivateJoystickAxis(joystick, i, value);
   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         SDL_PrivateJoystickButton(joystick, i, value);
   710         element = element->pNext;
   711         ++i;
   712     }
   713 
   714     element = device->firstHat;
   715     i = 0;
   716     while (element) {
   717         Uint8 pos = 0;
   718 
   719         range = (element->max - element->min + 1);
   720         value = GetHIDElementState(device, element) - element->min;
   721         if (range == 4) {         /* 4 position hatswitch - scale up value */
   722             value *= 2;
   723         } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
   724             value = -1;
   725         }
   726         switch (value) {
   727         case 0:
   728             pos = SDL_HAT_UP;
   729             break;
   730         case 1:
   731             pos = SDL_HAT_RIGHTUP;
   732             break;
   733         case 2:
   734             pos = SDL_HAT_RIGHT;
   735             break;
   736         case 3:
   737             pos = SDL_HAT_RIGHTDOWN;
   738             break;
   739         case 4:
   740             pos = SDL_HAT_DOWN;
   741             break;
   742         case 5:
   743             pos = SDL_HAT_LEFTDOWN;
   744             break;
   745         case 6:
   746             pos = SDL_HAT_LEFT;
   747             break;
   748         case 7:
   749             pos = SDL_HAT_LEFTUP;
   750             break;
   751         default:
   752             /* Every other value is mapped to center. We do that because some
   753              * joysticks use 8 and some 15 for this value, and apparently
   754              * there are even more variants out there - so we try to be generous.
   755              */
   756             pos = SDL_HAT_CENTERED;
   757             break;
   758         }
   759 
   760         SDL_PrivateJoystickHat(joystick, i, pos);
   761 
   762         element = element->pNext;
   763         ++i;
   764     }
   765 }
   766 
   767 /* Function to close a joystick after use */
   768 void
   769 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   770 {
   771 }
   772 
   773 /* Function to perform any system-specific joystick related cleanup */
   774 void
   775 SDL_SYS_JoystickQuit(void)
   776 {
   777     while (FreeDevice(gpDeviceList)) {
   778         /* spin */
   779     }
   780 
   781     if (hidman) {
   782         IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   783         IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
   784         CFRelease(hidman);
   785         hidman = NULL;
   786     }
   787 }
   788 
   789 
   790 SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
   791 {
   792     recDevice *device = GetDeviceForIndex(device_index);
   793     SDL_JoystickGUID guid;
   794     if (device) {
   795         guid = device->guid;
   796     } else {
   797         SDL_zero(guid);
   798     }
   799     return guid;
   800 }
   801 
   802 SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick *joystick)
   803 {
   804     return joystick->hwdata->guid;
   805 }
   806 
   807 #endif /* SDL_JOYSTICK_IOKIT */
   808 
   809 /* vi: set ts=4 sw=4 expandtab: */