Rewrite the "Sub Elements" design into a more powerful Custom Report design that can overwrite an entire report structure of a sepcific report by its ID
This commit is contained in:
parent
e5302a9b1e
commit
78e2b94cb5
@ -123,17 +123,17 @@ hacksByName = @{
|
|||||||
|
|
||||||
JOYActivationReport: [NSData dataWithBytes:(uint8_t[]){0x13} length:1],
|
JOYActivationReport: [NSData dataWithBytes:(uint8_t[]){0x13} length:1],
|
||||||
|
|
||||||
JOYSubElementStructs: @{
|
JOYCustomReports: @{
|
||||||
|
|
||||||
// Rumble
|
// Rumble
|
||||||
@(1364): @[
|
@(-17): @[
|
||||||
@{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1},
|
@{@"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": @(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": @(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},
|
@{@"reportID": @(4), @"size":@1, @"offset":@24, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1},
|
||||||
],
|
],
|
||||||
|
|
||||||
@(11): @[
|
@(33): @[
|
||||||
|
|
||||||
// Player 1
|
// Player 1
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
#import "JOYMultiplayerController.h"
|
#import "JOYMultiplayerController.h"
|
||||||
#import "JOYElement.h"
|
#import "JOYElement.h"
|
||||||
#import "JOYSubElement.h"
|
#import "JOYSubElement.h"
|
||||||
|
#import "JOYFullReportElement.h"
|
||||||
|
|
||||||
#import "JOYEmulatedButton.h"
|
#import "JOYEmulatedButton.h"
|
||||||
#include <IOKit/hid/IOHIDLib.h>
|
#include <IOKit/hid/IOHIDLib.h>
|
||||||
|
|
||||||
@ -12,7 +14,7 @@ static NSString const *JOYReportIDFilters = @"JOYReportIDFilters";
|
|||||||
static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping";
|
static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping";
|
||||||
static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping";
|
static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping";
|
||||||
static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping";
|
static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping";
|
||||||
static NSString const *JOYSubElementStructs = @"JOYSubElementStructs";
|
static NSString const *JOYCustomReports = @"JOYCustomReports";
|
||||||
static NSString const *JOYIsSwitch = @"JOYIsSwitch";
|
static NSString const *JOYIsSwitch = @"JOYIsSwitch";
|
||||||
static NSString const *JOYRumbleUsage = @"JOYRumbleUsage";
|
static NSString const *JOYRumbleUsage = @"JOYRumbleUsage";
|
||||||
static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage";
|
static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage";
|
||||||
@ -41,6 +43,8 @@ static NSLock *globalPWMThreadLock;
|
|||||||
+ (void)controllerAdded:(IOHIDDeviceRef) device;
|
+ (void)controllerAdded:(IOHIDDeviceRef) device;
|
||||||
+ (void)controllerRemoved:(IOHIDDeviceRef) device;
|
+ (void)controllerRemoved:(IOHIDDeviceRef) device;
|
||||||
- (void)elementChanged:(IOHIDElementRef) element;
|
- (void)elementChanged:(IOHIDElementRef) element;
|
||||||
|
- (void)gotReport:(NSData *)report;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface JOYButton ()
|
@interface JOYButton ()
|
||||||
@ -86,6 +90,11 @@ static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef
|
|||||||
[(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)];
|
[(__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)) {
|
typedef struct __attribute__((packed)) {
|
||||||
uint8_t reportID;
|
uint8_t reportID;
|
||||||
@ -102,7 +111,8 @@ typedef struct __attribute__((packed)) {
|
|||||||
NSMutableDictionary<JOYElement *, JOYAxis *> *_axes;
|
NSMutableDictionary<JOYElement *, JOYAxis *> *_axes;
|
||||||
NSMutableDictionary<JOYElement *, JOYAxes2D *> *_axes2D;
|
NSMutableDictionary<JOYElement *, JOYAxes2D *> *_axes2D;
|
||||||
NSMutableDictionary<JOYElement *, JOYHat *> *_hats;
|
NSMutableDictionary<JOYElement *, JOYHat *> *_hats;
|
||||||
NSMutableDictionary<JOYElement *, NSArray<JOYElement *> *> *_multiElements;
|
NSMutableDictionary<NSNumber *, JOYFullReportElement *> *_fullReportElements;
|
||||||
|
NSMutableDictionary<JOYFullReportElement *, NSArray<JOYElement *> *> *_multiElements;
|
||||||
|
|
||||||
// Button emulation
|
// Button emulation
|
||||||
NSMutableDictionary<NSNumber *, JOYEmulatedButton *> *_axisEmulatedButtons;
|
NSMutableDictionary<NSNumber *, JOYEmulatedButton *> *_axisEmulatedButtons;
|
||||||
@ -121,41 +131,59 @@ typedef struct __attribute__((packed)) {
|
|||||||
bool _logicallyConnected;
|
bool _logicallyConnected;
|
||||||
bool _rumblePWMThreadRunning;
|
bool _rumblePWMThreadRunning;
|
||||||
volatile bool _forceStopPWMThread;
|
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 <NSNumber *> *) filter serialSuffix:(NSString *)suffix
|
-(void)createOutputForElement:(JOYElement *)element
|
||||||
{
|
{
|
||||||
self = [super init];
|
uint16_t rumbleUsagePage = (uint16_t)[_hacks[JOYRumbleUsagePage] unsignedIntValue];
|
||||||
if (!self) return self;
|
uint16_t rumbleUsage = (uint16_t)[_hacks[JOYRumbleUsage] unsignedIntValue];
|
||||||
|
|
||||||
_physicallyConnected = true;
|
if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) {
|
||||||
_logicallyConnected = true;
|
if (_hacks[JOYRumbleMin]) {
|
||||||
_device = (IOHIDDeviceRef)CFRetain(device);
|
element.min = [_hacks[JOYRumbleMin] unsignedIntValue];
|
||||||
_serialSuffix = suffix;
|
}
|
||||||
|
if (_hacks[JOYRumbleMax]) {
|
||||||
|
element.max = [_hacks[JOYRumbleMax] unsignedIntValue];
|
||||||
|
}
|
||||||
|
_rumbleElement = element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self);
|
-(void)createInputForElement:(JOYElement *)element
|
||||||
IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
{
|
||||||
|
uint16_t connectedUsagePage = (uint16_t)[_hacks[JOYConnectedUsagePage] unsignedIntValue];
|
||||||
|
uint16_t connectedUsage = (uint16_t)[_hacks[JOYConnectedUsage] unsignedIntValue];
|
||||||
|
|
||||||
NSArray *array = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
|
if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) {
|
||||||
_buttons = [NSMutableDictionary dictionary];
|
_connectedElement = element;
|
||||||
_axes = [NSMutableDictionary dictionary];
|
_logicallyConnected = element.value != element.min;
|
||||||
_axes2D = [NSMutableDictionary dictionary];
|
return;
|
||||||
_hats = [NSMutableDictionary dictionary];
|
}
|
||||||
_axisEmulatedButtons = [NSMutableDictionary dictionary];
|
|
||||||
_axes2DEmulatedButtons = [NSMutableDictionary dictionary];
|
|
||||||
_hatEmulatedButtons = [NSMutableDictionary dictionary];
|
|
||||||
_multiElements = [NSMutableDictionary dictionary];
|
|
||||||
_iokitToJOY = [NSMutableDictionary dictionary];
|
|
||||||
_rumblePWMThreadLock = [[NSLock alloc] init];
|
|
||||||
|
|
||||||
|
|
||||||
//NSMutableArray *axes3d = [NSMutableArray array];
|
|
||||||
|
|
||||||
|
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 = @{
|
NSDictionary *axisGroups = @{
|
||||||
@(kHIDUsage_GD_X): @(0),
|
@(kHIDUsage_GD_X): @(0),
|
||||||
@(kHIDUsage_GD_Y): @(0),
|
@(kHIDUsage_GD_Y): @(0),
|
||||||
@ -164,104 +192,9 @@ typedef struct __attribute__((packed)) {
|
|||||||
@(kHIDUsage_GD_Ry): @(2),
|
@(kHIDUsage_GD_Ry): @(2),
|
||||||
@(kHIDUsage_GD_Rz): @(1),
|
@(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))];
|
|
||||||
}
|
|
||||||
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;
|
axisGroups = _hacks[JOYAxisGroups] ?: axisGroups;
|
||||||
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<NSDictionary <NSString *,NSNumber *>*> *subElementDefs = hacks[JOYSubElementStructs][@(element.uniqueID)];
|
|
||||||
|
|
||||||
bool isOutput = false;
|
|
||||||
if (subElementDefs && element.uniqueID != element.parentID) {
|
|
||||||
elements = [NSMutableArray array];
|
|
||||||
for (NSDictionary<NSString *,NSNumber *> *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];
|
|
||||||
}
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
_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) {
|
switch (element.usage) {
|
||||||
case kHIDUsage_GD_X:
|
case kHIDUsage_GD_X:
|
||||||
case kHIDUsage_GD_Y:
|
case kHIDUsage_GD_Y:
|
||||||
@ -270,8 +203,8 @@ typedef struct __attribute__((packed)) {
|
|||||||
case kHIDUsage_GD_Ry:
|
case kHIDUsage_GD_Ry:
|
||||||
case kHIDUsage_GD_Rz: {
|
case kHIDUsage_GD_Rz: {
|
||||||
|
|
||||||
JOYElement *other = previousAxisElement;
|
JOYElement *other = _previousAxisElement;
|
||||||
previousAxisElement = element;
|
_previousAxisElement = element;
|
||||||
if (!other) goto single;
|
if (!other) goto single;
|
||||||
if (other.usage >= element.usage) goto single;
|
if (other.usage >= element.usage) goto single;
|
||||||
if (other.reportID != element.reportID) goto single;
|
if (other.reportID != element.reportID) goto single;
|
||||||
@ -279,20 +212,20 @@ typedef struct __attribute__((packed)) {
|
|||||||
if (other.parentID != element.parentID) goto single;
|
if (other.parentID != element.parentID) goto single;
|
||||||
|
|
||||||
JOYAxes2D *axes = nil;
|
JOYAxes2D *axes = nil;
|
||||||
if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [hacks[JOYSwapZRz] boolValue]) {
|
if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [_hacks[JOYSwapZRz] boolValue]) {
|
||||||
axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other];
|
axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element];
|
axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element];
|
||||||
}
|
}
|
||||||
NSNumber *replacementUsage = hacks[JOYAxes2DUsageMapping][@(axes.usage)];
|
NSNumber *replacementUsage = _hacks[JOYAxes2DUsageMapping][@(axes.usage)];
|
||||||
if (replacementUsage) {
|
if (replacementUsage) {
|
||||||
axes.usage = [replacementUsage unsignedIntValue];
|
axes.usage = [replacementUsage unsignedIntValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
[_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)];
|
[_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)];
|
||||||
[_axes removeObjectForKey:other];
|
[_axes removeObjectForKey:other];
|
||||||
previousAxisElement = nil;
|
_previousAxisElement = nil;
|
||||||
_axes2D[other] = axes;
|
_axes2D[other] = axes;
|
||||||
_axes2D[element] = axes;
|
_axes2D[element] = axes;
|
||||||
|
|
||||||
@ -330,7 +263,7 @@ typedef struct __attribute__((packed)) {
|
|||||||
JOYAxis *axis = [[JOYAxis alloc] initWithElement: element];
|
JOYAxis *axis = [[JOYAxis alloc] initWithElement: element];
|
||||||
[_axes setObject:axis forKey:element];
|
[_axes setObject:axis forKey:element];
|
||||||
|
|
||||||
NSNumber *replacementUsage = hacks[JOYAxisUsageMapping][@(axis.usage)];
|
NSNumber *replacementUsage = _hacks[JOYAxisUsageMapping][@(axis.usage)];
|
||||||
if (replacementUsage) {
|
if (replacementUsage) {
|
||||||
axis.usage = [replacementUsage unsignedIntValue];
|
axis.usage = [replacementUsage unsignedIntValue];
|
||||||
}
|
}
|
||||||
@ -372,6 +305,131 @@ typedef struct __attribute__((packed)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
if (!self) return self;
|
||||||
|
|
||||||
|
_physicallyConnected = true;
|
||||||
|
_logicallyConnected = true;
|
||||||
|
_device = (IOHIDDeviceRef)CFRetain(device);
|
||||||
|
_serialSuffix = suffix;
|
||||||
|
|
||||||
|
IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self);
|
||||||
|
IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||||
|
|
||||||
|
NSArray *array = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
|
||||||
|
_buttons = [NSMutableDictionary dictionary];
|
||||||
|
_axes = [NSMutableDictionary dictionary];
|
||||||
|
_axes2D = [NSMutableDictionary dictionary];
|
||||||
|
_hats = [NSMutableDictionary dictionary];
|
||||||
|
_axisEmulatedButtons = [NSMutableDictionary dictionary];
|
||||||
|
_axes2DEmulatedButtons = [NSMutableDictionary dictionary];
|
||||||
|
_hatEmulatedButtons = [NSMutableDictionary dictionary];
|
||||||
|
_iokitToJOY = [NSMutableDictionary dictionary];
|
||||||
|
_rumblePWMThreadLock = [[NSLock alloc] init];
|
||||||
|
|
||||||
|
|
||||||
|
//NSMutableArray *axes3d = [NSMutableArray array];
|
||||||
|
|
||||||
|
_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 <NSString *,NSNumber *> *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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id previous = nil;
|
||||||
|
for (id _element in array) {
|
||||||
|
if (_element == previous) continue; // Some elements are reported twice for some reason
|
||||||
|
previous = _element;
|
||||||
|
JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element];
|
||||||
|
|
||||||
|
bool isOutput = false;
|
||||||
|
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 {
|
||||||
|
[self createInputForElement:element];
|
||||||
|
}
|
||||||
|
|
||||||
|
_iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element;
|
||||||
|
|
||||||
|
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];
|
[exposedControllers addObject:self];
|
||||||
@ -383,6 +441,11 @@ typedef struct __attribute__((packed)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_hacks[JOYActivationReport]) {
|
||||||
|
[self sendReport:hacks[JOYActivationReport]];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,6 +504,21 @@ typedef struct __attribute__((packed)) {
|
|||||||
return [_hats allValues];
|
return [_hats allValues];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)gotReport:(NSData *)report
|
||||||
|
{
|
||||||
|
JOYFullReportElement *element = _fullReportElements[@(*(uint8_t *)report.bytes)];
|
||||||
|
if (!element) return;
|
||||||
|
[element updateValue:report];
|
||||||
|
|
||||||
|
NSArray<JOYElement *> *subElements = _multiElements[element];
|
||||||
|
if (subElements) {
|
||||||
|
for (JOYElement *subElement in subElements) {
|
||||||
|
[self _elementChanged:subElement];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)elementChanged:(IOHIDElementRef)element
|
- (void)elementChanged:(IOHIDElementRef)element
|
||||||
{
|
{
|
||||||
JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))];
|
JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))];
|
||||||
@ -473,16 +551,6 @@ typedef struct __attribute__((packed)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
NSArray<JOYElement *> *subElements = _multiElements[element];
|
|
||||||
if (subElements) {
|
|
||||||
for (JOYElement *subElement in subElements) {
|
|
||||||
[self _elementChanged:subElement];
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self.connected) return;
|
if (!self.connected) return;
|
||||||
{
|
{
|
||||||
JOYButton *button = _buttons[element];
|
JOYButton *button = _buttons[element];
|
||||||
@ -602,7 +670,7 @@ typedef struct __attribute__((packed)) {
|
|||||||
_lastSwitchPacket.sequence &= 0xF;
|
_lastSwitchPacket.sequence &= 0xF;
|
||||||
_lastSwitchPacket.command = 0x30; // LED
|
_lastSwitchPacket.command = 0x30; // LED
|
||||||
_lastSwitchPacket.commandData[0] = mask;
|
_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;
|
JOYController *controller = nil;
|
||||||
if (filters) {
|
if (filters) {
|
||||||
controller = [[JOYMultiplayerController alloc] initWithDevice:device
|
controller = [[JOYMultiplayerController alloc] initWithDevice:device
|
||||||
reportIDFilters:filters];
|
reportIDFilters:filters
|
||||||
|
hacks:hacks];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
controller = [[JOYController alloc] initWithDevice:device];
|
controller = [[JOYController alloc] initWithDevice:device hacks:hacks];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hacks[JOYActivationReport]) {
|
|
||||||
[controller sendReport:hacks[JOYActivationReport]];
|
|
||||||
}
|
|
||||||
[controllers setObject:controller forKey:[NSValue valueWithPointer:device]];
|
[controllers setObject:controller forKey:[NSValue valueWithPointer:device]];
|
||||||
|
|
||||||
|
|
||||||
|
10
JoyKit/JOYFullReportElement.h
Normal file
10
JoyKit/JOYFullReportElement.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <IOKit/hid/IOHIDLib.h>
|
||||||
|
#include "JOYElement.h"
|
||||||
|
|
||||||
|
@interface JOYFullReportElement : JOYElement<NSCopying>
|
||||||
|
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportID:(unsigned)reportID;
|
||||||
|
- (void)updateValue:(NSData *)value;
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
73
JoyKit/JOYFullReportElement.m
Normal file
73
JoyKit/JOYFullReportElement.m
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#import "JOYFullReportElement.h"
|
||||||
|
#include <IOKit/hid/IOHIDLib.h>
|
||||||
|
|
||||||
|
@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
|
@ -2,7 +2,7 @@
|
|||||||
#include <IOKit/hid/IOHIDLib.h>
|
#include <IOKit/hid/IOHIDLib.h>
|
||||||
|
|
||||||
@interface JOYMultiplayerController : JOYController
|
@interface JOYMultiplayerController : JOYController
|
||||||
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray <NSArray <NSNumber *> *>*) reportIDFilters;
|
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray <NSArray <NSNumber *> *>*) reportIDFilters hacks:hacks;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#import "JOYMultiplayerController.h"
|
#import "JOYMultiplayerController.h"
|
||||||
|
|
||||||
@interface JOYController ()
|
@interface JOYController ()
|
||||||
- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix;
|
- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix hacks:(NSDictionary *)hacks;
|
||||||
- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value;
|
- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value;
|
||||||
- (void)disconnected;
|
- (void)disconnected;
|
||||||
- (void)sendReport:(NSData *)report;
|
- (void)sendReport:(NSData *)report;
|
||||||
@ -12,7 +12,7 @@
|
|||||||
NSMutableArray <JOYController *> *_children;
|
NSMutableArray <JOYController *> *_children;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray <NSArray <NSNumber *> *>*) reportIDFilters
|
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray <NSArray <NSNumber *> *>*) reportIDFilters hacks:(NSDictionary *)hacks;
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (!self) return self;
|
if (!self) return self;
|
||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
unsigned index = 1;
|
unsigned index = 1;
|
||||||
for (NSArray *filter in reportIDFilters) {
|
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];
|
[_children addObject:controller];
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user