SDL  2.0
SDL_sysjoystick.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_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 
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  IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
131  CFRelease(removeDevice->deviceRef);
132  removeDevice->deviceRef = NULL;
133  }
134 
135  /* save next device prior to disposing of this device */
136  pDeviceNext = removeDevice->pNext;
137 
138  if ( gpDeviceList == removeDevice ) {
139  gpDeviceList = pDeviceNext;
140  } else if (gpDeviceList) {
141  recDevice *device = gpDeviceList;
142  while (device->pNext != removeDevice) {
143  device = device->pNext;
144  }
145  device->pNext = pDeviceNext;
146  }
147  removeDevice->pNext = NULL;
148 
149  /* free element lists */
150  FreeElementList(removeDevice->firstAxis);
151  FreeElementList(removeDevice->firstButton);
152  FreeElementList(removeDevice->firstHat);
153 
154  SDL_free(removeDevice);
155  }
156  return pDeviceNext;
157 }
158 
159 static SDL_bool
160 GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue)
161 {
162  SInt32 value = 0;
163  int returnValue = SDL_FALSE;
164 
165  if (pDevice && pDevice->deviceRef && pElement) {
166  IOHIDValueRef valueRef;
167  if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
168  value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
169 
170  /* record min and max for auto calibration */
171  if (value < pElement->minReport) {
172  pElement->minReport = value;
173  }
174  if (value > pElement->maxReport) {
175  pElement->maxReport = value;
176  }
177  *pValue = value;
178 
179  returnValue = SDL_TRUE;
180  }
181  }
182  return returnValue;
183 }
184 
185 static SDL_bool
186 GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max, SInt32 *pValue)
187 {
188  const float deviceScale = max - min;
189  const float readScale = pElement->maxReport - pElement->minReport;
190  int returnValue = SDL_FALSE;
191  if (GetHIDElementState(pDevice, pElement, pValue))
192  {
193  if (readScale == 0) {
194  returnValue = SDL_TRUE; /* no scaling at all */
195  }
196  else
197  {
198  *pValue = ((*pValue - pElement->minReport) * deviceScale / readScale) + min;
199  returnValue = SDL_TRUE;
200  }
201  }
202  return returnValue;
203 }
204 
205 static void
206 JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
207 {
208  recDevice *device = (recDevice *) ctx;
209  device->removed = SDL_TRUE;
210  if (device->deviceRef) {
211  // deviceRef was invalidated due to the remove
212  CFRelease(device->deviceRef);
213  device->deviceRef = NULL;
214  }
215  if (device->ffeffect_ref) {
216  FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref);
217  device->ffeffect_ref = NULL;
218  }
219  if (device->ffeffect) {
220  FreeRumbleEffectData(device->ffeffect);
221  device->ffeffect = NULL;
222  }
223  if (device->ffdevice) {
224  FFReleaseDevice(device->ffdevice);
225  device->ffdevice = NULL;
226  device->ff_initialized = SDL_FALSE;
227  }
228 #if SDL_HAPTIC_IOKIT
230 #endif
231 
232  SDL_PrivateJoystickRemoved(device->instance_id);
233 }
234 
235 
236 static void AddHIDElement(const void *value, void *parameter);
237 
238 /* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
239 static void
240 AddHIDElements(CFArrayRef array, recDevice *pDevice)
241 {
242  const CFRange range = { 0, CFArrayGetCount(array) };
243  CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
244 }
245 
246 static SDL_bool
247 ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
248  while (listitem) {
249  if (listitem->cookie == cookie) {
250  return SDL_TRUE;
251  }
252  listitem = listitem->pNext;
253  }
254  return SDL_FALSE;
255 }
256 
257 /* See if we care about this HID element, and if so, note it in our recDevice. */
258 static void
259 AddHIDElement(const void *value, void *parameter)
260 {
261  recDevice *pDevice = (recDevice *) parameter;
262  IOHIDElementRef refElement = (IOHIDElementRef) value;
263  const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
264 
265  if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
266  const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
267  const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
268  const uint32_t usage = IOHIDElementGetUsage(refElement);
269  recElement *element = NULL;
270  recElement **headElement = NULL;
271 
272  /* look at types of interest */
273  switch (IOHIDElementGetType(refElement)) {
274  case kIOHIDElementTypeInput_Misc:
275  case kIOHIDElementTypeInput_Button:
276  case kIOHIDElementTypeInput_Axis: {
277  switch (usagePage) { /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
278  case kHIDPage_GenericDesktop:
279  switch (usage) {
280  case kHIDUsage_GD_X:
281  case kHIDUsage_GD_Y:
282  case kHIDUsage_GD_Z:
283  case kHIDUsage_GD_Rx:
284  case kHIDUsage_GD_Ry:
285  case kHIDUsage_GD_Rz:
286  case kHIDUsage_GD_Slider:
287  case kHIDUsage_GD_Dial:
288  case kHIDUsage_GD_Wheel:
289  if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
290  element = (recElement *) SDL_calloc(1, sizeof (recElement));
291  if (element) {
292  pDevice->axes++;
293  headElement = &(pDevice->firstAxis);
294  }
295  }
296  break;
297 
298  case kHIDUsage_GD_Hatswitch:
299  if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
300  element = (recElement *) SDL_calloc(1, sizeof (recElement));
301  if (element) {
302  pDevice->hats++;
303  headElement = &(pDevice->firstHat);
304  }
305  }
306  break;
307  case kHIDUsage_GD_DPadUp:
308  case kHIDUsage_GD_DPadDown:
309  case kHIDUsage_GD_DPadRight:
310  case kHIDUsage_GD_DPadLeft:
311  case kHIDUsage_GD_Start:
312  case kHIDUsage_GD_Select:
313  case kHIDUsage_GD_SystemMainMenu:
314  if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
315  element = (recElement *) SDL_calloc(1, sizeof (recElement));
316  if (element) {
317  pDevice->buttons++;
318  headElement = &(pDevice->firstButton);
319  }
320  }
321  break;
322  }
323  break;
324 
325  case kHIDPage_Simulation:
326  switch (usage) {
327  case kHIDUsage_Sim_Rudder:
328  case kHIDUsage_Sim_Throttle:
329  case kHIDUsage_Sim_Accelerator:
330  case kHIDUsage_Sim_Brake:
331  if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
332  element = (recElement *) SDL_calloc(1, sizeof (recElement));
333  if (element) {
334  pDevice->axes++;
335  headElement = &(pDevice->firstAxis);
336  }
337  }
338  break;
339 
340  default:
341  break;
342  }
343  break;
344 
345  case kHIDPage_Button:
346  case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
347  if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
348  element = (recElement *) SDL_calloc(1, sizeof (recElement));
349  if (element) {
350  pDevice->buttons++;
351  headElement = &(pDevice->firstButton);
352  }
353  }
354  break;
355 
356  default:
357  break;
358  }
359  }
360  break;
361 
362  case kIOHIDElementTypeCollection: {
363  CFArrayRef array = IOHIDElementGetChildren(refElement);
364  if (array) {
365  AddHIDElements(array, pDevice);
366  }
367  }
368  break;
369 
370  default:
371  break;
372  }
373 
374  if (element && headElement) { /* add to list */
375  recElement *elementPrevious = NULL;
376  recElement *elementCurrent = *headElement;
377  while (elementCurrent && usage >= elementCurrent->usage) {
378  elementPrevious = elementCurrent;
379  elementCurrent = elementCurrent->pNext;
380  }
381  if (elementPrevious) {
382  elementPrevious->pNext = element;
383  } else {
384  *headElement = element;
385  }
386 
387  element->elementRef = refElement;
388  element->usagePage = usagePage;
389  element->usage = usage;
390  element->pNext = elementCurrent;
391 
392  element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
393  element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
394  element->cookie = IOHIDElementGetCookie(refElement);
395 
396  pDevice->elements++;
397  }
398  }
399 }
400 
401 static SDL_bool
402 GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
403 {
404  Sint32 vendor = 0;
405  Sint32 product = 0;
406  Sint32 version = 0;
407  const char *name;
408  const char *manufacturer_remapped;
409  char manufacturer_string[256];
410  char product_string[256];
411  CFTypeRef refCF = NULL;
412  CFArrayRef array = NULL;
413  Uint16 *guid16 = (Uint16 *)pDevice->guid.data;
414  int i;
415 
416  /* get usage page and usage */
417  refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
418  if (refCF) {
419  CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
420  }
421  if (pDevice->usagePage != kHIDPage_GenericDesktop) {
422  return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
423  }
424 
425  refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
426  if (refCF) {
427  CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
428  }
429 
430  if ((pDevice->usage != kHIDUsage_GD_Joystick &&
431  pDevice->usage != kHIDUsage_GD_GamePad &&
432  pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
433  return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
434  }
435 
436  /* Make sure we retain the use of the IOKit-provided device-object,
437  lest the device get disconnected and we try to use it. (Fixes
438  SDL-Bugzilla #4961, aka. https://bugzilla.libsdl.org/show_bug.cgi?id=4961 )
439  */
440  CFRetain(hidDevice);
441 
442  /* Now that we've CFRetain'ed the device-object (for our use), we'll
443  save the reference to it.
444  */
445  pDevice->deviceRef = hidDevice;
446 
447  refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
448  if (refCF) {
449  CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
450  }
451 
452  refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
453  if (refCF) {
454  CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
455  }
456 
457  refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
458  if (refCF) {
459  CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
460  }
461 
462  /* get device name */
463  name = SDL_GetCustomJoystickName(vendor, product);
464  if (name) {
465  SDL_strlcpy(pDevice->product, name, sizeof(pDevice->product));
466  } else {
467  refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
468  if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) {
469  manufacturer_string[0] = '\0';
470  }
471  refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
472  if ((!refCF) || (!CFStringGetCString(refCF, product_string, sizeof(product_string), kCFStringEncodingUTF8))) {
473  SDL_strlcpy(product_string, "Unidentified joystick", sizeof(product_string));
474  }
475  for (i = (int)SDL_strlen(manufacturer_string) - 1; i > 0; --i) {
476  if (SDL_isspace(manufacturer_string[i])) {
477  manufacturer_string[i] = '\0';
478  } else {
479  break;
480  }
481  }
482 
483  manufacturer_remapped = SDL_GetCustomJoystickManufacturer(manufacturer_string);
484  if (manufacturer_remapped != manufacturer_string) {
485  SDL_strlcpy(manufacturer_string, manufacturer_remapped, sizeof(manufacturer_string));
486  }
487 
488  if (SDL_strncasecmp(manufacturer_string, product_string, SDL_strlen(manufacturer_string)) == 0) {
489  SDL_strlcpy(pDevice->product, product_string, sizeof(pDevice->product));
490  } else {
491  SDL_snprintf(pDevice->product, sizeof(pDevice->product), "%s %s", manufacturer_string, product_string);
492  }
493  }
494 
495 #ifdef SDL_JOYSTICK_HIDAPI
496  if (HIDAPI_IsDevicePresent(vendor, product, version, pDevice->product)) {
497  /* The HIDAPI driver is taking care of this device */
498  return 0;
499  }
500 #endif
501 
502  SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
503 
504  if (vendor && product) {
505  *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB);
506  *guid16++ = 0;
507  *guid16++ = SDL_SwapLE16((Uint16)vendor);
508  *guid16++ = 0;
509  *guid16++ = SDL_SwapLE16((Uint16)product);
510  *guid16++ = 0;
511  *guid16++ = SDL_SwapLE16((Uint16)version);
512  *guid16++ = 0;
513  } else {
515  *guid16++ = 0;
516  SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
517  }
518 
519  array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
520  if (array) {
521  AddHIDElements(array, pDevice);
522  CFRelease(array);
523  }
524 
525  return SDL_TRUE;
526 }
527 
528 static SDL_bool
529 JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
530 {
531  recDevice *i;
532  for (i = gpDeviceList; i != NULL; i = i->pNext) {
533  if (i->deviceRef == ioHIDDeviceObject) {
534  return SDL_TRUE;
535  }
536  }
537  return SDL_FALSE;
538 }
539 
540 
541 static void
542 JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
543 {
544  recDevice *device;
545  int device_index = 0;
546  io_service_t ioservice;
547 
548  if (res != kIOReturnSuccess) {
549  return;
550  }
551 
552  if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
553  return; /* IOKit sent us a duplicate. */
554  }
555 
556  device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
557  if (!device) {
558  SDL_OutOfMemory();
559  return;
560  }
561 
562  if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
563  FreeDevice(device);
564  return; /* not a device we care about, probably. */
565  }
566 
567  if (SDL_ShouldIgnoreJoystick(device->product, device->guid)) {
568  FreeDevice(device);
569  return;
570  }
571 
572  /* Get notified when this device is disconnected. */
573  IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
574  IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
575 
576  /* Allocate an instance ID for this device */
577  device->instance_id = SDL_GetNextJoystickInstanceID();
578 
579  /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
580  ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
581  if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
582  device->ffservice = ioservice;
583 #if SDL_HAPTIC_IOKIT
584  MacHaptic_MaybeAddDevice(ioservice);
585 #endif
586  }
587 
588  /* Add device to the end of the list */
589  if ( !gpDeviceList ) {
590  gpDeviceList = device;
591  } else {
592  recDevice *curdevice;
593 
594  curdevice = gpDeviceList;
595  while ( curdevice->pNext ) {
596  ++device_index;
597  curdevice = curdevice->pNext;
598  }
599  curdevice->pNext = device;
600  ++device_index; /* bump by one since we counted by pNext. */
601  }
602 
603  SDL_PrivateJoystickAdded(device->instance_id);
604 }
605 
606 static SDL_bool
607 ConfigHIDManager(CFArrayRef matchingArray)
608 {
609  CFRunLoopRef runloop = CFRunLoopGetCurrent();
610 
611  if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
612  return SDL_FALSE;
613  }
614 
615  IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
616  IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
617  IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
618 
619  while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
620  /* no-op. Callback fires once per existing device. */
621  }
622 
623  /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
624 
625  return SDL_TRUE; /* good to go. */
626 }
627 
628 
629 static CFDictionaryRef
630 CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
631 {
632  CFDictionaryRef retval = NULL;
633  CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
634  CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
635  const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
636  const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
637 
638  if (pageNumRef && usageNumRef) {
639  retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
640  }
641 
642  if (pageNumRef) {
643  CFRelease(pageNumRef);
644  }
645  if (usageNumRef) {
646  CFRelease(usageNumRef);
647  }
648 
649  if (!retval) {
650  *okay = 0;
651  }
652 
653  return retval;
654 }
655 
656 static SDL_bool
657 CreateHIDManager(void)
658 {
660  int okay = 1;
661  const void *vals[] = {
662  (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
663  (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
664  (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
665  };
666  const size_t numElements = SDL_arraysize(vals);
667  CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
668  size_t i;
669 
670  for (i = 0; i < numElements; i++) {
671  if (vals[i]) {
672  CFRelease((CFTypeRef) vals[i]);
673  }
674  }
675 
676  if (array) {
677  hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
678  if (hidman != NULL) {
679  retval = ConfigHIDManager(array);
680  }
681  CFRelease(array);
682  }
683 
684  return retval;
685 }
686 
687 
688 static int
689 DARWIN_JoystickInit(void)
690 {
691  if (gpDeviceList) {
692  return SDL_SetError("Joystick: Device list already inited.");
693  }
694 
695  if (!CreateHIDManager()) {
696  return SDL_SetError("Joystick: Couldn't initialize HID Manager");
697  }
698 
699  return 0;
700 }
701 
702 static int
703 DARWIN_JoystickGetCount(void)
704 {
705  recDevice *device = gpDeviceList;
706  int nJoySticks = 0;
707 
708  while (device) {
709  if (!device->removed) {
710  nJoySticks++;
711  }
712  device = device->pNext;
713  }
714 
715  return nJoySticks;
716 }
717 
718 static void
719 DARWIN_JoystickDetect(void)
720 {
721  recDevice *device = gpDeviceList;
722  while (device) {
723  if (device->removed) {
724  device = FreeDevice(device);
725  } else {
726  device = device->pNext;
727  }
728  }
729 
730  /* run this after the checks above so we don't set device->removed and delete the device before
731  DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
732  while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
733  /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
734  }
735 }
736 
737 /* Function to get the device-dependent name of a joystick */
738 const char *
739 DARWIN_JoystickGetDeviceName(int device_index)
740 {
741  recDevice *device = GetDeviceForIndex(device_index);
742  return device ? device->product : "UNKNOWN";
743 }
744 
745 static int
746 DARWIN_JoystickGetDevicePlayerIndex(int device_index)
747 {
748  return -1;
749 }
750 
751 static void
752 DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
753 {
754 }
755 
756 static SDL_JoystickGUID
757 DARWIN_JoystickGetDeviceGUID( int device_index )
758 {
759  recDevice *device = GetDeviceForIndex(device_index);
760  SDL_JoystickGUID guid;
761  if (device) {
762  guid = device->guid;
763  } else {
764  SDL_zero(guid);
765  }
766  return guid;
767 }
768 
769 static SDL_JoystickID
770 DARWIN_JoystickGetDeviceInstanceID(int device_index)
771 {
772  recDevice *device = GetDeviceForIndex(device_index);
773  return device ? device->instance_id : 0;
774 }
775 
776 static int
777 DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index)
778 {
779  recDevice *device = GetDeviceForIndex(device_index);
780 
781  joystick->instance_id = device->instance_id;
782  joystick->hwdata = device;
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 *
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
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
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->ffservice) {
895  return SDL_Unsupported();
896  }
897 
898  if (device->ff_initialized) {
899  FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
900  periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
901 
902  result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
903  (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
904  if (result != FF_OK) {
905  return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
906  }
907  } else {
908  if (DARWIN_JoystickInitRumble(device, magnitude) < 0) {
909  return -1;
910  }
911  device->ff_initialized = SDL_TRUE;
912  }
913 
914  result = FFEffectStart(device->ffeffect_ref, 1, 0);
915  if (result != FF_OK) {
916  return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
917  }
918  return 0;
919 }
920 
921 static void
922 DARWIN_JoystickUpdate(SDL_Joystick * joystick)
923 {
924  recDevice *device = joystick->hwdata;
925  recElement *element;
926  SInt32 value, range;
927  int i;
928 
929  if (!device) {
930  return;
931  }
932 
933  if (device->removed) { /* device was unplugged; ignore it. */
934  if (joystick->hwdata) {
935  joystick->force_recentering = SDL_TRUE;
936  joystick->hwdata = NULL;
937  }
938  return;
939  }
940 
941  element = device->firstAxis;
942  i = 0;
943 
944  int goodRead = SDL_FALSE;
945  while (element) {
946  goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);
947  if (goodRead) {
948  SDL_PrivateJoystickAxis(joystick, i, value);
949  }
950 
951  element = element->pNext;
952  ++i;
953  }
954 
955  element = device->firstButton;
956  i = 0;
957  while (element) {
958  goodRead = GetHIDElementState(device, element, &value);
959  if (goodRead) {
960  if (value > 1) { /* handle pressure-sensitive buttons */
961  value = 1;
962  }
963  SDL_PrivateJoystickButton(joystick, i, value);
964  }
965 
966  element = element->pNext;
967  ++i;
968  }
969 
970  element = device->firstHat;
971  i = 0;
972 
973  while (element) {
974  Uint8 pos = 0;
975 
976  range = (element->max - element->min + 1);
977  goodRead = GetHIDElementState(device, element, &value);
978  if (goodRead) {
979  value -= element->min;
980  if (range == 4) { /* 4 position hatswitch - scale up value */
981  value *= 2;
982  } else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */
983  value = -1;
984  }
985  switch (value) {
986  case 0:
987  pos = SDL_HAT_UP;
988  break;
989  case 1:
990  pos = SDL_HAT_RIGHTUP;
991  break;
992  case 2:
993  pos = SDL_HAT_RIGHT;
994  break;
995  case 3:
996  pos = SDL_HAT_RIGHTDOWN;
997  break;
998  case 4:
999  pos = SDL_HAT_DOWN;
1000  break;
1001  case 5:
1002  pos = SDL_HAT_LEFTDOWN;
1003  break;
1004  case 6:
1005  pos = SDL_HAT_LEFT;
1006  break;
1007  case 7:
1008  pos = SDL_HAT_LEFTUP;
1009  break;
1010  default:
1011  /* Every other value is mapped to center. We do that because some
1012  * joysticks use 8 and some 15 for this value, and apparently
1013  * there are even more variants out there - so we try to be generous.
1014  */
1015  pos = SDL_HAT_CENTERED;
1016  break;
1017  }
1018 
1019  SDL_PrivateJoystickHat(joystick, i, pos);
1020  }
1021 
1022  element = element->pNext;
1023  ++i;
1024  }
1025 }
1026 
1027 static void
1028 DARWIN_JoystickClose(SDL_Joystick * joystick)
1029 {
1030 }
1031 
1032 static void
1033 DARWIN_JoystickQuit(void)
1034 {
1035  while (FreeDevice(gpDeviceList)) {
1036  /* spin */
1037  }
1038 
1039  if (hidman) {
1040  IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
1041  IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
1042  CFRelease(hidman);
1043  hidman = NULL;
1044  }
1045 }
1046 
1048 {
1049  DARWIN_JoystickInit,
1050  DARWIN_JoystickGetCount,
1051  DARWIN_JoystickDetect,
1052  DARWIN_JoystickGetDeviceName,
1053  DARWIN_JoystickGetDevicePlayerIndex,
1054  DARWIN_JoystickSetDevicePlayerIndex,
1055  DARWIN_JoystickGetDeviceGUID,
1056  DARWIN_JoystickGetDeviceInstanceID,
1057  DARWIN_JoystickOpen,
1058  DARWIN_JoystickRumble,
1059  DARWIN_JoystickUpdate,
1060  DARWIN_JoystickClose,
1061  DARWIN_JoystickQuit,
1062 };
1063 
1064 #endif /* SDL_JOYSTICK_IOKIT */
1065 
1066 /* vi: set ts=4 sw=4 expandtab: */
SDL_zero
#define SDL_zero(x)
Definition: SDL_stdinc.h:418
SDL_ShouldIgnoreJoystick
SDL_bool SDL_ShouldIgnoreJoystick(const char *name, SDL_JoystickGUID guid)
Definition: SDL_joystick.c:1698
Uint8
uint8_t Uint8
Definition: SDL_stdinc.h:179
SDL_memset
#define SDL_memset
Definition: SDL_dynapi_overrides.h:386
SDL_MAX_RUMBLE_DURATION_MS
#define SDL_MAX_RUMBLE_DURATION_MS
Definition: SDL_sysjoystick.h:143
Sint32
int32_t Sint32
Definition: SDL_stdinc.h:197
SDL_events.h
SDL_strlcpy
#define SDL_strlcpy
Definition: SDL_dynapi_overrides.h:394
SDL_GetCustomJoystickName
const char * SDL_GetCustomJoystickName(Uint16 vendor, Uint16 product)
Definition: SDL_joystick.c:1386
Uint16
uint16_t Uint16
Definition: SDL_stdinc.h:191
recElement
Definition: SDL_sysjoystick_c.h:31
SDL_HAT_CENTERED
#define SDL_HAT_CENTERED
Definition: SDL_joystick.h:339
NULL
#define NULL
Definition: begin_code.h:167
SDL_HAT_DOWN
#define SDL_HAT_DOWN
Definition: SDL_joystick.h:342
SDL_joystick.h
SDL_JoystickID
Sint32 SDL_JoystickID
Definition: SDL_joystick.h:81
TRUE
#define TRUE
Definition: edid-parse.c:33
recElement::min
SInt32 min
Definition: SDL_sysjoystick_c.h:35
GetDeviceForIndex
static SDL_JoystickDeviceItem * GetDeviceForIndex(int device_index)
Definition: SDL_sysjoystick.m:87
SDL_PrivateJoystickAdded
void SDL_PrivateJoystickAdded(SDL_JoystickID device_instance)
Definition: SDL_joystick.c:921
HIDAPI_IsDevicePresent
SDL_bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
recElement::cookie
IOHIDElementCookie cookie
Definition: SDL_sysjoystick_c.h:33
SDL_GetNextJoystickInstanceID
SDL_JoystickID SDL_GetNextJoystickInstanceID()
Definition: SDL_joystick.c:250
recElement::max
SInt32 max
Definition: SDL_sysjoystick_c.h:36
recElement::elementRef
IOHIDElementRef elementRef
Definition: SDL_sysjoystick_c.h:32
SDL_SwapLE16
#define SDL_SwapLE16(X)
Definition: SDL_endian.h:235
ctx
EGLContext ctx
Definition: eglext.h:208
result
GLuint64EXT * result
Definition: SDL_opengl_glext.h:9435
SDL_GetCustomJoystickManufacturer
const char * SDL_GetCustomJoystickManufacturer(const char *manufacturer)
Definition: SDL_joystick.c:1373
SDL_HAT_LEFTDOWN
#define SDL_HAT_LEFTDOWN
Definition: SDL_joystick.h:347
SDL_PrivateJoystickRemoved
void SDL_PrivateJoystickRemoved(SDL_JoystickID device_instance)
Definition: SDL_joystick.c:987
SDL_PrivateJoystickAxis
int SDL_PrivateJoystickAxis(SDL_Joystick *joystick, Uint8 axis, Sint16 value)
Definition: SDL_joystick.c:1023
SDL_strncasecmp
#define SDL_strncasecmp
Definition: SDL_dynapi_overrides.h:420
Sint16
int16_t Sint16
Definition: SDL_stdinc.h:185
usage
GLsizeiptr const void GLenum usage
Definition: SDL_opengl_glext.h:540
recElement::usage
uint32_t usage
Definition: SDL_sysjoystick_c.h:34
recElement::maxReport
SInt32 maxReport
Definition: SDL_sysjoystick_c.h:40
array
GLenum array
Definition: SDL_opengl_glext.h:6303
retval
SDL_bool retval
Definition: testgamecontroller.c:65
SDL_free
#define SDL_free
Definition: SDL_dynapi_overrides.h:377
SDL_DARWIN_JoystickDriver
SDL_JoystickDriver SDL_DARWIN_JoystickDriver
name
GLuint const GLchar * name
Definition: SDL_opengl_glext.h:663
recElement::minReport
SInt32 minReport
Definition: SDL_sysjoystick_c.h:39
SDL_PrivateJoystickButton
int SDL_PrivateJoystickButton(SDL_Joystick *joystick, Uint8 button, Uint8 state)
Definition: SDL_joystick.c:1162
MacHaptic_MaybeRemoveDevice
int MacHaptic_MaybeRemoveDevice(io_object_t device)
recElement::usagePage
uint32_t usagePage
Definition: SDL_sysjoystick_c.h:34
SDL_HAT_LEFT
#define SDL_HAT_LEFT
Definition: SDL_joystick.h:343
SDL_isspace
#define SDL_isspace
Definition: SDL_dynapi_overrides.h:383
SDL_TRUE
@ SDL_TRUE
Definition: SDL_stdinc.h:164
SDL_HAT_LEFTUP
#define SDL_HAT_LEFTUP
Definition: SDL_joystick.h:346
SDL_HARDWARE_BUS_USB
#define SDL_HARDWARE_BUS_USB
Definition: SDL_sysjoystick.h:82
SDL_JoystickDriver
Definition: SDL_sysjoystick.h:89
SDL_OutOfMemory
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
SDL_PrivateJoystickHat
int SDL_PrivateJoystickHat(SDL_Joystick *joystick, Uint8 hat, Uint8 value)
Definition: SDL_joystick.c:1086
SDL_arraysize
#define SDL_arraysize(array)
Definition: SDL_stdinc.h:115
SDL_calloc
#define SDL_calloc
Definition: SDL_dynapi_overrides.h:375
recElement::pNext
struct recElement * pNext
Definition: SDL_sysjoystick_c.h:42
uint32_t
unsigned int uint32_t
Definition: SDL_config_windows.h:63
SDL_HAT_RIGHT
#define SDL_HAT_RIGHT
Definition: SDL_joystick.h:341
value
GLsizei const GLfloat * value
Definition: SDL_opengl_glext.h:701
SDL_SetError
#define SDL_SetError
Definition: SDL_dynapi_overrides.h:30
SDL_snprintf
#define SDL_snprintf
Definition: SDL_dynapi_overrides.h:40
SDL_strlen
#define SDL_strlen
Definition: SDL_dynapi_overrides.h:393
range
GLenum GLint * range
Definition: SDL_opengl_glext.h:1872
SDL_bool
SDL_bool
Definition: SDL_stdinc.h:162
SDL_HAT_UP
#define SDL_HAT_UP
Definition: SDL_joystick.h:340
SDL_HARDWARE_BUS_BLUETOOTH
#define SDL_HARDWARE_BUS_BLUETOOTH
Definition: SDL_sysjoystick.h:83
SDL_FALSE
@ SDL_FALSE
Definition: SDL_stdinc.h:163
SDL_Unsupported
#define SDL_Unsupported()
Definition: SDL_error.h:53
res
GLuint res
Definition: SDL_opengl_glext.h:7940
device
static SDL_AudioDeviceID device
Definition: loopwave.c:37
SDL_JoystickGUID
Definition: SDL_joystick.h:70
SDL_HAT_RIGHTDOWN
#define SDL_HAT_RIGHTDOWN
Definition: SDL_joystick.h:345
i
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
SDL_HAT_RIGHTUP
#define SDL_HAT_RIGHTUP
Definition: SDL_joystick.h:344
MacHaptic_MaybeAddDevice
int MacHaptic_MaybeAddDevice(io_object_t device)