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