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