Atlas - SDL_sysjoystick.c

Home / ext / SDL2 / src / joystick / darwin Lines: 1 | Size: 31930 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 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_events.h" 26#include "SDL_joystick.h" 27#include "../SDL_sysjoystick.h" 28#include "../SDL_joystick_c.h" 29#include "SDL_sysjoystick_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 */ 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, Uint32 duration_ms) 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 = 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 --device_index; 108 } 109 device = device->pNext; 110 } 111 return device; 112} 113 114static void 115FreeElementList(recElement *pElement) 116{ 117 while (pElement) { 118 recElement *pElementNext = pElement->pNext; 119 SDL_free(pElement); 120 pElement = pElementNext; 121 } 122} 123 124static recDevice * 125FreeDevice(recDevice *removeDevice) 126{ 127 recDevice *pDeviceNext = NULL; 128 if (removeDevice) { 129 if (removeDevice->deviceRef) { 130 IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); 131 removeDevice->deviceRef = NULL; 132 } 133 134 /* save next device prior to disposing of this device */ 135 pDeviceNext = removeDevice->pNext; 136 137 if ( gpDeviceList == removeDevice ) { 138 gpDeviceList = pDeviceNext; 139 } else { 140 recDevice *device = gpDeviceList; 141 while (device->pNext != removeDevice) { 142 device = device->pNext; 143 } 144 device->pNext = pDeviceNext; 145 } 146 removeDevice->pNext = NULL; 147 148 /* free element lists */ 149 FreeElementList(removeDevice->firstAxis); 150 FreeElementList(removeDevice->firstButton); 151 FreeElementList(removeDevice->firstHat); 152 153 SDL_free(removeDevice); 154 } 155 return pDeviceNext; 156} 157 158static SDL_bool 159GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue) 160{ 161 SInt32 value = 0; 162 int returnValue = SDL_FALSE; 163 164 if (pDevice && pElement) { 165 IOHIDValueRef valueRef; 166 if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) { 167 value = (SInt32) IOHIDValueGetIntegerValue(valueRef); 168 169 /* record min and max for auto calibration */ 170 if (value < pElement->minReport) { 171 pElement->minReport = value; 172 } 173 if (value > pElement->maxReport) { 174 pElement->maxReport = value; 175 } 176 *pValue = value; 177 178 returnValue = SDL_TRUE; 179 } 180 } 181 return returnValue; 182} 183 184static SDL_bool 185GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max, SInt32 *pValue) 186{ 187 const float deviceScale = max - min; 188 const float readScale = pElement->maxReport - pElement->minReport; 189 int returnValue = SDL_FALSE; 190 if (GetHIDElementState(pDevice, pElement, pValue)) 191 { 192 if (readScale == 0) { 193 returnValue = SDL_TRUE; /* no scaling at all */ 194 } 195 else 196 { 197 *pValue = ((*pValue - pElement->minReport) * deviceScale / readScale) + min; 198 returnValue = SDL_TRUE; 199 } 200 } 201 return returnValue; 202} 203 204static void 205JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender) 206{ 207 recDevice *device = (recDevice *) ctx; 208 device->removed = SDL_TRUE; 209 device->deviceRef = NULL; // deviceRef was invalidated due to the remove 210 if (device->ffeffect_ref) { 211 FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref); 212 device->ffeffect_ref = NULL; 213 } 214 if (device->ffeffect) { 215 FreeRumbleEffectData(device->ffeffect); 216 device->ffeffect = NULL; 217 } 218 if (device->ffdevice) { 219 FFReleaseDevice(device->ffdevice); 220 device->ffdevice = NULL; 221 device->ff_initialized = SDL_FALSE; 222 } 223#if SDL_HAPTIC_IOKIT 224 MacHaptic_MaybeRemoveDevice(device->ffservice); 225#endif 226 227 SDL_PrivateJoystickRemoved(device->instance_id); 228} 229 230 231static void AddHIDElement(const void *value, void *parameter); 232 233/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */ 234static void 235AddHIDElements(CFArrayRef array, recDevice *pDevice) 236{ 237 const CFRange range = { 0, CFArrayGetCount(array) }; 238 CFArrayApplyFunction(array, range, AddHIDElement, pDevice); 239} 240 241static SDL_bool 242ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) { 243 while (listitem) { 244 if (listitem->cookie == cookie) { 245 return SDL_TRUE; 246 } 247 listitem = listitem->pNext; 248 } 249 return SDL_FALSE; 250} 251 252/* See if we care about this HID element, and if so, note it in our recDevice. */ 253static void 254AddHIDElement(const void *value, void *parameter) 255{ 256 recDevice *pDevice = (recDevice *) parameter; 257 IOHIDElementRef refElement = (IOHIDElementRef) value; 258 const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0; 259 260 if (refElement && (elementTypeID == IOHIDElementGetTypeID())) { 261 const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement); 262 const uint32_t usagePage = IOHIDElementGetUsagePage(refElement); 263 const uint32_t usage = IOHIDElementGetUsage(refElement); 264 recElement *element = NULL; 265 recElement **headElement = NULL; 266 267 /* look at types of interest */ 268 switch (IOHIDElementGetType(refElement)) { 269 case kIOHIDElementTypeInput_Misc: 270 case kIOHIDElementTypeInput_Button: 271 case kIOHIDElementTypeInput_Axis: { 272 switch (usagePage) { /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */ 273 case kHIDPage_GenericDesktop: 274 switch (usage) { 275 case kHIDUsage_GD_X: 276 case kHIDUsage_GD_Y: 277 case kHIDUsage_GD_Z: 278 case kHIDUsage_GD_Rx: 279 case kHIDUsage_GD_Ry: 280 case kHIDUsage_GD_Rz: 281 case kHIDUsage_GD_Slider: 282 case kHIDUsage_GD_Dial: 283 case kHIDUsage_GD_Wheel: 284 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) { 285 element = (recElement *) SDL_calloc(1, sizeof (recElement)); 286 if (element) { 287 pDevice->axes++; 288 headElement = &(pDevice->firstAxis); 289 } 290 } 291 break; 292 293 case kHIDUsage_GD_Hatswitch: 294 if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) { 295 element = (recElement *) SDL_calloc(1, sizeof (recElement)); 296 if (element) { 297 pDevice->hats++; 298 headElement = &(pDevice->firstHat); 299 } 300 } 301 break; 302 case kHIDUsage_GD_DPadUp: 303 case kHIDUsage_GD_DPadDown: 304 case kHIDUsage_GD_DPadRight: 305 case kHIDUsage_GD_DPadLeft: 306 case kHIDUsage_GD_Start: 307 case kHIDUsage_GD_Select: 308 case kHIDUsage_GD_SystemMainMenu: 309 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) { 310 element = (recElement *) SDL_calloc(1, sizeof (recElement)); 311 if (element) { 312 pDevice->buttons++; 313 headElement = &(pDevice->firstButton); 314 } 315 } 316 break; 317 } 318 break; 319 320 case kHIDPage_Simulation: 321 switch (usage) { 322 case kHIDUsage_Sim_Rudder: 323 case kHIDUsage_Sim_Throttle: 324 case kHIDUsage_Sim_Accelerator: 325 case kHIDUsage_Sim_Brake: 326 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) { 327 element = (recElement *) SDL_calloc(1, sizeof (recElement)); 328 if (element) { 329 pDevice->axes++; 330 headElement = &(pDevice->firstAxis); 331 } 332 } 333 break; 334 335 default: 336 break; 337 } 338 break; 339 340 case kHIDPage_Button: 341 case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */ 342 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) { 343 element = (recElement *) SDL_calloc(1, sizeof (recElement)); 344 if (element) { 345 pDevice->buttons++; 346 headElement = &(pDevice->firstButton); 347 } 348 } 349 break; 350 351 default: 352 break; 353 } 354 } 355 break; 356 357 case kIOHIDElementTypeCollection: { 358 CFArrayRef array = IOHIDElementGetChildren(refElement); 359 if (array) { 360 AddHIDElements(array, pDevice); 361 } 362 } 363 break; 364 365 default: 366 break; 367 } 368 369 if (element && headElement) { /* add to list */ 370 recElement *elementPrevious = NULL; 371 recElement *elementCurrent = *headElement; 372 while (elementCurrent && usage >= elementCurrent->usage) { 373 elementPrevious = elementCurrent; 374 elementCurrent = elementCurrent->pNext; 375 } 376 if (elementPrevious) { 377 elementPrevious->pNext = element; 378 } else { 379 *headElement = element; 380 } 381 382 element->elementRef = refElement; 383 element->usagePage = usagePage; 384 element->usage = usage; 385 element->pNext = elementCurrent; 386 387 element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement); 388 element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement); 389 element->cookie = IOHIDElementGetCookie(refElement); 390 391 pDevice->elements++; 392 } 393 } 394} 395 396static SDL_bool 397GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) 398{ 399 Sint32 vendor = 0; 400 Sint32 product = 0; 401 Sint32 version = 0; 402 CFTypeRef refCF = NULL; 403 CFArrayRef array = NULL; 404 Uint16 *guid16 = (Uint16 *)pDevice->guid.data; 405 406 /* get usage page and usage */ 407 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey)); 408 if (refCF) { 409 CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage); 410 } 411 if (pDevice->usagePage != kHIDPage_GenericDesktop) { 412 return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */ 413 } 414 415 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey)); 416 if (refCF) { 417 CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage); 418 } 419 420 if ((pDevice->usage != kHIDUsage_GD_Joystick && 421 pDevice->usage != kHIDUsage_GD_GamePad && 422 pDevice->usage != kHIDUsage_GD_MultiAxisController)) { 423 return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */ 424 } 425 426 pDevice->deviceRef = hidDevice; 427 428 /* get device name */ 429 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey)); 430 if (!refCF) { 431 /* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */ 432 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey)); 433 } 434 if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) { 435 SDL_strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product)); 436 } 437 438 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey)); 439 if (refCF) { 440 CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor); 441 } 442 443 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey)); 444 if (refCF) { 445 CFNumberGetValue(refCF, kCFNumberSInt32Type, &product); 446 } 447 448 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey)); 449 if (refCF) { 450 CFNumberGetValue(refCF, kCFNumberSInt32Type, &version); 451 } 452 453#ifdef SDL_JOYSTICK_HIDAPI 454 if (HIDAPI_IsDevicePresent(vendor, product, version)) { 455 /* The HIDAPI driver is taking care of this device */ 456 return 0; 457 } 458#endif 459 460 SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data)); 461 462 if (vendor && product) { 463 *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB); 464 *guid16++ = 0; 465 *guid16++ = SDL_SwapLE16((Uint16)vendor); 466 *guid16++ = 0; 467 *guid16++ = SDL_SwapLE16((Uint16)product); 468 *guid16++ = 0; 469 *guid16++ = SDL_SwapLE16((Uint16)version); 470 *guid16++ = 0; 471 } else { 472 *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH); 473 *guid16++ = 0; 474 SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4); 475 } 476 477 array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone); 478 if (array) { 479 AddHIDElements(array, pDevice); 480 CFRelease(array); 481 } 482 483 return SDL_TRUE; 484} 485 486static SDL_bool 487JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject) 488{ 489 recDevice *i; 490 for (i = gpDeviceList; i != NULL; i = i->pNext) { 491 if (i->deviceRef == ioHIDDeviceObject) { 492 return SDL_TRUE; 493 } 494 } 495 return SDL_FALSE; 496} 497 498 499static void 500JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) 501{ 502 recDevice *device; 503 int device_index = 0; 504 io_service_t ioservice; 505 506 if (res != kIOReturnSuccess) { 507 return; 508 } 509 510 if (JoystickAlreadyKnown(ioHIDDeviceObject)) { 511 return; /* IOKit sent us a duplicate. */ 512 } 513 514 device = (recDevice *) SDL_calloc(1, sizeof(recDevice)); 515 if (!device) { 516 SDL_OutOfMemory(); 517 return; 518 } 519 520 if (!GetDeviceInfo(ioHIDDeviceObject, device)) { 521 SDL_free(device); 522 return; /* not a device we care about, probably. */ 523 } 524 525 if (SDL_ShouldIgnoreJoystick(device->product, device->guid)) { 526 SDL_free(device); 527 return; 528 } 529 530 /* Get notified when this device is disconnected. */ 531 IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device); 532 IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); 533 534 /* Allocate an instance ID for this device */ 535 device->instance_id = SDL_GetNextJoystickInstanceID(); 536 537 /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */ 538 ioservice = IOHIDDeviceGetService(ioHIDDeviceObject); 539 if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) { 540 device->ffservice = ioservice; 541#if SDL_HAPTIC_IOKIT 542 MacHaptic_MaybeAddDevice(ioservice); 543#endif 544 } 545 546 /* Add device to the end of the list */ 547 if ( !gpDeviceList ) { 548 gpDeviceList = device; 549 } else { 550 recDevice *curdevice; 551 552 curdevice = gpDeviceList; 553 while ( curdevice->pNext ) { 554 ++device_index; 555 curdevice = curdevice->pNext; 556 } 557 curdevice->pNext = device; 558 ++device_index; /* bump by one since we counted by pNext. */ 559 } 560 561 SDL_PrivateJoystickAdded(device->instance_id); 562} 563 564static SDL_bool 565ConfigHIDManager(CFArrayRef matchingArray) 566{ 567 CFRunLoopRef runloop = CFRunLoopGetCurrent(); 568 569 if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { 570 return SDL_FALSE; 571 } 572 573 IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray); 574 IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL); 575 IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE); 576 577 while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) { 578 /* no-op. Callback fires once per existing device. */ 579 } 580 581 /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */ 582 583 return SDL_TRUE; /* good to go. */ 584} 585 586 587static CFDictionaryRef 588CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay) 589{ 590 CFDictionaryRef retval = NULL; 591 CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); 592 CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); 593 const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) }; 594 const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef }; 595 596 if (pageNumRef && usageNumRef) { 597 retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 598 } 599 600 if (pageNumRef) { 601 CFRelease(pageNumRef); 602 } 603 if (usageNumRef) { 604 CFRelease(usageNumRef); 605 } 606 607 if (!retval) { 608 *okay = 0; 609 } 610 611 return retval; 612} 613 614static SDL_bool 615CreateHIDManager(void) 616{ 617 SDL_bool retval = SDL_FALSE; 618 int okay = 1; 619 const void *vals[] = { 620 (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), 621 (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), 622 (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), 623 }; 624 const size_t numElements = SDL_arraysize(vals); 625 CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL; 626 size_t i; 627 628 for (i = 0; i < numElements; i++) { 629 if (vals[i]) { 630 CFRelease((CFTypeRef) vals[i]); 631 } 632 } 633 634 if (array) { 635 hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); 636 if (hidman != NULL) { 637 retval = ConfigHIDManager(array); 638 } 639 CFRelease(array); 640 } 641 642 return retval; 643} 644 645 646static int 647DARWIN_JoystickInit(void) 648{ 649 if (gpDeviceList) { 650 return SDL_SetError("Joystick: Device list already inited."); 651 } 652 653 if (!CreateHIDManager()) { 654 return SDL_SetError("Joystick: Couldn't initialize HID Manager"); 655 } 656 657 return 0; 658} 659 660static int 661DARWIN_JoystickGetCount(void) 662{ 663 recDevice *device = gpDeviceList; 664 int nJoySticks = 0; 665 666 while (device) { 667 if (!device->removed) { 668 nJoySticks++; 669 } 670 device = device->pNext; 671 } 672 673 return nJoySticks; 674} 675 676static void 677DARWIN_JoystickDetect(void) 678{ 679 recDevice *device = gpDeviceList; 680 while (device) { 681 if (device->removed) { 682 device = FreeDevice(device); 683 } else { 684 device = device->pNext; 685 } 686 } 687 688 /* run this after the checks above so we don't set device->removed and delete the device before 689 DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */ 690 while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) { 691 /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */ 692 } 693} 694 695/* Function to get the device-dependent name of a joystick */ 696const char * 697DARWIN_JoystickGetDeviceName(int device_index) 698{ 699 recDevice *device = GetDeviceForIndex(device_index); 700 return device ? device->product : "UNKNOWN"; 701} 702 703static SDL_JoystickGUID 704DARWIN_JoystickGetDeviceGUID( int device_index ) 705{ 706 recDevice *device = GetDeviceForIndex(device_index); 707 SDL_JoystickGUID guid; 708 if (device) { 709 guid = device->guid; 710 } else { 711 SDL_zero(guid); 712 } 713 return guid; 714} 715 716static SDL_JoystickID 717DARWIN_JoystickGetDeviceInstanceID(int device_index) 718{ 719 recDevice *device = GetDeviceForIndex(device_index); 720 return device ? device->instance_id : 0; 721} 722 723static int 724DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index) 725{ 726 recDevice *device = GetDeviceForIndex(device_index); 727 728 joystick->instance_id = device->instance_id; 729 joystick->hwdata = device; 730 joystick->name = device->product; 731 732 joystick->naxes = device->axes; 733 joystick->nhats = device->hats; 734 joystick->nballs = 0; 735 joystick->nbuttons = device->buttons; 736 return 0; 737} 738 739/* 740 * Like strerror but for force feedback errors. 741 */ 742static const char * 743FFStrError(unsigned int err) 744{ 745 switch (err) { 746 case FFERR_DEVICEFULL: 747 return "device full"; 748 /* This should be valid, but for some reason isn't defined... */ 749 /* case FFERR_DEVICENOTREG: 750 return "device not registered"; */ 751 case FFERR_DEVICEPAUSED: 752 return "device paused"; 753 case FFERR_DEVICERELEASED: 754 return "device released"; 755 case FFERR_EFFECTPLAYING: 756 return "effect playing"; 757 case FFERR_EFFECTTYPEMISMATCH: 758 return "effect type mismatch"; 759 case FFERR_EFFECTTYPENOTSUPPORTED: 760 return "effect type not supported"; 761 case FFERR_GENERIC: 762 return "undetermined error"; 763 case FFERR_HASEFFECTS: 764 return "device has effects"; 765 case FFERR_INCOMPLETEEFFECT: 766 return "incomplete effect"; 767 case FFERR_INTERNAL: 768 return "internal fault"; 769 case FFERR_INVALIDDOWNLOADID: 770 return "invalid download id"; 771 case FFERR_INVALIDPARAM: 772 return "invalid parameter"; 773 case FFERR_MOREDATA: 774 return "more data"; 775 case FFERR_NOINTERFACE: 776 return "interface not supported"; 777 case FFERR_NOTDOWNLOADED: 778 return "effect is not downloaded"; 779 case FFERR_NOTINITIALIZED: 780 return "object has not been initialized"; 781 case FFERR_OUTOFMEMORY: 782 return "out of memory"; 783 case FFERR_UNPLUGGED: 784 return "device is unplugged"; 785 case FFERR_UNSUPPORTED: 786 return "function call unsupported"; 787 case FFERR_UNSUPPORTEDAXIS: 788 return "axis unsupported"; 789 790 default: 791 return "unknown error"; 792 } 793} 794 795static int 796DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude, Uint32 duration_ms) 797{ 798 HRESULT result; 799 800 if (!device->ffdevice) { 801 result = FFCreateDevice(device->ffservice, &device->ffdevice); 802 if (result != FF_OK) { 803 return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result)); 804 } 805 } 806 807 /* Reset and then enable actuators */ 808 result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET); 809 if (result != FF_OK) { 810 return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result)); 811 } 812 813 result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON); 814 if (result != FF_OK) { 815 return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result)); 816 } 817 818 /* Create the effect */ 819 device->ffeffect = CreateRumbleEffectData(magnitude, duration_ms); 820 if (!device->ffeffect) { 821 return SDL_OutOfMemory(); 822 } 823 824 result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID, 825 device->ffeffect, &device->ffeffect_ref); 826 if (result != FF_OK) { 827 return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result)); 828 } 829 return 0; 830} 831 832static int 833DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) 834{ 835 HRESULT result; 836 recDevice *device = joystick->hwdata; 837 838 /* Scale and average the two rumble strengths */ 839 Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2); 840 841 if (!device->ffservice) { 842 return SDL_Unsupported(); 843 } 844 845 if (device->ff_initialized) { 846 FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams); 847 device->ffeffect->dwDuration = duration_ms * 1000; /* In microseconds. */ 848 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude); 849 850 result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect, 851 (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS)); 852 if (result != FF_OK) { 853 return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result)); 854 } 855 } else { 856 if (DARWIN_JoystickInitRumble(device, magnitude, duration_ms) < 0) { 857 return -1; 858 } 859 device->ff_initialized = SDL_TRUE; 860 } 861 862 result = FFEffectStart(device->ffeffect_ref, 1, 0); 863 if (result != FF_OK) { 864 return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result)); 865 } 866 return 0; 867} 868 869static void 870DARWIN_JoystickUpdate(SDL_Joystick * joystick) 871{ 872 recDevice *device = joystick->hwdata; 873 recElement *element; 874 SInt32 value, range; 875 int i; 876 877 if (!device) { 878 return; 879 } 880 881 if (device->removed) { /* device was unplugged; ignore it. */ 882 if (joystick->hwdata) { 883 joystick->force_recentering = SDL_TRUE; 884 joystick->hwdata = NULL; 885 } 886 return; 887 } 888 889 element = device->firstAxis; 890 i = 0; 891 892 int goodRead = SDL_FALSE; 893 while (element) { 894 goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value); 895 if (goodRead) { 896 SDL_PrivateJoystickAxis(joystick, i, value); 897 } 898 899 element = element->pNext; 900 ++i; 901 } 902 903 element = device->firstButton; 904 i = 0; 905 while (element) { 906 goodRead = GetHIDElementState(device, element, &value); 907 if (goodRead) { 908 if (value > 1) { /* handle pressure-sensitive buttons */ 909 value = 1; 910 } 911 SDL_PrivateJoystickButton(joystick, i, value); 912 } 913 914 element = element->pNext; 915 ++i; 916 } 917 918 element = device->firstHat; 919 i = 0; 920 921 while (element) { 922 Uint8 pos = 0; 923 924 range = (element->max - element->min + 1); 925 goodRead = GetHIDElementState(device, element, &value); 926 if (goodRead) { 927 value -= element->min; 928 if (range == 4) { /* 4 position hatswitch - scale up value */ 929 value *= 2; 930 } else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */ 931 value = -1; 932 } 933 switch (value) { 934 case 0: 935 pos = SDL_HAT_UP; 936 break; 937 case 1: 938 pos = SDL_HAT_RIGHTUP; 939 break; 940 case 2: 941 pos = SDL_HAT_RIGHT; 942 break; 943 case 3: 944 pos = SDL_HAT_RIGHTDOWN; 945 break; 946 case 4: 947 pos = SDL_HAT_DOWN; 948 break; 949 case 5: 950 pos = SDL_HAT_LEFTDOWN; 951 break; 952 case 6: 953 pos = SDL_HAT_LEFT; 954 break; 955 case 7: 956 pos = SDL_HAT_LEFTUP; 957 break; 958 default: 959 /* Every other value is mapped to center. We do that because some 960 * joysticks use 8 and some 15 for this value, and apparently 961 * there are even more variants out there - so we try to be generous. 962 */ 963 pos = SDL_HAT_CENTERED; 964 break; 965 } 966 967 SDL_PrivateJoystickHat(joystick, i, pos); 968 } 969 970 element = element->pNext; 971 ++i; 972 } 973} 974 975static void 976DARWIN_JoystickClose(SDL_Joystick * joystick) 977{ 978} 979 980static void 981DARWIN_JoystickQuit(void) 982{ 983 while (FreeDevice(gpDeviceList)) { 984 /* spin */ 985 } 986 987 if (hidman) { 988 IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE); 989 IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone); 990 CFRelease(hidman); 991 hidman = NULL; 992 } 993} 994 995SDL_JoystickDriver SDL_DARWIN_JoystickDriver = 996{ 997 DARWIN_JoystickInit, 998 DARWIN_JoystickGetCount, 999 DARWIN_JoystickDetect, 1000 DARWIN_JoystickGetDeviceName, 1001 DARWIN_JoystickGetDeviceGUID, 1002 DARWIN_JoystickGetDeviceInstanceID, 1003 DARWIN_JoystickOpen, 1004 DARWIN_JoystickRumble, 1005 DARWIN_JoystickUpdate, 1006 DARWIN_JoystickClose, 1007 DARWIN_JoystickQuit, 1008}; 1009 1010#endif /* SDL_JOYSTICK_IOKIT */ 1011 1012/* vi: set ts=4 sw=4 expandtab: */ 1013
[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.