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)) {
605 if (joystick->hwdata->ffservice != 0) {
621 if (IOObjectIsEqualTo((io_object_t) ((
size_t)
haptic->hwdata->device),
622 joystick->hwdata->ffservice)) {
635 int device_index = 0;
642 if (IOObjectIsEqualTo((io_object_t) item->dev,
643 joystick->hwdata->ffservice)) {
644 haptic->index = device_index;
650 return SDL_SYS_HapticOpenFromService(
haptic, joystick->hwdata->ffservice);
668 FFReleaseDevice(
haptic->hwdata->device);
692 IOObjectRelease(item->dev);
698 SDL_hapticlist_tail =
NULL;
708 DWORD dwTriggerButton;
710 dwTriggerButton = FFEB_NOTRIGGER;
713 dwTriggerButton = FFJOFS_BUTTON(
button - 1);
716 return dwTriggerButton;
730 effect->dwFlags |= FFEFF_SPHERICAL;
737 if (rglDir ==
NULL) {
741 effect->rglDirection = rglDir;
745 effect->dwFlags |= FFEFF_POLAR;
746 rglDir[0] = dir->
dir[0];
749 effect->dwFlags |= FFEFF_CARTESIAN;
750 rglDir[0] = dir->
dir[0];
752 rglDir[1] = dir->
dir[1];
755 rglDir[2] = dir->
dir[2];
759 effect->dwFlags |= FFEFF_SPHERICAL;
760 rglDir[0] = dir->
dir[0];
762 rglDir[1] = dir->
dir[1];
765 rglDir[2] = dir->
dir[2];
776 #define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)
778 #define CONVERT(x) (((x)*10000) / 0x7FFF)
786 FFCONSTANTFORCE *constant =
NULL;
787 FFPERIODIC *periodic =
NULL;
789 FFRAMPFORCE *ramp =
NULL;
790 FFCUSTOMFORCE *custom =
NULL;
791 FFENVELOPE *envelope =
NULL;
801 dest->dwSize =
sizeof(FFEFFECT);
802 dest->dwSamplePeriod = 0;
803 dest->dwGain = 10000;
804 dest->dwFlags = FFEFF_OBJECTOFFSETS;
808 if (envelope ==
NULL) {
812 dest->lpEnvelope = envelope;
813 envelope->dwSize =
sizeof(FFENVELOPE);
816 dest->cAxes =
haptic->naxes;
817 if (dest->cAxes > 0) {
818 axes =
SDL_malloc(
sizeof(DWORD) * dest->cAxes);
822 axes[0] =
haptic->hwdata->axes[0];
823 if (dest->cAxes > 1) {
824 axes[1] =
haptic->hwdata->axes[1];
826 if (dest->cAxes > 2) {
827 axes[2] =
haptic->hwdata->axes[2];
829 dest->rgdwAxes = axes;
836 hap_constant = &
src->constant;
837 constant =
SDL_malloc(
sizeof(FFCONSTANTFORCE));
838 if (constant ==
NULL) {
841 SDL_memset(constant, 0,
sizeof(FFCONSTANTFORCE));
844 constant->lMagnitude = CONVERT(hap_constant->
level);
845 dest->cbTypeSpecificParams =
sizeof(FFCONSTANTFORCE);
846 dest->lpvTypeSpecificParams = constant;
849 dest->dwDuration = hap_constant->
length * 1000;
850 dest->dwTriggerButton = FFGetTriggerButton(hap_constant->
button);
851 dest->dwTriggerRepeatInterval = hap_constant->
interval;
852 dest->dwStartDelay = hap_constant->
delay * 1000;
855 if (SDL_SYS_SetDirection(dest, &hap_constant->
direction, dest->cAxes)
864 dest->lpEnvelope =
NULL;
866 envelope->dwAttackLevel = CCONVERT(hap_constant->
attack_level);
868 envelope->dwFadeLevel = CCONVERT(hap_constant->
fade_level);
869 envelope->dwFadeTime = hap_constant->
fade_length * 1000;
880 hap_periodic = &
src->periodic;
882 if (periodic ==
NULL) {
889 periodic->lOffset = CONVERT(hap_periodic->
offset);
891 (hap_periodic->
phase + (hap_periodic->
magnitude < 0 ? 18000 : 0)) % 36000;
892 periodic->dwPeriod = hap_periodic->
period * 1000;
893 dest->cbTypeSpecificParams =
sizeof(FFPERIODIC);
894 dest->lpvTypeSpecificParams = periodic;
897 dest->dwDuration = hap_periodic->
length * 1000;
898 dest->dwTriggerButton = FFGetTriggerButton(hap_periodic->
button);
899 dest->dwTriggerRepeatInterval = hap_periodic->
interval;
900 dest->dwStartDelay = hap_periodic->
delay * 1000;
903 if (SDL_SYS_SetDirection(dest, &hap_periodic->
direction, dest->cAxes)
912 dest->lpEnvelope =
NULL;
914 envelope->dwAttackLevel = CCONVERT(hap_periodic->
attack_level);
916 envelope->dwFadeLevel = CCONVERT(hap_periodic->
fade_level);
917 envelope->dwFadeTime = hap_periodic->
fade_length * 1000;
926 hap_condition = &
src->condition;
927 if (dest->cAxes > 0) {
935 for (
i = 0;
i < dest->cAxes;
i++) {
944 CCONVERT(hap_condition->
left_sat[
i] / 2);
949 dest->cbTypeSpecificParams =
sizeof(FFCONDITION) * dest->cAxes;
953 dest->dwDuration = hap_condition->
length * 1000;
954 dest->dwTriggerButton = FFGetTriggerButton(hap_condition->
button);
955 dest->dwTriggerRepeatInterval = hap_condition->
interval;
956 dest->dwStartDelay = hap_condition->
delay * 1000;
959 if (SDL_SYS_SetDirection(dest, &hap_condition->
direction, dest->cAxes)
966 dest->lpEnvelope =
NULL;
971 hap_ramp = &
src->ramp;
979 ramp->lStart = CONVERT(hap_ramp->
start);
980 ramp->lEnd = CONVERT(hap_ramp->
end);
981 dest->cbTypeSpecificParams =
sizeof(FFRAMPFORCE);
982 dest->lpvTypeSpecificParams = ramp;
985 dest->dwDuration = hap_ramp->
length * 1000;
986 dest->dwTriggerButton = FFGetTriggerButton(hap_ramp->
button);
987 dest->dwTriggerRepeatInterval = hap_ramp->
interval;
988 dest->dwStartDelay = hap_ramp->
delay * 1000;
991 if (SDL_SYS_SetDirection(dest, &hap_ramp->
direction, dest->cAxes) < 0) {
998 dest->lpEnvelope =
NULL;
1000 envelope->dwAttackLevel = CCONVERT(hap_ramp->
attack_level);
1002 envelope->dwFadeLevel = CCONVERT(hap_ramp->
fade_level);
1003 envelope->dwFadeTime = hap_ramp->
fade_length * 1000;
1009 hap_custom = &
src->custom;
1011 if (custom ==
NULL) {
1014 SDL_memset(custom, 0,
sizeof(FFCUSTOMFORCE));
1017 custom->cChannels = hap_custom->
channels;
1018 custom->dwSamplePeriod = hap_custom->
period * 1000;
1019 custom->cSamples = hap_custom->
samples;
1020 custom->rglForceData =
1021 SDL_malloc(
sizeof(LONG) * custom->cSamples * custom->cChannels);
1023 custom->rglForceData[
i] = CCONVERT(hap_custom->
data[
i]);
1025 dest->cbTypeSpecificParams =
sizeof(FFCUSTOMFORCE);
1026 dest->lpvTypeSpecificParams = custom;
1029 dest->dwDuration = hap_custom->
length * 1000;
1030 dest->dwTriggerButton = FFGetTriggerButton(hap_custom->
button);
1031 dest->dwTriggerRepeatInterval = hap_custom->
interval;
1032 dest->dwStartDelay = hap_custom->
delay * 1000;
1035 if (SDL_SYS_SetDirection(dest, &hap_custom->
direction, dest->cAxes) <
1044 dest->lpEnvelope =
NULL;
1046 envelope->dwAttackLevel = CCONVERT(hap_custom->
attack_level);
1048 envelope->dwFadeLevel = CCONVERT(hap_custom->
fade_level);
1049 envelope->dwFadeTime = hap_custom->
fade_length * 1000;
1067 SDL_SYS_HapticFreeFFEFFECT(FFEFFECT *
effect,
int type)
1069 FFCUSTOMFORCE *custom;
1077 custom = (FFCUSTOMFORCE *)
effect->lpvTypeSpecificParams;
1079 custom->rglForceData =
NULL;
1097 return kFFEffectType_ConstantForce_ID;
1100 return kFFEffectType_RampForce_ID;
1107 return kFFEffectType_Sine_ID;
1110 return kFFEffectType_Triangle_ID;
1113 return kFFEffectType_SawtoothUp_ID;
1116 return kFFEffectType_SawtoothDown_ID;
1119 return kFFEffectType_Spring_ID;
1122 return kFFEffectType_Damper_ID;
1125 return kFFEffectType_Inertia_ID;
1128 return kFFEffectType_Friction_ID;
1131 return kFFEffectType_CustomForce_ID;
1159 type = SDL_SYS_HapticEffectType(
base->type);
1166 goto err_effectdone;
1170 ret = FFDeviceCreateEffect(
haptic->hwdata->device,
type,
1174 SDL_SetError(
"Haptic: Unable to create effect: %s.", FFStrError(ret));
1175 goto err_effectdone;
1181 SDL_SYS_HapticFreeFFEFFECT(&effect->
hweffect->effect,
base->type);
1198 FFEffectParameterFlag
flags;
1203 if (SDL_SYS_ToFFEFFECT(
haptic, &temp,
data) < 0) {
1209 flags = FFEP_DIRECTION |
1213 FFEP_TRIGGERBUTTON |
1214 FFEP_TRIGGERREPEATINTERVAL | FFEP_TYPESPECIFICPARAMS;
1217 ret = FFEffectSetParameters(effect->
hweffect->ref, &temp,
flags);
1219 SDL_SetError(
"Haptic: Unable to update effect: %s.", FFStrError(ret));
1224 SDL_SYS_HapticFreeFFEFFECT(&effect->
hweffect->effect,
data->type);
1230 SDL_SYS_HapticFreeFFEFFECT(&temp,
data->type);
1252 ret = FFEffectStart(effect->
hweffect->ref, iter, 0);
1254 return SDL_SetError(
"Haptic: Unable to run the effect: %s.",
1270 ret = FFEffectStop(effect->
hweffect->ref);
1272 return SDL_SetError(
"Haptic: Unable to stop the effect: %s.",
1288 ret = FFDeviceReleaseEffect(
haptic->hwdata->device, effect->
hweffect->ref);
1290 SDL_SetError(
"Haptic: Error removing the effect from the device: %s.",
1293 SDL_SYS_HapticFreeFFEFFECT(&effect->
hweffect->effect,
1308 FFEffectStatusFlag status;
1310 ret = FFEffectGetEffectStatus(effect->
hweffect->ref, &status);
1312 SDL_SetError(
"Haptic: Unable to get effect status: %s.",
1334 ret = FFDeviceSetForceFeedbackProperty(
haptic->hwdata->device,
1335 FFPROP_FFGAIN, &
val);
1337 return SDL_SetError(
"Haptic: Error setting gain: %s.", FFStrError(ret));
1354 if (autocenter == 0) {
1360 ret = FFDeviceSetForceFeedbackProperty(
haptic->hwdata->device,
1361 FFPROP_AUTOCENTER, &
val);
1363 return SDL_SetError(
"Haptic: Error setting autocenter: %s.",
1379 ret = FFDeviceSendForceFeedbackCommand(
haptic->hwdata->device,
1382 return SDL_SetError(
"Haptic: Error pausing device: %s.", FFStrError(ret));
1397 ret = FFDeviceSendForceFeedbackCommand(
haptic->hwdata->device,
1400 return SDL_SetError(
"Haptic: Error pausing device: %s.", FFStrError(ret));
1415 ret = FFDeviceSendForceFeedbackCommand(
haptic->hwdata->device,
1418 return SDL_SetError(
"Haptic: Error stopping device: %s.", FFStrError(ret));