src/joystick/darwin/SDL_sysjoystick.c
author David Ludwig <dludwig@pobox.com>
Tue, 24 Dec 2013 21:28:31 -0500
changeset 8552 abd934eac415
parent 7947 f5f51b3c4dd2
child 8093 b43765095a6f
permissions -rw-r--r--
WinRT: moved ill-performing XInput device-detection calls to a separate thread
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 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 #include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
    36 #include <IOKit/hid/IOHIDLib.h>
    37 #include <IOKit/hid/IOHIDKeys.h>
    38 #include <CoreFoundation/CoreFoundation.h>
    39 #include <Carbon/Carbon.h>      /* for NewPtrClear, DisposePtr */
    40 #include <IOKit/IOMessage.h>
    41 
    42 /* For force feedback testing. */
    43 #include <ForceFeedback/ForceFeedback.h>
    44 #include <ForceFeedback/ForceFeedbackConstants.h>
    45 
    46 #include "SDL_joystick.h"
    47 #include "../SDL_sysjoystick.h"
    48 #include "../SDL_joystick_c.h"
    49 #include "SDL_sysjoystick_c.h"
    50 #include "SDL_events.h"
    51 #if !SDL_EVENTS_DISABLED
    52 #include "../../events/SDL_events_c.h"
    53 #endif
    54 
    55 
    56 /* Linked list of all available devices */
    57 static recDevice *gpDeviceList = NULL;
    58 /* OSX reference to the notification object that tells us about device insertion/removal */
    59 IONotificationPortRef notificationPort = 0;
    60 /* if 1 then a device was added since the last update call */
    61 static SDL_bool s_bDeviceAdded = SDL_FALSE;
    62 static SDL_bool s_bDeviceRemoved = SDL_FALSE;
    63 
    64 /* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
    65 static int s_joystick_instance_id = -1;
    66 
    67 static void
    68 HIDReportErrorNum(char *strError, long numError)
    69 {
    70     SDL_SetError(strError);
    71 }
    72 
    73 static void HIDGetCollectionElements(CFMutableDictionaryRef deviceProperties,
    74                                      recDevice * pDevice);
    75 
    76 /* returns current value for element, polling element
    77  * will return 0 on error conditions which should be accounted for by application
    78  */
    79 
    80 static SInt32
    81 HIDGetElementValue(recDevice * pDevice, recElement * pElement)
    82 {
    83     IOReturn result = kIOReturnSuccess;
    84     IOHIDEventStruct hidEvent;
    85     hidEvent.value = 0;
    86 
    87     if (NULL != pDevice && NULL != pElement && NULL != pDevice->interface) {
    88         result =
    89             (*(pDevice->interface))->getElementValue(pDevice->interface,
    90                                                      pElement->cookie,
    91                                                      &hidEvent);
    92         if (kIOReturnSuccess == result) {
    93             /* record min and max for auto calibration */
    94             if (hidEvent.value < pElement->minReport)
    95                 pElement->minReport = hidEvent.value;
    96             if (hidEvent.value > pElement->maxReport)
    97                 pElement->maxReport = hidEvent.value;
    98         }
    99     }
   100 
   101     /* auto user scale */
   102     return hidEvent.value;
   103 }
   104 
   105 static SInt32
   106 HIDScaledCalibratedValue(recDevice * pDevice, recElement * pElement,
   107                          long min, long max)
   108 {
   109     float deviceScale = max - min;
   110     float readScale = pElement->maxReport - pElement->minReport;
   111     SInt32 value = HIDGetElementValue(pDevice, pElement);
   112     if (readScale == 0)
   113         return value;           /* no scaling at all */
   114     else
   115         return ((value - pElement->minReport) * deviceScale / readScale) +
   116             min;
   117 }
   118 
   119 
   120 static void
   121 HIDRemovalCallback(void *target, IOReturn result, void *refcon, void *sender)
   122 {
   123     recDevice *device = (recDevice *) refcon;
   124     device->removed = 1;
   125     s_bDeviceRemoved = SDL_TRUE;
   126 }
   127 
   128 
   129 /* Called by the io port notifier on removal of this device
   130  */
   131 void JoystickDeviceWasRemovedCallback( void * refcon, io_service_t service, natural_t messageType, void * messageArgument )
   132 {
   133     if( messageType == kIOMessageServiceIsTerminated && refcon )
   134     {
   135         recDevice *device = (recDevice *) refcon;
   136         device->removed = 1;
   137         s_bDeviceRemoved = SDL_TRUE;
   138     }
   139 }
   140 
   141 
   142 /* Create and open an interface to device, required prior to extracting values or building queues.
   143  * Note: application now owns the device and must close and release it prior to exiting
   144  */
   145 
   146 static IOReturn
   147 HIDCreateOpenDeviceInterface(io_object_t hidDevice, recDevice * pDevice)
   148 {
   149     IOReturn result = kIOReturnSuccess;
   150     HRESULT plugInResult = S_OK;
   151     SInt32 score = 0;
   152     IOCFPlugInInterface **ppPlugInInterface = NULL;
   153 
   154     if (NULL == pDevice->interface) {
   155         result =
   156             IOCreatePlugInInterfaceForService(hidDevice,
   157                                               kIOHIDDeviceUserClientTypeID,
   158                                               kIOCFPlugInInterfaceID,
   159                                               &ppPlugInInterface, &score);
   160         if (kIOReturnSuccess == result) {
   161             /* Call a method of the intermediate plug-in to create the device interface */
   162             plugInResult =
   163                 (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
   164                                                      CFUUIDGetUUIDBytes
   165                                                      (kIOHIDDeviceInterfaceID),
   166                                                      (void *)
   167                                                      &(pDevice->interface));
   168             if (S_OK != plugInResult)
   169                 HIDReportErrorNum
   170                     ("Couldn't query HID class device interface from plugInInterface",
   171                      plugInResult);
   172             (*ppPlugInInterface)->Release(ppPlugInInterface);
   173         } else
   174             HIDReportErrorNum
   175                 ("Failed to create **plugInInterface via IOCreatePlugInInterfaceForService.",
   176                  result);
   177     }
   178     if (NULL != pDevice->interface) {
   179         result = (*(pDevice->interface))->open(pDevice->interface, 0);
   180         if (kIOReturnSuccess != result)
   181             HIDReportErrorNum
   182                 ("Failed to open pDevice->interface via open.", result);
   183         else
   184         {
   185             pDevice->portIterator = 0;
   186 
   187             /* It's okay if this fails, we have another detection method below */
   188             (*(pDevice->interface))->setRemovalCallback(pDevice->interface,
   189                                                         HIDRemovalCallback,
   190                                                         pDevice, pDevice);
   191 
   192             /* now connect notification for new devices */
   193             pDevice->notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
   194 
   195             CFRunLoopAddSource(CFRunLoopGetCurrent(),
   196                                IONotificationPortGetRunLoopSource(pDevice->notificationPort),
   197                                kCFRunLoopDefaultMode);
   198 
   199             /* Register for notifications when a serial port is added to the system */
   200             result = IOServiceAddInterestNotification(pDevice->notificationPort,
   201                                                       hidDevice,
   202                                                       kIOGeneralInterest,
   203                                                       JoystickDeviceWasRemovedCallback,
   204                                                       pDevice,
   205                                                       &pDevice->portIterator);
   206             if (kIOReturnSuccess != result) {
   207                 HIDReportErrorNum
   208                     ("Failed to register for removal callback.", result);
   209             }
   210         }
   211 
   212     }
   213     return result;
   214 }
   215 
   216 /* Closes and releases interface to device, should be done prior to exiting application
   217  * Note: will have no affect if device or interface do not exist
   218  * application will "own" the device if interface is not closed
   219  * (device may have to be plug and re-plugged in different location to get it working again without a restart)
   220  */
   221 
   222 static IOReturn
   223 HIDCloseReleaseInterface(recDevice * pDevice)
   224 {
   225     IOReturn result = kIOReturnSuccess;
   226 
   227     if ((NULL != pDevice) && (NULL != pDevice->interface)) {
   228         /* close the interface */
   229         result = (*(pDevice->interface))->close(pDevice->interface);
   230         if (kIOReturnNotOpen == result) {
   231             /* do nothing as device was not opened, thus can't be closed */
   232         } else if (kIOReturnSuccess != result)
   233             HIDReportErrorNum("Failed to close IOHIDDeviceInterface.",
   234                               result);
   235         /* release the interface */
   236         result = (*(pDevice->interface))->Release(pDevice->interface);
   237         if (kIOReturnSuccess != result)
   238             HIDReportErrorNum("Failed to release IOHIDDeviceInterface.",
   239                               result);
   240         pDevice->interface = NULL;
   241 
   242         if ( pDevice->portIterator )
   243         {
   244             IOObjectRelease( pDevice->portIterator );
   245             pDevice->portIterator = 0;
   246         }
   247     }
   248     return result;
   249 }
   250 
   251 /* extracts actual specific element information from each element CF dictionary entry */
   252 
   253 static void
   254 HIDGetElementInfo(CFTypeRef refElement, recElement * pElement)
   255 {
   256     long number;
   257     CFTypeRef refType;
   258 
   259     refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementCookieKey));
   260     if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
   261         pElement->cookie = (IOHIDElementCookie) number;
   262     refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementMinKey));
   263     if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
   264         pElement->minReport = pElement->min = number;
   265     pElement->maxReport = pElement->min;
   266     refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementMaxKey));
   267     if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number))
   268         pElement->maxReport = pElement->max = number;
   269 /*
   270     TODO: maybe should handle the following stuff somehow?
   271 
   272     refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementScaledMinKey));
   273     if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
   274         pElement->scaledMin = number;
   275     refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementScaledMaxKey));
   276     if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
   277         pElement->scaledMax = number;
   278     refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementSizeKey));
   279     if (refType && CFNumberGetValue (refType, kCFNumberLongType, &number))
   280         pElement->size = number;
   281     refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsRelativeKey));
   282     if (refType)
   283         pElement->relative = CFBooleanGetValue (refType);
   284     refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsWrappingKey));
   285     if (refType)
   286         pElement->wrapping = CFBooleanGetValue (refType);
   287     refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementIsNonLinearKey));
   288     if (refType)
   289         pElement->nonLinear = CFBooleanGetValue (refType);
   290     refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasPreferedStateKey));
   291     if (refType)
   292         pElement->preferredState = CFBooleanGetValue (refType);
   293     refType = CFDictionaryGetValue (refElement, CFSTR(kIOHIDElementHasNullStateKey));
   294     if (refType)
   295         pElement->nullState = CFBooleanGetValue (refType);
   296 */
   297 }
   298 
   299 /* examines CF dictionary value in device element hierarchy to determine if it is element of interest or a collection of more elements
   300  * if element of interest allocate storage, add to list and retrieve element specific info
   301  * if collection then pass on to deconstruction collection into additional individual elements
   302  */
   303 
   304 static void
   305 HIDAddElement(CFTypeRef refElement, recDevice * pDevice)
   306 {
   307     recElement *element = NULL;
   308     recElement **headElement = NULL;
   309     long elementType, usagePage, usage;
   310     CFTypeRef refElementType =
   311         CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementTypeKey));
   312     CFTypeRef refUsagePage =
   313         CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementUsagePageKey));
   314     CFTypeRef refUsage =
   315         CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementUsageKey));
   316 
   317 
   318     if ((refElementType)
   319         &&
   320         (CFNumberGetValue(refElementType, kCFNumberLongType, &elementType))) {
   321         /* look at types of interest */
   322         if ((elementType == kIOHIDElementTypeInput_Misc)
   323             || (elementType == kIOHIDElementTypeInput_Button)
   324             || (elementType == kIOHIDElementTypeInput_Axis)) {
   325             if (refUsagePage
   326                 && CFNumberGetValue(refUsagePage, kCFNumberLongType,
   327                                     &usagePage) && refUsage
   328                 && CFNumberGetValue(refUsage, kCFNumberLongType, &usage)) {
   329                 switch (usagePage) {    /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
   330                 case kHIDPage_GenericDesktop:
   331                     {
   332                         switch (usage) {        /* look at usage to determine function */
   333                         case kHIDUsage_GD_X:
   334                         case kHIDUsage_GD_Y:
   335                         case kHIDUsage_GD_Z:
   336                         case kHIDUsage_GD_Rx:
   337                         case kHIDUsage_GD_Ry:
   338                         case kHIDUsage_GD_Rz:
   339                         case kHIDUsage_GD_Slider:
   340                         case kHIDUsage_GD_Dial:
   341                         case kHIDUsage_GD_Wheel:
   342                             element = (recElement *)
   343                                 NewPtrClear(sizeof(recElement));
   344                             if (element) {
   345                                 pDevice->axes++;
   346                                 headElement = &(pDevice->firstAxis);
   347                             }
   348                             break;
   349                         case kHIDUsage_GD_Hatswitch:
   350                             element = (recElement *)
   351                                 NewPtrClear(sizeof(recElement));
   352                             if (element) {
   353                                 pDevice->hats++;
   354                                 headElement = &(pDevice->firstHat);
   355                             }
   356                             break;
   357                         }
   358                     }
   359                     break;
   360                 case kHIDPage_Simulation:
   361                     switch (usage) {
   362                         case kHIDUsage_Sim_Rudder:
   363                         case kHIDUsage_Sim_Throttle:
   364                             element = (recElement *)
   365                                 NewPtrClear(sizeof(recElement));
   366                             if (element) {
   367                                 pDevice->axes++;
   368                                 headElement = &(pDevice->firstAxis);
   369                             }
   370                             break;
   371 
   372                         default:
   373                             break;
   374                     }
   375                     break;
   376                 case kHIDPage_Button:
   377                     element = (recElement *)
   378                         NewPtrClear(sizeof(recElement));
   379                     if (element) {
   380                         pDevice->buttons++;
   381                         headElement = &(pDevice->firstButton);
   382                     }
   383                     break;
   384                 default:
   385                     break;
   386                 }
   387             }
   388         } else if (kIOHIDElementTypeCollection == elementType)
   389             HIDGetCollectionElements((CFMutableDictionaryRef) refElement,
   390                                      pDevice);
   391     }
   392 
   393     if (element && headElement) {       /* add to list */
   394         recElement *elementPrevious = NULL;
   395         recElement *elementCurrent = *headElement;
   396         while (elementCurrent && usage >= elementCurrent->usage) {
   397             elementPrevious = elementCurrent;
   398             elementCurrent = elementCurrent->pNext;
   399         }
   400         if (elementPrevious) {
   401             elementPrevious->pNext = element;
   402         } else {
   403             *headElement = element;
   404         }
   405         element->usagePage = usagePage;
   406         element->usage = usage;
   407         element->pNext = elementCurrent;
   408         HIDGetElementInfo(refElement, element);
   409         pDevice->elements++;
   410     }
   411 }
   412 
   413 /* collects information from each array member in device element list (each array member = element) */
   414 
   415 static void
   416 HIDGetElementsCFArrayHandler(const void *value, void *parameter)
   417 {
   418     if (CFGetTypeID(value) == CFDictionaryGetTypeID())
   419         HIDAddElement((CFTypeRef) value, (recDevice *) parameter);
   420 }
   421 
   422 /* handles retrieval of element information from arrays of elements in device IO registry information */
   423 
   424 static void
   425 HIDGetElements(CFTypeRef refElementCurrent, recDevice * pDevice)
   426 {
   427     CFTypeID type = CFGetTypeID(refElementCurrent);
   428     if (type == CFArrayGetTypeID()) {   /* if element is an array */
   429         CFRange range = { 0, CFArrayGetCount(refElementCurrent) };
   430         /* CountElementsCFArrayHandler called for each array member */
   431         CFArrayApplyFunction(refElementCurrent, range,
   432                              HIDGetElementsCFArrayHandler, pDevice);
   433     }
   434 }
   435 
   436 /* handles extracting element information from element collection CF types
   437  * used from top level element decoding and hierarchy deconstruction to flatten device element list
   438  */
   439 
   440 static void
   441 HIDGetCollectionElements(CFMutableDictionaryRef deviceProperties,
   442                          recDevice * pDevice)
   443 {
   444     CFTypeRef refElementTop =
   445         CFDictionaryGetValue(deviceProperties, CFSTR(kIOHIDElementKey));
   446     if (refElementTop)
   447         HIDGetElements(refElementTop, pDevice);
   448 }
   449 
   450 /* use top level element usage page and usage to discern device usage page and usage setting appropriate vlaues in device record */
   451 
   452 static void
   453 HIDTopLevelElementHandler(const void *value, void *parameter)
   454 {
   455     CFTypeRef refCF = 0;
   456     if (CFGetTypeID(value) != CFDictionaryGetTypeID())
   457         return;
   458     refCF = CFDictionaryGetValue(value, CFSTR(kIOHIDElementUsagePageKey));
   459     if (!CFNumberGetValue
   460         (refCF, kCFNumberLongType, &((recDevice *) parameter)->usagePage))
   461         SDL_SetError("CFNumberGetValue error retrieving pDevice->usagePage.");
   462     refCF = CFDictionaryGetValue(value, CFSTR(kIOHIDElementUsageKey));
   463     if (!CFNumberGetValue
   464         (refCF, kCFNumberLongType, &((recDevice *) parameter)->usage))
   465         SDL_SetError("CFNumberGetValue error retrieving pDevice->usage.");
   466 }
   467 
   468 /* extracts device info from CF dictionary records in IO registry */
   469 
   470 static void
   471 HIDGetDeviceInfo(io_object_t hidDevice, CFMutableDictionaryRef hidProperties,
   472                  recDevice * pDevice)
   473 {
   474     CFMutableDictionaryRef usbProperties = 0;
   475     io_registry_entry_t parent1, parent2;
   476 
   477     /* Mac OS X currently is not mirroring all USB properties to HID page so need to look at USB device page also
   478      * get dictionary for USB properties: step up two levels and get CF dictionary for USB properties
   479      */
   480     if ((KERN_SUCCESS == IORegistryEntryGetParentEntry(hidDevice, kIOServicePlane, &parent1))
   481         && (KERN_SUCCESS == IORegistryEntryGetParentEntry(parent1, kIOServicePlane, &parent2))
   482         && (KERN_SUCCESS == IORegistryEntryCreateCFProperties(parent2, &usbProperties, kCFAllocatorDefault, kNilOptions))) {
   483         if (usbProperties) {
   484             CFTypeRef refCF = 0;
   485             /* get device info
   486              * try hid dictionary first, if fail then go to usb dictionary
   487              */
   488 
   489             /* get product name */
   490             refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductKey));
   491             if (!refCF) {
   492                 refCF = CFDictionaryGetValue(usbProperties, CFSTR("USB Product Name"));
   493             }
   494             if (refCF) {
   495                 if (!CFStringGetCString(refCF, pDevice->product, 256, CFStringGetSystemEncoding())) {
   496                     SDL_SetError("CFStringGetCString error retrieving pDevice->product.");
   497                 }
   498             }
   499 
   500             /* get usage page and usage */
   501             refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDPrimaryUsagePageKey));
   502             if (refCF) {
   503                 if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->usagePage)) {
   504                     SDL_SetError("CFNumberGetValue error retrieving pDevice->usagePage.");
   505                 }
   506 
   507                 refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDPrimaryUsageKey));
   508                 if (refCF) {
   509                     if (!CFNumberGetValue (refCF, kCFNumberLongType, &pDevice->usage)) {
   510                         SDL_SetError("CFNumberGetValue error retrieving pDevice->usage.");
   511                     }
   512                 }
   513             }
   514 
   515             refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDVendorIDKey));
   516             if (refCF) {
   517                 if (!CFNumberGetValue(refCF, kCFNumberLongType, &pDevice->guid.data[0])) {
   518                     SDL_SetError("CFNumberGetValue error retrieving pDevice->guid[0]");
   519                 }
   520             }
   521 
   522             refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductIDKey));
   523             if (refCF) {
   524                 if (!CFNumberGetValue(refCF, kCFNumberLongType, &pDevice->guid.data[8])) {
   525                     SDL_SetError("CFNumberGetValue error retrieving pDevice->guid[8]");
   526                 }
   527             }
   528 
   529             /* Check to make sure we have a vendor and product ID
   530                If we don't, use the same algorithm as the Linux code for Bluetooth devices */
   531             {
   532                 Uint32 *guid32 = (Uint32*)pDevice->guid.data;
   533                 if (!guid32[0] && !guid32[1]) {
   534                     const Uint16 BUS_BLUETOOTH = 0x05;
   535                     Uint16 *guid16 = (Uint16 *)guid32;
   536                     *guid16++ = BUS_BLUETOOTH;
   537                     *guid16++ = 0;
   538                     SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
   539                 }
   540             }
   541 
   542             /* If we don't have a vendor and product ID this is probably a Bluetooth device */
   543 
   544             if (NULL == refCF) {    /* get top level element HID usage page or usage */
   545                 /* use top level element instead */
   546                 CFTypeRef refCFTopElement = 0;
   547                 refCFTopElement = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDElementKey));
   548                 {
   549                     /* refCFTopElement points to an array of element dictionaries */
   550                     CFRange range = { 0, CFArrayGetCount(refCFTopElement) };
   551                     CFArrayApplyFunction(refCFTopElement, range, HIDTopLevelElementHandler, pDevice);
   552                 }
   553             }
   554 
   555             CFRelease(usbProperties);
   556         } else {
   557             SDL_SetError("IORegistryEntryCreateCFProperties failed to create usbProperties.");
   558         }
   559 
   560         if (kIOReturnSuccess != IOObjectRelease(parent2)) {
   561             SDL_SetError("IOObjectRelease error with parent2");
   562         }
   563         if (kIOReturnSuccess != IOObjectRelease(parent1)) {
   564             SDL_SetError("IOObjectRelease error with parent1");
   565         }
   566     }
   567 }
   568 
   569 
   570 static recDevice *
   571 HIDBuildDevice(io_object_t hidDevice)
   572 {
   573     recDevice *pDevice = (recDevice *) NewPtrClear(sizeof(recDevice));
   574     if (pDevice) {
   575         /* get dictionary for HID properties */
   576         CFMutableDictionaryRef hidProperties = 0;
   577         kern_return_t result =
   578             IORegistryEntryCreateCFProperties(hidDevice, &hidProperties,
   579                                               kCFAllocatorDefault,
   580                                               kNilOptions);
   581         if ((result == KERN_SUCCESS) && hidProperties) {
   582             /* create device interface */
   583             result = HIDCreateOpenDeviceInterface(hidDevice, pDevice);
   584             if (kIOReturnSuccess == result) {
   585                 HIDGetDeviceInfo(hidDevice, hidProperties, pDevice);    /* hidDevice used to find parents in registry tree */
   586                 HIDGetCollectionElements(hidProperties, pDevice);
   587             } else {
   588                 DisposePtr((Ptr) pDevice);
   589                 pDevice = NULL;
   590             }
   591             CFRelease(hidProperties);
   592         } else {
   593             DisposePtr((Ptr) pDevice);
   594             pDevice = NULL;
   595         }
   596     }
   597     return pDevice;
   598 }
   599 
   600 /* disposes of the element list associated with a device and the memory associated with the list
   601  */
   602 
   603 static void
   604 HIDDisposeElementList(recElement ** elementList)
   605 {
   606     recElement *pElement = *elementList;
   607     while (pElement) {
   608         recElement *pElementNext = pElement->pNext;
   609         DisposePtr((Ptr) pElement);
   610         pElement = pElementNext;
   611     }
   612     *elementList = NULL;
   613 }
   614 
   615 /* disposes of a single device, closing and releaseing interface, freeing memory fro device and elements, setting device pointer to NULL
   616  * all your device no longer belong to us... (i.e., you do not 'own' the device anymore)
   617  */
   618 
   619 static recDevice *
   620 HIDDisposeDevice(recDevice ** ppDevice)
   621 {
   622     kern_return_t result = KERN_SUCCESS;
   623     recDevice *pDeviceNext = NULL;
   624     if (*ppDevice) {
   625         /* save next device prior to disposing of this device */
   626         pDeviceNext = (*ppDevice)->pNext;
   627 
   628         /* free posible io_service_t */
   629         if ((*ppDevice)->ffservice) {
   630             IOObjectRelease((*ppDevice)->ffservice);
   631             (*ppDevice)->ffservice = 0;
   632         }
   633 
   634         /* free element lists */
   635         HIDDisposeElementList(&(*ppDevice)->firstAxis);
   636         HIDDisposeElementList(&(*ppDevice)->firstButton);
   637         HIDDisposeElementList(&(*ppDevice)->firstHat);
   638 
   639         result = HIDCloseReleaseInterface(*ppDevice);   /* function sanity checks interface value (now application does not own device) */
   640         if (kIOReturnSuccess != result)
   641             HIDReportErrorNum
   642                 ("HIDCloseReleaseInterface failed when trying to dipose device.",
   643                  result);
   644         DisposePtr((Ptr) * ppDevice);
   645         *ppDevice = NULL;
   646     }
   647     return pDeviceNext;
   648 }
   649 
   650 
   651 /* Given an io_object_t from OSX adds a joystick device to our list if appropriate
   652  */
   653 int
   654 AddDeviceHelper( io_object_t ioHIDDeviceObject )
   655 {
   656     recDevice *device;
   657 
   658     /* build a device record */
   659     device = HIDBuildDevice(ioHIDDeviceObject);
   660     if (!device)
   661         return 0;
   662 
   663     /* Filter device list to non-keyboard/mouse stuff */
   664     if ((device->usagePage != kHIDPage_GenericDesktop) ||
   665         ((device->usage != kHIDUsage_GD_Joystick &&
   666           device->usage != kHIDUsage_GD_GamePad &&
   667           device->usage != kHIDUsage_GD_MultiAxisController))) {
   668 
   669         /* release memory for the device */
   670         HIDDisposeDevice(&device);
   671         DisposePtr((Ptr) device);
   672         return 0;
   673     }
   674 
   675     /* Allocate an instance ID for this device */
   676     device->instance_id = ++s_joystick_instance_id;
   677 
   678     /* We have to do some storage of the io_service_t for
   679      * SDL_HapticOpenFromJoystick */
   680     if (FFIsForceFeedback(ioHIDDeviceObject) == FF_OK) {
   681         device->ffservice = ioHIDDeviceObject;
   682     } else {
   683         device->ffservice = 0;
   684     }
   685 
   686     device->send_open_event = 1;
   687     s_bDeviceAdded = SDL_TRUE;
   688 
   689     /* Add device to the end of the list */
   690     if ( !gpDeviceList )
   691     {
   692         gpDeviceList = device;
   693     }
   694     else
   695     {
   696         recDevice *curdevice;
   697 
   698         curdevice = gpDeviceList;
   699         while ( curdevice->pNext )
   700         {
   701             curdevice = curdevice->pNext;
   702         }
   703         curdevice->pNext = device;
   704     }
   705 
   706     return 1;
   707 }
   708 
   709 
   710 /* Called by our IO port notifier on the master port when a HID device is inserted, we iterate
   711  *  and check for new joysticks
   712  */
   713 void JoystickDeviceWasAddedCallback( void *refcon, io_iterator_t iterator )
   714 {
   715     io_object_t ioHIDDeviceObject = 0;
   716 
   717     while ( ( ioHIDDeviceObject = IOIteratorNext(iterator) ) )
   718     {
   719         if ( ioHIDDeviceObject )
   720         {
   721             AddDeviceHelper( ioHIDDeviceObject );
   722         }
   723     }
   724 }
   725 
   726 
   727 /* Function to scan the system for joysticks.
   728  * Joystick 0 should be the system default joystick.
   729  * This function should return the number of available joysticks, or -1
   730  * on an unrecoverable fatal error.
   731  */
   732 int
   733 SDL_SYS_JoystickInit(void)
   734 {
   735     IOReturn result = kIOReturnSuccess;
   736     mach_port_t masterPort = 0;
   737     io_iterator_t hidObjectIterator = 0;
   738     CFMutableDictionaryRef hidMatchDictionary = NULL;
   739     io_object_t ioHIDDeviceObject = 0;
   740     io_iterator_t portIterator = 0;
   741 
   742     if (gpDeviceList) {
   743         return SDL_SetError("Joystick: Device list already inited.");
   744     }
   745 
   746     result = IOMasterPort(bootstrap_port, &masterPort);
   747     if (kIOReturnSuccess != result) {
   748         return SDL_SetError("Joystick: IOMasterPort error with bootstrap_port.");
   749     }
   750 
   751     /* Set up a matching dictionary to search I/O Registry by class name for all HID class devices. */
   752     hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey);
   753     if (hidMatchDictionary) {
   754         /* Add key for device type (joystick, in this case) to refine the matching dictionary. */
   755 
   756         /* NOTE: we now perform this filtering later
   757            UInt32 usagePage = kHIDPage_GenericDesktop;
   758            UInt32 usage = kHIDUsage_GD_Joystick;
   759            CFNumberRef refUsage = NULL, refUsagePage = NULL;
   760 
   761            refUsage = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &usage);
   762            CFDictionarySetValue (hidMatchDictionary, CFSTR (kIOHIDPrimaryUsageKey), refUsage);
   763            refUsagePage = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &usagePage);
   764            CFDictionarySetValue (hidMatchDictionary, CFSTR (kIOHIDPrimaryUsagePageKey), refUsagePage);
   765          */
   766     } else {
   767         return SDL_SetError
   768             ("Joystick: Failed to get HID CFMutableDictionaryRef via IOServiceMatching.");
   769     }
   770 
   771     /* Now search I/O Registry for matching devices. */
   772     result =
   773         IOServiceGetMatchingServices(masterPort, hidMatchDictionary,
   774                                      &hidObjectIterator);
   775     /* Check for errors */
   776     if (kIOReturnSuccess != result) {
   777         return SDL_SetError("Joystick: Couldn't create a HID object iterator.");
   778     }
   779     if (!hidObjectIterator) {   /* there are no joysticks */
   780         gpDeviceList = NULL;
   781         return 0;
   782     }
   783     /* IOServiceGetMatchingServices consumes a reference to the dictionary, so we don't need to release the dictionary ref. */
   784 
   785     /* build flat linked list of devices from device iterator */
   786 
   787     gpDeviceList = NULL;
   788 
   789     while ((ioHIDDeviceObject = IOIteratorNext(hidObjectIterator))) {
   790         AddDeviceHelper( ioHIDDeviceObject );
   791     }
   792     result = IOObjectRelease(hidObjectIterator);        /* release the iterator */
   793 
   794     /* now connect notification for new devices */
   795     notificationPort = IONotificationPortCreate(masterPort);
   796     hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey);
   797 
   798     CFRunLoopAddSource(CFRunLoopGetCurrent(),
   799                        IONotificationPortGetRunLoopSource(notificationPort),
   800                        kCFRunLoopDefaultMode);
   801 
   802     /* Register for notifications when a serial port is added to the system */
   803     result = IOServiceAddMatchingNotification(notificationPort,
   804                                                             kIOFirstMatchNotification,
   805                                                             hidMatchDictionary,
   806                                                             JoystickDeviceWasAddedCallback,
   807                                                             NULL,
   808                                                             &portIterator);
   809     while (IOIteratorNext(portIterator)) {}; /* Run out the iterator or notifications won't start (you can also use it to iterate the available devices). */
   810 
   811     return SDL_SYS_NumJoysticks();
   812 }
   813 
   814 /* Function to return the number of joystick devices plugged in right now */
   815 int
   816 SDL_SYS_NumJoysticks()
   817 {
   818     recDevice *device = gpDeviceList;
   819     int nJoySticks = 0;
   820 
   821     while ( device )
   822     {
   823         if ( !device->removed )
   824             nJoySticks++;
   825         device = device->pNext;
   826     }
   827 
   828     return nJoySticks;
   829 }
   830 
   831 /* Function to cause any queued joystick insertions to be processed
   832  */
   833 void
   834 SDL_SYS_JoystickDetect()
   835 {
   836     if ( s_bDeviceAdded || s_bDeviceRemoved )
   837     {
   838         recDevice *device = gpDeviceList;
   839         s_bDeviceAdded = SDL_FALSE;
   840         s_bDeviceRemoved = SDL_FALSE;
   841         int device_index = 0;
   842         /* send notifications */
   843         while ( device )
   844         {
   845             if ( device->send_open_event )
   846             {
   847                 device->send_open_event = 0;
   848 #if !SDL_EVENTS_DISABLED
   849                 SDL_Event event;
   850                 event.type = SDL_JOYDEVICEADDED;
   851 
   852                 if (SDL_GetEventState(event.type) == SDL_ENABLE) {
   853                     event.jdevice.which = device_index;
   854                     if ((SDL_EventOK == NULL)
   855                         || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
   856                         SDL_PushEvent(&event);
   857                     }
   858                 }
   859 #endif /* !SDL_EVENTS_DISABLED */
   860 
   861             }
   862 
   863             if ( device->removed )
   864             {
   865                 recDevice *removeDevice = device;
   866                 if ( gpDeviceList == removeDevice )
   867                 {
   868                     device = device->pNext;
   869                     gpDeviceList = device;
   870                 }
   871                 else
   872                 {
   873                     device = gpDeviceList;
   874                     while ( device->pNext != removeDevice )
   875                     {
   876                         device = device->pNext;
   877                     }
   878 
   879                     device->pNext = removeDevice->pNext;
   880                 }
   881 
   882 #if !SDL_EVENTS_DISABLED
   883                 SDL_Event event;
   884                 event.type = SDL_JOYDEVICEREMOVED;
   885 
   886                 if (SDL_GetEventState(event.type) == SDL_ENABLE) {
   887                     event.jdevice.which = removeDevice->instance_id;
   888                     if ((SDL_EventOK == NULL)
   889                         || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
   890                         SDL_PushEvent(&event);
   891                     }
   892                 }
   893 
   894                 DisposePtr((Ptr) removeDevice);
   895 #endif /* !SDL_EVENTS_DISABLED */
   896 
   897             }
   898             else
   899             {
   900                 device = device->pNext;
   901                 device_index++;
   902             }
   903         }
   904     }
   905 }
   906 
   907 SDL_bool
   908 SDL_SYS_JoystickNeedsPolling()
   909 {
   910     return s_bDeviceAdded || s_bDeviceRemoved;
   911 }
   912 
   913 /* Function to get the device-dependent name of a joystick */
   914 const char *
   915 SDL_SYS_JoystickNameForDeviceIndex(int device_index)
   916 {
   917     recDevice *device = gpDeviceList;
   918 
   919     for (; device_index > 0; device_index--)
   920         device = device->pNext;
   921 
   922     return device->product;
   923 }
   924 
   925 /* Function to return the instance id of the joystick at device_index
   926  */
   927 SDL_JoystickID
   928 SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
   929 {
   930     recDevice *device = gpDeviceList;
   931     int index;
   932 
   933     for (index = device_index; index > 0; index--)
   934         device = device->pNext;
   935 
   936     return device->instance_id;
   937 }
   938 
   939 /* Function to open a joystick for use.
   940  * The joystick to open is specified by the index field of the joystick.
   941  * This should fill the nbuttons and naxes fields of the joystick structure.
   942  * It returns 0, or -1 if there is an error.
   943  */
   944 int
   945 SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
   946 {
   947     recDevice *device = gpDeviceList;
   948     int index;
   949 
   950     for (index = device_index; index > 0; index--)
   951         device = device->pNext;
   952 
   953     joystick->instance_id = device->instance_id;
   954     joystick->hwdata = device;
   955     joystick->name = device->product;
   956 
   957     joystick->naxes = device->axes;
   958     joystick->nhats = device->hats;
   959     joystick->nballs = 0;
   960     joystick->nbuttons = device->buttons;
   961     return 0;
   962 }
   963 
   964 /* Function to query if the joystick is currently attached
   965  *   It returns 1 if attached, 0 otherwise.
   966  */
   967 SDL_bool
   968 SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
   969 {
   970     recDevice *device = gpDeviceList;
   971 
   972     while ( device )
   973     {
   974         if ( joystick->instance_id == device->instance_id )
   975             return SDL_TRUE;
   976 
   977         device = device->pNext;
   978     }
   979 
   980     return SDL_FALSE;
   981 }
   982 
   983 /* Function to update the state of a joystick - called as a device poll.
   984  * This function shouldn't update the joystick structure directly,
   985  * but instead should call SDL_PrivateJoystick*() to deliver events
   986  * and update joystick device state.
   987  */
   988 void
   989 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   990 {
   991     recDevice *device = joystick->hwdata;
   992     recElement *element;
   993     SInt32 value, range;
   994     int i;
   995 
   996     if ( !device )
   997         return;
   998 
   999     if (device->removed) {      /* device was unplugged; ignore it. */
  1000         recDevice *devicelist = gpDeviceList;
  1001         joystick->closed = 1;
  1002         joystick->uncentered = 1;
  1003 
  1004         if ( devicelist == device )
  1005         {
  1006             gpDeviceList = device->pNext;
  1007         }
  1008         else
  1009         {
  1010             while ( devicelist->pNext != device )
  1011             {
  1012                 devicelist = devicelist->pNext;
  1013             }
  1014 
  1015             devicelist->pNext = device->pNext;
  1016         }
  1017 
  1018         DisposePtr((Ptr) device);
  1019         joystick->hwdata = NULL;
  1020 
  1021 #if !SDL_EVENTS_DISABLED
  1022         SDL_Event event;
  1023         event.type = SDL_JOYDEVICEREMOVED;
  1024 
  1025         if (SDL_GetEventState(event.type) == SDL_ENABLE) {
  1026             event.jdevice.which = joystick->instance_id;
  1027             if ((SDL_EventOK == NULL)
  1028                 || (*SDL_EventOK) (SDL_EventOKParam, &event)) {
  1029                 SDL_PushEvent(&event);
  1030             }
  1031         }
  1032 #endif /* !SDL_EVENTS_DISABLED */
  1033 
  1034         return;
  1035     }
  1036 
  1037     element = device->firstAxis;
  1038     i = 0;
  1039     while (element) {
  1040         value = HIDScaledCalibratedValue(device, element, -32768, 32767);
  1041         if (value != joystick->axes[i])
  1042             SDL_PrivateJoystickAxis(joystick, i, value);
  1043         element = element->pNext;
  1044         ++i;
  1045     }
  1046 
  1047     element = device->firstButton;
  1048     i = 0;
  1049     while (element) {
  1050         value = HIDGetElementValue(device, element);
  1051         if (value > 1)          /* handle pressure-sensitive buttons */
  1052             value = 1;
  1053         if (value != joystick->buttons[i])
  1054             SDL_PrivateJoystickButton(joystick, i, value);
  1055         element = element->pNext;
  1056         ++i;
  1057     }
  1058 
  1059     element = device->firstHat;
  1060     i = 0;
  1061     while (element) {
  1062         Uint8 pos = 0;
  1063 
  1064         range = (element->max - element->min + 1);
  1065         value = HIDGetElementValue(device, element) - element->min;
  1066         if (range == 4)         /* 4 position hatswitch - scale up value */
  1067             value *= 2;
  1068         else if (range != 8)    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
  1069             value = -1;
  1070         switch (value) {
  1071         case 0:
  1072             pos = SDL_HAT_UP;
  1073             break;
  1074         case 1:
  1075             pos = SDL_HAT_RIGHTUP;
  1076             break;
  1077         case 2:
  1078             pos = SDL_HAT_RIGHT;
  1079             break;
  1080         case 3:
  1081             pos = SDL_HAT_RIGHTDOWN;
  1082             break;
  1083         case 4:
  1084             pos = SDL_HAT_DOWN;
  1085             break;
  1086         case 5:
  1087             pos = SDL_HAT_LEFTDOWN;
  1088             break;
  1089         case 6:
  1090             pos = SDL_HAT_LEFT;
  1091             break;
  1092         case 7:
  1093             pos = SDL_HAT_LEFTUP;
  1094             break;
  1095         default:
  1096             /* Every other value is mapped to center. We do that because some
  1097              * joysticks use 8 and some 15 for this value, and apparently
  1098              * there are even more variants out there - so we try to be generous.
  1099              */
  1100             pos = SDL_HAT_CENTERED;
  1101             break;
  1102         }
  1103         if (pos != joystick->hats[i])
  1104             SDL_PrivateJoystickHat(joystick, i, pos);
  1105         element = element->pNext;
  1106         ++i;
  1107     }
  1108 
  1109     return;
  1110 }
  1111 
  1112 /* Function to close a joystick after use */
  1113 void
  1114 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
  1115 {
  1116     joystick->closed = 1;
  1117 }
  1118 
  1119 /* Function to perform any system-specific joystick related cleanup */
  1120 void
  1121 SDL_SYS_JoystickQuit(void)
  1122 {
  1123     while (NULL != gpDeviceList)
  1124         gpDeviceList = HIDDisposeDevice(&gpDeviceList);
  1125 
  1126     if ( notificationPort )
  1127     {
  1128         IONotificationPortDestroy( notificationPort );
  1129         notificationPort = 0;
  1130     }
  1131 }
  1132 
  1133 
  1134 SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
  1135 {
  1136     recDevice *device = gpDeviceList;
  1137     int index;
  1138 
  1139     for (index = device_index; index > 0; index--)
  1140         device = device->pNext;
  1141 
  1142     return device->guid;
  1143 }
  1144 
  1145 SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick *joystick)
  1146 {
  1147     return joystick->hwdata->guid;
  1148 }
  1149 
  1150 #endif /* SDL_JOYSTICK_IOKIT */
  1151 
  1152 /* vi: set ts=4 sw=4 expandtab: */