Atlas - SDL_iokitjoystick.c

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