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: */