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