21 #include "../../SDL_internal.h"
23 #ifdef SDL_JOYSTICK_IOKIT
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"
34 #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
36 #define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF)
39 static IOHIDManagerRef hidman =
NULL;
42 static recDevice *gpDeviceList =
NULL;
44 void FreeRumbleEffectData(FFEFFECT *effect)
51 SDL_free(effect->lpvTypeSpecificParams);
55 FFEFFECT *CreateRumbleEffectData(
Sint16 magnitude)
61 effect = (FFEFFECT *)
SDL_calloc(1,
sizeof(*effect));
65 effect->dwSize =
sizeof(*effect);
66 effect->dwGain = 10000;
67 effect->dwFlags = FFEFF_OBJECTOFFSETS;
69 effect->dwTriggerButton = FFEB_NOTRIGGER;
72 effect->rgdwAxes = (DWORD *)
SDL_calloc(effect->cAxes,
sizeof(DWORD));
73 if (!effect->rgdwAxes) {
74 FreeRumbleEffectData(effect);
78 effect->rglDirection = (LONG *)
SDL_calloc(effect->cAxes,
sizeof(LONG));
79 if (!effect->rglDirection) {
80 FreeRumbleEffectData(effect);
83 effect->dwFlags |= FFEFF_CARTESIAN;
85 periodic = (FFPERIODIC *)
SDL_calloc(1,
sizeof(*periodic));
87 FreeRumbleEffectData(effect);
90 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
91 periodic->dwPeriod = 1000000;
93 effect->cbTypeSpecificParams =
sizeof(*periodic);
94 effect->lpvTypeSpecificParams = periodic;
101 recDevice *
device = gpDeviceList;
104 if (device_index == 0)
120 pElement = pElementNext;
125 FreeDevice(recDevice *removeDevice)
127 recDevice *pDeviceNext =
NULL;
129 if (removeDevice->deviceRef) {
130 IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
131 CFRelease(removeDevice->deviceRef);
132 removeDevice->deviceRef =
NULL;
136 pDeviceNext = removeDevice->pNext;
138 if ( gpDeviceList == removeDevice ) {
139 gpDeviceList = pDeviceNext;
140 }
else if (gpDeviceList) {
141 recDevice *
device = gpDeviceList;
142 while (
device->pNext != removeDevice) {
145 device->pNext = pDeviceNext;
147 removeDevice->pNext =
NULL;
150 FreeElementList(removeDevice->firstAxis);
151 FreeElementList(removeDevice->firstButton);
152 FreeElementList(removeDevice->firstHat);
160 GetHIDElementState(recDevice *pDevice,
recElement *pElement, SInt32 *pValue)
165 if (pDevice && pDevice->deviceRef && pElement) {
166 IOHIDValueRef valueRef;
167 if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->
elementRef, &valueRef) == kIOReturnSuccess) {
168 value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
186 GetHIDScaledCalibratedState(recDevice * pDevice,
recElement * pElement, SInt32 min, SInt32 max, SInt32 *pValue)
188 const float deviceScale = max - min;
191 if (GetHIDElementState(pDevice, pElement, pValue))
193 if (readScale == 0) {
198 *pValue = ((*pValue - pElement->
minReport) * deviceScale / readScale) + min;
206 JoystickDeviceWasRemovedCallback(
void *
ctx, IOReturn
result,
void *sender)
212 CFRelease(
device->deviceRef);
215 if (
device->ffeffect_ref) {
216 FFDeviceReleaseEffect(
device->ffdevice,
device->ffeffect_ref);
220 FreeRumbleEffectData(
device->ffeffect);
224 FFReleaseDevice(
device->ffdevice);
236 static void AddHIDElement(
const void *
value,
void *parameter);
240 AddHIDElements(CFArrayRef
array, recDevice *pDevice)
242 const CFRange
range = { 0, CFArrayGetCount(
array) };
243 CFArrayApplyFunction(
array,
range, AddHIDElement, pDevice);
247 ElementAlreadyAdded(
const IOHIDElementCookie cookie,
const recElement *listitem) {
249 if (listitem->
cookie == cookie) {
252 listitem = listitem->
pNext;
259 AddHIDElement(
const void *
value,
void *parameter)
261 recDevice *pDevice = (recDevice *) parameter;
262 IOHIDElementRef refElement = (IOHIDElementRef)
value;
263 const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
265 if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
266 const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
267 const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
273 switch (IOHIDElementGetType(refElement)) {
274 case kIOHIDElementTypeInput_Misc:
275 case kIOHIDElementTypeInput_Button:
276 case kIOHIDElementTypeInput_Axis: {
278 case kHIDPage_GenericDesktop:
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)) {
293 headElement = &(pDevice->firstAxis);
298 case kHIDUsage_GD_Hatswitch:
299 if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
303 headElement = &(pDevice->firstHat);
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)) {
318 headElement = &(pDevice->firstButton);
325 case kHIDPage_Simulation:
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)) {
335 headElement = &(pDevice->firstAxis);
345 case kHIDPage_Button:
346 case kHIDPage_Consumer:
347 if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
351 headElement = &(pDevice->firstButton);
362 case kIOHIDElementTypeCollection: {
363 CFArrayRef
array = IOHIDElementGetChildren(refElement);
365 AddHIDElements(
array, pDevice);
374 if (element && headElement) {
377 while (elementCurrent &&
usage >= elementCurrent->
usage) {
378 elementPrevious = elementCurrent;
379 elementCurrent = elementCurrent->
pNext;
381 if (elementPrevious) {
382 elementPrevious->
pNext = element;
384 *headElement = element;
390 element->
pNext = elementCurrent;
392 element->
minReport = element->
min = (SInt32) IOHIDElementGetLogicalMin(refElement);
393 element->
maxReport = element->
max = (SInt32) IOHIDElementGetLogicalMax(refElement);
394 element->
cookie = IOHIDElementGetCookie(refElement);
402 GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
408 const char *manufacturer_remapped;
409 char manufacturer_string[256];
410 char product_string[256];
411 CFTypeRef refCF =
NULL;
417 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
419 CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
421 if (pDevice->usagePage != kHIDPage_GenericDesktop) {
425 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
427 CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
430 if ((pDevice->usage != kHIDUsage_GD_Joystick &&
431 pDevice->usage != kHIDUsage_GD_GamePad &&
432 pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
445 pDevice->deviceRef = hidDevice;
447 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
449 CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
452 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
454 CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
457 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
459 CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
467 refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
468 if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string,
sizeof(manufacturer_string), kCFStringEncodingUTF8))) {
469 manufacturer_string[0] =
'\0';
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));
475 for (
i = (
int)
SDL_strlen(manufacturer_string) - 1;
i > 0; --
i) {
477 manufacturer_string[
i] =
'\0';
484 if (manufacturer_remapped != manufacturer_string) {
485 SDL_strlcpy(manufacturer_string, manufacturer_remapped,
sizeof(manufacturer_string));
489 SDL_strlcpy(pDevice->product, product_string,
sizeof(pDevice->product));
491 SDL_snprintf(pDevice->product,
sizeof(pDevice->product),
"%s %s", manufacturer_string, product_string);
495 #ifdef SDL_JOYSTICK_HIDAPI
502 SDL_memset(pDevice->guid.data, 0,
sizeof(pDevice->guid.data));
504 if (vendor && product) {
516 SDL_strlcpy((
char*)guid16, pDevice->product,
sizeof(pDevice->guid.data) - 4);
519 array = IOHIDDeviceCopyMatchingElements(hidDevice,
NULL, kIOHIDOptionsTypeNone);
521 AddHIDElements(
array, pDevice);
529 JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
532 for (
i = gpDeviceList;
i !=
NULL;
i =
i->pNext) {
533 if (
i->deviceRef == ioHIDDeviceObject) {
542 JoystickDeviceWasAddedCallback(
void *
ctx, IOReturn
res,
void *sender, IOHIDDeviceRef ioHIDDeviceObject)
545 int device_index = 0;
546 io_service_t ioservice;
548 if (
res != kIOReturnSuccess) {
552 if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
562 if (!GetDeviceInfo(ioHIDDeviceObject,
device)) {
573 IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback,
device);
574 IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
580 ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
581 if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
582 device->ffservice = ioservice;
589 if ( !gpDeviceList ) {
592 recDevice *curdevice;
594 curdevice = gpDeviceList;
595 while ( curdevice->pNext ) {
597 curdevice = curdevice->pNext;
599 curdevice->pNext =
device;
607 ConfigHIDManager(CFArrayRef matchingArray)
609 CFRunLoopRef runloop = CFRunLoopGetCurrent();
611 if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
615 IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
616 IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback,
NULL);
617 IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
619 while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,
TRUE) == kCFRunLoopRunHandledSource) {
629 static CFDictionaryRef
630 CreateHIDDeviceMatchDictionary(
const UInt32 page,
const UInt32
usage,
int *okay)
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 };
638 if (pageNumRef && usageNumRef) {
639 retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
643 CFRelease(pageNumRef);
646 CFRelease(usageNumRef);
657 CreateHIDManager(
void)
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),
667 CFArrayRef
array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) :
NULL;
670 for (
i = 0;
i < numElements;
i++) {
672 CFRelease((CFTypeRef) vals[
i]);
677 hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
678 if (hidman !=
NULL) {
689 DARWIN_JoystickInit(
void)
692 return SDL_SetError(
"Joystick: Device list already inited.");
695 if (!CreateHIDManager()) {
696 return SDL_SetError(
"Joystick: Couldn't initialize HID Manager");
703 DARWIN_JoystickGetCount(
void)
705 recDevice *
device = gpDeviceList;
719 DARWIN_JoystickDetect(
void)
721 recDevice *
device = gpDeviceList;
732 while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,
TRUE) == kCFRunLoopRunHandledSource) {
739 DARWIN_JoystickGetDeviceName(
int device_index)
746 DARWIN_JoystickGetDevicePlayerIndex(
int device_index)
752 DARWIN_JoystickSetDevicePlayerIndex(
int device_index,
int player_index)
757 DARWIN_JoystickGetDeviceGUID(
int device_index )
770 DARWIN_JoystickGetDeviceInstanceID(
int device_index)
777 DARWIN_JoystickOpen(SDL_Joystick * joystick,
int device_index)
781 joystick->instance_id =
device->instance_id;
782 joystick->hwdata =
device;
783 joystick->name =
device->product;
785 joystick->naxes =
device->axes;
786 joystick->nhats =
device->hats;
787 joystick->nballs = 0;
788 joystick->nbuttons =
device->buttons;
796 FFStrError(
unsigned int err)
799 case FFERR_DEVICEFULL:
800 return "device full";
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";
815 return "undetermined error";
816 case FFERR_HASEFFECTS:
817 return "device has effects";
818 case FFERR_INCOMPLETEEFFECT:
819 return "incomplete effect";
821 return "internal fault";
822 case FFERR_INVALIDDOWNLOADID:
823 return "invalid download id";
824 case FFERR_INVALIDPARAM:
825 return "invalid parameter";
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";
844 return "unknown error";
849 DARWIN_JoystickInitRumble(recDevice *
device,
Sint16 magnitude)
856 return SDL_SetError(
"Unable to create force feedback device from service: %s", FFStrError(
result));
861 result = FFDeviceSendForceFeedbackCommand(
device->ffdevice, FFSFFC_RESET);
866 result = FFDeviceSendForceFeedbackCommand(
device->ffdevice, FFSFFC_SETACTUATORSON);
868 return SDL_SetError(
"Unable to enable force feedback actuators: %s", FFStrError(
result));
872 device->ffeffect = CreateRumbleEffectData(magnitude);
877 result = FFDeviceCreateEffect(
device->ffdevice, kFFEffectType_Sine_ID,
886 DARWIN_JoystickRumble(SDL_Joystick * joystick,
Uint16 low_frequency_rumble,
Uint16 high_frequency_rumble)
889 recDevice *
device = joystick->hwdata;
892 Sint16 magnitude = (
Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
898 if (
device->ff_initialized) {
899 FFPERIODIC *periodic = ((FFPERIODIC *)
device->ffeffect->lpvTypeSpecificParams);
900 periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
903 (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
908 if (DARWIN_JoystickInitRumble(
device, magnitude) < 0) {
922 DARWIN_JoystickUpdate(SDL_Joystick * joystick)
924 recDevice *
device = joystick->hwdata;
934 if (joystick->hwdata) {
935 joystick->force_recentering =
SDL_TRUE;
936 joystick->hwdata =
NULL;
941 element =
device->firstAxis;
946 goodRead = GetHIDScaledCalibratedState(
device, element, -32768, 32767, &
value);
951 element = element->
pNext;
955 element =
device->firstButton;
958 goodRead = GetHIDElementState(
device, element, &
value);
966 element = element->
pNext;
970 element =
device->firstHat;
977 goodRead = GetHIDElementState(
device, element, &
value);
982 }
else if (
range != 8) {
1022 element = element->
pNext;
1028 DARWIN_JoystickClose(SDL_Joystick * joystick)
1033 DARWIN_JoystickQuit(
void)
1035 while (FreeDevice(gpDeviceList)) {
1040 IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
1041 IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
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,