cocoa_joystick.m (15891B)
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> 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 // It is fine to use C99 in this file because it will not be built with VS 28 //======================================================================== 29 30 #include "internal.h" 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 // 45 typedef 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 // 58 static 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 // 78 static CFComparisonResult compareElements(const void* fp, 79 const void* sp, 80 void* user) 81 { 82 const _GLFWjoyelementNS* fe = (const _GLFWjoyelementNS*)fp; 83 const _GLFWjoyelementNS* se = (const _GLFWjoyelementNS*)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 // 97 static void closeJoystick(_GLFWjoystick* js) 98 { 99 int i; 100 101 if (!js->present) 102 return; 103 104 for (i = 0; i < CFArrayGetCount(js->ns.axes); i++) 105 free((void*) CFArrayGetValueAtIndex(js->ns.axes, i)); 106 CFRelease(js->ns.axes); 107 108 for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++) 109 free((void*) CFArrayGetValueAtIndex(js->ns.buttons, i)); 110 CFRelease(js->ns.buttons); 111 112 for (i = 0; i < CFArrayGetCount(js->ns.hats); i++) 113 free((void*) CFArrayGetValueAtIndex(js->ns.hats, i)); 114 CFRelease(js->ns.hats); 115 116 _glfwFreeJoystick(js); 117 _glfwInputJoystick(js, GLFW_DISCONNECTED); 118 } 119 120 // Callback for user-initiated joystick addition 121 // 122 static void matchCallback(void* context, 123 IOReturn result, 124 void* sender, 125 IOHIDDeviceRef device) 126 { 127 int jid; 128 char name[256]; 129 char guid[33]; 130 CFIndex i; 131 CFTypeRef property; 132 uint32_t vendor = 0, product = 0, version = 0; 133 _GLFWjoystick* js; 134 CFMutableArrayRef axes, buttons, hats; 135 136 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 137 { 138 if (_glfw.joysticks[jid].ns.device == device) 139 return; 140 } 141 142 axes = CFArrayCreateMutable(NULL, 0, NULL); 143 buttons = CFArrayCreateMutable(NULL, 0, NULL); 144 hats = CFArrayCreateMutable(NULL, 0, NULL); 145 146 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); 147 if (property) 148 { 149 CFStringGetCString((CFStringRef)property, 150 name, 151 sizeof(name), 152 kCFStringEncodingUTF8); 153 } 154 else 155 strncpy(name, "Unknown", sizeof(name)); 156 157 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)); 158 if (property) 159 CFNumberGetValue((CFNumberRef)property, kCFNumberSInt32Type, &vendor); 160 161 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)); 162 if (property) 163 CFNumberGetValue((CFNumberRef)property, kCFNumberSInt32Type, &product); 164 165 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVersionNumberKey)); 166 if (property) 167 CFNumberGetValue((CFNumberRef)property, kCFNumberSInt32Type, &version); 168 169 // Generate a joystick GUID that matches the SDL 2.0.5+ one 170 if (vendor && product) 171 { 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)); 176 } 177 else 178 { 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]); 183 } 184 185 CFArrayRef elements = 186 IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); 187 188 for (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 = (_GLFWjoyelementNS*)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 // 288 static void removeCallback(void* context, 289 IOReturn result, 290 void* sender, 291 IOHIDDeviceRef device) 292 { 293 int jid; 294 295 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 296 { 297 if (_glfw.joysticks[jid].ns.device == device) 298 { 299 closeJoystick(_glfw.joysticks + jid); 300 break; 301 } 302 } 303 } 304 305 306 ////////////////////////////////////////////////////////////////////////// 307 ////// GLFW internal API ////// 308 ////////////////////////////////////////////////////////////////////////// 309 310 // Initialize joystick interface 311 // 312 void _glfwInitJoysticksNS(void) 313 { 314 CFMutableArrayRef matching; 315 const long usages[] = 316 { 317 kHIDUsage_GD_Joystick, 318 kHIDUsage_GD_GamePad, 319 kHIDUsage_GD_MultiAxisController 320 }; 321 322 _glfw.ns.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, 323 kIOHIDOptionsTypeNone); 324 325 matching = CFArrayCreateMutable(kCFAllocatorDefault, 326 0, 327 &kCFTypeArrayCallBacks); 328 if (!matching) 329 { 330 _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array"); 331 return; 332 } 333 334 for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++) 335 { 336 const long page = kHIDPage_GenericDesktop; 337 338 CFMutableDictionaryRef dict = 339 CFDictionaryCreateMutable(kCFAllocatorDefault, 340 0, 341 &kCFTypeDictionaryKeyCallBacks, 342 &kCFTypeDictionaryValueCallBacks); 343 if (!dict) 344 continue; 345 346 CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault, 347 kCFNumberLongType, 348 &page); 349 CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault, 350 kCFNumberLongType, 351 &usages[i]); 352 if (pageRef && usageRef) 353 { 354 CFDictionarySetValue(dict, 355 CFSTR(kIOHIDDeviceUsagePageKey), 356 pageRef); 357 CFDictionarySetValue(dict, 358 CFSTR(kIOHIDDeviceUsageKey), 359 usageRef); 360 CFArrayAppendValue(matching, dict); 361 } 362 363 if (pageRef) 364 CFRelease(pageRef); 365 if (usageRef) 366 CFRelease(usageRef); 367 368 CFRelease(dict); 369 } 370 371 IOHIDManagerSetDeviceMatchingMultiple(_glfw.ns.hidManager, matching); 372 CFRelease(matching); 373 374 IOHIDManagerRegisterDeviceMatchingCallback(_glfw.ns.hidManager, 375 &matchCallback, NULL); 376 IOHIDManagerRegisterDeviceRemovalCallback(_glfw.ns.hidManager, 377 &removeCallback, NULL); 378 IOHIDManagerScheduleWithRunLoop(_glfw.ns.hidManager, 379 CFRunLoopGetMain(), 380 kCFRunLoopDefaultMode); 381 IOHIDManagerOpen(_glfw.ns.hidManager, kIOHIDOptionsTypeNone); 382 383 // Execute the run loop once in order to register any initially-attached 384 // joysticks 385 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); 386 } 387 388 // Close all opened joystick handles 389 // 390 void _glfwTerminateJoysticksNS(void) 391 { 392 int jid; 393 394 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 395 closeJoystick(_glfw.joysticks + jid); 396 397 CFRelease(_glfw.ns.hidManager); 398 _glfw.ns.hidManager = NULL; 399 } 400 401 402 ////////////////////////////////////////////////////////////////////////// 403 ////// GLFW platform API ////// 404 ////////////////////////////////////////////////////////////////////////// 405 406 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) 407 { 408 if (mode & _GLFW_POLL_AXES) 409 { 410 CFIndex i; 411 412 for (i = 0; i < CFArrayGetCount(js->ns.axes); i++) 413 { 414 _GLFWjoyelementNS* axis = (_GLFWjoyelementNS*) 415 CFArrayGetValueAtIndex(js->ns.axes, i); 416 417 const long raw = getElementValue(js, axis); 418 // Perform auto calibration 419 if (raw < axis->minimum) 420 axis->minimum = raw; 421 if (raw > axis->maximum) 422 axis->maximum = raw; 423 424 const long size = axis->maximum - axis->minimum; 425 if (size == 0) 426 _glfwInputJoystickAxis(js, (int) i, 0.f); 427 else 428 { 429 const float value = (2.f * (raw - axis->minimum) / size) - 1.f; 430 _glfwInputJoystickAxis(js, (int) i, value); 431 } 432 } 433 } 434 435 if (mode & _GLFW_POLL_BUTTONS) 436 { 437 CFIndex i; 438 439 for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++) 440 { 441 _GLFWjoyelementNS* button = (_GLFWjoyelementNS*) 442 CFArrayGetValueAtIndex(js->ns.buttons, i); 443 const char value = getElementValue(js, button) - button->minimum; 444 const int state = (value > 0) ? GLFW_PRESS : GLFW_RELEASE; 445 _glfwInputJoystickButton(js, (int) i, state); 446 } 447 448 for (i = 0; i < CFArrayGetCount(js->ns.hats); i++) 449 { 450 const int states[9] = 451 { 452 GLFW_HAT_UP, 453 GLFW_HAT_RIGHT_UP, 454 GLFW_HAT_RIGHT, 455 GLFW_HAT_RIGHT_DOWN, 456 GLFW_HAT_DOWN, 457 GLFW_HAT_LEFT_DOWN, 458 GLFW_HAT_LEFT, 459 GLFW_HAT_LEFT_UP, 460 GLFW_HAT_CENTERED 461 }; 462 463 _GLFWjoyelementNS* hat = (_GLFWjoyelementNS*) 464 CFArrayGetValueAtIndex(js->ns.hats, i); 465 long state = getElementValue(js, hat) - hat->minimum; 466 if (state < 0 || state > 8) 467 state = 8; 468 469 _glfwInputJoystickHat(js, (int) i, states[state]); 470 } 471 } 472 473 return js->present; 474 } 475 476 void _glfwPlatformUpdateGamepadGUID(char* guid) 477 { 478 if ((strncmp(guid + 4, "000000000000", 12) == 0) && 479 (strncmp(guid + 20, "000000000000", 12) == 0)) 480 { 481 char original[33]; 482 strncpy(original, guid, sizeof(original) - 1); 483 sprintf(guid, "03000000%.4s0000%.4s000000000000", 484 original, original + 16); 485 } 486 } 487