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