1#import "JOYController.h" 2#import "JOYMultiplayerController.h" 3#import "JOYElement.h" 4#import "JOYSubElement.h" 5#import "JOYFullReportElement.h" 6 7#import "JOYEmulatedButton.h" 8#include <IOKit/hid/IOHIDLib.h> 9 10#include <AppKit/AppKit.h> 11extern NSTextField *globalDebugField; 12 13#define PWM_RESOLUTION 16 14 15static NSString const *JOYAxisGroups = @"JOYAxisGroups"; 16static NSString const *JOYReportIDFilters = @"JOYReportIDFilters"; 17static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping"; 18static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping"; 19static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping"; 20static NSString const *JOYCustomReports = @"JOYCustomReports"; 21static NSString const *JOYIsSwitch = @"JOYIsSwitch"; 22static NSString const *JOYRumbleUsage = @"JOYRumbleUsage"; 23static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage"; 24static NSString const *JOYConnectedUsage = @"JOYConnectedUsage"; 25static NSString const *JOYConnectedUsagePage = @"JOYConnectedUsagePage"; 26static NSString const *JOYRumbleMin = @"JOYRumbleMin"; 27static NSString const *JOYRumbleMax = @"JOYRumbleMax"; 28static NSString const *JOYSwapZRz = @"JOYSwapZRz"; 29static NSString const *JOYActivationReport = @"JOYActivationReport"; 30static NSString const *JOYIgnoredReports = @"JOYIgnoredReports"; 31static NSString const *JOYIsDualShock3 = @"JOYIsDualShock3"; 32static NSString const *JOYIsSony = @"JOYIsSony"; 33static NSString const *JOYEmulateAxisButtons = @"JOYEmulateAxisButtons"; 34 35static NSMutableDictionary<id, JOYController *> *controllers; // Physical controllers 36static NSMutableArray<JOYController *> *exposedControllers; // Logical controllers 37 38static NSDictionary *hacksByName = nil; 39static NSDictionary *hacksByManufacturer = nil; 40 41static NSMutableSet<id<JOYListener>> *listeners = nil; 42 43static bool axes2DEmulateButtons = false; 44static bool hatsEmulateButtons = false; 45 46@interface JOYController () 47+ (void)controllerAdded:(IOHIDDeviceRef) device; 48+ (void)controllerRemoved:(IOHIDDeviceRef) device; 49- (void)elementChanged:(IOHIDElementRef) element; 50- (void)gotReport:(NSData *)report; 51 52@end 53 54@interface JOYButton () 55- (instancetype)initWithElement:(JOYElement *)element; 56- (bool)updateState; 57@end 58 59@interface JOYAxis () 60- (instancetype)initWithElement:(JOYElement *)element; 61- (bool)updateState; 62@end 63 64@interface JOYHat () 65- (instancetype)initWithElement:(JOYElement *)element; 66- (bool)updateState; 67@end 68 69@interface JOYAxes2D () 70- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2; 71- (bool)updateState; 72@end 73 74static NSDictionary *CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage) 75{ 76 return @{ 77 @kIOHIDDeviceUsagePageKey: @(page), 78 @kIOHIDDeviceUsageKey: @(usage), 79 }; 80} 81 82static void HIDDeviceAdded(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) 83{ 84 [JOYController controllerAdded:device]; 85} 86 87static void HIDDeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) 88{ 89 [JOYController controllerRemoved:device]; 90} 91 92static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef value) 93{ 94 [(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)]; 95} 96 97static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type, 98 uint32_t reportID, uint8_t *report, CFIndex reportLength) 99{ 100 if (reportLength) { 101 [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:false]]; 102 } 103} 104 105typedef struct __attribute__((packed)) { 106 uint8_t reportID; 107 uint8_t sequence; 108 uint8_t rumbleData[8]; 109 uint8_t command; 110 uint8_t commandData[26]; 111} JOYSwitchPacket; 112 113typedef struct __attribute__((packed)) { 114 uint8_t reportID; 115 uint8_t padding; 116 uint8_t rumbleRightDuration; 117 uint8_t rumbleRightStrength; 118 uint8_t rumbleLeftDuration; 119 uint8_t rumbleLeftStrength; 120 uint32_t padding2; 121 uint8_t ledsEnabled; 122 struct { 123 uint8_t timeEnabled; 124 uint8_t dutyLength; 125 uint8_t enabled; 126 uint8_t dutyOff; 127 uint8_t dutyOn; 128 } __attribute__((packed)) led[5]; 129 uint8_t padding3[13]; 130} JOYDualShock3Output; 131 132typedef struct __attribute__((packed)) { 133 uint8_t reportID; 134 uint8_t sequence; 135 union { 136 uint8_t tag; 137 uint8_t reportIDOnUSB; 138 }; 139 uint16_t flags; 140 uint8_t rumbleRightStrength; // Weak 141 uint8_t rumbleLeftStrength; // Strong 142 uint8_t reserved[4]; 143 uint8_t muteButtonLED; 144 uint8_t powerSaveControl; 145 uint8_t reserved2[28]; 146 uint8_t flags2; 147 uint8_t reserved3[2]; 148 uint8_t lightbarSetup; 149 uint8_t LEDBrightness; 150 uint8_t playerLEDs; 151 uint8_t lightbarRed; 152 uint8_t lightbarGreen; 153 uint8_t lightbarBlue; 154 uint8_t bluetoothSpecific[24]; 155 uint32_t crc32; 156} JOYDualSenseOutput; 157 158 159typedef union { 160 JOYSwitchPacket switchPacket; 161 JOYDualShock3Output ds3Output; 162 JOYDualSenseOutput dualsenseOutput; 163} JOYVendorSpecificOutput; 164 165@implementation JOYController 166{ 167 IOHIDDeviceRef _device; 168 NSMutableDictionary<JOYElement *, JOYButton *> *_buttons; 169 NSMutableDictionary<JOYElement *, JOYAxis *> *_axes; 170 NSMutableDictionary<JOYElement *, JOYAxes2D *> *_axes2D; 171 NSMutableDictionary<JOYElement *, JOYHat *> *_hats; 172 NSMutableDictionary<NSNumber *, JOYFullReportElement *> *_fullReportElements; 173 NSMutableDictionary<JOYFullReportElement *, NSArray<JOYElement *> *> *_multiElements; 174 175 // Button emulation 176 NSMutableDictionary<NSNumber *, JOYEmulatedButton *> *_axisEmulatedButtons; 177 NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_axes2DEmulatedButtons; 178 NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_hatEmulatedButtons; 179 180 JOYElement *_rumbleElement; 181 JOYElement *_connectedElement; 182 NSMutableDictionary<NSValue *, JOYElement *> *_iokitToJOY; 183 NSString *_serialSuffix; 184 bool _isSwitch; // Does this controller use the Switch protocol? 185 bool _isDualShock3; // Does this controller use DS3 outputs? 186 bool _isSony; // Is this a DS4 or newer Sony controller? 187 bool _isDualSense; 188 bool _isUSBDualSense; 189 190 JOYVendorSpecificOutput _lastVendorSpecificOutput; 191 volatile double _rumbleAmplitude; 192 bool _physicallyConnected; 193 bool _logicallyConnected; 194 195 NSDictionary *_hacks; 196 NSMutableData *_lastReport; 197 198 // Used when creating inputs 199 JOYElement *_previousAxisElement; 200 201 uint8_t _playerLEDs; 202 double _sentRumbleAmp; 203 unsigned _rumbleCounter; 204 bool _deviceCantSendReports; 205 dispatch_queue_t _rumbleQueue; 206} 207 208- (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks 209{ 210 return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil hacks:hacks]; 211} 212 213-(void)createOutputForElement:(JOYElement *)element 214{ 215 uint16_t rumbleUsagePage = (uint16_t)[_hacks[JOYRumbleUsagePage] unsignedIntValue]; 216 uint16_t rumbleUsage = (uint16_t)[_hacks[JOYRumbleUsage] unsignedIntValue]; 217 218 if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) { 219 if (_hacks[JOYRumbleMin]) { 220 element.min = [_hacks[JOYRumbleMin] unsignedIntValue]; 221 } 222 if (_hacks[JOYRumbleMax]) { 223 element.max = [_hacks[JOYRumbleMax] unsignedIntValue]; 224 } 225 _rumbleElement = element; 226 } 227} 228 229-(void)createInputForElement:(JOYElement *)element 230{ 231 uint16_t connectedUsagePage = (uint16_t)[_hacks[JOYConnectedUsagePage] unsignedIntValue]; 232 uint16_t connectedUsage = (uint16_t)[_hacks[JOYConnectedUsage] unsignedIntValue]; 233 234 if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) { 235 _connectedElement = element; 236 _logicallyConnected = element.value != element.min; 237 return; 238 } 239 240 NSDictionary *axisGroups = @{ 241 @(kHIDUsage_GD_X): @(0), 242 @(kHIDUsage_GD_Y): @(0), 243 @(kHIDUsage_GD_Z): @(1), 244 @(kHIDUsage_GD_Rx): @(2), 245 @(kHIDUsage_GD_Ry): @(2), 246 @(kHIDUsage_GD_Rz): @(1), 247 }; 248 249 axisGroups = _hacks[JOYAxisGroups] ?: axisGroups; 250 251 if (element.usagePage == kHIDPage_Button || 252 (element.usagePage == kHIDPage_Consumer && (element.usage == kHIDUsage_Csmr_ACHome || 253 element.usage == kHIDUsage_Csmr_ACBack))) { 254 button: { 255 JOYButton *button = [[JOYButton alloc] initWithElement: element]; 256 [_buttons setObject:button forKey:element]; 257 NSNumber *replacementUsage = element.usagePage == kHIDPage_Button? _hacks[JOYButtonUsageMapping][@(button.usage)] : nil; 258 if (replacementUsage) { 259 button.usage = [replacementUsage unsignedIntValue]; 260 } 261 return; 262 } 263 } 264 else if (element.usagePage == kHIDPage_Simulation) { 265 switch (element.usage) { 266 case kHIDUsage_Sim_Accelerator: 267 case kHIDUsage_Sim_Brake: 268 case kHIDUsage_Sim_Rudder: 269 case kHIDUsage_Sim_Throttle: 270 goto single; 271 } 272 } 273 else if (element.usagePage == kHIDPage_GenericDesktop) { 274 switch (element.usage) { 275 case kHIDUsage_GD_X: 276 case kHIDUsage_GD_Y: 277 case kHIDUsage_GD_Z: 278 case kHIDUsage_GD_Rx: 279 case kHIDUsage_GD_Ry: 280 case kHIDUsage_GD_Rz: { 281 282 JOYElement *other = _previousAxisElement; 283 _previousAxisElement = element; 284 if (!other) goto single; 285 if (other.usage >= element.usage) goto single; 286 if (other.reportID != element.reportID) goto single; 287 if (![axisGroups[@(other.usage)] isEqualTo: axisGroups[@(element.usage)]]) goto single; 288 if (other.parentID != element.parentID) goto single; 289 290 JOYAxes2D *axes = nil; 291 if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [_hacks[JOYSwapZRz] boolValue]) { 292 axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other]; 293 } 294 else { 295 axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element]; 296 } 297 NSNumber *replacementUsage = _hacks[JOYAxes2DUsageMapping][@(axes.usage)]; 298 if (replacementUsage) { 299 axes.usage = [replacementUsage unsignedIntValue]; 300 } 301 302 [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)]; 303 [_axes removeObjectForKey:other]; 304 _previousAxisElement = nil; 305 _axes2D[other] = axes; 306 _axes2D[element] = axes; 307 308 if (axes2DEmulateButtons) { 309 _axes2DEmulatedButtons[@(axes.uniqueID)] = @[ 310 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:axes.uniqueID | 0x100000000L], 311 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:axes.uniqueID | 0x200000000L], 312 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:axes.uniqueID | 0x300000000L], 313 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:axes.uniqueID | 0x400000000L], 314 ]; 315 } 316 317 /* 318 for (NSArray *group in axes2d) { 319 break; 320 IOHIDElementRef first = (__bridge IOHIDElementRef)group[0]; 321 IOHIDElementRef second = (__bridge IOHIDElementRef)group[1]; 322 if (IOHIDElementGetUsage(first) > element.usage) continue; 323 if (IOHIDElementGetUsage(second) > element.usage) continue; 324 if (IOHIDElementGetReportID(first) != IOHIDElementGetReportID(element)) continue; 325 if ((IOHIDElementGetUsage(first) - kHIDUsage_GD_X) / 3 != (element.usage - kHIDUsage_GD_X) / 3) continue; 326 if (IOHIDElementGetParent(first) != IOHIDElementGetParent(element)) continue; 327 328 [axes2d removeObject:group]; 329 [axes3d addObject:@[(__bridge id)first, (__bridge id)second, _element]]; 330 found = true; 331 break; 332 }*/ 333 break; 334 } 335 case kHIDUsage_GD_Slider: 336 case kHIDUsage_GD_Dial: 337 case kHIDUsage_GD_Wheel: 338 { single: { 339 JOYAxis *axis = [[JOYAxis alloc] initWithElement: element]; 340 [_axes setObject:axis forKey:element]; 341 342 NSNumber *replacementUsage = element.usagePage == kHIDPage_GenericDesktop? _hacks[JOYAxisUsageMapping][@(axis.usage)] : nil; 343 if (replacementUsage) { 344 axis.usage = [replacementUsage unsignedIntValue]; 345 } 346 347 if ([_hacks[JOYEmulateAxisButtons] boolValue]) { 348 _axisEmulatedButtons[@(axis.uniqueID)] = 349 [[JOYEmulatedButton alloc] initWithUsage:axis.equivalentButtonUsage uniqueID:axis.uniqueID]; 350 } 351 352 353 break; 354 }} 355 case kHIDUsage_GD_DPadUp: 356 case kHIDUsage_GD_DPadDown: 357 case kHIDUsage_GD_DPadRight: 358 case kHIDUsage_GD_DPadLeft: 359 case kHIDUsage_GD_Start: 360 case kHIDUsage_GD_Select: 361 case kHIDUsage_GD_SystemMainMenu: 362 goto button; 363 364 case kHIDUsage_GD_Hatswitch: { 365 JOYHat *hat = [[JOYHat alloc] initWithElement: element]; 366 [_hats setObject:hat forKey:element]; 367 if (hatsEmulateButtons) { 368 _hatEmulatedButtons[@(hat.uniqueID)] = @[ 369 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:hat.uniqueID | 0x100000000L], 370 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:hat.uniqueID | 0x200000000L], 371 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:hat.uniqueID | 0x300000000L], 372 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:hat.uniqueID | 0x400000000L], 373 ]; 374 } 375 break; 376 } 377 } 378 } 379} 380 381- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks 382{ 383 self = [super init]; 384 if (!self) return self; 385 386 _physicallyConnected = true; 387 _logicallyConnected = true; 388 _device = (IOHIDDeviceRef)CFRetain(device); 389 _serialSuffix = suffix; 390 _playerLEDs = -1; 391 392 IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self); 393 IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 394 395 NSArray *array = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone)); 396 _buttons = [NSMutableDictionary dictionary]; 397 _axes = [NSMutableDictionary dictionary]; 398 _axes2D = [NSMutableDictionary dictionary]; 399 _hats = [NSMutableDictionary dictionary]; 400 _axisEmulatedButtons = [NSMutableDictionary dictionary]; 401 _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; 402 _hatEmulatedButtons = [NSMutableDictionary dictionary]; 403 _iokitToJOY = [NSMutableDictionary dictionary]; 404 405 406 //NSMutableArray *axes3d = [NSMutableArray array]; 407 408 _hacks = hacks; 409 _isSwitch = [_hacks[JOYIsSwitch] boolValue]; 410 _isDualShock3 = [_hacks[JOYIsDualShock3] boolValue]; 411 _isSony = [_hacks[JOYIsSony] boolValue]; 412 413 NSDictionary *customReports = hacks[JOYCustomReports]; 414 _lastReport = [NSMutableData dataWithLength:MAX( 415 MAX( 416 [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue], 417 [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue] 418 ), 419 [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue] 420 )]; 421 IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self); 422 423 if (hacks[JOYCustomReports]) { 424 _multiElements = [NSMutableDictionary dictionary]; 425 _fullReportElements = [NSMutableDictionary dictionary]; 426 427 428 for (NSNumber *_reportID in customReports) { 429 signed reportID = [_reportID intValue]; 430 bool isOutput = false; 431 if (reportID < 0) { 432 isOutput = true; 433 reportID = -reportID; 434 } 435 436 JOYFullReportElement *element = [[JOYFullReportElement alloc] initWithDevice:device reportID:reportID]; 437 NSMutableArray *elements = [NSMutableArray array]; 438 for (NSDictionary <NSString *,NSNumber *> *subElementDef in customReports[_reportID]) { 439 if (filter && subElementDef[@"reportID"] && ![filter containsObject:subElementDef[@"reportID"]]) continue; 440 JOYSubElement *subElement = [[JOYSubElement alloc] initWithRealElement:element 441 size:subElementDef[@"size"].unsignedLongValue 442 offset:subElementDef[@"offset"].unsignedLongValue + 8 // Compensate for the reportID 443 usagePage:subElementDef[@"usagePage"].unsignedLongValue 444 usage:subElementDef[@"usage"].unsignedLongValue 445 min:subElementDef[@"min"].unsignedIntValue 446 max:subElementDef[@"max"].unsignedIntValue]; 447 [elements addObject:subElement]; 448 if (isOutput) { 449 [self createOutputForElement:subElement]; 450 } 451 else { 452 [self createInputForElement:subElement]; 453 } 454 } 455 _multiElements[element] = elements; 456 if (!isOutput) { 457 _fullReportElements[@(reportID)] = element; 458 } 459 } 460 } 461 462 id previous = nil; 463 NSSet *ignoredReports = nil; 464 if (hacks[JOYIgnoredReports]) { 465 ignoredReports = [NSSet setWithArray:hacks[JOYIgnoredReports]]; 466 } 467 468 for (id _element in array) { 469 if (_element == previous) continue; // Some elements are reported twice for some reason 470 previous = _element; 471 JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element]; 472 473 bool isOutput = false; 474 if (filter && ![filter containsObject:@(element.reportID)]) continue; 475 476 switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) { 477 /* Handled */ 478 case kIOHIDElementTypeInput_Misc: 479 case kIOHIDElementTypeInput_Button: 480 case kIOHIDElementTypeInput_Axis: 481 break; 482 case kIOHIDElementTypeOutput: 483 isOutput = true; 484 break; 485 /* Ignored */ 486 default: 487 case kIOHIDElementTypeInput_ScanCodes: 488 case kIOHIDElementTypeInput_NULL: 489 case kIOHIDElementTypeFeature: 490 case kIOHIDElementTypeCollection: 491 continue; 492 } 493 if ((!isOutput && [ignoredReports containsObject:@(element.reportID)]) || 494 (isOutput && [ignoredReports containsObject:@(-element.reportID)])) continue; 495 496 497 if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue; 498 499 if (isOutput) { 500 [self createOutputForElement:element]; 501 } 502 else { 503 [self createInputForElement:element]; 504 } 505 506 _iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element; 507 } 508 509 [exposedControllers addObject:self]; 510 if (_logicallyConnected) { 511 for (id<JOYListener> listener in listeners) { 512 if ([listener respondsToSelector:@selector(controllerConnected:)]) { 513 [listener controllerConnected:self]; 514 } 515 } 516 } 517 518 if (_hacks[JOYActivationReport]) { 519 [self sendReport:hacks[JOYActivationReport]]; 520 } 521 522 if (_isSwitch) { 523 [self sendReport:[NSData dataWithBytes:(uint8_t[]){0x80, 0x04} length:2]]; 524 [self sendReport:[NSData dataWithBytes:(uint8_t[]){0x80, 0x02} length:2]]; 525 } 526 527 if (_isDualShock3) { 528 _lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){ 529 .reportID = 1, 530 .led = { 531 {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, 532 {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, 533 {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, 534 {.timeEnabled = 0xff, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32}, 535 {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0}, 536 } 537 }; 538 } 539 if (_isSony) { 540 _isDualSense = [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue] == 0xce6; 541 } 542 543 if (_isDualSense) { 544 _isUSBDualSense = [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]; 545 _lastVendorSpecificOutput.dualsenseOutput = (JOYDualSenseOutput){ 546 .reportID = 0x31, 547 .tag = 0x10, 548 .flags = 0x1403, // Rumble, lightbar and player LEDs 549 .flags2 = 2, 550 .lightbarSetup = 2, 551 .lightbarBlue = 255, 552 }; 553 if (_isUSBDualSense) { 554 _lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB = 1; 555 _lastVendorSpecificOutput.dualsenseOutput.lightbarBlue = 0; 556 _lastVendorSpecificOutput.dualsenseOutput.lightbarGreen = 96; 557 _lastVendorSpecificOutput.dualsenseOutput.lightbarRed = 255; 558 559 } 560 // Send a report to switch the controller to a more capable mode 561 [self sendDualSenseOutput]; 562 _lastVendorSpecificOutput.dualsenseOutput.flags2 = 0; 563 _lastVendorSpecificOutput.dualsenseOutput.lightbarSetup = 0; 564 } 565 566 _rumbleQueue = dispatch_queue_create([NSString stringWithFormat:@"Rumble Queue for %@", self.deviceName].UTF8String, 567 NULL); 568 569 return self; 570} 571 572- (NSString *)deviceName 573{ 574 if (!_device) return nil; 575 return IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey)); 576} 577 578- (NSString *)uniqueID 579{ 580 if (!_device) return nil; 581 NSString *serial = (__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDSerialNumberKey)); 582 if (!serial || [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]) { 583 serial = [NSString stringWithFormat:@"%04x%04x%08x", 584 [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDVendorIDKey)) unsignedIntValue], 585 [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue], 586 [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDLocationIDKey)) unsignedIntValue]]; 587 } 588 if (_serialSuffix) { 589 return [NSString stringWithFormat:@"%@-%@", serial, _serialSuffix]; 590 } 591 return serial; 592} 593 594- (NSString *)description 595{ 596 return [NSString stringWithFormat:@"<%@: %p, %@, %@>", self.className, self, self.deviceName, self.uniqueID]; 597} 598 599- (NSArray<JOYButton *> *)buttons 600{ 601 NSMutableArray *ret = [[_buttons allValues] mutableCopy]; 602 [ret addObjectsFromArray:_axisEmulatedButtons.allValues]; 603 for (NSArray *array in _axes2DEmulatedButtons.allValues) { 604 [ret addObjectsFromArray:array]; 605 } 606 for (NSArray *array in _hatEmulatedButtons.allValues) { 607 [ret addObjectsFromArray:array]; 608 } 609 return ret; 610} 611 612- (NSArray<JOYAxis *> *)axes 613{ 614 return [_axes allValues]; 615} 616 617- (NSArray<JOYAxes2D *> *)axes2D 618{ 619 return [[NSSet setWithArray:[_axes2D allValues]] allObjects]; 620} 621 622- (NSArray<JOYHat *> *)hats 623{ 624 return [_hats allValues]; 625} 626 627- (void)gotReport:(NSData *)report 628{ 629 JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)]; 630 if (element) { 631 [element updateValue:report]; 632 633 NSArray<JOYElement *> *subElements = _multiElements[element]; 634 if (subElements) { 635 for (JOYElement *subElement in subElements) { 636 [self _elementChanged:subElement]; 637 } 638 } 639 } 640 dispatch_async(_rumbleQueue, ^{ 641 [self updateRumble]; 642 }); 643} 644 645- (void)elementChanged:(IOHIDElementRef)element 646{ 647 JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))]; 648 if (_element) { 649 [self _elementChanged:_element]; 650 } 651 else { 652 //NSLog(@"Unhandled usage %x (Cookie: %x, Usage: %x)", IOHIDElementGetUsage(element), IOHIDElementGetCookie(element), IOHIDElementGetUsage(element)); 653 } 654} 655 656- (void)_elementChanged:(JOYElement *)element 657{ 658 if (element == _connectedElement) { 659 bool old = self.connected; 660 _logicallyConnected = _connectedElement.value != _connectedElement.min; 661 if (!old && self.connected) { 662 for (id<JOYListener> listener in listeners) { 663 if ([listener respondsToSelector:@selector(controllerConnected:)]) { 664 [listener controllerConnected:self]; 665 } 666 } 667 } 668 else if (old && !self.connected) { 669 for (id<JOYListener> listener in listeners) { 670 if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { 671 [listener controllerDisconnected:self]; 672 } 673 } 674 } 675 } 676 677 if (!self.connected) return; 678 { 679 JOYButton *button = _buttons[element]; 680 if (button) { 681 if ([button updateState]) { 682 for (id<JOYListener> listener in listeners) { 683 if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { 684 [listener controller:self buttonChangedState:button]; 685 } 686 } 687 } 688 return; 689 } 690 } 691 692 693 { 694 JOYAxis *axis = _axes[element]; 695 if (axis) { 696 if ([axis updateState]) { 697 for (id<JOYListener> listener in listeners) { 698 if ([listener respondsToSelector:@selector(controller:movedAxis:)]) { 699 [listener controller:self movedAxis:axis]; 700 } 701 } 702 JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID)]; 703 if ([button updateStateFromAxis:axis]) { 704 for (id<JOYListener> listener in listeners) { 705 if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { 706 [listener controller:self buttonChangedState:button]; 707 } 708 } 709 } 710 } 711 return; 712 } 713 } 714 715 { 716 JOYAxes2D *axes = _axes2D[element]; 717 if (axes) { 718 if ([axes updateState]) { 719 for (id<JOYListener> listener in listeners) { 720 if ([listener respondsToSelector:@selector(controller:movedAxes2D:)]) { 721 [listener controller:self movedAxes2D:axes]; 722 } 723 } 724 NSArray <JOYEmulatedButton *> *buttons = _axes2DEmulatedButtons[@(axes.uniqueID)]; 725 for (JOYEmulatedButton *button in buttons) { 726 if ([button updateStateFromAxes2D:axes]) { 727 for (id<JOYListener> listener in listeners) { 728 if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { 729 [listener controller:self buttonChangedState:button]; 730 } 731 } 732 } 733 } 734 } 735 return; 736 } 737 } 738 739 { 740 JOYHat *hat = _hats[element]; 741 if (hat) { 742 if ([hat updateState]) { 743 for (id<JOYListener> listener in listeners) { 744 if ([listener respondsToSelector:@selector(controller:movedHat:)]) { 745 [listener controller:self movedHat:hat]; 746 } 747 } 748 749 NSArray <JOYEmulatedButton *> *buttons = _hatEmulatedButtons[@(hat.uniqueID)]; 750 for (JOYEmulatedButton *button in buttons) { 751 if ([button updateStateFromHat:hat]) { 752 for (id<JOYListener> listener in listeners) { 753 if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) { 754 [listener controller:self buttonChangedState:button]; 755 } 756 } 757 } 758 } 759 } 760 return; 761 } 762 } 763} 764 765- (void)disconnected 766{ 767 if (_logicallyConnected && [exposedControllers containsObject:self]) { 768 for (id<JOYListener> listener in listeners) { 769 if ([listener respondsToSelector:@selector(controllerDisconnected:)]) { 770 [listener controllerDisconnected:self]; 771 } 772 } 773 } 774 _physicallyConnected = false; 775 [exposedControllers removeObject:self]; 776 [self setRumbleAmplitude:0]; 777 dispatch_sync(_rumbleQueue, ^{ 778 [self updateRumble]; 779 }); 780 _device = nil; 781} 782 783- (void)sendReport:(NSData *)report 784{ 785 if (!report.length) return; 786 if (!_device) return; 787 if (_deviceCantSendReports) return; 788 /* Some Macs fail to send reports to some devices, specifically the DS3, returning the bogus(?) error code 1 after 789 freezing for 5 seconds. Stop sending reports if that's the case. */ 790 if (IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length) == 1) { 791 _deviceCantSendReports = true; 792 NSLog(@"This Mac appears to be incapable of sending output reports to %@", self); 793 } 794} 795 796- (void) sendDualSenseOutput 797{ 798 if (_isUSBDualSense) { 799 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB length:_lastVendorSpecificOutput.dualsenseOutput.bluetoothSpecific - &_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB]]; 800 return; 801 } 802 _lastVendorSpecificOutput.dualsenseOutput.sequence += 0x10; 803 static const uint32_t table[] = { 804 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 805 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 806 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 807 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 808 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 809 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 810 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 811 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 812 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 813 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 814 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 815 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 816 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 817 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 818 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 819 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 820 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 821 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 822 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 823 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 824 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 825 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 826 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 827 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 828 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 829 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 830 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 831 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 832 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 833 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 834 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 835 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 836 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 837 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 838 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 839 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 840 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 841 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 842 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 843 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 844 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 845 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 846 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 847 }; 848 849 const uint8_t *byte = (void *)&_lastVendorSpecificOutput.dualsenseOutput; 850 uint32_t size = sizeof(_lastVendorSpecificOutput.dualsenseOutput) - 4; 851 uint32_t ret = 0xFFFFFFFF; 852 ret = table[(ret ^ 0xa2) & 0xFF] ^ (ret >> 8); 853 854 while (size--) { 855 ret = table[(ret ^ *byte++) & 0xFF] ^ (ret >> 8); 856 } 857 858 _lastVendorSpecificOutput.dualsenseOutput.crc32 = ~ret; 859 860 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput length:sizeof(_lastVendorSpecificOutput.dualsenseOutput)]]; 861} 862 863- (uint8_t)LEDMaskForPlayer:(unsigned)player 864{ 865 if (_isDualShock3) { 866 return 2 << player; 867 } 868 if (_isUSBDualSense) { 869 switch (player) { 870 case 0: return 0x04; 871 case 1: return 0x0A; 872 case 2: return 0x15; 873 case 3: return 0x1B; 874 default: return 0; 875 } 876 } 877 return 1 << player; 878} 879 880- (void)setPlayerLEDs:(uint8_t)mask 881{ 882 if (mask == _playerLEDs) { 883 return; 884 } 885 _playerLEDs = mask; 886 if (_isSwitch) { 887 _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs 888 _lastVendorSpecificOutput.switchPacket.sequence++; 889 _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; 890 _lastVendorSpecificOutput.switchPacket.command = 0x30; // LED 891 _lastVendorSpecificOutput.switchPacket.commandData[0] = mask & 0xF; 892 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; 893 } 894 else if (_isDualShock3) { 895 _lastVendorSpecificOutput.ds3Output.reportID = 1; 896 _lastVendorSpecificOutput.ds3Output.ledsEnabled = (mask & 0x1F); 897 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; 898 } 899 else if (_isDualSense) { 900 _lastVendorSpecificOutput.dualsenseOutput.playerLEDs = mask & 0x1F; 901 [self sendDualSenseOutput]; 902 } 903} 904 905- (void)updateRumble 906{ 907 if (!self.connected) { 908 return; 909 } 910 if (!_rumbleElement && !_isSwitch && !_isDualShock3 && !_isDualSense) { 911 return; 912 } 913 if (_rumbleElement.max == 1 && _rumbleElement.min == 0) { 914 double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION); 915 if (ampToSend != _sentRumbleAmp) { 916 [_rumbleElement setValue:ampToSend]; 917 _sentRumbleAmp = ampToSend; 918 } 919 _rumbleCounter += round(_rumbleAmplitude * PWM_RESOLUTION); 920 if (_rumbleCounter >= PWM_RESOLUTION) { 921 _rumbleCounter -= PWM_RESOLUTION; 922 } 923 } 924 else { 925 if (_rumbleAmplitude == _sentRumbleAmp) { 926 return; 927 } 928 _sentRumbleAmp = _rumbleAmplitude; 929 if (_isSwitch) { 930 double frequency = 144; 931 double amp = _rumbleAmplitude; 932 933 uint8_t highAmp = amp * 0x64; 934 uint8_t lowAmp = amp * 0x32 + 0x40; 935 if (frequency < 0) frequency = 0; 936 if (frequency > 1252) frequency = 1252; 937 uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0); 938 939 uint16_t highFreq = (encodedFrequency - 0x60) * 4; 940 uint8_t lowFreq = encodedFrequency - 0x40; 941 942 //if (frequency < 82 || frequency > 312) { 943 if (amp) { 944 highAmp = 0; 945 } 946 947 if (frequency < 40 || frequency > 626) { 948 lowAmp = 0; 949 } 950 951 _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF; 952 _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1); 953 _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq; 954 _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp; 955 956 957 _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only 958 _lastVendorSpecificOutput.switchPacket.sequence++; 959 _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; 960 _lastVendorSpecificOutput.switchPacket.command = 0; // LED 961 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; 962 } 963 else if (_isDualShock3) { 964 _lastVendorSpecificOutput.ds3Output.reportID = 1; 965 _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xff : 0; 966 _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xff); 967 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]]; 968 } 969 else if (_isDualSense) { 970 _lastVendorSpecificOutput.dualsenseOutput.rumbleLeftStrength = round(_rumbleAmplitude * _rumbleAmplitude * 0xff); 971 _lastVendorSpecificOutput.dualsenseOutput.rumbleRightStrength = _rumbleAmplitude > 0.25 ? round(pow(_rumbleAmplitude - 0.25, 2) * 0xff) : 0; 972 [self sendDualSenseOutput]; 973 } 974 else { 975 [_rumbleElement setValue:_rumbleAmplitude * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min]; 976 } 977 } 978} 979 980- (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */ 981{ 982 if (amp < 0) amp = 0; 983 if (amp > 1) amp = 1; 984 _rumbleAmplitude = amp; 985} 986 987- (bool)isConnected 988{ 989 return _logicallyConnected && _physicallyConnected; 990} 991 992+ (void)controllerAdded:(IOHIDDeviceRef) device 993{ 994 NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); 995 NSDictionary *hacks = hacksByName[name]; 996 if (!hacks) { 997 hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))]; 998 } 999 NSArray *filters = hacks[JOYReportIDFilters]; 1000 JOYController *controller = nil; 1001 if (filters) { 1002 controller = [[JOYMultiplayerController alloc] initWithDevice:device 1003 reportIDFilters:filters 1004 hacks:hacks]; 1005 } 1006 else { 1007 controller = [[JOYController alloc] initWithDevice:device hacks:hacks]; 1008 } 1009 1010 [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; 1011 1012 1013} 1014 1015+ (void)controllerRemoved:(IOHIDDeviceRef) device 1016{ 1017 [[controllers objectForKey:[NSValue valueWithPointer:device]] disconnected]; 1018 [controllers removeObjectForKey:[NSValue valueWithPointer:device]]; 1019} 1020 1021+ (NSArray<JOYController *> *)allControllers 1022{ 1023 return exposedControllers; 1024} 1025 1026+ (void)load 1027{ 1028#include "ControllerConfiguration.inc" 1029} 1030 1031+(void)registerListener:(id<JOYListener>)listener 1032{ 1033 [listeners addObject:listener]; 1034} 1035 1036+(void)unregisterListener:(id<JOYListener>)listener 1037{ 1038 [listeners removeObject:listener]; 1039} 1040 1041+ (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options 1042{ 1043 axes2DEmulateButtons = [options[JOYAxes2DEmulateButtonsKey] boolValue]; 1044 hatsEmulateButtons = [options[JOYHatsEmulateButtonsKey] boolValue]; 1045 1046 controllers = [NSMutableDictionary dictionary]; 1047 exposedControllers = [NSMutableArray array]; 1048 NSArray *array = @[ 1049 CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), 1050 CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), 1051 CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController), 1052 @{@kIOHIDDeviceUsagePageKey: @(kHIDPage_Game)}, 1053 ]; 1054 1055 listeners = [NSMutableSet set]; 1056 static IOHIDManagerRef manager = nil; 1057 if (manager) { 1058 CFRelease(manager); // Stop the previous session 1059 } 1060 manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); 1061 1062 if (!manager) return; 1063 if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone)) { 1064 CFRelease(manager); 1065 return; 1066 } 1067 1068 IOHIDManagerSetDeviceMatchingMultiple(manager, (__bridge CFArrayRef)array); 1069 IOHIDManagerRegisterDeviceMatchingCallback(manager, HIDDeviceAdded, NULL); 1070 IOHIDManagerRegisterDeviceRemovalCallback(manager, HIDDeviceRemoved, NULL); 1071 IOHIDManagerScheduleWithRunLoop(manager, [runloop getCFRunLoop], kCFRunLoopDefaultMode); 1072} 1073 1074- (void)dealloc 1075{ 1076 if (_device) { 1077 CFRelease(_device); 1078 _device = NULL; 1079 } 1080} 1081@end 1082