diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc index c8b49cc..35a6cff 100644 --- a/JoyKit/ControllerConfiguration.inc +++ b/JoyKit/ControllerConfiguration.inc @@ -123,17 +123,17 @@ hacksByName = @{ JOYActivationReport: [NSData dataWithBytes:(uint8_t[]){0x13} length:1], - JOYSubElementStructs: @{ + JOYCustomReports: @{ // Rumble - @(1364): @[ + @(-17): @[ @{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(2), @"size":@1, @"offset":@8, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(3), @"size":@1, @"offset":@16, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, @{@"reportID": @(4), @"size":@1, @"offset":@24, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1}, ], - @(11): @[ + @(33): @[ // Player 1 @@ -141,11 +141,11 @@ hacksByName = @{ @{@"reportID": @(1), @"size":@1, @"offset":@8, @"usagePage":@(kHIDPage_Button), @"usage":@1}, @{@"reportID": @(1), @"size":@1, @"offset":@9, @"usagePage":@(kHIDPage_Button), @"usage":@2}, - @{@"reportID": @(1), @"size":@1, @"offset":@10, @"usagePage":@(kHIDPage_Button), @"usage":@3}, - @{@"reportID": @(1), @"size":@1, @"offset":@11, @"usagePage":@(kHIDPage_Button), @"usage":@4}, + @{@"reportID": @(1), @"size":@1, @"offset":@10,@"usagePage":@(kHIDPage_Button), @"usage":@3}, + @{@"reportID": @(1), @"size":@1, @"offset":@11,@"usagePage":@(kHIDPage_Button), @"usage":@4}, - @{@"reportID": @(1), @"size":@1, @"offset":@12, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, - @{@"reportID": @(1), @"size":@1, @"offset":@13, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, + @{@"reportID": @(1), @"size":@1, @"offset":@12, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)}, + @{@"reportID": @(1), @"size":@1, @"offset":@13, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)}, @{@"reportID": @(1), @"size":@1, @"offset":@14, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)}, @{@"reportID": @(1), @"size":@1, @"offset":@15, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)}, diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index 459896d..131441c 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -2,6 +2,8 @@ #import "JOYMultiplayerController.h" #import "JOYElement.h" #import "JOYSubElement.h" +#import "JOYFullReportElement.h" + #import "JOYEmulatedButton.h" #include @@ -12,7 +14,7 @@ static NSString const *JOYReportIDFilters = @"JOYReportIDFilters"; static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping"; static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping"; static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping"; -static NSString const *JOYSubElementStructs = @"JOYSubElementStructs"; +static NSString const *JOYCustomReports = @"JOYCustomReports"; static NSString const *JOYIsSwitch = @"JOYIsSwitch"; static NSString const *JOYRumbleUsage = @"JOYRumbleUsage"; static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage"; @@ -41,6 +43,8 @@ static NSLock *globalPWMThreadLock; + (void)controllerAdded:(IOHIDDeviceRef) device; + (void)controllerRemoved:(IOHIDDeviceRef) device; - (void)elementChanged:(IOHIDElementRef) element; +- (void)gotReport:(NSData *)report; + @end @interface JOYButton () @@ -86,6 +90,11 @@ static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef [(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)]; } +static void HIDReport(void *context, IOReturn result, void *sender, IOHIDReportType type, + uint32_t reportID, uint8_t *report, CFIndex reportLength) +{ + [(__bridge JOYController *)context gotReport:[[NSData alloc] initWithBytesNoCopy:report length:reportLength freeWhenDone:NO]]; +} typedef struct __attribute__((packed)) { uint8_t reportID; @@ -102,7 +111,8 @@ typedef struct __attribute__((packed)) { NSMutableDictionary *_axes; NSMutableDictionary *_axes2D; NSMutableDictionary *_hats; - NSMutableDictionary *> *_multiElements; + NSMutableDictionary *_fullReportElements; + NSMutableDictionary *> *_multiElements; // Button emulation NSMutableDictionary *_axisEmulatedButtons; @@ -121,14 +131,182 @@ typedef struct __attribute__((packed)) { bool _logicallyConnected; bool _rumblePWMThreadRunning; volatile bool _forceStopPWMThread; + + NSDictionary *_hacks; + NSMutableData *_lastReport; + + // Used when creating inputs + JOYElement *_previousAxisElement; + } -- (instancetype)initWithDevice:(IOHIDDeviceRef) device +- (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks { - return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil]; + return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil hacks:hacks]; } -- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix +-(void)createOutputForElement:(JOYElement *)element +{ + uint16_t rumbleUsagePage = (uint16_t)[_hacks[JOYRumbleUsagePage] unsignedIntValue]; + uint16_t rumbleUsage = (uint16_t)[_hacks[JOYRumbleUsage] unsignedIntValue]; + + if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) { + if (_hacks[JOYRumbleMin]) { + element.min = [_hacks[JOYRumbleMin] unsignedIntValue]; + } + if (_hacks[JOYRumbleMax]) { + element.max = [_hacks[JOYRumbleMax] unsignedIntValue]; + } + _rumbleElement = element; + } +} + +-(void)createInputForElement:(JOYElement *)element +{ + uint16_t connectedUsagePage = (uint16_t)[_hacks[JOYConnectedUsagePage] unsignedIntValue]; + uint16_t connectedUsage = (uint16_t)[_hacks[JOYConnectedUsage] unsignedIntValue]; + + if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) { + _connectedElement = element; + _logicallyConnected = element.value != element.min; + return; + } + + if (element.usagePage == kHIDPage_Button) { + button: { + JOYButton *button = [[JOYButton alloc] initWithElement: element]; + [_buttons setObject:button forKey:element]; + NSNumber *replacementUsage = _hacks[JOYButtonUsageMapping][@(button.usage)]; + if (replacementUsage) { + button.usage = [replacementUsage unsignedIntValue]; + } + return; + } + } + else if (element.usagePage == kHIDPage_GenericDesktop) { + NSDictionary *axisGroups = @{ + @(kHIDUsage_GD_X): @(0), + @(kHIDUsage_GD_Y): @(0), + @(kHIDUsage_GD_Z): @(1), + @(kHIDUsage_GD_Rx): @(2), + @(kHIDUsage_GD_Ry): @(2), + @(kHIDUsage_GD_Rz): @(1), + }; + + axisGroups = _hacks[JOYAxisGroups] ?: axisGroups; + + switch (element.usage) { + case kHIDUsage_GD_X: + case kHIDUsage_GD_Y: + case kHIDUsage_GD_Z: + case kHIDUsage_GD_Rx: + case kHIDUsage_GD_Ry: + case kHIDUsage_GD_Rz: { + + JOYElement *other = _previousAxisElement; + _previousAxisElement = element; + if (!other) goto single; + if (other.usage >= element.usage) goto single; + if (other.reportID != element.reportID) goto single; + if (![axisGroups[@(other.usage)] isEqualTo: axisGroups[@(element.usage)]]) goto single; + if (other.parentID != element.parentID) goto single; + + JOYAxes2D *axes = nil; + if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [_hacks[JOYSwapZRz] boolValue]) { + axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other]; + } + else { + axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element]; + } + NSNumber *replacementUsage = _hacks[JOYAxes2DUsageMapping][@(axes.usage)]; + if (replacementUsage) { + axes.usage = [replacementUsage unsignedIntValue]; + } + + [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)]; + [_axes removeObjectForKey:other]; + _previousAxisElement = nil; + _axes2D[other] = axes; + _axes2D[element] = axes; + + if (axes2DEmulateButtons) { + _axes2DEmulatedButtons[@(axes.uniqueID)] = @[ + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:axes.uniqueID | 0x100000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:axes.uniqueID | 0x200000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:axes.uniqueID | 0x300000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:axes.uniqueID | 0x400000000L], + ]; + } + + /* + for (NSArray *group in axes2d) { + break; + IOHIDElementRef first = (__bridge IOHIDElementRef)group[0]; + IOHIDElementRef second = (__bridge IOHIDElementRef)group[1]; + if (IOHIDElementGetUsage(first) > element.usage) continue; + if (IOHIDElementGetUsage(second) > element.usage) continue; + if (IOHIDElementGetReportID(first) != IOHIDElementGetReportID(element)) continue; + if ((IOHIDElementGetUsage(first) - kHIDUsage_GD_X) / 3 != (element.usage - kHIDUsage_GD_X) / 3) continue; + if (IOHIDElementGetParent(first) != IOHIDElementGetParent(element)) continue; + + [axes2d removeObject:group]; + [axes3d addObject:@[(__bridge id)first, (__bridge id)second, _element]]; + found = true; + break; + }*/ + break; + } + single: + case kHIDUsage_GD_Slider: + case kHIDUsage_GD_Dial: + case kHIDUsage_GD_Wheel: { + JOYAxis *axis = [[JOYAxis alloc] initWithElement: element]; + [_axes setObject:axis forKey:element]; + + NSNumber *replacementUsage = _hacks[JOYAxisUsageMapping][@(axis.usage)]; + if (replacementUsage) { + axis.usage = [replacementUsage unsignedIntValue]; + } + + if (axesEmulateButtons && axis.usage >= JOYAxisUsageL1 && axis.usage <= JOYAxisUsageR3) { + _axisEmulatedButtons[@(axis.uniqueID)] = + [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageL1 + JOYButtonUsageL1 uniqueID:axis.uniqueID]; + } + + if (axesEmulateButtons && axis.usage >= JOYAxisUsageGeneric0) { + _axisEmulatedButtons[@(axis.uniqueID)] = + [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0 uniqueID:axis.uniqueID]; + } + + break; + } + case kHIDUsage_GD_DPadUp: + case kHIDUsage_GD_DPadDown: + case kHIDUsage_GD_DPadRight: + case kHIDUsage_GD_DPadLeft: + case kHIDUsage_GD_Start: + case kHIDUsage_GD_Select: + case kHIDUsage_GD_SystemMainMenu: + goto button; + + case kHIDUsage_GD_Hatswitch: { + JOYHat *hat = [[JOYHat alloc] initWithElement: element]; + [_hats setObject:hat forKey:element]; + if (hatsEmulateButtons) { + _hatEmulatedButtons[@(hat.uniqueID)] = @[ + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:hat.uniqueID | 0x100000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:hat.uniqueID | 0x200000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:hat.uniqueID | 0x300000000L], + [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:hat.uniqueID | 0x400000000L], + ]; + } + break; + } + } + } +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks { self = [super init]; if (!self) return self; @@ -149,229 +327,109 @@ typedef struct __attribute__((packed)) { _axisEmulatedButtons = [NSMutableDictionary dictionary]; _axes2DEmulatedButtons = [NSMutableDictionary dictionary]; _hatEmulatedButtons = [NSMutableDictionary dictionary]; - _multiElements = [NSMutableDictionary dictionary]; _iokitToJOY = [NSMutableDictionary dictionary]; _rumblePWMThreadLock = [[NSLock alloc] init]; //NSMutableArray *axes3d = [NSMutableArray array]; - NSDictionary *axisGroups = @{ - @(kHIDUsage_GD_X): @(0), - @(kHIDUsage_GD_Y): @(0), - @(kHIDUsage_GD_Z): @(1), - @(kHIDUsage_GD_Rx): @(2), - @(kHIDUsage_GD_Ry): @(2), - @(kHIDUsage_GD_Rz): @(1), - }; - NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); - NSDictionary *hacks = hacksByName[name]; - if (!hacks) { - hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))]; + _hacks = hacks; + _isSwitch = [_hacks[JOYIsSwitch] boolValue]; + NSDictionary *customReports = hacks[JOYCustomReports]; + + if (hacks[JOYCustomReports]) { + _multiElements = [NSMutableDictionary dictionary]; + _fullReportElements = [NSMutableDictionary dictionary]; + _lastReport = [NSMutableData dataWithLength:MAX( + MAX( + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey)) unsignedIntValue], + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue] + ), + [(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxFeatureReportSizeKey)) unsignedIntValue] + )]; + IOHIDDeviceRegisterInputReportCallback(device, _lastReport.mutableBytes, _lastReport.length, HIDReport, (void *)self); + + for (NSNumber *_reportID in customReports) { + signed reportID = [_reportID intValue]; + bool isOutput = false; + if (reportID < 0) { + isOutput = true; + reportID = -reportID; + } + + JOYFullReportElement *element = [[JOYFullReportElement alloc] initWithDevice:device reportID:reportID]; + NSMutableArray *elements = [NSMutableArray array]; + for (NSDictionary *subElementDef in customReports[_reportID]) { + if (filter && subElementDef[@"reportID"] && ![filter containsObject:subElementDef[@"reportID"]]) continue; + JOYSubElement *subElement = [[JOYSubElement alloc] initWithRealElement:element + size:subElementDef[@"size"].unsignedLongValue + offset:subElementDef[@"offset"].unsignedLongValue + 8 // Compensate for the reportID + usagePage:subElementDef[@"usagePage"].unsignedLongValue + usage:subElementDef[@"usage"].unsignedLongValue + min:subElementDef[@"min"].unsignedIntValue + max:subElementDef[@"max"].unsignedIntValue]; + [elements addObject:subElement]; + if (isOutput) { + [self createOutputForElement:subElement]; + } + else { + [self createInputForElement:subElement]; + } + } + _multiElements[element] = elements; + if (!isOutput) { + _fullReportElements[@(reportID)] = element; + } + } } - axisGroups = hacks[JOYAxisGroups] ?: axisGroups; - _isSwitch = [hacks[JOYIsSwitch] boolValue]; - uint16_t rumbleUsagePage = (uint16_t)[hacks[JOYRumbleUsagePage] unsignedIntValue]; - uint16_t rumbleUsage = (uint16_t)[hacks[JOYRumbleUsage] unsignedIntValue]; - uint16_t connectedUsagePage = (uint16_t)[hacks[JOYConnectedUsagePage] unsignedIntValue]; - uint16_t connectedUsage = (uint16_t)[hacks[JOYConnectedUsage] unsignedIntValue]; - - JOYElement *previousAxisElement = nil; + id previous = nil; for (id _element in array) { if (_element == previous) continue; // Some elements are reported twice for some reason previous = _element; - NSArray *elements = nil; JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element]; - - NSArray*> *subElementDefs = hacks[JOYSubElementStructs][@(element.uniqueID)]; bool isOutput = false; - if (subElementDefs && element.uniqueID != element.parentID) { - elements = [NSMutableArray array]; - for (NSDictionary *virtualInput in subElementDefs) { - if (filter && virtualInput[@"reportID"] && ![filter containsObject:virtualInput[@"reportID"]]) continue; - [(NSMutableArray *)elements addObject:[[JOYSubElement alloc] initWithRealElement:element - size:virtualInput[@"size"].unsignedLongValue - offset:virtualInput[@"offset"].unsignedLongValue - usagePage:virtualInput[@"usagePage"].unsignedLongValue - usage:virtualInput[@"usage"].unsignedLongValue - min:virtualInput[@"min"].unsignedIntValue - max:virtualInput[@"max"].unsignedIntValue]]; - } - isOutput = IOHIDElementGetType((__bridge IOHIDElementRef)_element) == kIOHIDElementTypeOutput; - [_multiElements setObject:elements forKey:element]; + if (filter && ![filter containsObject:@(element.reportID)]) continue; + + switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) { + /* Handled */ + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + break; + case kIOHIDElementTypeOutput: + isOutput = true; + break; + /* Ignored */ + default: + case kIOHIDElementTypeInput_ScanCodes: + case kIOHIDElementTypeInput_NULL: + case kIOHIDElementTypeFeature: + case kIOHIDElementTypeCollection: + continue; + } + if ((!isOutput && customReports[@(element.reportID)]) || + (isOutput && customReports[@(-element.reportID)])) continue; + + + if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue; + + if (isOutput) { + [self createOutputForElement:element]; } else { - if (filter && ![filter containsObject:@(element.reportID)]) continue; - - switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) { - /* Handled */ - case kIOHIDElementTypeInput_Misc: - case kIOHIDElementTypeInput_Button: - case kIOHIDElementTypeInput_Axis: - break; - case kIOHIDElementTypeOutput: - isOutput = true; - break; - /* Ignored */ - default: - case kIOHIDElementTypeInput_ScanCodes: - case kIOHIDElementTypeInput_NULL: - case kIOHIDElementTypeFeature: - case kIOHIDElementTypeCollection: - continue; - } - if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue; - - elements = @[element]; + [self createInputForElement:element]; } _iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element; - for (JOYElement *element in elements) { - if (isOutput) { - if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) { - if (hacks[JOYRumbleMin]) { - element.min = [hacks[JOYRumbleMin] unsignedIntValue]; - } - if (hacks[JOYRumbleMax]) { - element.max = [hacks[JOYRumbleMax] unsignedIntValue]; - } - _rumbleElement = element; - } - continue; - } - else { - if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) { - _connectedElement = element; - _logicallyConnected = element.value != element.min; - continue; - } - } - - if (element.usagePage == kHIDPage_Button) { - button: { - JOYButton *button = [[JOYButton alloc] initWithElement: element]; - [_buttons setObject:button forKey:element]; - NSNumber *replacementUsage = hacks[JOYButtonUsageMapping][@(button.usage)]; - if (replacementUsage) { - button.usage = [replacementUsage unsignedIntValue]; - } - continue; - } - } - else if (element.usagePage == kHIDPage_GenericDesktop) { - switch (element.usage) { - case kHIDUsage_GD_X: - case kHIDUsage_GD_Y: - case kHIDUsage_GD_Z: - case kHIDUsage_GD_Rx: - case kHIDUsage_GD_Ry: - case kHIDUsage_GD_Rz: { - - JOYElement *other = previousAxisElement; - previousAxisElement = element; - if (!other) goto single; - if (other.usage >= element.usage) goto single; - if (other.reportID != element.reportID) goto single; - if (![axisGroups[@(other.usage)] isEqualTo: axisGroups[@(element.usage)]]) goto single; - if (other.parentID != element.parentID) goto single; - - JOYAxes2D *axes = nil; - if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [hacks[JOYSwapZRz] boolValue]) { - axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other]; - } - else { - axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element]; - } - NSNumber *replacementUsage = hacks[JOYAxes2DUsageMapping][@(axes.usage)]; - if (replacementUsage) { - axes.usage = [replacementUsage unsignedIntValue]; - } - - [_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)]; - [_axes removeObjectForKey:other]; - previousAxisElement = nil; - _axes2D[other] = axes; - _axes2D[element] = axes; - - if (axes2DEmulateButtons) { - _axes2DEmulatedButtons[@(axes.uniqueID)] = @[ - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:axes.uniqueID | 0x100000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:axes.uniqueID | 0x200000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:axes.uniqueID | 0x300000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:axes.uniqueID | 0x400000000L], - ]; - } - - /* - for (NSArray *group in axes2d) { - break; - IOHIDElementRef first = (__bridge IOHIDElementRef)group[0]; - IOHIDElementRef second = (__bridge IOHIDElementRef)group[1]; - if (IOHIDElementGetUsage(first) > element.usage) continue; - if (IOHIDElementGetUsage(second) > element.usage) continue; - if (IOHIDElementGetReportID(first) != IOHIDElementGetReportID(element)) continue; - if ((IOHIDElementGetUsage(first) - kHIDUsage_GD_X) / 3 != (element.usage - kHIDUsage_GD_X) / 3) continue; - if (IOHIDElementGetParent(first) != IOHIDElementGetParent(element)) continue; - - [axes2d removeObject:group]; - [axes3d addObject:@[(__bridge id)first, (__bridge id)second, _element]]; - found = true; - break; - }*/ - break; - } - single: - case kHIDUsage_GD_Slider: - case kHIDUsage_GD_Dial: - case kHIDUsage_GD_Wheel: { - JOYAxis *axis = [[JOYAxis alloc] initWithElement: element]; - [_axes setObject:axis forKey:element]; - - NSNumber *replacementUsage = hacks[JOYAxisUsageMapping][@(axis.usage)]; - if (replacementUsage) { - axis.usage = [replacementUsage unsignedIntValue]; - } - - if (axesEmulateButtons && axis.usage >= JOYAxisUsageL1 && axis.usage <= JOYAxisUsageR3) { - _axisEmulatedButtons[@(axis.uniqueID)] = - [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageL1 + JOYButtonUsageL1 uniqueID:axis.uniqueID]; - } - - if (axesEmulateButtons && axis.usage >= JOYAxisUsageGeneric0) { - _axisEmulatedButtons[@(axis.uniqueID)] = - [[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0 uniqueID:axis.uniqueID]; - } - - break; - } - case kHIDUsage_GD_DPadUp: - case kHIDUsage_GD_DPadDown: - case kHIDUsage_GD_DPadRight: - case kHIDUsage_GD_DPadLeft: - case kHIDUsage_GD_Start: - case kHIDUsage_GD_Select: - case kHIDUsage_GD_SystemMainMenu: - goto button; - - case kHIDUsage_GD_Hatswitch: { - JOYHat *hat = [[JOYHat alloc] initWithElement: element]; - [_hats setObject:hat forKey:element]; - if (hatsEmulateButtons) { - _hatEmulatedButtons[@(hat.uniqueID)] = @[ - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:hat.uniqueID | 0x100000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:hat.uniqueID | 0x200000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:hat.uniqueID | 0x300000000L], - [[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:hat.uniqueID | 0x400000000L], - ]; - } - break; - } - } - } + if (_isSwitch && element.reportID == 0x30) { + /* This report does not match its report descriptor (The descriptor ignores several fields) so + we can't use the elements in it directly.*/ + continue; } + } [exposedControllers addObject:self]; @@ -383,6 +441,11 @@ typedef struct __attribute__((packed)) { } } + if (_hacks[JOYActivationReport]) { + [self sendReport:hacks[JOYActivationReport]]; + } + + return self; } @@ -441,6 +504,21 @@ typedef struct __attribute__((packed)) { return [_hats allValues]; } +- (void)gotReport:(NSData *)report +{ + JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)]; + if (!element) return; + [element updateValue:report]; + + NSArray *subElements = _multiElements[element]; + if (subElements) { + for (JOYElement *subElement in subElements) { + [self _elementChanged:subElement]; + } + return; + } +} + - (void)elementChanged:(IOHIDElementRef)element { JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))]; @@ -473,16 +551,6 @@ typedef struct __attribute__((packed)) { } } - { - NSArray *subElements = _multiElements[element]; - if (subElements) { - for (JOYElement *subElement in subElements) { - [self _elementChanged:subElement]; - } - return; - } - } - if (!self.connected) return; { JOYButton *button = _buttons[element]; @@ -602,7 +670,7 @@ typedef struct __attribute__((packed)) { _lastSwitchPacket.sequence &= 0xF; _lastSwitchPacket.command = 0x30; // LED _lastSwitchPacket.commandData[0] = mask; - [self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; + //[self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]]; } } @@ -723,15 +791,13 @@ typedef struct __attribute__((packed)) { JOYController *controller = nil; if (filters) { controller = [[JOYMultiplayerController alloc] initWithDevice:device - reportIDFilters:filters]; + reportIDFilters:filters + hacks:hacks]; } else { - controller = [[JOYController alloc] initWithDevice:device]; - } - - if (hacks[JOYActivationReport]) { - [controller sendReport:hacks[JOYActivationReport]]; + controller = [[JOYController alloc] initWithDevice:device hacks:hacks]; } + [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; diff --git a/JoyKit/JOYFullReportElement.h b/JoyKit/JOYFullReportElement.h new file mode 100644 index 0000000..808644e --- /dev/null +++ b/JoyKit/JOYFullReportElement.h @@ -0,0 +1,10 @@ +#import +#include +#include "JOYElement.h" + +@interface JOYFullReportElement : JOYElement +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportID:(unsigned)reportID; +- (void)updateValue:(NSData *)value; +@end + + diff --git a/JoyKit/JOYFullReportElement.m b/JoyKit/JOYFullReportElement.m new file mode 100644 index 0000000..c8efb27 --- /dev/null +++ b/JoyKit/JOYFullReportElement.m @@ -0,0 +1,73 @@ +#import "JOYFullReportElement.h" +#include + +@implementation JOYFullReportElement +{ + IOHIDDeviceRef _device; + NSData *_data; + unsigned _reportID; + size_t _capacity; +} + +- (uint32_t)uniqueID +{ + return _reportID ^ 0xFFFF; +} + +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportID:(unsigned)reportID +{ + if ((self = [super init])) { + _data = [[NSMutableData alloc] initWithLength:[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDMaxOutputReportSizeKey)) unsignedIntValue]]; + *(uint8_t *)(((NSMutableData *)_data).mutableBytes) = reportID; + _reportID = reportID; + _device = device; + } + return self; +} + +- (int32_t)value +{ + [self doesNotRecognizeSelector:_cmd]; + return 0; +} + +- (NSData *)dataValue +{ + return _data; +} + +- (IOReturn)setValue:(uint32_t)value +{ + [self doesNotRecognizeSelector:_cmd]; + return -1; +} + +- (IOReturn)setDataValue:(NSData *)value +{ + + [self updateValue:value]; + return IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, _reportID, [_data bytes], [_data length]);; +} + +- (void)updateValue:(NSData *)value +{ + _data = [value copy]; +} + +/* For use as a dictionary key */ + +- (NSUInteger)hash +{ + return self.uniqueID; +} + +- (BOOL)isEqual:(id)object +{ + return self.uniqueID == self.uniqueID; +} + +- (id)copyWithZone:(nullable NSZone *)zone; +{ + return self; +} +@end diff --git a/JoyKit/JOYMultiplayerController.h b/JoyKit/JOYMultiplayerController.h index 24004f5..44d7421 100644 --- a/JoyKit/JOYMultiplayerController.h +++ b/JoyKit/JOYMultiplayerController.h @@ -2,7 +2,7 @@ #include @interface JOYMultiplayerController : JOYController -- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters; +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters hacks:hacks; @end diff --git a/JoyKit/JOYMultiplayerController.m b/JoyKit/JOYMultiplayerController.m index 50840a1..a31ae92 100644 --- a/JoyKit/JOYMultiplayerController.m +++ b/JoyKit/JOYMultiplayerController.m @@ -1,7 +1,7 @@ #import "JOYMultiplayerController.h" @interface JOYController () -- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix; +- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks; - (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value; - (void)disconnected; - (void)sendReport:(NSData *)report; @@ -12,7 +12,7 @@ NSMutableArray *_children; } -- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters +- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray *>*) reportIDFilters hacks:(NSDictionary *)hacks; { self = [super init]; if (!self) return self; @@ -21,7 +21,7 @@ unsigned index = 1; for (NSArray *filter in reportIDFilters) { - JOYController *controller = [[JOYController alloc] initWithDevice:device reportIDFilter:filter serialSuffix:[NSString stringWithFormat:@"%d", index]]; + JOYController *controller = [[JOYController alloc] initWithDevice:device reportIDFilter:filter serialSuffix:[NSString stringWithFormat:@"%d", index] hacks:hacks]; [_children addObject:controller]; index++; }