638 lines
20 KiB
Objective-C
638 lines
20 KiB
Objective-C
//========================================================================
|
|
// GLFW - An OpenGL library
|
|
// Platform: Cocoa
|
|
// API Version: 3.0
|
|
// WWW: http://www.glfw.org/
|
|
//------------------------------------------------------------------------
|
|
// Copyright (c) 2009-2010 Camilla Berglund <elmindreda@elmindreda.org>
|
|
// Copyright (c) 2012 Torsten Walluhn <tw@mad-cad.net>
|
|
//
|
|
// 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 "internal.h"
|
|
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
#include <mach/mach.h>
|
|
#include <mach/mach_error.h>
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <IOKit/IOKitLib.h>
|
|
#include <IOKit/IOCFPlugIn.h>
|
|
#include <IOKit/hid/IOHIDLib.h>
|
|
#include <IOKit/hid/IOHIDKeys.h>
|
|
#include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
// Joystick element information
|
|
//------------------------------------------------------------------------
|
|
typedef struct
|
|
{
|
|
IOHIDElementCookie cookie;
|
|
|
|
long value;
|
|
|
|
long min;
|
|
long max;
|
|
|
|
long minReport;
|
|
long maxReport;
|
|
|
|
} _glfwJoystickElement;
|
|
|
|
|
|
//------------------------------------------------------------------------
|
|
// Joystick information & state
|
|
//------------------------------------------------------------------------
|
|
typedef struct
|
|
{
|
|
int present;
|
|
char name[256];
|
|
|
|
IOHIDDeviceInterface** interface;
|
|
|
|
int numAxes;
|
|
int numButtons;
|
|
int numHats;
|
|
|
|
CFMutableArrayRef axes;
|
|
CFMutableArrayRef buttons;
|
|
CFMutableArrayRef hats;
|
|
|
|
} _glfwJoystick;
|
|
|
|
static _glfwJoystick _glfwJoysticks[GLFW_JOYSTICK_LAST + 1];
|
|
|
|
|
|
static void getElementsCFArrayHandler(const void* value, void* parameter);
|
|
|
|
|
|
//========================================================================
|
|
// Adds an element to the specified joystick
|
|
//========================================================================
|
|
|
|
static void addJoystickElement(_glfwJoystick* 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)
|
|
{
|
|
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;
|
|
|
|
_glfwJoystickElement* element = (_glfwJoystickElement*) malloc(sizeof(_glfwJoystickElement));
|
|
|
|
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())
|
|
{
|
|
CFRange range = {0, CFArrayGetCount (refElementTop)};
|
|
CFArrayApplyFunction(refElementTop, range, getElementsCFArrayHandler, joystick);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//========================================================================
|
|
// Adds an element to the specified joystick
|
|
//========================================================================
|
|
|
|
static void getElementsCFArrayHandler(const void* value, void* parameter)
|
|
{
|
|
if (CFGetTypeID(value) == CFDictionaryGetTypeID())
|
|
addJoystickElement((_glfwJoystick*) parameter, (CFTypeRef) value);
|
|
}
|
|
|
|
|
|
//========================================================================
|
|
// Returns the value of the specified element of the specified joystick
|
|
//========================================================================
|
|
|
|
static long getElementValue(_glfwJoystick* joystick, _glfwJoystickElement* element)
|
|
{
|
|
IOReturn result = kIOReturnSuccess;
|
|
IOHIDEventStruct hidEvent;
|
|
hidEvent.value = 0;
|
|
|
|
if (joystick && element && 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;
|
|
}
|
|
|
|
|
|
//========================================================================
|
|
// Removes the specified joystick
|
|
//========================================================================
|
|
|
|
static void removeJoystick(_glfwJoystick* joystick)
|
|
{
|
|
int i;
|
|
|
|
if (joystick->present)
|
|
{
|
|
joystick->present = GL_FALSE;
|
|
|
|
for (i = 0; i < joystick->numAxes; i++)
|
|
{
|
|
_glfwJoystickElement* axes =
|
|
(_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick->axes, i);
|
|
free(axes);
|
|
}
|
|
CFArrayRemoveAllValues(joystick->axes);
|
|
joystick->numAxes = 0;
|
|
|
|
for (i = 0; i < joystick->numButtons; i++)
|
|
{
|
|
_glfwJoystickElement* button =
|
|
(_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick->buttons, i);
|
|
free(button);
|
|
}
|
|
CFArrayRemoveAllValues(joystick->buttons);
|
|
joystick->numButtons = 0;
|
|
|
|
for (i = 0; i < joystick->numHats; i++)
|
|
{
|
|
_glfwJoystickElement* hat =
|
|
(_glfwJoystickElement*) 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;
|
|
}
|
|
}
|
|
|
|
|
|
//========================================================================
|
|
// Callback for user-initiated joystick removal
|
|
//========================================================================
|
|
|
|
static void removalCallback(void* target, IOReturn result, void* refcon, void* sender)
|
|
{
|
|
removeJoystick((_glfwJoystick*) refcon);
|
|
}
|
|
|
|
|
|
//========================================================================
|
|
// Polls for joystick events and updates GLFW state
|
|
//========================================================================
|
|
|
|
static void pollJoystickEvents(void)
|
|
{
|
|
int i;
|
|
CFIndex j;
|
|
|
|
for (i = 0; i < GLFW_JOYSTICK_LAST + 1; i++)
|
|
{
|
|
_glfwJoystick* joystick = &_glfwJoysticks[i];
|
|
|
|
if (joystick->present)
|
|
{
|
|
for (j = 0; j < joystick->numButtons; j++)
|
|
{
|
|
_glfwJoystickElement* button =
|
|
(_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick->buttons, j);
|
|
button->value = getElementValue(joystick, button);
|
|
}
|
|
|
|
for (j = 0; j < joystick->numAxes; j++)
|
|
{
|
|
_glfwJoystickElement* axes =
|
|
(_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick->axes, j);
|
|
axes->value = getElementValue(joystick, axes);
|
|
}
|
|
|
|
for (j = 0; j < joystick->numHats; j++)
|
|
{
|
|
_glfwJoystickElement* hat =
|
|
(_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick->hats, j);
|
|
hat->value = getElementValue(joystick, hat);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////// 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;
|
|
|
|
memset(&_glfwJoysticks, 0, sizeof(_glfwJoysticks));
|
|
|
|
result = IOMasterPort(bootstrap_port, &masterPort);
|
|
hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey);
|
|
if (kIOReturnSuccess != result || !hidMatchDictionary)
|
|
{
|
|
if (hidMatchDictionary)
|
|
CFRelease(hidMatchDictionary);
|
|
|
|
return;
|
|
}
|
|
|
|
result = IOServiceGetMatchingServices(masterPort,
|
|
hidMatchDictionary,
|
|
&objectIterator);
|
|
if (result != kIOReturnSuccess)
|
|
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);
|
|
if (usagePage != kHIDPage_GenericDesktop)
|
|
{
|
|
// This device is not relevant to GLFW
|
|
continue;
|
|
}
|
|
}
|
|
|
|
refCF = CFDictionaryGetValue(hidProperties, CFSTR(kIOHIDPrimaryUsageKey));
|
|
if (refCF)
|
|
{
|
|
CFNumberGetValue(refCF, kCFNumberLongType, &usage);
|
|
|
|
if ((usage != kHIDUsage_GD_Joystick &&
|
|
usage != kHIDUsage_GD_GamePad &&
|
|
usage != kHIDUsage_GD_MultiAxisController))
|
|
{
|
|
// This device is not relevant to GLFW
|
|
continue;
|
|
}
|
|
}
|
|
|
|
_glfwJoystick* joystick = &_glfwJoysticks[deviceCounter];
|
|
|
|
joystick->present = GL_TRUE;
|
|
|
|
result = IOCreatePlugInInterfaceForService(ioHIDDeviceObject,
|
|
kIOHIDDeviceUserClientTypeID,
|
|
kIOCFPlugInInterfaceID,
|
|
&ppPlugInInterface,
|
|
&score);
|
|
|
|
if (kIOReturnSuccess != result)
|
|
return;
|
|
|
|
plugInResult = (*ppPlugInInterface)->QueryInterface(
|
|
ppPlugInInterface,
|
|
CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID),
|
|
(void *) &(joystick->interface));
|
|
|
|
if (plugInResult != S_OK)
|
|
return;
|
|
|
|
(*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,
|
|
joystick->name,
|
|
sizeof(joystick->name),
|
|
kCFStringEncodingUTF8);
|
|
}
|
|
|
|
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)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < GLFW_JOYSTICK_LAST + 1; i++)
|
|
{
|
|
_glfwJoystick* joystick = &_glfwJoysticks[i];
|
|
removeJoystick(joystick);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////// GLFW platform API //////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//========================================================================
|
|
// Determine joystick capabilities
|
|
//========================================================================
|
|
|
|
int _glfwPlatformGetJoystickParam(int joy, int param)
|
|
{
|
|
if (!_glfwJoysticks[joy].present)
|
|
{
|
|
// TODO: Figure out if this is an error
|
|
return GL_FALSE;
|
|
}
|
|
|
|
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) +
|
|
(int) CFArrayGetCount(_glfwJoysticks[joy].hats) * 4;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GL_FALSE;
|
|
}
|
|
|
|
|
|
//========================================================================
|
|
// Get joystick axis positions
|
|
//========================================================================
|
|
|
|
int _glfwPlatformGetJoystickAxes(int joy, float* axes, int numaxes)
|
|
{
|
|
int i;
|
|
|
|
if (joy < GLFW_JOYSTICK_1 || joy > GLFW_JOYSTICK_LAST)
|
|
return 0;
|
|
|
|
_glfwJoystick 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 (i = 0; i < numaxes; i++)
|
|
{
|
|
_glfwJoystickElement* elements =
|
|
(_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick.axes, i);
|
|
|
|
long readScale = elements->maxReport - elements->minReport;
|
|
|
|
if (readScale == 0)
|
|
axes[i] = elements->value;
|
|
else
|
|
axes[i] = (2.0f * (elements->value - elements->minReport) / readScale) - 1.0f;
|
|
|
|
if (i & 1)
|
|
axes[i] = -axes[i];
|
|
}
|
|
|
|
return numaxes;
|
|
}
|
|
|
|
|
|
//========================================================================
|
|
// Get joystick button states
|
|
//========================================================================
|
|
|
|
int _glfwPlatformGetJoystickButtons(int joy, unsigned char* buttons,
|
|
int numbuttons)
|
|
{
|
|
int i, j, button;
|
|
|
|
if (joy < GLFW_JOYSTICK_1 || joy > GLFW_JOYSTICK_LAST)
|
|
return 0;
|
|
|
|
_glfwJoystick joystick = _glfwJoysticks[joy];
|
|
|
|
if (!joystick.present)
|
|
{
|
|
// TODO: Figure out if this is an error
|
|
return 0;
|
|
}
|
|
|
|
// Update joystick state
|
|
pollJoystickEvents();
|
|
|
|
for (button = 0; button < numbuttons && button < joystick.numButtons; button++)
|
|
{
|
|
_glfwJoystickElement* element = (_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick.buttons, button);
|
|
buttons[button] = element->value ? GLFW_PRESS : GLFW_RELEASE;
|
|
}
|
|
|
|
// Virtual buttons - Inject data from hats
|
|
// Each hat is exposed as 4 buttons which exposes 8 directions with concurrent button presses
|
|
|
|
// Bit fields of button presses for each direction, including nil
|
|
const int directions[9] = { 1, 3, 2, 6, 4, 12, 8, 9, 0 };
|
|
|
|
for (i = 0; i < joystick.numHats; i++)
|
|
{
|
|
_glfwJoystickElement* hat = (_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick.hats, i);
|
|
|
|
int value = hat->value;
|
|
if (value < 0 || value > 8)
|
|
value = 8;
|
|
|
|
for (j = 0; j < 4 && button < numbuttons; j++)
|
|
{
|
|
if (directions[value] & (1 << j))
|
|
buttons[button] = GLFW_PRESS;
|
|
else
|
|
buttons[button] = GLFW_RELEASE;
|
|
|
|
button++;
|
|
}
|
|
}
|
|
|
|
return button;
|
|
}
|
|
|
|
|
|
//========================================================================
|
|
// Get joystick name
|
|
//========================================================================
|
|
|
|
const char* _glfwPlatformGetJoystickName(int joy)
|
|
{
|
|
return _glfwJoysticks[joy].name;
|
|
}
|
|
|