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