• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//========================================================================
2// GLFW 3.2 Cocoa - www.glfw.org
3//------------------------------------------------------------------------
4// Copyright (c) 2009-2016 Camilla Berglund <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#include <unistd.h>
31#include <ctype.h>
32#include <string.h>
33
34#include <mach/mach.h>
35#include <mach/mach_error.h>
36
37#include <CoreFoundation/CoreFoundation.h>
38#include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
39
40
41// Joystick element information
42//
43typedef struct _GLFWjoyelementNS
44{
45    IOHIDElementRef elementRef;
46
47    long min;
48    long max;
49
50    long minReport;
51    long maxReport;
52
53} _GLFWjoyelementNS;
54
55
56static void getElementsCFArrayHandler(const void* value, void* parameter);
57
58// Adds an element to the specified joystick
59//
60static void addJoystickElement(_GLFWjoystickNS* js,
61                               IOHIDElementRef elementRef)
62{
63    IOHIDElementType elementType;
64    long usagePage, usage;
65    CFMutableArrayRef elementsArray = NULL;
66
67    elementType = IOHIDElementGetType(elementRef);
68    usagePage = IOHIDElementGetUsagePage(elementRef);
69    usage = IOHIDElementGetUsage(elementRef);
70
71    if ((elementType != kIOHIDElementTypeInput_Axis) &&
72        (elementType != kIOHIDElementTypeInput_Button) &&
73        (elementType != kIOHIDElementTypeInput_Misc))
74    {
75        return;
76    }
77
78    switch (usagePage)
79    {
80        case kHIDPage_GenericDesktop:
81        {
82            switch (usage)
83            {
84                case kHIDUsage_GD_X:
85                case kHIDUsage_GD_Y:
86                case kHIDUsage_GD_Z:
87                case kHIDUsage_GD_Rx:
88                case kHIDUsage_GD_Ry:
89                case kHIDUsage_GD_Rz:
90                case kHIDUsage_GD_Slider:
91                case kHIDUsage_GD_Dial:
92                case kHIDUsage_GD_Wheel:
93                    elementsArray = js->axisElements;
94                    break;
95                case kHIDUsage_GD_Hatswitch:
96                    elementsArray = js->hatElements;
97                    break;
98            }
99
100            break;
101        }
102
103        case kHIDPage_Button:
104            elementsArray = js->buttonElements;
105            break;
106        default:
107            break;
108    }
109
110    if (elementsArray)
111    {
112        _GLFWjoyelementNS* element = calloc(1, sizeof(_GLFWjoyelementNS));
113
114        CFArrayAppendValue(elementsArray, element);
115
116        element->elementRef = elementRef;
117
118        element->minReport = IOHIDElementGetLogicalMin(elementRef);
119        element->maxReport = IOHIDElementGetLogicalMax(elementRef);
120    }
121}
122
123// Adds an element to the specified joystick
124//
125static void getElementsCFArrayHandler(const void* value, void* parameter)
126{
127    if (CFGetTypeID(value) == IOHIDElementGetTypeID())
128    {
129        addJoystickElement((_GLFWjoystickNS*) parameter,
130                           (IOHIDElementRef) value);
131    }
132}
133
134// Returns the value of the specified element of the specified joystick
135//
136static long getElementValue(_GLFWjoystickNS* js, _GLFWjoyelementNS* element)
137{
138    IOReturn result = kIOReturnSuccess;
139    IOHIDValueRef valueRef;
140    long value = 0;
141
142    if (js && element && js->deviceRef)
143    {
144        result = IOHIDDeviceGetValue(js->deviceRef,
145                                     element->elementRef,
146                                     &valueRef);
147
148        if (kIOReturnSuccess == result)
149        {
150            value = IOHIDValueGetIntegerValue(valueRef);
151
152            // Record min and max for auto calibration
153            if (value < element->minReport)
154                element->minReport = value;
155            if (value > element->maxReport)
156                element->maxReport = value;
157        }
158    }
159
160    // Auto user scale
161    return value;
162}
163
164// Removes the specified joystick
165//
166static void removeJoystick(_GLFWjoystickNS* js)
167{
168    int i;
169
170    if (!js->present)
171        return;
172
173    for (i = 0;  i < CFArrayGetCount(js->axisElements);  i++)
174        free((void*) CFArrayGetValueAtIndex(js->axisElements, i));
175    CFArrayRemoveAllValues(js->axisElements);
176    CFRelease(js->axisElements);
177
178    for (i = 0;  i < CFArrayGetCount(js->buttonElements);  i++)
179        free((void*) CFArrayGetValueAtIndex(js->buttonElements, i));
180    CFArrayRemoveAllValues(js->buttonElements);
181    CFRelease(js->buttonElements);
182
183    for (i = 0;  i < CFArrayGetCount(js->hatElements);  i++)
184        free((void*) CFArrayGetValueAtIndex(js->hatElements, i));
185    CFArrayRemoveAllValues(js->hatElements);
186    CFRelease(js->hatElements);
187
188    free(js->axes);
189    free(js->buttons);
190
191    memset(js, 0, sizeof(_GLFWjoystickNS));
192
193    _glfwInputJoystickChange(js - _glfw.ns_js, GLFW_DISCONNECTED);
194}
195
196// Polls for joystick axis events and updates GLFW state
197//
198static GLFWbool pollJoystickAxisEvents(_GLFWjoystickNS* js)
199{
200    CFIndex i;
201
202    if (!js->present)
203        return GLFW_FALSE;
204
205    for (i = 0;  i < CFArrayGetCount(js->axisElements);  i++)
206    {
207        _GLFWjoyelementNS* axis = (_GLFWjoyelementNS*)
208            CFArrayGetValueAtIndex(js->axisElements, i);
209
210        long value = getElementValue(js, axis);
211        long readScale = axis->maxReport - axis->minReport;
212
213        if (readScale == 0)
214            js->axes[i] = value;
215        else
216            js->axes[i] = (2.f * (value - axis->minReport) / readScale) - 1.f;
217    }
218
219    return GLFW_TRUE;
220}
221
222// Polls for joystick button events and updates GLFW state
223//
224static GLFWbool pollJoystickButtonEvents(_GLFWjoystickNS* js)
225{
226    CFIndex i;
227    int buttonIndex = 0;
228
229    if (!js->present)
230        return GLFW_FALSE;
231
232    for (i = 0;  i < CFArrayGetCount(js->buttonElements);  i++)
233    {
234        _GLFWjoyelementNS* button = (_GLFWjoyelementNS*)
235            CFArrayGetValueAtIndex(js->buttonElements, i);
236
237        if (getElementValue(js, button))
238            js->buttons[buttonIndex++] = GLFW_PRESS;
239        else
240            js->buttons[buttonIndex++] = GLFW_RELEASE;
241    }
242
243    for (i = 0;  i < CFArrayGetCount(js->hatElements);  i++)
244    {
245        _GLFWjoyelementNS* hat = (_GLFWjoyelementNS*)
246            CFArrayGetValueAtIndex(js->hatElements, i);
247
248        // Bit fields of button presses for each direction, including nil
249        const int directions[9] = { 1, 3, 2, 6, 4, 12, 8, 9, 0 };
250
251        long j, value = getElementValue(js, hat);
252        if (value < 0 || value > 8)
253            value = 8;
254
255        for (j = 0;  j < 4;  j++)
256        {
257            if (directions[value] & (1 << j))
258                js->buttons[buttonIndex++] = GLFW_PRESS;
259            else
260                js->buttons[buttonIndex++] = GLFW_RELEASE;
261        }
262    }
263
264    return GLFW_TRUE;
265}
266
267// Callback for user-initiated joystick addition
268//
269static void matchCallback(void* context,
270                          IOReturn result,
271                          void* sender,
272                          IOHIDDeviceRef deviceRef)
273{
274    _GLFWjoystickNS* js;
275    int joy;
276
277    for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
278    {
279        if (_glfw.ns_js[joy].present && _glfw.ns_js[joy].deviceRef == deviceRef)
280            return;
281    }
282
283    for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
284    {
285        if (!_glfw.ns_js[joy].present)
286            break;
287    }
288
289    if (joy > GLFW_JOYSTICK_LAST)
290        return;
291
292    js = _glfw.ns_js + joy;
293    js->present = GLFW_TRUE;
294    js->deviceRef = deviceRef;
295
296    CFStringRef name = IOHIDDeviceGetProperty(deviceRef,
297                                              CFSTR(kIOHIDProductKey));
298    if (name)
299    {
300        CFStringGetCString(name,
301                           js->name,
302                           sizeof(js->name),
303                           kCFStringEncodingUTF8);
304    }
305    else
306        strncpy(js->name, "Unknown", sizeof(js->name));
307
308    js->axisElements = CFArrayCreateMutable(NULL, 0, NULL);
309    js->buttonElements = CFArrayCreateMutable(NULL, 0, NULL);
310    js->hatElements = CFArrayCreateMutable(NULL, 0, NULL);
311
312    CFArrayRef arrayRef = IOHIDDeviceCopyMatchingElements(deviceRef,
313                                                          NULL,
314                                                          kIOHIDOptionsTypeNone);
315    CFRange range = { 0, CFArrayGetCount(arrayRef) };
316    CFArrayApplyFunction(arrayRef,
317                         range,
318                         getElementsCFArrayHandler,
319                         (void*) js);
320
321    CFRelease(arrayRef);
322
323    js->axes = calloc(CFArrayGetCount(js->axisElements), sizeof(float));
324    js->buttons = calloc(CFArrayGetCount(js->buttonElements) +
325                         CFArrayGetCount(js->hatElements) * 4, 1);
326
327    _glfwInputJoystickChange(joy, GLFW_CONNECTED);
328}
329
330// Callback for user-initiated joystick removal
331//
332static void removeCallback(void* context,
333                           IOReturn result,
334                           void* sender,
335                           IOHIDDeviceRef deviceRef)
336{
337    int joy;
338
339    for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
340    {
341        if (_glfw.ns_js[joy].deviceRef == deviceRef)
342        {
343            removeJoystick(_glfw.ns_js + joy);
344            break;
345        }
346    }
347}
348
349// Creates a dictionary to match against devices with the specified usage page
350// and usage
351//
352static CFMutableDictionaryRef createMatchingDictionary(long usagePage,
353                                                       long usage)
354{
355    CFMutableDictionaryRef result =
356        CFDictionaryCreateMutable(kCFAllocatorDefault,
357                                  0,
358                                  &kCFTypeDictionaryKeyCallBacks,
359                                  &kCFTypeDictionaryValueCallBacks);
360
361    if (result)
362    {
363        CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault,
364                                             kCFNumberLongType,
365                                             &usagePage);
366        if (pageRef)
367        {
368            CFDictionarySetValue(result,
369                                 CFSTR(kIOHIDDeviceUsagePageKey),
370                                 pageRef);
371            CFRelease(pageRef);
372
373            CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault,
374                                                  kCFNumberLongType,
375                                                  &usage);
376            if (usageRef)
377            {
378                CFDictionarySetValue(result,
379                                     CFSTR(kIOHIDDeviceUsageKey),
380                                     usageRef);
381                CFRelease(usageRef);
382            }
383        }
384    }
385
386    return result;
387}
388
389
390//////////////////////////////////////////////////////////////////////////
391//////                       GLFW internal API                      //////
392//////////////////////////////////////////////////////////////////////////
393
394// Initialize joystick interface
395//
396void _glfwInitJoysticksNS(void)
397{
398    CFMutableArrayRef matchingCFArrayRef;
399
400    _glfw.ns.hidManager = IOHIDManagerCreate(kCFAllocatorDefault,
401                                             kIOHIDOptionsTypeNone);
402
403    matchingCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault,
404                                              0,
405                                              &kCFTypeArrayCallBacks);
406    if (matchingCFArrayRef)
407    {
408        CFDictionaryRef matchingCFDictRef =
409            createMatchingDictionary(kHIDPage_GenericDesktop,
410                                     kHIDUsage_GD_Joystick);
411        if (matchingCFDictRef)
412        {
413            CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
414            CFRelease(matchingCFDictRef);
415        }
416
417        matchingCFDictRef = createMatchingDictionary(kHIDPage_GenericDesktop,
418                                                     kHIDUsage_GD_GamePad);
419        if (matchingCFDictRef)
420        {
421            CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
422            CFRelease(matchingCFDictRef);
423        }
424
425        matchingCFDictRef =
426            createMatchingDictionary(kHIDPage_GenericDesktop,
427                                     kHIDUsage_GD_MultiAxisController);
428        if (matchingCFDictRef)
429        {
430            CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
431            CFRelease(matchingCFDictRef);
432        }
433
434        IOHIDManagerSetDeviceMatchingMultiple(_glfw.ns.hidManager,
435                                              matchingCFArrayRef);
436        CFRelease(matchingCFArrayRef);
437    }
438
439    IOHIDManagerRegisterDeviceMatchingCallback(_glfw.ns.hidManager,
440                                               &matchCallback, NULL);
441    IOHIDManagerRegisterDeviceRemovalCallback(_glfw.ns.hidManager,
442                                              &removeCallback, NULL);
443
444    IOHIDManagerScheduleWithRunLoop(_glfw.ns.hidManager,
445                                    CFRunLoopGetMain(),
446                                    kCFRunLoopDefaultMode);
447
448    IOHIDManagerOpen(_glfw.ns.hidManager, kIOHIDOptionsTypeNone);
449
450    // Execute the run loop once in order to register any initially-attached
451    // joysticks
452    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
453}
454
455// Close all opened joystick handles
456//
457void _glfwTerminateJoysticksNS(void)
458{
459    int joy;
460
461    for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
462    {
463        _GLFWjoystickNS* js = _glfw.ns_js + joy;
464        removeJoystick(js);
465    }
466
467    CFRelease(_glfw.ns.hidManager);
468    _glfw.ns.hidManager = NULL;
469}
470
471
472//////////////////////////////////////////////////////////////////////////
473//////                       GLFW platform API                      //////
474//////////////////////////////////////////////////////////////////////////
475
476int _glfwPlatformJoystickPresent(int joy)
477{
478    _GLFWjoystickNS* js = _glfw.ns_js + joy;
479    return js->present;
480}
481
482const float* _glfwPlatformGetJoystickAxes(int joy, int* count)
483{
484    _GLFWjoystickNS* js = _glfw.ns_js + joy;
485    if (!pollJoystickAxisEvents(js))
486        return NULL;
487
488    *count = (int) CFArrayGetCount(js->axisElements);
489    return js->axes;
490}
491
492const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count)
493{
494    _GLFWjoystickNS* js = _glfw.ns_js + joy;
495    if (!pollJoystickButtonEvents(js))
496        return NULL;
497
498    *count = (int) CFArrayGetCount(js->buttonElements) +
499             (int) CFArrayGetCount(js->hatElements) * 4;
500    return js->buttons;
501}
502
503const char* _glfwPlatformGetJoystickName(int joy)
504{
505    _GLFWjoystickNS* js = _glfw.ns_js + joy;
506    if (!js->present)
507        return NULL;
508
509    return js->name;
510}
511
512