Atlas - SDL_iokitjoystick.c

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