src/joystick/darwin/SDL_sysjoystick.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 08 Apr 2011 13:03:26 -0700
changeset 5535 96594ac5fd1a
parent 5262 b530ef003506
child 6138 4c64952a58fb
permissions -rw-r--r--
SDL 1.3 is now under the zlib license.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2011 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_config.h"
    22 
    23 #ifdef SDL_JOYSTICK_IOKIT
    24 
    25 /* SDL joystick driver for Darwin / Mac OS X, based on the IOKit HID API */
    26 /* Written 2001 by Max Horn */
    27 
    28 #include <unistd.h>
    29 #include <ctype.h>
    30 #include <sysexits.h>
    31 #include <mach/mach.h>
    32 #include <mach/mach_error.h>
    33 #include <IOKit/IOKitLib.h>
    34 #include <IOKit/IOCFPlugIn.h>
    35 #ifdef MACOS_10_0_4
    36 #include <IOKit/hidsystem/IOHIDUsageTables.h>
    37 #else
    38 /* The header was moved here in Mac OS X 10.1 */
    39 #include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
    40 #endif
    41 #include <IOKit/hid/IOHIDLib.h>
    42 #include <IOKit/hid/IOHIDKeys.h>
    43 #include <CoreFoundation/CoreFoundation.h>
    44 #include <Carbon/Carbon.h>      /* for NewPtrClear, DisposePtr */
    45 
    46 /* For force feedback testing. */
    47 #include <ForceFeedback/ForceFeedback.h>
    48 #include <ForceFeedback/ForceFeedbackConstants.h>
    49 
    50 #include "SDL_joystick.h"
    51 #include "../SDL_sysjoystick.h"
    52 #include "../SDL_joystick_c.h"
    53 #include "SDL_sysjoystick_c.h"
    54 
    55 
    56 /* Linked list of all available devices */
    57 static recDevice *gpDeviceList = NULL;
    58 
    59 
    60 static void
    61 HIDReportErrorNum(char *strError, long numError)
    62 {
    63     SDL_SetError(strError);
    64 }
    65 
    66 static void HIDGetCollectionElements(CFMutableDictionaryRef deviceProperties,
    67                                      recDevice * pDevice);
    68 
    69 /* returns current value for element, polling element
    70  * will return 0 on error conditions which should be accounted for by application
    71  */
    72 
    73 static SInt32
    74 HIDGetElementValue(recDevice * pDevice, recElement * pElement)
    75 {
    76     IOReturn result = kIOReturnSuccess;
    77     IOHIDEventStruct hidEvent;
    78     hidEvent.value = 0;
    79 
    80     if (NULL != pDevice && NULL != pElement && NULL != pDevice->interface) {
    81         result =
    82             (*(pDevice->interface))->getElementValue(pDevice->interface,
    83                                                      pElement->cookie,
    84                                                      &hidEvent);
    85         if (kIOReturnSuccess == result) {
    86             /* record min and max for auto calibration */
    87             if (hidEvent.value < pElement->minReport)
    88                 pElement->minReport = hidEvent.value;
    89             if (hidEvent.value > pElement->maxReport)
    90                 pElement->maxReport = hidEvent.value;
    91         }
    92     }
    93 
    94     /* auto user scale */
    95     return hidEvent.value;
    96 }
    97 
    98 static SInt32
    99 HIDScaledCalibratedValue(recDevice * pDevice, recElement * pElement,
   100                          long min, long max)
   101 {
   102     float deviceScale = max - min;
   103     float readScale = pElement->maxReport - pElement->minReport;
   104     SInt32 value = HIDGetElementValue(pDevice, pElement);
   105     if (readScale == 0)
   106         return value;           /* no scaling at all */
   107     else
   108         return ((value - pElement->minReport) * deviceScale / readScale) +
   109             min;
   110 }
   111 
   112 
   113 static void
   114 HIDRemovalCallback(void *target, IOReturn result, void *refcon, void *sender)
   115 {
   116     recDevice *device = (recDevice *) refcon;
   117     device->removed = 1;
   118     device->uncentered = 1;
   119 }
   120 
   121 
   122 
   123 /* Create and open an interface to device, required prior to extracting values or building queues.
   124  * Note: appliction now owns the device and must close and release it prior to exiting
   125  */
   126 
   127 static IOReturn
   128 HIDCreateOpenDeviceInterface(io_object_t hidDevice, recDevice * pDevice)
   129 {
   130     IOReturn result = kIOReturnSuccess;
   131     HRESULT plugInResult = S_OK;
   132     SInt32 score = 0;
   133     IOCFPlugInInterface **ppPlugInInterface = NULL;
   134 
   135     if (NULL == pDevice->interface) {
   136         result =
   137             IOCreatePlugInInterfaceForService(hidDevice,
   138                                               kIOHIDDeviceUserClientTypeID,
   139                                               kIOCFPlugInInterfaceID,
   140                                               &ppPlugInInterface, &score);
   141         if (kIOReturnSuccess == result) {
   142             /* Call a method of the intermediate plug-in to create the device interface */
   143             plugInResult =
   144                 (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
   145                                                      CFUUIDGetUUIDBytes
   146                                                      (kIOHIDDeviceInterfaceID),
   147                                                      (void *)
   148                                                      &(pDevice->interface));
   149             if (S_OK != plugInResult)
   150                 HIDReportErrorNum
   151                     ("CouldnŐt query HID class device interface from plugInInterface",
   152                      plugInResult);
   153             (*ppPlugInInterface)->Release(ppPlugInInterface);
   154         } else
   155             HIDReportErrorNum
   156                 ("Failed to create **plugInInterface via IOCreatePlugInInterfaceForService.",
   157                  result);
   158     }
   159     if (NULL != pDevice->interface) {
   160         result = (*(pDevice->interface))->open(pDevice->interface, 0);
   161         if (kIOReturnSuccess != result)
   162             HIDReportErrorNum
   163                 ("Failed to open pDevice->interface via open.", result);
   164         else
   165             (*(pDevice->interface))->setRemovalCallback(pDevice->interface,
   166                                                         HIDRemovalCallback,
   167                                                         pDevice, pDevice);
   168 
   169     }
   170     return result;
   171 }
   172 
   173 /* Closes and releases interface to device, should be done prior to exting application
   174  * Note: will have no affect if device or interface do not exist
   175  * application will "own" the device if interface is not closed
   176  * (device may have to be plug and re-plugged in different location to get it working again without a restart)
   177  */
   178 
   179 static IOReturn
   180 HIDCloseReleaseInterface(recDevice * pDevice)
   181 {
   182     IOReturn result = kIOReturnSuccess;
   183 
   184     if ((NULL != pDevice) && (NULL != pDevice->interface)) {
   185         /* close the interface */
   186         result = (*(pDevice->interface))->close(pDevice->interface);
   187         if (kIOReturnNotOpen == result) {
   188             /* do nothing as device was not opened, thus can't be closed */
   189         } else if (kIOReturnSuccess != result)
   190             HIDReportErrorNum("Failed to close IOHIDDeviceInterface.",
   191                               result);
   192         /* release the interface */
   193         result = (*(pDevice->interface))->Release(pDevice->interface);
   194         if (kIOReturnSuccess != result)
   195             HIDReportErrorNum("Failed to release IOHIDDeviceInterface.",
   196                               result);
   197         pDevice->interface = NULL;
   198     }
   199     return result;
   200 }
   201 
   202 /* extracts actual specific element information from each element CF dictionary entry */
   203 
   204 static void
   205 HIDGetElementInfo(CFTypeRef refElement, recElement * pElement)
   206 {
   207     long number;
   208     CFTypeRef refType;
   209 
   210     refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementCookieKey));
   211     if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
   212         pElement->cookie = (IOHIDElementCookie) number;
   213     refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementMinKey));
   214     if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
   215         pElement->minReport = pElement->min = number;
   216     pElement->maxReport = pElement->min;
   217     refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementMaxKey));
   218     if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
   219         pElement->maxReport = pElement->max = number;
   220 /*
   221 	TODO: maybe should handle the following stuff somehow?
   222 
   223 	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementScaledMinKey));
   224 	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
   225 		pElement->scaledMin = number;
   226 	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementScaledMaxKey));
   227 	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
   228 		pElement->scaledMax = number;
   229 	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementSizeKey));
   230 	if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
   231 		pElement->size = number;
   232 	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsRelativeKey));
   233 	if (refType)
   234 		pElement->relative = CFBooleanGetValue (refType);
   235 	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsWrappingKey));
   236 	if (refType)
   237 		pElement->wrapping = CFBooleanGetValue (refType);
   238 	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsNonLinearKey));
   239 	if (refType)
   240 		pElement->nonLinear = CFBooleanGetValue (refType);
   241 	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasPreferedStateKey));
   242 	if (refType)
   243 		pElement->preferredState = CFBooleanGetValue (refType);
   244 	refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasNullStateKey));
   245 	if (refType)
   246 		pElement->nullState = CFBooleanGetValue (refType);
   247 */
   248 }
   249 
   250 /* examines CF dictionary vlaue in device element hierarchy to determine if it is element of interest or a collection of more elements
   251  * if element of interest allocate storage, add to list and retrieve element specific info
   252  * if collection then pass on to deconstruction collection into additional individual elements
   253  */
   254 
   255 static void
   256 HIDAddElement(CFTypeRef refElement, recDevice * pDevice)
   257 {
   258     recElement *element = NULL;
   259     recElement **headElement = NULL;
   260     long elementType, usagePage, usage;
   261     CFTypeRef refElementType =
   262         CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementTypeKey));
   263     CFTypeRef refUsagePage =
   264         CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementUsagePageKey));
   265     CFTypeRef refUsage =
   266         CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementUsageKey));
   267 
   268 
   269     if ((refElementType)
   270         &&
   271         (CFNumberGetValue(refElementType, kCFNumberLongType, &elementType))) {
   272         /* look at types of interest */
   273         if ((elementType == kIOHIDElementTypeInput_Misc)
   274             || (elementType == kIOHIDElementTypeInput_Button)
   275             || (elementType == kIOHIDElementTypeInput_Axis)) {
   276             if (refUsagePage
   277                 && CFNumberGetValue(refUsagePage, kCFNumberLongType,
   278                                     &usagePage) && refUsage
   279                 && CFNumberGetValue(refUsage, kCFNumberLongType, &usage)) {
   280                 switch (usagePage) {    /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
   281                 case kHIDPage_GenericDesktop:
   282                     {
   283                         switch (usage) {        /* look at usage to determine function */
   284                         case kHIDUsage_GD_X:
   285                         case kHIDUsage_GD_Y:
   286                         case kHIDUsage_GD_Z:
   287                         case kHIDUsage_GD_Rx:
   288                         case kHIDUsage_GD_Ry:
   289                         case kHIDUsage_GD_Rz:
   290                         case kHIDUsage_GD_Slider:
   291                         case kHIDUsage_GD_Dial:
   292                         case kHIDUsage_GD_Wheel:
   293                             element = (recElement *)
   294                                 NewPtrClear(sizeof(recElement));
   295                             if (element) {
   296                                 pDevice->axes++;
   297                                 headElement = &(pDevice->firstAxis);
   298                             }
   299                             break;
   300                         case kHIDUsage_GD_Hatswitch:
   301                             element = (recElement *)
   302                                 NewPtrClear(sizeof(recElement));
   303                             if (element) {
   304                                 pDevice->hats++;
   305                                 headElement = &(pDevice->firstHat);
   306                             }
   307                             break;
   308                         }
   309                     }
   310                     break;
   311                 case kHIDPage_Button:
   312                     element = (recElement *)
   313                         NewPtrClear(sizeof(recElement));
   314                     if (element) {
   315                         pDevice->buttons++;
   316                         headElement = &(pDevice->firstButton);
   317                     }
   318                     break;
   319                 default:
   320                     break;
   321                 }
   322             }
   323         } else if (kIOHIDElementTypeCollection == elementType)
   324             HIDGetCollectionElements((CFMutableDictionaryRef) refElement,
   325                                      pDevice);
   326     }
   327 
   328     if (element && headElement) {       /* add to list */
   329         recElement *elementPrevious = NULL;
   330         recElement *elementCurrent = *headElement;
   331         while (elementCurrent && usage >= elementCurrent->usage) {
   332             elementPrevious = elementCurrent;
   333             elementCurrent = elementCurrent->pNext;
   334         }
   335         if (elementPrevious) {
   336             elementPrevious->pNext = element;
   337         } else {
   338             *headElement = element;
   339         }
   340         element->usagePage = usagePage;
   341         element->usage = usage;
   342         element->pNext = elementCurrent;
   343         HIDGetElementInfo(refElement, element);
   344         pDevice->elements++;
   345     }
   346 }
   347 
   348 /* collects information from each array member in device element list (each array memeber = element) */
   349 
   350 static void
   351 HIDGetElementsCFArrayHandler(const void *value, void *parameter)
   352 {
   353     if (CFGetTypeID(value) == CFDictionaryGetTypeID())
   354         HIDAddElement((CFTypeRef) value, (recDevice *) parameter);
   355 }
   356 
   357 /* handles retrieval of element information from arrays of elements in device IO registry information */
   358 
   359 static void
   360 HIDGetElements(CFTypeRef refElementCurrent, recDevice * pDevice)
   361 {
   362     CFTypeID type = CFGetTypeID(refElementCurrent);
   363     if (type == CFArrayGetTypeID()) {   /* if element is an array */
   364         CFRange range = { 0, CFArrayGetCount(refElementCurrent) };
   365         /* CountElementsCFArrayHandler called for each array member */
   366         CFArrayApplyFunction(refElementCurrent, range,
   367                              HIDGetElementsCFArrayHandler, pDevice);
   368     }
   369 }
   370 
   371 /* handles extracting element information from element collection CF types
   372  * used from top level element decoding and hierarchy deconstruction to flatten device element list
   373  */
   374 
   375 static void
   376 HIDGetCollectionElements(CFMutableDictionaryRef deviceProperties,
   377                          recDevice * pDevice)
   378 {
   379     CFTypeRef refElementTop =
   380         CFDictionaryGetValue(deviceProperties, CFSTR(kIOHIDElementKey));
   381     if (refElementTop)
   382         HIDGetElements(refElementTop, pDevice);
   383 }
   384 
   385 /* use top level element usage page and usage to discern device usage page and usage setting appropriate vlaues in device record */
   386 
   387 static void
   388 HIDTopLevelElementHandler(const void *value, void *parameter)
   389 {
   390     CFTypeRef refCF = 0;
   391     if (CFGetTypeID(value) != CFDictionaryGetTypeID())
   392         return;
   393     refCF = CFDictionaryGetValue(value, CFSTR(kIOHIDElementUsagePageKey));
   394     if (!CFNumberGetValue
   395         (refCF, kCFNumberLongType, &((recDevice *) parameter)->usagePage))
   396         SDL_SetError("CFNumberGetValue error retrieving pDevice->usagePage.");
   397     refCF = CFDictionaryGetValue(value, CFSTR(kIOHIDElementUsageKey));
   398     if (!CFNumberGetValue
   399         (refCF, kCFNumberLongType, &((recDevice *) parameter)->usage))
   400         SDL_SetError("CFNumberGetValue error retrieving pDevice->usage.");
   401 }
   402 
   403 /* extracts device info from CF dictionary records in IO registry */
   404 
   405 static void
   406 HIDGetDeviceInfo(io_object_t hidDevice, CFMutableDictionaryRef hidProperties,
   407                  recDevice * pDevice)
   408 {
   409     CFMutableDictionaryRef usbProperties = 0;
   410     io_registry_entry_t parent1, parent2;
   411 
   412     /* Mac OS X currently is not mirroring all USB properties to HID page so need to look at USB device page also
   413      * get dictionary for usb properties: step up two levels and get CF dictionary for USB properties
   414      */
   415     if ((KERN_SUCCESS ==
   416          IORegistryEntryGetParentEntry(hidDevice, kIOServicePlane, &parent1))
   417         && (KERN_SUCCESS ==
   418             IORegistryEntryGetParentEntry(parent1, kIOServicePlane, &parent2))
   419         && (KERN_SUCCESS ==
   420             IORegistryEntryCreateCFProperties(parent2, &usbProperties,
   421                                               kCFAllocatorDefault,
   422                                               kNilOptions))) {
   423         if (usbProperties) {
   424             CFTypeRef refCF = 0;
   425             /* get device info
   426              * try hid dictionary first, if fail then go to usb dictionary
   427              */
   428 
   429 
   430             /* get product name */
   431             refCF =
   432                 CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductKey));
   433             if (!refCF)
   434                 refCF =
   435                     CFDictionaryGetValue(usbProperties,
   436                                          CFSTR("USB Product Name"));
   437             if (refCF) {
   438                 if (!CFStringGetCString
   439                     (refCF, pDevice->product, 256,
   440                      CFStringGetSystemEncoding()))
   441                     SDL_SetError
   442                         ("CFStringGetCString error retrieving pDevice->product.");
   443             }
   444 
   445             /* get usage page and usage */
   446             refCF =
   447                 CFDictionaryGetValue(hidProperties,
   448                                      CFSTR(kIOHIDPrimaryUsagePageKey));
   449             if (refCF) {
   450                 if (!CFNumberGetValue
   451                     (refCF, kCFNumberLongType, &pDevice->usagePage))
   452                     SDL_SetError
   453                         ("CFNumberGetValue error retrieving pDevice->usagePage.");
   454                 refCF =
   455                     CFDictionaryGetValue(hidProperties,
   456                                          CFSTR(kIOHIDPrimaryUsageKey));
   457                 if (refCF)
   458                     if (!CFNumberGetValue
   459                         (refCF, kCFNumberLongType, &pDevice->usage))
   460                         SDL_SetError
   461                             ("CFNumberGetValue error retrieving pDevice->usage.");
   462             }
   463 
   464             if (NULL == refCF) {        /* get top level element HID usage page or usage */
   465                 /* use top level element instead */
   466                 CFTypeRef refCFTopElement = 0;
   467                 refCFTopElement =
   468                     CFDictionaryGetValue(hidProperties,
   469                                          CFSTR(kIOHIDElementKey));
   470                 {
   471                     /* refCFTopElement points to an array of element dictionaries */
   472                     CFRange range = { 0, CFArrayGetCount(refCFTopElement) };
   473                     CFArrayApplyFunction(refCFTopElement, range,
   474                                          HIDTopLevelElementHandler, pDevice);
   475                 }
   476             }
   477 
   478             CFRelease(usbProperties);
   479         } else
   480             SDL_SetError
   481                 ("IORegistryEntryCreateCFProperties failed to create usbProperties.");
   482 
   483         if (kIOReturnSuccess != IOObjectRelease(parent2))
   484             SDL_SetError("IOObjectRelease error with parent2.");
   485         if (kIOReturnSuccess != IOObjectRelease(parent1))
   486             SDL_SetError("IOObjectRelease error with parent1.");
   487     }
   488 }
   489 
   490 
   491 static recDevice *
   492 HIDBuildDevice(io_object_t hidDevice)
   493 {
   494     recDevice *pDevice = (recDevice *) NewPtrClear(sizeof(recDevice));
   495     if (pDevice) {
   496         /* get dictionary for HID properties */
   497         CFMutableDictionaryRef hidProperties = 0;
   498         kern_return_t result =
   499             IORegistryEntryCreateCFProperties(hidDevice, &hidProperties,
   500                                               kCFAllocatorDefault,
   501                                               kNilOptions);
   502         if ((result == KERN_SUCCESS) && hidProperties) {
   503             /* create device interface */
   504             result = HIDCreateOpenDeviceInterface(hidDevice, pDevice);
   505             if (kIOReturnSuccess == result) {
   506                 HIDGetDeviceInfo(hidDevice, hidProperties, pDevice);    /* hidDevice used to find parents in registry tree */
   507                 HIDGetCollectionElements(hidProperties, pDevice);
   508             } else {
   509                 DisposePtr((Ptr) pDevice);
   510                 pDevice = NULL;
   511             }
   512             CFRelease(hidProperties);
   513         } else {
   514             DisposePtr((Ptr) pDevice);
   515             pDevice = NULL;
   516         }
   517     }
   518     return pDevice;
   519 }
   520 
   521 /* disposes of the element list associated with a device and the memory associated with the list
   522  */
   523 
   524 static void
   525 HIDDisposeElementList(recElement ** elementList)
   526 {
   527     recElement *pElement = *elementList;
   528     while (pElement) {
   529         recElement *pElementNext = pElement->pNext;
   530         DisposePtr((Ptr) pElement);
   531         pElement = pElementNext;
   532     }
   533     *elementList = NULL;
   534 }
   535 
   536 /* disposes of a single device, closing and releaseing interface, freeing memory fro device and elements, setting device pointer to NULL
   537  * all your device no longer belong to us... (i.e., you do not 'own' the device anymore)
   538  */
   539 
   540 static recDevice *
   541 HIDDisposeDevice(recDevice ** ppDevice)
   542 {
   543     kern_return_t result = KERN_SUCCESS;
   544     recDevice *pDeviceNext = NULL;
   545     if (*ppDevice) {
   546         /* save next device prior to disposing of this device */
   547         pDeviceNext = (*ppDevice)->pNext;
   548 
   549         /* free posible io_service_t */
   550         if ((*ppDevice)->ffservice) {
   551             IOObjectRelease((*ppDevice)->ffservice);
   552             (*ppDevice)->ffservice = 0;
   553         }
   554 
   555         /* free element lists */
   556         HIDDisposeElementList(&(*ppDevice)->firstAxis);
   557         HIDDisposeElementList(&(*ppDevice)->firstButton);
   558         HIDDisposeElementList(&(*ppDevice)->firstHat);
   559 
   560         result = HIDCloseReleaseInterface(*ppDevice);   /* function sanity checks interface value (now application does not own device) */
   561         if (kIOReturnSuccess != result)
   562             HIDReportErrorNum
   563                 ("HIDCloseReleaseInterface failed when trying to dipose device.",
   564                  result);
   565         DisposePtr((Ptr) * ppDevice);
   566         *ppDevice = NULL;
   567     }
   568     return pDeviceNext;
   569 }
   570 
   571 
   572 /* Function to scan the system for joysticks.
   573  * Joystick 0 should be the system default joystick.
   574  * This function should return the number of available joysticks, or -1
   575  * on an unrecoverable fatal error.
   576  */
   577 int
   578 SDL_SYS_JoystickInit(void)
   579 {
   580     IOReturn result = kIOReturnSuccess;
   581     mach_port_t masterPort = 0;
   582     io_iterator_t hidObjectIterator = 0;
   583     CFMutableDictionaryRef hidMatchDictionary = NULL;
   584     recDevice *device, *lastDevice;
   585     io_object_t ioHIDDeviceObject = 0;
   586 
   587     SDL_numjoysticks = 0;
   588 
   589     if (gpDeviceList) {
   590         SDL_SetError("Joystick: Device list already inited.");
   591         return -1;
   592     }
   593 
   594     result = IOMasterPort(bootstrap_port, &masterPort);
   595     if (kIOReturnSuccess != result) {
   596         SDL_SetError("Joystick: IOMasterPort error with bootstrap_port.");
   597         return -1;
   598     }
   599 
   600     /* Set up a matching dictionary to search I/O Registry by class name for all HID class devices. */
   601     hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey);
   602     if (hidMatchDictionary) {
   603         /* Add key for device type (joystick, in this case) to refine the matching dictionary. */
   604 
   605         /* NOTE: we now perform this filtering later
   606            UInt32 usagePage = kHIDPage_GenericDesktop;
   607            UInt32 usage = kHIDUsage_GD_Joystick;
   608            CFNumberRef refUsage = NULL, refUsagePage = NULL;
   609 
   610            refUsage = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &usage);
   611            CFDictionarySetValue (hidMatchDictionary, CFSTR (kIOHIDPrimaryUsageKey), refUsage);
   612            refUsagePage = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &usagePage);
   613            CFDictionarySetValue (hidMatchDictionary, CFSTR (kIOHIDPrimaryUsagePageKey), refUsagePage);
   614          */
   615     } else {
   616         SDL_SetError
   617             ("Joystick: Failed to get HID CFMutableDictionaryRef via IOServiceMatching.");
   618         return -1;
   619     }
   620 
   621     /*/ Now search I/O Registry for matching devices. */
   622     result =
   623         IOServiceGetMatchingServices(masterPort, hidMatchDictionary,
   624                                      &hidObjectIterator);
   625     /* Check for errors */
   626     if (kIOReturnSuccess != result) {
   627         SDL_SetError("Joystick: Couldn't create a HID object iterator.");
   628         return -1;
   629     }
   630     if (!hidObjectIterator) {   /* there are no joysticks */
   631         gpDeviceList = NULL;
   632         SDL_numjoysticks = 0;
   633         return 0;
   634     }
   635     /* IOServiceGetMatchingServices consumes a reference to the dictionary, so we don't need to release the dictionary ref. */
   636 
   637     /* build flat linked list of devices from device iterator */
   638 
   639     gpDeviceList = lastDevice = NULL;
   640 
   641     while ((ioHIDDeviceObject = IOIteratorNext(hidObjectIterator))) {
   642         /* build a device record */
   643         device = HIDBuildDevice(ioHIDDeviceObject);
   644         if (!device)
   645             continue;
   646 
   647         /* Filter device list to non-keyboard/mouse stuff */
   648         if ((device->usagePage != kHIDPage_GenericDesktop) ||
   649             ((device->usage != kHIDUsage_GD_Joystick &&
   650               device->usage != kHIDUsage_GD_GamePad &&
   651               device->usage != kHIDUsage_GD_MultiAxisController))) {
   652 
   653             /* release memory for the device */
   654             HIDDisposeDevice(&device);
   655             DisposePtr((Ptr) device);
   656             continue;
   657         }
   658 
   659         /* We have to do some storage of the io_service_t for
   660          * SDL_HapticOpenFromJoystick */
   661         if (FFIsForceFeedback(ioHIDDeviceObject) == FF_OK) {
   662             device->ffservice = ioHIDDeviceObject;
   663         } else {
   664             device->ffservice = 0;
   665         }
   666 
   667         /* Add device to the end of the list */
   668         if (lastDevice)
   669             lastDevice->pNext = device;
   670         else
   671             gpDeviceList = device;
   672         lastDevice = device;
   673     }
   674     result = IOObjectRelease(hidObjectIterator);        /* release the iterator */
   675 
   676     /* Count the total number of devices we found */
   677     device = gpDeviceList;
   678     while (device) {
   679         SDL_numjoysticks++;
   680         device = device->pNext;
   681     }
   682 
   683     return SDL_numjoysticks;
   684 }
   685 
   686 /* Function to get the device-dependent name of a joystick */
   687 const char *
   688 SDL_SYS_JoystickName(int index)
   689 {
   690     recDevice *device = gpDeviceList;
   691 
   692     for (; index > 0; index--)
   693         device = device->pNext;
   694 
   695     return device->product;
   696 }
   697 
   698 /* Function to open a joystick for use.
   699  * The joystick to open is specified by the index field of the joystick.
   700  * This should fill the nbuttons and naxes fields of the joystick structure.
   701  * It returns 0, or -1 if there is an error.
   702  */
   703 int
   704 SDL_SYS_JoystickOpen(SDL_Joystick * joystick)
   705 {
   706     recDevice *device = gpDeviceList;
   707     int index;
   708 
   709     for (index = joystick->index; index > 0; index--)
   710         device = device->pNext;
   711 
   712     joystick->hwdata = device;
   713     joystick->name = device->product;
   714 
   715     joystick->naxes = device->axes;
   716     joystick->nhats = device->hats;
   717     joystick->nballs = 0;
   718     joystick->nbuttons = device->buttons;
   719 
   720     return 0;
   721 }
   722 
   723 /* Function to update the state of a joystick - called as a device poll.
   724  * This function shouldn't update the joystick structure directly,
   725  * but instead should call SDL_PrivateJoystick*() to deliver events
   726  * and update joystick device state.
   727  */
   728 void
   729 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   730 {
   731     recDevice *device = joystick->hwdata;
   732     recElement *element;
   733     SInt32 value, range;
   734     int i;
   735 
   736     if (device->removed) {      /* device was unplugged; ignore it. */
   737         if (device->uncentered) {
   738             device->uncentered = 0;
   739 
   740             /* Tell the app that everything is centered/unpressed... */
   741             for (i = 0; i < device->axes; i++)
   742                 SDL_PrivateJoystickAxis(joystick, i, 0);
   743 
   744             for (i = 0; i < device->buttons; i++)
   745                 SDL_PrivateJoystickButton(joystick, i, 0);
   746 
   747             for (i = 0; i < device->hats; i++)
   748                 SDL_PrivateJoystickHat(joystick, i, SDL_HAT_CENTERED);
   749         }
   750 
   751         return;
   752     }
   753 
   754     element = device->firstAxis;
   755     i = 0;
   756     while (element) {
   757         value = HIDScaledCalibratedValue(device, element, -32768, 32767);
   758         if (value != joystick->axes[i])
   759             SDL_PrivateJoystickAxis(joystick, i, value);
   760         element = element->pNext;
   761         ++i;
   762     }
   763 
   764     element = device->firstButton;
   765     i = 0;
   766     while (element) {
   767         value = HIDGetElementValue(device, element);
   768         if (value > 1)          /* handle pressure-sensitive buttons */
   769             value = 1;
   770         if (value != joystick->buttons[i])
   771             SDL_PrivateJoystickButton(joystick, i, value);
   772         element = element->pNext;
   773         ++i;
   774     }
   775 
   776     element = device->firstHat;
   777     i = 0;
   778     while (element) {
   779         Uint8 pos = 0;
   780 
   781         range = (element->max - element->min + 1);
   782         value = HIDGetElementValue(device, element) - element->min;
   783         if (range == 4)         /* 4 position hatswitch - scale up value */
   784             value *= 2;
   785         else if (range != 8)    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
   786             value = -1;
   787         switch (value) {
   788         case 0:
   789             pos = SDL_HAT_UP;
   790             break;
   791         case 1:
   792             pos = SDL_HAT_RIGHTUP;
   793             break;
   794         case 2:
   795             pos = SDL_HAT_RIGHT;
   796             break;
   797         case 3:
   798             pos = SDL_HAT_RIGHTDOWN;
   799             break;
   800         case 4:
   801             pos = SDL_HAT_DOWN;
   802             break;
   803         case 5:
   804             pos = SDL_HAT_LEFTDOWN;
   805             break;
   806         case 6:
   807             pos = SDL_HAT_LEFT;
   808             break;
   809         case 7:
   810             pos = SDL_HAT_LEFTUP;
   811             break;
   812         default:
   813             /* Every other value is mapped to center. We do that because some
   814              * joysticks use 8 and some 15 for this value, and apparently
   815              * there are even more variants out there - so we try to be generous.
   816              */
   817             pos = SDL_HAT_CENTERED;
   818             break;
   819         }
   820         if (pos != joystick->hats[i])
   821             SDL_PrivateJoystickHat(joystick, i, pos);
   822         element = element->pNext;
   823         ++i;
   824     }
   825 
   826     return;
   827 }
   828 
   829 /* Function to close a joystick after use */
   830 void
   831 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   832 {
   833     /* Should we do anything here? */
   834     return;
   835 }
   836 
   837 /* Function to perform any system-specific joystick related cleanup */
   838 void
   839 SDL_SYS_JoystickQuit(void)
   840 {
   841     while (NULL != gpDeviceList)
   842         gpDeviceList = HIDDisposeDevice(&gpDeviceList);
   843 }
   844 
   845 #endif /* SDL_JOYSTICK_IOKIT */
   846 /* vi: set ts=4 sw=4 expandtab: */