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