git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

commit cfee59918e52e0e7c64f58ae64b95c4aced69b4b
parent 2776c8ad36e06d1f211b5f73438594bcd6e25f9b
Author: Lior Halphon <LIJI32@gmail.com>
Date:   Mon, 31 Oct 2022 21:11:00 +0200

Add Joy-Con orientation change support, motion controls

Diffstat:
MCocoa/GBApp.m1+
MCocoa/GBJoyConManager.h2+-
MCocoa/GBJoyConManager.m86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
MCocoa/GBView.m9+++++++++
ACocoa/HorizontalJoyConLeftTemplate.png0
ACocoa/HorizontalJoyConLeftTemplate@2x.png0
ACocoa/HorizontalJoyConRightTemplate.png0
ACocoa/HorizontalJoyConRightTemplate@2x.png0
MCocoa/Preferences.xib4++++
MJoyKit/ControllerConfiguration.inc5+++--
MJoyKit/JOYAxes2D.m112++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
MJoyKit/JOYAxes3D.m21+++++++++++++++++++++
MJoyKit/JOYButton.m6++++++
MJoyKit/JOYController.h5+++--
MJoyKit/JOYController.m76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
15 files changed, 254 insertions(+), 73 deletions(-)

diff --git a/Cocoa/GBApp.m b/Cocoa/GBApp.m @@ -81,6 +81,7 @@ static uint32_t color_to_int(NSColor *color) @"GBMBC7AllowMouse": @YES, @"GBJoyConAutoPair": @YES, + @"GBJoyConsDefaultsToHorizontal": @YES, // Default themes @"GBThemes": @{ diff --git a/Cocoa/GBJoyConManager.h b/Cocoa/GBJoyConManager.h @@ -8,6 +8,6 @@ @property bool arrangementMode; @property (weak) IBOutlet NSTableView *tableView; @property (nonatomic) IBOutlet NSButton *autoPairCheckbox; - +@property (nonatomic) IBOutlet NSButton *horizontalCheckbox; @end diff --git a/Cocoa/GBJoyConManager.m b/Cocoa/GBJoyConManager.m @@ -7,6 +7,7 @@ GBTintedImageCell *_tintedImageCell; NSImageCell *_imageCell; NSMutableDictionary<NSString *, NSString *> *_pairings; + NSMutableDictionary<NSString *, NSNumber *> *_orientationSettings; NSButton *_autoPairCheckbox; bool _unpairing; } @@ -49,7 +50,8 @@ _tintedImageCell.tint = [NSColor selectedMenuItemColor]; } _pairings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConPairings"] ?: @{} mutableCopy]; - + _orientationSettings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConOrientations"] ?: @{} mutableCopy]; + // Sanity check the pairings for (NSString *key in _pairings) { if (![_pairings[_pairings[key]] isEqualToString:key]) { @@ -76,22 +78,21 @@ if (row >= [self numberOfRowsInTableView:tableView]) return nil; unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + JOYController *controller = self.joycons[row]; switch (columnIndex) { case 0: { - JOYController *controller = self.joycons[row]; switch (controller.joyconType) { case JOYJoyConTypeNone: return nil; case JOYJoyConTypeLeft: - return [NSImage imageNamed:@"JoyConLeftTemplate"]; + return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConLeftTemplate", controller.usesHorizontalJoyConMode? "Horizontal" :""]]; case JOYJoyConTypeRight: - return [NSImage imageNamed:@"JoyConRightTemplate"]; + return [NSImage imageNamed:[NSString stringWithFormat:@"%sJoyConRightTemplate", controller.usesHorizontalJoyConMode? "Horizontal" :""]]; case JOYJoyConTypeCombined: return [NSImage imageNamed:@"JoyConCombinedTemplate"]; } } case 1: { - JOYController *controller = self.joycons[row]; NSMutableAttributedString *ret = [[NSMutableAttributedString alloc] initWithString:controller.deviceName attributes:@{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]]}]; @@ -103,12 +104,46 @@ return ret; } case 2: - return @(rand() % 3); + return @([(_orientationSettings[controller.uniqueID] ?: @(-1)) unsignedIntValue] + 1); } return nil; } --(NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +- (void)updateOrientationForController:(JOYController *)controller +{ + NSNumber *orientation = _orientationSettings[controller.uniqueID]; + if (!orientation) { + controller.usesHorizontalJoyConMode = [[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConsDefaultsToHorizontal"]; + return; + } + controller.usesHorizontalJoyConMode = [orientation unsignedIntValue] == 1; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + if (columnIndex != 2) return; + if (row >= [self numberOfRowsInTableView:tableView]) return; + JOYController *controller = self.joycons[row]; + if (controller.joyconType == JOYJoyConTypeCombined) { + return; + } + switch ([object unsignedIntValue]) { + case 0: + [_orientationSettings removeObjectForKey:controller.uniqueID]; + break; + case 1: + _orientationSettings[controller.uniqueID] = @(0); + break; + case 2: + _orientationSettings[controller.uniqueID] = @(1); + break; + } + [[NSUserDefaults standardUserDefaults] setObject:_orientationSettings forKey:@"GBJoyConOrientations"]; + [self updateOrientationForController:controller]; +} + +- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { if (row >= [self numberOfRowsInTableView:tableView]) return [[NSCell alloc] init]; @@ -120,13 +155,15 @@ cell.bezelStyle = NSBezelStyleRounded; cell.action = @selector(invoke); id block = ^(void) { - for (JOYController *child in controller.children) { - [_pairings removeObjectForKey:child.uniqueID]; - } - [[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"]; - _unpairing = true; - [controller breakApart]; - _unpairing = false; + dispatch_async(dispatch_get_main_queue(), ^{ + for (JOYController *child in controller.children) { + [_pairings removeObjectForKey:child.uniqueID]; + } + [[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"]; + _unpairing = true; + [controller breakApart]; + _unpairing = false; + }); }; // To retain the block objc_setAssociatedObject(cell, @selector(breakApart), block, OBJC_ASSOCIATION_RETAIN); @@ -148,6 +185,7 @@ - (void)controllerConnected:(JOYController *)controller { + [self updateOrientationForController:controller]; for (JOYController *partner in [JOYController allControllers]) { if ([partner.uniqueID isEqualToString:_pairings[controller.uniqueID]]) { [self pairJoyCon:controller withJoyCon:partner]; @@ -185,7 +223,8 @@ - (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { - return false; + unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn]; + return columnIndex == 2; } - (JOYCombinedController *)pairJoyCon:(JOYController *)first withJoyCon:(JOYController *)second @@ -196,6 +235,8 @@ _pairings[first.uniqueID] = second.uniqueID; _pairings[second.uniqueID] = first.uniqueID; + first.usesHorizontalJoyConMode = false; + second.usesHorizontalJoyConMode = false; [[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"]; return [[JOYCombinedController alloc] initWithChildren:@[first, second]]; } @@ -248,4 +289,19 @@ [self autopair]; } +- (void)setHorizontalCheckbox:(NSButton *)horizontalCheckbox +{ + _horizontalCheckbox = horizontalCheckbox; + [_horizontalCheckbox setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConsDefaultsToHorizontal"]]; +} + +- (IBAction)toggleHorizontalDefault:(NSButton *)sender +{ + [[NSUserDefaults standardUserDefaults] setBool:sender.state forKey:@"GBJoyConsDefaultsToHorizontal"]; + for (JOYController *controller in self.joycons) { + [self updateOrientationForController:controller]; + } + [self.tableView reloadData]; +} + @end diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m @@ -523,6 +523,15 @@ static const uint8_t workboy_vk_to_key[] = { if (!_gb) return; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBMBC7JoystickOverride"]) return; if (self.mouseControlsActive) return; + if (controller != lastController) return; + // When using a combined two Joy-Cons, ignore motion data from the left Joy-Con + if (controller.joyconType == JOYJoyConTypeCombined) { + for (JOYController *child in [(JOYCombinedController *)controller children]) { + if (child.joyconType != JOYJoyConTypeRight && [child.axes3D containsObject:axes]) { + return; + } + } + } if (axes.usage == JOYAxes3DUsageOrientation) { for (JOYAxes3D *axes in controller.axes3D) { diff --git a/Cocoa/HorizontalJoyConLeftTemplate.png b/Cocoa/HorizontalJoyConLeftTemplate.png Binary files differ. diff --git a/Cocoa/HorizontalJoyConLeftTemplate@2x.png b/Cocoa/HorizontalJoyConLeftTemplate@2x.png Binary files differ. diff --git a/Cocoa/HorizontalJoyConRightTemplate.png b/Cocoa/HorizontalJoyConRightTemplate.png Binary files differ. diff --git a/Cocoa/HorizontalJoyConRightTemplate@2x.png b/Cocoa/HorizontalJoyConRightTemplate@2x.png Binary files differ. diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib @@ -1337,6 +1337,9 @@ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <font key="font" metaFont="system"/> </buttonCell> + <connections> + <action selector="toggleHorizontalDefault:" target="fMo-Ht-Dis" id="yhh-pY-XOq"/> + </connections> </button> <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gmw-t5-3nG"> <rect key="frame" x="18" y="20" width="411" height="14"/> @@ -1439,6 +1442,7 @@ Test</string> <customObject id="fMo-Ht-Dis" customClass="GBJoyConManager"> <connections> <outlet property="autoPairCheckbox" destination="xhI-kr-h3J" id="CaJ-9q-dUh"/> + <outlet property="horizontalCheckbox" destination="DaV-H7-VVr" id="bFm-Qm-CgL"/> <outlet property="tableView" destination="XQa-0K-gl3" id="fhZ-wM-dnm"/> </connections> </customObject> diff --git a/JoyKit/ControllerConfiguration.inc b/JoyKit/ControllerConfiguration.inc @@ -570,11 +570,12 @@ hacksByName = @{ @{@"reportID": @(1), @"size":@12, @"offset":@64, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @0xFFF}, @{@"reportID": @(1), @"size":@12, @"offset":@76, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @0xFFF, @"max": @0}, + // The X axis is inverted on the right Joy-Con @{@"reportID": @(1), @"size":@16, @"offset":@96, @"usagePage":@(kHIDPage_Sensor), @"min": @0x7FFF, @"max": @-0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisZ)}, - @{@"reportID": @(1), @"size":@16, @"offset":@112, @"usagePage":@(kHIDPage_Sensor), @"min": @0x7FFF, @"max": @-0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisX)}, + @{@"reportID": @(1), @"size":@16, @"offset":@112, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisX)}, @{@"reportID": @(1), @"size":@16, @"offset":@128, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AccelerationAxisY)}, @{@"reportID": @(1), @"size":@16, @"offset":@144, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityZAxis)}, - @{@"reportID": @(1), @"size":@16, @"offset":@160, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis)}, + @{@"reportID": @(1), @"size":@16, @"offset":@160, @"usagePage":@(kHIDPage_Sensor), @"min": @0x7FFF, @"max": @-0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityXAxis)}, @{@"reportID": @(1), @"size":@16, @"offset":@176, @"usagePage":@(kHIDPage_Sensor), @"min": @-0x7FFF, @"max": @0x7FFF, @"usage":@(kHIDUsage_Snsr_Data_Motion_AngularVelocityYAxis)}, ], }, diff --git a/JoyKit/JOYAxes2D.m b/JoyKit/JOYAxes2D.m @@ -1,13 +1,17 @@ #import "JOYAxes2D.h" #import "JOYElement.h" +@interface JOYAxes2D() +@property unsigned rotation; // in 90 degrees units, clockwise +@end + @implementation JOYAxes2D { JOYElement *_element1, *_element2; double _state1, _state2; - int32_t initialX, initialY; - int32_t minX, minY; - int32_t maxX, maxY; + int32_t _initialX, _initialY; + int32_t _minX, _minY; + int32_t _maxX, _maxY; } + (NSString *)usageToString: (JOYAxes2DUsage) usage @@ -56,12 +60,12 @@ uint16_t usage = element1.usage; _usage = JOYAxes2DUsageGeneric0 + usage - kHIDUsage_GD_X + 1; } - initialX = 0; - initialY = 0; - minX = element1.max; - minY = element2.max; - maxX = element1.min; - maxY = element2.min; + _initialX = 0; + _initialY = 0; + _minX = element1.max; + _minY = element2.max; + _maxX = element1.min; + _maxY = element2.min; return self; } @@ -71,44 +75,44 @@ return NSMakePoint(_state1, _state2); } --(int32_t) effectiveMinX +- (int32_t)effectiveMinX { int32_t rawMin = _element1.min; int32_t rawMax = _element1.max; - if (initialX == 0) return rawMin; - if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return minX; - if ((initialX - rawMin) < (rawMax - initialX)) return rawMin; - return initialX - (rawMax - initialX); + if (_initialX == 0) return rawMin; + if (_minX <= (rawMin * 2 + _initialX) / 3 && _maxX >= (rawMax * 2 + _initialX) / 3 ) return _minX; + if ((_initialX - rawMin) < (rawMax - _initialX)) return rawMin; + return _initialX - (rawMax - _initialX); } --(int32_t) effectiveMinY +- (int32_t)effectiveMinY { int32_t rawMin = _element2.min; int32_t rawMax = _element2.max; - if (initialY == 0) return rawMin; - if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return minY; - if ((initialY - rawMin) < (rawMax - initialY)) return rawMin; - return initialY - (rawMax - initialY); + if (_initialY == 0) return rawMin; + if (_minX <= (rawMin * 2 + _initialY) / 3 && _maxY >= (rawMax * 2 + _initialY) / 3 ) return _minY; + if ((_initialY - rawMin) < (rawMax - _initialY)) return rawMin; + return _initialY - (rawMax - _initialY); } --(int32_t) effectiveMaxX +- (int32_t)effectiveMaxX { int32_t rawMin = _element1.min; int32_t rawMax = _element1.max; - if (initialX == 0) return rawMax; - if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return maxX; - if ((initialX - rawMin) > (rawMax - initialX)) return rawMax; - return initialX + (initialX - rawMin); + if (_initialX == 0) return rawMax; + if (_minX <= (rawMin * 2 + _initialX) / 3 && _maxX >= (rawMax * 2 + _initialX) / 3 ) return _maxX; + if ((_initialX - rawMin) > (rawMax - _initialX)) return rawMax; + return _initialX + (_initialX - rawMin); } --(int32_t) effectiveMaxY +- (int32_t)effectiveMaxY { int32_t rawMin = _element2.min; int32_t rawMax = _element2.max; - if (initialY == 0) return rawMax; - if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return maxY; - if ((initialY - rawMin) > (rawMax - initialY)) return rawMax; - return initialY + (initialY - rawMin); + if (_initialY == 0) return rawMax; + if (_minX <= (rawMin * 2 + _initialY) / 3 && _maxY >= (rawMax * 2 + _initialY) / 3 ) return _maxY; + if ((_initialY - rawMin) > (rawMax - _initialY)) return rawMax; + return _initialY + (_initialY - rawMin); } - (bool)updateState @@ -117,18 +121,18 @@ int32_t y = [_element2 value]; if (x == 0 && y == 0) return false; - if (initialX == 0 && initialY == 0) { - initialX = x; - initialY = y; + if (_initialX == 0 && _initialY == 0) { + _initialX = x; + _initialY = y; } double old1 = _state1, old2 = _state2; { int32_t value = x; - if (initialX != 0) { - minX = MIN(value, minX); - maxX = MAX(value, maxX); + if (_initialX != 0) { + _minX = MIN(value, _minX); + _maxX = MAX(value, _maxX); } double min = [self effectiveMinX]; @@ -141,9 +145,9 @@ { int32_t value = y; - if (initialY != 0) { - minY = MIN(value, minY); - maxY = MAX(value, maxY); + if (_initialY != 0) { + _minY = MIN(value, _minY); + _maxY = MAX(value, _maxY); } double min = [self effectiveMinY]; @@ -157,11 +161,29 @@ _state2 < -1 || _state2 > 1) { // Makes no sense, recalibrate _state1 = _state2 = 0; - initialX = initialY = 0; - minX = _element1.max; - minY = _element2.max; - maxX = _element1.min; - maxY = _element2.min; + _initialX = _initialY = 0; + _minX = _element1.max; + _minY = _element2.max; + _maxX = _element1.min; + _maxY = _element2.min; + } + + + double temp = _state1; + switch (_rotation & 3) { + case 0: break; + case 1: + _state1 = -_state2; + _state2 = temp; + break; + case 2: + _state1 = -_state1; + _state2 = -_state2; + break; + case 3: + _state1 = _state2; + _state2 = -temp; + break; } return old1 != _state1 || old2 != _state2; @@ -172,9 +194,11 @@ return MIN(sqrt(_state1 * _state1 + _state2 * _state2), 1.0); } -- (double)angle { +- (double)angle +{ double temp = atan2(_state2, _state1) * 180 / M_PI; if (temp >= 0) return temp; return temp + 360; } + @end diff --git a/JoyKit/JOYAxes3D.m b/JoyKit/JOYAxes3D.m @@ -1,6 +1,10 @@ #import "JOYAxes3D.h" #import "JOYElement.h" +@interface JOYAxes3D() +@property unsigned rotation; // in 90 degrees units, clockwise +@end + @implementation JOYAxes3D { JOYElement *_element1, *_element2, *_element3; @@ -101,6 +105,23 @@ else { _gApproximation = _gApproximation * 0.9999 + distance * 0.0001; } + + double temp = _state1; + switch (_rotation & 3) { + case 0: break; + case 1: + _state1 = -_state3; + _state3 = temp; + break; + case 2: + _state1 = -_state1; + _state3 = -_state3; + break; + case 3: + _state1 = _state3; + _state3 = -temp; + break; + } return old1 != _state1 || old2 != _state2 || old3 != _state3; } diff --git a/JoyKit/JOYButton.m b/JoyKit/JOYButton.m @@ -2,6 +2,10 @@ #import "JOYElement.h" #import <AppKit/AppKit.h> +@interface JOYButton () +@property JOYButtonUsage originalUsage; +@end + @implementation JOYButton { JOYElement *_element; @@ -88,6 +92,8 @@ } } + _originalUsage = _usage; + return self; } diff --git a/JoyKit/JOYController.h b/JoyKit/JOYController.h @@ -53,8 +53,9 @@ typedef enum { - (uint8_t)LEDMaskForPlayer:(unsigned)player; @property (readonly, getter=isConnected) bool connected; @property (readonly) JOYJoyConType joyconType; -@property (readonly) NSString *deviceName; -@property (readonly) NSString *uniqueID; +@property (readonly) NSString *deviceName; +@property (readonly) NSString *uniqueID; +@property (nonatomic) bool usesHorizontalJoyConMode; @end @interface JOYCombinedController : JOYController diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m @@ -55,6 +55,7 @@ static bool hatsEmulateButtons = false; @interface JOYButton () - (instancetype)initWithElement:(JOYElement *)element; - (bool)updateState; +@property JOYButtonUsage originalUsage; @end @interface JOYAxis () @@ -70,6 +71,7 @@ static bool hatsEmulateButtons = false; @interface JOYAxes2D () - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2; - (bool)updateState; +@property unsigned rotation; // in 90 degrees units, clockwise @end @interface JOYAxes3D () @@ -78,6 +80,7 @@ static bool hatsEmulateButtons = false; } - (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2 thirdElement:(JOYElement *)element2; - (bool)updateState; +@property unsigned rotation; // in 90 degrees units, clockwise @end @interface JOYInput () @@ -336,7 +339,7 @@ typedef union { [_buttons setObject:button forKey:element]; NSNumber *replacementUsage = element.usagePage == kHIDPage_Button? _hacks[JOYButtonUsageMapping][@(button.usage)] : nil; if (replacementUsage) { - button.usage = [replacementUsage unsignedIntValue]; + button.originalUsage = button.usage = [replacementUsage unsignedIntValue]; } return; } @@ -596,15 +599,18 @@ typedef union { _lastVendorSpecificOutput.switchPacket.sequence++; _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; - _lastVendorSpecificOutput.switchPacket.command = 0x40; // Enable/disableIMU - _lastVendorSpecificOutput.switchPacket.commandData[0] = 1; // Enabled - [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; - - _lastVendorSpecificOutput.switchPacket.sequence++; - _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; _lastVendorSpecificOutput.switchPacket.command = 0x48; // Set vibration enabled _lastVendorSpecificOutput.switchPacket.commandData[0] = 1; // enabled [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; + + // The Joy-Cons don't like having their IMU enabled too quickly + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + _lastVendorSpecificOutput.switchPacket.sequence++; + _lastVendorSpecificOutput.switchPacket.sequence &= 0xF; + _lastVendorSpecificOutput.switchPacket.command = 0x40; // Enable/disableIMU + _lastVendorSpecificOutput.switchPacket.commandData[0] = 1; // Enabled + [self sendReport:[NSData dataWithBytes:&_lastVendorSpecificOutput.switchPacket length:sizeof(_lastVendorSpecificOutput.switchPacket)]]; + }); } if (_isDualShock3) { @@ -1105,6 +1111,60 @@ typedef union { return ret; } +- (void)setUsesHorizontalJoyConMode:(bool)usesHorizontalJoyConMode +{ + if (usesHorizontalJoyConMode == _usesHorizontalJoyConMode) return; // Nothing to do + _usesHorizontalJoyConMode = usesHorizontalJoyConMode; + switch (self.joyconType) { + case JOYJoyConTypeLeft: + case JOYJoyConTypeRight: { + NSArray <JOYButton *> *buttons = _buttons.allValues; // not self.buttons to skip emulated buttons + if (!usesHorizontalJoyConMode) { + for (JOYAxes2D *axes in self.axes2D) { + axes.rotation = 0; + } + for (JOYAxes3D *axes in self.axes3D) { + axes.rotation = 0; + } + for (JOYButton *button in buttons) { + button.usage = button.originalUsage; + } + return; + } + for (JOYAxes2D *axes in self.axes2D) { + axes.rotation = self.joyconType == JOYJoyConTypeLeft? -1 : 1; + } + for (JOYAxes3D *axes in self.axes3D) { + axes.rotation = self.joyconType == JOYJoyConTypeLeft? -1 : 1; + } + if (self.joyconType == JOYJoyConTypeLeft) { + for (JOYButton *button in buttons) { + switch (button.originalUsage) { + case JOYButtonUsageDPadLeft: button.usage = JOYButtonUsageB; break; + case JOYButtonUsageDPadRight: button.usage = JOYButtonUsageX; break; + case JOYButtonUsageDPadUp: button.usage = JOYButtonUsageY; break; + case JOYButtonUsageDPadDown: button.usage = JOYButtonUsageA; break; + default: button.usage = button.originalUsage; break; + } + } + } + else { + for (JOYButton *button in buttons) { + switch (button.originalUsage) { + case JOYButtonUsageY: button.usage = JOYButtonUsageX; break; + case JOYButtonUsageA: button.usage = JOYButtonUsageB; break; + case JOYButtonUsageX: button.usage = JOYButtonUsageA; break; + case JOYButtonUsageB: button.usage = JOYButtonUsageY; break; + default: button.usage = button.originalUsage; break; + } + } + } + } + default: + return; + } +} + + (void)controllerAdded:(IOHIDDeviceRef) device { NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); @@ -1124,8 +1184,6 @@ typedef union { } [controllers setObject:controller forKey:[NSValue valueWithPointer:device]]; - - } + (void)controllerRemoved:(IOHIDDeviceRef) device

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.