21 #include "../../SDL_internal.h"
23 #ifdef SDL_HAPTIC_IOKIT
28 #include "../SDL_syshaptic.h"
30 #include "../../joystick/SDL_sysjoystick.h"
31 #include "../../joystick/darwin/SDL_sysjoystick_c.h"
32 #include "SDL_syshaptic_c.h"
34 #include <IOKit/IOKitLib.h>
35 #include <IOKit/hid/IOHIDKeys.h>
36 #include <IOKit/hid/IOHIDUsageTables.h>
37 #include <ForceFeedback/ForceFeedback.h>
38 #include <ForceFeedback/ForceFeedbackConstants.h>
40 #ifndef IO_OBJECT_NULL
41 #define IO_OBJECT_NULL ((io_service_t)0)
67 FFDeviceObjectReference
device;
77 FFEffectObjectReference
ref;
78 struct FFEFFECT effect;
84 static void SDL_SYS_HapticFreeFFEFFECT(FFEFFECT * effect,
int type);
85 static int HIDGetDeviceProduct(io_service_t dev,
char *
name);
89 static int numhaptics = -1;
95 FFStrError(
unsigned int err)
98 case FFERR_DEVICEFULL:
103 case FFERR_DEVICEPAUSED:
104 return "device paused";
105 case FFERR_DEVICERELEASED:
106 return "device released";
107 case FFERR_EFFECTPLAYING:
108 return "effect playing";
109 case FFERR_EFFECTTYPEMISMATCH:
110 return "effect type mismatch";
111 case FFERR_EFFECTTYPENOTSUPPORTED:
112 return "effect type not supported";
114 return "undetermined error";
115 case FFERR_HASEFFECTS:
116 return "device has effects";
117 case FFERR_INCOMPLETEEFFECT:
118 return "incomplete effect";
120 return "internal fault";
121 case FFERR_INVALIDDOWNLOADID:
122 return "invalid download id";
123 case FFERR_INVALIDPARAM:
124 return "invalid parameter";
127 case FFERR_NOINTERFACE:
128 return "interface not supported";
129 case FFERR_NOTDOWNLOADED:
130 return "effect is not downloaded";
131 case FFERR_NOTINITIALIZED:
132 return "object has not been initialized";
133 case FFERR_OUTOFMEMORY:
134 return "out of memory";
135 case FFERR_UNPLUGGED:
136 return "device is unplugged";
137 case FFERR_UNSUPPORTED:
138 return "function call unsupported";
139 case FFERR_UNSUPPORTEDAXIS:
140 return "axis unsupported";
143 return "unknown error";
156 CFDictionaryRef match;
159 if (numhaptics != -1) {
160 return SDL_SetError(
"Haptic subsystem already initialized!");
165 match = IOServiceMatching(kIOHIDDeviceKey);
167 return SDL_SetError(
"Haptic: Failed to get IOServiceMatching.");
171 result = IOServiceGetMatchingServices(kIOMasterPortDefault, match, &iter);
172 if (
result != kIOReturnSuccess) {
173 return SDL_SetError(
"Haptic: Couldn't create a HID object iterator.");
177 if (!IOIteratorIsValid(iter)) {
181 while ((
device = IOIteratorNext(iter)) != IO_OBJECT_NULL) {
186 IOObjectRelease(iter);
198 HapticByDevIndex(
int device_index)
202 if ((device_index < 0) || (device_index >= numhaptics)) {
206 while (device_index > 0) {
219 CFMutableDictionaryRef hidProperties;
223 if (numhaptics == -1) {
228 if (FFIsForceFeedback(
device) != FF_OK) {
235 if (IOObjectIsEqualTo((io_object_t) item->dev,
device)) {
243 return SDL_SetError(
"Could not allocate haptic storage");
260 if ((
result == KERN_SUCCESS) && hidProperties) {
261 refCF = CFDictionaryGetValue(hidProperties,
262 CFSTR(kIOHIDPrimaryUsagePageKey));
264 if (!CFNumberGetValue(refCF, kCFNumberLongType, &item->usagePage)) {
267 refCF = CFDictionaryGetValue(hidProperties,
268 CFSTR(kIOHIDPrimaryUsageKey));
270 if (!CFNumberGetValue(refCF, kCFNumberLongType, &item->usage)) {
275 CFRelease(hidProperties);
278 if (SDL_hapticlist_tail ==
NULL) {
281 SDL_hapticlist_tail->
next = item;
282 SDL_hapticlist_tail = item;
297 if (numhaptics == -1) {
303 if (IOObjectIsEqualTo((io_object_t) item->dev,
device)) {
312 if (item == SDL_hapticlist_tail) {
313 SDL_hapticlist_tail = prev;
320 IOObjectRelease(item->dev);
337 item = HapticByDevIndex(
index);
345 HIDGetDeviceProduct(io_service_t dev,
char *
name)
347 CFMutableDictionaryRef hidProperties, usbProperties;
348 io_registry_entry_t parent1, parent2;
351 hidProperties = usbProperties = 0;
353 ret = IORegistryEntryCreateCFProperties(dev, &hidProperties,
354 kCFAllocatorDefault, kNilOptions);
355 if ((ret != KERN_SUCCESS) || !hidProperties) {
356 return SDL_SetError(
"Haptic: Unable to create CFProperties.");
363 IORegistryEntryGetParentEntry(dev, kIOServicePlane, &parent1))
365 IORegistryEntryGetParentEntry(parent1, kIOServicePlane, &parent2))
367 IORegistryEntryCreateCFProperties(parent2, &usbProperties,
378 refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductKey));
380 refCF = CFDictionaryGetValue(usbProperties,
381 CFSTR(
"USB Product Name"));
384 if (!CFStringGetCString(refCF,
name, 256,
385 CFStringGetSystemEncoding())) {
386 return SDL_SetError(
"Haptic: CFStringGetCString error retrieving pDevice->product.");
390 CFRelease(usbProperties);
392 return SDL_SetError(
"Haptic: IORegistryEntryCreateCFProperties failed to create usbProperties.");
396 if (kIOReturnSuccess != IOObjectRelease(parent2)) {
397 SDL_SetError(
"Haptic: IOObjectRelease error with parent2.");
399 if (kIOReturnSuccess != IOObjectRelease(parent1)) {
400 SDL_SetError(
"Haptic: IOObjectRelease error with parent1.");
403 return SDL_SetError(
"Haptic: Error getting registry entries.");
410 #define FF_TEST(ff, s) \
411 if (features.supportedEffects & (ff)) supported |= (s)
416 GetSupportedFeatures(SDL_Haptic *
haptic)
419 FFDeviceObjectReference
device;
420 FFCAPABILITIES features;
421 unsigned int supported;
426 ret = FFDeviceGetForceFeedbackCapabilities(
device, &features);
428 return SDL_SetError(
"Haptic: Unable to get device's supported features.");
434 haptic->neffects = features.storageCapacity;
435 haptic->nplaying = features.playbackCapacity;
453 ret = FFDeviceGetForceFeedbackProperty(
device, FFPROP_FFGAIN,
457 }
else if (ret != FFERR_UNSUPPORTED) {
458 return SDL_SetError(
"Haptic: Unable to get if device supports gain: %s.",
463 ret = FFDeviceGetForceFeedbackProperty(
device, FFPROP_AUTOCENTER,
467 }
else if (ret != FFERR_UNSUPPORTED) {
469 (
"Haptic: Unable to get if device supports autocenter: %s.",
474 haptic->naxes = ((features.numFfAxes) > 3) ? 3 : features.numFfAxes;
482 haptic->supported = supported;
491 SDL_SYS_HapticOpenFromService(SDL_Haptic *
haptic, io_service_t service)
506 ret = FFCreateDevice(service, &
haptic->hwdata->device);
508 SDL_SetError(
"Haptic: Unable to create device from service: %s.",
514 ret2 = GetSupportedFeatures(
haptic);
521 ret = FFDeviceSendForceFeedbackCommand(
haptic->hwdata->device,
524 SDL_SetError(
"Haptic: Unable to reset device: %s.", FFStrError(ret));
527 ret = FFDeviceSendForceFeedbackCommand(
haptic->hwdata->device,
528 FFSFFC_SETACTUATORSON);
551 FFReleaseDevice(
haptic->hwdata->device);
569 item = HapticByDevIndex(
haptic->index);
571 return SDL_SYS_HapticOpenFromService(
haptic, item->dev);
581 int device_index = 0;
585 if ((item->usagePage == kHIDPage_GenericDesktop) &&
586 (item->usage == kHIDUsage_GD_Mouse)) {
602 if (joystick->hwdata->ffservice != 0) {
615 if (IOObjectIsEqualTo((io_object_t) ((
size_t)
haptic->hwdata->device),
616 joystick->hwdata->ffservice)) {
629 int device_index = 0;
633 if (IOObjectIsEqualTo((io_object_t) item->dev,
634 joystick->hwdata->ffservice)) {
635 haptic->index = device_index;
641 return SDL_SYS_HapticOpenFromService(
haptic, joystick->hwdata->ffservice);
659 FFReleaseDevice(
haptic->hwdata->device);
683 IOObjectRelease(item->dev);
689 SDL_hapticlist_tail =
NULL;
699 DWORD dwTriggerButton;
701 dwTriggerButton = FFEB_NOTRIGGER;
704 dwTriggerButton = FFJOFS_BUTTON(
button - 1);
707 return dwTriggerButton;
721 effect->dwFlags |= FFEFF_SPHERICAL;
728 if (rglDir ==
NULL) {
732 effect->rglDirection = rglDir;
736 effect->dwFlags |= FFEFF_POLAR;
737 rglDir[0] = dir->
dir[0];
740 effect->dwFlags |= FFEFF_CARTESIAN;
741 rglDir[0] = dir->
dir[0];
743 rglDir[1] = dir->
dir[1];
746 rglDir[2] = dir->
dir[2];
750 effect->dwFlags |= FFEFF_SPHERICAL;
751 rglDir[0] = dir->
dir[0];
753 rglDir[1] = dir->
dir[1];
756 rglDir[2] = dir->
dir[2];
767 #define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)
769 #define CONVERT(x) (((x)*10000) / 0x7FFF)
777 FFCONSTANTFORCE *constant =
NULL;
778 FFPERIODIC *periodic =
NULL;
780 FFRAMPFORCE *ramp =
NULL;
781 FFCUSTOMFORCE *custom =
NULL;
782 FFENVELOPE *envelope =
NULL;
792 dest->dwSize =
sizeof(FFEFFECT);
793 dest->dwSamplePeriod = 0;
794 dest->dwGain = 10000;
795 dest->dwFlags = FFEFF_OBJECTOFFSETS;
799 if (envelope ==
NULL) {
803 dest->lpEnvelope = envelope;
804 envelope->dwSize =
sizeof(FFENVELOPE);
807 dest->cAxes =
haptic->naxes;
808 if (dest->cAxes > 0) {
809 axes =
SDL_malloc(
sizeof(DWORD) * dest->cAxes);
813 axes[0] =
haptic->hwdata->axes[0];
814 if (dest->cAxes > 1) {
815 axes[1] =
haptic->hwdata->axes[1];
817 if (dest->cAxes > 2) {
818 axes[2] =
haptic->hwdata->axes[2];
820 dest->rgdwAxes = axes;
827 hap_constant = &
src->constant;
828 constant =
SDL_malloc(
sizeof(FFCONSTANTFORCE));
829 if (constant ==
NULL) {
832 SDL_memset(constant, 0,
sizeof(FFCONSTANTFORCE));
835 constant->lMagnitude = CONVERT(hap_constant->
level);
836 dest->cbTypeSpecificParams =
sizeof(FFCONSTANTFORCE);
837 dest->lpvTypeSpecificParams = constant;
840 dest->dwDuration = hap_constant->
length * 1000;
841 dest->dwTriggerButton = FFGetTriggerButton(hap_constant->
button);
842 dest->dwTriggerRepeatInterval = hap_constant->
interval;
843 dest->dwStartDelay = hap_constant->
delay * 1000;
846 if (SDL_SYS_SetDirection(dest, &hap_constant->
direction, dest->cAxes)
855 dest->lpEnvelope =
NULL;
857 envelope->dwAttackLevel = CCONVERT(hap_constant->
attack_level);
859 envelope->dwFadeLevel = CCONVERT(hap_constant->
fade_level);
860 envelope->dwFadeTime = hap_constant->
fade_length * 1000;
871 hap_periodic = &
src->periodic;
873 if (periodic ==
NULL) {
880 periodic->lOffset = CONVERT(hap_periodic->
offset);
882 (hap_periodic->
phase + (hap_periodic->
magnitude < 0 ? 18000 : 0)) % 36000;
883 periodic->dwPeriod = hap_periodic->
period * 1000;
884 dest->cbTypeSpecificParams =
sizeof(FFPERIODIC);
885 dest->lpvTypeSpecificParams = periodic;
888 dest->dwDuration = hap_periodic->
length * 1000;
889 dest->dwTriggerButton = FFGetTriggerButton(hap_periodic->
button);
890 dest->dwTriggerRepeatInterval = hap_periodic->
interval;
891 dest->dwStartDelay = hap_periodic->
delay * 1000;
894 if (SDL_SYS_SetDirection(dest, &hap_periodic->
direction, dest->cAxes)
903 dest->lpEnvelope =
NULL;
905 envelope->dwAttackLevel = CCONVERT(hap_periodic->
attack_level);
907 envelope->dwFadeLevel = CCONVERT(hap_periodic->
fade_level);
908 envelope->dwFadeTime = hap_periodic->
fade_length * 1000;
917 hap_condition = &
src->condition;
918 if (dest->cAxes > 0) {
926 for (
i = 0;
i < dest->cAxes;
i++) {
935 CCONVERT(hap_condition->
left_sat[
i] / 2);
940 dest->cbTypeSpecificParams =
sizeof(FFCONDITION) * dest->cAxes;
944 dest->dwDuration = hap_condition->
length * 1000;
945 dest->dwTriggerButton = FFGetTriggerButton(hap_condition->
button);
946 dest->dwTriggerRepeatInterval = hap_condition->
interval;
947 dest->dwStartDelay = hap_condition->
delay * 1000;
950 if (SDL_SYS_SetDirection(dest, &hap_condition->
direction, dest->cAxes)
957 dest->lpEnvelope =
NULL;
962 hap_ramp = &
src->ramp;
970 ramp->lStart = CONVERT(hap_ramp->
start);
971 ramp->lEnd = CONVERT(hap_ramp->
end);
972 dest->cbTypeSpecificParams =
sizeof(FFRAMPFORCE);
973 dest->lpvTypeSpecificParams = ramp;
976 dest->dwDuration = hap_ramp->
length * 1000;
977 dest->dwTriggerButton = FFGetTriggerButton(hap_ramp->
button);
978 dest->dwTriggerRepeatInterval = hap_ramp->
interval;
979 dest->dwStartDelay = hap_ramp->
delay * 1000;
982 if (SDL_SYS_SetDirection(dest, &hap_ramp->
direction, dest->cAxes) < 0) {
989 dest->lpEnvelope =
NULL;
991 envelope->dwAttackLevel = CCONVERT(hap_ramp->
attack_level);
993 envelope->dwFadeLevel = CCONVERT(hap_ramp->
fade_level);
994 envelope->dwFadeTime = hap_ramp->
fade_length * 1000;
1000 hap_custom = &
src->custom;
1002 if (custom ==
NULL) {
1005 SDL_memset(custom, 0,
sizeof(FFCUSTOMFORCE));
1008 custom->cChannels = hap_custom->
channels;
1009 custom->dwSamplePeriod = hap_custom->
period * 1000;
1010 custom->cSamples = hap_custom->
samples;
1011 custom->rglForceData =
1012 SDL_malloc(
sizeof(LONG) * custom->cSamples * custom->cChannels);
1014 custom->rglForceData[
i] = CCONVERT(hap_custom->
data[
i]);
1016 dest->cbTypeSpecificParams =
sizeof(FFCUSTOMFORCE);
1017 dest->lpvTypeSpecificParams = custom;
1020 dest->dwDuration = hap_custom->
length * 1000;
1021 dest->dwTriggerButton = FFGetTriggerButton(hap_custom->
button);
1022 dest->dwTriggerRepeatInterval = hap_custom->
interval;
1023 dest->dwStartDelay = hap_custom->
delay * 1000;
1026 if (SDL_SYS_SetDirection(dest, &hap_custom->
direction, dest->cAxes) <
1035 dest->lpEnvelope =
NULL;
1037 envelope->dwAttackLevel = CCONVERT(hap_custom->
attack_level);
1039 envelope->dwFadeLevel = CCONVERT(hap_custom->
fade_level);
1040 envelope->dwFadeTime = hap_custom->
fade_length * 1000;
1058 SDL_SYS_HapticFreeFFEFFECT(FFEFFECT *
effect,
int type)
1060 FFCUSTOMFORCE *custom;
1068 custom = (FFCUSTOMFORCE *)
effect->lpvTypeSpecificParams;
1070 custom->rglForceData =
NULL;
1088 return kFFEffectType_ConstantForce_ID;
1091 return kFFEffectType_RampForce_ID;
1098 return kFFEffectType_Sine_ID;
1101 return kFFEffectType_Triangle_ID;
1104 return kFFEffectType_SawtoothUp_ID;
1107 return kFFEffectType_SawtoothDown_ID;
1110 return kFFEffectType_Spring_ID;
1113 return kFFEffectType_Damper_ID;
1116 return kFFEffectType_Inertia_ID;
1119 return kFFEffectType_Friction_ID;
1122 return kFFEffectType_CustomForce_ID;
1150 type = SDL_SYS_HapticEffectType(base->
type);
1156 if (SDL_SYS_ToFFEFFECT(
haptic, &effect->
hweffect->effect, base) < 0) {
1157 goto err_effectdone;
1161 ret = FFDeviceCreateEffect(
haptic->hwdata->device,
type,
1165 SDL_SetError(
"Haptic: Unable to create effect: %s.", FFStrError(ret));
1166 goto err_effectdone;
1172 SDL_SYS_HapticFreeFFEFFECT(&effect->
hweffect->effect, base->
type);
1189 FFEffectParameterFlag
flags;
1194 if (SDL_SYS_ToFFEFFECT(
haptic, &temp,
data) < 0) {
1200 flags = FFEP_DIRECTION |
1204 FFEP_TRIGGERBUTTON |
1205 FFEP_TRIGGERREPEATINTERVAL | FFEP_TYPESPECIFICPARAMS;
1208 ret = FFEffectSetParameters(effect->
hweffect->ref, &temp,
flags);
1210 SDL_SetError(
"Haptic: Unable to update effect: %s.", FFStrError(ret));
1215 SDL_SYS_HapticFreeFFEFFECT(&effect->
hweffect->effect,
data->type);
1221 SDL_SYS_HapticFreeFFEFFECT(&temp,
data->type);
1243 ret = FFEffectStart(effect->
hweffect->ref, iter, 0);
1245 return SDL_SetError(
"Haptic: Unable to run the effect: %s.",
1261 ret = FFEffectStop(effect->
hweffect->ref);
1263 return SDL_SetError(
"Haptic: Unable to stop the effect: %s.",
1279 ret = FFDeviceReleaseEffect(
haptic->hwdata->device, effect->
hweffect->ref);
1281 SDL_SetError(
"Haptic: Error removing the effect from the device: %s.",
1284 SDL_SYS_HapticFreeFFEFFECT(&effect->
hweffect->effect,
1299 FFEffectStatusFlag status;
1301 ret = FFEffectGetEffectStatus(effect->
hweffect->ref, &status);
1303 SDL_SetError(
"Haptic: Unable to get effect status: %s.",
1325 ret = FFDeviceSetForceFeedbackProperty(
haptic->hwdata->device,
1326 FFPROP_FFGAIN, &
val);
1328 return SDL_SetError(
"Haptic: Error setting gain: %s.", FFStrError(ret));
1345 if (autocenter == 0) {
1351 ret = FFDeviceSetForceFeedbackProperty(
haptic->hwdata->device,
1352 FFPROP_AUTOCENTER, &
val);
1354 return SDL_SetError(
"Haptic: Error setting autocenter: %s.",
1370 ret = FFDeviceSendForceFeedbackCommand(
haptic->hwdata->device,
1373 return SDL_SetError(
"Haptic: Error pausing device: %s.", FFStrError(ret));
1388 ret = FFDeviceSendForceFeedbackCommand(
haptic->hwdata->device,
1391 return SDL_SetError(
"Haptic: Error pausing device: %s.", FFStrError(ret));
1406 ret = FFDeviceSendForceFeedbackCommand(
haptic->hwdata->device,
1409 return SDL_SetError(
"Haptic: Error stopping device: %s.", FFStrError(ret));