1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 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 <IOKit/hid/IOHIDLib.h>
26 
27 /* For force feedback testing. */
28 #include <ForceFeedback/ForceFeedback.h>
29 #include <ForceFeedback/ForceFeedbackConstants.h>
30 
31 #include "SDL_joystick.h"
32 #include "../SDL_sysjoystick.h"
33 #include "../SDL_joystick_c.h"
34 #include "SDL_sysjoystick_c.h"
35 #include "SDL_events.h"
36 #include "../../haptic/darwin/SDL_syshaptic_c.h"    /* For haptic hot plugging */
37 
38 #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
39 
40 /* The base object of the HID Manager API */
41 static IOHIDManagerRef hidman = NULL;
42 
43 /* Linked list of all available devices */
44 static recDevice *gpDeviceList = NULL;
45 
46 /* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
47 static int s_joystick_instance_id = -1;
48 
GetDeviceForIndex(int device_index)49 static recDevice *GetDeviceForIndex(int device_index)
50 {
51     recDevice *device = gpDeviceList;
52     while (device) {
53         if (!device->removed) {
54             if (device_index == 0)
55                 break;
56 
57             --device_index;
58         }
59         device = device->pNext;
60     }
61     return device;
62 }
63 
64 static void
FreeElementList(recElement * pElement)65 FreeElementList(recElement *pElement)
66 {
67     while (pElement) {
68         recElement *pElementNext = pElement->pNext;
69         SDL_free(pElement);
70         pElement = pElementNext;
71     }
72 }
73 
74 static recDevice *
FreeDevice(recDevice * removeDevice)75 FreeDevice(recDevice *removeDevice)
76 {
77     recDevice *pDeviceNext = NULL;
78     if (removeDevice) {
79         if (removeDevice->deviceRef) {
80             IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
81             removeDevice->deviceRef = NULL;
82         }
83 
84         /* save next device prior to disposing of this device */
85         pDeviceNext = removeDevice->pNext;
86 
87         if ( gpDeviceList == removeDevice ) {
88             gpDeviceList = pDeviceNext;
89         } else {
90             recDevice *device = gpDeviceList;
91             while (device->pNext != removeDevice) {
92                 device = device->pNext;
93             }
94             device->pNext = pDeviceNext;
95         }
96         removeDevice->pNext = NULL;
97 
98         /* free element lists */
99         FreeElementList(removeDevice->firstAxis);
100         FreeElementList(removeDevice->firstButton);
101         FreeElementList(removeDevice->firstHat);
102 
103         SDL_free(removeDevice);
104     }
105     return pDeviceNext;
106 }
107 
108 static SInt32
GetHIDElementState(recDevice * pDevice,recElement * pElement)109 GetHIDElementState(recDevice *pDevice, recElement *pElement)
110 {
111     SInt32 value = 0;
112 
113     if (pDevice && pElement) {
114         IOHIDValueRef valueRef;
115         if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
116             value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
117 
118             /* record min and max for auto calibration */
119             if (value < pElement->minReport) {
120                 pElement->minReport = value;
121             }
122             if (value > pElement->maxReport) {
123                 pElement->maxReport = value;
124             }
125         }
126     }
127 
128     return value;
129 }
130 
131 static SInt32
GetHIDScaledCalibratedState(recDevice * pDevice,recElement * pElement,SInt32 min,SInt32 max)132 GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max)
133 {
134     const float deviceScale = max - min;
135     const float readScale = pElement->maxReport - pElement->minReport;
136     const SInt32 value = GetHIDElementState(pDevice, pElement);
137     if (readScale == 0) {
138         return value;           /* no scaling at all */
139     }
140     return ((value - pElement->minReport) * deviceScale / readScale) + min;
141 }
142 
143 
144 static void
JoystickDeviceWasRemovedCallback(void * ctx,IOReturn result,void * sender)145 JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
146 {
147     recDevice *device = (recDevice *) ctx;
148     device->removed = SDL_TRUE;
149     device->deviceRef = NULL; // deviceRef was invalidated due to the remove
150 #if SDL_HAPTIC_IOKIT
151     MacHaptic_MaybeRemoveDevice(device->ffservice);
152 #endif
153 
154     SDL_PrivateJoystickRemoved(device->instance_id);
155 }
156 
157 
158 static void AddHIDElement(const void *value, void *parameter);
159 
160 /* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
161 static void
AddHIDElements(CFArrayRef array,recDevice * pDevice)162 AddHIDElements(CFArrayRef array, recDevice *pDevice)
163 {
164     const CFRange range = { 0, CFArrayGetCount(array) };
165     CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
166 }
167 
168 static SDL_bool
ElementAlreadyAdded(const IOHIDElementCookie cookie,const recElement * listitem)169 ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
170     while (listitem) {
171         if (listitem->cookie == cookie) {
172             return SDL_TRUE;
173         }
174         listitem = listitem->pNext;
175     }
176     return SDL_FALSE;
177 }
178 
179 /* See if we care about this HID element, and if so, note it in our recDevice. */
180 static void
AddHIDElement(const void * value,void * parameter)181 AddHIDElement(const void *value, void *parameter)
182 {
183     recDevice *pDevice = (recDevice *) parameter;
184     IOHIDElementRef refElement = (IOHIDElementRef) value;
185     const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
186 
187     if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
188         const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
189         const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
190         const uint32_t usage = IOHIDElementGetUsage(refElement);
191         recElement *element = NULL;
192         recElement **headElement = NULL;
193 
194         /* look at types of interest */
195         switch (IOHIDElementGetType(refElement)) {
196             case kIOHIDElementTypeInput_Misc:
197             case kIOHIDElementTypeInput_Button:
198             case kIOHIDElementTypeInput_Axis: {
199                 switch (usagePage) {    /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
200                     case kHIDPage_GenericDesktop:
201                         switch (usage) {
202                             case kHIDUsage_GD_X:
203                             case kHIDUsage_GD_Y:
204                             case kHIDUsage_GD_Z:
205                             case kHIDUsage_GD_Rx:
206                             case kHIDUsage_GD_Ry:
207                             case kHIDUsage_GD_Rz:
208                             case kHIDUsage_GD_Slider:
209                             case kHIDUsage_GD_Dial:
210                             case kHIDUsage_GD_Wheel:
211                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
212                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
213                                     if (element) {
214                                         pDevice->axes++;
215                                         headElement = &(pDevice->firstAxis);
216                                     }
217                                 }
218                                 break;
219 
220                             case kHIDUsage_GD_Hatswitch:
221                                 if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
222                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
223                                     if (element) {
224                                         pDevice->hats++;
225                                         headElement = &(pDevice->firstHat);
226                                     }
227                                 }
228                                 break;
229                             case kHIDUsage_GD_DPadUp:
230                             case kHIDUsage_GD_DPadDown:
231                             case kHIDUsage_GD_DPadRight:
232                             case kHIDUsage_GD_DPadLeft:
233                             case kHIDUsage_GD_Start:
234                             case kHIDUsage_GD_Select:
235                             case kHIDUsage_GD_SystemMainMenu:
236                                 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
237                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
238                                     if (element) {
239                                         pDevice->buttons++;
240                                         headElement = &(pDevice->firstButton);
241                                     }
242                                 }
243                                 break;
244                         }
245                         break;
246 
247                     case kHIDPage_Simulation:
248                         switch (usage) {
249                             case kHIDUsage_Sim_Rudder:
250                             case kHIDUsage_Sim_Throttle:
251                                 if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
252                                     element = (recElement *) SDL_calloc(1, sizeof (recElement));
253                                     if (element) {
254                                         pDevice->axes++;
255                                         headElement = &(pDevice->firstAxis);
256                                     }
257                                 }
258                                 break;
259 
260                             default:
261                                 break;
262                         }
263                         break;
264 
265                     case kHIDPage_Button:
266                     case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
267                         if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
268                             element = (recElement *) SDL_calloc(1, sizeof (recElement));
269                             if (element) {
270                                 pDevice->buttons++;
271                                 headElement = &(pDevice->firstButton);
272                             }
273                         }
274                         break;
275 
276                     default:
277                         break;
278                 }
279             }
280             break;
281 
282             case kIOHIDElementTypeCollection: {
283                 CFArrayRef array = IOHIDElementGetChildren(refElement);
284                 if (array) {
285                     AddHIDElements(array, pDevice);
286                 }
287             }
288             break;
289 
290             default:
291                 break;
292         }
293 
294         if (element && headElement) {       /* add to list */
295             recElement *elementPrevious = NULL;
296             recElement *elementCurrent = *headElement;
297             while (elementCurrent && usage >= elementCurrent->usage) {
298                 elementPrevious = elementCurrent;
299                 elementCurrent = elementCurrent->pNext;
300             }
301             if (elementPrevious) {
302                 elementPrevious->pNext = element;
303             } else {
304                 *headElement = element;
305             }
306 
307             element->elementRef = refElement;
308             element->usagePage = usagePage;
309             element->usage = usage;
310             element->pNext = elementCurrent;
311 
312             element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
313             element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
314             element->cookie = IOHIDElementGetCookie(refElement);
315 
316             pDevice->elements++;
317         }
318     }
319 }
320 
321 static SDL_bool
GetDeviceInfo(IOHIDDeviceRef hidDevice,recDevice * pDevice)322 GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
323 {
324     Uint32 *guid32 = NULL;
325     CFTypeRef refCF = NULL;
326     CFArrayRef array = NULL;
327 
328     /* get usage page and usage */
329     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
330     if (refCF) {
331         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
332     }
333     if (pDevice->usagePage != kHIDPage_GenericDesktop) {
334         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
335     }
336 
337     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
338     if (refCF) {
339         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
340     }
341 
342     if ((pDevice->usage != kHIDUsage_GD_Joystick &&
343          pDevice->usage != kHIDUsage_GD_GamePad &&
344          pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
345         return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
346     }
347 
348     pDevice->deviceRef = hidDevice;
349 
350     /* get device name */
351     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
352     if (!refCF) {
353         /* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
354         refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
355     }
356     if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
357         SDL_strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
358     }
359 
360     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
361     if (refCF) {
362         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->guid.data[0]);
363     }
364 
365     refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
366     if (refCF) {
367         CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->guid.data[8]);
368     }
369 
370     /* Check to make sure we have a vendor and product ID
371        If we don't, use the same algorithm as the Linux code for Bluetooth devices */
372     guid32 = (Uint32*)pDevice->guid.data;
373     if (!guid32[0] && !guid32[1]) {
374         /* If we don't have a vendor and product ID this is probably a Bluetooth device */
375         const Uint16 BUS_BLUETOOTH = 0x05;
376         Uint16 *guid16 = (Uint16 *)guid32;
377         *guid16++ = BUS_BLUETOOTH;
378         *guid16++ = 0;
379         SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
380     }
381 
382     array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
383     if (array) {
384         AddHIDElements(array, pDevice);
385         CFRelease(array);
386     }
387 
388     return SDL_TRUE;
389 }
390 
391 static SDL_bool
JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)392 JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
393 {
394     recDevice *i;
395     for (i = gpDeviceList; i != NULL; i = i->pNext) {
396         if (i->deviceRef == ioHIDDeviceObject) {
397             return SDL_TRUE;
398         }
399     }
400     return SDL_FALSE;
401 }
402 
403 
404 static void
JoystickDeviceWasAddedCallback(void * ctx,IOReturn res,void * sender,IOHIDDeviceRef ioHIDDeviceObject)405 JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
406 {
407     recDevice *device;
408     int device_index = 0;
409     io_service_t ioservice;
410 
411     if (res != kIOReturnSuccess) {
412         return;
413     }
414 
415     if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
416         return;  /* IOKit sent us a duplicate. */
417     }
418 
419     device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
420 
421     if (!device) {
422         SDL_OutOfMemory();
423         return;
424     }
425 
426     if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
427         SDL_free(device);
428         return;   /* not a device we care about, probably. */
429     }
430 
431     /* Get notified when this device is disconnected. */
432     IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
433     IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
434 
435     /* Allocate an instance ID for this device */
436     device->instance_id = ++s_joystick_instance_id;
437 
438     /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
439     ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
440 #if SDL_HAPTIC_IOKIT
441     if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
442         device->ffservice = ioservice;
443         MacHaptic_MaybeAddDevice(ioservice);
444     }
445 #endif
446 
447     /* Add device to the end of the list */
448     if ( !gpDeviceList ) {
449         gpDeviceList = device;
450     } else {
451         recDevice *curdevice;
452 
453         curdevice = gpDeviceList;
454         while ( curdevice->pNext ) {
455             ++device_index;
456             curdevice = curdevice->pNext;
457         }
458         curdevice->pNext = device;
459         ++device_index;  /* bump by one since we counted by pNext. */
460     }
461 
462     SDL_PrivateJoystickAdded(device_index);
463 }
464 
465 static SDL_bool
ConfigHIDManager(CFArrayRef matchingArray)466 ConfigHIDManager(CFArrayRef matchingArray)
467 {
468     CFRunLoopRef runloop = CFRunLoopGetCurrent();
469 
470     if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
471         return SDL_FALSE;
472     }
473 
474     IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
475     IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
476     IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
477 
478     while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
479         /* no-op. Callback fires once per existing device. */
480     }
481 
482     /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
483 
484     return SDL_TRUE;  /* good to go. */
485 }
486 
487 
488 static CFDictionaryRef
CreateHIDDeviceMatchDictionary(const UInt32 page,const UInt32 usage,int * okay)489 CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
490 {
491     CFDictionaryRef retval = NULL;
492     CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
493     CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
494     const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
495     const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
496 
497     if (pageNumRef && usageNumRef) {
498         retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
499     }
500 
501     if (pageNumRef) {
502         CFRelease(pageNumRef);
503     }
504     if (usageNumRef) {
505         CFRelease(usageNumRef);
506     }
507 
508     if (!retval) {
509         *okay = 0;
510     }
511 
512     return retval;
513 }
514 
515 static SDL_bool
CreateHIDManager(void)516 CreateHIDManager(void)
517 {
518     SDL_bool retval = SDL_FALSE;
519     int okay = 1;
520     const void *vals[] = {
521         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
522         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
523         (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
524     };
525     const size_t numElements = SDL_arraysize(vals);
526     CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
527     size_t i;
528 
529     for (i = 0; i < numElements; i++) {
530         if (vals[i]) {
531             CFRelease((CFTypeRef) vals[i]);
532         }
533     }
534 
535     if (array) {
536         hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
537         if (hidman != NULL) {
538             retval = ConfigHIDManager(array);
539         }
540         CFRelease(array);
541     }
542 
543     return retval;
544 }
545 
546 
547 /* Function to scan the system for joysticks.
548  * Joystick 0 should be the system default joystick.
549  * This function should return the number of available joysticks, or -1
550  * on an unrecoverable fatal error.
551  */
552 int
SDL_SYS_JoystickInit(void)553 SDL_SYS_JoystickInit(void)
554 {
555     if (gpDeviceList) {
556         return SDL_SetError("Joystick: Device list already inited.");
557     }
558 
559     if (!CreateHIDManager()) {
560         return SDL_SetError("Joystick: Couldn't initialize HID Manager");
561     }
562 
563     return SDL_SYS_NumJoysticks();
564 }
565 
566 /* Function to return the number of joystick devices plugged in right now */
567 int
SDL_SYS_NumJoysticks()568 SDL_SYS_NumJoysticks()
569 {
570     recDevice *device = gpDeviceList;
571     int nJoySticks = 0;
572 
573     while (device) {
574         if (!device->removed) {
575             nJoySticks++;
576         }
577         device = device->pNext;
578     }
579 
580     return nJoySticks;
581 }
582 
583 /* Function to cause any queued joystick insertions to be processed
584  */
585 void
SDL_SYS_JoystickDetect()586 SDL_SYS_JoystickDetect()
587 {
588     recDevice *device = gpDeviceList;
589     while (device) {
590         if (device->removed) {
591             device = FreeDevice(device);
592         } else {
593             device = device->pNext;
594         }
595     }
596 
597 	// run this after the checks above so we don't set device->removed and delete the device before
598 	// SDL_SYS_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device
599 	while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
600 		/* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
601 	}
602 }
603 
604 /* Function to get the device-dependent name of a joystick */
605 const char *
SDL_SYS_JoystickNameForDeviceIndex(int device_index)606 SDL_SYS_JoystickNameForDeviceIndex(int device_index)
607 {
608     recDevice *device = GetDeviceForIndex(device_index);
609     return device ? device->product : "UNKNOWN";
610 }
611 
612 /* Function to return the instance id of the joystick at device_index
613  */
614 SDL_JoystickID
SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)615 SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
616 {
617     recDevice *device = GetDeviceForIndex(device_index);
618     return device ? device->instance_id : 0;
619 }
620 
621 /* Function to open a joystick for use.
622  * The joystick to open is specified by the device index.
623  * This should fill the nbuttons and naxes fields of the joystick structure.
624  * It returns 0, or -1 if there is an error.
625  */
626 int
SDL_SYS_JoystickOpen(SDL_Joystick * joystick,int device_index)627 SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
628 {
629     recDevice *device = GetDeviceForIndex(device_index);
630 
631     joystick->instance_id = device->instance_id;
632     joystick->hwdata = device;
633     joystick->name = device->product;
634 
635     joystick->naxes = device->axes;
636     joystick->nhats = device->hats;
637     joystick->nballs = 0;
638     joystick->nbuttons = device->buttons;
639     return 0;
640 }
641 
642 /* Function to query if the joystick is currently attached
643  * It returns SDL_TRUE if attached, SDL_FALSE otherwise.
644  */
645 SDL_bool
SDL_SYS_JoystickAttached(SDL_Joystick * joystick)646 SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
647 {
648     return joystick->hwdata != NULL;
649 }
650 
651 /* Function to update the state of a joystick - called as a device poll.
652  * This function shouldn't update the joystick structure directly,
653  * but instead should call SDL_PrivateJoystick*() to deliver events
654  * and update joystick device state.
655  */
656 void
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)657 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
658 {
659     recDevice *device = joystick->hwdata;
660     recElement *element;
661     SInt32 value, range;
662     int i;
663 
664     if (!device) {
665         return;
666     }
667 
668     if (device->removed) {      /* device was unplugged; ignore it. */
669         if (joystick->hwdata) {
670             joystick->force_recentering = SDL_TRUE;
671             joystick->hwdata = NULL;
672         }
673         return;
674     }
675 
676     element = device->firstAxis;
677     i = 0;
678     while (element) {
679         value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
680         if (value != joystick->axes[i]) {
681             SDL_PrivateJoystickAxis(joystick, i, value);
682         }
683         element = element->pNext;
684         ++i;
685     }
686 
687     element = device->firstButton;
688     i = 0;
689     while (element) {
690         value = GetHIDElementState(device, element);
691         if (value > 1) {          /* handle pressure-sensitive buttons */
692             value = 1;
693         }
694         if (value != joystick->buttons[i]) {
695             SDL_PrivateJoystickButton(joystick, i, value);
696         }
697         element = element->pNext;
698         ++i;
699     }
700 
701     element = device->firstHat;
702     i = 0;
703     while (element) {
704         Uint8 pos = 0;
705 
706         range = (element->max - element->min + 1);
707         value = GetHIDElementState(device, element) - element->min;
708         if (range == 4) {         /* 4 position hatswitch - scale up value */
709             value *= 2;
710         } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
711             value = -1;
712         }
713         switch (value) {
714         case 0:
715             pos = SDL_HAT_UP;
716             break;
717         case 1:
718             pos = SDL_HAT_RIGHTUP;
719             break;
720         case 2:
721             pos = SDL_HAT_RIGHT;
722             break;
723         case 3:
724             pos = SDL_HAT_RIGHTDOWN;
725             break;
726         case 4:
727             pos = SDL_HAT_DOWN;
728             break;
729         case 5:
730             pos = SDL_HAT_LEFTDOWN;
731             break;
732         case 6:
733             pos = SDL_HAT_LEFT;
734             break;
735         case 7:
736             pos = SDL_HAT_LEFTUP;
737             break;
738         default:
739             /* Every other value is mapped to center. We do that because some
740              * joysticks use 8 and some 15 for this value, and apparently
741              * there are even more variants out there - so we try to be generous.
742              */
743             pos = SDL_HAT_CENTERED;
744             break;
745         }
746 
747         if (pos != joystick->hats[i]) {
748             SDL_PrivateJoystickHat(joystick, i, pos);
749         }
750 
751         element = element->pNext;
752         ++i;
753     }
754 }
755 
756 /* Function to close a joystick after use */
757 void
SDL_SYS_JoystickClose(SDL_Joystick * joystick)758 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
759 {
760 }
761 
762 /* Function to perform any system-specific joystick related cleanup */
763 void
SDL_SYS_JoystickQuit(void)764 SDL_SYS_JoystickQuit(void)
765 {
766     while (FreeDevice(gpDeviceList)) {
767         /* spin */
768     }
769 
770     if (hidman) {
771         IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
772         IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
773         CFRelease(hidman);
774         hidman = NULL;
775     }
776 }
777 
778 
SDL_SYS_JoystickGetDeviceGUID(int device_index)779 SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
780 {
781     recDevice *device = GetDeviceForIndex(device_index);
782     SDL_JoystickGUID guid;
783     if (device) {
784         guid = device->guid;
785     } else {
786         SDL_zero(guid);
787     }
788     return guid;
789 }
790 
SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)791 SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick *joystick)
792 {
793     return joystick->hwdata->guid;
794 }
795 
796 #endif /* SDL_JOYSTICK_IOKIT */
797 
798 /* vi: set ts=4 sw=4 expandtab: */
799