You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
477 lines
15 KiB
477 lines
15 KiB
//======================================================================== |
|
// GLFW 3.4 Cocoa - www.glfw.org |
|
//------------------------------------------------------------------------ |
|
// Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.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. |
|
// |
|
//======================================================================== |
|
// It is fine to use C99 in this file because it will not be built with VS |
|
//======================================================================== |
|
|
|
#include "internal.h" |
|
|
|
#include <unistd.h> |
|
#include <ctype.h> |
|
#include <string.h> |
|
|
|
#include <mach/mach.h> |
|
#include <mach/mach_error.h> |
|
|
|
#include <CoreFoundation/CoreFoundation.h> |
|
#include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h> |
|
|
|
|
|
// Joystick element information |
|
// |
|
typedef struct _GLFWjoyelementNS |
|
{ |
|
IOHIDElementRef native; |
|
uint32_t usage; |
|
int index; |
|
long minimum; |
|
long maximum; |
|
|
|
} _GLFWjoyelementNS; |
|
|
|
|
|
// Returns the value of the specified element of the specified joystick |
|
// |
|
static long getElementValue(_GLFWjoystick* js, _GLFWjoyelementNS* element) |
|
{ |
|
IOHIDValueRef valueRef; |
|
long value = 0; |
|
|
|
if (js->ns.device) |
|
{ |
|
if (IOHIDDeviceGetValue(js->ns.device, |
|
element->native, |
|
&valueRef) == kIOReturnSuccess) |
|
{ |
|
value = IOHIDValueGetIntegerValue(valueRef); |
|
} |
|
} |
|
|
|
return value; |
|
} |
|
|
|
// Comparison function for matching the SDL element order |
|
// |
|
static CFComparisonResult compareElements(const void* fp, |
|
const void* sp, |
|
void* user) |
|
{ |
|
const _GLFWjoyelementNS* fe = fp; |
|
const _GLFWjoyelementNS* se = sp; |
|
if (fe->usage < se->usage) |
|
return kCFCompareLessThan; |
|
if (fe->usage > se->usage) |
|
return kCFCompareGreaterThan; |
|
if (fe->index < se->index) |
|
return kCFCompareLessThan; |
|
if (fe->index > se->index) |
|
return kCFCompareGreaterThan; |
|
return kCFCompareEqualTo; |
|
} |
|
|
|
// Removes the specified joystick |
|
// |
|
static void closeJoystick(_GLFWjoystick* js) |
|
{ |
|
if (!js->present) |
|
return; |
|
|
|
for (int i = 0; i < CFArrayGetCount(js->ns.axes); i++) |
|
_glfw_free((void*) CFArrayGetValueAtIndex(js->ns.axes, i)); |
|
CFRelease(js->ns.axes); |
|
|
|
for (int i = 0; i < CFArrayGetCount(js->ns.buttons); i++) |
|
_glfw_free((void*) CFArrayGetValueAtIndex(js->ns.buttons, i)); |
|
CFRelease(js->ns.buttons); |
|
|
|
for (int i = 0; i < CFArrayGetCount(js->ns.hats); i++) |
|
_glfw_free((void*) CFArrayGetValueAtIndex(js->ns.hats, i)); |
|
CFRelease(js->ns.hats); |
|
|
|
_glfwFreeJoystick(js); |
|
_glfwInputJoystick(js, GLFW_DISCONNECTED); |
|
} |
|
|
|
// Callback for user-initiated joystick addition |
|
// |
|
static void matchCallback(void* context, |
|
IOReturn result, |
|
void* sender, |
|
IOHIDDeviceRef device) |
|
{ |
|
int jid; |
|
char name[256]; |
|
char guid[33]; |
|
CFTypeRef property; |
|
uint32_t vendor = 0, product = 0, version = 0; |
|
_GLFWjoystick* js; |
|
CFMutableArrayRef axes, buttons, hats; |
|
|
|
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) |
|
{ |
|
if (_glfw.joysticks[jid].ns.device == device) |
|
return; |
|
} |
|
|
|
axes = CFArrayCreateMutable(NULL, 0, NULL); |
|
buttons = CFArrayCreateMutable(NULL, 0, NULL); |
|
hats = CFArrayCreateMutable(NULL, 0, NULL); |
|
|
|
property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); |
|
if (property) |
|
{ |
|
CFStringGetCString(property, |
|
name, |
|
sizeof(name), |
|
kCFStringEncodingUTF8); |
|
} |
|
else |
|
strncpy(name, "Unknown", sizeof(name)); |
|
|
|
property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)); |
|
if (property) |
|
CFNumberGetValue(property, kCFNumberSInt32Type, &vendor); |
|
|
|
property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)); |
|
if (property) |
|
CFNumberGetValue(property, kCFNumberSInt32Type, &product); |
|
|
|
property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVersionNumberKey)); |
|
if (property) |
|
CFNumberGetValue(property, kCFNumberSInt32Type, &version); |
|
|
|
// Generate a joystick GUID that matches the SDL 2.0.5+ one |
|
if (vendor && product) |
|
{ |
|
sprintf(guid, "03000000%02x%02x0000%02x%02x0000%02x%02x0000", |
|
(uint8_t) vendor, (uint8_t) (vendor >> 8), |
|
(uint8_t) product, (uint8_t) (product >> 8), |
|
(uint8_t) version, (uint8_t) (version >> 8)); |
|
} |
|
else |
|
{ |
|
sprintf(guid, "05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00", |
|
name[0], name[1], name[2], name[3], |
|
name[4], name[5], name[6], name[7], |
|
name[8], name[9], name[10]); |
|
} |
|
|
|
CFArrayRef elements = |
|
IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); |
|
|
|
for (CFIndex 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; |
|
} |
|
|
|
CFMutableArrayRef target = NULL; |
|
|
|
const uint32_t usage = IOHIDElementGetUsage(native); |
|
const uint32_t page = IOHIDElementGetUsagePage(native); |
|
if (page == 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: |
|
target = axes; |
|
break; |
|
case kHIDUsage_GD_Hatswitch: |
|
target = hats; |
|
break; |
|
case kHIDUsage_GD_DPadUp: |
|
case kHIDUsage_GD_DPadRight: |
|
case kHIDUsage_GD_DPadDown: |
|
case kHIDUsage_GD_DPadLeft: |
|
case kHIDUsage_GD_SystemMainMenu: |
|
case kHIDUsage_GD_Select: |
|
case kHIDUsage_GD_Start: |
|
target = buttons; |
|
break; |
|
} |
|
} |
|
else if (page == kHIDPage_Simulation) |
|
{ |
|
switch (usage) |
|
{ |
|
case kHIDUsage_Sim_Accelerator: |
|
case kHIDUsage_Sim_Brake: |
|
case kHIDUsage_Sim_Throttle: |
|
case kHIDUsage_Sim_Rudder: |
|
case kHIDUsage_Sim_Steering: |
|
target = axes; |
|
break; |
|
} |
|
} |
|
else if (page == kHIDPage_Button || page == kHIDPage_Consumer) |
|
target = buttons; |
|
|
|
if (target) |
|
{ |
|
_GLFWjoyelementNS* element = _glfw_calloc(1, sizeof(_GLFWjoyelementNS)); |
|
element->native = native; |
|
element->usage = usage; |
|
element->index = (int) CFArrayGetCount(target); |
|
element->minimum = IOHIDElementGetLogicalMin(native); |
|
element->maximum = IOHIDElementGetLogicalMax(native); |
|
CFArrayAppendValue(target, element); |
|
} |
|
} |
|
|
|
CFRelease(elements); |
|
|
|
CFArraySortValues(axes, CFRangeMake(0, CFArrayGetCount(axes)), |
|
compareElements, NULL); |
|
CFArraySortValues(buttons, CFRangeMake(0, CFArrayGetCount(buttons)), |
|
compareElements, NULL); |
|
CFArraySortValues(hats, CFRangeMake(0, CFArrayGetCount(hats)), |
|
compareElements, NULL); |
|
|
|
js = _glfwAllocJoystick(name, guid, |
|
(int) CFArrayGetCount(axes), |
|
(int) CFArrayGetCount(buttons), |
|
(int) CFArrayGetCount(hats)); |
|
|
|
js->ns.device = device; |
|
js->ns.axes = axes; |
|
js->ns.buttons = buttons; |
|
js->ns.hats = hats; |
|
|
|
_glfwInputJoystick(js, GLFW_CONNECTED); |
|
} |
|
|
|
// Callback for user-initiated joystick removal |
|
// |
|
static void removeCallback(void* context, |
|
IOReturn result, |
|
void* sender, |
|
IOHIDDeviceRef device) |
|
{ |
|
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) |
|
{ |
|
if (_glfw.joysticks[jid].ns.device == device) |
|
{ |
|
closeJoystick(_glfw.joysticks + jid); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
////////////////////////////////////////////////////////////////////////// |
|
////// GLFW platform API ////// |
|
////////////////////////////////////////////////////////////////////////// |
|
|
|
GLFWbool _glfwInitJoysticksCocoa(void) |
|
{ |
|
CFMutableArrayRef matching; |
|
const long usages[] = |
|
{ |
|
kHIDUsage_GD_Joystick, |
|
kHIDUsage_GD_GamePad, |
|
kHIDUsage_GD_MultiAxisController |
|
}; |
|
|
|
_glfw.ns.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, |
|
kIOHIDOptionsTypeNone); |
|
|
|
matching = CFArrayCreateMutable(kCFAllocatorDefault, |
|
0, |
|
&kCFTypeArrayCallBacks); |
|
if (!matching) |
|
{ |
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array"); |
|
return GLFW_FALSE; |
|
} |
|
|
|
for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++) |
|
{ |
|
const long page = kHIDPage_GenericDesktop; |
|
|
|
CFMutableDictionaryRef dict = |
|
CFDictionaryCreateMutable(kCFAllocatorDefault, |
|
0, |
|
&kCFTypeDictionaryKeyCallBacks, |
|
&kCFTypeDictionaryValueCallBacks); |
|
if (!dict) |
|
continue; |
|
|
|
CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault, |
|
kCFNumberLongType, |
|
&page); |
|
CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault, |
|
kCFNumberLongType, |
|
&usages[i]); |
|
if (pageRef && usageRef) |
|
{ |
|
CFDictionarySetValue(dict, |
|
CFSTR(kIOHIDDeviceUsagePageKey), |
|
pageRef); |
|
CFDictionarySetValue(dict, |
|
CFSTR(kIOHIDDeviceUsageKey), |
|
usageRef); |
|
CFArrayAppendValue(matching, dict); |
|
} |
|
|
|
if (pageRef) |
|
CFRelease(pageRef); |
|
if (usageRef) |
|
CFRelease(usageRef); |
|
|
|
CFRelease(dict); |
|
} |
|
|
|
IOHIDManagerSetDeviceMatchingMultiple(_glfw.ns.hidManager, matching); |
|
CFRelease(matching); |
|
|
|
IOHIDManagerRegisterDeviceMatchingCallback(_glfw.ns.hidManager, |
|
&matchCallback, NULL); |
|
IOHIDManagerRegisterDeviceRemovalCallback(_glfw.ns.hidManager, |
|
&removeCallback, NULL); |
|
IOHIDManagerScheduleWithRunLoop(_glfw.ns.hidManager, |
|
CFRunLoopGetMain(), |
|
kCFRunLoopDefaultMode); |
|
IOHIDManagerOpen(_glfw.ns.hidManager, kIOHIDOptionsTypeNone); |
|
|
|
// Execute the run loop once in order to register any initially-attached |
|
// joysticks |
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); |
|
return GLFW_TRUE; |
|
} |
|
|
|
void _glfwTerminateJoysticksCocoa(void) |
|
{ |
|
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) |
|
closeJoystick(_glfw.joysticks + jid); |
|
|
|
if (_glfw.ns.hidManager) |
|
{ |
|
CFRelease(_glfw.ns.hidManager); |
|
_glfw.ns.hidManager = NULL; |
|
} |
|
} |
|
|
|
|
|
int _glfwPollJoystickCocoa(_GLFWjoystick* js, int mode) |
|
{ |
|
if (mode & _GLFW_POLL_AXES) |
|
{ |
|
for (CFIndex i = 0; i < CFArrayGetCount(js->ns.axes); i++) |
|
{ |
|
_GLFWjoyelementNS* axis = (_GLFWjoyelementNS*) |
|
CFArrayGetValueAtIndex(js->ns.axes, i); |
|
|
|
const long raw = getElementValue(js, axis); |
|
// Perform auto calibration |
|
if (raw < axis->minimum) |
|
axis->minimum = raw; |
|
if (raw > axis->maximum) |
|
axis->maximum = raw; |
|
|
|
const long size = axis->maximum - axis->minimum; |
|
if (size == 0) |
|
_glfwInputJoystickAxis(js, (int) i, 0.f); |
|
else |
|
{ |
|
const float value = (2.f * (raw - axis->minimum) / size) - 1.f; |
|
_glfwInputJoystickAxis(js, (int) i, value); |
|
} |
|
} |
|
} |
|
|
|
if (mode & _GLFW_POLL_BUTTONS) |
|
{ |
|
for (CFIndex i = 0; i < CFArrayGetCount(js->ns.buttons); i++) |
|
{ |
|
_GLFWjoyelementNS* button = (_GLFWjoyelementNS*) |
|
CFArrayGetValueAtIndex(js->ns.buttons, i); |
|
const char value = getElementValue(js, button) - button->minimum; |
|
const int state = (value > 0) ? GLFW_PRESS : GLFW_RELEASE; |
|
_glfwInputJoystickButton(js, (int) i, state); |
|
} |
|
|
|
for (CFIndex i = 0; i < CFArrayGetCount(js->ns.hats); i++) |
|
{ |
|
const int states[9] = |
|
{ |
|
GLFW_HAT_UP, |
|
GLFW_HAT_RIGHT_UP, |
|
GLFW_HAT_RIGHT, |
|
GLFW_HAT_RIGHT_DOWN, |
|
GLFW_HAT_DOWN, |
|
GLFW_HAT_LEFT_DOWN, |
|
GLFW_HAT_LEFT, |
|
GLFW_HAT_LEFT_UP, |
|
GLFW_HAT_CENTERED |
|
}; |
|
|
|
_GLFWjoyelementNS* hat = (_GLFWjoyelementNS*) |
|
CFArrayGetValueAtIndex(js->ns.hats, i); |
|
long state = getElementValue(js, hat) - hat->minimum; |
|
if (state < 0 || state > 8) |
|
state = 8; |
|
|
|
_glfwInputJoystickHat(js, (int) i, states[state]); |
|
} |
|
} |
|
|
|
return js->present; |
|
} |
|
|
|
const char* _glfwGetMappingNameCocoa(void) |
|
{ |
|
return "Mac OS X"; |
|
} |
|
|
|
void _glfwUpdateGamepadGUIDCocoa(char* guid) |
|
{ |
|
if ((strncmp(guid + 4, "000000000000", 12) == 0) && |
|
(strncmp(guid + 20, "000000000000", 12) == 0)) |
|
{ |
|
char original[33]; |
|
strncpy(original, guid, sizeof(original) - 1); |
|
sprintf(guid, "03000000%.4s0000%.4s000000000000", |
|
original, original + 16); |
|
} |
|
} |
|
|
|
|