Path: blob/master/thirdparty/sdl/joystick/darwin/SDL_iokitjoystick.c
22572 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_JOYSTICK_IOKIT2324#include "../SDL_sysjoystick.h"25#include "../SDL_joystick_c.h"26#include "SDL_iokitjoystick_c.h"27#include "../hidapi/SDL_hidapijoystick_c.h"28#include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging29#include "../usb_ids.h"3031#define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")3233#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF)3435// The base object of the HID Manager API36static IOHIDManagerRef hidman = NULL;3738// Linked list of all available devices39static recDevice *gpDeviceList = NULL;4041void FreeRumbleEffectData(FFEFFECT *effect)42{43if (!effect) {44return;45}46SDL_free(effect->rgdwAxes);47SDL_free(effect->rglDirection);48SDL_free(effect->lpvTypeSpecificParams);49SDL_free(effect);50}5152FFEFFECT *CreateRumbleEffectData(Sint16 magnitude)53{54FFEFFECT *effect;55FFPERIODIC *periodic;5657// Create the effect58effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect));59if (!effect) {60return NULL;61}62effect->dwSize = sizeof(*effect);63effect->dwGain = 10000;64effect->dwFlags = FFEFF_OBJECTOFFSETS;65effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; // In microseconds.66effect->dwTriggerButton = FFEB_NOTRIGGER;6768effect->cAxes = 2;69effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));70if (!effect->rgdwAxes) {71FreeRumbleEffectData(effect);72return NULL;73}7475effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));76if (!effect->rglDirection) {77FreeRumbleEffectData(effect);78return NULL;79}80effect->dwFlags |= FFEFF_CARTESIAN;8182periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic));83if (!periodic) {84FreeRumbleEffectData(effect);85return NULL;86}87periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);88periodic->dwPeriod = 1000000;8990effect->cbTypeSpecificParams = sizeof(*periodic);91effect->lpvTypeSpecificParams = periodic;9293return effect;94}9596static recDevice *GetDeviceForIndex(int device_index)97{98recDevice *device = gpDeviceList;99while (device) {100if (!device->removed) {101if (device_index == 0) {102break;103}104105--device_index;106}107device = device->pNext;108}109return device;110}111112static void FreeElementList(recElement *pElement)113{114while (pElement) {115recElement *pElementNext = pElement->pNext;116SDL_free(pElement);117pElement = pElementNext;118}119}120121static recDevice *FreeDevice(recDevice *removeDevice)122{123recDevice *pDeviceNext = NULL;124if (removeDevice) {125if (removeDevice->deviceRef) {126if (removeDevice->runLoopAttached) {127/* Calling IOHIDDeviceUnscheduleFromRunLoop without a prior,128* paired call to IOHIDDeviceScheduleWithRunLoop can lead129* to crashes in MacOS 10.14.x and earlier. This doesn't130* appear to be a problem in MacOS 10.15.x, but we'll131* do it anyways. (Part-of fix for Bug 5034)132*/133IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);134}135CFRelease(removeDevice->deviceRef);136removeDevice->deviceRef = NULL;137}138139/* clear out any reference to removeDevice from an associated,140* live instance of SDL_Joystick (Part-of fix for Bug 5034)141*/142SDL_LockJoysticks();143if (removeDevice->joystick) {144removeDevice->joystick->hwdata = NULL;145}146SDL_UnlockJoysticks();147148// save next device prior to disposing of this device149pDeviceNext = removeDevice->pNext;150151if (gpDeviceList == removeDevice) {152gpDeviceList = pDeviceNext;153} else if (gpDeviceList) {154recDevice *device;155156for (device = gpDeviceList; device; device = device->pNext) {157if (device->pNext == removeDevice) {158device->pNext = pDeviceNext;159break;160}161}162}163removeDevice->pNext = NULL;164165// free element lists166FreeElementList(removeDevice->firstAxis);167FreeElementList(removeDevice->firstButton);168FreeElementList(removeDevice->firstHat);169170SDL_free(removeDevice);171}172return pDeviceNext;173}174175static bool GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue)176{177SInt32 value = 0;178bool result = false;179180if (pDevice && pDevice->deviceRef && pElement) {181IOHIDValueRef valueRef;182if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {183value = (SInt32)IOHIDValueGetIntegerValue(valueRef);184185// record min and max for auto calibration186if (value < pElement->minReport) {187pElement->minReport = value;188}189if (value > pElement->maxReport) {190pElement->maxReport = value;191}192*pValue = value;193194result = true;195}196}197return result;198}199200static bool GetHIDScaledCalibratedState(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue)201{202const float deviceScale = max - min;203const float readScale = pElement->maxReport - pElement->minReport;204bool result = false;205if (GetHIDElementState(pDevice, pElement, pValue)) {206if (readScale == 0) {207result = true; // no scaling at all208} else {209*pValue = (Sint32)(((*pValue - pElement->minReport) * deviceScale / readScale) + min);210result = true;211}212}213return result;214}215216static bool GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue)217{218if (pElement->minReport == 0 && pElement->maxReport == 255) {219return GetHIDScaledCalibratedState(pDevice, pElement, min, max, pValue);220}221222// This device thumbstick axes have an unusual axis range that223// doesn't work with GetHIDScaledCalibratedState() above.224//225// See https://github.com/libsdl-org/SDL/issues/13143 for details226if (GetHIDElementState(pDevice, pElement, pValue)) {227if (*pValue >= 0) {228// Negative axis values range from 32767 (at rest) to 0 (minimum)229*pValue = -32767 + *pValue;230} else if (*pValue < 0) {231// Positive axis values range from -32768 (at rest) to 0 (maximum)232*pValue = 32768 + *pValue;233}234return true;235}236return false;237}238239static void JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)240{241recDevice *device = (recDevice *)ctx;242device->removed = true;243if (device->deviceRef) {244// deviceRef was invalidated due to the remove245CFRelease(device->deviceRef);246device->deviceRef = NULL;247}248if (device->ffeffect_ref) {249FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref);250device->ffeffect_ref = NULL;251}252if (device->ffeffect) {253FreeRumbleEffectData(device->ffeffect);254device->ffeffect = NULL;255}256if (device->ffdevice) {257FFReleaseDevice(device->ffdevice);258device->ffdevice = NULL;259device->ff_initialized = false;260}261#ifdef SDL_HAPTIC_IOKIT262MacHaptic_MaybeRemoveDevice(device->ffservice);263#endif264265SDL_PrivateJoystickRemoved(device->instance_id);266}267268static void AddHIDElement(const void *value, void *parameter);269270// Call AddHIDElement() on all elements in an array of IOHIDElementRefs271static void AddHIDElements(CFArrayRef array, recDevice *pDevice)272{273const CFRange range = { 0, CFArrayGetCount(array) };274CFArrayApplyFunction(array, range, AddHIDElement, pDevice);275}276277static bool ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem)278{279while (listitem) {280if (listitem->cookie == cookie) {281return true;282}283listitem = listitem->pNext;284}285return false;286}287288// See if we care about this HID element, and if so, note it in our recDevice.289static void AddHIDElement(const void *value, void *parameter)290{291recDevice *pDevice = (recDevice *)parameter;292IOHIDElementRef refElement = (IOHIDElementRef)value;293const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;294295if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {296const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);297const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);298const uint32_t usage = IOHIDElementGetUsage(refElement);299recElement *element = NULL;300recElement **headElement = NULL;301302// look at types of interest303switch (IOHIDElementGetType(refElement)) {304case kIOHIDElementTypeInput_Misc:305case kIOHIDElementTypeInput_Button:306case kIOHIDElementTypeInput_Axis:307{308switch (usagePage) { // only interested in kHIDPage_GenericDesktop and kHIDPage_Button309case kHIDPage_GenericDesktop:310switch (usage) {311case kHIDUsage_GD_X:312case kHIDUsage_GD_Y:313case kHIDUsage_GD_Z:314case kHIDUsage_GD_Rx:315case kHIDUsage_GD_Ry:316case kHIDUsage_GD_Rz:317case kHIDUsage_GD_Slider:318case kHIDUsage_GD_Dial:319case kHIDUsage_GD_Wheel:320if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {321element = (recElement *)SDL_calloc(1, sizeof(recElement));322if (element) {323pDevice->axes++;324headElement = &(pDevice->firstAxis);325}326}327break;328329case kHIDUsage_GD_Hatswitch:330if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {331element = (recElement *)SDL_calloc(1, sizeof(recElement));332if (element) {333pDevice->hats++;334headElement = &(pDevice->firstHat);335}336}337break;338case kHIDUsage_GD_DPadUp:339case kHIDUsage_GD_DPadDown:340case kHIDUsage_GD_DPadRight:341case kHIDUsage_GD_DPadLeft:342case kHIDUsage_GD_Start:343case kHIDUsage_GD_Select:344case kHIDUsage_GD_SystemMainMenu:345if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {346element = (recElement *)SDL_calloc(1, sizeof(recElement));347if (element) {348pDevice->buttons++;349headElement = &(pDevice->firstButton);350}351}352break;353}354break;355356case kHIDPage_Simulation:357switch (usage) {358case kHIDUsage_Sim_Rudder:359case kHIDUsage_Sim_Throttle:360case kHIDUsage_Sim_Accelerator:361case kHIDUsage_Sim_Brake:362if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {363element = (recElement *)SDL_calloc(1, sizeof(recElement));364if (element) {365pDevice->axes++;366headElement = &(pDevice->firstAxis);367}368}369break;370371default:372break;373}374break;375376case kHIDPage_Button:377case kHIDPage_Consumer: // e.g. 'pause' button on Steelseries MFi gamepads.378if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {379element = (recElement *)SDL_calloc(1, sizeof(recElement));380if (element) {381pDevice->buttons++;382headElement = &(pDevice->firstButton);383}384}385break;386387default:388break;389}390} break;391392case kIOHIDElementTypeCollection:393{394CFArrayRef array = IOHIDElementGetChildren(refElement);395if (array) {396AddHIDElements(array, pDevice);397}398} break;399400default:401break;402}403404if (element && headElement) { // add to list405recElement *elementPrevious = NULL;406recElement *elementCurrent = *headElement;407while (elementCurrent && usage >= elementCurrent->usage) {408elementPrevious = elementCurrent;409elementCurrent = elementCurrent->pNext;410}411if (elementPrevious) {412elementPrevious->pNext = element;413} else {414*headElement = element;415}416417element->elementRef = refElement;418element->usagePage = usagePage;419element->usage = usage;420element->pNext = elementCurrent;421422element->minReport = element->min = (SInt32)IOHIDElementGetLogicalMin(refElement);423element->maxReport = element->max = (SInt32)IOHIDElementGetLogicalMax(refElement);424element->cookie = IOHIDElementGetCookie(refElement);425426pDevice->elements++;427}428}429}430431static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *product_string)432{433int slot = -1;434435if (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) {436// Gamepad name is "GamePad-N", where N is slot + 1437if (SDL_sscanf(product_string, "GamePad-%d", &slot) == 1) {438slot -= 1;439}440}441return slot;442}443444static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)445{446Sint32 vendor = 0;447Sint32 product = 0;448Sint32 version = 0;449char *name;450char manufacturer_string[256];451char product_string[256];452CFTypeRef refCF = NULL;453CFArrayRef array = NULL;454455// get usage page and usage456refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));457if (refCF) {458CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);459}460if (pDevice->usagePage != kHIDPage_GenericDesktop) {461return false; // Filter device list to non-keyboard/mouse stuff462}463464refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));465if (refCF) {466CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);467}468469if ((pDevice->usage != kHIDUsage_GD_Joystick &&470pDevice->usage != kHIDUsage_GD_GamePad &&471pDevice->usage != kHIDUsage_GD_MultiAxisController)) {472return false; // Filter device list to non-keyboard/mouse stuff473}474475/* Make sure we retain the use of the IOKit-provided device-object,476lest the device get disconnected and we try to use it. (Fixes477SDL-Bugzilla #4961, aka. https://bugzilla.libsdl.org/show_bug.cgi?id=4961 )478*/479CFRetain(hidDevice);480481/* Now that we've CFRetain'ed the device-object (for our use), we'll482save the reference to it.483*/484pDevice->deviceRef = hidDevice;485486refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));487if (refCF) {488CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);489}490491refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));492if (refCF) {493CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);494}495496refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));497if (refCF) {498CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);499}500501if (SDL_IsJoystickXboxOne(vendor, product)) {502// We can't actually use this API for Xbox controllers503return false;504}505506// get device name507refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));508if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) {509manufacturer_string[0] = '\0';510}511refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));512if ((!refCF) || (!CFStringGetCString(refCF, product_string, sizeof(product_string), kCFStringEncodingUTF8))) {513product_string[0] = '\0';514}515name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);516if (name) {517SDL_strlcpy(pDevice->product, name, sizeof(pDevice->product));518SDL_free(name);519}520521if (SDL_ShouldIgnoreJoystick(vendor, product, version, pDevice->product)) {522return false;523}524525if (SDL_JoystickHandledByAnotherDriver(&SDL_DARWIN_JoystickDriver, vendor, product, version, pDevice->product)) {526return false;527}528529pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, manufacturer_string, product_string, 0, 0);530pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string);531532if (vendor == USB_VENDOR_NACON_ALT &&533product == USB_PRODUCT_NACON_REVOLUTION_X_UNLIMITED_BT) {534pDevice->nacon_revolution_x_unlimited = true;535}536537array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);538if (array) {539AddHIDElements(array, pDevice);540CFRelease(array);541}542543return true;544}545546static bool JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)547{548recDevice *i;549550#ifdef SDL_JOYSTICK_MFI551extern bool IOS_SupportedHIDDevice(IOHIDDeviceRef device);552if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) {553return true;554}555#endif556557for (i = gpDeviceList; i; i = i->pNext) {558if (i->deviceRef == ioHIDDeviceObject) {559return true;560}561}562return false;563}564565static void JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)566{567recDevice *device;568io_service_t ioservice;569570if (res != kIOReturnSuccess) {571return;572}573574if (JoystickAlreadyKnown(ioHIDDeviceObject)) {575return; // IOKit sent us a duplicate.576}577578device = (recDevice *)SDL_calloc(1, sizeof(recDevice));579if (!device) {580return;581}582583if (!GetDeviceInfo(ioHIDDeviceObject, device)) {584FreeDevice(device);585return; // not a device we care about, probably.586}587588// Get notified when this device is disconnected.589IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);590IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);591device->runLoopAttached = true;592593// Allocate an instance ID for this device594device->instance_id = SDL_GetNextObjectID();595596// We have to do some storage of the io_service_t for SDL_OpenHapticFromJoystick597ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);598if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {599device->ffservice = ioservice;600#ifdef SDL_HAPTIC_IOKIT601MacHaptic_MaybeAddDevice(ioservice);602#endif603}604605// Add device to the end of the list606if (!gpDeviceList) {607gpDeviceList = device;608} else {609recDevice *curdevice;610611curdevice = gpDeviceList;612while (curdevice->pNext) {613curdevice = curdevice->pNext;614}615curdevice->pNext = device;616}617618SDL_PrivateJoystickAdded(device->instance_id);619}620621static bool ConfigHIDManager(CFArrayRef matchingArray)622{623CFRunLoopRef runloop = CFRunLoopGetCurrent();624625if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {626return false;627}628629IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);630IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);631IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);632633while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {634// no-op. Callback fires once per existing device.635}636637// future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now.638639return true; // good to go.640}641642static CFDictionaryRef CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)643{644CFDictionaryRef result = NULL;645CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);646CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);647const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) };648const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef };649650if (pageNumRef && usageNumRef) {651result = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);652}653654if (pageNumRef) {655CFRelease(pageNumRef);656}657if (usageNumRef) {658CFRelease(usageNumRef);659}660661if (!result) {662*okay = 0;663}664665return result;666}667668static bool CreateHIDManager(void)669{670bool result = false;671int okay = 1;672const void *vals[] = {673(void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),674(void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),675(void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),676};677const size_t numElements = SDL_arraysize(vals);678CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;679size_t i;680681for (i = 0; i < numElements; i++) {682if (vals[i]) {683CFRelease((CFTypeRef)vals[i]);684}685}686687if (array) {688hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);689if (hidman != NULL) {690result = ConfigHIDManager(array);691}692CFRelease(array);693}694695return result;696}697698static bool DARWIN_JoystickInit(void)699{700if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_IOKIT, true)) {701return true;702}703704if (!CreateHIDManager()) {705return SDL_SetError("Joystick: Couldn't initialize HID Manager");706}707708return true;709}710711static int DARWIN_JoystickGetCount(void)712{713recDevice *device = gpDeviceList;714int nJoySticks = 0;715716while (device) {717if (!device->removed) {718nJoySticks++;719}720device = device->pNext;721}722723return nJoySticks;724}725726static void DARWIN_JoystickDetect(void)727{728recDevice *device = gpDeviceList;729while (device) {730if (device->removed) {731device = FreeDevice(device);732} else {733device = device->pNext;734}735}736737if (hidman) {738/* run this after the checks above so we don't set device->removed and delete the device before739DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */740while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {741// no-op. Pending callbacks will fire in CFRunLoopRunInMode().742}743}744}745746static bool DARWIN_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)747{748// We don't override any other drivers749return false;750}751752static const char *DARWIN_JoystickGetDeviceName(int device_index)753{754recDevice *device = GetDeviceForIndex(device_index);755return device ? device->product : "UNKNOWN";756}757758static const char *DARWIN_JoystickGetDevicePath(int device_index)759{760return NULL;761}762763static int DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)764{765recDevice *device = GetDeviceForIndex(device_index);766return device ? device->steam_virtual_gamepad_slot : -1;767}768769static int DARWIN_JoystickGetDevicePlayerIndex(int device_index)770{771return -1;772}773774static void DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index)775{776}777778static SDL_GUID DARWIN_JoystickGetDeviceGUID(int device_index)779{780recDevice *device = GetDeviceForIndex(device_index);781SDL_GUID guid;782if (device) {783guid = device->guid;784} else {785SDL_zero(guid);786}787return guid;788}789790static SDL_JoystickID DARWIN_JoystickGetDeviceInstanceID(int device_index)791{792recDevice *device = GetDeviceForIndex(device_index);793return device ? device->instance_id : 0;794}795796static bool DARWIN_JoystickOpen(SDL_Joystick *joystick, int device_index)797{798recDevice *device = GetDeviceForIndex(device_index);799800joystick->hwdata = device;801device->joystick = joystick;802joystick->name = device->product;803804joystick->naxes = device->axes;805joystick->nhats = device->hats;806joystick->nbuttons = device->buttons;807808if (device->ffservice) {809SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);810}811812return true;813}814815/*816* Like strerror but for force feedback errors.817*/818static const char *FFStrError(unsigned int err)819{820switch (err) {821case FFERR_DEVICEFULL:822return "device full";823// This should be valid, but for some reason isn't defined...824/* case FFERR_DEVICENOTREG:825return "device not registered"; */826case FFERR_DEVICEPAUSED:827return "device paused";828case FFERR_DEVICERELEASED:829return "device released";830case FFERR_EFFECTPLAYING:831return "effect playing";832case FFERR_EFFECTTYPEMISMATCH:833return "effect type mismatch";834case FFERR_EFFECTTYPENOTSUPPORTED:835return "effect type not supported";836case FFERR_GENERIC:837return "undetermined error";838case FFERR_HASEFFECTS:839return "device has effects";840case FFERR_INCOMPLETEEFFECT:841return "incomplete effect";842case FFERR_INTERNAL:843return "internal fault";844case FFERR_INVALIDDOWNLOADID:845return "invalid download id";846case FFERR_INVALIDPARAM:847return "invalid parameter";848case FFERR_MOREDATA:849return "more data";850case FFERR_NOINTERFACE:851return "interface not supported";852case FFERR_NOTDOWNLOADED:853return "effect is not downloaded";854case FFERR_NOTINITIALIZED:855return "object has not been initialized";856case FFERR_OUTOFMEMORY:857return "out of memory";858case FFERR_UNPLUGGED:859return "device is unplugged";860case FFERR_UNSUPPORTED:861return "function call unsupported";862case FFERR_UNSUPPORTEDAXIS:863return "axis unsupported";864865default:866return "unknown error";867}868}869870static bool DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude)871{872HRESULT result;873874if (!device->ffdevice) {875result = FFCreateDevice(device->ffservice, &device->ffdevice);876if (result != FF_OK) {877return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));878}879}880881// Reset and then enable actuators882result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);883if (result != FF_OK) {884return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));885}886887result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);888if (result != FF_OK) {889return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));890}891892// Create the effect893device->ffeffect = CreateRumbleEffectData(magnitude);894if (!device->ffeffect) {895return false;896}897898result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,899device->ffeffect, &device->ffeffect_ref);900if (result != FF_OK) {901return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));902}903return true;904}905906static bool DARWIN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)907{908HRESULT result;909recDevice *device = joystick->hwdata;910911// Scale and average the two rumble strengths912Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);913914if (!device) {915return SDL_SetError("Rumble failed, device disconnected");916}917918if (!device->ffservice) {919return SDL_Unsupported();920}921922if (device->ff_initialized) {923FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);924periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);925926result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,927(FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));928if (result != FF_OK) {929return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));930}931} else {932if (!DARWIN_JoystickInitRumble(device, magnitude)) {933return false;934}935device->ff_initialized = true;936}937938result = FFEffectStart(device->ffeffect_ref, 1, 0);939if (result != FF_OK) {940return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));941}942return true;943}944945static bool DARWIN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)946{947return SDL_Unsupported();948}949950static bool DARWIN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)951{952return SDL_Unsupported();953}954955static bool DARWIN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)956{957return SDL_Unsupported();958}959960static bool DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)961{962return SDL_Unsupported();963}964965static void DARWIN_JoystickUpdate(SDL_Joystick *joystick)966{967recDevice *device = joystick->hwdata;968recElement *element;969SInt32 value, range;970int i, goodRead = false;971Uint64 timestamp = SDL_GetTicksNS();972973if (!device) {974return;975}976977if (device->removed) { // device was unplugged; ignore it.978if (joystick->hwdata) {979joystick->hwdata = NULL;980}981return;982}983984element = device->firstAxis;985i = 0;986987while (element) {988if (device->nacon_revolution_x_unlimited) {989goodRead = GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(device, element, -32768, 32767, &value);990} else {991goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);992}993if (goodRead) {994SDL_SendJoystickAxis(timestamp, joystick, i, value);995}996997element = element->pNext;998++i;999}10001001element = device->firstButton;1002i = 0;1003while (element) {1004goodRead = GetHIDElementState(device, element, &value);1005if (goodRead) {1006SDL_SendJoystickButton(timestamp, joystick, i, (value != 0));1007}10081009element = element->pNext;1010++i;1011}10121013element = device->firstHat;1014i = 0;10151016while (element) {1017Uint8 pos = 0;10181019range = (element->max - element->min + 1);1020goodRead = GetHIDElementState(device, element, &value);1021if (goodRead) {1022value -= element->min;1023if (range == 4) { // 4 position hatswitch - scale up value1024value *= 2;1025} else if (range != 8) { // Neither a 4 nor 8 positions - fall back to default position (centered)1026value = -1;1027}1028switch (value) {1029case 0:1030pos = SDL_HAT_UP;1031break;1032case 1:1033pos = SDL_HAT_RIGHTUP;1034break;1035case 2:1036pos = SDL_HAT_RIGHT;1037break;1038case 3:1039pos = SDL_HAT_RIGHTDOWN;1040break;1041case 4:1042pos = SDL_HAT_DOWN;1043break;1044case 5:1045pos = SDL_HAT_LEFTDOWN;1046break;1047case 6:1048pos = SDL_HAT_LEFT;1049break;1050case 7:1051pos = SDL_HAT_LEFTUP;1052break;1053default:1054/* Every other value is mapped to center. We do that because some1055* joysticks use 8 and some 15 for this value, and apparently1056* there are even more variants out there - so we try to be generous.1057*/1058pos = SDL_HAT_CENTERED;1059break;1060}10611062SDL_SendJoystickHat(timestamp, joystick, i, pos);1063}10641065element = element->pNext;1066++i;1067}1068}10691070static void DARWIN_JoystickClose(SDL_Joystick *joystick)1071{1072recDevice *device = joystick->hwdata;1073if (device) {1074device->joystick = NULL;1075}1076}10771078static void DARWIN_JoystickQuit(void)1079{1080while (FreeDevice(gpDeviceList)) {1081// spin1082}10831084if (hidman) {1085IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);1086IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);1087CFRelease(hidman);1088hidman = NULL;1089}1090}10911092static bool DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)1093{1094return false;1095}10961097SDL_JoystickDriver SDL_DARWIN_JoystickDriver = {1098DARWIN_JoystickInit,1099DARWIN_JoystickGetCount,1100DARWIN_JoystickDetect,1101DARWIN_JoystickIsDevicePresent,1102DARWIN_JoystickGetDeviceName,1103DARWIN_JoystickGetDevicePath,1104DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot,1105DARWIN_JoystickGetDevicePlayerIndex,1106DARWIN_JoystickSetDevicePlayerIndex,1107DARWIN_JoystickGetDeviceGUID,1108DARWIN_JoystickGetDeviceInstanceID,1109DARWIN_JoystickOpen,1110DARWIN_JoystickRumble,1111DARWIN_JoystickRumbleTriggers,1112DARWIN_JoystickSetLED,1113DARWIN_JoystickSendEffect,1114DARWIN_JoystickSetSensorsEnabled,1115DARWIN_JoystickUpdate,1116DARWIN_JoystickClose,1117DARWIN_JoystickQuit,1118DARWIN_JoystickGetGamepadMapping1119};11201121#endif // SDL_JOYSTICK_IOKIT112211231124