mirror of https://github.com/LIJI32/SameBoy.git
Add Joy-Con pairing interface
This commit is contained in:
parent
5ef668251c
commit
2776c8ad36
|
@ -1,8 +1,9 @@
|
|||
#import "GBApp.h"
|
||||
#include "GBButtons.h"
|
||||
#include "GBView.h"
|
||||
#include "Document.h"
|
||||
#include <Core/gb.h>
|
||||
#import "GBButtons.h"
|
||||
#import "GBView.h"
|
||||
#import "Document.h"
|
||||
#import "GBJoyConManager.h"
|
||||
#import <Core/gb.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
@ -79,6 +80,8 @@ static uint32_t color_to_int(NSColor *color)
|
|||
@"GBMBC7JoystickOverride": @NO,
|
||||
@"GBMBC7AllowMouse": @YES,
|
||||
|
||||
@"GBJoyConAutoPair": @YES,
|
||||
|
||||
// Default themes
|
||||
@"GBThemes": @{
|
||||
@"Desert": @{
|
||||
|
@ -147,6 +150,8 @@ static uint32_t color_to_int(NSColor *color)
|
|||
JOYHatsEmulateButtonsKey: @YES,
|
||||
}];
|
||||
|
||||
[GBJoyConManager sharedInstance]; // Starts handling Joy-Cons
|
||||
|
||||
[JOYController registerListener:self];
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
@interface GBJoyConManager : NSObject<JOYListener, NSTableViewDataSource, NSTableViewDelegate>
|
||||
+ (instancetype) sharedInstance;
|
||||
|
||||
@property bool arrangementMode;
|
||||
@property (weak) IBOutlet NSTableView *tableView;
|
||||
@property (nonatomic) IBOutlet NSButton *autoPairCheckbox;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
#import "GBJoyConManager.h"
|
||||
#import "GBTintedImageCell.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation GBJoyConManager
|
||||
{
|
||||
GBTintedImageCell *_tintedImageCell;
|
||||
NSImageCell *_imageCell;
|
||||
NSMutableDictionary<NSString *, NSString *> *_pairings;
|
||||
NSButton *_autoPairCheckbox;
|
||||
bool _unpairing;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static GBJoyConManager *manager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[self alloc] _init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (NSArray <JOYController *> *)joycons
|
||||
{
|
||||
NSMutableArray *ret = [[JOYController allControllers] mutableCopy];
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
if (controller.joyconType == JOYJoyConTypeNone) {
|
||||
[ret removeObject:controller];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self.class sharedInstance];
|
||||
}
|
||||
|
||||
- (instancetype) _init
|
||||
{
|
||||
self = [super init];
|
||||
_imageCell = [[NSImageCell alloc] init];
|
||||
_tintedImageCell = [[GBTintedImageCell alloc] init];
|
||||
if (@available(macOS 10.14, *)) {
|
||||
_tintedImageCell.tint = [NSColor controlAccentColor];
|
||||
}
|
||||
else {
|
||||
_tintedImageCell.tint = [NSColor selectedMenuItemColor];
|
||||
}
|
||||
_pairings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoyConPairings"] ?: @{} mutableCopy];
|
||||
|
||||
// Sanity check the pairings
|
||||
for (NSString *key in _pairings) {
|
||||
if (![_pairings[_pairings[key]] isEqualToString:key]) {
|
||||
[_pairings removeAllObjects];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[JOYController registerListener:self];
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
[self controllerConnected:controller];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||
{
|
||||
return self.joycons.count;
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
if (row >= [self numberOfRowsInTableView:tableView]) return nil;
|
||||
|
||||
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
|
||||
switch (columnIndex) {
|
||||
case 0: {
|
||||
JOYController *controller = self.joycons[row];
|
||||
switch (controller.joyconType) {
|
||||
case JOYJoyConTypeNone:
|
||||
return nil;
|
||||
case JOYJoyConTypeLeft:
|
||||
return [NSImage imageNamed:@"JoyConLeftTemplate"];
|
||||
case JOYJoyConTypeRight:
|
||||
return [NSImage imageNamed:@"JoyConRightTemplate"];
|
||||
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]]}];
|
||||
|
||||
[ret appendAttributedString:[[NSAttributedString alloc] initWithString:[@"\n" stringByAppendingString:controller.uniqueID]
|
||||
attributes:@{NSFontAttributeName:
|
||||
[NSFont systemFontOfSize:[NSFont smallSystemFontSize]],
|
||||
NSForegroundColorAttributeName:[NSColor disabledControlTextColor]}]];
|
||||
return ret;
|
||||
}
|
||||
case 2:
|
||||
return @(rand() % 3);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
-(NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
if (row >= [self numberOfRowsInTableView:tableView]) return [[NSCell alloc] init];
|
||||
|
||||
unsigned columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
|
||||
if (columnIndex == 2) {
|
||||
JOYCombinedController *controller = (JOYCombinedController *)self.joycons[row];
|
||||
if (controller.joyconType == JOYJoyConTypeCombined) {
|
||||
NSButtonCell *cell = [[NSButtonCell alloc] initTextCell:@"Separate Joy-Cons"];
|
||||
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;
|
||||
};
|
||||
// To retain the block
|
||||
objc_setAssociatedObject(cell, @selector(breakApart), block, OBJC_ASSOCIATION_RETAIN);
|
||||
cell.target = block;
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
if (columnIndex == 0) {
|
||||
JOYController *controller = self.joycons[row];
|
||||
for (JOYButton *button in controller.buttons) {
|
||||
if (button.isPressed) {
|
||||
return _tintedImageCell;
|
||||
}
|
||||
}
|
||||
return _imageCell;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)controllerConnected:(JOYController *)controller
|
||||
{
|
||||
for (JOYController *partner in [JOYController allControllers]) {
|
||||
if ([partner.uniqueID isEqualToString:_pairings[controller.uniqueID]]) {
|
||||
[self pairJoyCon:controller withJoyCon:partner];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (controller.joyconType == JOYJoyConTypeLeft || controller.joyconType == JOYJoyConTypeRight) {
|
||||
[self autopair];
|
||||
}
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)autopair
|
||||
{
|
||||
if (_unpairing) return;
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConAutoPair"]) return;
|
||||
NSArray<JOYController *> *controllers = [[JOYController allControllers] copy];
|
||||
for (JOYController *first in controllers) {
|
||||
if (_pairings[first.uniqueID]) continue; // Has an established partner
|
||||
if (first.joyconType != JOYJoyConTypeLeft) continue;
|
||||
for (JOYController *second in controllers) {
|
||||
if (_pairings[second.uniqueID]) continue; // Has an established partner
|
||||
if (second.joyconType != JOYJoyConTypeRight) continue;
|
||||
[self pairJoyCon:first withJoyCon:second];
|
||||
break;
|
||||
}
|
||||
}
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)controllerDisconnected:(JOYController *)controller
|
||||
{
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
- (JOYCombinedController *)pairJoyCon:(JOYController *)first withJoyCon:(JOYController *)second
|
||||
{
|
||||
if (first.joyconType != JOYJoyConTypeLeft && first.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con
|
||||
if (second.joyconType != JOYJoyConTypeLeft && second.joyconType != JOYJoyConTypeRight) return nil; // Not a Joy-Con
|
||||
if (first.joyconType == second.joyconType) return nil; // Not a sensible pair
|
||||
|
||||
_pairings[first.uniqueID] = second.uniqueID;
|
||||
_pairings[second.uniqueID] = first.uniqueID;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:_pairings forKey:@"GBJoyConPairings"];
|
||||
return [[JOYCombinedController alloc] initWithChildren:@[first, second]];
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
|
||||
{
|
||||
if (!_arrangementMode) return;
|
||||
if (controller.joyconType == JOYJoyConTypeNone) return;
|
||||
[self.tableView setNeedsDisplay:true];
|
||||
if (controller.joyconType != JOYJoyConTypeLeft && controller.joyconType != JOYJoyConTypeRight) return;
|
||||
if (button.usage != JOYButtonUsageL1 && button.usage != JOYButtonUsageR1) return;
|
||||
|
||||
|
||||
// L or R were pressed on a single Joy-Con, try and pair available Joy-Cons
|
||||
JOYController *left = nil;
|
||||
JOYController *right = nil;
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
if (!left && controller.joyconType == JOYJoyConTypeLeft) {
|
||||
for (JOYButton *button in controller.buttons) {
|
||||
if (button.usage == JOYButtonUsageL1 && button.isPressed) {
|
||||
left = controller;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!right && controller.joyconType == JOYJoyConTypeRight) {
|
||||
for (JOYButton *button in controller.buttons) {
|
||||
if (button.usage == JOYButtonUsageR1 && button.isPressed) {
|
||||
right = controller;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (left && right) {
|
||||
[self pairJoyCon:left withJoyCon:right];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAutoPairCheckbox:(NSButton *)autoPairCheckbox
|
||||
{
|
||||
_autoPairCheckbox = autoPairCheckbox;
|
||||
[_autoPairCheckbox setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"GBJoyConAutoPair"]];
|
||||
}
|
||||
|
||||
- (IBAction)toggleAutoPair:(NSButton *)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:sender.state forKey:@"GBJoyConAutoPair"];
|
||||
[self autopair];
|
||||
}
|
||||
|
||||
@end
|
|
@ -38,4 +38,5 @@
|
|||
@property IBOutlet NSWindow *paletteEditor;
|
||||
@property IBOutlet NSButton *joystickMBC7Checkbox;
|
||||
@property IBOutlet NSButton *mouseMBC7Checkbox;
|
||||
@property IBOutlet NSWindow *joyconsSheet;
|
||||
@end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#import "GBPreferencesWindow.h"
|
||||
#import "GBJoyConManager.h"
|
||||
#import "NSString+StringForKey.h"
|
||||
#import "GBButtons.h"
|
||||
#import "BigSurToolbar.h"
|
||||
|
@ -999,4 +1000,16 @@ static inline NSString *keyEquivalentString(NSMenuItem *item)
|
|||
preferredEdge:NSRectEdgeMaxX];
|
||||
}
|
||||
|
||||
- (IBAction)arrangeJoyCons:(id)sender
|
||||
{
|
||||
[GBJoyConManager sharedInstance].arrangementMode = true;
|
||||
[self beginSheet:self.joyconsSheet completionHandler:nil];
|
||||
}
|
||||
|
||||
- (IBAction)closeJoyConsSheet:(id)sender
|
||||
{
|
||||
[self endSheet:self.joyconsSheet];
|
||||
[GBJoyConManager sharedInstance].arrangementMode = false;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBTintedImageCell : NSImageCell
|
||||
@property NSColor *tint;
|
||||
@end
|
|
@ -0,0 +1,20 @@
|
|||
#import "GBTintedImageCell.h"
|
||||
|
||||
@implementation GBTintedImageCell
|
||||
|
||||
- (NSImage *)image
|
||||
{
|
||||
if (!self.tint || !super.image.isTemplate) {
|
||||
return [super image];
|
||||
}
|
||||
|
||||
NSImage *tinted = [super.image copy];
|
||||
[tinted lockFocus];
|
||||
[self.tint set];
|
||||
NSRectFillUsingOperation((NSRect){.size = tinted.size}, NSCompositeSourceIn);
|
||||
[tinted unlockFocus];
|
||||
tinted.template = false;
|
||||
return tinted;
|
||||
}
|
||||
|
||||
@end
|
Binary file not shown.
After Width: | Height: | Size: 315 B |
Binary file not shown.
After Width: | Height: | Size: 483 B |
Binary file not shown.
After Width: | Height: | Size: 266 B |
Binary file not shown.
After Width: | Height: | Size: 502 B |
Binary file not shown.
After Width: | Height: | Size: 262 B |
Binary file not shown.
After Width: | Height: | Size: 411 B |
|
@ -89,6 +89,7 @@
|
|||
<outlet property="hotkey1PopupButton" destination="qsH-w5-Rja" id="TFr-p8-IYo"/>
|
||||
<outlet property="hotkey2PopupButton" destination="Ryk-mD-c0y" id="m2J-O5-dng"/>
|
||||
<outlet property="interferenceSlider" destination="FpE-5i-j5L" id="hfH-e8-7cx"/>
|
||||
<outlet property="joyconsSheet" destination="bn1-MD-iQb" id="rOY-jn-trO"/>
|
||||
<outlet property="joystickMBC7Checkbox" destination="i7F-1r-NkQ" id="msM-WX-w9H"/>
|
||||
<outlet property="mouseMBC7Checkbox" destination="wUE-aB-ub1" id="mXX-mZ-sKJ"/>
|
||||
<outlet property="paletteEditor" destination="g32-xe-7al" id="WLk-Hh-h5v"/>
|
||||
|
@ -940,6 +941,9 @@
|
|||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="arrangeJoyCons:" target="QvC-M9-y7g" id="BuK-Mb-nkq"/>
|
||||
</connections>
|
||||
</button>
|
||||
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa">
|
||||
<rect key="frame" x="32" y="124" width="262" height="211"/>
|
||||
|
@ -1074,7 +1078,7 @@
|
|||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="5Al-aC-tq8">
|
||||
<rect key="frame" x="1" y="1" width="158" height="316"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" id="ZVn-bk-duk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="158" height="316"/>
|
||||
|
@ -1306,6 +1310,138 @@
|
|||
<outlet property="themesList" destination="ZVn-bk-duk" id="S4b-vM-ioi"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<window title="Arrange Joy-Cons" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="bn1-MD-iQb">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="283" y="305" width="521" height="322"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<view key="contentView" id="waz-WG-RYg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="521" height="322"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xhI-kr-h3J">
|
||||
<rect key="frame" x="18" y="287" width="485" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Automatically pair together left and right Joy-Cons when they're connected" bezelStyle="regularSquare" imagePosition="left" state="on" focusRingType="none" inset="2" id="dPv-XO-6Fp">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="toggleAutoPair:" target="fMo-Ht-Dis" id="Luy-Rm-C5j"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DaV-H7-VVr">
|
||||
<rect key="frame" x="18" y="266" width="485" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Single Joy-Cons default to horizontal orientation" bezelStyle="regularSquare" imagePosition="left" state="on" focusRingType="none" inset="2" id="18L-S2-0wg">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" focusRingType="none" title="Hold L and R on two Joy-Cons to pair them together" id="kRC-pE-5Pd">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="38" horizontalPageScroll="10" verticalLineScroll="38" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9m1-mD-ctP">
|
||||
<rect key="frame" x="20" y="55" width="481" height="205"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" id="4U7-cB-J7O">
|
||||
<rect key="frame" x="1" y="1" width="479" height="203"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="36" rowSizeStyle="large" id="XQa-0K-gl3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="479" height="203"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn width="40" minWidth="40" maxWidth="40" id="GX5-ka-beq">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<imageCell key="dataCell" alignment="left" imageScaling="proportionallyDown" id="HEi-ii-9Hv" customClass="GBTintedImageCell"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="250" minWidth="40" maxWidth="1000" id="YhU-Z2-p6i">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="5rv-V2-pBy">
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="title">Text Cell
|
||||
Test</string>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="180" minWidth="180" maxWidth="180" id="9fC-GK-Fzn">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<popUpButtonCell key="dataCell" type="bevel" title="Horizontal Orientation" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="bezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" selectedItem="IJX-Qq-TaU" id="rif-6W-xsN">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="36b-lh-dDy">
|
||||
<items>
|
||||
<menuItem title="Default Orientation" id="8im-vm-Bj9"/>
|
||||
<menuItem title="Vertical Orientation" id="bU8-po-vzF"/>
|
||||
<menuItem title="Horizontal Orientation" state="on" id="IJX-Qq-TaU"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="fMo-Ht-Dis" id="7oa-g5-U2s"/>
|
||||
<outlet property="delegate" destination="fMo-Ht-Dis" id="7Vg-ta-SaM"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="QaD-xE-QPs">
|
||||
<rect key="frame" x="1" y="188" width="476" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="Plj-YY-6jm">
|
||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6Tm-Sf-8w1">
|
||||
<rect key="frame" x="432" y="13" width="75" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Close" bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="leb-Jp-RfR">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="closeJoyConsSheet:" target="QvC-M9-y7g" id="K2P-xu-B1o"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</view>
|
||||
<point key="canvasLocation" x="-1062.5" y="-96"/>
|
||||
</window>
|
||||
<customObject id="fMo-Ht-Dis" customClass="GBJoyConManager">
|
||||
<connections>
|
||||
<outlet property="autoPairCheckbox" destination="xhI-kr-h3J" id="CaJ-9q-dUh"/>
|
||||
<outlet property="tableView" destination="XQa-0K-gl3" id="fhZ-wM-dnm"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="CPU" width="32" height="32"/>
|
||||
|
|
|
@ -29,13 +29,18 @@ typedef enum {
|
|||
JOYControllerCombinedTypeCombined,
|
||||
} JOYControllerCombinedType;
|
||||
|
||||
typedef enum {
|
||||
JOYJoyConTypeNone,
|
||||
JOYJoyConTypeLeft,
|
||||
JOYJoyConTypeRight,
|
||||
JOYJoyConTypeCombined,
|
||||
} JOYJoyConType;
|
||||
|
||||
@interface JOYController : NSObject
|
||||
+ (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options;
|
||||
+ (NSArray<JOYController *> *) allControllers;
|
||||
+ (void) registerListener:(id<JOYListener>)listener;
|
||||
+ (void) unregisterListener:(id<JOYListener>)listener;
|
||||
- (NSString *)deviceName;
|
||||
- (NSString *)uniqueID;
|
||||
- (JOYControllerCombinedType)combinedControllerType;
|
||||
- (NSArray<JOYButton *> *) buttons;
|
||||
- (NSArray<JOYAxis *> *) axes;
|
||||
|
@ -47,12 +52,15 @@ typedef enum {
|
|||
- (void)setPlayerLEDs:(uint8_t)mask;
|
||||
- (uint8_t)LEDMaskForPlayer:(unsigned)player;
|
||||
@property (readonly, getter=isConnected) bool connected;
|
||||
@property (readonly) JOYJoyConType joyconType;
|
||||
@property (readonly) NSString *deviceName;
|
||||
@property (readonly) NSString *uniqueID;
|
||||
@end
|
||||
|
||||
@interface JOYCombinedController : JOYController
|
||||
- (instancetype)initWithChildren:(NSArray<JOYController *> *)children;
|
||||
- (void)breakApart;
|
||||
@property (readonly) NSArray<JOYController *> *chidlren;
|
||||
@property (readonly) NSArray<JOYController *> *children;
|
||||
@end
|
||||
|
||||
|
||||
|
|
|
@ -12,13 +12,6 @@ extern NSTextField *globalDebugField;
|
|||
|
||||
#define PWM_RESOLUTION 16
|
||||
|
||||
typedef enum {
|
||||
JOYJoyConTypeNone,
|
||||
JOYJoyConTypeLeft,
|
||||
JOYJoyConTypeRight,
|
||||
JOYJoyConTypeCombined,
|
||||
} JOYJoyConType;
|
||||
|
||||
static NSString const *JOYAxisGroups = @"JOYAxisGroups";
|
||||
static NSString const *JOYReportIDFilters = @"JOYReportIDFilters";
|
||||
static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping";
|
||||
|
@ -458,6 +451,7 @@ typedef union {
|
|||
_device = (IOHIDDeviceRef)CFRetain(device);
|
||||
_serialSuffix = suffix;
|
||||
_playerLEDs = -1;
|
||||
[self obtainInfo];
|
||||
|
||||
IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self);
|
||||
IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
|
@ -478,6 +472,7 @@ typedef union {
|
|||
_isSwitch = [_hacks[JOYIsSwitch] boolValue];
|
||||
_isDualShock3 = [_hacks[JOYIsDualShock3] boolValue];
|
||||
_isSony = [_hacks[JOYIsSony] boolValue];
|
||||
_joyconType = [_hacks[JOYJoyCon] unsignedIntValue];
|
||||
|
||||
NSDictionary *customReports = hacks[JOYCustomReports];
|
||||
_lastReport = [NSMutableData dataWithLength:MAX(
|
||||
|
@ -657,15 +652,10 @@ typedef union {
|
|||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)deviceName
|
||||
{
|
||||
if (!_device) return nil;
|
||||
return IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey));
|
||||
}
|
||||
|
||||
- (NSString *)uniqueID
|
||||
- (void)obtainInfo
|
||||
{
|
||||
if (!_device) return nil;
|
||||
_deviceName = IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey));
|
||||
NSString *serial = (__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDSerialNumberKey));
|
||||
if (!serial || [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]) {
|
||||
serial = [NSString stringWithFormat:@"%04x%04x%08x",
|
||||
|
@ -674,9 +664,10 @@ typedef union {
|
|||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDLocationIDKey)) unsignedIntValue]];
|
||||
}
|
||||
if (_serialSuffix) {
|
||||
return [NSString stringWithFormat:@"%@-%@", serial, _serialSuffix];
|
||||
_uniqueID = [NSString stringWithFormat:@"%@-%@", serial, _serialSuffix];
|
||||
return;
|
||||
}
|
||||
return serial;
|
||||
_uniqueID = serial;
|
||||
}
|
||||
|
||||
- (JOYControllerCombinedType)combinedControllerType
|
||||
|
@ -798,7 +789,7 @@ typedef union {
|
|||
[listener controller:_parent ?: self movedAxis:axis];
|
||||
}
|
||||
}
|
||||
JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID)];
|
||||
JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
|
||||
if ([button updateStateFromAxis:axis]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
|
||||
|
@ -820,7 +811,7 @@ typedef union {
|
|||
[listener controller:_parent ?: self movedAxes2D:axes];
|
||||
}
|
||||
}
|
||||
NSArray <JOYEmulatedButton *> *buttons = _axes2DEmulatedButtons[@(axes.uniqueID)];
|
||||
NSArray <JOYEmulatedButton *> *buttons = _axes2DEmulatedButtons[@(axes.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
|
||||
for (JOYEmulatedButton *button in buttons) {
|
||||
if ([button updateStateFromAxes2D:axes]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
|
@ -859,7 +850,7 @@ typedef union {
|
|||
}
|
||||
}
|
||||
|
||||
NSArray <JOYEmulatedButton *> *buttons = _hatEmulatedButtons[@(hat.uniqueID)];
|
||||
NSArray <JOYEmulatedButton *> *buttons = _hatEmulatedButtons[@(hat.uniqueID & 0xFFFFFFFF)]; // Mask the combined prefix away
|
||||
for (JOYEmulatedButton *button in buttons) {
|
||||
if ([button updateStateFromHat:hat]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
|
@ -877,6 +868,7 @@ typedef union {
|
|||
|
||||
- (void)disconnected
|
||||
{
|
||||
_physicallyConnected = false;
|
||||
[_parent breakApart];
|
||||
if (_logicallyConnected && [exposedControllers containsObject:self]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
|
@ -885,7 +877,6 @@ typedef union {
|
|||
}
|
||||
}
|
||||
}
|
||||
_physicallyConnected = false;
|
||||
[exposedControllers removeObject:self];
|
||||
[self setRumbleAmplitude:0];
|
||||
dispatch_sync(_rumbleQueue, ^{
|
||||
|
@ -1211,13 +1202,13 @@ typedef union {
|
|||
{
|
||||
self = [super init];
|
||||
// Sorting makes the device name and unique id consistent
|
||||
_chidlren = [children sortedArrayUsingComparator:^NSComparisonResult(JOYController *a, JOYController *b) {
|
||||
_children = [children sortedArrayUsingComparator:^NSComparisonResult(JOYController *a, JOYController *b) {
|
||||
return [a.uniqueID compare:b.uniqueID];
|
||||
}];
|
||||
|
||||
if (_chidlren.count == 0) return nil;
|
||||
if (_children.count == 0) return nil;
|
||||
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (child.combinedControllerType != JOYControllerCombinedTypeSingle) {
|
||||
NSLog(@"Cannot combine non-single controller %@", child);
|
||||
return nil;
|
||||
|
@ -1229,7 +1220,7 @@ typedef union {
|
|||
}
|
||||
|
||||
unsigned index = 0;
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
|
||||
[listener controllerDisconnected:child];
|
||||
|
@ -1267,11 +1258,12 @@ typedef union {
|
|||
}
|
||||
}
|
||||
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
child->_parent = nil;
|
||||
for (JOYInput *input in child.allInputs) {
|
||||
input.combinedIndex = 0;
|
||||
}
|
||||
if (!child.connected) break;
|
||||
[exposedControllers addObject:child];
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controllerConnected:)]) {
|
||||
|
@ -1284,7 +1276,7 @@ typedef union {
|
|||
- (NSString *)deviceName
|
||||
{
|
||||
NSString *ret = nil;
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (ret) {
|
||||
ret = [ret stringByAppendingFormat:@" + %@", child.deviceName];
|
||||
}
|
||||
|
@ -1298,7 +1290,7 @@ typedef union {
|
|||
- (NSString *)uniqueID
|
||||
{
|
||||
NSString *ret = nil;
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (ret) {
|
||||
ret = [ret stringByAppendingFormat:@"+%@", child.uniqueID];
|
||||
}
|
||||
|
@ -1317,7 +1309,7 @@ typedef union {
|
|||
- (NSArray<JOYButton *> *)buttons
|
||||
{
|
||||
NSArray<JOYButton *> *ret = nil;
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (ret) {
|
||||
ret = [ret arrayByAddingObjectsFromArray:child.buttons];
|
||||
}
|
||||
|
@ -1331,7 +1323,7 @@ typedef union {
|
|||
- (NSArray<JOYAxis *> *)axes
|
||||
{
|
||||
NSArray<JOYAxis *> *ret = nil;
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (ret) {
|
||||
ret = [ret arrayByAddingObjectsFromArray:child.axes];
|
||||
}
|
||||
|
@ -1345,7 +1337,7 @@ typedef union {
|
|||
- (NSArray<JOYAxes2D *> *)axes2D
|
||||
{
|
||||
NSArray<JOYAxes2D *> *ret = nil;
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (ret) {
|
||||
ret = [ret arrayByAddingObjectsFromArray:child.axes2D];
|
||||
}
|
||||
|
@ -1359,7 +1351,7 @@ typedef union {
|
|||
- (NSArray<JOYAxes3D *> *)axes3D
|
||||
{
|
||||
NSArray<JOYAxes3D *> *ret = nil;
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (ret) {
|
||||
ret = [ret arrayByAddingObjectsFromArray:child.axes3D];
|
||||
}
|
||||
|
@ -1373,7 +1365,7 @@ typedef union {
|
|||
- (NSArray<JOYHat *> *)hats
|
||||
{
|
||||
NSArray<JOYHat *> *ret = nil;
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (ret) {
|
||||
ret = [ret arrayByAddingObjectsFromArray:child.hats];
|
||||
}
|
||||
|
@ -1386,7 +1378,7 @@ typedef union {
|
|||
|
||||
- (void)setRumbleAmplitude:(double)amp
|
||||
{
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
[child setRumbleAmplitude:amp];
|
||||
}
|
||||
}
|
||||
|
@ -1395,7 +1387,7 @@ typedef union {
|
|||
{
|
||||
// Mask is actually just the player ID in a combined controller to
|
||||
// allow combining controllers with different LED layouts
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
[child setPlayerLEDs:[child LEDMaskForPlayer:mask]];
|
||||
}
|
||||
}
|
||||
|
@ -1412,7 +1404,7 @@ typedef union {
|
|||
return false;
|
||||
}
|
||||
|
||||
for (JOYController *child in _chidlren) {
|
||||
for (JOYController *child in _children) {
|
||||
if (!child.isConnected) {
|
||||
return false; // Should never happen
|
||||
}
|
||||
|
@ -1421,4 +1413,19 @@ typedef union {
|
|||
return true;
|
||||
}
|
||||
|
||||
- (JOYJoyConType)joyconType
|
||||
{
|
||||
if (_children.count != 2) return JOYJoyConTypeNone;
|
||||
if (_children[0].joyconType == JOYJoyConTypeLeft &&
|
||||
_children[1].joyconType == JOYJoyConTypeRight) {
|
||||
return JOYJoyConTypeCombined;
|
||||
}
|
||||
|
||||
if (_children[1].joyconType == JOYJoyConTypeLeft &&
|
||||
_children[0].joyconType == JOYJoyConTypeRight) {
|
||||
return JOYJoyConTypeCombined;
|
||||
}
|
||||
return JOYJoyConTypeNone;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in New Issue