Commit bfd564f2 authored by Camilla Löwy's avatar Camilla Löwy
Browse files

Move internals to shared joystick objects

Preparation for gamecontrollerdb support and the gamepad API.
parent 43c19104
......@@ -32,25 +32,18 @@
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDKeys.h>
#define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE \
_GLFWjoystickNS ns_js[GLFW_JOYSTICK_LAST + 1]
#define _GLFW_PLATFORM_JOYSTICK_STATE _GLFWjoystickNS ns
#define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE
// Cocoa-specific per-joystick data
//
typedef struct _GLFWjoystickNS
{
GLFWbool present;
char name[256];
IOHIDDeviceRef deviceRef;
CFMutableArrayRef axisElements;
CFMutableArrayRef buttonElements;
CFMutableArrayRef hatElements;
float* axes;
unsigned char* buttons;
IOHIDDeviceRef device;
CFMutableArrayRef axes;
CFMutableArrayRef buttons;
CFMutableArrayRef hats;
} _GLFWjoystickNS;
......
......@@ -42,107 +42,25 @@
//
typedef struct _GLFWjoyelementNS
{
IOHIDElementRef elementRef;
long min;
long max;
long minReport;
long maxReport;
IOHIDElementRef native;
long minimum;
long maximum;
} _GLFWjoyelementNS;
static void getElementsCFArrayHandler(const void* value, void* parameter);
// Adds an element to the specified joystick
//
static void addJoystickElement(_GLFWjoystickNS* js,
IOHIDElementRef elementRef)
{
IOHIDElementType elementType;
long usagePage, usage;
CFMutableArrayRef elementsArray = NULL;
elementType = IOHIDElementGetType(elementRef);
usagePage = IOHIDElementGetUsagePage(elementRef);
usage = IOHIDElementGetUsage(elementRef);
if ((elementType != kIOHIDElementTypeInput_Axis) &&
(elementType != kIOHIDElementTypeInput_Button) &&
(elementType != kIOHIDElementTypeInput_Misc))
{
return;
}
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:
elementsArray = js->axisElements;
break;
case kHIDUsage_GD_Hatswitch:
elementsArray = js->hatElements;
break;
}
break;
}
case kHIDPage_Button:
elementsArray = js->buttonElements;
break;
default:
break;
}
if (elementsArray)
{
_GLFWjoyelementNS* element = calloc(1, sizeof(_GLFWjoyelementNS));
CFArrayAppendValue(elementsArray, element);
element->elementRef = elementRef;
element->minReport = IOHIDElementGetLogicalMin(elementRef);
element->maxReport = IOHIDElementGetLogicalMax(elementRef);
}
}
// Adds an element to the specified joystick
//
static void getElementsCFArrayHandler(const void* value, void* parameter)
{
if (CFGetTypeID(value) == IOHIDElementGetTypeID())
{
addJoystickElement((_GLFWjoystickNS*) parameter,
(IOHIDElementRef) value);
}
}
// Returns the value of the specified element of the specified joystick
//
static long getElementValue(_GLFWjoystickNS* js, _GLFWjoyelementNS* element)
static long getElementValue(_GLFWjoystick* js, _GLFWjoyelementNS* element)
{
IOReturn result = kIOReturnSuccess;
IOHIDValueRef valueRef;
long value = 0;
if (js && element && js->deviceRef)
if (js && element && js->ns.device)
{
result = IOHIDDeviceGetValue(js->deviceRef,
element->elementRef,
result = IOHIDDeviceGetValue(js->ns.device,
element->native,
&valueRef);
if (kIOReturnSuccess == result)
......@@ -150,10 +68,10 @@ static long getElementValue(_GLFWjoystickNS* js, _GLFWjoyelementNS* element)
value = IOHIDValueGetIntegerValue(valueRef);
// Record min and max for auto calibration
if (value < element->minReport)
element->minReport = value;
if (value > element->maxReport)
element->maxReport = value;
if (value < element->minimum)
element->minimum = value;
if (value > element->maximum)
element->maximum = value;
}
}
......@@ -163,105 +81,27 @@ static long getElementValue(_GLFWjoystickNS* js, _GLFWjoyelementNS* element)
// Removes the specified joystick
//
static void removeJoystick(_GLFWjoystickNS* js)
static void closeJoystick(_GLFWjoystick* js)
{
int i;
if (!js->present)
return;
for (i = 0; i < CFArrayGetCount(js->axisElements); i++)
free((void*) CFArrayGetValueAtIndex(js->axisElements, i));
CFArrayRemoveAllValues(js->axisElements);
CFRelease(js->axisElements);
for (i = 0; i < CFArrayGetCount(js->buttonElements); i++)
free((void*) CFArrayGetValueAtIndex(js->buttonElements, i));
CFArrayRemoveAllValues(js->buttonElements);
CFRelease(js->buttonElements);
for (i = 0; i < CFArrayGetCount(js->ns.axes); i++)
free((void*) CFArrayGetValueAtIndex(js->ns.axes, i));
CFRelease(js->ns.axes);
for (i = 0; i < CFArrayGetCount(js->hatElements); i++)
free((void*) CFArrayGetValueAtIndex(js->hatElements, i));
CFArrayRemoveAllValues(js->hatElements);
CFRelease(js->hatElements);
for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++)
free((void*) CFArrayGetValueAtIndex(js->ns.buttons, i));
CFRelease(js->ns.buttons);
free(js->axes);
free(js->buttons);
for (i = 0; i < CFArrayGetCount(js->ns.hats); i++)
free((void*) CFArrayGetValueAtIndex(js->ns.hats, i));
CFRelease(js->ns.hats);
memset(js, 0, sizeof(_GLFWjoystickNS));
_glfwInputJoystickChange(js - _glfw.ns_js, GLFW_DISCONNECTED);
}
// Polls for joystick axis events and updates GLFW state
//
static GLFWbool pollJoystickAxisEvents(_GLFWjoystickNS* js)
{
CFIndex i;
if (!js->present)
return GLFW_FALSE;
for (i = 0; i < CFArrayGetCount(js->axisElements); i++)
{
_GLFWjoyelementNS* axis = (_GLFWjoyelementNS*)
CFArrayGetValueAtIndex(js->axisElements, i);
long value = getElementValue(js, axis);
long readScale = axis->maxReport - axis->minReport;
if (readScale == 0)
js->axes[i] = value;
else
js->axes[i] = (2.f * (value - axis->minReport) / readScale) - 1.f;
}
return GLFW_TRUE;
}
// Polls for joystick button events and updates GLFW state
//
static GLFWbool pollJoystickButtonEvents(_GLFWjoystickNS* js)
{
CFIndex i;
int buttonIndex = 0;
if (!js->present)
return GLFW_FALSE;
for (i = 0; i < CFArrayGetCount(js->buttonElements); i++)
{
_GLFWjoyelementNS* button = (_GLFWjoyelementNS*)
CFArrayGetValueAtIndex(js->buttonElements, i);
if (getElementValue(js, button))
js->buttons[buttonIndex++] = GLFW_PRESS;
else
js->buttons[buttonIndex++] = GLFW_RELEASE;
}
for (i = 0; i < CFArrayGetCount(js->hatElements); i++)
{
_GLFWjoyelementNS* hat = (_GLFWjoyelementNS*)
CFArrayGetValueAtIndex(js->hatElements, i);
// Bit fields of button presses for each direction, including nil
const int directions[9] = { 1, 3, 2, 6, 4, 12, 8, 9, 0 };
long j, value = getElementValue(js, hat);
if (value < 0 || value > 8)
value = 8;
for (j = 0; j < 4; j++)
{
if (directions[value] & (1 << j))
js->buttons[buttonIndex++] = GLFW_PRESS;
else
js->buttons[buttonIndex++] = GLFW_RELEASE;
}
}
return GLFW_TRUE;
_glfwFreeJoystick(js);
_glfwInputJoystick(_GLFW_JOYSTICK_ID(js), GLFW_DISCONNECTED);
}
// Callback for user-initiated joystick addition
......@@ -269,62 +109,109 @@ static GLFWbool pollJoystickButtonEvents(_GLFWjoystickNS* js)
static void matchCallback(void* context,
IOReturn result,
void* sender,
IOHIDDeviceRef deviceRef)
IOHIDDeviceRef device)
{
_GLFWjoystickNS* js;
int jid;
char name[256];
CFIndex i;
CFStringRef productKey;
_GLFWjoystick* js;
CFMutableArrayRef axes, buttons, hats;
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
if (_glfw.ns_js[jid].present && _glfw.ns_js[jid].deviceRef == deviceRef)
if (_glfw.joysticks[jid].ns.device == device)
return;
}
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
if (!_glfw.ns_js[jid].present)
break;
}
axes = CFArrayCreateMutable(NULL, 0, NULL);
buttons = CFArrayCreateMutable(NULL, 0, NULL);
hats = CFArrayCreateMutable(NULL, 0, NULL);
if (jid > GLFW_JOYSTICK_LAST)
return;
js = _glfw.ns_js + jid;
js->present = GLFW_TRUE;
js->deviceRef = deviceRef;
CFStringRef name = IOHIDDeviceGetProperty(deviceRef,
CFSTR(kIOHIDProductKey));
if (name)
productKey = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
if (productKey)
{
CFStringGetCString(name,
js->name,
sizeof(js->name),
CFStringGetCString(productKey,
name,
sizeof(name),
kCFStringEncodingUTF8);
}
else
strncpy(js->name, "Unknown", sizeof(js->name));
strncpy(name, "Unknown", sizeof(name));
js->axisElements = CFArrayCreateMutable(NULL, 0, NULL);
js->buttonElements = CFArrayCreateMutable(NULL, 0, NULL);
js->hatElements = CFArrayCreateMutable(NULL, 0, NULL);
CFArrayRef elements =
IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
for (i = 0; i < CFArrayGetCount(elements); i++)
{
IOHIDElementRef native = (IOHIDElementRef) CFArrayGetValueAtIndex(elements, i);
if (CFGetTypeID(native) != IOHIDElementGetTypeID())
continue;
const IOHIDElementType type = IOHIDElementGetType(native);
if ((type != kIOHIDElementTypeInput_Axis) &&
(type != kIOHIDElementTypeInput_Button) &&
(type != kIOHIDElementTypeInput_Misc))
{
continue;
}
CFArrayRef arrayRef = IOHIDDeviceCopyMatchingElements(deviceRef,
NULL,
kIOHIDOptionsTypeNone);
CFRange range = { 0, CFArrayGetCount(arrayRef) };
CFArrayApplyFunction(arrayRef,
range,
getElementsCFArrayHandler,
(void*) js);
CFMutableArrayRef target = NULL;
CFRelease(arrayRef);
switch (IOHIDElementGetUsagePage(native))
{
case kHIDPage_GenericDesktop:
{
switch (IOHIDElementGetUsage(native))
{
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:
target = axes;
break;
case kHIDUsage_GD_Hatswitch:
target = hats;
break;
}
break;
}
case kHIDPage_Button:
target = buttons;
break;
default:
break;
}
if (target)
{
_GLFWjoyelementNS* element = calloc(1, sizeof(_GLFWjoyelementNS));
element->native = native;
element->minimum = IOHIDElementGetLogicalMin(native);
element->maximum = IOHIDElementGetLogicalMax(native);
CFArrayAppendValue(target, element);
}
}
js->axes = calloc(CFArrayGetCount(js->axisElements), sizeof(float));
js->buttons = calloc(CFArrayGetCount(js->buttonElements) +
CFArrayGetCount(js->hatElements) * 4, 1);
CFRelease(elements);
_glfwInputJoystickChange(jid, GLFW_CONNECTED);
js = _glfwAllocJoystick(name,
CFArrayGetCount(axes),
CFArrayGetCount(buttons) + CFArrayGetCount(hats) * 4);
js->ns.device = device;
js->ns.axes = axes;
js->ns.buttons = buttons;
js->ns.hats = hats;
_glfwInputJoystick(_GLFW_JOYSTICK_ID(js), GLFW_CONNECTED);
}
// Callback for user-initiated joystick removal
......@@ -332,15 +219,15 @@ static void matchCallback(void* context,
static void removeCallback(void* context,
IOReturn result,
void* sender,
IOHIDDeviceRef deviceRef)
IOHIDDeviceRef device)
{
int jid;
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
if (_glfw.ns_js[jid].deviceRef == deviceRef)
if (_glfw.joysticks[jid].ns.device == device)
{
removeJoystick(_glfw.ns_js + jid);
closeJoystick(_glfw.joysticks + jid);
break;
}
}
......@@ -459,10 +346,7 @@ void _glfwTerminateJoysticksNS(void)
int jid;
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
_GLFWjoystickNS* js = _glfw.ns_js + jid;
removeJoystick(js);
}
closeJoystick(_glfw.joysticks + jid);
CFRelease(_glfw.ns.hidManager);
_glfw.ns.hidManager = NULL;
......@@ -473,39 +357,60 @@ void _glfwTerminateJoysticksNS(void)
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformJoystickPresent(int jid)
int _glfwPlatformPollJoystick(int jid, int mode)
{
_GLFWjoystickNS* js = _glfw.ns_js + jid;
return js->present;
}
_GLFWjoystick* js = _glfw.joysticks + jid;
const float* _glfwPlatformGetJoystickAxes(int jid, int* count)
{
_GLFWjoystickNS* js = _glfw.ns_js + jid;
if (!pollJoystickAxisEvents(js))
return NULL;
if (mode == _GLFW_POLL_AXES)
{
CFIndex i;
*count = (int) CFArrayGetCount(js->axisElements);
return js->axes;
}
for (i = 0; i < CFArrayGetCount(js->ns.axes); i++)
{
_GLFWjoyelementNS* axis = (_GLFWjoyelementNS*)
CFArrayGetValueAtIndex(js->ns.axes, i);
const unsigned char* _glfwPlatformGetJoystickButtons(int jid, int* count)
{
_GLFWjoystickNS* js = _glfw.ns_js + jid;
if (!pollJoystickButtonEvents(js))
return NULL;
const long value = getElementValue(js, axis);
const long delta = axis->maximum - axis->minimum;
*count = (int) CFArrayGetCount(js->buttonElements) +
(int) CFArrayGetCount(js->hatElements) * 4;
return js->buttons;
}
if (delta == 0)
_glfwInputJoystickAxis(jid, i, value);
else
_glfwInputJoystickAxis(jid, i, (2.f * (value - axis->minimum) / delta) - 1.f);
}
}
else if (mode == _GLFW_POLL_BUTTONS)
{
CFIndex i, bi = 0;
const char* _glfwPlatformGetJoystickName(int jid)
{
_GLFWjoystickNS* js = _glfw.ns_js + jid;
if (!js->present)
return NULL;
for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++)
{
_GLFWjoyelementNS* button = (_GLFWjoyelementNS*)
CFArrayGetValueAtIndex(js->ns.buttons, i);
const char value = getElementValue(js, button) ? 1 : 0;
_glfwInputJoystickButton(jid, bi++, value);
}
for (i = 0; i < CFArrayGetCount(js->ns.hats); i++)
{
_GLFWjoyelementNS* hat = (_GLFWjoyelementNS*)
CFArrayGetValueAtIndex(js->ns.hats, i);
// Bit fields of button presses for each direction, including nil
const int directions[9] = { 1, 3, 2, 6, 4, 12, 8, 9, 0 };
return js->name;
long j, state = getElementValue(js, hat);
if (state < 0 || state > 8)
state = 8;
for (j = 0; j < 4; j++)
{
const char value = directions[state] & (1 << j) ? 1 : 0;
_glfwInputJoystickButton(jid, bi++, value);
}
}
}
return js->present;
}
......@@ -30,6 +30,7 @@
#include <assert.h>
#include <float.h>
#include <stdlib.h>
#include <string.h>
// Internal key state used for sticky keys
#define _GLFW_STICK 3
......@@ -123,12 +124,22 @@ void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths)
window->callbacks.drop((GLFWwindow*) window, count, paths);
}
void _glfwInputJoystickChange(int jid, int event)
void _glfwInputJoystick(int jid, int event)
{
if (_glfw.callbacks.joystick)
_glfw.callbacks.joystick(jid, event);
}
void _glfwInputJoystickAxis(int jid, int axis, float value)
{
_glfw.joysticks[jid].axes[axis] = value;
}
void _glfwInputJoystickButton(int jid, int button, char value)
{
_glfw.joysticks[jid].buttons[button] = value;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
......@@ -141,6 +152,39 @@ GLFWbool _glfwIsPrintable(int key)
key == GLFW_KEY_KP_EQUAL;
}
_GLFWjoystick* _glfwAllocJoystick(const char* name, int axisCount, int buttonCount)
{
int jid;
_GLFWjoystick* js;
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
if (!_glfw.joysticks[jid].present)
break;
}
if (jid > GLFW_JOYSTICK_LAST)
return NULL;
js = _glfw.joysticks + jid;
js->present = GLFW_TRUE;
js->name = strdup(name);
js->axes = calloc(axisCount, sizeof(float));
js->buttons = calloc(buttonCount, 1);
js->axisCount = axisCount;
js->buttonCount = buttonCount;
return js;
}
void _glfwFreeJoystick(_GLFWjoystick* js)
{
free(js->name);
free(js->axes);
free(js->buttons);
memset(js, 0, sizeof(_GLFWjoystick));
}
//////////////////////////////////////////////////////////////////////////
////// GLFW public API //////
......@@ -554,20 +598,29 @@ GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* handle, GLFWdropfun cbfun)
GLFWAPI int glfwJoystickPresent(int jid)
{
_GLFW_REQUIRE_INIT_OR_RETURN(0);
assert(jid >= GLFW_JOYSTICK_1);
assert(jid <= GLFW_JOYSTICK_LAST);
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
if (jid < 0 || jid > GLFW_JOYSTICK_LAST)
{
_glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick %i", jid);
return 0;
return GLFW_FALSE;
}
return _glfwPlatformJoystickPresent(jid);
if (!_glfw.joysticks[jid].present)
return GLFW_FALSE;
return _glfwPlatformPollJoystick(jid, _GLFW_POLL_PRESENCE);
}
GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count)
{
assert(jid >= GLFW_JOYSTICK_1);
assert(jid <= GLFW_JOYSTICK_LAST);
assert(count != NULL);
*count = 0;
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
......@@ -578,12 +631,22 @@ GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count)
return NULL;
}