Add support for joystick hot swapping on OS X.

This commit is contained in:
Aaron Jacobs 2015-06-20 11:58:26 -07:00 committed by Camilla Berglund
parent 19a28e2c9f
commit 1a96c294ee
2 changed files with 190 additions and 209 deletions

View File

@ -33,17 +33,17 @@
#include <IOKit/hid/IOHIDKeys.h> #include <IOKit/hid/IOHIDKeys.h>
#define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE \ #define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE \
_GLFWjoystickIOKit iokit_js[GLFW_JOYSTICK_LAST + 1] _GLFWjoystickIOKit iokit_js
// IOKit-specific per-joystick data // IOKit-specific per-joystick data
// //
typedef struct _GLFWjoystickIOKit typedef struct _GLFWjoydevice
{ {
int present; int present;
char name[256]; char name[256];
IOHIDDeviceInterface** interface; IOHIDDeviceRef deviceRef;
CFMutableArrayRef axisElements; CFMutableArrayRef axisElements;
CFMutableArrayRef buttonElements; CFMutableArrayRef buttonElements;
@ -51,7 +51,16 @@ typedef struct _GLFWjoystickIOKit
float* axes; float* axes;
unsigned char* buttons; unsigned char* buttons;
} _GLFWjoydevice;
// IOKit-specific joystick API data
//
typedef struct _GLFWjoystickIOKit
{
_GLFWjoydevice devices[GLFW_JOYSTICK_LAST + 1];
IOHIDManagerRef managerRef;
} _GLFWjoystickIOKit; } _GLFWjoystickIOKit;

View File

