Path: blob/master/thirdparty/sdl/haptic/darwin/SDL_syshaptic.c
9913 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_HAPTIC_IOKIT2324#include "../SDL_syshaptic.h"25#include "../../joystick/SDL_sysjoystick.h" // For the real SDL_Joystick26#include "../../joystick/darwin/SDL_iokitjoystick_c.h" // For joystick hwdata27#include "SDL_syshaptic_c.h"2829#include <IOKit/IOKitLib.h>30#include <IOKit/hid/IOHIDKeys.h>31#include <IOKit/hid/IOHIDUsageTables.h>32#include <ForceFeedback/ForceFeedback.h>33#include <ForceFeedback/ForceFeedbackConstants.h>3435#ifndef IO_OBJECT_NULL36#define IO_OBJECT_NULL ((io_service_t)0)37#endif3839/*40* List of available haptic devices.41*/42typedef struct SDL_hapticlist_item43{44SDL_HapticID instance_id;45char name[256]; // Name of the device.4647io_service_t dev; // Node we use to create the device.48SDL_Haptic *haptic; // Haptic currently associated with it.4950// Usage pages for determining if it's a mouse or not.51long usage;52long usagePage;5354struct SDL_hapticlist_item *next;55} SDL_hapticlist_item;5657/*58* Haptic system hardware data.59*/60struct haptic_hwdata61{62FFDeviceObjectReference device; // Hardware device.63UInt8 axes[3];64};6566/*67* Haptic system effect data.68*/69struct haptic_hweffect70{71FFEffectObjectReference ref; // Reference.72struct FFEFFECT effect; // Hardware effect.73};7475/*76* Prototypes.77*/78static void SDL_SYS_HapticFreeFFEFFECT(FFEFFECT *effect, int type);79static bool HIDGetDeviceProduct(io_service_t dev, char *name);8081static SDL_hapticlist_item *SDL_hapticlist = NULL;82static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;83static int numhaptics = -1;8485/*86* Like strerror but for force feedback errors.87*/88static const char *FFStrError(unsigned int err)89{90switch (err) {91case FFERR_DEVICEFULL:92return "device full";93// This should be valid, but for some reason isn't defined...94/* case FFERR_DEVICENOTREG:95return "device not registered"; */96case FFERR_DEVICEPAUSED:97return "device paused";98case FFERR_DEVICERELEASED:99return "device released";100case FFERR_EFFECTPLAYING:101return "effect playing";102case FFERR_EFFECTTYPEMISMATCH:103return "effect type mismatch";104case FFERR_EFFECTTYPENOTSUPPORTED:105return "effect type not supported";106case FFERR_GENERIC:107return "undetermined error";108case FFERR_HASEFFECTS:109return "device has effects";110case FFERR_INCOMPLETEEFFECT:111return "incomplete effect";112case FFERR_INTERNAL:113return "internal fault";114case FFERR_INVALIDDOWNLOADID:115return "invalid download id";116case FFERR_INVALIDPARAM:117return "invalid parameter";118case FFERR_MOREDATA:119return "more data";120case FFERR_NOINTERFACE:121return "interface not supported";122case FFERR_NOTDOWNLOADED:123return "effect is not downloaded";124case FFERR_NOTINITIALIZED:125return "object has not been initialized";126case FFERR_OUTOFMEMORY:127return "out of memory";128case FFERR_UNPLUGGED:129return "device is unplugged";130case FFERR_UNSUPPORTED:131return "function call unsupported";132case FFERR_UNSUPPORTEDAXIS:133return "axis unsupported";134135default:136return "unknown error";137}138}139140/*141* Initializes the haptic subsystem.142*/143bool SDL_SYS_HapticInit(void)144{145IOReturn result;146io_iterator_t iter;147CFDictionaryRef match;148io_service_t device;149150if (numhaptics != -1) {151return SDL_SetError("Haptic subsystem already initialized!");152}153numhaptics = 0;154155// Get HID devices.156match = IOServiceMatching(kIOHIDDeviceKey);157if (!match) {158return SDL_SetError("Haptic: Failed to get IOServiceMatching.");159}160161// Now search I/O Registry for matching devices.162result = IOServiceGetMatchingServices(kIOMainPortDefault, match, &iter);163if (result != kIOReturnSuccess) {164return SDL_SetError("Haptic: Couldn't create a HID object iterator.");165}166// IOServiceGetMatchingServices consumes dictionary.167168if (!IOIteratorIsValid(iter)) { // No iterator.169return true;170}171172while ((device = IOIteratorNext(iter)) != IO_OBJECT_NULL) {173MacHaptic_MaybeAddDevice(device);174// always release as the AddDevice will retain IF it's a forcefeedback device175IOObjectRelease(device);176}177IOObjectRelease(iter);178179return true;180}181182int SDL_SYS_NumHaptics(void)183{184return numhaptics;185}186187static SDL_hapticlist_item *HapticByDevIndex(int device_index)188{189SDL_hapticlist_item *item = SDL_hapticlist;190191if ((device_index < 0) || (device_index >= numhaptics)) {192return NULL;193}194195while (device_index > 0) {196SDL_assert(item != NULL);197--device_index;198item = item->next;199}200201return item;202}203204static SDL_hapticlist_item *HapticByInstanceID(SDL_HapticID instance_id)205{206SDL_hapticlist_item *item;207for (item = SDL_hapticlist; item; item = item->next) {208if (instance_id == item->instance_id) {209return item;210}211}212return NULL;213}214215bool MacHaptic_MaybeAddDevice(io_object_t device)216{217IOReturn result;218CFMutableDictionaryRef hidProperties;219CFTypeRef refCF;220SDL_hapticlist_item *item;221222if (numhaptics == -1) {223return false; // not initialized. We'll pick these up on enumeration if we init later.224}225226// Check for force feedback.227if (FFIsForceFeedback(device) != FF_OK) {228return false;229}230231// Make sure we don't already have it232for (item = SDL_hapticlist; item; item = item->next) {233if (IOObjectIsEqualTo((io_object_t)item->dev, device)) {234// Already added235return false;236}237}238239item = (SDL_hapticlist_item *)SDL_calloc(1, sizeof(SDL_hapticlist_item));240if (!item) {241return SDL_SetError("Could not allocate haptic storage");242}243item->instance_id = SDL_GetNextObjectID();244245// retain it as we are going to keep it around a while246IOObjectRetain(device);247248// Set basic device data.249HIDGetDeviceProduct(device, item->name);250item->dev = device;251252// Set usage pages.253hidProperties = 0;254refCF = 0;255result = IORegistryEntryCreateCFProperties(device,256&hidProperties,257kCFAllocatorDefault,258kNilOptions);259if ((result == KERN_SUCCESS) && hidProperties) {260refCF = CFDictionaryGetValue(hidProperties,261CFSTR(kIOHIDPrimaryUsagePageKey));262if (refCF) {263if (!CFNumberGetValue(refCF, kCFNumberLongType, &item->usagePage)) {264SDL_SetError("Haptic: Receiving device's usage page.");265}266refCF = CFDictionaryGetValue(hidProperties,267CFSTR(kIOHIDPrimaryUsageKey));268if (refCF) {269if (!CFNumberGetValue(refCF, kCFNumberLongType, &item->usage)) {270SDL_SetError("Haptic: Receiving device's usage.");271}272}273}274CFRelease(hidProperties);275}276277if (!SDL_hapticlist_tail) {278SDL_hapticlist = SDL_hapticlist_tail = item;279} else {280SDL_hapticlist_tail->next = item;281SDL_hapticlist_tail = item;282}283284// Device has been added.285++numhaptics;286287return true;288}289290bool MacHaptic_MaybeRemoveDevice(io_object_t device)291{292SDL_hapticlist_item *item;293SDL_hapticlist_item *prev = NULL;294295if (numhaptics == -1) {296return false; // not initialized. ignore this.297}298299for (item = SDL_hapticlist; item; item = item->next) {300// found it, remove it.301if (IOObjectIsEqualTo((io_object_t)item->dev, device)) {302bool result = item->haptic ? true : false;303304if (prev) {305prev->next = item->next;306} else {307SDL_assert(SDL_hapticlist == item);308SDL_hapticlist = item->next;309}310if (item == SDL_hapticlist_tail) {311SDL_hapticlist_tail = prev;312}313314// Need to decrement the haptic count315--numhaptics;316// !!! TODO: Send a haptic remove event?317318IOObjectRelease(item->dev);319SDL_free(item);320return result;321}322prev = item;323}324325return false;326}327328SDL_HapticID SDL_SYS_HapticInstanceID(int index)329{330SDL_hapticlist_item *item;331item = HapticByDevIndex(index);332if (item) {333return item->instance_id;334}335return 0;336}337338/*339* Return the name of a haptic device, does not need to be opened.340*/341const char *SDL_SYS_HapticName(int index)342{343SDL_hapticlist_item *item;344item = HapticByDevIndex(index);345if (item) {346return item->name;347}348return NULL;349}350351/*352* Gets the device's product name.353*/354static bool HIDGetDeviceProduct(io_service_t dev, char *name)355{356CFMutableDictionaryRef hidProperties, usbProperties;357io_registry_entry_t parent1, parent2;358kern_return_t ret;359360hidProperties = usbProperties = 0;361362ret = IORegistryEntryCreateCFProperties(dev, &hidProperties,363kCFAllocatorDefault, kNilOptions);364if ((ret != KERN_SUCCESS) || !hidProperties) {365return SDL_SetError("Haptic: Unable to create CFProperties.");366}367368/* macOS currently is not mirroring all USB properties to HID page so need to look at USB device page also369* get dictionary for USB properties: step up two levels and get CF dictionary for USB properties370*/371if ((KERN_SUCCESS ==372IORegistryEntryGetParentEntry(dev, kIOServicePlane, &parent1)) &&373(KERN_SUCCESS ==374IORegistryEntryGetParentEntry(parent1, kIOServicePlane, &parent2)) &&375(KERN_SUCCESS ==376IORegistryEntryCreateCFProperties(parent2, &usbProperties,377kCFAllocatorDefault,378kNilOptions))) {379if (usbProperties) {380CFTypeRef refCF = 0;381/* get device info382* try hid dictionary first, if fail then go to USB dictionary383*/384385// Get product name386refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductKey));387if (!refCF) {388refCF = CFDictionaryGetValue(usbProperties,389CFSTR("USB Product Name"));390}391if (refCF) {392if (!CFStringGetCString(refCF, name, 256,393CFStringGetSystemEncoding())) {394return SDL_SetError("Haptic: CFStringGetCString error retrieving pDevice->product.");395}396}397398CFRelease(usbProperties);399} else {400return SDL_SetError("Haptic: IORegistryEntryCreateCFProperties failed to create usbProperties.");401}402403// Release stuff.404if (kIOReturnSuccess != IOObjectRelease(parent2)) {405SDL_SetError("Haptic: IOObjectRelease error with parent2.");406}407if (kIOReturnSuccess != IOObjectRelease(parent1)) {408SDL_SetError("Haptic: IOObjectRelease error with parent1.");409}410} else {411return SDL_SetError("Haptic: Error getting registry entries.");412}413414return true;415}416417#define FF_TEST(ff, s) \418if (features.supportedEffects & (ff)) \419supported |= (s)420/*421* Gets supported features.422*/423static bool GetSupportedFeatures(SDL_Haptic *haptic)424{425HRESULT ret;426FFDeviceObjectReference device;427FFCAPABILITIES features;428unsigned int supported;429Uint32 val;430431device = haptic->hwdata->device;432433ret = FFDeviceGetForceFeedbackCapabilities(device, &features);434if (ret != FF_OK) {435return SDL_SetError("Haptic: Unable to get device's supported features.");436}437438supported = 0;439440// Get maximum effects.441haptic->neffects = features.storageCapacity;442haptic->nplaying = features.playbackCapacity;443444// Test for effects.445FF_TEST(FFCAP_ET_CONSTANTFORCE, SDL_HAPTIC_CONSTANT);446FF_TEST(FFCAP_ET_RAMPFORCE, SDL_HAPTIC_RAMP);447FF_TEST(FFCAP_ET_SQUARE, SDL_HAPTIC_SQUARE);448FF_TEST(FFCAP_ET_SINE, SDL_HAPTIC_SINE);449FF_TEST(FFCAP_ET_TRIANGLE, SDL_HAPTIC_TRIANGLE);450FF_TEST(FFCAP_ET_SAWTOOTHUP, SDL_HAPTIC_SAWTOOTHUP);451FF_TEST(FFCAP_ET_SAWTOOTHDOWN, SDL_HAPTIC_SAWTOOTHDOWN);452FF_TEST(FFCAP_ET_SPRING, SDL_HAPTIC_SPRING);453FF_TEST(FFCAP_ET_DAMPER, SDL_HAPTIC_DAMPER);454FF_TEST(FFCAP_ET_INERTIA, SDL_HAPTIC_INERTIA);455FF_TEST(FFCAP_ET_FRICTION, SDL_HAPTIC_FRICTION);456FF_TEST(FFCAP_ET_CUSTOMFORCE, SDL_HAPTIC_CUSTOM);457458// Check if supports gain.459ret = FFDeviceGetForceFeedbackProperty(device, FFPROP_FFGAIN,460&val, sizeof(val));461if (ret == FF_OK) {462supported |= SDL_HAPTIC_GAIN;463} else if (ret != FFERR_UNSUPPORTED) {464return SDL_SetError("Haptic: Unable to get if device supports gain: %s.",465FFStrError(ret));466}467468// Checks if supports autocenter.469ret = FFDeviceGetForceFeedbackProperty(device, FFPROP_AUTOCENTER,470&val, sizeof(val));471if (ret == FF_OK) {472supported |= SDL_HAPTIC_AUTOCENTER;473} else if (ret != FFERR_UNSUPPORTED) {474return SDL_SetError("Haptic: Unable to get if device supports autocenter: %s.",475FFStrError(ret));476}477478// Check for axes, we have an artificial limit on axes479haptic->naxes = ((features.numFfAxes) > 3) ? 3 : features.numFfAxes;480// Actually store the axes we want to use481SDL_memcpy(haptic->hwdata->axes, features.ffAxes,482haptic->naxes * sizeof(Uint8));483484// Always supported features.485supported |= SDL_HAPTIC_STATUS | SDL_HAPTIC_PAUSE;486487haptic->supported = supported;488return true;489}490491/*492* Opens the haptic device from the file descriptor.493*/494static bool SDL_SYS_HapticOpenFromService(SDL_Haptic *haptic, io_service_t service)495{496HRESULT ret;497498// Allocate the hwdata499haptic->hwdata = (struct haptic_hwdata *) SDL_calloc(1, sizeof(*haptic->hwdata));500if (!haptic->hwdata) {501goto creat_err;502}503504// Open the device505ret = FFCreateDevice(service, &haptic->hwdata->device);506if (ret != FF_OK) {507SDL_SetError("Haptic: Unable to create device from service: %s.", FFStrError(ret));508goto creat_err;509}510511// Get supported features.512if (!GetSupportedFeatures(haptic)) {513goto open_err;514}515516// Reset and then enable actuators.517ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,518FFSFFC_RESET);519if (ret != FF_OK) {520SDL_SetError("Haptic: Unable to reset device: %s.", FFStrError(ret));521goto open_err;522}523ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,524FFSFFC_SETACTUATORSON);525if (ret != FF_OK) {526SDL_SetError("Haptic: Unable to enable actuators: %s.",527FFStrError(ret));528goto open_err;529}530531// Allocate effects memory.532haptic->effects = (struct haptic_effect *)533SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects);534if (!haptic->effects) {535goto open_err;536}537// Clear the memory538SDL_memset(haptic->effects, 0,539sizeof(struct haptic_effect) * haptic->neffects);540541return true;542543// Error handling544open_err:545FFReleaseDevice(haptic->hwdata->device);546creat_err:547if (haptic->hwdata) {548SDL_free(haptic->hwdata);549haptic->hwdata = NULL;550}551return false;552}553554/*555* Opens a haptic device for usage.556*/557bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)558{559SDL_hapticlist_item *item;560item = HapticByInstanceID(haptic->instance_id);561562return SDL_SYS_HapticOpenFromService(haptic, item->dev);563}564565/*566* Opens a haptic device from first mouse it finds for usage.567*/568int SDL_SYS_HapticMouse(void)569{570int device_index = 0;571SDL_hapticlist_item *item;572573for (item = SDL_hapticlist; item; item = item->next) {574if ((item->usagePage == kHIDPage_GenericDesktop) &&575(item->usage == kHIDUsage_GD_Mouse)) {576return device_index;577}578++device_index;579}580581return 0;582}583584/*585* Checks to see if a joystick has haptic features.586*/587bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)588{589#ifdef SDL_JOYSTICK_IOKIT590if (joystick->driver != &SDL_DARWIN_JoystickDriver) {591return false;592}593if (joystick->hwdata->ffservice != 0) {594return true;595}596#endif597return false;598}599600/*601* Checks to see if the haptic device and joystick are in reality the same.602*/603bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)604{605#ifdef SDL_JOYSTICK_IOKIT606if (joystick->driver != &SDL_DARWIN_JoystickDriver) {607return false;608}609if (IOObjectIsEqualTo((io_object_t)((size_t)haptic->hwdata->device),610joystick->hwdata->ffservice)) {611return true;612}613#endif614return false;615}616617/*618* Opens a SDL_Haptic from a SDL_Joystick.619*/620bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)621{622#ifdef SDL_JOYSTICK_IOKIT623SDL_hapticlist_item *item;624625if (joystick->driver != &SDL_DARWIN_JoystickDriver) {626return false;627}628for (item = SDL_hapticlist; item; item = item->next) {629if (IOObjectIsEqualTo((io_object_t)item->dev,630joystick->hwdata->ffservice)) {631haptic->instance_id = item->instance_id;632break;633}634}635636if (joystick->name) {637haptic->name = SDL_strdup(joystick->name);638}639640return SDL_SYS_HapticOpenFromService(haptic, joystick->hwdata->ffservice);641#else642return false;643#endif644}645646/*647* Closes the haptic device.648*/649void SDL_SYS_HapticClose(SDL_Haptic *haptic)650{651if (haptic->hwdata) {652653// Free Effects.654SDL_free(haptic->effects);655haptic->effects = NULL;656haptic->neffects = 0;657658// Clean up659FFReleaseDevice(haptic->hwdata->device);660661// Free662SDL_free(haptic->hwdata);663haptic->hwdata = NULL;664}665}666667/*668* Clean up after system specific haptic stuff669*/670void SDL_SYS_HapticQuit(void)671{672SDL_hapticlist_item *item;673SDL_hapticlist_item *next = NULL;674675for (item = SDL_hapticlist; item; item = next) {676next = item->next;677/* Opened and not closed haptics are leaked, this is on purpose.678* Close your haptic devices after usage. */679680// Free the io_service_t681IOObjectRelease(item->dev);682SDL_free(item);683}684685numhaptics = -1;686SDL_hapticlist = NULL;687SDL_hapticlist_tail = NULL;688}689690/*691* Converts an SDL trigger button to an FFEFFECT trigger button.692*/693static DWORD FFGetTriggerButton(Uint16 button)694{695DWORD dwTriggerButton;696697dwTriggerButton = FFEB_NOTRIGGER;698699if (button != 0) {700dwTriggerButton = FFJOFS_BUTTON(button - 1);701}702703return dwTriggerButton;704}705706/*707* Sets the direction.708*/709static bool SDL_SYS_SetDirection(FFEFFECT *effect, const SDL_HapticDirection *dir, int naxes)710{711LONG *rglDir;712713// Handle no axes a part.714if (naxes == 0) {715effect->dwFlags |= FFEFF_SPHERICAL; // Set as default.716effect->rglDirection = NULL;717return true;718}719720// Has axes.721rglDir = SDL_malloc(sizeof(LONG) * naxes);722if (!rglDir) {723return false;724}725SDL_memset(rglDir, 0, sizeof(LONG) * naxes);726effect->rglDirection = rglDir;727728switch (dir->type) {729case SDL_HAPTIC_POLAR:730effect->dwFlags |= FFEFF_POLAR;731rglDir[0] = dir->dir[0];732return true;733case SDL_HAPTIC_CARTESIAN:734effect->dwFlags |= FFEFF_CARTESIAN;735rglDir[0] = dir->dir[0];736if (naxes > 1) {737rglDir[1] = dir->dir[1];738}739if (naxes > 2) {740rglDir[2] = dir->dir[2];741}742return true;743case SDL_HAPTIC_SPHERICAL:744effect->dwFlags |= FFEFF_SPHERICAL;745rglDir[0] = dir->dir[0];746if (naxes > 1) {747rglDir[1] = dir->dir[1];748}749if (naxes > 2) {750rglDir[2] = dir->dir[2];751}752return true;753case SDL_HAPTIC_STEERING_AXIS:754effect->dwFlags |= FFEFF_CARTESIAN;755rglDir[0] = 0;756return true;757758default:759return SDL_SetError("Haptic: Unknown direction type.");760}761}762763// Clamps and converts.764#define CCONVERT(x) (((x) > 0x7FFF) ? 10000 : ((x)*10000) / 0x7FFF)765// Just converts.766#define CONVERT(x) (((x)*10000) / 0x7FFF)767/*768* Creates the FFEFFECT from a SDL_HapticEffect.769*/770static bool SDL_SYS_ToFFEFFECT(SDL_Haptic *haptic, FFEFFECT *dest, const SDL_HapticEffect *src)771{772int i;773FFCONSTANTFORCE *constant = NULL;774FFPERIODIC *periodic = NULL;775FFCONDITION *condition = NULL; // Actually an array of conditions - one per axis.776FFRAMPFORCE *ramp = NULL;777FFCUSTOMFORCE *custom = NULL;778FFENVELOPE *envelope = NULL;779const SDL_HapticConstant *hap_constant = NULL;780const SDL_HapticPeriodic *hap_periodic = NULL;781const SDL_HapticCondition *hap_condition = NULL;782const SDL_HapticRamp *hap_ramp = NULL;783const SDL_HapticCustom *hap_custom = NULL;784DWORD *axes = NULL;785786// Set global stuff.787SDL_memset(dest, 0, sizeof(FFEFFECT));788dest->dwSize = sizeof(FFEFFECT); // Set the structure size.789dest->dwSamplePeriod = 0; // Not used by us.790dest->dwGain = 10000; // Gain is set globally, not locally.791dest->dwFlags = FFEFF_OBJECTOFFSETS; // Seems obligatory.792793// Envelope.794envelope = SDL_calloc(1, sizeof(FFENVELOPE));795if (!envelope) {796return false;797}798dest->lpEnvelope = envelope;799envelope->dwSize = sizeof(FFENVELOPE); // Always should be this.800801// Axes.802if (src->constant.direction.type == SDL_HAPTIC_STEERING_AXIS) {803dest->cAxes = 1;804} else {805dest->cAxes = haptic->naxes;806}807if (dest->cAxes > 0) {808axes = SDL_malloc(sizeof(DWORD) * dest->cAxes);809if (!axes) {810return false;811}812axes[0] = haptic->hwdata->axes[0]; // Always at least one axis.813if (dest->cAxes > 1) {814axes[1] = haptic->hwdata->axes[1];815}816if (dest->cAxes > 2) {817axes[2] = haptic->hwdata->axes[2];818}819dest->rgdwAxes = axes;820}821822// The big type handling switch, even bigger then Linux's version.823switch (src->type) {824case SDL_HAPTIC_CONSTANT:825hap_constant = &src->constant;826constant = SDL_calloc(1, sizeof(FFCONSTANTFORCE));827if (!constant) {828return false;829}830831// Specifics832constant->lMagnitude = CONVERT(hap_constant->level);833dest->cbTypeSpecificParams = sizeof(FFCONSTANTFORCE);834dest->lpvTypeSpecificParams = constant;835836// Generics837dest->dwDuration = hap_constant->length * 1000; // In microseconds.838dest->dwTriggerButton = FFGetTriggerButton(hap_constant->button);839dest->dwTriggerRepeatInterval = hap_constant->interval;840dest->dwStartDelay = hap_constant->delay * 1000; // In microseconds.841842// Direction.843if (!SDL_SYS_SetDirection(dest, &hap_constant->direction, dest->cAxes)) {844return false;845}846847// Envelope848if ((hap_constant->attack_length == 0) && (hap_constant->fade_length == 0)) {849SDL_free(envelope);850dest->lpEnvelope = NULL;851} else {852envelope->dwAttackLevel = CCONVERT(hap_constant->attack_level);853envelope->dwAttackTime = hap_constant->attack_length * 1000;854envelope->dwFadeLevel = CCONVERT(hap_constant->fade_level);855envelope->dwFadeTime = hap_constant->fade_length * 1000;856}857858break;859860case SDL_HAPTIC_SINE:861case SDL_HAPTIC_SQUARE:862case SDL_HAPTIC_TRIANGLE:863case SDL_HAPTIC_SAWTOOTHUP:864case SDL_HAPTIC_SAWTOOTHDOWN:865hap_periodic = &src->periodic;866periodic = SDL_calloc(1, sizeof(FFPERIODIC));867if (!periodic) {868return false;869}870871// Specifics872periodic->dwMagnitude = CONVERT(SDL_abs(hap_periodic->magnitude));873periodic->lOffset = CONVERT(hap_periodic->offset);874periodic->dwPhase =875(hap_periodic->phase + (hap_periodic->magnitude < 0 ? 18000 : 0)) % 36000;876periodic->dwPeriod = hap_periodic->period * 1000;877dest->cbTypeSpecificParams = sizeof(FFPERIODIC);878dest->lpvTypeSpecificParams = periodic;879880// Generics881dest->dwDuration = hap_periodic->length * 1000; // In microseconds.882dest->dwTriggerButton = FFGetTriggerButton(hap_periodic->button);883dest->dwTriggerRepeatInterval = hap_periodic->interval;884dest->dwStartDelay = hap_periodic->delay * 1000; // In microseconds.885886// Direction.887if (!SDL_SYS_SetDirection(dest, &hap_periodic->direction, dest->cAxes)) {888return false;889}890891// Envelope892if ((hap_periodic->attack_length == 0) && (hap_periodic->fade_length == 0)) {893SDL_free(envelope);894dest->lpEnvelope = NULL;895} else {896envelope->dwAttackLevel = CCONVERT(hap_periodic->attack_level);897envelope->dwAttackTime = hap_periodic->attack_length * 1000;898envelope->dwFadeLevel = CCONVERT(hap_periodic->fade_level);899envelope->dwFadeTime = hap_periodic->fade_length * 1000;900}901902break;903904case SDL_HAPTIC_SPRING:905case SDL_HAPTIC_DAMPER:906case SDL_HAPTIC_INERTIA:907case SDL_HAPTIC_FRICTION:908hap_condition = &src->condition;909if (dest->cAxes > 0) {910condition = SDL_calloc(dest->cAxes, sizeof(FFCONDITION));911if (!condition) {912return false;913}914915// Specifics916for (i = 0; i < dest->cAxes; i++) {917condition[i].lOffset = CONVERT(hap_condition->center[i]);918condition[i].lPositiveCoefficient =919CONVERT(hap_condition->right_coeff[i]);920condition[i].lNegativeCoefficient =921CONVERT(hap_condition->left_coeff[i]);922condition[i].dwPositiveSaturation =923CCONVERT(hap_condition->right_sat[i] / 2);924condition[i].dwNegativeSaturation =925CCONVERT(hap_condition->left_sat[i] / 2);926condition[i].lDeadBand = CCONVERT(hap_condition->deadband[i] / 2);927}928}929930dest->cbTypeSpecificParams = sizeof(FFCONDITION) * dest->cAxes;931dest->lpvTypeSpecificParams = condition;932933// Generics934dest->dwDuration = hap_condition->length * 1000; // In microseconds.935dest->dwTriggerButton = FFGetTriggerButton(hap_condition->button);936dest->dwTriggerRepeatInterval = hap_condition->interval;937dest->dwStartDelay = hap_condition->delay * 1000; // In microseconds.938939// Direction.940if (!SDL_SYS_SetDirection(dest, &hap_condition->direction, dest->cAxes)) {941return false;942}943944// Envelope - Not actually supported by most CONDITION implementations.945SDL_free(dest->lpEnvelope);946dest->lpEnvelope = NULL;947948break;949950case SDL_HAPTIC_RAMP:951hap_ramp = &src->ramp;952ramp = SDL_calloc(1, sizeof(FFRAMPFORCE));953if (!ramp) {954return false;955}956957// Specifics958ramp->lStart = CONVERT(hap_ramp->start);959ramp->lEnd = CONVERT(hap_ramp->end);960dest->cbTypeSpecificParams = sizeof(FFRAMPFORCE);961dest->lpvTypeSpecificParams = ramp;962963// Generics964dest->dwDuration = hap_ramp->length * 1000; // In microseconds.965dest->dwTriggerButton = FFGetTriggerButton(hap_ramp->button);966dest->dwTriggerRepeatInterval = hap_ramp->interval;967dest->dwStartDelay = hap_ramp->delay * 1000; // In microseconds.968969// Direction.970if (!SDL_SYS_SetDirection(dest, &hap_ramp->direction, dest->cAxes)) {971return false;972}973974// Envelope975if ((hap_ramp->attack_length == 0) && (hap_ramp->fade_length == 0)) {976SDL_free(envelope);977dest->lpEnvelope = NULL;978} else {979envelope->dwAttackLevel = CCONVERT(hap_ramp->attack_level);980envelope->dwAttackTime = hap_ramp->attack_length * 1000;981envelope->dwFadeLevel = CCONVERT(hap_ramp->fade_level);982envelope->dwFadeTime = hap_ramp->fade_length * 1000;983}984985break;986987case SDL_HAPTIC_CUSTOM:988hap_custom = &src->custom;989custom = SDL_calloc(1, sizeof(FFCUSTOMFORCE));990if (!custom) {991return false;992}993994// Specifics995custom->cChannels = hap_custom->channels;996custom->dwSamplePeriod = hap_custom->period * 1000;997custom->cSamples = hap_custom->samples;998custom->rglForceData =999SDL_malloc(sizeof(LONG) * custom->cSamples * custom->cChannels);1000for (i = 0; i < hap_custom->samples * hap_custom->channels; i++) { // Copy data.1001custom->rglForceData[i] = CCONVERT(hap_custom->data[i]);1002}1003dest->cbTypeSpecificParams = sizeof(FFCUSTOMFORCE);1004dest->lpvTypeSpecificParams = custom;10051006// Generics1007dest->dwDuration = hap_custom->length * 1000; // In microseconds.1008dest->dwTriggerButton = FFGetTriggerButton(hap_custom->button);1009dest->dwTriggerRepeatInterval = hap_custom->interval;1010dest->dwStartDelay = hap_custom->delay * 1000; // In microseconds.10111012// Direction.1013if (!SDL_SYS_SetDirection(dest, &hap_custom->direction, dest->cAxes)) {1014return false;1015}10161017// Envelope1018if ((hap_custom->attack_length == 0) && (hap_custom->fade_length == 0)) {1019SDL_free(envelope);1020dest->lpEnvelope = NULL;1021} else {1022envelope->dwAttackLevel = CCONVERT(hap_custom->attack_level);1023envelope->dwAttackTime = hap_custom->attack_length * 1000;1024envelope->dwFadeLevel = CCONVERT(hap_custom->fade_level);1025envelope->dwFadeTime = hap_custom->fade_length * 1000;1026}10271028break;10291030default:1031return SDL_SetError("Haptic: Unknown effect type.");1032}10331034return true;1035}10361037/*1038* Frees an FFEFFECT allocated by SDL_SYS_ToFFEFFECT.1039*/1040static void SDL_SYS_HapticFreeFFEFFECT(FFEFFECT *effect, int type)1041{1042FFCUSTOMFORCE *custom;10431044SDL_free(effect->lpEnvelope);1045effect->lpEnvelope = NULL;1046SDL_free(effect->rgdwAxes);1047effect->rgdwAxes = NULL;1048if (effect->lpvTypeSpecificParams) {1049if (type == SDL_HAPTIC_CUSTOM) { // Must free the custom data.1050custom = (FFCUSTOMFORCE *)effect->lpvTypeSpecificParams;1051SDL_free(custom->rglForceData);1052custom->rglForceData = NULL;1053}1054SDL_free(effect->lpvTypeSpecificParams);1055effect->lpvTypeSpecificParams = NULL;1056}1057SDL_free(effect->rglDirection);1058effect->rglDirection = NULL;1059}10601061/*1062* Gets the effect type from the generic SDL haptic effect wrapper.1063*/1064CFUUIDRef1065SDL_SYS_HapticEffectType(Uint16 type)1066{1067switch (type) {1068case SDL_HAPTIC_CONSTANT:1069return kFFEffectType_ConstantForce_ID;10701071case SDL_HAPTIC_RAMP:1072return kFFEffectType_RampForce_ID;10731074case SDL_HAPTIC_SQUARE:1075return kFFEffectType_Square_ID;10761077case SDL_HAPTIC_SINE:1078return kFFEffectType_Sine_ID;10791080case SDL_HAPTIC_TRIANGLE:1081return kFFEffectType_Triangle_ID;10821083case SDL_HAPTIC_SAWTOOTHUP:1084return kFFEffectType_SawtoothUp_ID;10851086case SDL_HAPTIC_SAWTOOTHDOWN:1087return kFFEffectType_SawtoothDown_ID;10881089case SDL_HAPTIC_SPRING:1090return kFFEffectType_Spring_ID;10911092case SDL_HAPTIC_DAMPER:1093return kFFEffectType_Damper_ID;10941095case SDL_HAPTIC_INERTIA:1096return kFFEffectType_Inertia_ID;10971098case SDL_HAPTIC_FRICTION:1099return kFFEffectType_Friction_ID;11001101case SDL_HAPTIC_CUSTOM:1102return kFFEffectType_CustomForce_ID;11031104default:1105SDL_SetError("Haptic: Unknown effect type.");1106return NULL;1107}1108}11091110/*1111* Creates a new haptic effect.1112*/1113bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect,1114const SDL_HapticEffect *base)1115{1116HRESULT ret;1117CFUUIDRef type;11181119// Alloc the effect.1120effect->hweffect = (struct haptic_hweffect *)1121SDL_calloc(1, sizeof(struct haptic_hweffect));1122if (!effect->hweffect) {1123goto err_hweffect;1124}11251126// Get the type.1127type = SDL_SYS_HapticEffectType(base->type);1128if (!type) {1129goto err_hweffect;1130}11311132// Get the effect.1133if (!SDL_SYS_ToFFEFFECT(haptic, &effect->hweffect->effect, base)) {1134goto err_effectdone;1135}11361137// Create the actual effect.1138ret = FFDeviceCreateEffect(haptic->hwdata->device, type,1139&effect->hweffect->effect,1140&effect->hweffect->ref);1141if (ret != FF_OK) {1142SDL_SetError("Haptic: Unable to create effect: %s.", FFStrError(ret));1143goto err_effectdone;1144}11451146return true;11471148err_effectdone:1149SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, base->type);1150err_hweffect:1151SDL_free(effect->hweffect);1152effect->hweffect = NULL;1153return false;1154}11551156/*1157* Updates an effect.1158*/1159bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,1160struct haptic_effect *effect,1161const SDL_HapticEffect *data)1162{1163HRESULT ret;1164FFEffectParameterFlag flags;1165FFEFFECT temp;11661167// Get the effect.1168SDL_memset(&temp, 0, sizeof(FFEFFECT));1169if (!SDL_SYS_ToFFEFFECT(haptic, &temp, data)) {1170goto err_update;1171}11721173/* Set the flags. Might be worthwhile to diff temp with loaded effect and1174* only change those parameters. */1175flags = FFEP_DIRECTION |1176FFEP_DURATION |1177FFEP_ENVELOPE |1178FFEP_STARTDELAY |1179FFEP_TRIGGERBUTTON |1180FFEP_TRIGGERREPEATINTERVAL | FFEP_TYPESPECIFICPARAMS;11811182// Create the actual effect.1183ret = FFEffectSetParameters(effect->hweffect->ref, &temp, flags);1184if (ret != FF_OK) {1185SDL_SetError("Haptic: Unable to update effect: %s.", FFStrError(ret));1186goto err_update;1187}11881189// Copy it over.1190SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect, data->type);1191SDL_memcpy(&effect->hweffect->effect, &temp, sizeof(FFEFFECT));11921193return true;11941195err_update:1196SDL_SYS_HapticFreeFFEFFECT(&temp, data->type);1197return false;1198}11991200/*1201* Runs an effect.1202*/1203bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect,1204Uint32 iterations)1205{1206HRESULT ret;1207Uint32 iter;12081209// Check if it's infinite.1210if (iterations == SDL_HAPTIC_INFINITY) {1211iter = FF_INFINITE;1212} else {1213iter = iterations;1214}12151216// Run the effect.1217ret = FFEffectStart(effect->hweffect->ref, iter, 0);1218if (ret != FF_OK) {1219return SDL_SetError("Haptic: Unable to run the effect: %s.",1220FFStrError(ret));1221}12221223return true;1224}12251226/*1227* Stops an effect.1228*/1229bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1230{1231HRESULT ret;12321233ret = FFEffectStop(effect->hweffect->ref);1234if (ret != FF_OK) {1235return SDL_SetError("Haptic: Unable to stop the effect: %s.",1236FFStrError(ret));1237}12381239return true;1240}12411242/*1243* Frees the effect.1244*/1245void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)1246{1247HRESULT ret;12481249ret = FFDeviceReleaseEffect(haptic->hwdata->device, effect->hweffect->ref);1250if (ret != FF_OK) {1251SDL_SetError("Haptic: Error removing the effect from the device: %s.",1252FFStrError(ret));1253}1254SDL_SYS_HapticFreeFFEFFECT(&effect->hweffect->effect,1255effect->effect.type);1256SDL_free(effect->hweffect);1257effect->hweffect = NULL;1258}12591260/*1261* Gets the status of a haptic effect.1262*/1263int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic,1264struct haptic_effect *effect)1265{1266HRESULT ret;1267FFEffectStatusFlag status;12681269ret = FFEffectGetEffectStatus(effect->hweffect->ref, &status);1270if (ret != FF_OK) {1271SDL_SetError("Haptic: Unable to get effect status: %s.", FFStrError(ret));1272return -1;1273}12741275if (status == 0) {1276return 0;1277}1278return 1; // Assume it's playing or emulated.1279}12801281/*1282* Sets the gain.1283*/1284bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)1285{1286HRESULT ret;1287Uint32 val;12881289val = gain * 100; // macOS uses 0 to 10,0001290ret = FFDeviceSetForceFeedbackProperty(haptic->hwdata->device,1291FFPROP_FFGAIN, &val);1292if (ret != FF_OK) {1293return SDL_SetError("Haptic: Error setting gain: %s.", FFStrError(ret));1294}12951296return true;1297}12981299/*1300* Sets the autocentering.1301*/1302bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)1303{1304HRESULT ret;1305Uint32 val;13061307// macOS only has 0 (off) and 1 (on)1308if (autocenter == 0) {1309val = 0;1310} else {1311val = 1;1312}13131314ret = FFDeviceSetForceFeedbackProperty(haptic->hwdata->device,1315FFPROP_AUTOCENTER, &val);1316if (ret != FF_OK) {1317return SDL_SetError("Haptic: Error setting autocenter: %s.",1318FFStrError(ret));1319}13201321return true;1322}13231324/*1325* Pauses the device.1326*/1327bool SDL_SYS_HapticPause(SDL_Haptic *haptic)1328{1329HRESULT ret;13301331ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,1332FFSFFC_PAUSE);1333if (ret != FF_OK) {1334return SDL_SetError("Haptic: Error pausing device: %s.", FFStrError(ret));1335}13361337return true;1338}13391340/*1341* Unpauses the device.1342*/1343bool SDL_SYS_HapticResume(SDL_Haptic *haptic)1344{1345HRESULT ret;13461347ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,1348FFSFFC_CONTINUE);1349if (ret != FF_OK) {1350return SDL_SetError("Haptic: Error resuming device: %s.", FFStrError(ret));1351}13521353return true;1354}13551356/*1357* Stops all currently playing effects.1358*/1359bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)1360{1361HRESULT ret;13621363ret = FFDeviceSendForceFeedbackCommand(haptic->hwdata->device,1364FFSFFC_STOPALL);1365if (ret != FF_OK) {1366return SDL_SetError("Haptic: Error stopping device: %s.", FFStrError(ret));1367}13681369return true;1370}13711372#endif // SDL_HAPTIC_IOKIT137313741375