cocoa_joystick.m (14577B)
1 //======================================================================== 2 // GLFW 3.3 Cocoa - www.glfw.org 3 //------------------------------------------------------------------------ 4 // Copyright (c) 2009-2016 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 #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 // 43 typedef struct _GLFWjoyelementNS 44 { 45 IOHIDElementRef native; 46 uint32_t usage; 47 int index; 48 long minimum; 49 long maximum; 50 51 } _GLFWjoyelementNS; 52 53 54 // Returns the value of the specified element of the specified joystick 55 // 56 static long getElementValue(_GLFWjoystick* js, _GLFWjoyelementNS* element) 57 { 58 IOHIDValueRef valueRef; 59 long value = 0; 60 61 if (js->ns.device) 62 { 63 if (IOHIDDeviceGetValue(js->ns.device, 64 element->native, 65 &valueRef) == kIOReturnSuccess) 66 { 67 value = IOHIDValueGetIntegerValue(valueRef); 68 } 69 } 70 71 return value; 72 } 73 74 // Comparison function for matching the SDL element order 75 // 76 static CFComparisonResult compareElements(const void* fp, const void* sp, void* user) 77 { 78 const _GLFWjoyelementNS* fe = fp; 79 const _GLFWjoyelementNS* se = sp; 80 if (fe->usage < se->usage) 81 return kCFCompareLessThan; 82 if (fe->usage > se->usage) 83 return kCFCompareGreaterThan; 84 if (fe->index < se->index) 85 return kCFCompareLessThan; 86 if (fe->index > se->index) 87 return kCFCompareGreaterThan; 88 return kCFCompareEqualTo; 89 } 90 91 // Removes the specified joystick 92 // 93 static void closeJoystick(_GLFWjoystick* js) 94 { 95 int i; 96 97 if (!js->present) 98 return; 99 100 for (i = 0; i < CFArrayGetCount(js->ns.axes); i++) 101 free((void*) CFArrayGetValueAtIndex(js->ns.axes, i)); 102 CFRelease(js->ns.axes); 103 104 for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++) 105 free((void*) CFArrayGetValueAtIndex(js->ns.buttons, i)); 106 CFRelease(js->ns.buttons); 107 108 for (i = 0; i < CFArrayGetCount(js->ns.hats); i++) 109 free((void*) CFArrayGetValueAtIndex(js->ns.hats, i)); 110 CFRelease(js->ns.hats); 111 112 _glfwFreeJoystick(js); 113 _glfwInputJoystick(js, GLFW_DISCONNECTED); 114 } 115 116 // Callback for user-initiated joystick addition 117 // 118 static 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 CFIndex i; 127 CFTypeRef property; 128 uint32_t vendor = 0, product = 0, version = 0; 129 _GLFWjoystick* js; 130 CFMutableArrayRef axes, buttons, hats; 131 132 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 133 { 134 if (_glfw.joysticks[jid].ns.device == device) 135 return; 136 } 137 138 axes = CFArrayCreateMutable(NULL, 0, NULL); 139 buttons = CFArrayCreateMutable(NULL, 0, NULL); 140 hats = CFArrayCreateMutable(NULL, 0, NULL); 141 142 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); 143 if (property) 144 { 145 CFStringGetCString(property, 146 name, 147 sizeof(name), 148 kCFStringEncodingUTF8); 149 } 150 else 151 strncpy(name, "Unknown", sizeof(name)); 152 153 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)); 154 if (property) 155 CFNumberGetValue(property, kCFNumberSInt32Type, &vendor); 156 157 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)); 158 if (property) 159 CFNumberGetValue(property, kCFNumberSInt32Type, &product); 160 161 property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVersionNumberKey)); 162 if (property) 163 CFNumberGetValue(property, kCFNumberSInt32Type, &version); 164 165 // Generate a joystick GUID that matches the SDL 2.0.5+ one 166 if (vendor && product) 167 { 168 sprintf(guid, "03000000%02x%02x0000%02x%02x0000%02x%02x0000", 169 (uint8_t) vendor, (uint8_t) (vendor >> 8), 170 (uint8_t) product, (uint8_t) (product >> 8), 171 (uint8_t) version, (uint8_t) (version >> 8)); 172 } 173 else 174 { 175 sprintf(guid, "05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00", 176 name[0], name[1], name[2], name[3], 177 name[4], name[5], name[6], name[7], 178 name[8], name[9], name[10]); 179 } 180 181 CFArrayRef elements = 182 IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); 183 184 for (i = 0; i < CFArrayGetCount(elements); i++) 185 { 186 IOHIDElementRef native = (IOHIDElementRef) CFArrayGetValueAtIndex(elements, i); 187 if (CFGetTypeID(native) != IOHIDElementGetTypeID()) 188 continue; 189 190 const IOHIDElementType type = IOHIDElementGetType(native); 191 if ((type != kIOHIDElementTypeInput_Axis) && 192 (type != kIOHIDElementTypeInput_Button) && 193 (type != kIOHIDElementTypeInput_Misc)) 194 { 195 continue; 196 } 197 198 CFMutableArrayRef target = NULL; 199 200 const uint32_t usage = IOHIDElementGetUsage(native); 201 const uint32_t page = IOHIDElementGetUsagePage(native); 202 if (page == kHIDPage_GenericDesktop) 203 { 204 switch (usage) 205 { 206 case kHIDUsage_GD_X: 207 case kHIDUsage_GD_Y: 208 case kHIDUsage_GD_Z: 209 case kHIDUsage_GD_Rx: 210 case kHIDUsage_GD_Ry: 211 case kHIDUsage_GD_Rz: 212 case kHIDUsage_GD_Slider: 213 case kHIDUsage_GD_Dial: 214 case kHIDUsage_GD_Wheel: 215 target = axes; 216 break; 217 case kHIDUsage_GD_Hatswitch: 218 target = hats; 219 break; 220 } 221 } 222 else if (page == kHIDPage_Button) 223 target = buttons; 224 225 if (target) 226 { 227 _GLFWjoyelementNS* element = calloc(1, sizeof(_GLFWjoyelementNS)); 228 element->native = native; 229 element->usage = usage; 230 element->index = (int) CFArrayGetCount(target); 231 element->minimum = IOHIDElementGetLogicalMin(native); 232 element->maximum = IOHIDElementGetLogicalMax(native); 233 CFArrayAppendValue(target, element); 234 } 235 } 236 237 CFRelease(elements); 238 239 CFArraySortValues(axes, CFRangeMake(0, CFArrayGetCount(axes)), 240 compareElements, NULL); 241 CFArraySortValues(buttons, CFRangeMake(0, CFArrayGetCount(buttons)), 242 compareElements, NULL); 243 CFArraySortValues(hats, CFRangeMake(0, CFArrayGetCount(hats)), 244 compareElements, NULL); 245 246 js = _glfwAllocJoystick(name, guid, 247 CFArrayGetCount(axes), 248 CFArrayGetCount(buttons), 249 CFArrayGetCount(hats)); 250 251 js->ns.device = device; 252 js->ns.axes = axes; 253 js->ns.buttons = buttons; 254 js->ns.hats = hats; 255 256 _glfwInputJoystick(js, GLFW_CONNECTED); 257 } 258 259 // Callback for user-initiated joystick removal 260 // 261 static void removeCallback(void* context, 262 IOReturn result, 263 void* sender, 264 IOHIDDeviceRef device) 265 { 266 int jid; 267 268 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 269 { 270 if (_glfw.joysticks[jid].ns.device == device) 271 { 272 closeJoystick(_glfw.joysticks + jid); 273 break; 274 } 275 } 276 } 277 278 279 ////////////////////////////////////////////////////////////////////////// 280 ////// GLFW internal API ////// 281 ////////////////////////////////////////////////////////////////////////// 282 283 // Initialize joystick interface 284 // 285 void _glfwInitJoysticksNS(void) 286 { 287 CFMutableArrayRef matching; 288 const long usages[] = 289 { 290 kHIDUsage_GD_Joystick, 291 kHIDUsage_GD_GamePad, 292 kHIDUsage_GD_MultiAxisController 293 }; 294 295 _glfw.ns.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, 296 kIOHIDOptionsTypeNone); 297 298 matching = CFArrayCreateMutable(kCFAllocatorDefault, 299 0, 300 &kCFTypeArrayCallBacks); 301 if (!matching) 302 { 303 _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array"); 304 return; 305 } 306 307 for (int i = 0; i < sizeof(usages) / sizeof(long); i++) 308 { 309 const long page = kHIDPage_GenericDesktop; 310 311 CFMutableDictionaryRef dict = 312 CFDictionaryCreateMutable(kCFAllocatorDefault, 313 0, 314 &kCFTypeDictionaryKeyCallBacks, 315 &kCFTypeDictionaryValueCallBacks); 316 if (!dict) 317 continue; 318 319 CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault, 320 kCFNumberLongType, 321 &page); 322 CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault, 323 kCFNumberLongType, 324 &usages[i]); 325 if (pageRef && usageRef) 326 { 327 CFDictionarySetValue(dict, 328 CFSTR(kIOHIDDeviceUsagePageKey), 329 pageRef); 330 CFDictionarySetValue(dict, 331 CFSTR(kIOHIDDeviceUsageKey), 332 usageRef); 333 CFArrayAppendValue(matching, dict); 334 } 335 336 if (pageRef) 337 CFRelease(pageRef); 338 if (usageRef) 339 CFRelease(usageRef); 340 341 CFRelease(dict); 342 } 343 344 IOHIDManagerSetDeviceMatchingMultiple(_glfw.ns.hidManager, matching); 345 CFRelease(matching); 346 347 IOHIDManagerRegisterDeviceMatchingCallback(_glfw.ns.hidManager, 348 &matchCallback, NULL); 349 IOHIDManagerRegisterDeviceRemovalCallback(_glfw.ns.hidManager, 350 &removeCallback, NULL); 351 IOHIDManagerScheduleWithRunLoop(_glfw.ns.hidManager, 352 CFRunLoopGetMain(), 353 kCFRunLoopDefaultMode); 354 IOHIDManagerOpen(_glfw.ns.hidManager, kIOHIDOptionsTypeNone); 355 356 // Execute the run loop once in order to register any initially-attached 357 // joysticks 358 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); 359 } 360 361 // Close all opened joystick handles 362 // 363 void _glfwTerminateJoysticksNS(void) 364 { 365 int jid; 366 367 for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) 368 closeJoystick(_glfw.joysticks + jid); 369 370 CFRelease(_glfw.ns.hidManager); 371 _glfw.ns.hidManager = NULL; 372 } 373 374 375 ////////////////////////////////////////////////////////////////////////// 376 ////// GLFW platform API ////// 377 ////////////////////////////////////////////////////////////////////////// 378 379 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) 380 { 381 if (mode & _GLFW_POLL_AXES) 382 { 383 CFIndex i; 384 385 for (i = 0; i < CFArrayGetCount(js->ns.axes); i++) 386 { 387 _GLFWjoyelementNS* axis = (_GLFWjoyelementNS*) 388 CFArrayGetValueAtIndex(js->ns.axes, i); 389 390 const long raw = getElementValue(js, axis); 391 // Perform auto calibration 392 if (raw < axis->minimum) 393 axis->minimum = raw; 394 if (raw > axis->maximum) 395 axis->maximum = raw; 396 397 const long delta = axis->maximum - axis->minimum; 398 if (delta == 0) 399 _glfwInputJoystickAxis(js, i, 0.f); 400 else 401 { 402 const float value = (2.f * (raw - axis->minimum) / delta) - 1.f; 403 _glfwInputJoystickAxis(js, i, value); 404 } 405 } 406 } 407 408 if (mode & _GLFW_POLL_BUTTONS) 409 { 410 CFIndex i; 411 412 for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++) 413 { 414 _GLFWjoyelementNS* button = (_GLFWjoyelementNS*) 415 CFArrayGetValueAtIndex(js->ns.buttons, i); 416 const char value = getElementValue(js, button) - button->minimum; 417 _glfwInputJoystickButton(js, i, value); 418 } 419 420 for (i = 0; i < CFArrayGetCount(js->ns.hats); i++) 421 { 422 const int states[9] = 423 { 424 GLFW_HAT_UP, 425 GLFW_HAT_RIGHT_UP, 426 GLFW_HAT_RIGHT, 427 GLFW_HAT_RIGHT_DOWN, 428 GLFW_HAT_DOWN, 429 GLFW_HAT_LEFT_DOWN, 430 GLFW_HAT_LEFT, 431 GLFW_HAT_LEFT_UP, 432 GLFW_HAT_CENTERED 433 }; 434 435 _GLFWjoyelementNS* hat = (_GLFWjoyelementNS*) 436 CFArrayGetValueAtIndex(js->ns.hats, i); 437 long state = getElementValue(js, hat) - hat->minimum; 438 if (state < 0 || state > 8) 439 state = 8; 440 441 _glfwInputJoystickHat(js, i, states[state]); 442 } 443 } 444 445 return js->present; 446 } 447 448 void _glfwPlatformUpdateGamepadGUID(char* guid) 449 { 450 if ((strncmp(guid + 4, "000000000000", 12) == 0) && 451 (strncmp(guid + 20, "000000000000", 12) == 0)) 452 { 453 char original[33]; 454 strcpy(original, guid); 455 sprintf(guid, "03000000%.4s0000%.4s000000000000", 456 original, original + 16); 457 } 458 } 459