@ -42,7 +42,7 @@
//------------------------------------------------------------------------ //------------------------------------------------------------------------
typedef struct typedef struct
{ {
IOHIDElementCookie cookie; IOHIDElementRef elementRef;
long min; long min;
long max; long max;
@ -55,20 +55,17 @@ typedef struct
static void getElementsCFArrayHandler(const void* value, void* parameter); static void getElementsCFArrayHandler(const void* value, void* parameter);
// Adds an element to the specified joystick // Adds an element to the specified joystick
// //
static void addJoystickElement(_GLFWjoystickIOKit* joystick, CFTypeRef elementRef) static void addJoystickElement(_GLFWjoydevice* joystick, IOHIDElementRef elementRef)
{ {
long elementType, usagePage, usage; IOHIDElementType elementType;
long usagePage, usage;
CFMutableArrayRef elementsArray = NULL; CFMutableArrayRef elementsArray = NULL;
CFNumberGetValue(CFDictionaryGetValue(elementRef, CFSTR(kIOHIDElementTypeKey)), elementType = IOHIDElementGetType(elementRef);
kCFNumberLongType, &elementType); usagePage = IOHIDElementGetUsagePage(elementRef);
CFNumberGetValue(CFDictionaryGetValue(elementRef, CFSTR(kIOHIDElementUsagePageKey)), usage = IOHIDElementGetUsage(elementRef);
kCFNumberLongType, &usagePage);
CFNumberGetValue(CFDictionaryGetValue(elementRef, CFSTR(kIOHIDElementUsageKey)),
kCFNumberLongType, &usage);
if ((elementType == kIOHIDElementTypeInput_Axis) || if ((elementType == kIOHIDElementTypeInput_Axis) ||
(elementType == kIOHIDElementTypeInput_Button) || (elementType == kIOHIDElementTypeInput_Button) ||
@ -108,28 +105,19 @@ static void addJoystickElement(_GLFWjoystickIOKit* joystick, CFTypeRef elementRe
if (elementsArray) if (elementsArray)
{ {
long number;
CFTypeRef numberRef;
_GLFWjoyelement* element = calloc(1, sizeof(_GLFWjoyelement)); _GLFWjoyelement* element = calloc(1, sizeof(_GLFWjoyelement));
CFArrayAppendValue(elementsArray, element); CFArrayAppendValue(elementsArray, element);
numberRef = CFDictionaryGetValue(elementRef, CFSTR(kIOHIDElementCookieKey)); element->elementRef = elementRef;
if (numberRef && CFNumberGetValue(numberRef, kCFNumberLongType, &number))
element->cookie = (IOHIDElementCookie) number;
numberRef = CFDictionaryGetValue(elementRef, CFSTR(kIOHIDElementMinKey)); element->minReport = IOHIDElementGetLogicalMin(elementRef);
if (numberRef && CFNumberGetValue(numberRef, kCFNumberLongType, &number)) element->maxReport = IOHIDElementGetLogicalMax(elementRef);
element->minReport = element->min = number;
numberRef = CFDictionaryGetValue(elementRef, CFSTR(kIOHIDElementMaxKey));
if (numberRef && CFNumberGetValue(numberRef, kCFNumberLongType, &number))
element->maxReport = element->max = number;
} }
} }
else else
{ {
CFTypeRef array = CFDictionaryGetValue(elementRef, CFSTR(kIOHIDElementKey)); CFArrayRef array = IOHIDElementGetChildren(elementRef);
if (array) if (array)
{ {
if (CFGetTypeID(array) == CFArrayGetTypeID()) if (CFGetTypeID(array) == CFArrayGetTypeID())
@ -145,40 +133,43 @@ static void addJoystickElement(_GLFWjoystickIOKit* joystick, CFTypeRef elementRe
// //
static void getElementsCFArrayHandler(const void* value, void* parameter) static void getElementsCFArrayHandler(const void* value, void* parameter)
{ {
if (CFGetTypeID(value) == CFDictionaryGetTypeID()) if (CFGetTypeID(value) == IOHIDElementGetTypeID())
addJoystickElement((_GLFWjoystickIOKit*) parameter, (CFTypeRef) value); addJoystickElement((_GLFWjoydevice*) parameter, (IOHIDElementRef) value);
} }
// Returns the value of the specified element of the specified joystick // Returns the value of the specified element of the specified joystick
// //
static long getElementValue(_GLFWjoystickIOKit* joystick, _GLFWjoyelement* element) static long getElementValue(_GLFWjoydevice* joystick, _GLFWjoyelement* element)
{ {
IOReturn result = kIOReturnSuccess; IOReturn result = kIOReturnSuccess;
IOHIDEventStruct hidEvent; IOHIDValueRef valueRef;
hidEvent.value = 0; long value = 0;
if (joystick && element && joystick->interface) if (joystick && element && joystick->deviceRef)
{ {
result = (*(joystick->interface))->getElementValue(joystick->interface, result = IOHIDDeviceGetValue(joystick->deviceRef,
element->cookie, element->elementRef,
&hidEvent); &valueRef);
if (kIOReturnSuccess == result) if (kIOReturnSuccess == result)
{ {
value = IOHIDValueGetIntegerValue(valueRef);
// Record min and max for auto calibration // Record min and max for auto calibration
if (hidEvent.value < element->minReport) if (value < element->minReport)
element->minReport = hidEvent.value; element->minReport = value;
if (hidEvent.value > element->maxReport) if (value > element->maxReport)
element->maxReport = hidEvent.value; element->maxReport = value;
} }
} }
// Auto user scale // Auto user scale
return (long) hidEvent.value; return value;
} }
// Removes the specified joystick // Removes the specified joystick
// //
static void removeJoystick(_GLFWjoystickIOKit* joystick) static void removeJoystick(_GLFWjoydevice* joystick)
{ {
int i; int i;
@ -188,29 +179,22 @@ static void removeJoystick(_GLFWjoystickIOKit* joystick)
for (i = 0; i < CFArrayGetCount(joystick->axisElements); i++) for (i = 0; i < CFArrayGetCount(joystick->axisElements); i++)
free((void*) CFArrayGetValueAtIndex(joystick->axisElements, i)); free((void*) CFArrayGetValueAtIndex(joystick->axisElements, i));
CFArrayRemoveAllValues(joystick->axisElements); CFArrayRemoveAllValues(joystick->axisElements);
CFRelease(joystick->axisElements);
for (i = 0; i < CFArrayGetCount(joystick->buttonElements); i++) for (i = 0; i < CFArrayGetCount(joystick->buttonElements); i++)
free((void*) CFArrayGetValueAtIndex(joystick->buttonElements, i)); free((void*) CFArrayGetValueAtIndex(joystick->buttonElements, i));
CFArrayRemoveAllValues(joystick->buttonElements); CFArrayRemoveAllValues(joystick->buttonElements);
CFRelease(joystick->buttonElements);
for (i = 0; i < CFArrayGetCount(joystick->hatElements); i++) for (i = 0; i < CFArrayGetCount(joystick->hatElements); i++)
free((void*) CFArrayGetValueAtIndex(joystick->hatElements, i)); free((void*) CFArrayGetValueAtIndex(joystick->hatElements, i));
CFArrayRemoveAllValues(joystick->hatElements); CFArrayRemoveAllValues(joystick->hatElements);
CFRelease(joystick->hatElements);
free(joystick->axes); free(joystick->axes);
free(joystick->buttons); free(joystick->buttons);
(*(joystick->interface))->close(joystick->interface); memset(joystick, 0, sizeof(_GLFWjoydevice));
(*(joystick->interface))->Release(joystick->interface);
memset(joystick, 0, sizeof(_GLFWjoystickIOKit));
}
// Callback for user-initiated joystick removal
//
static void removalCallback(void* target, IOReturn result, void* refcon, void* sender)
{
removeJoystick((_GLFWjoystickIOKit*) refcon);
} }
// Polls for joystick events and updates GLFW state // Polls for joystick events and updates GLFW state
@ -223,7 +207,7 @@ static void pollJoystickEvents(void)
{ {
CFIndex i; CFIndex i;
int buttonIndex = 0; int buttonIndex = 0;
_GLFWjoystickIOKit* joystick = _glfw.iokit_js + joy; _GLFWjoydevice* joystick = _glfw.iokit_js.devices + joy;
if (!joystick->present) if (!joystick->present)
continue; continue;
@ -276,6 +260,107 @@ static void pollJoystickEvents(void)
} }
} }
// Callback for user-initiated joystick addition
//
static void matchCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef deviceRef)
{
_GLFWjoydevice* joystick;
int joy;
for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++)
{
joystick = _glfw.iokit_js.devices + joy;
if (!joystick->present)
continue;
if (joystick->deviceRef == deviceRef)
return;
}
for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++)
{
joystick = _glfw.iokit_js.devices + joy;
if (!joystick->present)
break;
}
if (joy > GLFW_JOYSTICK_LAST)
return;
joystick->present = GL_TRUE;
joystick->deviceRef = deviceRef;
CFStringRef name = IOHIDDeviceGetProperty(deviceRef, CFSTR(kIOHIDProductKey));
CFStringGetCString(name,
joystick->name,
sizeof(joystick->name),
kCFStringEncodingUTF8);
joystick->axisElements = CFArrayCreateMutable(NULL, 0, NULL);
joystick->buttonElements = CFArrayCreateMutable(NULL, 0, NULL);
joystick->hatElements = CFArrayCreateMutable(NULL, 0, NULL);
CFArrayRef arrayRef = IOHIDDeviceCopyMatchingElements(deviceRef, NULL, kIOHIDOptionsTypeNone);
CFRange range = { 0, CFArrayGetCount(arrayRef) };
CFArrayApplyFunction(arrayRef,
range,
getElementsCFArrayHandler,
(void*) joystick);
CFRelease(arrayRef);
joystick->axes = calloc(CFArrayGetCount(joystick->axisElements),
sizeof(float));
joystick->buttons = calloc(CFArrayGetCount(joystick->buttonElements) +
CFArrayGetCount(joystick->hatElements) * 4, 1);
}
// Callback for user-initiated joystick removal
//
static void removeCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef deviceRef)
{
int joy;
for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++) {
_GLFWjoydevice* joystick = _glfw.iokit_js.devices + joy;
if (joystick->deviceRef == deviceRef) {
removeJoystick(joystick);
break;
}
}
}
// Creates a dictionary to match against devices with the specified usage page and usage
//
static CFMutableDictionaryRef createMatchingDictionary(long usagePage, long usage)
{
CFMutableDictionaryRef result = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (result)
{
CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage);
if (pageRef)
{
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsagePageKey), pageRef);
CFRelease(pageRef);
CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
if (usageRef)
{
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsageKey), usageRef);
CFRelease(usageRef);
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
////// GLFW internal API ////// ////// GLFW internal API //////
@ -285,177 +370,64 @@ static void pollJoystickEvents(void)
// //
void _glfwInitJoysticks(void) void _glfwInitJoysticks(void)
{ {
int joy = 0; CFMutableArrayRef matchingCFArrayRef;
IOReturn result = kIOReturnSuccess;
mach_port_t masterPort = 0;
io_iterator_t objectIterator = 0;
CFMutableDictionaryRef hidMatchDictionary = NULL;
io_object_t ioHIDDeviceObject = 0;
result = IOMasterPort(bootstrap_port, &masterPort); _glfw.iokit_js.managerRef = IOHIDManagerCreate(kCFAllocatorDefault,
hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey); kIOHIDOptionsTypeNone);
if (kIOReturnSuccess != result || !hidMatchDictionary)
matchingCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (matchingCFArrayRef)
{ {
if (hidMatchDictionary) CFDictionaryRef matchingCFDictRef = createMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
CFRelease(hidMatchDictionary); if (matchingCFDictRef)
{
CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
CFRelease(matchingCFDictRef);
}
return; matchingCFDictRef = createMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
if (matchingCFDictRef)
{
CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
CFRelease(matchingCFDictRef);
}
matchingCFDictRef = createMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
if (matchingCFDictRef)
{
CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
CFRelease(matchingCFDictRef);
}
} }
result = IOServiceGetMatchingServices(masterPort, IOHIDManagerSetDeviceMatchingMultiple(_glfw.iokit_js.managerRef, matchingCFArrayRef);
hidMatchDictionary, CFRelease(matchingCFArrayRef);
&objectIterator);
if (result != kIOReturnSuccess)
return;
if (!objectIterator) IOHIDManagerRegisterDeviceMatchingCallback(_glfw.iokit_js.managerRef, &matchCallback, NULL);
{ IOHIDManagerRegisterDeviceRemovalCallback(_glfw.iokit_js.managerRef, &removeCallback, NULL);
// There are no joysticks
return;
}
while ((ioHIDDeviceObject = IOIteratorNext(objectIterator))) IOHIDManagerScheduleWithRunLoop(_glfw.iokit_js.managerRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
{
CFMutableDictionaryRef propsRef = NULL;
CFTypeRef valueRef = NULL;
kern_return_t result;
IOCFPlugInInterface** ppPlugInInterface = NULL; IOHIDManagerOpen(_glfw.iokit_js.managerRef, kIOHIDOptionsTypeNone);
HRESULT plugInResult = S_OK;
SInt32 score = 0;
long usagePage = 0; // Execute the run loop once in order to register any initially-attached joysticks
long usage = 0; CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
valueRef = IORegistryEntryCreateCFProperty(ioHIDDeviceObject,
CFSTR(kIOHIDPrimaryUsagePageKey),
kCFAllocatorDefault, kNilOptions);
if (valueRef)
{
CFNumberGetValue(valueRef, kCFNumberLongType, &usagePage);
CFRelease(valueRef);
}
valueRef = IORegistryEntryCreateCFProperty(ioHIDDeviceObject,
CFSTR(kIOHIDPrimaryUsageKey),
kCFAllocatorDefault, kNilOptions);
if (valueRef)
{
CFNumberGetValue(valueRef, kCFNumberLongType, &usage);
CFRelease(valueRef);
}
if (usagePage != kHIDPage_GenericDesktop)
{
// This device is not relevant to GLFW
continue;
}
if ((usage != kHIDUsage_GD_Joystick &&
usage != kHIDUsage_GD_GamePad &&
usage != kHIDUsage_GD_MultiAxisController))
{
// This device is not relevant to GLFW
continue;
}
result = IORegistryEntryCreateCFProperties(ioHIDDeviceObject,
&propsRef,
kCFAllocatorDefault,
kNilOptions);
if (result != kIOReturnSuccess)
continue;
_GLFWjoystickIOKit* joystick = _glfw.iokit_js + joy;
joystick->present = GL_TRUE;
result = IOCreatePlugInInterfaceForService(ioHIDDeviceObject,
kIOHIDDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&ppPlugInInterface,
&score);
if (kIOReturnSuccess != result)
{
CFRelease(propsRef);
return;
}
plugInResult = (*ppPlugInInterface)->QueryInterface(
ppPlugInInterface,
CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID),
(void *) &(joystick->interface));
if (plugInResult != S_OK)
{
CFRelease(propsRef);
return;
}
(*ppPlugInInterface)->Release(ppPlugInInterface);
(*(joystick->interface))->open(joystick->interface, 0);
(*(joystick->interface))->setRemovalCallback(joystick->interface,
removalCallback,
joystick,
joystick);
// Get product string
valueRef = CFDictionaryGetValue(propsRef, CFSTR(kIOHIDProductKey));
if (valueRef)
{
CFStringGetCString(valueRef,
joystick->name,
sizeof(joystick->name),
kCFStringEncodingUTF8);
}
joystick->axisElements = CFArrayCreateMutable(NULL, 0, NULL);
joystick->buttonElements = CFArrayCreateMutable(NULL, 0, NULL);
joystick->hatElements = CFArrayCreateMutable(NULL, 0, NULL);
valueRef = CFDictionaryGetValue(propsRef, CFSTR(kIOHIDElementKey));
if (CFGetTypeID(valueRef) == CFArrayGetTypeID())
{
CFRange range = { 0, CFArrayGetCount(valueRef) };
CFArrayApplyFunction(valueRef,
range,
getElementsCFArrayHandler,
(void*) joystick);
}
CFRelease(propsRef);
joystick->axes = calloc(CFArrayGetCount(joystick->axisElements),
sizeof(float));
joystick->buttons = calloc(CFArrayGetCount(joystick->buttonElements) +
CFArrayGetCount(joystick->hatElements) * 4, 1);
joy++;
if (joy > GLFW_JOYSTICK_LAST)
break;
}
} }
// Close all opened joystick handles // Close all opened joystick handles
// //
void _glfwTerminateJoysticks(void) void _glfwTerminateJoysticks(void)
{ {
int i; int joy;
for (i = 0; i < GLFW_JOYSTICK_LAST + 1; i++) for (joy = 0; joy <= GLFW_JOYSTICK_LAST; joy++)
{ {
_GLFWjoystickIOKit* joystick = &_glfw.iokit_js[i]; _GLFWjoydevice* joystick = _glfw.iokit_js.devices + joy;
removeJoystick(joystick); removeJoystick(joystick);
if (joystick->axisElements)
CFRelease(joystick->axisElements);
if (joystick->buttonElements)
CFRelease(joystick->buttonElements);
if (joystick->hatElements)
CFRelease(joystick->hatElements);
} }
CFRelease(_glfw.iokit_js.managerRef);
_glfw.iokit_js.managerRef = NULL;
} }
@ -467,12 +439,12 @@ int _glfwPlatformJoystickPresent(int joy)
{ {
pollJoystickEvents(); pollJoystickEvents();
return _glfw.iokit_js[joy].present; return _glfw.iokit_js.devices[joy].present;
} }
const float* _glfwPlatformGetJoystickAxes(int joy, int* count) const float* _glfwPlatformGetJoystickAxes(int joy, int* count)
{ {
_GLFWjoystickIOKit* joystick = _glfw.iokit_js + joy; _GLFWjoydevice* joystick = _glfw.iokit_js.devices + joy;
pollJoystickEvents(); pollJoystickEvents();
@ -485,7 +457,7 @@ const float* _glfwPlatformGetJoystickAxes(int joy, int* count)
const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count) const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count)
{ {
_GLFWjoystickIOKit* joystick = _glfw.iokit_js + joy; _GLFWjoydevice* joystick = _glfw.iokit_js.devices + joy;
pollJoystickEvents(); pollJoystickEvents();
@ -501,6 +473,6 @@ const char* _glfwPlatformGetJoystickName(int joy)
{ {
pollJoystickEvents(); pollJoystickEvents();
return _glfw.iokit_js[joy].name; return _glfw.iokit_js.devices[joy].name;
} }