summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/joystick/darwin
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/joystick/darwin')
-rw-r--r--contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick.c1089
-rw-r--r--contrib/SDL-3.2.8/src/joystick/darwin/SDL_iokitjoystick_c.h80
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
36static IOHIDManagerRef hidman = NULL;
37
38// Linked list of all available devices
39static recDevice *gpDeviceList = NULL;
40
41void 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
52FFEFFECT *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
96static 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
112static void FreeElementList(recElement *pElement)
113{
114 while (pElement) {
115 recElement *pElementNext = pElement->pNext;
116 SDL_free(pElement);
117 pElement = pElementNext;
118 }
119}
120
121static 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
175static 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
200static 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
216static 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
245static void AddHIDElement(const void *value, void *parameter);
246
247// Call AddHIDElement() on all elements in an array of IOHIDElementRefs
248static void AddHIDElements(CFArrayRef array, recDevice *pDevice)
249{
250 const CFRange range = { 0, CFArrayGetCount(array) };
251 CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
252}
253
254static 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.
266static 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
408static 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
421static 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
518static 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
537static 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
593static 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
614static 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
640static 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
670static 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
683static 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
698static 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
718static 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
724static const char *DARWIN_JoystickGetDeviceName(int device_index)
725{
726 recDevice *device = GetDeviceForIndex(device_index);
727 return device ? device->product : "UNKNOWN";
728}
729
730static const char *DARWIN_JoystickGetDevicePath(int device_index)
731{
732 return NULL;
733}
734
735static int DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
736{
737 recDevice *device = GetDeviceForIndex(device_index);
738 return device ? device->steam_virtual_gamepad_slot : -1;
739}
740
741static int DARWIN_JoystickGetDevicePlayerIndex(int device_index)
742{
743 return -1;
744}
745
746static void DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
747{
748}
749
750static 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
762static SDL_JoystickID DARWIN_JoystickGetDeviceInstanceID(int device_index)
763{
764 recDevice *device = GetDeviceForIndex(device_index);
765 return device ? device->instance_id : 0;
766}
767
768static 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 */
790static 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
842static 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
878static 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
917static bool DARWIN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
918{
919 return SDL_Unsupported();
920}
921
922static bool DARWIN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
923{
924 return SDL_Unsupported();
925}
926
927static bool DARWIN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
928{
929 return SDL_Unsupported();
930}
931
932static bool DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
933{
934 return SDL_Unsupported();
935}
936
937static 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
1038static void DARWIN_JoystickClose(SDL_Joystick *joystick)
1039{
1040 recDevice *device = joystick->hwdata;
1041 if (device) {
1042 device->joystick = NULL;
1043 }
1044}
1045
1046static 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
1060static bool DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1061{
1062 return false;
1063}
1064
1065SDL_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
30struct 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};
44typedef struct recElement recElement;
45
46struct 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};
78typedef struct joystick_hwdata recDevice;
79
80#endif // SDL_JOYSTICK_IOKIT_H