#include "../../SDL_internal.h"
#ifdef SDL_JOYSTICK_IOKIT
#include "SDL_events.h"
#include "SDL_joystick.h"
#include "../SDL_sysjoystick.h"
#include "../SDL_joystick_c.h"
#include "SDL_iokitjoystick_c.h"
#include "../hidapi/SDL_hidapijoystick_c.h"
#include "../../haptic/darwin/SDL_syshaptic_c.h"
#define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF)
static IOHIDManagerRef hidman = NULL;
static recDevice *gpDeviceList = NULL;
void FreeRumbleEffectData(FFEFFECT *effect)
{
if (!effect) {
return;
}
SDL_free(effect->rgdwAxes);
SDL_free(effect->rglDirection);
SDL_free(effect->lpvTypeSpecificParams);
SDL_free(effect);
}
FFEFFECT *CreateRumbleEffectData(Sint16 magnitude)
{
FFEFFECT *effect;
FFPERIODIC *periodic;
effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect));
if (!effect) {
return NULL;
}
effect->dwSize = sizeof(*effect);
effect->dwGain = 10000;
effect->dwFlags = FFEFF_OBJECTOFFSETS;
effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000;
effect->dwTriggerButton = FFEB_NOTRIGGER;
effect->cAxes = 2;
effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
if (!effect->rgdwAxes) {
FreeRumbleEffectData(effect);
return NULL;
}
effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
if (!effect->rglDirection) {
FreeRumbleEffectData(effect);
return NULL;
}
effect->dwFlags |= FFEFF_CARTESIAN;
periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic));
if (!periodic) {
FreeRumbleEffectData(effect);
return NULL;
}
periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
periodic->dwPeriod = 1000000;
effect->cbTypeSpecificParams = sizeof(*periodic);
effect->lpvTypeSpecificParams = periodic;
return effect;
}
static recDevice *GetDeviceForIndex(int device_index)
{
recDevice *device = gpDeviceList;
while (device) {
if (!device->removed) {
if (device_index == 0)
break;
--device_index;
}
device = device->pNext;
}
return device;
}
static void
FreeElementList(recElement *pElement)
{
while (pElement) {
recElement *pElementNext = pElement->pNext;
SDL_free(pElement);
pElement = pElementNext;
}
}
static recDevice *
FreeDevice(recDevice *removeDevice)
{
recDevice *pDeviceNext = NULL;
if (removeDevice) {
if (removeDevice->deviceRef) {
if (removeDevice->runLoopAttached) {
IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
}
CFRelease(removeDevice->deviceRef);
removeDevice->deviceRef = NULL;
}
SDL_LockJoysticks();
if (removeDevice->joystick) {
removeDevice->joystick->hwdata = NULL;
}
SDL_UnlockJoysticks();
pDeviceNext = removeDevice->pNext;
if (gpDeviceList == removeDevice) {
gpDeviceList = pDeviceNext;
} else if (gpDeviceList) {
recDevice *device;
for (device = gpDeviceList; device; device = device->pNext) {
if (device->pNext == removeDevice) {
device->pNext = pDeviceNext;
break;
}
}
}
removeDevice->pNext = NULL;
FreeElementList(removeDevice->firstAxis);
FreeElementList(removeDevice->firstButton);
FreeElementList(removeDevice->firstHat);
SDL_free(removeDevice);
}
return pDeviceNext;
}
static SDL_bool
GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue)
{
SInt32 value = 0;
int returnValue = SDL_FALSE;
if (pDevice && pDevice->deviceRef && pElement) {
IOHIDValueRef valueRef;
if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
if (value < pElement->minReport) {
pElement->minReport = value;
}
if (value > pElement->maxReport) {
pElement->maxReport = value;
}
*pValue = value;
returnValue = SDL_TRUE;
}
}
return returnValue;
}
static SDL_bool
GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max, SInt32 *pValue)
{
const float deviceScale = max - min;
const float readScale = pElement->maxReport - pElement->minReport;
int returnValue = SDL_FALSE;
if (GetHIDElementState(pDevice, pElement, pValue))
{
if (readScale == 0) {
returnValue = SDL_TRUE;
}
else
{
*pValue = ((*pValue - pElement->minReport) * deviceScale / readScale) + min;
returnValue = SDL_TRUE;
}
}
return returnValue;
}
static void
JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
{
recDevice *device = (recDevice *) ctx;
device->removed = SDL_TRUE;
if (device->deviceRef) {
CFRelease(device->deviceRef);
device->deviceRef = NULL;
}
if (device->ffeffect_ref) {
FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref);
device->ffeffect_ref = NULL;
}
if (device->ffeffect) {
FreeRumbleEffectData(device->ffeffect);
device->ffeffect = NULL;
}
if (device->ffdevice) {
FFReleaseDevice(device->ffdevice);
device->ffdevice = NULL;
device->ff_initialized = SDL_FALSE;
}
#if SDL_HAPTIC_IOKIT
MacHaptic_MaybeRemoveDevice(device->ffservice);
#endif
SDL_PrivateJoystickRemoved(device->instance_id);
}
static void AddHIDElement(const void *value, void *parameter);
static void
AddHIDElements(CFArrayRef array, recDevice *pDevice)
{
const CFRange range = { 0, CFArrayGetCount(array) };
CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
}
static SDL_bool
ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
while (listitem) {
if (listitem->cookie == cookie) {
return SDL_TRUE;
}
listitem = listitem->pNext;
}
return SDL_FALSE;
}
static void
AddHIDElement(const void *value, void *parameter)
{
recDevice *pDevice = (recDevice *) parameter;
IOHIDElementRef refElement = (IOHIDElementRef) value;
const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
const uint32_t usage = IOHIDElementGetUsage(refElement);
recElement *element = NULL;
recElement **headElement = NULL;
switch (IOHIDElementGetType(refElement)) {
case kIOHIDElementTypeInput_Misc:
case kIOHIDElementTypeInput_Button:
case kIOHIDElementTypeInput_Axis: {
switch (usagePage) {
case kHIDPage_GenericDesktop:
switch (usage) {
case kHIDUsage_GD_X:
case kHIDUsage_GD_Y:
case kHIDUsage_GD_Z:
case kHIDUsage_GD_Rx:
case kHIDUsage_GD_Ry:
case kHIDUsage_GD_Rz:
case kHIDUsage_GD_Slider:
case kHIDUsage_GD_Dial:
case kHIDUsage_GD_Wheel:
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
element = (recElement *) SDL_calloc(1, sizeof (recElement));
if (element) {
pDevice->axes++;
headElement = &(pDevice->firstAxis);
}
}
break;
case kHIDUsage_GD_Hatswitch:
if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
element = (recElement *) SDL_calloc(1, sizeof (recElement));
if (element) {
pDevice->hats++;
headElement = &(pDevice->firstHat);
}
}
break;
case kHIDUsage_GD_DPadUp:
case kHIDUsage_GD_DPadDown:
case kHIDUsage_GD_DPadRight:
case kHIDUsage_GD_DPadLeft:
case kHIDUsage_GD_Start:
case kHIDUsage_GD_Select:
case kHIDUsage_GD_SystemMainMenu:
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
element = (recElement *) SDL_calloc(1, sizeof (recElement));
if (element) {
pDevice->buttons++;
headElement = &(pDevice->firstButton);
}
}
break;
}
break;
case kHIDPage_Simulation:
switch (usage) {
case kHIDUsage_Sim_Rudder:
case kHIDUsage_Sim_Throttle:
case kHIDUsage_Sim_Accelerator:
case kHIDUsage_Sim_Brake:
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
element = (recElement *) SDL_calloc(1, sizeof (recElement));
if (element) {
pDevice->axes++;
headElement = &(pDevice->firstAxis);
}
}
break;
default:
break;
}
break;
case kHIDPage_Button:
case kHIDPage_Consumer:
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
element = (recElement *) SDL_calloc(1, sizeof (recElement));
if (element) {
pDevice->buttons++;
headElement = &(pDevice->firstButton);
}
}
break;
default:
break;
}
}
break;
case kIOHIDElementTypeCollection: {
CFArrayRef array = IOHIDElementGetChildren(refElement);
if (array) {
AddHIDElements(array, pDevice);
}
}
break;
default:
break;
}
if (element && headElement) {
recElement *elementPrevious = NULL;
recElement *elementCurrent = *headElement;
while (elementCurrent && usage >= elementCurrent->usage) {
elementPrevious = elementCurrent;
elementCurrent = elementCurrent->pNext;
}
if (elementPrevious) {
elementPrevious->pNext = element;
} else {
*headElement = element;
}
element->elementRef = refElement;
element->usagePage = usagePage;
element->usage = usage;
element->pNext = elementCurrent;
element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
element->cookie = IOHIDElementGetCookie(refElement);
pDevice->elements++;
}
}
}
static SDL_bool
GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
{
Sint32 vendor = 0;
Sint32 product = 0;
Sint32 version = 0;
char *name;
char manufacturer_string[256];
char product_string[256];
CFTypeRef refCF = NULL;
CFArrayRef array = NULL;
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
}
if (pDevice->usagePage != kHIDPage_GenericDesktop) {
return SDL_FALSE;
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
}
if ((pDevice->usage != kHIDUsage_GD_Joystick &&
pDevice->usage != kHIDUsage_GD_GamePad &&
pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
return SDL_FALSE;
}
CFRetain(hidDevice);
pDevice->deviceRef = hidDevice;
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
if (refCF) {
CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) {
manufacturer_string[0] = '\0';
}
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
if ((!refCF) || (!CFStringGetCString(refCF, product_string, sizeof(product_string), kCFStringEncodingUTF8))) {
product_string[0] = '\0';
}
name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
if (name) {
SDL_strlcpy(pDevice->product, name, sizeof(pDevice->product));
SDL_free(name);
}
#ifdef SDL_JOYSTICK_HIDAPI
if (HIDAPI_IsDevicePresent(vendor, product, version, pDevice->product)) {
return SDL_FALSE;
}
#endif
pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, pDevice->product, 0, 0);
array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
if (array) {
AddHIDElements(array, pDevice);
CFRelease(array);
}
return SDL_TRUE;
}
static SDL_bool
JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
{
recDevice *i;
#if defined(SDL_JOYSTICK_MFI)
extern SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device);
if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) {
return SDL_TRUE;
}
#endif
for (i = gpDeviceList; i != NULL; i = i->pNext) {
if (i->deviceRef == ioHIDDeviceObject) {
return SDL_TRUE;
}
}
return SDL_FALSE;
}
static void
JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
{
recDevice *device;
int device_index = 0;
io_service_t ioservice;
if (res != kIOReturnSuccess) {
return;
}
if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
return;
}
device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
if (!device) {
SDL_OutOfMemory();
return;
}
if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
FreeDevice(device);
return;
}
if (SDL_ShouldIgnoreJoystick(device->product, device->guid)) {
FreeDevice(device);
return;
}
IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
device->runLoopAttached = SDL_TRUE;
device->instance_id = SDL_GetNextJoystickInstanceID();
ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
device->ffservice = ioservice;
#if SDL_HAPTIC_IOKIT
MacHaptic_MaybeAddDevice(ioservice);
#endif
}
if ( !gpDeviceList ) {
gpDeviceList = device;
} else {
recDevice *curdevice;
curdevice = gpDeviceList;
while ( curdevice->pNext ) {
++device_index;
curdevice = curdevice->pNext;
}
curdevice->pNext = device;
++device_index;
}
SDL_PrivateJoystickAdded(device->instance_id);
}
static SDL_bool
ConfigHIDManager(CFArrayRef matchingArray)
{
CFRunLoopRef runloop = CFRunLoopGetCurrent();
if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
return SDL_FALSE;
}
IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
}
return SDL_TRUE;
}
static CFDictionaryRef
CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
{
CFDictionaryRef retval = NULL;
CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
if (pageNumRef && usageNumRef) {
retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
if (pageNumRef) {
CFRelease(pageNumRef);
}
if (usageNumRef) {
CFRelease(usageNumRef);
}
if (!retval) {
*okay = 0;
}
return retval;
}
static SDL_bool
CreateHIDManager(void)
{
SDL_bool retval = SDL_FALSE;
int okay = 1;
const void *vals[] = {
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
};
const size_t numElements = SDL_arraysize(vals);
CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
size_t i;
for (i = 0; i < numElements; i++) {
if (vals[i]) {
CFRelease((CFTypeRef) vals[i]);
}
}
if (array) {
hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if (hidman != NULL) {
retval = ConfigHIDManager(array);
}
CFRelease(array);
}
return retval;
}
static int
DARWIN_JoystickInit(void)
{
if (gpDeviceList) {
return SDL_SetError("Joystick: Device list already inited.");
}
if (!CreateHIDManager()) {
return SDL_SetError("Joystick: Couldn't initialize HID Manager");
}
return 0;
}
static int
DARWIN_JoystickGetCount(void)
{
recDevice *device = gpDeviceList;
int nJoySticks = 0;
while (device) {
if (!device->removed) {
nJoySticks++;
}
device = device->pNext;
}
return nJoySticks;
}
static void
DARWIN_JoystickDetect(void)
{
recDevice *device = gpDeviceList;
while (device) {
if (device->removed) {
device = FreeDevice(device);
} else {
device = device->pNext;
}
}
while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
}
}
const char *
DARWIN_JoystickGetDeviceName(int device_index)
{
recDevice *device = GetDeviceForIndex(device_index);
return device ? device->product : "UNKNOWN";
}
const char *
DARWIN_JoystickGetDevicePath(int device_index)
{
return NULL;
}
static int
DARWIN_JoystickGetDevicePlayerIndex(int device_index)
{
return -1;
}
static void
DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
{
}
static SDL_JoystickGUID
DARWIN_JoystickGetDeviceGUID( int device_index )
{
recDevice *device = GetDeviceForIndex(device_index);
SDL_JoystickGUID guid;
if (device) {
guid = device->guid;
} else {
SDL_zero(guid);
}
return guid;
}
static SDL_JoystickID
DARWIN_JoystickGetDeviceInstanceID(int device_index)
{
recDevice *device = GetDeviceForIndex(device_index);
return device ? device->instance_id : 0;
}
static int
DARWIN_JoystickOpen(SDL_Joystick *joystick, int device_index)
{
recDevice *device = GetDeviceForIndex(device_index);
joystick->instance_id = device->instance_id;
joystick->hwdata = device;
device->joystick = joystick;
joystick->name = device->product;
joystick->naxes = device->axes;
joystick->nhats = device->hats;
joystick->nballs = 0;
joystick->nbuttons = device->buttons;
return 0;
}
static const char *
FFStrError(unsigned int err)
{
switch (err) {
case FFERR_DEVICEFULL:
return "device full";
case FFERR_DEVICEPAUSED:
return "device paused";
case FFERR_DEVICERELEASED:
return "device released";
case FFERR_EFFECTPLAYING:
return "effect playing";
case FFERR_EFFECTTYPEMISMATCH:
return "effect type mismatch";
case FFERR_EFFECTTYPENOTSUPPORTED:
return "effect type not supported";
case FFERR_GENERIC:
return "undetermined error";
case FFERR_HASEFFECTS:
return "device has effects";
case FFERR_INCOMPLETEEFFECT:
return "incomplete effect";
case FFERR_INTERNAL:
return "internal fault";
case FFERR_INVALIDDOWNLOADID:
return "invalid download id";
case FFERR_INVALIDPARAM:
return "invalid parameter";
case FFERR_MOREDATA:
return "more data";
case FFERR_NOINTERFACE:
return "interface not supported";
case FFERR_NOTDOWNLOADED:
return "effect is not downloaded";
case FFERR_NOTINITIALIZED:
return "object has not been initialized";
case FFERR_OUTOFMEMORY:
return "out of memory";
case FFERR_UNPLUGGED:
return "device is unplugged";
case FFERR_UNSUPPORTED:
return "function call unsupported";
case FFERR_UNSUPPORTEDAXIS:
return "axis unsupported";
default:
return "unknown error";
}
}
static int
DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude)
{
HRESULT result;
if (!device->ffdevice) {
result = FFCreateDevice(device->ffservice, &device->ffdevice);
if (result != FF_OK) {
return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));
}
}
result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);
if (result != FF_OK) {
return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));
}
result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);
if (result != FF_OK) {
return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));
}
device->ffeffect = CreateRumbleEffectData(magnitude);
if (!device->ffeffect) {
return SDL_OutOfMemory();
}
result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,
device->ffeffect, &device->ffeffect_ref);
if (result != FF_OK) {
return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));
}
return 0;
}
static int
DARWIN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
HRESULT result;
recDevice *device = joystick->hwdata;
Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
if (!device) {
return SDL_SetError("Rumble failed, device disconnected");
}
if (!device->ffservice) {
return SDL_Unsupported();
}
if (device->ff_initialized) {
FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
(FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
if (result != FF_OK) {
return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
}
} else {
if (DARWIN_JoystickInitRumble(device, magnitude) < 0) {
return -1;
}
device->ff_initialized = SDL_TRUE;
}
result = FFEffectStart(device->ffeffect_ref, 1, 0);
if (result != FF_OK) {
return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
}
return 0;
}
static int
DARWIN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32
DARWIN_JoystickGetCapabilities(SDL_Joystick *joystick)
{
recDevice *device = joystick->hwdata;
Uint32 result = 0;
if (!device) {
return 0;
}
if (device->ffservice) {
result |= SDL_JOYCAP_RUMBLE;
}
return result;
}
static int
DARWIN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int
DARWIN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static int
DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
{
return SDL_Unsupported();
}
static void
DARWIN_JoystickUpdate(SDL_Joystick *joystick)
{
recDevice *device = joystick->hwdata;
recElement *element;
SInt32 value, range;
int i, goodRead = SDL_FALSE;
if (!device) {
return;
}
if (device->removed) {
if (joystick->hwdata) {
joystick->hwdata = NULL;
}
return;
}
element = device->firstAxis;
i = 0;
while (element) {
goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);
if (goodRead) {
SDL_PrivateJoystickAxis(joystick, i, value);
}
element = element->pNext;
++i;
}
element = device->firstButton;
i = 0;
while (element) {
goodRead = GetHIDElementState(device, element, &value);
if (goodRead) {
if (value > 1) {
value = 1;
}
SDL_PrivateJoystickButton(joystick, i, value);
}
element = element->pNext;
++i;
}
element = device->firstHat;
i = 0;
while (element) {
Uint8 pos = 0;
range = (element->max - element->min + 1);
goodRead = GetHIDElementState(device, element, &value);
if (goodRead) {
value -= element->min;
if (range == 4) {
value *= 2;
} else if (range != 8) {
value = -1;
}
switch (value) {
case 0:
pos = SDL_HAT_UP;
break;
case 1:
pos = SDL_HAT_RIGHTUP;
break;
case 2:
pos = SDL_HAT_RIGHT;
break;
case 3:
pos = SDL_HAT_RIGHTDOWN;
break;
case 4:
pos = SDL_HAT_DOWN;
break;
case 5:
pos = SDL_HAT_LEFTDOWN;
break;
case 6:
pos = SDL_HAT_LEFT;
break;
case 7:
pos = SDL_HAT_LEFTUP;
break;
default:
pos = SDL_HAT_CENTERED;
break;
}
SDL_PrivateJoystickHat(joystick, i, pos);
}
element = element->pNext;
++i;
}
}
static void
DARWIN_JoystickClose(SDL_Joystick *joystick)
{
recDevice *device = joystick->hwdata;
if (device) {
device->joystick = NULL;
}
}
static void
DARWIN_JoystickQuit(void)
{
while (FreeDevice(gpDeviceList)) {
}
if (hidman) {
IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
CFRelease(hidman);
hidman = NULL;
}
}
static SDL_bool
DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
{
return SDL_FALSE;
}
SDL_JoystickDriver SDL_DARWIN_JoystickDriver =
{
DARWIN_JoystickInit,
DARWIN_JoystickGetCount,
DARWIN_JoystickDetect,
DARWIN_JoystickGetDeviceName,
DARWIN_JoystickGetDevicePath,
DARWIN_JoystickGetDevicePlayerIndex,
DARWIN_JoystickSetDevicePlayerIndex,
DARWIN_JoystickGetDeviceGUID,
DARWIN_JoystickGetDeviceInstanceID,
DARWIN_JoystickOpen,
DARWIN_JoystickRumble,
DARWIN_JoystickRumbleTriggers,
DARWIN_JoystickGetCapabilities,
DARWIN_JoystickSetLED,
DARWIN_JoystickSendEffect,
DARWIN_JoystickSetSensorsEnabled,
DARWIN_JoystickUpdate,
DARWIN_JoystickClose,
DARWIN_JoystickQuit,
DARWIN_JoystickGetGamepadMapping
};
#endif