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