From 2357cf6f30a4eb5c05b13b21ecc50b846b8861d1 Mon Sep 17 00:00:00 2001 From: Sergey Tikhomirov Date: Sun, 18 Sep 2011 22:05:00 +0300 Subject: [PATCH] Added initial joystick support on OS X --- CMakeLists.txt | 4 + src/cocoa_init.m | 4 + src/cocoa_joystick.m | 477 ++++++++++++++++++++++++++++++++++++++++++- src/cocoa_platform.h | 9 + 4 files changed, 486 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 83eb8bf8..12d16424 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,8 +117,12 @@ if (UNIX AND APPLE) # Set up library and include paths find_library(COCOA_FRAMEWORK Cocoa) + find_library(IOKIT_FRAMEWORK IOKit) + find_library(CORE_FOUNDATION_FRAMEWORK CoreFoundation) list(APPEND GLFW_LIBRARIES ${COCOA_FRAMEWORK}) list(APPEND GLFW_LIBRARIES ${OPENGL_gl_LIBRARY}) + list(APPEND GLFW_LIBRARIES ${IOKIT_FRAMEWORK}) + list(APPEND GLFW_LIBRARIES ${CORE_FOUNDATION_FRAMEWORK}) endif(UNIX AND APPLE) #-------------------------------------------------------------------- diff --git a/src/cocoa_init.m b/src/cocoa_init.m index 2455d594..c6af9e67 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -225,6 +225,8 @@ int _glfwPlatformInit(void) _glfwLibrary.NS.desktopMode = (NSDictionary*) CGDisplayCurrentMode(CGMainDisplayID()); + _glfwInitJoysticks(); + return GL_TRUE; } @@ -243,6 +245,8 @@ int _glfwPlatformTerminate(void) [_glfwLibrary.NS.autoreleasePool release]; _glfwLibrary.NS.autoreleasePool = nil; + _glfwTerminateJoysticks(); + return GL_TRUE; } diff --git a/src/cocoa_joystick.m b/src/cocoa_joystick.m index d56ff679..3b2c1533 100644 --- a/src/cocoa_joystick.m +++ b/src/cocoa_joystick.m @@ -30,6 +30,388 @@ #include "internal.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//------------------------------------------------------------------------ +// Joystick element information +//------------------------------------------------------------------------ + +typedef struct { + IOHIDElementCookie Cookie; + + long Value; + + long Min; + long Max; + + long MinReport; + long MaxReport; +} joystick_element_t; + + +//------------------------------------------------------------------------ +// Joystick information & state +//------------------------------------------------------------------------ + +typedef struct { + int Present; + char Product[256]; + + IOHIDDeviceInterface ** Interface; + + int NumAxes; + int NumButtons; + int NumHats; + + CFMutableArrayRef Axes; + CFMutableArrayRef Buttons; + CFMutableArrayRef Hats; +} joystick_t; + +joystick_t _glfwJoysticks[GLFW_JOYSTICK_LAST + 1]; + + +void GetElementsCFArrayHandler(const void * value, void * parameter); + +static void JoystickAddElemet(joystick_t * joystick, CFTypeRef refElement) +{ + long elementType, usagePage, usage; + CFTypeRef refElementType, refUsagePage, refUsage; + + refElementType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementTypeKey)); + refUsagePage = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementUsagePageKey)); + refUsage = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementUsageKey)); + + CFMutableArrayRef elementsArray = NULL; + + CFNumberGetValue(refElementType, kCFNumberLongType, &elementType); + CFNumberGetValue(refUsagePage, kCFNumberLongType, &usagePage); + CFNumberGetValue(refUsage, kCFNumberLongType, &usage); + + if ((elementType == kIOHIDElementTypeInput_Axis) || + (elementType == kIOHIDElementTypeInput_Button) || + (elementType == kIOHIDElementTypeInput_Misc)) + { + 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: + joystick->NumAxes++; + elementsArray = joystick->Axes; + break; + case kHIDUsage_GD_Hatswitch: + joystick->NumHats++; + elementsArray = joystick->Hats; + break; + } + break; + case kHIDPage_Button: + joystick->NumButtons++; + elementsArray = joystick->Buttons; + break; + default: + break; + } + + if (elementsArray) + { + long number; + CFTypeRef refType; + + joystick_element_t * element = (joystick_element_t *) malloc(sizeof(joystick_element_t)); + + CFArrayAppendValue(elementsArray, element); + + refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementCookieKey)); + if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number)) + element->Cookie = (IOHIDElementCookie) number; + + refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementMinKey)); + if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number)) + element->MinReport = element->Min = number; + + refType = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementMaxKey)); + if (refType && CFNumberGetValue(refType, kCFNumberLongType, &number)) + element->MaxReport = element->Max = number; + } + } + else + { + CFTypeRef refElementTop = CFDictionaryGetValue(refElement, CFSTR(kIOHIDElementKey)); + if (refElementTop) + { + CFTypeID type = CFGetTypeID (refElementTop); + if (type == CFArrayGetTypeID()) /* if element is an array */ + { + CFRange range = {0, CFArrayGetCount (refElementTop)}; + CFArrayApplyFunction(refElementTop, range, GetElementsCFArrayHandler, joystick); + } + + } + } +} + +void GetElementsCFArrayHandler(const void * value, void * parameter) +{ + if (CFGetTypeID(value) == CFDictionaryGetTypeID()) + JoystickAddElemet((joystick_t *) parameter, (CFTypeRef) value); +} + + +static long GetElementValue(joystick_t * joystick, joystick_element_t * element) +{ + IOReturn result = kIOReturnSuccess; + IOHIDEventStruct hidEvent; + hidEvent.value = 0; + + if (NULL != joystick && NULL != element && NULL != joystick->Interface) + { + result = (*(joystick->Interface))->getElementValue(joystick->Interface, element->Cookie, &hidEvent); + if (kIOReturnSuccess == result) + { + /* record min and max for auto calibration */ + if (hidEvent.value < element->MinReport) + element->MinReport = hidEvent.value; + if (hidEvent.value > element->MaxReport) + element->MaxReport = hidEvent.value; + } + } + + /* auto user scale */ + return (long) hidEvent.value; +} + + +static void RemoveJoystick(joystick_t * joystick) +{ + if (joystick->Present) + { + joystick->Present = GL_FALSE; + + for (int i = 0; i < joystick->NumAxes; i++) + { + joystick_element_t * axes = + (joystick_element_t *) CFArrayGetValueAtIndex(joystick->Axes, i); + free(axes); + } + CFArrayRemoveAllValues(joystick->Axes); + joystick->NumAxes = 0; + + for (int i = 0; i < joystick->NumButtons; i++) + { + joystick_element_t * button = + (joystick_element_t *) CFArrayGetValueAtIndex(joystick->Buttons, i); + free(button); + } + CFArrayRemoveAllValues(joystick->Buttons); + joystick->NumButtons = 0; + + for (int i = 0; i < joystick->NumHats; i++) + { + joystick_element_t * hat = + (joystick_element_t *) CFArrayGetValueAtIndex(joystick->Hats, i); + free(hat); + } + CFArrayRemoveAllValues(joystick->Hats); + joystick->Hats = 0; + + (*(joystick->Interface))->close(joystick->Interface); + (*(joystick->Interface))->Release(joystick->Interface); + + joystick->Interface = NULL; + } +} + + +static void RemovalCallback(void * target, IOReturn result, void * refcon, void * sender) +{ + RemoveJoystick((joystick_t *) refcon); +} + + +static void PollJoystickEvents(void) +{ + for (int i = 0; i < GLFW_JOYSTICK_LAST + 1; i++) + { + joystick_t * joystick = &_glfwJoysticks[i]; + + if (joystick->Present) + { + for (CFIndex i = 0; i < joystick->NumButtons; i++) + { + joystick_element_t * button = + (joystick_element_t *) CFArrayGetValueAtIndex(joystick->Buttons, i); + button->Value = GetElementValue(joystick, button); + } + + for (CFIndex i = 0; i < joystick->NumAxes; i++) + { + joystick_element_t * axes = + (joystick_element_t *) CFArrayGetValueAtIndex(joystick->Axes, i); + axes->Value = GetElementValue(joystick, axes); + } + } + } +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +//======================================================================== +// Initialize joystick interface +//======================================================================== + +void _glfwInitJoysticks(void) +{ + int deviceCounter = 0; + 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); + hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey); + if (kIOReturnSuccess != result || !hidMatchDictionary) + return; + + result = IOServiceGetMatchingServices(masterPort, hidMatchDictionary, &objectIterator); + if (kIOReturnSuccess != result) + return; + + if (!objectIterator) /* there are no joysticks */ + return; + + while ((ioHIDDeviceObject = IOIteratorNext(objectIterator))) + { + CFMutableDictionaryRef hidProperties = 0; + kern_return_t result; + CFTypeRef refCF = 0; + + IOCFPlugInInterface ** ppPlugInInterface = NULL; + HRESULT plugInResult = S_OK; + SInt32 score = 0; + + long usagePage, usage; + + + result = IORegistryEntryCreateCFProperties( + ioHIDDeviceObject, + &hidProperties, + kCFAllocatorDefault, + kNilOptions); + + if (result != kIOReturnSuccess) continue; + + /* Check device type */ + refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDPrimaryUsagePageKey)); + if (refCF) + CFNumberGetValue(refCF, kCFNumberLongType, &usagePage); + + refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDPrimaryUsageKey)); + if (refCF) + CFNumberGetValue (refCF, kCFNumberLongType, &usage); + + if ((usagePage != kHIDPage_GenericDesktop) || + (usage != kHIDUsage_GD_Joystick && + usage != kHIDUsage_GD_GamePad && + usage != kHIDUsage_GD_MultiAxisController)) + { + /* We don't interested in this device */ + continue; + } + + + joystick_t * joystick = &_glfwJoysticks[deviceCounter]; + + joystick->Present = GL_TRUE; + + + result = IOCreatePlugInInterfaceForService( + ioHIDDeviceObject, + kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &ppPlugInInterface, + &score); + + if (kIOReturnSuccess != result) + exit(EXIT_SUCCESS); + + plugInResult = (*ppPlugInInterface)->QueryInterface( + ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), + (void *) &(joystick->Interface)); + + if (S_OK != plugInResult) + exit(EXIT_FAILURE); + + (*ppPlugInInterface)->Release(ppPlugInInterface); + + (*(joystick->Interface))->open(joystick->Interface, 0); + (*(joystick->Interface))->setRemovalCallback(joystick->Interface, RemovalCallback, joystick, joystick); + + /* Get product string */ + refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDProductKey)); + if (refCF) + CFStringGetCString(refCF, (char *) &(joystick->Product), 256, CFStringGetSystemEncoding()); + + joystick->NumAxes = 0; + joystick->NumButtons = 0; + joystick->NumHats = 0; + joystick->Axes = CFArrayCreateMutable(NULL, 0, NULL); + joystick->Buttons = CFArrayCreateMutable(NULL, 0, NULL); + joystick->Hats = CFArrayCreateMutable(NULL, 0, NULL); + + CFTypeRef refTopElement = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDElementKey)); + CFTypeID type = CFGetTypeID(refTopElement); + if (type == CFArrayGetTypeID()) + { + CFRange range = {0, CFArrayGetCount(refTopElement)}; + CFArrayApplyFunction(refTopElement, range, GetElementsCFArrayHandler, (void *) joystick); + } + + deviceCounter++; + } +} + + +//======================================================================== +// Close all opened joystick handles +//======================================================================== + +void _glfwTerminateJoysticks(void) +{ + for (int i = 0; i < GLFW_JOYSTICK_LAST + 1; i++) + { + joystick_t * joystick = &_glfwJoysticks[i]; + RemoveJoystick(joystick); + } +} + + ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// @@ -38,29 +420,108 @@ // Determine joystick capabilities //======================================================================== -int _glfwPlatformGetJoystickParam( int joy, int param ) +int _glfwPlatformGetJoystickParam(int joy, int param) { - // TODO: Implement this. + if (!_glfwJoysticks[joy].Present) + { + // TODO: Figure out if this is an error + return 0; + } + + switch (param) + { + case GLFW_PRESENT: + return GL_TRUE; + + case GLFW_AXES: + return (int) CFArrayGetCount(_glfwJoysticks[joy].Axes); + + case GLFW_BUTTONS: + return (int) CFArrayGetCount(_glfwJoysticks[joy].Buttons); + + default: + break; + } + return 0; } + //======================================================================== // Get joystick axis positions //======================================================================== -int _glfwPlatformGetJoystickPos( int joy, float *pos, int numaxes ) +int _glfwPlatformGetJoystickPos(int joy, float *pos, int numaxes) { - // TODO: Implement this. - return 0; + if (joy < GLFW_JOYSTICK_1 || joy > GLFW_JOYSTICK_LAST) + return 0; + + joystick_t joystick = _glfwJoysticks[joy]; + + if (!joystick.Present) + { + // TODO: Figure out if this is an error + return 0; + } + + numaxes = numaxes < joystick.NumAxes ? numaxes : joystick.NumAxes; + + // Update joystick state + PollJoystickEvents(); + + for (int i = 0; i < numaxes; i++) + { + joystick_element_t * axes = + (joystick_element_t *) CFArrayGetValueAtIndex(joystick.Axes, i); + + long readScale = axes->MaxReport - axes->MinReport; + + if (readScale == 0) + pos[i] = axes->Value; + else + pos[i] = (2.0f * (axes->Value - axes->MinReport) / readScale) - 1.0f; + + printf("%ld, %ld, %ld\n", axes->Value, axes->MinReport, axes->MaxReport); + + if (i & 1) + pos[i] = -pos[i]; + } + + return numaxes; } + //======================================================================== // Get joystick button states //======================================================================== -int _glfwPlatformGetJoystickButtons( int joy, unsigned char *buttons, int numbuttons ) +int _glfwPlatformGetJoystickButtons(int joy, unsigned char *buttons, + int numbuttons) { - // TODO: Implement this. - return 0; + if (joy < GLFW_JOYSTICK_1 || joy > GLFW_JOYSTICK_LAST) + return 0; + + joystick_t joystick = _glfwJoysticks[joy]; + + if (!joystick.Present) + { + // TODO: Figure out if this is an error + return 0; + } + + numbuttons = numbuttons < joystick.NumButtons ? numbuttons : joystick.NumButtons; + + // Update joystick state + PollJoystickEvents(); + + + for (int i = 0; i < numbuttons; i++) + { + joystick_element_t * button = (joystick_element_t *) CFArrayGetValueAtIndex(joystick.Buttons, i); + buttons[i] = button->Value ? GLFW_PRESS : GLFW_RELEASE; + } + + return numbuttons; } + diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 17e92a8f..d5daf775 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -97,4 +97,13 @@ typedef struct _GLFWlibraryNS } _GLFWlibraryNS; +//======================================================================== +// Prototypes for platform specific internal functions +//======================================================================== + +// Joystick input +void _glfwInitJoysticks(void); +void _glfwTerminateJoysticks(void); + + #endif // _platform_h_