sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_iokitjoystick.c (34814B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
      4 
      5   This software is provided 'as-is', without any express or implied
      6   warranty.  In no event will the authors be held liable for any damages
      7   arising from the use of this software.
      8 
      9   Permission is granted to anyone to use this software for any purpose,
     10   including commercial applications, and to alter it and redistribute it
     11   freely, subject to the following restrictions:
     12 
     13   1. The origin of this software must not be misrepresented; you must not
     14      claim that you wrote the original software. If you use this software
     15      in a product, an acknowledgment in the product documentation would be
     16      appreciated but is not required.
     17   2. Altered source versions must be plainly marked as such, and must not be
     18      misrepresented as being the original software.
     19   3. This notice may not be removed or altered from any source distribution.
     20 */
     21 #include "../../SDL_internal.h"
     22 
     23 #ifdef SDL_JOYSTICK_IOKIT
     24 
     25 #include "SDL_events.h"
     26 #include "SDL_joystick.h"
     27 #include "../SDL_sysjoystick.h"
     28 #include "../SDL_joystick_c.h"
     29 #include "SDL_iokitjoystick_c.h"
     30 #include "../hidapi/SDL_hidapijoystick_c.h"
     31 #include "../../haptic/darwin/SDL_syshaptic_c.h"    /* For haptic hot plugging */
     32 
     33 
     34 #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
     35 
     36 #define CONVERT_MAGNITUDE(x)    (((x)*10000) / 0x7FFF)
     37 
     38 /* The base object of the HID Manager API */
     39 static IOHIDManagerRef hidman = NULL;
     40 
     41 /* Linked list of all available devices */
     42 static recDevice *gpDeviceList = NULL;
     43 
     44 void FreeRumbleEffectData(FFEFFECT *effect)
     45 {
     46     if (!effect) {
     47         return;
     48     }
     49     SDL_free(effect->rgdwAxes);
     50     SDL_free(effect->rglDirection);
     51     SDL_free(effect->lpvTypeSpecificParams);
     52     SDL_free(effect);
     53 }
     54 
     55 FFEFFECT *CreateRumbleEffectData(Sint16 magnitude)
     56 {
     57     FFEFFECT *effect;
     58     FFPERIODIC *periodic;
     59 
     60     /* Create the effect */
     61     effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect));
     62     if (!effect) {
     63         return NULL;
     64     }
     65     effect->dwSize = sizeof(*effect);
     66     effect->dwGain = 10000;
     67     effect->dwFlags = FFEFF_OBJECTOFFSETS;
     68     effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; /* In microseconds. */
     69     effect->dwTriggerButton = FFEB_NOTRIGGER;
     70 
     71     effect->cAxes = 2;
     72     effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
     73     if (!effect->rgdwAxes) {
     74         FreeRumbleEffectData(effect);
     75         return NULL;
     76     }
     77 
     78     effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
     79     if (!effect->rglDirection) {
     80         FreeRumbleEffectData(effect);
     81         return NULL;
     82     }
     83     effect->dwFlags |= FFEFF_CARTESIAN;
     84 
     85     periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic));
     86     if (!periodic) {
     87         FreeRumbleEffectData(effect);
     88         return NULL;
     89     }
     90     periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
     91     periodic->dwPeriod = 1000000;
     92 
     93     effect->cbTypeSpecificParams = sizeof(*periodic);
     94     effect->lpvTypeSpecificParams = periodic;
     95 
     96     return effect;
     97 }
     98 
     99 static recDevice *GetDeviceForIndex(int device_index)
    100 {
    101     recDevice *device = gpDeviceList;
    102     while (device) {
    103         if (!device->removed) {
    104             if (device_index == 0)
    105                 break;
    106 
    107             --device_index;
    108         }
    109         device = device->pNext;
    110     }
    111     return device;
    112 }
    113 
    114 static void
    115 FreeElementList(recElement *pElement)
    116 {
    117     while (pElement) {
    118         recElement *pElementNext = pElement->pNext;
    119         SDL_free(pElement);
    120         pElement = pElementNext;
    121     }
    122 }
    123 
    124 static recDevice *
    125 FreeDevice(recDevice *removeDevice)
    126 {
    127     recDevice *pDeviceNext = NULL;
    128     if (removeDevice) {
    129         if (removeDevice->deviceRef) {
    130             if (removeDevice->runLoopAttached) {
    131                 /* Calling IOHIDDeviceUnscheduleFromRunLoop without a prior,
    132                  * paired call to IOHIDDeviceScheduleWithRunLoop can lead
    133                  * to crashes in MacOS 10.14.x and earlier.  This doesn't
    134                  * appear to be a problem in MacOS 10.15.x, but we'll
    135                  * do it anyways.  (Part-of fix for Bug 5034)
    136                  */
    137                 IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
    138             }
    139             CFRelease(removeDevice->deviceRef);
    140             removeDevice->deviceRef = NULL;
    141         }
    142 
    143         /* clear out any reference to removeDevice from an associated,
    144          * live instance of SDL_Joystick  (Part-of fix for Bug 5034)
    145          */
    146         SDL_LockJoysticks();
    147         if (removeDevice->joystick) {
    148             removeDevice->joystick->hwdata = NULL;
    149         }
    150         SDL_UnlockJoysticks();
    151 
    152         /* save next device prior to disposing of this device */
    153         pDeviceNext = removeDevice->pNext;
    154 
    155         if ( gpDeviceList == removeDevice ) {
    156             gpDeviceList = pDeviceNext;
    157         } else if (gpDeviceList) {
    158             recDevice *device = gpDeviceList;
    159             while (device->pNext != removeDevice) {
    160                 device = device->pNext;
    161             }
    162             device->pNext = pDeviceNext;
    163         }
    164         removeDevice->pNext = NULL;
    165 
    166         /* free element lists */
    167         FreeElementList(removeDevice->firstAxis);
    168         FreeElementList(removeDevice->firstButton);
    169         FreeElementList(removeDevice->firstHat);
    170 
    171         SDL_free(removeDevice);
    172     }
    173     return pDeviceNext;
    174 }
    175 
    176 static SDL_bool
    177 GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue)
    178 {
    179     SInt32 value = 0;
    180     int returnValue = SDL_FALSE;
    181 
    182     if (pDevice && pDevice->deviceRef && pElement) {
    183         IOHIDValueRef valueRef;
    184         if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
    185             value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
    186 
    187             /* record min and max for auto calibration */
    188             if (value < pElement->minReport) {
    189                 pElement->minReport = value;
    190             }
    191             if (value > pElement->maxReport) {
    192                 pElement->maxReport = value;
    193             }
    194             *pValue = value;
    195 
    196             returnValue = SDL_TRUE;
    197         }
    198     }
    199     return returnValue;
    200 }
    201 
    202 static SDL_bool
    203 GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max, SInt32 *pValue)
    204 {
    205     const float deviceScale = max - min;
    206     const float readScale = pElement->maxReport - pElement->minReport;
    207     int returnValue = SDL_FALSE;
    208     if (GetHIDElementState(pDevice, pElement, pValue))
    209     {
    210         if (readScale == 0) {
    211             returnValue = SDL_TRUE;           /* no scaling at all */
    212         }
    213         else
    214         {
    215             *pValue = ((*pValue - pElement->minReport) * deviceScale / readScale) + min;
    216             returnValue = SDL_TRUE;
    217         }
    218     } 
    219     return returnValue;
    220 }
    221 
    222 static void
    223 JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
    224 {
    225     recDevice *device = (recDevice *) ctx;
    226     device->removed = SDL_TRUE;
    227     if (device->deviceRef) {
    228         // deviceRef was invalidated due to the remove
    229         CFRelease(device->deviceRef);
    230         device->deviceRef = NULL;
    231     }
    232     if (device->ffeffect_ref) {
    233         FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref);
    234         device->ffeffect_ref = NULL;
    235     }
    236     if (device->ffeffect) {
    237         FreeRumbleEffectData(device->ffeffect);
    238         device->ffeffect = NULL;
    239     }
    240     if (device->ffdevice) {
    241         FFReleaseDevice(device->ffdevice);
    242         device->ffdevice = NULL;
    243         device->ff_initialized = SDL_FALSE;
    244     }
    245 #if SDL_HAPTIC_IOKIT
    246     MacHaptic_MaybeRemoveDevice(device->ffservice);
    247 #endif
    248 
    249     SDL_PrivateJoystickRemoved(device->instance_id);
    250 }
    251 
    252 
    253 static void AddHIDElement(const void *value, void *parameter);
    254 
    255 /* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
    256 static void
    257 AddHIDElements(CFArrayRef array, recDevice *pDevice)
    258 {
    259     const CFRange range = { 0, CFArrayGetCount(array) };
    260     CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
    261 }
    262 
    263 static SDL_bool
    264 ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
    265     while (listitem) {
    266         if (listitem->cookie == cookie) {
    267             return SDL_TRUE;
    268         }
    269         listitem = listitem->pNext;
    270     }
    271     return SDL_FALSE;
    272 }
    273 
    274 /* See if we care about this HID element, and if so, note it in our recDevice. */
    275 static void
    276 AddHIDElement(const void *value, void *parameter)
    277 {
    278     recDevice *pDevice = (recDevice *) parameter;
    279     IOHIDElementRef refElement = (IOHIDElementRef) value;
    280     const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
    281 
    282     if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
    283         const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
    284         const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
    285         const uint32_t usage = IOHIDElementGetUsage(refElement);
    286         recElement *element = NULL;
    287         recElement **headElement = NULL;
    288 
    289         /* look at types of interest */
    290         switch (IOHIDElementGetType(refElement)) {
    291             case kIOHIDElementTypeInput_Misc:
    292             case kIOHIDElementTypeInput_Button:
    293             case kIOHIDElementTypeInput_Axis: {
    294                 switch (usagePage) {    /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
    295                     case kHIDPage_GenericDesktop:
    296                         switch (usage) {
    297                             case kHIDUsage_GD_X:
    298                             case kHIDUsage_GD_Y:
    299                             case kHIDUsage_GD_Z:
    300                             case kHIDUsage_GD_Rx:
    301                             case kHIDUsage_GD_Ry:
    302                             case kHIDUsage_GD_Rz:
    303                             case kHIDUsage_GD_Slider:
    304                             case kHIDUsage_GD_Dial:
    305                             case kHIDUsage_GD_Wheel:
    306                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
    307                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
    308                                     if (element) {
    309                                         pDevice->axes++;
    310                                         headElement = &(pDevice->firstAxis);
    311                                     }
    312                                 }
    313                                 break;
    314 
    315                             case kHIDUsage_GD_Hatswitch:
    316                                 if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
    317                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
    318                                     if (element) {
    319                                         pDevice->hats++;
    320                                         headElement = &(pDevice->firstHat);
    321                                     }
    322                                 }
    323                                 break;
    324                             case kHIDUsage_GD_DPadUp:
    325                             case kHIDUsage_GD_DPadDown:
    326                             case kHIDUsage_GD_DPadRight:
    327                             case kHIDUsage_GD_DPadLeft:
    328                             case kHIDUsage_GD_Start:
    329                             case kHIDUsage_GD_Select:
    330                             case kHIDUsage_GD_SystemMainMenu:
    331                                 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
    332                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
    333                                     if (element) {
    334                                         pDevice->buttons++;
    335                                         headElement = &(pDevice->firstButton);
    336                                     }
    337                                 }
    338                                 break;
    339                         }
    340                         break;
    341 
    342                     case kHIDPage_Simulation:
    343                         switch (usage) {
    344                             case kHIDUsage_Sim_Rudder:
    345                             case kHIDUsage_Sim_Throttle:
    346                             case kHIDUsage_Sim_Accelerator:
    347                             case kHIDUsage_Sim_Brake:
    348                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
    349                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
    350                                     if (element) {
    351                                         pDevice->axes++;
    352                                         headElement = &(pDevice->firstAxis);
    353                                     }
    354                                 }
    355                                 break;
    356 
    357                             default:
    358                                 break;
    359                         }
    360                         break;
    361 
    362                     case kHIDPage_Button:
    363                     case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
    364                         if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
    365                             element = (recElement *) SDL_calloc(1, sizeof (recElement));
    366                             if (element) {
    367                                 pDevice->buttons++;
    368                                 headElement = &(pDevice->firstButton);
    369                             }
    370                         }
    371                         break;
    372 
    373                     default:
    374                         break;
    375                 }
    376             }
    377             break;
    378 
    379             case kIOHIDElementTypeCollection: {
    380                 CFArrayRef array = IOHIDElementGetChildren(refElement);
    381                 if (array) {
    382                     AddHIDElements(array, pDevice);
    383                 }
    384             }
    385             break;
    386 
    387             default:
    388                 break;
    389         }
    390 
    391         if (element && headElement) {       /* add to list */
    392             recElement *elementPrevious = NULL;
    393             recElement *elementCurrent = *headElement;
    394             while (elementCurrent && usage >= elementCurrent->usage) {
    395                 elementPrevious = elementCurrent;
    396                 elementCurrent = elementCurrent->pNext;
    397             }
    398             if (elementPrevious) {
    399                 elementPrevious->pNext = element;
    400             } else {
    401                 *headElement = element;
    402             }
    403 
    404             element->elementRef = refElement;
    405             element->usagePage = usagePage;
    406             element->usage = usage;
    407             element->pNext = elementCurrent;
    408 
    409             element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
    410             element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
    411             element->cookie = IOHIDElementGetCookie(refElement);
    412 
    413             pDevice->elements++;
    414         }
    415     }
    416 }
    417 
    418 
    419 static SDL_bool
    420 GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
    421 {
    422     Sint32 vendor = 0;
    423     Sint32 product = 0;
    424     Sint32 version = 0;
    425     char *name;
    426     char manufacturer_string[256];
    427     char product_string[256];
    428     CFTypeRef refCF = NULL;
    429     CFArrayRef array = NULL;
    430     Uint16 *guid16 = (Uint16 *)pDevice->guid.data;
    431 
    432     /* get usage page and usage */
    433     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
    434     if (refCF) {
    435         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
    436     }
    437     if (pDevice->usagePage != kHIDPage_GenericDesktop) {
    438         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
    439     }
    440 
    441     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
    442     if (refCF) {
    443         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
    444     }
    445 
    446     if ((pDevice->usage != kHIDUsage_GD_Joystick &&
    447          pDevice->usage != kHIDUsage_GD_GamePad &&
    448          pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
    449         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
    450     }
    451 
    452     /* Make sure we retain the use of the IOKit-provided device-object,
    453        lest the device get disconnected and we try to use it.  (Fixes
    454        SDL-Bugzilla #4961, aka. https://bugzilla.libsdl.org/show_bug.cgi?id=4961 )
    455     */
    456     CFRetain(hidDevice);
    457 
    458     /* Now that we've CFRetain'ed the device-object (for our use), we'll
    459        save the reference to it.
    460     */
    461     pDevice->deviceRef = hidDevice;
    462 
    463     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
    464     if (refCF) {
    465         CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
    466     }
    467 
    468     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
    469     if (refCF) {
    470         CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
    471     }
    472 
    473     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
    474     if (refCF) {
    475         CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
    476     }
    477 
    478     /* get device name */
    479     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
    480     if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) {
    481         manufacturer_string[0] = '\0';
    482     }
    483     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
    484     if ((!refCF) || (!CFStringGetCString(refCF, product_string, sizeof(product_string), kCFStringEncodingUTF8))) {
    485         product_string[0] = '\0';
    486     }
    487     name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
    488     if (name) {
    489         SDL_strlcpy(pDevice->product, name, sizeof(pDevice->product));
    490         SDL_free(name);
    491     }
    492 
    493 #ifdef SDL_JOYSTICK_HIDAPI
    494     if (HIDAPI_IsDevicePresent(vendor, product, version, pDevice->product)) {
    495         /* The HIDAPI driver is taking care of this device */
    496         return 0;
    497     }
    498 #endif
    499 
    500     SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
    501 
    502     if (vendor && product) {
    503         *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB);
    504         *guid16++ = 0;
    505         *guid16++ = SDL_SwapLE16((Uint16)vendor);
    506         *guid16++ = 0;
    507         *guid16++ = SDL_SwapLE16((Uint16)product);
    508         *guid16++ = 0;
    509         *guid16++ = SDL_SwapLE16((Uint16)version);
    510         *guid16++ = 0;
    511     } else {
    512         *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
    513         *guid16++ = 0;
    514         SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
    515     }
    516 
    517     array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
    518     if (array) {
    519         AddHIDElements(array, pDevice);
    520         CFRelease(array);
    521     }
    522 
    523     return SDL_TRUE;
    524 }
    525 
    526 static SDL_bool
    527 JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
    528 {
    529     recDevice *i;
    530 
    531 #if defined(SDL_JOYSTICK_MFI)
    532     extern SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device);
    533     if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) {
    534         return SDL_TRUE;
    535     }
    536 #endif
    537 
    538     for (i = gpDeviceList; i != NULL; i = i->pNext) {
    539         if (i->deviceRef == ioHIDDeviceObject) {
    540             return SDL_TRUE;
    541         }
    542     }
    543     return SDL_FALSE;
    544 }
    545 
    546 
    547 static void
    548 JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
    549 {
    550     recDevice *device;
    551     int device_index = 0;
    552     io_service_t ioservice;
    553 
    554     if (res != kIOReturnSuccess) {
    555         return;
    556     }
    557 
    558     if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
    559         return;  /* IOKit sent us a duplicate. */
    560     }
    561 
    562     device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
    563     if (!device) {
    564         SDL_OutOfMemory();
    565         return;
    566     }
    567 
    568     if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
    569         FreeDevice(device);
    570         return;   /* not a device we care about, probably. */
    571     }
    572 
    573     if (SDL_ShouldIgnoreJoystick(device->product, device->guid)) {
    574         FreeDevice(device);
    575         return;
    576     }
    577 
    578     /* Get notified when this device is disconnected. */
    579     IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
    580     IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
    581     device->runLoopAttached = SDL_TRUE;
    582 
    583     /* Allocate an instance ID for this device */
    584     device->instance_id = SDL_GetNextJoystickInstanceID();
    585 
    586     /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
    587     ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
    588     if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
    589         device->ffservice = ioservice;
    590 #if SDL_HAPTIC_IOKIT
    591         MacHaptic_MaybeAddDevice(ioservice);
    592 #endif
    593     }
    594 
    595     /* Add device to the end of the list */
    596     if ( !gpDeviceList ) {
    597         gpDeviceList = device;
    598     } else {
    599         recDevice *curdevice;
    600 
    601         curdevice = gpDeviceList;
    602         while ( curdevice->pNext ) {
    603             ++device_index;
    604             curdevice = curdevice->pNext;
    605         }
    606         curdevice->pNext = device;
    607         ++device_index;  /* bump by one since we counted by pNext. */
    608     }
    609 
    610     SDL_PrivateJoystickAdded(device->instance_id);
    611 }
    612 
    613 static SDL_bool
    614 ConfigHIDManager(CFArrayRef matchingArray)
    615 {
    616     CFRunLoopRef runloop = CFRunLoopGetCurrent();
    617 
    618     if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
    619         return SDL_FALSE;
    620     }
    621 
    622     IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
    623     IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
    624     IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
    625 
    626     while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
    627         /* no-op. Callback fires once per existing device. */
    628     }
    629 
    630     /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
    631 
    632     return SDL_TRUE;  /* good to go. */
    633 }
    634 
    635 
    636 static CFDictionaryRef
    637 CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
    638 {
    639     CFDictionaryRef retval = NULL;
    640     CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
    641     CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
    642     const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
    643     const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
    644 
    645     if (pageNumRef && usageNumRef) {
    646         retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    647     }
    648 
    649     if (pageNumRef) {
    650         CFRelease(pageNumRef);
    651     }
    652     if (usageNumRef) {
    653         CFRelease(usageNumRef);
    654     }
    655 
    656     if (!retval) {
    657         *okay = 0;
    658     }
    659 
    660     return retval;
    661 }
    662 
    663 static SDL_bool
    664 CreateHIDManager(void)
    665 {
    666     SDL_bool retval = SDL_FALSE;
    667     int okay = 1;
    668     const void *vals[] = {
    669         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
    670         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
    671         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
    672     };
    673     const size_t numElements = SDL_arraysize(vals);
    674     CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
    675     size_t i;
    676 
    677     for (i = 0; i < numElements; i++) {
    678         if (vals[i]) {
    679             CFRelease((CFTypeRef) vals[i]);
    680         }
    681     }
    682 
    683     if (array) {
    684         hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
    685         if (hidman != NULL) {
    686             retval = ConfigHIDManager(array);
    687         }
    688         CFRelease(array);
    689     }
    690 
    691     return retval;
    692 }
    693 
    694 
    695 static int
    696 DARWIN_JoystickInit(void)
    697 {
    698     if (gpDeviceList) {
    699         return SDL_SetError("Joystick: Device list already inited.");
    700     }
    701 
    702     if (!CreateHIDManager()) {
    703         return SDL_SetError("Joystick: Couldn't initialize HID Manager");
    704     }
    705 
    706     return 0;
    707 }
    708 
    709 static int
    710 DARWIN_JoystickGetCount(void)
    711 {
    712     recDevice *device = gpDeviceList;
    713     int nJoySticks = 0;
    714 
    715     while (device) {
    716         if (!device->removed) {
    717             nJoySticks++;
    718         }
    719         device = device->pNext;
    720     }
    721 
    722     return nJoySticks;
    723 }
    724 
    725 static void
    726 DARWIN_JoystickDetect(void)
    727 {
    728     recDevice *device = gpDeviceList;
    729     while (device) {
    730         if (device->removed) {
    731             device = FreeDevice(device);
    732         } else {
    733             device = device->pNext;
    734         }
    735     }
    736 
    737     /* run this after the checks above so we don't set device->removed and delete the device before
    738        DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
    739     while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
    740         /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
    741     }
    742 }
    743 
    744 /* Function to get the device-dependent name of a joystick */
    745 const char *
    746 DARWIN_JoystickGetDeviceName(int device_index)
    747 {
    748     recDevice *device = GetDeviceForIndex(device_index);
    749     return device ? device->product : "UNKNOWN";
    750 }
    751 
    752 static int
    753 DARWIN_JoystickGetDevicePlayerIndex(int device_index)
    754 {
    755     return -1;
    756 }
    757 
    758 static void
    759 DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
    760 {
    761 }
    762 
    763 static SDL_JoystickGUID
    764 DARWIN_JoystickGetDeviceGUID( int device_index )
    765 {
    766     recDevice *device = GetDeviceForIndex(device_index);
    767     SDL_JoystickGUID guid;
    768     if (device) {
    769         guid = device->guid;
    770     } else {
    771         SDL_zero(guid);
    772     }
    773     return guid;
    774 }
    775 
    776 static SDL_JoystickID
    777 DARWIN_JoystickGetDeviceInstanceID(int device_index)
    778 {
    779     recDevice *device = GetDeviceForIndex(device_index);
    780     return device ? device->instance_id : 0;
    781 }
    782 
    783 static int
    784 DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index)
    785 {
    786     recDevice *device = GetDeviceForIndex(device_index);
    787 
    788     joystick->instance_id = device->instance_id;
    789     joystick->hwdata = device;
    790     device->joystick = joystick;
    791     joystick->name = device->product;
    792 
    793     joystick->naxes = device->axes;
    794     joystick->nhats = device->hats;
    795     joystick->nballs = 0;
    796     joystick->nbuttons = device->buttons;
    797     return 0;
    798 }
    799 
    800 /*
    801  * Like strerror but for force feedback errors.
    802  */
    803 static const char *
    804 FFStrError(unsigned int err)
    805 {
    806     switch (err) {
    807     case FFERR_DEVICEFULL:
    808         return "device full";
    809     /* This should be valid, but for some reason isn't defined... */
    810     /* case FFERR_DEVICENOTREG:
    811         return "device not registered"; */
    812     case FFERR_DEVICEPAUSED:
    813         return "device paused";
    814     case FFERR_DEVICERELEASED:
    815         return "device released";
    816     case FFERR_EFFECTPLAYING:
    817         return "effect playing";
    818     case FFERR_EFFECTTYPEMISMATCH:
    819         return "effect type mismatch";
    820     case FFERR_EFFECTTYPENOTSUPPORTED:
    821         return "effect type not supported";
    822     case FFERR_GENERIC:
    823         return "undetermined error";
    824     case FFERR_HASEFFECTS:
    825         return "device has effects";
    826     case FFERR_INCOMPLETEEFFECT:
    827         return "incomplete effect";
    828     case FFERR_INTERNAL:
    829         return "internal fault";
    830     case FFERR_INVALIDDOWNLOADID:
    831         return "invalid download id";
    832     case FFERR_INVALIDPARAM:
    833         return "invalid parameter";
    834     case FFERR_MOREDATA:
    835         return "more data";
    836     case FFERR_NOINTERFACE:
    837         return "interface not supported";
    838     case FFERR_NOTDOWNLOADED:
    839         return "effect is not downloaded";
    840     case FFERR_NOTINITIALIZED:
    841         return "object has not been initialized";
    842     case FFERR_OUTOFMEMORY:
    843         return "out of memory";
    844     case FFERR_UNPLUGGED:
    845         return "device is unplugged";
    846     case FFERR_UNSUPPORTED:
    847         return "function call unsupported";
    848     case FFERR_UNSUPPORTEDAXIS:
    849         return "axis unsupported";
    850 
    851     default:
    852         return "unknown error";
    853     }
    854 }
    855 
    856 static int
    857 DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude)
    858 {
    859     HRESULT result;
    860 
    861     if (!device->ffdevice) {
    862         result = FFCreateDevice(device->ffservice, &device->ffdevice);
    863         if (result != FF_OK) {
    864             return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));
    865         }
    866     }
    867 
    868     /* Reset and then enable actuators */
    869     result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);
    870     if (result != FF_OK) {
    871         return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));
    872     }
    873 
    874     result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);
    875     if (result != FF_OK) {
    876         return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));
    877     }
    878 
    879     /* Create the effect */
    880     device->ffeffect = CreateRumbleEffectData(magnitude);
    881     if (!device->ffeffect) {
    882         return SDL_OutOfMemory();
    883     }
    884 
    885     result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,
    886                                device->ffeffect, &device->ffeffect_ref);
    887     if (result != FF_OK) {
    888         return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));
    889     }
    890     return 0;
    891 }
    892 
    893 static int
    894 DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
    895 {
    896     HRESULT result;
    897     recDevice *device = joystick->hwdata;
    898 
    899     /* Scale and average the two rumble strengths */
    900     Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
    901     
    902     if (!device) {
    903         return SDL_SetError("Rumble failed, device disconnected");
    904     }
    905 
    906     if (!device->ffservice) {
    907         return SDL_Unsupported();
    908     }
    909 
    910     if (device->ff_initialized) {
    911         FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
    912         periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
    913 
    914         result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
    915                                     (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
    916         if (result != FF_OK) {
    917             return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
    918         }
    919     } else {
    920         if (DARWIN_JoystickInitRumble(device, magnitude) < 0) {
    921             return -1;
    922         }
    923         device->ff_initialized = SDL_TRUE;
    924     }
    925 
    926     result = FFEffectStart(device->ffeffect_ref, 1, 0);
    927     if (result != FF_OK) {
    928         return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
    929     }
    930     return 0;
    931 }
    932 
    933 static int
    934 DARWIN_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 right_rumble)
    935 {
    936     return SDL_Unsupported();
    937 }
    938 
    939 static SDL_bool
    940 DARWIN_JoystickHasLED(SDL_Joystick * joystick)
    941 {
    942     return SDL_FALSE;
    943 }
    944 
    945 static int
    946 DARWIN_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
    947 {
    948     return SDL_Unsupported();
    949 }
    950 
    951 static int
    952 DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
    953 {
    954     return SDL_Unsupported();
    955 }
    956 
    957 static void
    958 DARWIN_JoystickUpdate(SDL_Joystick * joystick)
    959 {
    960     recDevice *device = joystick->hwdata;
    961     recElement *element;
    962     SInt32 value, range;
    963     int i;
    964 
    965     if (!device) {
    966         return;
    967     }
    968 
    969     if (device->removed) {      /* device was unplugged; ignore it. */
    970         if (joystick->hwdata) {
    971             joystick->hwdata = NULL;
    972         }
    973         return;
    974     }
    975 
    976     element = device->firstAxis;
    977     i = 0;
    978 
    979     int goodRead = SDL_FALSE;
    980     while (element) {
    981         goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);
    982         if (goodRead) {
    983             SDL_PrivateJoystickAxis(joystick, i, value);
    984         }
    985 
    986         element = element->pNext;
    987         ++i;
    988     }
    989 
    990     element = device->firstButton;
    991     i = 0;
    992     while (element) {
    993         goodRead = GetHIDElementState(device, element, &value);
    994         if (goodRead) {
    995             if (value > 1) {          /* handle pressure-sensitive buttons */
    996                 value = 1;
    997             }
    998             SDL_PrivateJoystickButton(joystick, i, value);
    999         }
   1000 
   1001         element = element->pNext;
   1002         ++i;
   1003     }
   1004 
   1005     element = device->firstHat;
   1006     i = 0;
   1007     
   1008     while (element) {
   1009         Uint8 pos = 0;
   1010 
   1011         range = (element->max - element->min + 1);
   1012         goodRead = GetHIDElementState(device, element, &value);
   1013         if (goodRead) {
   1014             value -= element->min;
   1015             if (range == 4) {         /* 4 position hatswitch - scale up value */
   1016                 value *= 2;
   1017             } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
   1018                 value = -1;
   1019             }
   1020             switch (value) {
   1021             case 0:
   1022                 pos = SDL_HAT_UP;
   1023                 break;
   1024             case 1:
   1025                 pos = SDL_HAT_RIGHTUP;
   1026                 break;
   1027             case 2:
   1028                 pos = SDL_HAT_RIGHT;
   1029                 break;
   1030             case 3:
   1031                 pos = SDL_HAT_RIGHTDOWN;
   1032                 break;
   1033             case 4:
   1034                 pos = SDL_HAT_DOWN;
   1035                 break;
   1036             case 5:
   1037                 pos = SDL_HAT_LEFTDOWN;
   1038                 break;
   1039             case 6:
   1040                 pos = SDL_HAT_LEFT;
   1041                 break;
   1042             case 7:
   1043                 pos = SDL_HAT_LEFTUP;
   1044                 break;
   1045             default:
   1046                 /* Every other value is mapped to center. We do that because some
   1047                  * joysticks use 8 and some 15 for this value, and apparently
   1048                  * there are even more variants out there - so we try to be generous.
   1049                  */
   1050                 pos = SDL_HAT_CENTERED;
   1051                 break;
   1052             }
   1053 
   1054             SDL_PrivateJoystickHat(joystick, i, pos);
   1055         }
   1056         
   1057         element = element->pNext;
   1058         ++i;
   1059     }
   1060 }
   1061 
   1062 static void
   1063 DARWIN_JoystickClose(SDL_Joystick * joystick)
   1064 {
   1065     recDevice *device = joystick->hwdata;
   1066     if (device) {
   1067         device->joystick = NULL;
   1068     }
   1069 }
   1070 
   1071 static void
   1072 DARWIN_JoystickQuit(void)
   1073 {
   1074     while (FreeDevice(gpDeviceList)) {
   1075         /* spin */
   1076     }
   1077 
   1078     if (hidman) {
   1079         IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
   1080         IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
   1081         CFRelease(hidman);
   1082         hidman = NULL;
   1083     }
   1084 }
   1085 
   1086 static SDL_bool
   1087 DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
   1088 {
   1089     return SDL_FALSE;
   1090 }
   1091 
   1092 SDL_JoystickDriver SDL_DARWIN_JoystickDriver =
   1093 {
   1094     DARWIN_JoystickInit,
   1095     DARWIN_JoystickGetCount,
   1096     DARWIN_JoystickDetect,
   1097     DARWIN_JoystickGetDeviceName,
   1098     DARWIN_JoystickGetDevicePlayerIndex,
   1099     DARWIN_JoystickSetDevicePlayerIndex,
   1100     DARWIN_JoystickGetDeviceGUID,
   1101     DARWIN_JoystickGetDeviceInstanceID,
   1102     DARWIN_JoystickOpen,
   1103     DARWIN_JoystickRumble,
   1104     DARWIN_JoystickRumbleTriggers,
   1105     DARWIN_JoystickHasLED,
   1106     DARWIN_JoystickSetLED,
   1107     DARWIN_JoystickSetSensorsEnabled,
   1108     DARWIN_JoystickUpdate,
   1109     DARWIN_JoystickClose,
   1110     DARWIN_JoystickQuit,
   1111     DARWIN_JoystickGetGamepadMapping
   1112 };
   1113 
   1114 #endif /* SDL_JOYSTICK_IOKIT */
   1115 
   1116 /* vi: set ts=4 sw=4 expandtab: */