|  | /* | 
|  | Simple DirectMedia Layer | 
|  | Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> | 
|  |  | 
|  | This software is provided 'as-is', without any express or implied | 
|  | warranty.  In no event will the authors be held liable for any damages | 
|  | arising from the use of this software. | 
|  |  | 
|  | Permission is granted to anyone to use this software for any purpose, | 
|  | including commercial applications, and to alter it and redistribute it | 
|  | freely, subject to the following restrictions: | 
|  |  | 
|  | 1. The origin of this software must not be misrepresented; you must not | 
|  | claim that you wrote the original software. If you use this software | 
|  | in a product, an acknowledgment in the product documentation would be | 
|  | appreciated but is not required. | 
|  | 2. Altered source versions must be plainly marked as such, and must not be | 
|  | misrepresented as being the original software. | 
|  | 3. This notice may not be removed or altered from any source distribution. | 
|  | */ | 
|  | #include "../../SDL_internal.h" | 
|  |  | 
|  | #ifdef SDL_JOYSTICK_IOKIT | 
|  |  | 
|  | #include "SDL_events.h" | 
|  | #include "SDL_joystick.h" | 
|  | #include "../SDL_sysjoystick.h" | 
|  | #include "../SDL_joystick_c.h" | 
|  | #include "SDL_sysjoystick_c.h" | 
|  | #include "../hidapi/SDL_hidapijoystick_c.h" | 
|  | #include "../../haptic/darwin/SDL_syshaptic_c.h"    /* For haptic hot plugging */ | 
|  |  | 
|  |  | 
|  | #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick") | 
|  |  | 
|  | #define CONVERT_MAGNITUDE(x)    (((x)*10000) / 0x7FFF) | 
|  |  | 
|  | /* The base object of the HID Manager API */ | 
|  | static IOHIDManagerRef hidman = NULL; | 
|  |  | 
|  | /* Linked list of all available devices */ | 
|  | static recDevice *gpDeviceList = NULL; | 
|  |  | 
|  | void FreeRumbleEffectData(FFEFFECT *effect) | 
|  | { | 
|  | if (!effect) { | 
|  | return; | 
|  | } | 
|  | SDL_free(effect->rgdwAxes); | 
|  | SDL_free(effect->rglDirection); | 
|  | SDL_free(effect->lpvTypeSpecificParams); | 
|  | SDL_free(effect); | 
|  | } | 
|  |  | 
|  | FFEFFECT *CreateRumbleEffectData(Sint16 magnitude) | 
|  | { | 
|  | FFEFFECT *effect; | 
|  | FFPERIODIC *periodic; | 
|  |  | 
|  | /* Create the effect */ | 
|  | effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect)); | 
|  | if (!effect) { | 
|  | return NULL; | 
|  | } | 
|  | effect->dwSize = sizeof(*effect); | 
|  | effect->dwGain = 10000; | 
|  | effect->dwFlags = FFEFF_OBJECTOFFSETS; | 
|  | effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; /* In microseconds. */ | 
|  | effect->dwTriggerButton = FFEB_NOTRIGGER; | 
|  |  | 
|  | effect->cAxes = 2; | 
|  | effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD)); | 
|  | if (!effect->rgdwAxes) { | 
|  | FreeRumbleEffectData(effect); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG)); | 
|  | if (!effect->rglDirection) { | 
|  | FreeRumbleEffectData(effect); | 
|  | return NULL; | 
|  | } | 
|  | effect->dwFlags |= FFEFF_CARTESIAN; | 
|  |  | 
|  | periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic)); | 
|  | if (!periodic) { | 
|  | FreeRumbleEffectData(effect); | 
|  | return NULL; | 
|  | } | 
|  | periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); | 
|  | periodic->dwPeriod = 1000000; | 
|  |  | 
|  | effect->cbTypeSpecificParams = sizeof(*periodic); | 
|  | effect->lpvTypeSpecificParams = periodic; | 
|  |  | 
|  | return effect; | 
|  | } | 
|  |  | 
|  | static recDevice *GetDeviceForIndex(int device_index) | 
|  | { | 
|  | recDevice *device = gpDeviceList; | 
|  | while (device) { | 
|  | if (!device->removed) { | 
|  | if (device_index == 0) | 
|  | break; | 
|  |  | 
|  | --device_index; | 
|  | } | 
|  | device = device->pNext; | 
|  | } | 
|  | return device; | 
|  | } | 
|  |  | 
|  | static void | 
|  | FreeElementList(recElement *pElement) | 
|  | { | 
|  | while (pElement) { | 
|  | recElement *pElementNext = pElement->pNext; | 
|  | SDL_free(pElement); | 
|  | pElement = pElementNext; | 
|  | } | 
|  | } | 
|  |  | 
|  | static recDevice * | 
|  | FreeDevice(recDevice *removeDevice) | 
|  | { | 
|  | recDevice *pDeviceNext = NULL; | 
|  | if (removeDevice) { | 
|  | if (removeDevice->deviceRef) { | 
|  | if (removeDevice->runLoopAttached) { | 
|  | /* Calling IOHIDDeviceUnscheduleFromRunLoop without a prior, | 
|  | * paired call to IOHIDDeviceScheduleWithRunLoop can lead | 
|  | * to crashes in MacOS 10.14.x and earlier.  This doesn't | 
|  | * appear to be a problem in MacOS 10.15.x, but we'll | 
|  | * do it anyways.  (Part-of fix for Bug 5034) | 
|  | */ | 
|  | IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); | 
|  | } | 
|  | CFRelease(removeDevice->deviceRef); | 
|  | removeDevice->deviceRef = NULL; | 
|  | } | 
|  |  | 
|  | /* clear out any reference to removeDevice from an associated, | 
|  | * live instance of SDL_Joystick  (Part-of fix for Bug 5034) | 
|  | */ | 
|  | SDL_LockJoysticks(); | 
|  | if (removeDevice->joystick) { | 
|  | removeDevice->joystick->hwdata = NULL; | 
|  | } | 
|  | SDL_UnlockJoysticks(); | 
|  |  | 
|  | /* save next device prior to disposing of this device */ | 
|  | pDeviceNext = removeDevice->pNext; | 
|  |  | 
|  | if ( gpDeviceList == removeDevice ) { | 
|  | gpDeviceList = pDeviceNext; | 
|  | } else if (gpDeviceList) { | 
|  | recDevice *device = gpDeviceList; | 
|  | while (device->pNext != removeDevice) { | 
|  | device = device->pNext; | 
|  | } | 
|  | device->pNext = pDeviceNext; | 
|  | } | 
|  | removeDevice->pNext = NULL; | 
|  |  | 
|  | /* free element lists */ | 
|  | FreeElementList(removeDevice->firstAxis); | 
|  | FreeElementList(removeDevice->firstButton); | 
|  | FreeElementList(removeDevice->firstHat); | 
|  |  | 
|  | SDL_free(removeDevice); | 
|  | } | 
|  | return pDeviceNext; | 
|  | } | 
|  |  | 
|  | static SDL_bool | 
|  | GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue) | 
|  | { | 
|  | SInt32 value = 0; | 
|  | int returnValue = SDL_FALSE; | 
|  |  | 
|  | if (pDevice && pDevice->deviceRef && pElement) { | 
|  | IOHIDValueRef valueRef; | 
|  | if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) { | 
|  | value = (SInt32) IOHIDValueGetIntegerValue(valueRef); | 
|  |  | 
|  | /* record min and max for auto calibration */ | 
|  | if (value < pElement->minReport) { | 
|  | pElement->minReport = value; | 
|  | } | 
|  | if (value > pElement->maxReport) { | 
|  | pElement->maxReport = value; | 
|  | } | 
|  | *pValue = value; | 
|  |  | 
|  | returnValue = SDL_TRUE; | 
|  | } | 
|  | } | 
|  | return returnValue; | 
|  | } | 
|  |  | 
|  | static SDL_bool | 
|  | GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max, SInt32 *pValue) | 
|  | { | 
|  | const float deviceScale = max - min; | 
|  | const float readScale = pElement->maxReport - pElement->minReport; | 
|  | int returnValue = SDL_FALSE; | 
|  | if (GetHIDElementState(pDevice, pElement, pValue)) | 
|  | { | 
|  | if (readScale == 0) { | 
|  | returnValue = SDL_TRUE;           /* no scaling at all */ | 
|  | } | 
|  | else | 
|  | { | 
|  | *pValue = ((*pValue - pElement->minReport) * deviceScale / readScale) + min; | 
|  | returnValue = SDL_TRUE; | 
|  | } | 
|  | } | 
|  | return returnValue; | 
|  | } | 
|  |  | 
|  | static void | 
|  | JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender) | 
|  | { | 
|  | recDevice *device = (recDevice *) ctx; | 
|  | device->removed = SDL_TRUE; | 
|  | if (device->deviceRef) { | 
|  | // deviceRef was invalidated due to the remove | 
|  | CFRelease(device->deviceRef); | 
|  | device->deviceRef = NULL; | 
|  | } | 
|  | if (device->ffeffect_ref) { | 
|  | FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref); | 
|  | device->ffeffect_ref = NULL; | 
|  | } | 
|  | if (device->ffeffect) { | 
|  | FreeRumbleEffectData(device->ffeffect); | 
|  | device->ffeffect = NULL; | 
|  | } | 
|  | if (device->ffdevice) { | 
|  | FFReleaseDevice(device->ffdevice); | 
|  | device->ffdevice = NULL; | 
|  | device->ff_initialized = SDL_FALSE; | 
|  | } | 
|  | #if SDL_HAPTIC_IOKIT | 
|  | MacHaptic_MaybeRemoveDevice(device->ffservice); | 
|  | #endif | 
|  |  | 
|  | SDL_PrivateJoystickRemoved(device->instance_id); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void AddHIDElement(const void *value, void *parameter); | 
|  |  | 
|  | /* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */ | 
|  | static void | 
|  | AddHIDElements(CFArrayRef array, recDevice *pDevice) | 
|  | { | 
|  | const CFRange range = { 0, CFArrayGetCount(array) }; | 
|  | CFArrayApplyFunction(array, range, AddHIDElement, pDevice); | 
|  | } | 
|  |  | 
|  | static SDL_bool | 
|  | ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) { | 
|  | while (listitem) { | 
|  | if (listitem->cookie == cookie) { | 
|  | return SDL_TRUE; | 
|  | } | 
|  | listitem = listitem->pNext; | 
|  | } | 
|  | return SDL_FALSE; | 
|  | } | 
|  |  | 
|  | /* See if we care about this HID element, and if so, note it in our recDevice. */ | 
|  | static void | 
|  | AddHIDElement(const void *value, void *parameter) | 
|  | { | 
|  | recDevice *pDevice = (recDevice *) parameter; | 
|  | IOHIDElementRef refElement = (IOHIDElementRef) value; | 
|  | const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0; | 
|  |  | 
|  | if (refElement && (elementTypeID == IOHIDElementGetTypeID())) { | 
|  | const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement); | 
|  | const uint32_t usagePage = IOHIDElementGetUsagePage(refElement); | 
|  | const uint32_t usage = IOHIDElementGetUsage(refElement); | 
|  | recElement *element = NULL; | 
|  | recElement **headElement = NULL; | 
|  |  | 
|  | /* look at types of interest */ | 
|  | switch (IOHIDElementGetType(refElement)) { | 
|  | case kIOHIDElementTypeInput_Misc: | 
|  | case kIOHIDElementTypeInput_Button: | 
|  | case kIOHIDElementTypeInput_Axis: { | 
|  | switch (usagePage) {    /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */ | 
|  | case kHIDPage_GenericDesktop: | 
|  | switch (usage) { | 
|  | case kHIDUsage_GD_X: | 
|  | case kHIDUsage_GD_Y: | 
|  | case kHIDUsage_GD_Z: | 
|  | case kHIDUsage_GD_Rx: | 
|  | case kHIDUsage_GD_Ry: | 
|  | case kHIDUsage_GD_Rz: | 
|  | case kHIDUsage_GD_Slider: | 
|  | case kHIDUsage_GD_Dial: | 
|  | case kHIDUsage_GD_Wheel: | 
|  | if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) { | 
|  | element = (recElement *) SDL_calloc(1, sizeof (recElement)); | 
|  | if (element) { | 
|  | pDevice->axes++; | 
|  | headElement = &(pDevice->firstAxis); | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case kHIDUsage_GD_Hatswitch: | 
|  | if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) { | 
|  | element = (recElement *) SDL_calloc(1, sizeof (recElement)); | 
|  | if (element) { | 
|  | pDevice->hats++; | 
|  | headElement = &(pDevice->firstHat); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case kHIDUsage_GD_DPadUp: | 
|  | case kHIDUsage_GD_DPadDown: | 
|  | case kHIDUsage_GD_DPadRight: | 
|  | case kHIDUsage_GD_DPadLeft: | 
|  | case kHIDUsage_GD_Start: | 
|  | case kHIDUsage_GD_Select: | 
|  | case kHIDUsage_GD_SystemMainMenu: | 
|  | if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) { | 
|  | element = (recElement *) SDL_calloc(1, sizeof (recElement)); | 
|  | if (element) { | 
|  | pDevice->buttons++; | 
|  | headElement = &(pDevice->firstButton); | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case kHIDPage_Simulation: | 
|  | switch (usage) { | 
|  | case kHIDUsage_Sim_Rudder: | 
|  | case kHIDUsage_Sim_Throttle: | 
|  | case kHIDUsage_Sim_Accelerator: | 
|  | case kHIDUsage_Sim_Brake: | 
|  | if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) { | 
|  | element = (recElement *) SDL_calloc(1, sizeof (recElement)); | 
|  | if (element) { | 
|  | pDevice->axes++; | 
|  | headElement = &(pDevice->firstAxis); | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case kHIDPage_Button: | 
|  | case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */ | 
|  | if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) { | 
|  | element = (recElement *) SDL_calloc(1, sizeof (recElement)); | 
|  | if (element) { | 
|  | pDevice->buttons++; | 
|  | headElement = &(pDevice->firstButton); | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case kIOHIDElementTypeCollection: { | 
|  | CFArrayRef array = IOHIDElementGetChildren(refElement); | 
|  | if (array) { | 
|  | AddHIDElements(array, pDevice); | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (element && headElement) {       /* add to list */ | 
|  | recElement *elementPrevious = NULL; | 
|  | recElement *elementCurrent = *headElement; | 
|  | while (elementCurrent && usage >= elementCurrent->usage) { | 
|  | elementPrevious = elementCurrent; | 
|  | elementCurrent = elementCurrent->pNext; | 
|  | } | 
|  | if (elementPrevious) { | 
|  | elementPrevious->pNext = element; | 
|  | } else { | 
|  | *headElement = element; | 
|  | } | 
|  |  | 
|  | element->elementRef = refElement; | 
|  | element->usagePage = usagePage; | 
|  | element->usage = usage; | 
|  | element->pNext = elementCurrent; | 
|  |  | 
|  | element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement); | 
|  | element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement); | 
|  | element->cookie = IOHIDElementGetCookie(refElement); | 
|  |  | 
|  | pDevice->elements++; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static SDL_bool | 
|  | GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) | 
|  | { | 
|  | Sint32 vendor = 0; | 
|  | Sint32 product = 0; | 
|  | Sint32 version = 0; | 
|  | char *name; | 
|  | char manufacturer_string[256]; | 
|  | char product_string[256]; | 
|  | CFTypeRef refCF = NULL; | 
|  | CFArrayRef array = NULL; | 
|  | Uint16 *guid16 = (Uint16 *)pDevice->guid.data; | 
|  |  | 
|  | /* get usage page and usage */ | 
|  | refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey)); | 
|  | if (refCF) { | 
|  | CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage); | 
|  | } | 
|  | if (pDevice->usagePage != kHIDPage_GenericDesktop) { | 
|  | return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */ | 
|  | } | 
|  |  | 
|  | refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey)); | 
|  | if (refCF) { | 
|  | CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage); | 
|  | } | 
|  |  | 
|  | if ((pDevice->usage != kHIDUsage_GD_Joystick && | 
|  | pDevice->usage != kHIDUsage_GD_GamePad && | 
|  | pDevice->usage != kHIDUsage_GD_MultiAxisController)) { | 
|  | return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */ | 
|  | } | 
|  |  | 
|  | /* Make sure we retain the use of the IOKit-provided device-object, | 
|  | lest the device get disconnected and we try to use it.  (Fixes | 
|  | SDL-Bugzilla #4961, aka. https://bugzilla.libsdl.org/show_bug.cgi?id=4961 ) | 
|  | */ | 
|  | CFRetain(hidDevice); | 
|  |  | 
|  | /* Now that we've CFRetain'ed the device-object (for our use), we'll | 
|  | save the reference to it. | 
|  | */ | 
|  | pDevice->deviceRef = hidDevice; | 
|  |  | 
|  | refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey)); | 
|  | if (refCF) { | 
|  | CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor); | 
|  | } | 
|  |  | 
|  | refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey)); | 
|  | if (refCF) { | 
|  | CFNumberGetValue(refCF, kCFNumberSInt32Type, &product); | 
|  | } | 
|  |  | 
|  | refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey)); | 
|  | if (refCF) { | 
|  | CFNumberGetValue(refCF, kCFNumberSInt32Type, &version); | 
|  | } | 
|  |  | 
|  | /* get device name */ | 
|  | refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey)); | 
|  | if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) { | 
|  | manufacturer_string[0] = '\0'; | 
|  | } | 
|  | refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey)); | 
|  | if ((!refCF) || (!CFStringGetCString(refCF, product_string, sizeof(product_string), kCFStringEncodingUTF8))) { | 
|  | product_string[0] = '\0'; | 
|  | } | 
|  | name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string); | 
|  | if (name) { | 
|  | SDL_strlcpy(pDevice->product, name, sizeof(pDevice->product)); | 
|  | SDL_free(name); | 
|  | } | 
|  |  | 
|  | #ifdef SDL_JOYSTICK_HIDAPI | 
|  | if (HIDAPI_IsDevicePresent(vendor, product, version, pDevice->product)) { | 
|  | /* The HIDAPI driver is taking care of this device */ | 
|  | return 0; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data)); | 
|  |  | 
|  | if (vendor && product) { | 
|  | *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB); | 
|  | *guid16++ = 0; | 
|  | *guid16++ = SDL_SwapLE16((Uint16)vendor); | 
|  | *guid16++ = 0; | 
|  | *guid16++ = SDL_SwapLE16((Uint16)product); | 
|  | *guid16++ = 0; | 
|  | *guid16++ = SDL_SwapLE16((Uint16)version); | 
|  | *guid16++ = 0; | 
|  | } else { | 
|  | *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH); | 
|  | *guid16++ = 0; | 
|  | SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4); | 
|  | } | 
|  |  | 
|  | array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone); | 
|  | if (array) { | 
|  | AddHIDElements(array, pDevice); | 
|  | CFRelease(array); | 
|  | } | 
|  |  | 
|  | return SDL_TRUE; | 
|  | } | 
|  |  | 
|  | static SDL_bool | 
|  | JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject) | 
|  | { | 
|  | recDevice *i; | 
|  | for (i = gpDeviceList; i != NULL; i = i->pNext) { | 
|  | if (i->deviceRef == ioHIDDeviceObject) { | 
|  | return SDL_TRUE; | 
|  | } | 
|  | } | 
|  | return SDL_FALSE; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) | 
|  | { | 
|  | recDevice *device; | 
|  | int device_index = 0; | 
|  | io_service_t ioservice; | 
|  |  | 
|  | if (res != kIOReturnSuccess) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (JoystickAlreadyKnown(ioHIDDeviceObject)) { | 
|  | return;  /* IOKit sent us a duplicate. */ | 
|  | } | 
|  |  | 
|  | device = (recDevice *) SDL_calloc(1, sizeof(recDevice)); | 
|  | if (!device) { | 
|  | SDL_OutOfMemory(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!GetDeviceInfo(ioHIDDeviceObject, device)) { | 
|  | FreeDevice(device); | 
|  | return;   /* not a device we care about, probably. */ | 
|  | } | 
|  |  | 
|  | if (SDL_ShouldIgnoreJoystick(device->product, device->guid)) { | 
|  | FreeDevice(device); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Get notified when this device is disconnected. */ | 
|  | IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device); | 
|  | IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); | 
|  | device->runLoopAttached = SDL_TRUE; | 
|  |  | 
|  | /* Allocate an instance ID for this device */ | 
|  | device->instance_id = SDL_GetNextJoystickInstanceID(); | 
|  |  | 
|  | /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */ | 
|  | ioservice = IOHIDDeviceGetService(ioHIDDeviceObject); | 
|  | if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) { | 
|  | device->ffservice = ioservice; | 
|  | #if SDL_HAPTIC_IOKIT | 
|  | MacHaptic_MaybeAddDevice(ioservice); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /* Add device to the end of the list */ | 
|  | if ( !gpDeviceList ) { | 
|  | gpDeviceList = device; | 
|  | } else { | 
|  | recDevice *curdevice; | 
|  |  | 
|  | curdevice = gpDeviceList; | 
|  | while ( curdevice->pNext ) { | 
|  | ++device_index; | 
|  | curdevice = curdevice->pNext; | 
|  | } | 
|  | curdevice->pNext = device; | 
|  | ++device_index;  /* bump by one since we counted by pNext. */ | 
|  | } | 
|  |  | 
|  | SDL_PrivateJoystickAdded(device->instance_id); | 
|  | } | 
|  |  | 
|  | static SDL_bool | 
|  | ConfigHIDManager(CFArrayRef matchingArray) | 
|  | { | 
|  | CFRunLoopRef runloop = CFRunLoopGetCurrent(); | 
|  |  | 
|  | if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { | 
|  | return SDL_FALSE; | 
|  | } | 
|  |  | 
|  | IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray); | 
|  | IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL); | 
|  | IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE); | 
|  |  | 
|  | while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) { | 
|  | /* no-op. Callback fires once per existing device. */ | 
|  | } | 
|  |  | 
|  | /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */ | 
|  |  | 
|  | return SDL_TRUE;  /* good to go. */ | 
|  | } | 
|  |  | 
|  |  | 
|  | static CFDictionaryRef | 
|  | CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay) | 
|  | { | 
|  | CFDictionaryRef retval = NULL; | 
|  | CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); | 
|  | CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); | 
|  | const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) }; | 
|  | const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef }; | 
|  |  | 
|  | if (pageNumRef && usageNumRef) { | 
|  | retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | 
|  | } | 
|  |  | 
|  | if (pageNumRef) { | 
|  | CFRelease(pageNumRef); | 
|  | } | 
|  | if (usageNumRef) { | 
|  | CFRelease(usageNumRef); | 
|  | } | 
|  |  | 
|  | if (!retval) { | 
|  | *okay = 0; | 
|  | } | 
|  |  | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | static SDL_bool | 
|  | CreateHIDManager(void) | 
|  | { | 
|  | SDL_bool retval = SDL_FALSE; | 
|  | int okay = 1; | 
|  | const void *vals[] = { | 
|  | (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), | 
|  | (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), | 
|  | (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), | 
|  | }; | 
|  | const size_t numElements = SDL_arraysize(vals); | 
|  | CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL; | 
|  | size_t i; | 
|  |  | 
|  | for (i = 0; i < numElements; i++) { | 
|  | if (vals[i]) { | 
|  | CFRelease((CFTypeRef) vals[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (array) { | 
|  | hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); | 
|  | if (hidman != NULL) { | 
|  | retval = ConfigHIDManager(array); | 
|  | } | 
|  | CFRelease(array); | 
|  | } | 
|  |  | 
|  | return retval; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int | 
|  | DARWIN_JoystickInit(void) | 
|  | { | 
|  | if (gpDeviceList) { | 
|  | return SDL_SetError("Joystick: Device list already inited."); | 
|  | } | 
|  |  | 
|  | if (!CreateHIDManager()) { | 
|  | return SDL_SetError("Joystick: Couldn't initialize HID Manager"); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | DARWIN_JoystickGetCount(void) | 
|  | { | 
|  | recDevice *device = gpDeviceList; | 
|  | int nJoySticks = 0; | 
|  |  | 
|  | while (device) { | 
|  | if (!device->removed) { | 
|  | nJoySticks++; | 
|  | } | 
|  | device = device->pNext; | 
|  | } | 
|  |  | 
|  | return nJoySticks; | 
|  | } | 
|  |  | 
|  | static void | 
|  | DARWIN_JoystickDetect(void) | 
|  | { | 
|  | recDevice *device = gpDeviceList; | 
|  | while (device) { | 
|  | if (device->removed) { | 
|  | device = FreeDevice(device); | 
|  | } else { | 
|  | device = device->pNext; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* run this after the checks above so we don't set device->removed and delete the device before | 
|  | DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */ | 
|  | while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) { | 
|  | /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */ | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Function to get the device-dependent name of a joystick */ | 
|  | const char * | 
|  | DARWIN_JoystickGetDeviceName(int device_index) | 
|  | { | 
|  | recDevice *device = GetDeviceForIndex(device_index); | 
|  | return device ? device->product : "UNKNOWN"; | 
|  | } | 
|  |  | 
|  | static int | 
|  | DARWIN_JoystickGetDevicePlayerIndex(int device_index) | 
|  | { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static void | 
|  | DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index) | 
|  | { | 
|  | } | 
|  |  | 
|  | static SDL_JoystickGUID | 
|  | DARWIN_JoystickGetDeviceGUID( int device_index ) | 
|  | { | 
|  | recDevice *device = GetDeviceForIndex(device_index); | 
|  | SDL_JoystickGUID guid; | 
|  | if (device) { | 
|  | guid = device->guid; | 
|  | } else { | 
|  | SDL_zero(guid); | 
|  | } | 
|  | return guid; | 
|  | } | 
|  |  | 
|  | static SDL_JoystickID | 
|  | DARWIN_JoystickGetDeviceInstanceID(int device_index) | 
|  | { | 
|  | recDevice *device = GetDeviceForIndex(device_index); | 
|  | return device ? device->instance_id : 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index) | 
|  | { | 
|  | recDevice *device = GetDeviceForIndex(device_index); | 
|  |  | 
|  | joystick->instance_id = device->instance_id; | 
|  | joystick->hwdata = device; | 
|  | device->joystick = joystick; | 
|  | joystick->name = device->product; | 
|  |  | 
|  | joystick->naxes = device->axes; | 
|  | joystick->nhats = device->hats; | 
|  | joystick->nballs = 0; | 
|  | joystick->nbuttons = device->buttons; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Like strerror but for force feedback errors. | 
|  | */ | 
|  | static const char * | 
|  | FFStrError(unsigned int err) | 
|  | { | 
|  | switch (err) { | 
|  | case FFERR_DEVICEFULL: | 
|  | return "device full"; | 
|  | /* This should be valid, but for some reason isn't defined... */ | 
|  | /* case FFERR_DEVICENOTREG: | 
|  | return "device not registered"; */ | 
|  | case FFERR_DEVICEPAUSED: | 
|  | return "device paused"; | 
|  | case FFERR_DEVICERELEASED: | 
|  | return "device released"; | 
|  | case FFERR_EFFECTPLAYING: | 
|  | return "effect playing"; | 
|  | case FFERR_EFFECTTYPEMISMATCH: | 
|  | return "effect type mismatch"; | 
|  | case FFERR_EFFECTTYPENOTSUPPORTED: | 
|  | return "effect type not supported"; | 
|  | case FFERR_GENERIC: | 
|  | return "undetermined error"; | 
|  | case FFERR_HASEFFECTS: | 
|  | return "device has effects"; | 
|  | case FFERR_INCOMPLETEEFFECT: | 
|  | return "incomplete effect"; | 
|  | case FFERR_INTERNAL: | 
|  | return "internal fault"; | 
|  | case FFERR_INVALIDDOWNLOADID: | 
|  | return "invalid download id"; | 
|  | case FFERR_INVALIDPARAM: | 
|  | return "invalid parameter"; | 
|  | case FFERR_MOREDATA: | 
|  | return "more data"; | 
|  | case FFERR_NOINTERFACE: | 
|  | return "interface not supported"; | 
|  | case FFERR_NOTDOWNLOADED: | 
|  | return "effect is not downloaded"; | 
|  | case FFERR_NOTINITIALIZED: | 
|  | return "object has not been initialized"; | 
|  | case FFERR_OUTOFMEMORY: | 
|  | return "out of memory"; | 
|  | case FFERR_UNPLUGGED: | 
|  | return "device is unplugged"; | 
|  | case FFERR_UNSUPPORTED: | 
|  | return "function call unsupported"; | 
|  | case FFERR_UNSUPPORTEDAXIS: | 
|  | return "axis unsupported"; | 
|  |  | 
|  | default: | 
|  | return "unknown error"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int | 
|  | DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude) | 
|  | { | 
|  | HRESULT result; | 
|  |  | 
|  | if (!device->ffdevice) { | 
|  | result = FFCreateDevice(device->ffservice, &device->ffdevice); | 
|  | if (result != FF_OK) { | 
|  | return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Reset and then enable actuators */ | 
|  | result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET); | 
|  | if (result != FF_OK) { | 
|  | return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result)); | 
|  | } | 
|  |  | 
|  | result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON); | 
|  | if (result != FF_OK) { | 
|  | return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result)); | 
|  | } | 
|  |  | 
|  | /* Create the effect */ | 
|  | device->ffeffect = CreateRumbleEffectData(magnitude); | 
|  | if (!device->ffeffect) { | 
|  | return SDL_OutOfMemory(); | 
|  | } | 
|  |  | 
|  | result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID, | 
|  | device->ffeffect, &device->ffeffect_ref); | 
|  | if (result != FF_OK) { | 
|  | return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result)); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | 
|  | { | 
|  | HRESULT result; | 
|  | recDevice *device = joystick->hwdata; | 
|  |  | 
|  | /* Scale and average the two rumble strengths */ | 
|  | Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2); | 
|  |  | 
|  | if (!device) { | 
|  | return SDL_SetError("Rumble failed, device disconnected"); | 
|  | } | 
|  |  | 
|  | if (!device->ffservice) { | 
|  | return SDL_Unsupported(); | 
|  | } | 
|  |  | 
|  | if (device->ff_initialized) { | 
|  | FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams); | 
|  | periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); | 
|  |  | 
|  | result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect, | 
|  | (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS)); | 
|  | if (result != FF_OK) { | 
|  | return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result)); | 
|  | } | 
|  | } else { | 
|  | if (DARWIN_JoystickInitRumble(device, magnitude) < 0) { | 
|  | return -1; | 
|  | } | 
|  | device->ff_initialized = SDL_TRUE; | 
|  | } | 
|  |  | 
|  | result = FFEffectStart(device->ffeffect_ref, 1, 0); | 
|  | if (result != FF_OK) { | 
|  | return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result)); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void | 
|  | DARWIN_JoystickUpdate(SDL_Joystick * joystick) | 
|  | { | 
|  | recDevice *device = joystick->hwdata; | 
|  | recElement *element; | 
|  | SInt32 value, range; | 
|  | int i; | 
|  |  | 
|  | if (!device) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (device->removed) {      /* device was unplugged; ignore it. */ | 
|  | if (joystick->hwdata) { | 
|  | joystick->hwdata = NULL; | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | element = device->firstAxis; | 
|  | i = 0; | 
|  |  | 
|  | int goodRead = SDL_FALSE; | 
|  | while (element) { | 
|  | goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); | 
|  | if (goodRead) { | 
|  | SDL_PrivateJoystickAxis(joystick, i, value); | 
|  | } | 
|  |  | 
|  | element = element->pNext; | 
|  | ++i; | 
|  | } | 
|  |  | 
|  | element = device->firstButton; | 
|  | i = 0; | 
|  | while (element) { | 
|  | goodRead = GetHIDElementState(device, element, &value); | 
|  | if (goodRead) { | 
|  | if (value > 1) {          /* handle pressure-sensitive buttons */ | 
|  | value = 1; | 
|  | } | 
|  | SDL_PrivateJoystickButton(joystick, i, value); | 
|  | } | 
|  |  | 
|  | element = element->pNext; | 
|  | ++i; | 
|  | } | 
|  |  | 
|  | element = device->firstHat; | 
|  | i = 0; | 
|  |  | 
|  | while (element) { | 
|  | Uint8 pos = 0; | 
|  |  | 
|  | range = (element->max - element->min + 1); | 
|  | goodRead = GetHIDElementState(device, element, &value); | 
|  | if (goodRead) { | 
|  | value -= element->min; | 
|  | if (range == 4) {         /* 4 position hatswitch - scale up value */ | 
|  | value *= 2; | 
|  | } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */ | 
|  | value = -1; | 
|  | } | 
|  | switch (value) { | 
|  | case 0: | 
|  | pos = SDL_HAT_UP; | 
|  | break; | 
|  | case 1: | 
|  | pos = SDL_HAT_RIGHTUP; | 
|  | break; | 
|  | case 2: | 
|  | pos = SDL_HAT_RIGHT; | 
|  | break; | 
|  | case 3: | 
|  | pos = SDL_HAT_RIGHTDOWN; | 
|  | break; | 
|  | case 4: | 
|  | pos = SDL_HAT_DOWN; | 
|  | break; | 
|  | case 5: | 
|  | pos = SDL_HAT_LEFTDOWN; | 
|  | break; | 
|  | case 6: | 
|  | pos = SDL_HAT_LEFT; | 
|  | break; | 
|  | case 7: | 
|  | pos = SDL_HAT_LEFTUP; | 
|  | break; | 
|  | default: | 
|  | /* Every other value is mapped to center. We do that because some | 
|  | * joysticks use 8 and some 15 for this value, and apparently | 
|  | * there are even more variants out there - so we try to be generous. | 
|  | */ | 
|  | pos = SDL_HAT_CENTERED; | 
|  | break; | 
|  | } | 
|  |  | 
|  | SDL_PrivateJoystickHat(joystick, i, pos); | 
|  | } | 
|  |  | 
|  | element = element->pNext; | 
|  | ++i; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | DARWIN_JoystickClose(SDL_Joystick * joystick) | 
|  | { | 
|  | recDevice *device = joystick->hwdata; | 
|  | if (device) { | 
|  | device->joystick = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | DARWIN_JoystickQuit(void) | 
|  | { | 
|  | while (FreeDevice(gpDeviceList)) { | 
|  | /* spin */ | 
|  | } | 
|  |  | 
|  | if (hidman) { | 
|  | IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); | 
|  | IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone); | 
|  | CFRelease(hidman); | 
|  | hidman = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | SDL_JoystickDriver SDL_DARWIN_JoystickDriver = | 
|  | { | 
|  | DARWIN_JoystickInit, | 
|  | DARWIN_JoystickGetCount, | 
|  | DARWIN_JoystickDetect, | 
|  | DARWIN_JoystickGetDeviceName, | 
|  | DARWIN_JoystickGetDevicePlayerIndex, | 
|  | DARWIN_JoystickSetDevicePlayerIndex, | 
|  | DARWIN_JoystickGetDeviceGUID, | 
|  | DARWIN_JoystickGetDeviceInstanceID, | 
|  | DARWIN_JoystickOpen, | 
|  | DARWIN_JoystickRumble, | 
|  | DARWIN_JoystickUpdate, | 
|  | DARWIN_JoystickClose, | 
|  | DARWIN_JoystickQuit, | 
|  | }; | 
|  |  | 
|  | #endif /* SDL_JOYSTICK_IOKIT */ | 
|  |  | 
|  | /* vi: set ts=4 sw=4 expandtab: */ |