Add support for joystick hot swapping on OS X.

master
Aaron Jacobs ago%!(EXTRA string=10 years) committed by Camilla Berglund
parent 19a28e2c9f
commit 1a96c294ee
  1. 15
      src/iokit_joystick.h
  2. 348
      src/iokit_joystick.m

@ -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;

@ -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));
if (numberRef && CFNumberGetValue(numberRef, kCFNumberLongType, &number))
element->minReport = element->min = number;
numberRef = CFDictionaryGetValue(elementRef, CFSTR(kIOHIDElementMaxKey)); element->minReport = IOHIDElementGetLogicalMin(elementRef);
if (numberRef && CFNumberGetValue(numberRef, kCFNumberLongType, &number)) element->maxReport = IOHIDElementGetLogicalMax(elementRef);
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,186 +260,174 @@ static void pollJoystickEvents(void)
} }
} }
// Callback for user-initiated joystick addition
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize joystick interface
// //
void _glfwInitJoysticks(void) static void matchCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef deviceRef)
{ {
int joy = 0; _GLFWjoydevice* joystick;
IOReturn result = kIOReturnSuccess; int joy;
mach_port_t masterPort = 0;
io_iterator_t objectIterator = 0;
CFMutableDictionaryRef hidMatchDictionary = NULL;
io_object_t ioHIDDeviceObject = 0;
result = IOMasterPort(bootstrap_port, &masterPort);
hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey);
if (kIOReturnSuccess != result || !hidMatchDictionary)
{
if (hidMatchDictionary)
CFRelease(hidMatchDictionary);
return; for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++)
} {
joystick = _glfw.iokit_js.devices + joy;
result = IOServiceGetMatchingServices(masterPort, if (!joystick->present)
hidMatchDictionary, continue;
&objectIterator);
if (result != kIOReturnSuccess)
return;
if (!objectIterator) if (joystick->deviceRef == deviceRef)
{ return;
// There are no joysticks
return;
} }
while ((ioHIDDeviceObject = IOIteratorNext(objectIterator))) for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++)
{ {
CFMutableDictionaryRef propsRef = NULL; joystick = _glfw.iokit_js.devices + joy;
CFTypeRef valueRef = NULL;
kern_return_t result;
IOCFPlugInInterface** ppPlugInInterface = NULL; if (!joystick->present)
HRESULT plugInResult = S_OK; break;
SInt32 score = 0; }
long usagePage = 0; if (joy > GLFW_JOYSTICK_LAST)
long usage = 0; return;
valueRef = IORegistryEntryCreateCFProperty(ioHIDDeviceObject, joystick->present = GL_TRUE;
CFSTR(kIOHIDPrimaryUsagePageKey), joystick->deviceRef = deviceRef;
kCFAllocatorDefault, kNilOptions);
if (valueRef)
{
CFNumberGetValue(valueRef, kCFNumberLongType, &usagePage);
CFRelease(valueRef);
}
valueRef = IORegistryEntryCreateCFProperty(ioHIDDeviceObject, CFStringRef name = IOHIDDeviceGetProperty(deviceRef, CFSTR(kIOHIDProductKey));
CFSTR(kIOHIDPrimaryUsageKey), CFStringGetCString(name,
kCFAllocatorDefault, kNilOptions); joystick->name,
if (valueRef) sizeof(joystick->name),
{ kCFStringEncodingUTF8);
CFNumberGetValue(valueRef, kCFNumberLongType, &usage);
CFRelease(valueRef);
}
if (usagePage != kHIDPage_GenericDesktop) joystick->axisElements = CFArrayCreateMutable(NULL, 0, NULL);
{ joystick->buttonElements = CFArrayCreateMutable(NULL, 0, NULL);
// This device is not relevant to GLFW joystick->hatElements = CFArrayCreateMutable(NULL, 0, NULL);
continue;
}
if ((usage != kHIDUsage_GD_Joystick && CFArrayRef arrayRef = IOHIDDeviceCopyMatchingElements(deviceRef, NULL, kIOHIDOptionsTypeNone);
usage != kHIDUsage_GD_GamePad && CFRange range = { 0, CFArrayGetCount(arrayRef) };
usage != kHIDUsage_GD_MultiAxisController)) CFArrayApplyFunction(arrayRef,
{ range,
// This device is not relevant to GLFW getElementsCFArrayHandler,
continue; (void*) joystick);
}
result = IORegistryEntryCreateCFProperties(ioHIDDeviceObject, CFRelease(arrayRef);
&propsRef,
kCFAllocatorDefault,
kNilOptions);
if (result != kIOReturnSuccess) joystick->axes = calloc(CFArrayGetCount(joystick->axisElements),
continue; sizeof(float));
joystick->buttons = calloc(CFArrayGetCount(joystick->buttonElements) +
CFArrayGetCount(joystick->hatElements) * 4, 1);
}
_GLFWjoystickIOKit* joystick = _glfw.iokit_js + joy; // Callback for user-initiated joystick removal
joystick->present = GL_TRUE; //
static void removeCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef deviceRef)
{
int joy;
result = IOCreatePlugInInterfaceForService(ioHIDDeviceObject, for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++) {
kIOHIDDeviceUserClientTypeID, _GLFWjoydevice* joystick = _glfw.iokit_js.devices + joy;
kIOCFPlugInInterfaceID,
&ppPlugInInterface,
&score);
if (kIOReturnSuccess != result) if (joystick->deviceRef == deviceRef) {
{ removeJoystick(joystick);
CFRelease(propsRef); break;
return;
} }
}
}
plugInResult = (*ppPlugInInterface)->QueryInterface( // Creates a dictionary to match against devices with the specified usage page and usage
ppPlugInInterface, //
CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), static CFMutableDictionaryRef createMatchingDictionary(long usagePage, long usage)
(void *) &(joystick->interface)); {
CFMutableDictionaryRef result = CFDictionaryCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (plugInResult != S_OK) if (result)
{
CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage);
if (pageRef)
{ {
CFRelease(propsRef); CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsagePageKey), pageRef);
return; CFRelease(pageRef);
CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
if (usageRef)
{
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsageKey), usageRef);
CFRelease(usageRef);
}
} }
}
return result;
}
(*ppPlugInInterface)->Release(ppPlugInInterface); //////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
(*(joystick->interface))->open(joystick->interface, 0); // Initialize joystick interface
(*(joystick->interface))->setRemovalCallback(joystick->interface, //
removalCallback, void _glfwInitJoysticks(void)
joystick, {
joystick); CFMutableArrayRef matchingCFArrayRef;
_glfw.iokit_js.managerRef = IOHIDManagerCreate(kCFAllocatorDefault,
kIOHIDOptionsTypeNone);
// Get product string matchingCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
valueRef = CFDictionaryGetValue(propsRef, CFSTR(kIOHIDProductKey)); if (matchingCFArrayRef)
if (valueRef) {
CFDictionaryRef matchingCFDictRef = createMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
if (matchingCFDictRef)
{ {
CFStringGetCString(valueRef, CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
joystick->name, CFRelease(matchingCFDictRef);
sizeof(joystick->name),
kCFStringEncodingUTF8);
} }
joystick->axisElements = CFArrayCreateMutable(NULL, 0, NULL); matchingCFDictRef = createMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
joystick->buttonElements = CFArrayCreateMutable(NULL, 0, NULL); if (matchingCFDictRef)
joystick->hatElements = CFArrayCreateMutable(NULL, 0, NULL); {
CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
CFRelease(matchingCFDictRef);
}
valueRef = CFDictionaryGetValue(propsRef, CFSTR(kIOHIDElementKey)); matchingCFDictRef = createMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
if (CFGetTypeID(valueRef) == CFArrayGetTypeID()) if (matchingCFDictRef)
{ {
CFRange range = { 0, CFArrayGetCount(valueRef) }; CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
CFArrayApplyFunction(valueRef, CFRelease(matchingCFDictRef);
range,
getElementsCFArrayHandler,
(void*) joystick);
} }
}
CFRelease(propsRef); IOHIDManagerSetDeviceMatchingMultiple(_glfw.iokit_js.managerRef, matchingCFArrayRef);
CFRelease(matchingCFArrayRef);
joystick->axes = calloc(CFArrayGetCount(joystick->axisElements), IOHIDManagerRegisterDeviceMatchingCallback(_glfw.iokit_js.managerRef, &matchCallback, NULL);
sizeof(float)); IOHIDManagerRegisterDeviceRemovalCallback(_glfw.iokit_js.managerRef, &removeCallback, NULL);
joystick->buttons = calloc(CFArrayGetCount(joystick->buttonElements) +
CFArrayGetCount(joystick->hatElements) * 4, 1);
joy++; IOHIDManagerScheduleWithRunLoop(_glfw.iokit_js.managerRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
if (joy > GLFW_JOYSTICK_LAST)
break; IOHIDManagerOpen(_glfw.iokit_js.managerRef, kIOHIDOptionsTypeNone);
}
// Execute the run loop once in order to register any initially-attached joysticks
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
} }
// 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;
} }

Loading…
Cancel
Save