• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//========================================================================
2// GLFW 3.5 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>
6//
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.
10//
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:
14//
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.
19//
20// 2. Altered source versions must be plainly marked as such, and must not
21//    be misrepresented as being the original software.
22//
23// 3. This notice may not be removed or altered from any source
24//    distribution.
25//
26//========================================================================
27
28#include "internal.h"
29
30#if defined(_GLFW_COCOA)
31
32#include <unistd.h>
33#include <ctype.h>
34#include <string.h>
35
36#include <mach/mach.h>
37#include <mach/mach_error.h>
38
39#include <CoreFoundation/CoreFoundation.h>
40#include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
41
42
43// Joystick element information
44//
45typedef struct _GLFWjoyelementNS
46{
47    IOHIDElementRef native;
48    uint32_t        usage;
49    int             index;
50    long            minimum;
51    long            maximum;
52
53} _GLFWjoyelementNS;
54
55
56// Returns the value of the specified element of the specified joystick
57//
58static long getElementValue(_GLFWjoystick* js, _GLFWjoyelementNS* element)
59{
60    IOHIDValueRef valueRef;
61    long value = 0;
62
63    if (js->ns.device)
64    {
65        if (IOHIDDeviceGetValue(js->ns.device,
66                                element->native,
67                                &valueRef) == kIOReturnSuccess)
68        {
69            value = IOHIDValueGetIntegerValue(valueRef);
70        }
71    }
72
73    return value;
74}
75
76// Comparison function for matching the SDL element order
77//
78static CFComparisonResult compareElements(const void* fp,
79                                          const void* sp,
80                                          void* user)
81{
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;
93}
94
95// Removes the specified joystick
96//
97static void closeJoystick(_GLFWjoystick* js)
98{
99    _glfwInputJoystick(js, GLFW_DISCONNECTED);
100
101    for (int i = 0;  i < CFArrayGetCount(js->ns.axes);  i++)
102        _glfw_free((void*) CFArrayGetValueAtIndex(js->ns.axes, i));
103    CFRelease(js->ns.axes);
104
105    for (int i = 0;  i < CFArrayGetCount(js->ns.buttons);  i++)
106        _glfw_free((void*) CFArrayGetValueAtIndex(js->ns.buttons, i));
107    CFRelease(js->ns.buttons);
108
109    for (int i = 0;  i < CFArrayGetCount(js->ns.hats);  i++)
110        _glfw_free((void*) CFArrayGetValueAtIndex(js->ns.hats, i));
111    CFRelease(js->ns.hats);
112
113    _glfwFreeJoystick(js);
114}
115
116// Callback for user-initiated joystick addition
117//
118static void matchCallback(void* context,
119                          IOReturn result,
120                          void* sender,
121                          IOHIDDeviceRef device)
122{
123    int jid;
124    char name[256];
125    char guid[33];
126    CFTypeRef property;
127    uint32_t vendor = 0, product = 0, version = 0;
128    _GLFWjoystick* js;
129    CFMutableArrayRef axes, buttons, hats;
130
131    for (jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
132    {
133        if (_glfw.joysticks[jid].ns.device == device)
134            return;
135    }
136
137    CFArrayRef elements =
138        IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
139
140    // It is reportedly possible for this to fail on macOS 13 Ventura
141    // if the application does not have input monitoring permissions
142    if (!elements)
143        return;
144
145    axes    = CFArrayCreateMutable(NULL, 0, NULL);
146    buttons = CFArrayCreateMutable(NULL, 0, NULL);
147    hats    = CFArrayCreateMutable(NULL, 0, NULL);
148
149    property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
150    if (property)
151    {
152        CFStringGetCString(property,
153                           name,
154                           sizeof(name),
155                           kCFStringEncodingUTF8);
156    }
157    else
158        strncpy(name, "Unknown", sizeof(name));
159
160    property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
161    if (property)
162        CFNumberGetValue(property, kCFNumberSInt32Type, &vendor);
163
164    property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
165    if (property)
166        CFNumberGetValue(property, kCFNumberSInt32Type, &product);
167
168    property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVersionNumberKey));
169    if (property)
170        CFNumberGetValue(property, kCFNumberSInt32Type, &version);
171
172    // Generate a joystick GUID that matches the SDL 2.0.5+ one
173    if (vendor && product)
174    {
175        sprintf(guid, "03000000%02x%02x0000%02x%02x0000%02x%02x0000",
176                (uint8_t) vendor, (uint8_t) (vendor >> 8),
177                (uint8_t) product, (uint8_t) (product >> 8),
178                (uint8_t) version, (uint8_t) (version >> 8));
179    }
180    else
181    {
182        sprintf(guid, "05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00",
183                name[0], name[1], name[2], name[3],
184                name[4], name[5], name[6], name[7],
185                name[8], name[9], name[10]);
186    }
187
188    for (CFIndex i = 0;  i < CFArrayGetCount(elements);  i++)
189    {
190        IOHIDElementRef native = (IOHIDElementRef)
191            CFArrayGetValueAtIndex(elements, i);
192        if (CFGetTypeID(native) != IOHIDElementGetTypeID())
193            continue;
194
195        const IOHIDElementType type = IOHIDElementGetType(native);
196        if ((type != kIOHIDElementTypeInput_Axis) &&
197            (type != kIOHIDElementTypeInput_Button) &&
198            (type != kIOHIDElementTypeInput_Misc))
199        {
200            continue;
201        }
202
203        CFMutableArrayRef target = NULL;
204
205        const uint32_t usage = IOHIDElementGetUsage(native);
206        const uint32_t page = IOHIDElementGetUsagePage(native);
207        if (page == kHIDPage_GenericDesktop)
208        {
209            switch (usage)
210            {
211                case kHIDUsage_GD_X:
212                case kHIDUsage_GD_Y:
213                case kHIDUsage_GD_Z:
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:
220                    target = axes;
221                    break;
222                case kHIDUsage_GD_Hatswitch:
223                    target = hats;
224                    break;
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:
232                    target = buttons;
233                    break;
234            }
235        }
236        else if (page == kHIDPage_Simulation)
237        {
238            switch (usage)
239            {
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:
245                    target = axes;
246                    break;
247            }
248        }
249        else if (page == kHIDPage_Button || page == kHIDPage_Consumer)
250            target = buttons;
251
252        if (target)
253        {
254            _GLFWjoyelementNS* element = _glfw_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);
261        }
262    }
263
264    CFRelease(elements);
265
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);
272
273    js = _glfwAllocJoystick(name, guid,
274                            (int) CFArrayGetCount(axes),
275                            (int) CFArrayGetCount(buttons),
276                            (int) CFArrayGetCount(hats));
277
278    js->ns.device  = device;
279    js->ns.axes    = axes;
280    js->ns.buttons = buttons;
281    js->ns.hats    = hats;
282
283    _glfwInputJoystick(js, GLFW_CONNECTED);
284}
285
286// Callback for user-initiated joystick removal
287//
288static void removeCallback(void* context,
289                           IOReturn result,
290                           void* sender,
291                           IOHIDDeviceRef device)
292{
293    for (int jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
294    {
295        if (_glfw.joysticks[jid].connected && _glfw.joysticks[jid].ns.device == device)
296        {
297            closeJoystick(&_glfw.joysticks[jid]);
298            break;
299        }
300    }
301}
302
303
304//////////////////////////////////////////////////////////////////////////
305//////                       GLFW platform API                      //////
306//////////////////////////////////////////////////////////////////////////
307
308GLFWbool _glfwInitJoysticksCocoa(void)
309{
310    CFMutableArrayRef matching;
311    const long usages[] =
312    {
313        kHIDUsage_GD_Joystick,
314        kHIDUsage_GD_GamePad,
315        kHIDUsage_GD_MultiAxisController
316    };
317
318    _glfw.ns.hidManager = IOHIDManagerCreate(kCFAllocatorDefault,
319                                             kIOHIDOptionsTypeNone);
320
321    matching = CFArrayCreateMutable(kCFAllocatorDefault,
322                                    0,
323                                    &kCFTypeArrayCallBacks);
324    if (!matching)
325    {
326        _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array");
327        return GLFW_FALSE;
328    }
329
330    for (size_t i = 0;  i < sizeof(usages) / sizeof(long);  i++)
331    {
332        const long page = kHIDPage_GenericDesktop;
333
334        CFMutableDictionaryRef dict =
335            CFDictionaryCreateMutable(kCFAllocatorDefault,
336                                      0,
337                                      &kCFTypeDictionaryKeyCallBacks,
338                                      &kCFTypeDictionaryValueCallBacks);
339        if (!dict)
340            continue;
341
342        CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault,
343                                             kCFNumberLongType,
344                                             &page);
345        CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault,
346                                              kCFNumberLongType,
347                                              &usages[i]);
348        if (pageRef && usageRef)
349        {
350            CFDictionarySetValue(dict,
351                                 CFSTR(kIOHIDDeviceUsagePageKey),
352                                 pageRef);
353            CFDictionarySetValue(dict,
354                                 CFSTR(kIOHIDDeviceUsageKey),
355                                 usageRef);
356            CFArrayAppendValue(matching, dict);
357        }
358
359        if (pageRef)
360            CFRelease(pageRef);
361        if (usageRef)
362            CFRelease(usageRef);
363
364        CFRelease(dict);
365    }
366
367    IOHIDManagerSetDeviceMatchingMultiple(_glfw.ns.hidManager, matching);
368    CFRelease(matching);
369
370    IOHIDManagerRegisterDeviceMatchingCallback(_glfw.ns.hidManager,
371                                               &matchCallback, NULL);
372    IOHIDManagerRegisterDeviceRemovalCallback(_glfw.ns.hidManager,
373                                              &removeCallback, NULL);
374    IOHIDManagerScheduleWithRunLoop(_glfw.ns.hidManager,
375                                    CFRunLoopGetMain(),
376                                    kCFRunLoopDefaultMode);
377    IOHIDManagerOpen(_glfw.ns.hidManager, kIOHIDOptionsTypeNone);
378
379    // Execute the run loop once in order to register any initially-attached
380    // joysticks
381    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
382    return GLFW_TRUE;
383}
384
385void _glfwTerminateJoysticksCocoa(void)
386{
387    for (int jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
388    {
389        if (_glfw.joysticks[jid].connected)
390            closeJoystick(&_glfw.joysticks[jid]);
391    }
392
393    if (_glfw.ns.hidManager)
394    {
395        CFRelease(_glfw.ns.hidManager);
396        _glfw.ns.hidManager = NULL;
397    }
398}
399
400
401GLFWbool _glfwPollJoystickCocoa(_GLFWjoystick* js, int mode)
402{
403    if (mode & _GLFW_POLL_AXES)
404    {
405        for (CFIndex i = 0;  i < CFArrayGetCount(js->ns.axes);  i++)
406        {
407            _GLFWjoyelementNS* axis = (_GLFWjoyelementNS*)
408                CFArrayGetValueAtIndex(js->ns.axes, i);
409
410            const long raw = getElementValue(js, axis);
411            // Perform auto calibration
412            if (raw < axis->minimum)
413                axis->minimum = raw;
414            if (raw > axis->maximum)
415                axis->maximum = raw;
416
417            const long size = axis->maximum - axis->minimum;
418            if (size == 0)
419                _glfwInputJoystickAxis(js, (int) i, 0.f);
420            else
421            {
422                const float value = (2.f * (raw - axis->minimum) / size) - 1.f;
423                _glfwInputJoystickAxis(js, (int) i, value);
424            }
425        }
426    }
427
428    if (mode & _GLFW_POLL_BUTTONS)
429    {
430        for (CFIndex i = 0;  i < CFArrayGetCount(js->ns.buttons);  i++)
431        {
432            _GLFWjoyelementNS* button = (_GLFWjoyelementNS*)
433                CFArrayGetValueAtIndex(js->ns.buttons, i);
434            const char value = getElementValue(js, button) - button->minimum;
435            const int state = (value > 0) ? GLFW_PRESS : GLFW_RELEASE;
436            _glfwInputJoystickButton(js, (int) i, state);
437        }
438
439        for (CFIndex i = 0;  i < CFArrayGetCount(js->ns.hats);  i++)
440        {
441            const int states[9] =
442            {
443                GLFW_HAT_UP,
444                GLFW_HAT_RIGHT_UP,
445                GLFW_HAT_RIGHT,
446                GLFW_HAT_RIGHT_DOWN,
447                GLFW_HAT_DOWN,
448                GLFW_HAT_LEFT_DOWN,
449                GLFW_HAT_LEFT,
450                GLFW_HAT_LEFT_UP,
451                GLFW_HAT_CENTERED
452            };
453
454            _GLFWjoyelementNS* hat = (_GLFWjoyelementNS*)
455                CFArrayGetValueAtIndex(js->ns.hats, i);
456            long state = getElementValue(js, hat) - hat->minimum;
457            if (state < 0 || state > 8)
458                state = 8;
459
460            _glfwInputJoystickHat(js, (int) i, states[state]);
461        }
462    }
463
464    return js->connected;
465}
466
467const char* _glfwGetMappingNameCocoa(void)
468{
469    return "Mac OS X";
470}
471
472void _glfwUpdateGamepadGUIDCocoa(char* guid)
473{
474    if ((strncmp(guid + 4, "000000000000", 12) == 0) &&
475        (strncmp(guid + 20, "000000000000", 12) == 0))
476    {
477        char original[33];
478        strncpy(original, guid, sizeof(original) - 1);
479        sprintf(guid, "03000000%.4s0000%.4s000000000000",
480                original, original + 16);
481    }
482}
483
484#endif // _GLFW_COCOA
485
486