//========================================================================
// 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.
#include "internal.h"
#if defined(_GLFW_COCOA)
#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)
if (fe->index > se->index)
return kCFCompareEqualTo;
// Removes the specified joystick
static void closeJoystick(_GLFWjoystick* js)
_glfwInputJoystick(js, GLFW_DISCONNECTED);
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);
// 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;
CFArrayRef elements =
IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
// It is reportedly possible for this to fail on macOS 13 Ventura
// if the application does not have input monitoring permissions
if (!elements)
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));
CFNumberGetValue(property, kCFNumberSInt32Type, &vendor);
property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
CFNumberGetValue(property, kCFNumberSInt32Type, &product);
property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVersionNumberKey));
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));
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]);
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))
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;
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;
else if (page == kHIDPage_Simulation)
case kHIDUsage_Sim_Accelerator:
case kHIDUsage_Sim_Brake:
case kHIDUsage_Sim_Throttle:
case kHIDUsage_Sim_Rudder:
case kHIDUsage_Sim_Steering:
else if (page == kHIDPage_Button || page == kHIDPage_Consumer)
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)),
CFArraySortValues(hats, CFRangeMake(0, CFArrayGetCount(hats)),
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,
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
if (_glfw.joysticks[jid].connected && _glfw.joysticks[jid].ns.device == device)
closeJoystick(&_glfw.joysticks[jid]);
//////////////////////////////////////////////////////////////////////////
////// 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,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!dict)
CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault,
kCFNumberLongType,
&page);
CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault,
&usages[i]);
if (pageRef && usageRef)
CFDictionarySetValue(dict,
CFSTR(kIOHIDDeviceUsagePageKey),
pageRef);
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)
if (_glfw.joysticks[jid].connected)
if (_glfw.ns.hidManager)
CFRelease(_glfw.ns.hidManager);
_glfw.ns.hidManager = NULL;
GLFWbool _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);
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->connected;
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);
#endif // _GLFW_COCOA