SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
JoyKit/JOYController.m
1 #import "JOYController.h"
2 #import "JOYMultiplayerController.h"
3 #import "JOYElement.h"
4 #import "JOYSubElement.h"
5 #import "JOYFullReportElement.h"
6 #import "JOYButton.h"
7 #import "JOYEmulatedButton.h"
8 #import <IOKit/hid/IOHIDLib.h>
9
10 #import <AppKit/AppKit.h>
11 extern NSTextField *globalDebugField;
12
13 #define PWM_RESOLUTION 16
14
15 static NSString const *JOYAxisGroups = @"JOYAxisGroups";
16 static NSString const *JOYReportIDFilters = @"JOYReportIDFilters";
17 static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping";
18 static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping";
19 static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping";
20 static NSString const *JOYCustomReports = @"JOYCustomReports";
21 static NSString const *JOYIsSwitch = @"JOYIsSwitch";
22 static NSString const *JOYJoyCon = @"JOYJoyCon";
23 static NSString const *JOYRumbleUsage = @"JOYRumbleUsage";
24 static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage";
25 static NSString const *JOYConnectedUsage = @"JOYConnectedUsage";
26 static NSString const *JOYConnectedUsagePage = @"JOYConnectedUsagePage";
27 static NSString const *JOYRumbleMin = @"JOYRumbleMin";
28 static NSString const *JOYRumbleMax = @"JOYRumbleMax";
29 static NSString const *JOYSwapZRz = @"JOYSwapZRz";
30 static NSString const *JOYActivationReport = @"JOYActivationReport";
31 static NSString const *JOYIgnoredReports = @"JOYIgnoredReports";
32 static NSString const *JOYIsDualShock3 = @"JOYIsDualShock3";
33 static NSString const *JOYIsSony = @"JOYIsSony";
34 static NSString const *JOYEmulateAxisButtons = @"JOYEmulateAxisButtons";
35
36 static NSMutableDictionary<id, JOYController *> *controllers; // Physical controllers
37 static NSMutableArray<JOYController *> *exposedControllers; // Logical controllers
38
39 static NSDictionary *hacksByName = nil;
40 static NSDictionary *hacksByManufacturer = nil;
41
42 static NSMutableSet<id<JOYListener>> *listeners = nil;
43
44 static bool axes2DEmulateButtons = false;
45 static bool hatsEmulateButtons = false;
46
47 @interface JOYController ()
48 + (void)controllerAdded:(IOHIDDeviceRef) device;
49 + (void)controllerRemoved:(IOHIDDeviceRef) device;
50 - (void)elementChanged:(IOHIDElementRef) element;
51 - (void)gotReport:(NSData *)report;
52
53 @end
54
55 @interface JOYButton ()
56 - (instancetype)initWithElement:(JOYElement *)element;
57 - (bool)updateState;
58 @property JOYButtonUsage originalUsage;
59 @end
60
61 @interface JOYAxis ()
62 - (instancetype)initWithElement:(JOYElement *)element;
63 - (bool)updateState;
64 @end
65
66 @interface JOYHat ()
67 - (instancetype)initWithElement:(JOYElement *)element;
68 - (bool)updateState;
69 @end
70
71 @interface JOYAxes2D ()
72 - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2;
73 - (bool)updateState;
74 @property unsigned rotation; // in 90 degrees units, clockwise
75 @end
76
77 @interface JOYAxes3D ()
78 {
79 @public JOYElement *_element1, *_element2, *_element3;
80 }
81 - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 thirdElement:(JOYElement *)element2;
82 - (bool)updateState;
83 @property unsigned rotation; // in 90 degrees units, clockwise
84 @end
85
86 @interface JOYInput ()
87 @property unsigned combinedIndex;
88 @end
89
90 static NSDictionary *CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage)
91 {
92 return @{
93 @kIOHIDDeviceUsagePageKey: @(page),
94 @kIOHIDDeviceUsageKey: @(usage),
95 };
96 }
97
98 static void HIDDeviceAdded(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
99 {
100 [JOYController controllerAdded:device];
101 }
102
103 static void HIDDeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
104 {
105 [JOYController controllerRemoved:device];
106 }
107
108 static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef value)
109 {
110 [(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)];
111 }
112
113 static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type,
114 uint32_t reportID, uint8_t *report, CFIndex reportLength)
115 {
116 if (reportLength) {
117 [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:false]];
118 }
119 }
120
121 typedef struct __attribute__((packed)) {
122 uint8_t reportID;
123 uint8_t sequence;
124 uint8_t rumbleData[8];
125 uint8_t command;
126 uint8_t commandData[26];
127 } JOYSwitchPacket;
128
129 typedef struct __attribute__((packed)) {
130 uint8_t reportID;
131 uint8_t padding;
132 uint8_t rumbleRightDuration;
133 uint8_t rumbleRightStrength;
134 uint8_t rumbleLeftDuration;
135 uint8_t rumbleLeftStrength;
136 uint32_t padding2;
137 uint8_t ledsEnabled;
138 struct {
139 uint8_t timeEnabled;
140 uint8_t dutyLength;
141 uint8_t enabled;
142 uint8_t dutyOff;
143 uint8_t dutyOn;
144 } __attribute__((packed)) led[5];
145 uint8_t padding3[13];
146 } JOYDualShock3Output;
147
148 typedef struct __attribute__((packed)) {
149 uint8_t reportID;
150 uint8_t sequence;
151 union {
152 uint8_t tag;
153 uint8_t reportIDOnUSB;
154 };
155 uint16_t flags;
156 uint8_t rumbleRightStrength; // Weak
157 uint8_t rumbleLeftStrength; // Strong
158 uint8_t reserved[4];
159 uint8_t muteButtonLED;
160 uint8_t powerSaveControl;
161 uint8_t reserved2[28];
162 uint8_t flags2;
163 uint8_t reserved3[2];
164 uint8_t lightbarSetup;
165 uint8_t LEDBrightness;
166 uint8_t playerLEDs;
167 uint8_t lightbarRed;
168 uint8_t lightbarGreen;
169 uint8_t lightbarBlue;
170 uint8_t bluetoothSpecific[24];
171 uint32_t crc32;
172 } JOYDualSenseOutput;
173
174
175 typedef union {
176 JOYSwitchPacket switchPacket;
177 JOYDualShock3Output ds3Output;
178 JOYDualSenseOutput dualsenseOutput;
179 } JOYVendorSpecificOutput;
180
181 @implementation JOYController
182 {
183 @public // Let JOYCombinedController access everything
184 IOHIDDeviceRef _device;
185 NSMutableDictionary<JOYElement *, JOYButton *> *_buttons;
186 NSMutableDictionary<JOYElement *, JOYAxis *> *_axes;
187 NSMutableDictionary<JOYElement *, JOYAxes2D *> *_axes2D;
188 NSMutableDictionary<JOYElement *, JOYAxes3D *> *_axes3D;
189 NSMutableDictionary<JOYElement *, JOYHat *> *_hats;
190 NSMutableDictionary<NSNumber *, JOYFullReportElement *> *_fullReportElements;
191 NSMutableDictionary<JOYFullReportElement *, NSArray<JOYElement *> *> *_multiElements;
192 JOYAxes3D *_lastAxes3D;
193
194 // Button emulation
195 NSMutableDictionary<NSNumber *, JOYEmulatedButton *> *_axisEmulatedButtons;
196 NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_axes2DEmulatedButtons;
197 NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_hatEmulatedButtons;
198
199 JOYElement *_rumbleElement;
200 JOYElement *_connectedElement;
201 NSMutableDictionary<NSValue *, JOYElement *> *_iokitToJOY;
202 NSString *_serialSuffix;
203 bool _isSwitch; // Does this controller use the Switch protocol?
204 bool _isDualShock3; // Does this controller use DS3 outputs?
205 bool _isSony; // Is this a DS4 or newer Sony controller?
206 bool _isDualSense;
207 bool _isUSBDualSense;
208
209 JOYVendorSpecificOutput _lastVendorSpecificOutput;
210 volatile double _rumbleAmplitude;
211 bool _physicallyConnected;
212 bool _logicallyConnected;
213
214 NSDictionary *_hacks;
215 NSMutableData *_lastReport;
216
217 // Used when creating inputs
218 JOYElement *_previousAxisElement;
219
220 uint8_t _playerLEDs;
221 double _sentRumbleAmp;
222 unsigned _rumbleCounter;
223 bool _deviceCantSendReports;
224 dispatch_queue_t _rumbleQueue;
225 JOYCombinedController *_parent;
226 }
227
228 - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks
229 {
230 return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil hacks:hacks];
231 }
232
233 -(void)createOutputForElement:(JOYElement *)element
234 {
235 uint16_t rumbleUsagePage = (uint16_t)[_hacks[JOYRumbleUsagePage] unsignedIntValue];
236 uint16_t rumbleUsage = (uint16_t)[_hacks[JOYRumbleUsage] unsignedIntValue];
237
238 if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) {
239 if (_hacks[JOYRumbleMin]) {
240 element.min = [_hacks[JOYRumbleMin] unsignedIntValue];
241 }
242 if (_hacks[JOYRumbleMax]) {
243 element.max = [_hacks[JOYRumbleMax] unsignedIntValue];
244 }
245 _rumbleElement = element;
246 }
247 }
248
249 -(void)createInputForElement:(JOYElement *)element
250 {
251 uint16_t connectedUsagePage = (uint16_t)[_hacks[JOYConnectedUsagePage] unsignedIntValue];
252 uint16_t connectedUsage = (uint16_t)[_hacks[JOYConnectedUsage] unsignedIntValue];
253
254 if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) {
255 _connectedElement = element;
256 _logicallyConnected = element.value != element.min;
257 return;
258 }
259
260 NSDictionary *axisGroups = @{
261 @(kHIDUsage_GD_X): @(0),
262 @(kHIDUsage_GD_Y): @(0),
263 @(kHIDUsage_GD_Z): @(1),
264 @(kHIDUsage_GD_Rx): @(2),
265 @(kHIDUsage_GD_Ry): @(2),
266 @(kHIDUsage_GD_Rz): @(1),
267 };
268
269 if (element.usagePage == kHIDPage_Sensor) {
270 JOYAxes3DUsage usage;
271 JOYElement *element1 = nil, *element2 = nil, *element3 = nil;
272
273 switch (element.usage) {
274 case kHIDUsage_Snsr_Data_Motion_AccelerationAxisX:
275 case kHIDUsage_Snsr_Data_Motion_AccelerationAxisY:
276 case kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ:
277 usage = JOYAxes3DUsageAcceleration;
278 break;
279 case kHIDUsage_Snsr_Data_Motion_AngularPositionXAxis:
280 case kHIDUsage_Snsr_Data_Motion_AngularPositionYAxis:
281 case kHIDUsage_Snsr_Data_Motion_AngularPositionZAxis:
282 usage = JOYAxes3DUsageOrientation;
283 break;
284 case kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis:
285 case kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis:
286 case kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis:
287 usage = JOYAxes3DUsageGyroscope;
288 break;
289 default:
290 return;
291 }
292
293 switch (element.usage) {
294 case kHIDUsage_Snsr_Data_Motion_AccelerationAxisX:
295 case kHIDUsage_Snsr_Data_Motion_AngularPositionXAxis:
296 case kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis:
297 element1 = element;
298 if (_lastAxes3D && !_lastAxes3D->_element1 && _lastAxes3D.usage == usage) {
299 element2 = _lastAxes3D->_element2;
300 element3 = _lastAxes3D->_element3;
301 }
302 break;
303 case kHIDUsage_Snsr_Data_Motion_AccelerationAxisY:
304 case kHIDUsage_Snsr_Data_Motion_AngularPositionYAxis:
305 case kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis:
306 element2 = element;
307 if (_lastAxes3D && !_lastAxes3D->_element2 && _lastAxes3D.usage == usage) {
308 element1 = _lastAxes3D->_element1;
309 element3 = _lastAxes3D->_element3;
310 }
311 break;
312 case kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ:
313 case kHIDUsage_Snsr_Data_Motion_AngularPositionZAxis:
314 case kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis:
315 element3 = element;
316 if (_lastAxes3D && !_lastAxes3D->_element3 && _lastAxes3D.usage == usage) {
317 element1 = _lastAxes3D->_element1;
318 element2 = _lastAxes3D->_element2;
319 }
320 break;
321 }
322
323 _lastAxes3D = [[JOYAxes3D alloc] initWithFirstElement:element1 secondElement:element2 thirdElement:element3];
324 _lastAxes3D.usage = usage;
325 if (element1) _axes3D[element1] = _lastAxes3D;
326 if (element2) _axes3D[element2] = _lastAxes3D;
327 if (element3) _axes3D[element3] = _lastAxes3D;
328
329 return;
330 }
331
332 axisGroups = _hacks[JOYAxisGroups] ?: axisGroups;
333
334 if (element.usagePage == kHIDPage_Button ||
335 (element.usagePage == kHIDPage_Consumer && (element.usage == kHIDUsage_Csmr_ACHome ||
336 element.usage == kHIDUsage_Csmr_ACBack))) {
337 button: {
338 JOYButton *button = [[JOYButton alloc] initWithElement: element];
339 [_buttons setObject:button forKey:element];
340 NSNumber *replacementUsage = element.usagePage == kHIDPage_Button? _hacks[JOYButtonUsageMapping][@(button.usage)] : nil;
341 if (replacementUsage) {
342 button.originalUsage = button.usage = [replacementUsage unsignedIntValue];
343 }
344 return;
345 }
346 }
347 else if (element.usagePage == kHIDPage_Simulation) {
348 switch (element.usage) {
349 case kHIDUsage_Sim_Accelerator:
350 case kHIDUsage_Sim_Brake:
351 case kHIDUsage_Sim_Rudder:
352 case kHIDUsage_Sim_Throttle:
353 goto single;
354 }
355 }
356 else if (element.usagePage == kHIDPage_GenericDesktop) {
357 switch (element.usage) {
358 case kHIDUsage_GD_X:
359 case kHIDUsage_GD_Y:
360 case kHIDUsage_GD_Z:
361 case kHIDUsage_GD_Rx:
362 case kHIDUsage_GD_Ry:
363 case kHIDUsage_GD_Rz: {
364
365 JOYElement *other = _previousAxisElement;
366 _previousAxisElement = element;
367 if (!other) goto single;
368 if (other.usage >= element.usage) goto single;
369 if (other.reportID != element.reportID) goto single;
370 if (![axisGroups[@(other.usage)] isEqual: axisGroups[@(element.usage)]]) goto single;
371 if (other.parentID != element.parentID) goto single;
372
373 JOYAxes2D *axes = nil;
374 if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [_hacks[JOYSwapZRz] boolValue]) {
375 axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other];
376 }
377 else {
378 axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element];
379 }
380 NSNumber *replacementUsage = _hacks[JOYAxes2DUsageMapping][@(axes.usage)];
381 if (replacementUsage) {
382 axes.usage = [replacementUsage unsignedIntValue];
383 }
384
385 [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)];
386 [_axes removeObjectForKey:other];
387 _previousAxisElement = nil;
388 _axes2D[other] = axes;
389 _axes2D[element] = axes;
390
391 if (axes2DEmulateButtons) {
392 _axes2DEmulatedButtons[@(axes.uniqueID)] = @[
393 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x100000000L],
394 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x200000000L],
395 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x300000000L],
396 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown type:JOYButtonTypeAxes2DEmulated uniqueID:axes.uniqueID | 0x400000000L],
397 ];
398 }
399 break;
400 }
401 case kHIDUsage_GD_Slider:
402 case kHIDUsage_GD_Dial:
403 case kHIDUsage_GD_Wheel:
404 { single: {
405 JOYAxis *axis = [[JOYAxis alloc] initWithElement: element];
406 [_axes setObject:axis forKey:element];
407
408 NSNumber *replacementUsage = element.usagePage == kHIDPage_GenericDesktop? _hacks[JOYAxisUsageMapping][@(axis.usage)] : nil;
409 if (replacementUsage) {
410 axis.usage = [replacementUsage unsignedIntValue];
411 }
412
413 if ([_hacks[JOYEmulateAxisButtons] boolValue]) {
414 _axisEmulatedButtons[@(axis.uniqueID)] =
415 [[JOYEmulatedButton alloc] initWithUsage:axis.equivalentButtonUsage type:JOYButtonTypeAxisEmulated uniqueID:axis.uniqueID];
416 }
417
418
419 break;
420 }}
421 case kHIDUsage_GD_DPadUp:
422 case kHIDUsage_GD_DPadDown:
423 case kHIDUsage_GD_DPadRight:
424 case kHIDUsage_GD_DPadLeft:
425 case kHIDUsage_GD_Start:
426 case kHIDUsage_GD_Select:
427 case kHIDUsage_GD_SystemMainMenu:
428 goto button;
429
430 case kHIDUsage_GD_Hatswitch: {
431 JOYHat *hat = [[JOYHat alloc] initWithElement: element];
432 [_hats setObject:hat forKey:element];
433 if (hatsEmulateButtons) {
434 _hatEmulatedButtons[@(hat.uniqueID)] = @[
435 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x100000000L],
436 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x200000000L],
437 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x300000000L],
438 [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown type:JOYButtonTypeHatEmulated uniqueID:hat.uniqueID | 0x400000000L],
439 ];
440 }
441 break;
442 }
443 }
444 }
445 }
446
447 - (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks
448 {
449 self = [super init];
450 if (!self) return self;
451
452 _physicallyConnected = true;
453 _logicallyConnected = true;
454 _device = (IOHIDDeviceRef)CFRetain(device);
455 _serialSuffix = suffix;
456 _playerLEDs = -1;
457 [self obtainInfo];
458
459 IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self);
460 IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
461
462 NSArray *array = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
463 _buttons = [NSMutableDictionary dictionary];
464 _axes = [NSMutableDictionary dictionary];
465 _axes2D = [NSMutableDictionary dictionary];
466 _axes3D = [NSMutableDictionary dictionary];
467 _hats = [NSMutableDictionary dictionary];
468 _axisEmulatedButtons = [NSMutableDictionary dictionary];
469 _axes2DEmulatedButtons = [NSMutableDictionary dictionary];
470 _hatEmulatedButtons = [NSMutableDictionary dictionary];
471 _iokitToJOY = [NSMutableDictionary dictionary];
472
473
474 _hacks = hacks;
475 _isSwitch = [_hacks[JOYIsSwitch] boolValue];
476 _isDualShock3 = [_hacks[JOYIsDualShock3] boolValue];
477 _isSony = [_hacks[JOYIsSony] boolValue];
478 _joyconType = [_hacks[JOYJoyCon] unsignedIntValue];
479
480 NSDictionary *customReports = hacks[JOYCustomReports];
481 _lastReport = [NSMutableData dataWithLength:MAX(
482 MAX(
483 [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue],
484 [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue]
485 ),
486 [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue]
487 )];
488 IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self);
489
490 if (hacks[JOYCustomReports]) {
491 _multiElements = [NSMutableDictionary dictionary];
492 _fullReportElements = [NSMutableDictionary dictionary];
493
494
495 for (NSNumber *_reportID in customReports) {
496 signed reportID = [_reportID intValue];
497 bool isOutput = false;
498 if (reportID < 0) {
499 isOutput = true;
500 reportID = -reportID;
501 }
502
503 JOYFullReportElement *element = [[JOYFullReportElement alloc] initWithDevice:device reportID:reportID];
504 NSMutableArray *elements = [NSMutableArray array];
505 for (NSDictionary <NSString *,NSNumber *> *subElementDef in customReports[_reportID]) {
506 if (filter && subElementDef[@"reportID"] && ![filter containsObject:subElementDef[@"reportID"]]) continue;
507 JOYSubElement *subElement = [[JOYSubElement alloc] initWithRealElement:element
508 size:subElementDef[@"size"].unsignedLongValue
509 offset:subElementDef[@"offset"].unsignedLongValue + 8 // Compensate for the reportID
510 usagePage:subElementDef[@"usagePage"].unsignedLongValue
511 usage:subElementDef[@"usage"].unsignedLongValue
512 min:subElementDef[@"min"].unsignedIntValue
513 max:subElementDef[@"max"].unsignedIntValue];
514 [elements addObject:subElement];
515 if (isOutput) {
516 [self createOutputForElement:subElement];
517 }
518 else {
519 [self createInputForElement:subElement];
520 }
521 }
522 _multiElements[element] = elements;
523 if (!isOutput) {
524 _fullReportElements[@(reportID)] = element;
525 }
526 }
527 }
528
529 id previous = nil;
530 NSSet *ignoredReports = nil;
531 if (hacks[JOYIgnoredReports]) {
532 ignoredReports = [NSSet setWithArray:hacks[JOYIgnoredReports]];
533 }
534
535 for (id _element in array) {
536 if (_element == previous) continue; // Some elements are reported twice for some reason
537 previous = _element;
538 JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element];
539
540 bool isOutput = false;
541 if (filter && ![filter containsObject:@(element.reportID)]) continue;
542
543 switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) {
544 /* Handled */
545 case kIOHIDElementTypeInput_Misc:
546 case kIOHIDElementTypeInput_Button:
547 case kIOHIDElementTypeInput_Axis:
548 break;
549 case kIOHIDElementTypeOutput:
550 isOutput = true;
551 break;
552 /* Ignored */
553 default:
554 case kIOHIDElementTypeInput_ScanCodes:
555 case kIOHIDElementTypeInput_NULL:
556 case kIOHIDElementTypeFeature:
557 case kIOHIDElementTypeCollection:
558 continue;
559 }
560 if ((!isOutput && [ignoredReports containsObject:@(element.reportID)]) ||
561 (isOutput && [ignoredReports containsObject:@(-element.reportID)])) continue;
562
563
564 if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue;
565
566 if (isOutput) {
567 [self createOutputForElement:element];
568 }
569 else {
570 [self createInputForElement:element];
571 }
572
573 _iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element;
574 }
575
576 [exposedControllers addObject:self];
577 if (_logicallyConnected) {
578 for (id<JOYListener> listener in listeners) {
579 if ([listener respondsToSelector:@selector(controllerConnected:)]) {
580 [listener controllerConnected:self];
581 }
582 }
583 }
584
585 if (_hacks[JOYActivationReport]) {
586 [self sendReport:hacks[JOYActivationReport]];
587 }
588
589 if (_isSwitch) {
590 [self sendReport:[NSData dataWithBytes:inline_const(uint8_t[], {0x80, 0x04}) length:2]];
591 [self sendReport:[NSData dataWithBytes:inline_const(uint8_t[], {0x80, 0x02}) length:2]];
592
593 _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs
594 _lastVendorSpecificOutput.switchPacket.sequence++;
595 _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
596 _lastVendorSpecificOutput.switchPacket.command = 3; // Set input report mode
597 _lastVendorSpecificOutput.switchPacket.commandData[0] = 0x30; // Standard full mode
598 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
599
600 _lastVendorSpecificOutput.switchPacket.sequence++;
601 _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
602 _lastVendorSpecificOutput.switchPacket.command = 0x48; // Set vibration enabled
603 _lastVendorSpecificOutput.switchPacket.commandData[0] = 1; // enabled
604 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
605
606 // The Joy-Cons don't like having their IMU enabled too quickly
607 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
608 _lastVendorSpecificOutput.switchPacket.sequence++;
609 _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
610 _lastVendorSpecificOutput.switchPacket.command = 0x40; // Enable/disableIMU
611 _lastVendorSpecificOutput.switchPacket.commandData[0] = 1; // Enabled
612 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
613 });
614 }
615
616 if (_isDualShock3) {
617 _lastVendorSpecificOutput.ds3Output = (JOYDualShock3Output){
618 .reportID = 1,
619 .led = {
620 {.timeEnabled = 0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
621 {.timeEnabled = 0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
622 {.timeEnabled = 0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
623 {.timeEnabled = 0xFF, .dutyLength = 0x27, .enabled = 0x10, .dutyOff = 0, .dutyOn = 0x32},
624 {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0},
625 }
626 };
627 }
628 if (_isSony) {
629 _isDualSense = [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue] == 0xCE6;
630 }
631
632 if (_isDualSense) {
633 _isUSBDualSense = [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"];
634 _lastVendorSpecificOutput.dualsenseOutput = (JOYDualSenseOutput){
635 .reportID = 0x31,
636 .tag = 0x10,
637 .flags = 0x1403, // Rumble, lightbar and player LEDs
638 .flags2 = 2,
639 .lightbarSetup = 2,
640 .lightbarBlue = 255,
641 };
642 if (_isUSBDualSense) {
643 _lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB = 1;
644 _lastVendorSpecificOutput.dualsenseOutput.lightbarBlue = 0;
645 _lastVendorSpecificOutput.dualsenseOutput.lightbarGreen = 96;
646 _lastVendorSpecificOutput.dualsenseOutput.lightbarRed = 255;
647
648 }
649 // Send a report to switch the controller to a more capable mode
650 [self sendDualSenseOutput];
651 _lastVendorSpecificOutput.dualsenseOutput.flags2 = 0;
652 _lastVendorSpecificOutput.dualsenseOutput.lightbarSetup = 0;
653 }
654
655 _rumbleQueue = dispatch_queue_create([NSString stringWithFormat:@"Rumble Queue for %@", self.deviceName].UTF8String,
656 NULL);
657
658 return self;
659 }
660
661
662 - (void)obtainInfo
663 {
664 _deviceName = IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey));
665 NSString *serial = (__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDSerialNumberKey));
666 if (!serial || [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]) {
667 serial = [NSString stringWithFormat:@"%04x%04x%08x",
668 [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDVendorIDKey)) unsignedIntValue],
669 [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue],
670 [(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDLocationIDKey)) unsignedIntValue]];
671 }
672 if (_serialSuffix) {
673 _uniqueID = [NSString stringWithFormat:@"%@-%@", serial, _serialSuffix];
674 return;
675 }
676 _uniqueID = serial;
677 }
678
679 - (JOYControllerCombinedType)combinedControllerType
680 {
681 return _parent? JOYControllerCombinedTypeComponent : JOYControllerCombinedTypeSingle;
682 }
683
684 - (NSString *)description
685 {
686 return [NSString stringWithFormat:@"<%@: %p, %@, %@>", self.className, self, self.deviceName, self.uniqueID];
687 }
688
689 - (NSArray<JOYButton *> *)buttons
690 {
691 NSMutableArray *ret = [[_buttons allValues] mutableCopy];
692 [ret addObjectsFromArray:_axisEmulatedButtons.allValues];
693 for (NSArray *array in _axes2DEmulatedButtons.allValues) {
694 [ret addObjectsFromArray:array];
695 }
696 for (NSArray *array in _hatEmulatedButtons.allValues) {
697 [ret addObjectsFromArray:array];
698 }
699 return ret;
700 }
701
702 - (NSArray<JOYAxis *> *)axes
703 {
704 return [_axes allValues];
705 }
706
707 - (NSArray<JOYAxes2D *> *)axes2D
708 {
709 return [[NSSet setWithArray:[_axes2D allValues]] allObjects];
710 }
711
712 - (NSArray<JOYAxes3D *> *)axes3D
713 {
714 return [[NSSet setWithArray:[_axes3D allValues]] allObjects];
715 }
716
717 - (NSArray<JOYHat *> *)hats
718 {
719 return [_hats allValues];
720 }
721
722 - (void)gotReport:(NSData *)report
723 {
724 JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)];
725 if (element) {
726 [element updateValue:report];
727
728 NSArray<JOYElement *> *subElements = _multiElements[element];
729 if (subElements) {
730 for (JOYElement *subElement in subElements) {
731 [self _elementChanged:subElement];
732 }
733 }
734 }
735 dispatch_async(_rumbleQueue, ^{
736 [self updateRumble];
737 });
738 }
739
740 - (void)elementChanged:(IOHIDElementRef)element
741 {
742 JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))];
743 if (_element) {
744 [self _elementChanged:_element];
745 }
746 else {
747 //NSLog(@"Unhandled usage %x (Cookie: %x, Usage: %x)", IOHIDElementGetUsage(element), IOHIDElementGetCookie(element), IOHIDElementGetUsage(element));
748 }
749 }
750
751 - (void)_elementChanged:(JOYElement *)element
752 {
753 if (element == _connectedElement) {
754 bool old = self.connected;
755 _logicallyConnected = _connectedElement.value != _connectedElement.min;
756 if (!old && self.connected) {
757 for (id<JOYListener> listener in listeners) {
758 if ([listener respondsToSelector:@selector(controllerConnected:)]) {
759 [listener controllerConnected:self];
760 }
761 }
762 }
763 else if (old && !self.connected) {
764 [_parent breakApart];
765 for (id<JOYListener> listener in listeners) {
766 if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
767 [listener controllerDisconnected:self];
768 }
769 }
770 }
771 }
772
773 if (!self.connected) return;
774 {
775 JOYButton *button = _buttons[element];
776 if (button) {
777 if ([button updateState]) {
778 for (id<JOYListener> listener in listeners) {
779 if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
780 [listener controller:_parent ?: self buttonChangedState:button];
781 }
782 }
783 }
784 return;
785 }
786 }
787
788
789 {
790 JOYAxis *axis = _axes[element];
791 if (axis) {
792 if ([axis updateState]) {
793 for (id<JOYListener> listener in listeners) {
794 if ([listener respondsToSelector:@selector(controller:movedAxis:)]) {
795 [listener controller:_parent ?: self movedAxis:axis];
796 }
797 }
798 JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
799 if ([button updateStateFromAxis:axis]) {
800 for (id<JOYListener> listener in listeners) {
801 if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
802 [listener controller:_parent ?: self buttonChangedState:button];
803 }
804 }
805 }
806 }
807 return;
808 }
809 }
810
811 {
812 JOYAxes2D *axes = _axes2D[element];
813 if (axes) {
814 if ([axes updateState]) {
815 for (id<JOYListener> listener in listeners) {
816 if ([listener respondsToSelector:@selector(controller:movedAxes2D:)]) {
817 [listener controller:_parent ?: self movedAxes2D:axes];
818 }
819 }
820 NSArray <JOYEmulatedButton *> *buttons = _axes2DEmulatedButtons[@(axes.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
821 for (JOYEmulatedButton *button in buttons) {
822 if ([button updateStateFromAxes2D:axes]) {
823 for (id<JOYListener> listener in listeners) {
824 if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
825 [listener controller:_parent ?: self buttonChangedState:button];
826 }
827 }
828 }
829 }
830 }
831 return;
832 }
833 }
834
835 {
836 JOYAxes3D *axes = _axes3D[element];
837 if (axes) {
838 if ([axes updateState]) {
839 for (id<JOYListener> listener in listeners) {
840 if ([listener respondsToSelector:@selector(controller:movedAxes3D:)]) {
841 [listener controller:_parent ?: self movedAxes3D:axes];
842 }
843 }
844 }
845 return;
846 }
847 }
848
849 {
850 JOYHat *hat = _hats[element];
851 if (hat) {
852 if ([hat updateState]) {
853 for (id<JOYListener> listener in listeners) {
854 if ([listener respondsToSelector:@selector(controller:movedHat:)]) {
855 [listener controller:_parent ?: self movedHat:hat];
856 }
857 }
858
859 NSArray <JOYEmulatedButton *> *buttons = _hatEmulatedButtons[@(hat.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
860 for (JOYEmulatedButton *button in buttons) {
861 if ([button updateStateFromHat:hat]) {
862 for (id<JOYListener> listener in listeners) {
863 if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
864 [listener controller:_parent ?: self buttonChangedState:button];
865 }
866 }
867 }
868 }
869 }
870 return;
871 }
872 }
873 }
874
875 - (void)disconnected
876 {
877 _physicallyConnected = false;
878 [_parent breakApart];
879 if (_logicallyConnected && [exposedControllers containsObject:self]) {
880 for (id<JOYListener> listener in listeners) {
881 if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
882 [listener controllerDisconnected:self];
883 }
884 }
885 }
886 [exposedControllers removeObject:self];
887 [self setRumbleAmplitude:0];
888 dispatch_sync(_rumbleQueue, ^{
889 [self updateRumble];
890 });
891 _device = nil;
892 }
893
894 - (void)sendReport:(NSData *)report
895 {
896 if (!report.length) return;
897 if (!_device) return;
898 if (_deviceCantSendReports) return;
899 /* Some Macs fail to send reports to some devices, specifically the DS3, returning the bogus(?) error code 1 after
900 freezing for 5 seconds. Stop sending reports if that's the case. */
901 if (IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length) == 1) {
902 _deviceCantSendReports = true;
903 NSLog(@"This Mac appears to be incapable of sending output reports to %@", self);
904 }
905 }
906
907 - (void) sendDualSenseOutput
908 {
909 if (_isUSBDualSense) {
910 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB length:_lastVendorSpecificOutput.dualsenseOutput.bluetoothSpecific - &_lastVendorSpecificOutput.dualsenseOutput.reportIDOnUSB]];
911 return;
912 }
913 _lastVendorSpecificOutput.dualsenseOutput.sequence += 0x10;
914 static const uint32_t table[] = {
915 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
916 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
917 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
918 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
919 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
920 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
921 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
922 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
923 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
924 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
925 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
926 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
927 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
928 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
929 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
930 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
931 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
932 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
933 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
934 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
935 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
936 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
937 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
938 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
939 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
940 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
941 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
942 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
943 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
944 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
945 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
946 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
947 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
948 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
949 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
950 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
951 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
952 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
953 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
954 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
955 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
956 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
957 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
958 };
959
960 const uint8_t *byte = (void *)&_lastVendorSpecificOutput.dualsenseOutput;
961 uint32_t size = sizeof(_lastVendorSpecificOutput.dualsenseOutput) - 4;
962 uint32_t ret = 0xFFFFFFFF;
963 ret = table[(ret ^ 0xA2) & 0xFF] ^ (ret >> 8);
964
965 while (size--) {
966 ret = table[(ret ^ *byte++) & 0xFF] ^ (ret >> 8);
967 }
968
969 _lastVendorSpecificOutput.dualsenseOutput.crc32 = ~ret;
970
971 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.dualsenseOutput length:sizeof(_lastVendorSpecificOutput.dualsenseOutput)]];
972 }
973
974 - (uint8_t)LEDMaskForPlayer:(unsigned)player
975 {
976 if (_isDualShock3) {
977 return 2 << player;
978 }
979 if (_isDualSense) {
980 switch (player) {
981 case 0: return 0x04;
982 case 1: return 0x0A;
983 case 2: return 0x15;
984 case 3: return 0x1B;
985 default: return 0;
986 }
987 }
988 return 1 << player;
989 }
990
991 - (void)setPlayerLEDs:(uint8_t)mask
992 {
993 if (mask == _playerLEDs) {
994 return;
995 }
996 _playerLEDs = mask;
997 if (_isSwitch) {
998 _lastVendorSpecificOutput.switchPacket.reportID = 0x1; // Rumble and LEDs
999 _lastVendorSpecificOutput.switchPacket.sequence++;
1000 _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
1001 _lastVendorSpecificOutput.switchPacket.command = 0x30; // LED
1002 _lastVendorSpecificOutput.switchPacket.commandData[0] = mask & 0xF;
1003 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
1004 }
1005 else if (_isDualShock3) {
1006 _lastVendorSpecificOutput.ds3Output.reportID = 1;
1007 _lastVendorSpecificOutput.ds3Output.ledsEnabled = (mask & 0x1F);
1008 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]];
1009 }
1010 else if (_isDualSense) {
1011 _lastVendorSpecificOutput.dualsenseOutput.playerLEDs = mask & 0x1F;
1012 [self sendDualSenseOutput];
1013 }
1014 }
1015
1016 - (void)updateRumble
1017 {
1018 if (!self.connected) {
1019 return;
1020 }
1021 if (!_rumbleElement && !_isSwitch && !_isDualShock3 && !_isDualSense) {
1022 return;
1023 }
1024 if (_rumbleElement.max == 1 && _rumbleElement.min == 0) {
1025 double ampToSend = _rumbleCounter < round(_rumbleAmplitude * PWM_RESOLUTION);
1026 if (ampToSend != _sentRumbleAmp) {
1027 [_rumbleElement setValue:ampToSend];
1028 _sentRumbleAmp = ampToSend;
1029 }
1030 _rumbleCounter += round(_rumbleAmplitude * PWM_RESOLUTION);
1031 if (_rumbleCounter >= PWM_RESOLUTION) {
1032 _rumbleCounter -= PWM_RESOLUTION;
1033 }
1034 }
1035 else {
1036 if (_rumbleAmplitude == _sentRumbleAmp) {
1037 return;
1038 }
1039 _sentRumbleAmp = _rumbleAmplitude;
1040 if (_isSwitch) {
1041 double frequency = 144;
1042 double amp = _rumbleAmplitude;
1043
1044 uint8_t highAmp = amp * 0x64;
1045 uint8_t lowAmp = amp * 0x32 + 0x40;
1046 if (frequency < 0) frequency = 0;
1047 if (frequency > 1252) frequency = 1252;
1048 uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0);
1049
1050 uint16_t highFreq = (encodedFrequency - 0x60) * 4;
1051 uint8_t lowFreq = encodedFrequency - 0x40;
1052
1053 //if (frequency < 82 || frequency > 312) {
1054 if (amp) {
1055 highAmp = 0;
1056 }
1057
1058 if (frequency < 40 || frequency > 626) {
1059 lowAmp = 0;
1060 }
1061
1062 _lastVendorSpecificOutput.switchPacket.rumbleData[0] = _lastVendorSpecificOutput.switchPacket.rumbleData[4] = highFreq & 0xFF;
1063 _lastVendorSpecificOutput.switchPacket.rumbleData[1] = _lastVendorSpecificOutput.switchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1);
1064 _lastVendorSpecificOutput.switchPacket.rumbleData[2] = _lastVendorSpecificOutput.switchPacket.rumbleData[6] = lowFreq;
1065 _lastVendorSpecificOutput.switchPacket.rumbleData[3] = _lastVendorSpecificOutput.switchPacket.rumbleData[7] = lowAmp;
1066
1067
1068 _lastVendorSpecificOutput.switchPacket.reportID = 0x10; // Rumble only
1069 _lastVendorSpecificOutput.switchPacket.sequence++;
1070 _lastVendorSpecificOutput.switchPacket.sequence &= 0xF;
1071 _lastVendorSpecificOutput.switchPacket.command = 0; // LED
1072 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]];
1073 }
1074 else if (_isDualShock3) {
1075 _lastVendorSpecificOutput.ds3Output.reportID = 1;
1076 _lastVendorSpecificOutput.ds3Output.rumbleLeftDuration = _lastVendorSpecificOutput.ds3Output.rumbleRightDuration = _rumbleAmplitude? 0xFF : 0;
1077 _lastVendorSpecificOutput.ds3Output.rumbleLeftStrength = _lastVendorSpecificOutput.ds3Output.rumbleRightStrength = round(_rumbleAmplitude * 0xFF);
1078 [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.ds3Output length:sizeof(_lastVendorSpecificOutput.ds3Output)]];
1079 }
1080 else if (_isDualSense) {
1081 _lastVendorSpecificOutput.dualsenseOutput.rumbleLeftStrength = round(_rumbleAmplitude * _rumbleAmplitude * 0xFF);
1082 _lastVendorSpecificOutput.dualsenseOutput.rumbleRightStrength = _rumbleAmplitude > 0.25 ? round(pow(_rumbleAmplitude - 0.25, 2) * 0xFF) : 0;
1083 [self sendDualSenseOutput];
1084 }
1085 else {
1086 [_rumbleElement setValue:_rumbleAmplitude * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min];
1087 }
1088 }
1089 }
1090
1091 - (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */
1092 {
1093 if (amp < 0) amp = 0;
1094 if (amp > 1) amp = 1;
1095 _rumbleAmplitude = amp;
1096 }
1097
1098 - (bool)isConnected
1099 {
1100 return _logicallyConnected && _physicallyConnected;
1101 }
1102
1103 - (NSArray<JOYInput *> *)allInputs
1104 {
1105 NSMutableArray<JOYInput *> *ret = [NSMutableArray array];
1106 [ret addObjectsFromArray:self.buttons];
1107 [ret addObjectsFromArray:self.axes];
1108 [ret addObjectsFromArray:self.axes2D];
1109 [ret addObjectsFromArray:self.axes3D];
1110 [ret addObjectsFromArray:self.hats];
1111 return ret;
1112 }
1113
1114 - (void)setusesHorizontalJoyConGrip:(bool)usesHorizontalJoyConGrip
1115 {
1116 if (usesHorizontalJoyConGrip == _usesHorizontalJoyConGrip) return; // Nothing to do
1117 _usesHorizontalJoyConGrip = usesHorizontalJoyConGrip;
1118 switch (self.joyconType) {
1119 case JOYJoyConTypeLeft:
1120 case JOYJoyConTypeRight: {
1121 NSArray <JOYButton *> *buttons = _buttons.allValues; // not self.buttons to skip emulated buttons
1122 if (!usesHorizontalJoyConGrip) {
1123 for (JOYAxes2D *axes in self.axes2D) {
1124 axes.rotation = 0;
1125 }
1126 for (JOYAxes3D *axes in self.axes3D) {
1127 axes.rotation = 0;
1128 }
1129 for (JOYButton *button in buttons) {
1130 button.usage = button.originalUsage;
1131 }
1132 return;
1133 }
1134 for (JOYAxes2D *axes in self.axes2D) {
1135 axes.rotation = self.joyconType == JOYJoyConTypeLeft? -1 : 1;
1136 }
1137 for (JOYAxes3D *axes in self.axes3D) {
1138 axes.rotation = self.joyconType == JOYJoyConTypeLeft? -1 : 1;
1139 }
1140 if (self.joyconType == JOYJoyConTypeLeft) {
1141 for (JOYButton *button in buttons) {
1142 switch (button.originalUsage) {
1143 case JOYButtonUsageDPadLeft: button.usage = JOYButtonUsageB; break;
1144 case JOYButtonUsageDPadRight: button.usage = JOYButtonUsageX; break;
1145 case JOYButtonUsageDPadUp: button.usage = JOYButtonUsageY; break;
1146 case JOYButtonUsageDPadDown: button.usage = JOYButtonUsageA; break;
1147 default: button.usage = button.originalUsage; break;
1148 }
1149 }
1150 }
1151 else {
1152 for (JOYButton *button in buttons) {
1153 switch (button.originalUsage) {
1154 case JOYButtonUsageY: button.usage = JOYButtonUsageX; break;
1155 case JOYButtonUsageA: button.usage = JOYButtonUsageB; break;
1156 case JOYButtonUsageX: button.usage = JOYButtonUsageA; break;
1157 case JOYButtonUsageB: button.usage = JOYButtonUsageY; break;
1158 default: button.usage = button.originalUsage; break;
1159 }
1160 }
1161 }
1162 }
1163 default:
1164 return;
1165 }
1166 }
1167
1168 + (void)controllerAdded:(IOHIDDeviceRef) device
1169 {
1170 NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
1171 NSDictionary *hacks = hacksByName[name];
1172 if (!hacks) {
1173 hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))];
1174 }
1175 NSArray *filters = hacks[JOYReportIDFilters];
1176 JOYController *controller = nil;
1177 if (filters) {
1178 controller = [[JOYMultiplayerController alloc] initWithDevice:device
1179 reportIDFilters:filters
1180 hacks:hacks];
1181 }
1182 else {
1183 controller = [[JOYController alloc] initWithDevice:device hacks:hacks];
1184 }
1185
1186 [controllers setObject:controller forKey:[NSValue valueWithPointer:device]];
1187 }
1188
1189 + (void)controllerRemoved:(IOHIDDeviceRef) device
1190 {
1191 [[controllers objectForKey:[NSValue valueWithPointer:device]] disconnected];
1192 [controllers removeObjectForKey:[NSValue valueWithPointer:device]];
1193 }
1194
1195 + (NSArray<JOYController *> *)allControllers
1196 {
1197 return exposedControllers;
1198 }
1199
1200 + (void)load
1201 {
1202 #import "ControllerConfiguration.inc"
1203 }
1204
1205 +(void)registerListener:(id<JOYListener>)listener
1206 {
1207 [listeners addObject:listener];
1208 }
1209
1210 +(void)unregisterListener:(id<JOYListener>)listener
1211 {
1212 [listeners removeObject:listener];
1213 }
1214
1215 + (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options
1216 {
1217 axes2DEmulateButtons = [options[JOYAxes2DEmulateButtonsKey] boolValue];
1218 hatsEmulateButtons = [options[JOYHatsEmulateButtonsKey] boolValue];
1219
1220 controllers = [NSMutableDictionary dictionary];
1221 exposedControllers = [NSMutableArray array];
1222 NSArray *array = @[
1223 CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
1224 CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
1225 CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController),
1226 @{@kIOHIDDeviceUsagePageKey: @(kHIDPage_Game)},
1227 ];
1228
1229 listeners = [NSMutableSet set];
1230 static IOHIDManagerRef manager = nil;
1231 if (manager) {
1232 CFRelease(manager); // Stop the previous session
1233 }
1234 manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
1235
1236 if (!manager) return;
1237 if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone)) {
1238 CFRelease(manager);
1239 return;
1240 }
1241
1242 IOHIDManagerSetDeviceMatchingMultiple(manager, (__bridge CFArrayRef)array);
1243 IOHIDManagerRegisterDeviceMatchingCallback(manager, HIDDeviceAdded, NULL);
1244 IOHIDManagerRegisterDeviceRemovalCallback(manager, HIDDeviceRemoved, NULL);
1245 IOHIDManagerScheduleWithRunLoop(manager, [runloop getCFRunLoop], kCFRunLoopDefaultMode);
1246 }
1247
1248 - (void)dealloc
1249 {
1250 if (_device) {
1251 CFRelease(_device);
1252 _device = NULL;
1253 }
1254 }
1255 @end
1256
1257
1258 @implementation JOYCombinedController
1259 - (instancetype)initWithChildren:(NSArray<JOYController *> *)children
1260 {
1261 self = [super init];
1262 // Sorting makes the device name and unique id consistent
1263 _children = [children sortedArrayUsingComparator:^NSComparisonResult(JOYController *a, JOYController *b) {
1264 return [a.uniqueID compare:b.uniqueID];
1265 }];
1266
1267 if (_children.count == 0) return nil;
1268
1269 for (JOYController *child in _children) {
1270 if (child.combinedControllerType != JOYControllerCombinedTypeSingle) {
1271 NSLog(@"Cannot combine non-single controller %@", child);
1272 return nil;
1273 }
1274 if (![exposedControllers containsObject:child]) {
1275 NSLog(@"Cannot combine unexposed controller %@", child);
1276 return nil;
1277 }
1278 }
1279
1280 unsigned index = 0;
1281 for (JOYController *child in _children) {
1282 for (id<JOYListener> listener in listeners) {
1283 if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
1284 [listener controllerDisconnected:child];
1285 }
1286 }
1287 child->_parent = self;
1288 for (JOYInput *input in child.allInputs) {
1289 input.combinedIndex = index;
1290 }
1291 index++;
1292 [exposedControllers removeObject:child];
1293 }
1294
1295 [exposedControllers addObject:self];
1296 for (id<JOYListener> listener in listeners) {
1297 if ([listener respondsToSelector:@selector(controllerConnected:)]) {
1298 [listener controllerConnected:self];
1299 }
1300 }
1301
1302 return self;
1303 }
1304
1305 - (void)breakApart
1306 {
1307 if (![exposedControllers containsObject:self]) {
1308 // Already broken apart
1309 return;
1310 }
1311
1312 [exposedControllers removeObject:self];
1313 for (id<JOYListener> listener in listeners) {
1314 if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
1315 [listener controllerDisconnected:self];
1316 }
1317 }
1318
1319 for (JOYController *child in _children) {
1320 child->_parent = nil;
1321 for (JOYInput *input in child.allInputs) {
1322 input.combinedIndex = 0;
1323 }
1324 if (!child.connected) break;
1325 [exposedControllers addObject:child];
1326 for (id<JOYListener> listener in listeners) {
1327 if ([listener respondsToSelector:@selector(controllerConnected:)]) {
1328 [listener controllerConnected:child];
1329 }
1330 }
1331 }
1332 }
1333
1334 - (NSString *)deviceName
1335 {
1336 NSString *ret = nil;
1337 for (JOYController *child in _children) {
1338 if (ret) {
1339 ret = [ret stringByAppendingFormat:@" + %@", child.deviceName];
1340 }
1341 else {
1342 ret = child.deviceName;
1343 }
1344 }
1345 return ret;
1346 }
1347
1348 - (NSString *)uniqueID
1349 {
1350 NSString *ret = nil;
1351 for (JOYController *child in _children) {
1352 if (ret) {
1353 ret = [ret stringByAppendingFormat:@"+%@", child.uniqueID];
1354 }
1355 else {
1356 ret = child.uniqueID;
1357 }
1358 }
1359 return ret;
1360 }
1361
1362 - (JOYControllerCombinedType)combinedControllerType
1363 {
1364 return JOYControllerCombinedTypeCombined;
1365 }
1366
1367 - (NSArray<JOYButton *> *)buttons
1368 {
1369 NSArray<JOYButton *> *ret = nil;
1370 for (JOYController *child in _children) {
1371 if (ret) {
1372 ret = [ret arrayByAddingObjectsFromArray:child.buttons];
1373 }
1374 else {
1375 ret = child.buttons;
1376 }
1377 }
1378 return ret;
1379 }
1380
1381 - (NSArray<JOYAxis *> *)axes
1382 {
1383 NSArray<JOYAxis *> *ret = nil;
1384 for (JOYController *child in _children) {
1385 if (ret) {
1386 ret = [ret arrayByAddingObjectsFromArray:child.axes];
1387 }
1388 else {
1389 ret = child.axes;
1390 }
1391 }
1392 return ret;
1393 }
1394
1395 - (NSArray<JOYAxes2D *> *)axes2D
1396 {
1397 NSArray<JOYAxes2D *> *ret = nil;
1398 for (JOYController *child in _children) {
1399 if (ret) {
1400 ret = [ret arrayByAddingObjectsFromArray:child.axes2D];
1401 }
1402 else {
1403 ret = child.axes2D;
1404 }
1405 }
1406 return ret;
1407 }
1408
1409 - (NSArray<JOYAxes3D *> *)axes3D
1410 {
1411 NSArray<JOYAxes3D *> *ret = nil;
1412 for (JOYController *child in _children) {
1413 if (ret) {
1414 ret = [ret arrayByAddingObjectsFromArray:child.axes3D];
1415 }
1416 else {
1417 ret = child.axes3D;
1418 }
1419 }
1420 return ret;
1421 }
1422
1423 - (NSArray<JOYHat *> *)hats
1424 {
1425 NSArray<JOYHat *> *ret = nil;
1426 for (JOYController *child in _children) {
1427 if (ret) {
1428 ret = [ret arrayByAddingObjectsFromArray:child.hats];
1429 }
1430 else {
1431 ret = child.hats;
1432 }
1433 }
1434 return ret;
1435 }
1436
1437 - (void)setRumbleAmplitude:(double)amp
1438 {
1439 for (JOYController *child in _children) {
1440 [child setRumbleAmplitude:amp];
1441 }
1442 }
1443
1444 - (void)setPlayerLEDs:(uint8_t)mask
1445 {
1446 // Mask is actually just the player ID in a combined controller to
1447 // allow combining controllers with different LED layouts
1448 for (JOYController *child in _children) {
1449 [child setPlayerLEDs:[child LEDMaskForPlayer:mask]];
1450 }
1451 }
1452
1453 - (uint8_t)LEDMaskForPlayer:(unsigned)player
1454 {
1455 return player;
1456 }
1457
1458 - (bool)isConnected
1459 {
1460 if (![exposedControllers containsObject:self]) {
1461 // Controller was broken apart
1462 return false;
1463 }
1464
1465 for (JOYController *child in _children) {
1466 if (!child.isConnected) {
1467 return false; // Should never happen
1468 }
1469 }
1470
1471 return true;
1472 }
1473
1474 - (JOYJoyConType)joyconType
1475 {
1476 if (_children.count != 2) return JOYJoyConTypeNone;
1477 if (_children[0].joyconType == JOYJoyConTypeLeft &&
1478 _children[1].joyconType == JOYJoyConTypeRight) {
1479 return JOYJoyConTypeDual;
1480 }
1481
1482 if (_children[1].joyconType == JOYJoyConTypeLeft &&
1483 _children[0].joyconType == JOYJoyConTypeRight) {
1484 return JOYJoyConTypeDual;
1485 }
1486 return JOYJoyConTypeNone;
1487 }
1488
1489 @end
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.