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