2015-06-21 15:33:46 +00:00
|
|
|
/*
|
|
|
|
Simple DirectMedia Layer
|
2016-01-02 18:10:34 +00:00
|
|
|
Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
|
2015-06-21 15:33:46 +00:00
|
|
|
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
|
|
warranty. In no event will the authors be held liable for any damages
|
|
|
|
arising from the use of this software.
|
|
|
|
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
|
|
including commercial applications, and to alter it and redistribute it
|
|
|
|
freely, subject to the following restrictions:
|
|
|
|
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
|
|
claim that you wrote the original software. If you use this software
|
|
|
|
in a product, an acknowledgment in the product documentation would be
|
|
|
|
appreciated but is not required.
|
|
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
|
|
misrepresented as being the original software.
|
|
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
|
|
*/
|
|
|
|
#include "../../SDL_internal.h"
|
|
|
|
|
|
|
|
#ifdef SDL_JOYSTICK_IOKIT
|
|
|
|
|
|
|
|
#include <IOKit/hid/IOHIDLib.h>
|
|
|
|
|
|
|
|
/* For force feedback testing. */
|
|
|
|
#include <ForceFeedback/ForceFeedback.h>
|
|
|
|
#include <ForceFeedback/ForceFeedbackConstants.h>
|
|
|
|
|
|
|
|
#include "SDL_joystick.h"
|
|
|
|
#include "../SDL_sysjoystick.h"
|
|
|
|
#include "../SDL_joystick_c.h"
|
|
|
|
#include "SDL_sysjoystick_c.h"
|
|
|
|
#include "SDL_events.h"
|
|
|
|
#include "../../haptic/darwin/SDL_syshaptic_c.h" /* For haptic hot plugging */
|
|
|
|
|
|
|
|
#define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
|
|
|
|
|
|
|
|
/* The base object of the HID Manager API */
|
|
|
|
static IOHIDManagerRef hidman = NULL;
|
|
|
|
|
|
|
|
/* Linked list of all available devices */
|
|
|
|
static recDevice *gpDeviceList = NULL;
|
|
|
|
|
|
|
|
/* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
|
|
|
|
static int s_joystick_instance_id = -1;
|
|
|
|
|
|
|
|
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) {
|
|
|
|
IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
|
|
|
|
removeDevice->deviceRef = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* save next device prior to disposing of this device */
|
|
|
|
pDeviceNext = removeDevice->pNext;
|
|
|
|
|
|
|
|
if ( gpDeviceList == removeDevice ) {
|
|
|
|
gpDeviceList = pDeviceNext;
|
|
|
|
} else {
|
|
|
|
recDevice *device = gpDeviceList;
|
|
|
|
while (device->pNext != removeDevice) {
|
|
|
|
device = device->pNext;
|
|
|
|
}
|
|
|
|
device->pNext = pDeviceNext;
|
|
|
|
}
|
|
|
|
removeDevice->pNext = NULL;
|
|
|
|
|
|
|
|
/* free element lists */
|
|
|
|
FreeElementList(removeDevice->firstAxis);
|
|
|
|
FreeElementList(removeDevice->firstButton);
|
|
|
|
FreeElementList(removeDevice->firstHat);
|
|
|
|
|
|
|
|
SDL_free(removeDevice);
|
|
|
|
}
|
|
|
|
return pDeviceNext;
|
|
|
|
}
|
|
|
|
|
|
|
|
static SInt32
|
|
|
|
GetHIDElementState(recDevice *pDevice, recElement *pElement)
|
|
|
|
{
|
|
|
|
SInt32 value = 0;
|
|
|
|
|
|
|
|
if (pDevice && pElement) {
|
|
|
|
IOHIDValueRef valueRef;
|
|
|
|
if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
|
|
|
|
value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
|
|
|
|
|
|
|
|
/* record min and max for auto calibration */
|
|
|
|
if (value < pElement->minReport) {
|
|
|
|
pElement->minReport = value;
|
|
|
|
}
|
|
|
|
if (value > pElement->maxReport) {
|
|
|
|
pElement->maxReport = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
static SInt32
|
|
|
|
GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max)
|
|
|
|
{
|
|
|
|
const float deviceScale = max - min;
|
|
|
|
const float readScale = pElement->maxReport - pElement->minReport;
|
|
|
|
const SInt32 value = GetHIDElementState(pDevice, pElement);
|
|
|
|
if (readScale == 0) {
|
|
|
|
return value; /* no scaling at all */
|
|
|
|
}
|
|
|
|
return ((value - pElement->minReport) * deviceScale / readScale) + min;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
|
|
|
|
{
|
|
|
|
recDevice *device = (recDevice *) ctx;
|
|
|
|
device->removed = SDL_TRUE;
|
|
|
|
device->deviceRef = NULL; // deviceRef was invalidated due to the remove
|
|
|
|
#if SDL_HAPTIC_IOKIT
|
|
|
|
MacHaptic_MaybeRemoveDevice(device->ffservice);
|
|
|
|
#endif
|
|
|
|
|
2016-08-26 19:18:08 +00:00
|
|
|
SDL_PrivateJoystickRemoved(device->instance_id);
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void AddHIDElement(const void *value, void *parameter);
|
|
|
|
|
|
|
|
/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* See if we care about this HID element, and if so, note it in our recDevice. */
|
|
|
|
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;
|
|
|
|
|
|
|
|
/* look at types of interest */
|
|
|
|
switch (IOHIDElementGetType(refElement)) {
|
|
|
|
case kIOHIDElementTypeInput_Misc:
|
|
|
|
case kIOHIDElementTypeInput_Button:
|
|
|
|
case kIOHIDElementTypeInput_Axis: {
|
|
|
|
switch (usagePage) { /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
|
|
|
|
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;
|
2015-11-13 02:44:32 +00:00
|
|
|
case kHIDUsage_GD_DPadUp:
|
|
|
|
case kHIDUsage_GD_DPadDown:
|
|
|
|
case kHIDUsage_GD_DPadRight:
|
|
|
|
case kHIDUsage_GD_DPadLeft:
|
2015-12-28 19:44:09 +00:00
|
|
|
case kHIDUsage_GD_Start:
|
|
|
|
case kHIDUsage_GD_Select:
|
2016-08-08 19:17:53 +00:00
|
|
|
case kHIDUsage_GD_SystemMainMenu:
|
2015-11-13 02:44:32 +00:00
|
|
|
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
|
|
|
|
element = (recElement *) SDL_calloc(1, sizeof (recElement));
|
|
|
|
if (element) {
|
|
|
|
pDevice->buttons++;
|
|
|
|
headElement = &(pDevice->firstButton);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case kHIDPage_Simulation:
|
|
|
|
switch (usage) {
|
|
|
|
case kHIDUsage_Sim_Rudder:
|
|
|
|
case kHIDUsage_Sim_Throttle:
|
|
|
|
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:
|
2015-11-13 02:44:32 +00:00
|
|
|
case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
|
2015-06-21 15:33:46 +00:00
|
|
|
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) { /* add to list */
|
|
|
|
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)
|
|
|
|
{
|
2016-11-11 01:19:34 +00:00
|
|
|
const Uint16 BUS_USB = 0x03;
|
|
|
|
const Uint16 BUS_BLUETOOTH = 0x05;
|
|
|
|
Sint32 vendor = 0;
|
|
|
|
Sint32 product = 0;
|
|
|
|
Sint32 version = 0;
|
2015-06-21 15:33:46 +00:00
|
|
|
CFTypeRef refCF = NULL;
|
|
|
|
CFArrayRef array = NULL;
|
2016-11-11 01:19:34 +00:00
|
|
|
Uint16 *guid16 = (Uint16 *)pDevice->guid->data;
|
2015-06-21 15:33:46 +00:00
|
|
|
|
|
|
|
/* get usage page and usage */
|
|
|
|
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
|
|
|
|
if (refCF) {
|
|
|
|
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
|
|
|
|
}
|
|
|
|
if (pDevice->usagePage != kHIDPage_GenericDesktop) {
|
|
|
|
return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
|
|
|
|
}
|
|
|
|
|
|
|
|
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; /* Filter device list to non-keyboard/mouse stuff */
|
|
|
|
}
|
|
|
|
|
|
|
|
pDevice->deviceRef = hidDevice;
|
|
|
|
|
|
|
|
/* get device name */
|
|
|
|
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
|
|
|
|
if (!refCF) {
|
|
|
|
/* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
|
|
|
|
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
|
|
|
|
}
|
|
|
|
if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
|
|
|
|
SDL_strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
|
|
|
|
}
|
|
|
|
|
|
|
|
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
|
|
|
|
if (refCF) {
|
2016-11-11 01:19:34 +00:00
|
|
|
CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
|
|
|
|
if (refCF) {
|
2016-11-11 01:19:34 +00:00
|
|
|
CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2016-11-11 01:19:34 +00:00
|
|
|
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
|
|
|
|
if (refCF) {
|
|
|
|
CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_memset(pDevice->guid->data, 0, sizeof(pDevice->guid->data));
|
|
|
|
|
|
|
|
if (vendor && product) {
|
|
|
|
*guid16++ = SDL_SwapLE16(BUS_USB);
|
|
|
|
*guid16++ = 0;
|
|
|
|
*guid16++ = SDL_SwapLE16((Uint16)vendor);
|
|
|
|
*guid16++ = 0;
|
|
|
|
*guid16++ = SDL_SwapLE16((Uint16)product);
|
|
|
|
*guid16++ = 0;
|
|
|
|
*guid16++ = SDL_SwapLE16((Uint16)version);
|
|
|
|
*guid16++ = 0;
|
|
|
|
} else {
|
|
|
|
*guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
|
2015-06-21 15:33:46 +00:00
|
|
|
*guid16++ = 0;
|
|
|
|
SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
|
|
|
|
if (array) {
|
|
|
|
AddHIDElements(array, pDevice);
|
|
|
|
CFRelease(array);
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDL_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static SDL_bool
|
|
|
|
JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
|
|
|
|
{
|
|
|
|
recDevice *i;
|
|
|
|
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;
|
2016-05-21 03:20:52 +00:00
|
|
|
io_service_t ioservice;
|
2015-06-21 15:33:46 +00:00
|
|
|
|
|
|
|
if (res != kIOReturnSuccess) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
|
|
|
|
return; /* IOKit sent us a duplicate. */
|
|
|
|
}
|
|
|
|
|
|
|
|
device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
|
|
|
|
|
|
|
|
if (!device) {
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
|
|
|
|
SDL_free(device);
|
|
|
|
return; /* not a device we care about, probably. */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get notified when this device is disconnected. */
|
|
|
|
IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
|
|
|
|
IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
|
|
|
|
|
|
|
|
/* Allocate an instance ID for this device */
|
|
|
|
device->instance_id = ++s_joystick_instance_id;
|
|
|
|
|
|
|
|
/* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
|
2016-05-21 03:20:52 +00:00
|
|
|
ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
|
2015-06-21 15:33:46 +00:00
|
|
|
#if SDL_HAPTIC_IOKIT
|
2016-05-21 03:20:52 +00:00
|
|
|
if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
|
|
|
|
device->ffservice = ioservice;
|
|
|
|
MacHaptic_MaybeAddDevice(ioservice);
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Add device to the end of the list */
|
|
|
|
if ( !gpDeviceList ) {
|
|
|
|
gpDeviceList = device;
|
|
|
|
} else {
|
|
|
|
recDevice *curdevice;
|
|
|
|
|
|
|
|
curdevice = gpDeviceList;
|
|
|
|
while ( curdevice->pNext ) {
|
|
|
|
++device_index;
|
|
|
|
curdevice = curdevice->pNext;
|
|
|
|
}
|
|
|
|
curdevice->pNext = device;
|
2015-09-13 15:29:45 +00:00
|
|
|
++device_index; /* bump by one since we counted by pNext. */
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2016-08-26 19:18:08 +00:00
|
|
|
SDL_PrivateJoystickAdded(device_index);
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
/* no-op. Callback fires once per existing device. */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
|
|
|
|
|
|
|
|
return SDL_TRUE; /* good to go. */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Function to scan the system for joysticks.
|
|
|
|
* Joystick 0 should be the system default joystick.
|
|
|
|
* This function should return the number of available joysticks, or -1
|
|
|
|
* on an unrecoverable fatal error.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
SDL_SYS_JoystickInit(void)
|
|
|
|
{
|
|
|
|
if (gpDeviceList) {
|
|
|
|
return SDL_SetError("Joystick: Device list already inited.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!CreateHIDManager()) {
|
|
|
|
return SDL_SetError("Joystick: Couldn't initialize HID Manager");
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDL_SYS_NumJoysticks();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to return the number of joystick devices plugged in right now */
|
|
|
|
int
|
|
|
|
SDL_SYS_NumJoysticks()
|
|
|
|
{
|
|
|
|
recDevice *device = gpDeviceList;
|
|
|
|
int nJoySticks = 0;
|
|
|
|
|
|
|
|
while (device) {
|
|
|
|
if (!device->removed) {
|
|
|
|
nJoySticks++;
|
|
|
|
}
|
|
|
|
device = device->pNext;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nJoySticks;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to cause any queued joystick insertions to be processed
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
SDL_SYS_JoystickDetect()
|
|
|
|
{
|
|
|
|
recDevice *device = gpDeviceList;
|
|
|
|
while (device) {
|
|
|
|
if (device->removed) {
|
|
|
|
device = FreeDevice(device);
|
|
|
|
} else {
|
|
|
|
device = device->pNext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run this after the checks above so we don't set device->removed and delete the device before
|
|
|
|
// SDL_SYS_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device
|
|
|
|
while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
|
|
|
|
/* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to get the device-dependent name of a joystick */
|
|
|
|
const char *
|
|
|
|
SDL_SYS_JoystickNameForDeviceIndex(int device_index)
|
|
|
|
{
|
|
|
|
recDevice *device = GetDeviceForIndex(device_index);
|
|
|
|
return device ? device->product : "UNKNOWN";
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to return the instance id of the joystick at device_index
|
|
|
|
*/
|
|
|
|
SDL_JoystickID
|
|
|
|
SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
|
|
|
|
{
|
|
|
|
recDevice *device = GetDeviceForIndex(device_index);
|
|
|
|
return device ? device->instance_id : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to open a joystick for use.
|
|
|
|
* The joystick to open is specified by the device index.
|
|
|
|
* This should fill the nbuttons and naxes fields of the joystick structure.
|
|
|
|
* It returns 0, or -1 if there is an error.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
|
|
|
|
{
|
|
|
|
recDevice *device = GetDeviceForIndex(device_index);
|
|
|
|
|
|
|
|
joystick->instance_id = device->instance_id;
|
|
|
|
joystick->hwdata = device;
|
|
|
|
joystick->name = device->product;
|
|
|
|
|
|
|
|
joystick->naxes = device->axes;
|
|
|
|
joystick->nhats = device->hats;
|
|
|
|
joystick->nballs = 0;
|
|
|
|
joystick->nbuttons = device->buttons;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to query if the joystick is currently attached
|
|
|
|
* It returns SDL_TRUE if attached, SDL_FALSE otherwise.
|
|
|
|
*/
|
|
|
|
SDL_bool
|
|
|
|
SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
|
|
|
|
{
|
|
|
|
return joystick->hwdata != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to update the state of a joystick - called as a device poll.
|
|
|
|
* This function shouldn't update the joystick structure directly,
|
|
|
|
* but instead should call SDL_PrivateJoystick*() to deliver events
|
|
|
|
* and update joystick device state.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
|
|
|
|
{
|
|
|
|
recDevice *device = joystick->hwdata;
|
|
|
|
recElement *element;
|
|
|
|
SInt32 value, range;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!device) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (device->removed) { /* device was unplugged; ignore it. */
|
|
|
|
if (joystick->hwdata) {
|
|
|
|
joystick->force_recentering = SDL_TRUE;
|
|
|
|
joystick->hwdata = NULL;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
element = device->firstAxis;
|
|
|
|
i = 0;
|
|
|
|
while (element) {
|
|
|
|
value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
|
|
|
|
if (value != joystick->axes[i]) {
|
|
|
|
SDL_PrivateJoystickAxis(joystick, i, value);
|
|
|
|
}
|
|
|
|
element = element->pNext;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
element = device->firstButton;
|
|
|
|
i = 0;
|
|
|
|
while (element) {
|
|
|
|
value = GetHIDElementState(device, element);
|
|
|
|
if (value > 1) { /* handle pressure-sensitive buttons */
|
|
|
|
value = 1;
|
|
|
|
}
|
|
|
|
if (value != joystick->buttons[i]) {
|
|
|
|
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);
|
|
|
|
value = GetHIDElementState(device, element) - element->min;
|
|
|
|
if (range == 4) { /* 4 position hatswitch - scale up value */
|
|
|
|
value *= 2;
|
|
|
|
} else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */
|
|
|
|
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:
|
|
|
|
/* Every other value is mapped to center. We do that because some
|
|
|
|
* joysticks use 8 and some 15 for this value, and apparently
|
|
|
|
* there are even more variants out there - so we try to be generous.
|
|
|
|
*/
|
|
|
|
pos = SDL_HAT_CENTERED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pos != joystick->hats[i]) {
|
|
|
|
SDL_PrivateJoystickHat(joystick, i, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
element = element->pNext;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to close a joystick after use */
|
|
|
|
void
|
|
|
|
SDL_SYS_JoystickClose(SDL_Joystick * joystick)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Function to perform any system-specific joystick related cleanup */
|
|
|
|
void
|
|
|
|
SDL_SYS_JoystickQuit(void)
|
|
|
|
{
|
|
|
|
while (FreeDevice(gpDeviceList)) {
|
|
|
|
/* spin */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hidman) {
|
|
|
|
IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
|
|
|
|
IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
|
|
|
|
CFRelease(hidman);
|
|
|
|
hidman = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
|
|
|
|
{
|
|
|
|
recDevice *device = GetDeviceForIndex(device_index);
|
|
|
|
SDL_JoystickGUID guid;
|
|
|
|
if (device) {
|
|
|
|
guid = device->guid;
|
|
|
|
} else {
|
|
|
|
SDL_zero(guid);
|
|
|
|
}
|
|
|
|
return guid;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick *joystick)
|
|
|
|
{
|
|
|
|
return joystick->hwdata->guid;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* SDL_JOYSTICK_IOKIT */
|
|
|
|
|
|
|
|
/* vi: set ts=4 sw=4 expandtab: */
|