SDL  2.0
SDL_iokitjoystick.c
Go to the documentation of this file.
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_iokitjoystick_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 
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 
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 
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
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 *
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  */
147  if (removeDevice->joystick) {
148  removeDevice->joystick->hwdata = NULL;
149  }
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
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
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
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
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
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
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
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
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 {
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
527 JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
528 {
529  recDevice *i;
530 
531 #if defined(SDL_JOYSTICK_MFI)
532  extern SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device);
533  if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) {
534  return SDL_TRUE;
535  }
536 #endif
537 
538  for (i = gpDeviceList; i != NULL; i = i->pNext) {
539  if (i->deviceRef == ioHIDDeviceObject) {
540  return SDL_TRUE;
541  }
542  }
543  return SDL_FALSE;
544 }
545 
546 
547 static void
548 JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
549 {
550  recDevice *device;
551  int device_index = 0;
552  io_service_t ioservice;
553 
554  if (res != kIOReturnSuccess) {
555  return;
556  }
557 
558  if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
559  return; /* IOKit sent us a duplicate. */
560  }
561 
562  device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
563  if (!device) {
564  SDL_OutOfMemory();
565  return;
566  }
567 
568  if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
569  FreeDevice(device);
570  return; /* not a device we care about, probably. */
571  }
572 
573  if (SDL_ShouldIgnoreJoystick(device->product, device->guid)) {
574  FreeDevice(device);
575  return;
576  }
577 
578  /* Get notified when this device is disconnected. */
579  IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
580  IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
581  device->runLoopAttached = SDL_TRUE;
582 
583  /* Allocate an instance ID for this device */
584  device->instance_id = SDL_GetNextJoystickInstanceID();
585 
586  /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
587  ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
588  if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
589  device->ffservice = ioservice;
590 #if SDL_HAPTIC_IOKIT
591  MacHaptic_MaybeAddDevice(ioservice);
592 #endif
593  }
594 
595  /* Add device to the end of the list */
596  if ( !gpDeviceList ) {
597  gpDeviceList = device;
598  } else {
599  recDevice *curdevice;
600 
601  curdevice = gpDeviceList;
602  while ( curdevice->pNext ) {
603  ++device_index;
604  curdevice = curdevice->pNext;
605  }
606  curdevice->pNext = device;
607  ++device_index; /* bump by one since we counted by pNext. */
608  }
609 
610  SDL_PrivateJoystickAdded(device->instance_id);
611 }
612 
613 static SDL_bool
614 ConfigHIDManager(CFArrayRef matchingArray)
615 {
616  CFRunLoopRef runloop = CFRunLoopGetCurrent();
617 
618  if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
619  return SDL_FALSE;
620  }
621 
622  IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
623  IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
624  IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
625 
626  while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
627  /* no-op. Callback fires once per existing device. */
628  }
629 
630  /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
631 
632  return SDL_TRUE; /* good to go. */
633 }
634 
635 
636 static CFDictionaryRef
637 CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
638 {
639  CFDictionaryRef retval = NULL;
640  CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
641  CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
642  const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
643  const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
644 
645  if (pageNumRef && usageNumRef) {
646  retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
647  }
648 
649  if (pageNumRef) {
650  CFRelease(pageNumRef);
651  }
652  if (usageNumRef) {
653  CFRelease(usageNumRef);
654  }
655 
656  if (!retval) {
657  *okay = 0;
658  }
659 
660  return retval;
661 }
662 
663 static SDL_bool
664 CreateHIDManager(void)
665 {
667  int okay = 1;
668  const void *vals[] = {
669  (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
670  (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
671  (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
672  };
673  const size_t numElements = SDL_arraysize(vals);
674  CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
675  size_t i;
676 
677  for (i = 0; i < numElements; i++) {
678  if (vals[i]) {
679  CFRelease((CFTypeRef) vals[i]);
680  }
681  }
682 
683  if (array) {
684  hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
685  if (hidman != NULL) {
686  retval = ConfigHIDManager(array);
687  }
688  CFRelease(array);
689  }
690 
691  return retval;
692 }
693 
694 
695 static int
696 DARWIN_JoystickInit(void)
697 {
698  if (gpDeviceList) {
699  return SDL_SetError("Joystick: Device list already inited.");
700  }
701 
702  if (!CreateHIDManager()) {
703  return SDL_SetError("Joystick: Couldn't initialize HID Manager");
704  }
705 
706  return 0;
707 }
708 
709 static int
710 DARWIN_JoystickGetCount(void)
711 {
712  recDevice *device = gpDeviceList;
713  int nJoySticks = 0;
714 
715  while (device) {
716  if (!device->removed) {
717  nJoySticks++;
718  }
719  device = device->pNext;
720  }
721 
722  return nJoySticks;
723 }
724 
725 static void
726 DARWIN_JoystickDetect(void)
727 {
728  recDevice *device = gpDeviceList;
729  while (device) {
730  if (device->removed) {
731  device = FreeDevice(device);
732  } else {
733  device = device->pNext;
734  }
735  }
736 
737  /* run this after the checks above so we don't set device->removed and delete the device before
738  DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
739  while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
740  /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
741  }
742 }
743 
744 /* Function to get the device-dependent name of a joystick */
745 const char *
746 DARWIN_JoystickGetDeviceName(int device_index)
747 {
748  recDevice *device = GetDeviceForIndex(device_index);
749  return device ? device->product : "UNKNOWN";
750 }
751 
752 static int
753 DARWIN_JoystickGetDevicePlayerIndex(int device_index)
754 {
755  return -1;
756 }
757 
758 static void
759 DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
760 {
761 }
762 
763 static SDL_JoystickGUID
764 DARWIN_JoystickGetDeviceGUID( int device_index )
765 {
766  recDevice *device = GetDeviceForIndex(device_index);
767  SDL_JoystickGUID guid;
768  if (device) {
769  guid = device->guid;
770  } else {
771  SDL_zero(guid);
772  }
773  return guid;
774 }
775 
776 static SDL_JoystickID
777 DARWIN_JoystickGetDeviceInstanceID(int device_index)
778 {
779  recDevice *device = GetDeviceForIndex(device_index);
780  return device ? device->instance_id : 0;
781 }
782 
783 static int
784 DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index)
785 {
786  recDevice *device = GetDeviceForIndex(device_index);
787 
788  joystick->instance_id = device->instance_id;
789  joystick->hwdata = device;
790  device->joystick = joystick;
791  joystick->name = device->product;
792 
793  joystick->naxes = device->axes;
794  joystick->nhats = device->hats;
795  joystick->nballs = 0;
796  joystick->nbuttons = device->buttons;
797  return 0;
798 }
799 
800 /*
801  * Like strerror but for force feedback errors.
802  */
803 static const char *
804 FFStrError(unsigned int err)
805 {
806  switch (err) {
807  case FFERR_DEVICEFULL:
808  return "device full";
809  /* This should be valid, but for some reason isn't defined... */
810  /* case FFERR_DEVICENOTREG:
811  return "device not registered"; */
812  case FFERR_DEVICEPAUSED:
813  return "device paused";
814  case FFERR_DEVICERELEASED:
815  return "device released";
816  case FFERR_EFFECTPLAYING:
817  return "effect playing";
818  case FFERR_EFFECTTYPEMISMATCH:
819  return "effect type mismatch";
820  case FFERR_EFFECTTYPENOTSUPPORTED:
821  return "effect type not supported";
822  case FFERR_GENERIC:
823  return "undetermined error";
824  case FFERR_HASEFFECTS:
825  return "device has effects";
826  case FFERR_INCOMPLETEEFFECT:
827  return "incomplete effect";
828  case FFERR_INTERNAL:
829  return "internal fault";
830  case FFERR_INVALIDDOWNLOADID:
831  return "invalid download id";
832  case FFERR_INVALIDPARAM:
833  return "invalid parameter";
834  case FFERR_MOREDATA:
835  return "more data";
836  case FFERR_NOINTERFACE:
837  return "interface not supported";
838  case FFERR_NOTDOWNLOADED:
839  return "effect is not downloaded";
840  case FFERR_NOTINITIALIZED:
841  return "object has not been initialized";
842  case FFERR_OUTOFMEMORY:
843  return "out of memory";
844  case FFERR_UNPLUGGED:
845  return "device is unplugged";
846  case FFERR_UNSUPPORTED:
847  return "function call unsupported";
848  case FFERR_UNSUPPORTEDAXIS:
849  return "axis unsupported";
850 
851  default:
852  return "unknown error";
853  }
854 }
855 
856 static int
857 DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude)
858 {
859  HRESULT result;
860 
861  if (!device->ffdevice) {
862  result = FFCreateDevice(device->ffservice, &device->ffdevice);
863  if (result != FF_OK) {
864  return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));
865  }
866  }
867 
868  /* Reset and then enable actuators */
869  result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);
870  if (result != FF_OK) {
871  return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));
872  }
873 
874  result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);
875  if (result != FF_OK) {
876  return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));
877  }
878 
879  /* Create the effect */
880  device->ffeffect = CreateRumbleEffectData(magnitude);
881  if (!device->ffeffect) {
882  return SDL_OutOfMemory();
883  }
884 
885  result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,
886  device->ffeffect, &device->ffeffect_ref);
887  if (result != FF_OK) {
888  return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));
889  }
890  return 0;
891 }
892 
893 static int
894 DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
895 {
896  HRESULT result;
897  recDevice *device = joystick->hwdata;
898 
899  /* Scale and average the two rumble strengths */
900  Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
901 
902  if (!device) {
903  return SDL_SetError("Rumble failed, device disconnected");
904  }
905 
906  if (!device->ffservice) {
907  return SDL_Unsupported();
908  }
909 
910  if (device->ff_initialized) {
911  FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
912  periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
913 
914  result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
915  (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
916  if (result != FF_OK) {
917  return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
918  }
919  } else {
920  if (DARWIN_JoystickInitRumble(device, magnitude) < 0) {
921  return -1;
922  }
923  device->ff_initialized = SDL_TRUE;
924  }
925 
926  result = FFEffectStart(device->ffeffect_ref, 1, 0);
927  if (result != FF_OK) {
928  return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
929  }
930  return 0;
931 }
932 
933 static int
934 DARWIN_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 right_rumble)
935 {
936  return SDL_Unsupported();
937 }
938 
939 static SDL_bool
940 DARWIN_JoystickHasLED(SDL_Joystick * joystick)
941 {
942  return SDL_FALSE;
943 }
944 
945 static int
946 DARWIN_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
947 {
948  return SDL_Unsupported();
949 }
950 
951 static int
952 DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
953 {
954  return SDL_Unsupported();
955 }
956 
957 static void
958 DARWIN_JoystickUpdate(SDL_Joystick * joystick)
959 {
960  recDevice *device = joystick->hwdata;
961  recElement *element;
962  SInt32 value, range;
963  int i;
964 
965  if (!device) {
966  return;
967  }
968 
969  if (device->removed) { /* device was unplugged; ignore it. */
970  if (joystick->hwdata) {
971  joystick->hwdata = NULL;
972  }
973  return;
974  }
975 
976  element = device->firstAxis;
977  i = 0;
978 
979  int goodRead = SDL_FALSE;
980  while (element) {
981  goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);
982  if (goodRead) {
984  }
985 
986  element = element->pNext;
987  ++i;
988  }
989 
990  element = device->firstButton;
991  i = 0;
992  while (element) {
993  goodRead = GetHIDElementState(device, element, &value);
994  if (goodRead) {
995  if (value > 1) { /* handle pressure-sensitive buttons */
996  value = 1;
997  }
999  }
1000 
1001  element = element->pNext;
1002  ++i;
1003  }
1004 
1005  element = device->firstHat;
1006  i = 0;
1007 
1008  while (element) {
1009  Uint8 pos = 0;
1010 
1011  range = (element->max - element->min + 1);
1012  goodRead = GetHIDElementState(device, element, &value);
1013  if (goodRead) {
1014  value -= element->min;
1015  if (range == 4) { /* 4 position hatswitch - scale up value */
1016  value *= 2;
1017  } else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */
1018  value = -1;
1019  }
1020  switch (value) {
1021  case 0:
1022  pos = SDL_HAT_UP;
1023  break;
1024  case 1:
1025  pos = SDL_HAT_RIGHTUP;
1026  break;
1027  case 2:
1028  pos = SDL_HAT_RIGHT;
1029  break;
1030  case 3:
1031  pos = SDL_HAT_RIGHTDOWN;
1032  break;
1033  case 4:
1034  pos = SDL_HAT_DOWN;
1035  break;
1036  case 5:
1037  pos = SDL_HAT_LEFTDOWN;
1038  break;
1039  case 6:
1040  pos = SDL_HAT_LEFT;
1041  break;
1042  case 7:
1043  pos = SDL_HAT_LEFTUP;
1044  break;
1045  default:
1046  /* Every other value is mapped to center. We do that because some
1047  * joysticks use 8 and some 15 for this value, and apparently
1048  * there are even more variants out there - so we try to be generous.
1049  */
1050  pos = SDL_HAT_CENTERED;
1051  break;
1052  }
1053 
1055  }
1056 
1057  element = element->pNext;
1058  ++i;
1059  }
1060 }
1061 
1062 static void
1063 DARWIN_JoystickClose(SDL_Joystick * joystick)
1064 {
1065  recDevice *device = joystick->hwdata;
1066  if (device) {
1067  device->joystick = NULL;
1068  }
1069 }
1070 
1071 static void
1072 DARWIN_JoystickQuit(void)
1073 {
1074  while (FreeDevice(gpDeviceList)) {
1075  /* spin */
1076  }
1077 
1078  if (hidman) {
1079  IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
1080  IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
1081  CFRelease(hidman);
1082  hidman = NULL;
1083  }
1084 }
1085 
1086 static SDL_bool
1087 DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1088 {
1089  return SDL_FALSE;
1090 }
1091 
1093 {
1094  DARWIN_JoystickInit,
1095  DARWIN_JoystickGetCount,
1096  DARWIN_JoystickDetect,
1097  DARWIN_JoystickGetDeviceName,
1098  DARWIN_JoystickGetDevicePlayerIndex,
1099  DARWIN_JoystickSetDevicePlayerIndex,
1100  DARWIN_JoystickGetDeviceGUID,
1101  DARWIN_JoystickGetDeviceInstanceID,
1102  DARWIN_JoystickOpen,
1103  DARWIN_JoystickRumble,
1104  DARWIN_JoystickRumbleTriggers,
1105  DARWIN_JoystickHasLED,
1106  DARWIN_JoystickSetLED,
1107  DARWIN_JoystickSetSensorsEnabled,
1108  DARWIN_JoystickUpdate,
1109  DARWIN_JoystickClose,
1110  DARWIN_JoystickQuit,
1111  DARWIN_JoystickGetGamepadMapping
1112 };
1113 
1114 #endif /* SDL_JOYSTICK_IOKIT */
1115 
1116 /* vi: set ts=4 sw=4 expandtab: */
unsigned int uint32_t
#define SDL_SetError
#define SDL_memset
#define SDL_LockJoysticks
#define SDL_strlcpy
#define SDL_free
#define SDL_UnlockJoysticks
#define SDL_calloc
#define SDL_SwapLE16(X)
Definition: SDL_endian.h:235
#define SDL_OutOfMemory()
Definition: SDL_error.h:88
#define SDL_Unsupported()
Definition: SDL_error.h:89
const GLubyte GLuint red
Definition: SDL_glfuncs.h:80
SDL_bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
int SDL_PrivateJoystickHat(SDL_Joystick *joystick, Uint8 hat, Uint8 value)
void SDL_PrivateJoystickRemoved(SDL_JoystickID device_instance)
int SDL_PrivateJoystickAxis(SDL_Joystick *joystick, Uint8 axis, Sint16 value)
SDL_bool SDL_ShouldIgnoreJoystick(const char *name, SDL_JoystickGUID guid)
void SDL_PrivateJoystickAdded(SDL_JoystickID device_instance)
SDL_JoystickID SDL_GetNextJoystickInstanceID()
Definition: SDL_joystick.c:262
int SDL_PrivateJoystickButton(SDL_Joystick *joystick, Uint8 button, Uint8 state)
char * SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name)
#define SDL_HAT_LEFTDOWN
Definition: SDL_joystick.h:394
#define SDL_HAT_LEFT
Definition: SDL_joystick.h:390
#define SDL_HAT_RIGHT
Definition: SDL_joystick.h:388
#define SDL_HAT_RIGHTUP
Definition: SDL_joystick.h:391
#define SDL_HAT_LEFTUP
Definition: SDL_joystick.h:393
Sint32 SDL_JoystickID
Definition: SDL_joystick.h:81
#define SDL_HAT_DOWN
Definition: SDL_joystick.h:389
#define SDL_HAT_RIGHTDOWN
Definition: SDL_joystick.h:392
#define SDL_HAT_UP
Definition: SDL_joystick.h:387
#define SDL_HAT_CENTERED
Definition: SDL_joystick.h:386
static SDL_JoystickDeviceItem * GetDeviceForIndex(int device_index)
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLbyte GLbyte blue
GLuint res
GLuint64EXT * result
GLenum array
GLenum GLint * range
GLuint const GLchar * name
GLbyte green
GLsizei const GLfloat * value
GLsizeiptr const void GLenum usage
uint16_t Uint16
Definition: SDL_stdinc.h:197
int16_t Sint16
Definition: SDL_stdinc.h:191
#define SDL_zero(x)
Definition: SDL_stdinc.h:426
SDL_bool
Definition: SDL_stdinc.h:168
@ SDL_TRUE
Definition: SDL_stdinc.h:170
@ SDL_FALSE
Definition: SDL_stdinc.h:169
#define SDL_arraysize(array)
Definition: SDL_stdinc.h:121
uint8_t Uint8
Definition: SDL_stdinc.h:185
int32_t Sint32
Definition: SDL_stdinc.h:203
#define SDL_HARDWARE_BUS_USB
#define SDL_MAX_RUMBLE_DURATION_MS
#define SDL_HARDWARE_BUS_BLUETOOTH
SDL_JoystickDriver SDL_DARWIN_JoystickDriver
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:163
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
#define min(a, b)
Definition: os2iconv.c:45
IOHIDElementCookie cookie
IOHIDElementRef elementRef
uint32_t usagePage
struct recElement * pNext
SDL_bool retval
static SDL_Joystick * joystick
Definition: testjoystick.c:37