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