1 //========================================================================
2 // GLFW 3.4 Cocoa - www.glfw.org
3 //------------------------------------------------------------------------
4 // Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org>
5 // Copyright (c) 2012 Torsten Walluhn <tw@mad-cad.net>
7 // This software is provided 'as-is', without any express or implied
8 // warranty. In no event will the authors be held liable for any damages
9 // arising from the use of this software.
11 // Permission is granted to anyone to use this software for any purpose,
12 // including commercial applications, and to alter it and redistribute it
13 // freely, subject to the following restrictions:
15 // 1. The origin of this software must not be misrepresented; you must not
16 // claim that you wrote the original software. If you use this software
17 // in a product, an acknowledgment in the product documentation would
18 // be appreciated but is not required.
20 // 2. Altered source versions must be plainly marked as such, and must not
21 // be misrepresented as being the original software.
23 // 3. This notice may not be removed or altered from any source
26 //========================================================================
27 // It is fine to use C99 in this file because it will not be built with VS
28 //========================================================================
36 #include <mach/mach.h>
37 #include <mach/mach_error.h>
39 #include <CoreFoundation/CoreFoundation.h>
40 #include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
43 // Joystick element information
45 typedef struct _GLFWjoyelementNS
47 IOHIDElementRef native;
56 // Returns the value of the specified element of the specified joystick
58 static long getElementValue(_GLFWjoystick* js, _GLFWjoyelementNS* element)
60 IOHIDValueRef valueRef;
65 if (IOHIDDeviceGetValue(js->ns.device,
67 &valueRef) == kIOReturnSuccess)
69 value = IOHIDValueGetIntegerValue(valueRef);
76 // Comparison function for matching the SDL element order
78 static CFComparisonResult compareElements(const void* fp,
82 const _GLFWjoyelementNS* fe = fp;
83 const _GLFWjoyelementNS* se = sp;
84 if (fe->usage < se->usage)
85 return kCFCompareLessThan;
86 if (fe->usage > se->usage)
87 return kCFCompareGreaterThan;
88 if (fe->index < se->index)
89 return kCFCompareLessThan;
90 if (fe->index > se->index)
91 return kCFCompareGreaterThan;
92 return kCFCompareEqualTo;
95 // Removes the specified joystick
97 static void closeJoystick(_GLFWjoystick* js)
104 for (i = 0; i < CFArrayGetCount(js->ns.axes); i++)
105 free((void*) CFArrayGetValueAtIndex(js->ns.axes, i));
106 CFRelease(js->ns.axes);
108 for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++)
109 free((void*) CFArrayGetValueAtIndex(js->ns.buttons, i));
110 CFRelease(js->ns.buttons);
112 for (i = 0; i < CFArrayGetCount(js->ns.hats); i++)
113 free((void*) CFArrayGetValueAtIndex(js->ns.hats, i));
114 CFRelease(js->ns.hats);
116 _glfwFreeJoystick(js);
117 _glfwInputJoystick(js, GLFW_DISCONNECTED);
120 // Callback for user-initiated joystick addition
122 static void matchCallback(void* context,
125 IOHIDDeviceRef device)
132 uint32_t vendor = 0, product = 0, version = 0;
134 CFMutableArrayRef axes, buttons, hats;
136 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
138 if (_glfw.joysticks[jid].ns.device == device)
142 axes = CFArrayCreateMutable(NULL, 0, NULL);
143 buttons = CFArrayCreateMutable(NULL, 0, NULL);
144 hats = CFArrayCreateMutable(NULL, 0, NULL);
146 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
149 CFStringGetCString(property,
152 kCFStringEncodingUTF8);
155 strncpy(name, "Unknown", sizeof(name));
157 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
159 CFNumberGetValue(property, kCFNumberSInt32Type, &vendor);
161 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
163 CFNumberGetValue(property, kCFNumberSInt32Type, &product);
165 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVersionNumberKey));
167 CFNumberGetValue(property, kCFNumberSInt32Type, &version);
169 // Generate a joystick GUID that matches the SDL 2.0.5+ one
170 if (vendor && product)
172 sprintf(guid, "03000000%02x%02x0000%02x%02x0000%02x%02x0000",
173 (uint8_t) vendor, (uint8_t) (vendor >> 8),
174 (uint8_t) product, (uint8_t) (product >> 8),
175 (uint8_t) version, (uint8_t) (version >> 8));
179 sprintf(guid, "05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00",
180 name[0], name[1], name[2], name[3],
181 name[4], name[5], name[6], name[7],
182 name[8], name[9], name[10]);
185 CFArrayRef elements =
186 IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
188 for (i = 0; i < CFArrayGetCount(elements); i++)
190 IOHIDElementRef native = (IOHIDElementRef)
191 CFArrayGetValueAtIndex(elements, i);
192 if (CFGetTypeID(native) != IOHIDElementGetTypeID())
195 const IOHIDElementType type = IOHIDElementGetType(native);
196 if ((type != kIOHIDElementTypeInput_Axis) &&
197 (type != kIOHIDElementTypeInput_Button) &&
198 (type != kIOHIDElementTypeInput_Misc))
203 CFMutableArrayRef target = NULL;
205 const uint32_t usage = IOHIDElementGetUsage(native);
206 const uint32_t page = IOHIDElementGetUsagePage(native);
207 if (page == kHIDPage_GenericDesktop)
214 case kHIDUsage_GD_Rx:
215 case kHIDUsage_GD_Ry:
216 case kHIDUsage_GD_Rz:
217 case kHIDUsage_GD_Slider:
218 case kHIDUsage_GD_Dial:
219 case kHIDUsage_GD_Wheel:
222 case kHIDUsage_GD_Hatswitch:
225 case kHIDUsage_GD_DPadUp:
226 case kHIDUsage_GD_DPadRight:
227 case kHIDUsage_GD_DPadDown:
228 case kHIDUsage_GD_DPadLeft:
229 case kHIDUsage_GD_SystemMainMenu:
230 case kHIDUsage_GD_Select:
231 case kHIDUsage_GD_Start:
236 else if (page == kHIDPage_Simulation)
240 case kHIDUsage_Sim_Accelerator:
241 case kHIDUsage_Sim_Brake:
242 case kHIDUsage_Sim_Throttle:
243 case kHIDUsage_Sim_Rudder:
244 case kHIDUsage_Sim_Steering:
249 else if (page == kHIDPage_Button || page == kHIDPage_Consumer)
254 _GLFWjoyelementNS* element = calloc(1, sizeof(_GLFWjoyelementNS));
255 element->native = native;
256 element->usage = usage;
257 element->index = (int) CFArrayGetCount(target);
258 element->minimum = IOHIDElementGetLogicalMin(native);
259 element->maximum = IOHIDElementGetLogicalMax(native);
260 CFArrayAppendValue(target, element);
266 CFArraySortValues(axes, CFRangeMake(0, CFArrayGetCount(axes)),
267 compareElements, NULL);
268 CFArraySortValues(buttons, CFRangeMake(0, CFArrayGetCount(buttons)),
269 compareElements, NULL);
270 CFArraySortValues(hats, CFRangeMake(0, CFArrayGetCount(hats)),
271 compareElements, NULL);
273 js = _glfwAllocJoystick(name, guid,
274 (int) CFArrayGetCount(axes),
275 (int) CFArrayGetCount(buttons),
276 (int) CFArrayGetCount(hats));
278 js->ns.device = device;
280 js->ns.buttons = buttons;
283 _glfwInputJoystick(js, GLFW_CONNECTED);
286 // Callback for user-initiated joystick removal
288 static void removeCallback(void* context,
291 IOHIDDeviceRef device)
295 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
297 if (_glfw.joysticks[jid].ns.device == device)
299 closeJoystick(_glfw.joysticks + jid);
306 //////////////////////////////////////////////////////////////////////////
307 ////// GLFW platform API //////
308 //////////////////////////////////////////////////////////////////////////
310 GLFWbool _glfwPlatformInitJoysticks(void)
312 CFMutableArrayRef matching;
313 const long usages[] =
315 kHIDUsage_GD_Joystick,
316 kHIDUsage_GD_GamePad,
317 kHIDUsage_GD_MultiAxisController
320 _glfw.ns.hidManager = IOHIDManagerCreate(kCFAllocatorDefault,
321 kIOHIDOptionsTypeNone);
323 matching = CFArrayCreateMutable(kCFAllocatorDefault,
325 &kCFTypeArrayCallBacks);
328 _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array");
332 for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++)
334 const long page = kHIDPage_GenericDesktop;
336 CFMutableDictionaryRef dict =
337 CFDictionaryCreateMutable(kCFAllocatorDefault,
339 &kCFTypeDictionaryKeyCallBacks,
340 &kCFTypeDictionaryValueCallBacks);
344 CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault,
347 CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault,
350 if (pageRef && usageRef)
352 CFDictionarySetValue(dict,
353 CFSTR(kIOHIDDeviceUsagePageKey),
355 CFDictionarySetValue(dict,
356 CFSTR(kIOHIDDeviceUsageKey),
358 CFArrayAppendValue(matching, dict);
369 IOHIDManagerSetDeviceMatchingMultiple(_glfw.ns.hidManager, matching);
372 IOHIDManagerRegisterDeviceMatchingCallback(_glfw.ns.hidManager,
373 &matchCallback, NULL);
374 IOHIDManagerRegisterDeviceRemovalCallback(_glfw.ns.hidManager,
375 &removeCallback, NULL);
376 IOHIDManagerScheduleWithRunLoop(_glfw.ns.hidManager,
378 kCFRunLoopDefaultMode);
379 IOHIDManagerOpen(_glfw.ns.hidManager, kIOHIDOptionsTypeNone);
381 // Execute the run loop once in order to register any initially-attached
383 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
387 void _glfwPlatformTerminateJoysticks(void)
391 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
392 closeJoystick(_glfw.joysticks + jid);
394 if (_glfw.ns.hidManager)
396 CFRelease(_glfw.ns.hidManager);
397 _glfw.ns.hidManager = NULL;
402 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
404 if (mode & _GLFW_POLL_AXES)
408 for (i = 0; i < CFArrayGetCount(js->ns.axes); i++)
410 _GLFWjoyelementNS* axis = (_GLFWjoyelementNS*)
411 CFArrayGetValueAtIndex(js->ns.axes, i);
413 const long raw = getElementValue(js, axis);
414 // Perform auto calibration
415 if (raw < axis->minimum)
417 if (raw > axis->maximum)
420 const long size = axis->maximum - axis->minimum;
422 _glfwInputJoystickAxis(js, (int) i, 0.f);
425 const float value = (2.f * (raw - axis->minimum) / size) - 1.f;
426 _glfwInputJoystickAxis(js, (int) i, value);
431 if (mode & _GLFW_POLL_BUTTONS)
435 for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++)
437 _GLFWjoyelementNS* button = (_GLFWjoyelementNS*)
438 CFArrayGetValueAtIndex(js->ns.buttons, i);
439 const char value = getElementValue(js, button) - button->minimum;
440 const int state = (value > 0) ? GLFW_PRESS : GLFW_RELEASE;
441 _glfwInputJoystickButton(js, (int) i, state);
444 for (i = 0; i < CFArrayGetCount(js->ns.hats); i++)
446 const int states[9] =
459 _GLFWjoyelementNS* hat = (_GLFWjoyelementNS*)
460 CFArrayGetValueAtIndex(js->ns.hats, i);
461 long state = getElementValue(js, hat) - hat->minimum;
462 if (state < 0 || state > 8)
465 _glfwInputJoystickHat(js, (int) i, states[state]);
472 void _glfwPlatformUpdateGamepadGUID(char* guid)
474 if ((strncmp(guid + 4, "000000000000", 12) == 0) &&
475 (strncmp(guid + 20, "000000000000", 12) == 0))
478 strncpy(original, guid, sizeof(original) - 1);
479 sprintf(guid, "03000000%.4s0000%.4s000000000000",
480 original, original + 16);