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