COnfigurable joypad controls and video options

This commit is contained in:
Michael Buckley 2019-11-10 13:02:21 -08:00
parent b52cc5ee8b
commit 8117af03b1
16 changed files with 1091 additions and 134 deletions

View File

@ -20,9 +20,25 @@
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@interface AppDelegate : NSObject <NSApplicationDelegate, S9xInputDelegate>
- (void)setButtonCode:(S9xButtonCode)buttonCode forKeyCode:(int16)keyCode player:(int8)player;
- (void)clearButton:(S9xButtonCode)button forPlayer:(int8)player;
- (NSArray<S9xJoypad *> *)listJoypads;
- (void)setPlayer:(int8)player forVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index;
- (BOOL)setButton:(S9xButtonCode)button forVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index cookie:(uint32)cookie value:(int32)value;
- (void)clearJoypadForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index buttonCode:(S9xButtonCode)buttonCode;
- (NSString *)labelForVendorID:(uint32)vendorID productID:(uint32)productID cookie:(uint32)cookie value:(int32)value;
- (NSString *)prefsKeyForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index;
- (BOOL)getValuesFromString:(NSString *)str vendorID:(uint32 *)vendorID productID:(uint32 *)productID index:(uint32 *)index;
- (NSString *)prefValueForCookie:(uint32)cookie value:(int32)value;
- (BOOL)getValuesFromString:(NSString *)str cookie:(uint32 *)cookie value:(int32 *)value;
- (void)setVideoMode:(int)videoMode;
- (void)setShowFPS:(BOOL)showFPS;
@end

View File

@ -39,7 +39,8 @@ static NSWindowFrameAutosaveName const kMainWindowIdentifier = @"s9xMainWindow";
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.s9xEngine = [S9xEngine new];
[self setupKeyboard];
self.s9xEngine.inputDelegate = self;
[self setupDefaults];
[self importRecentItems];
NSWindow *window = [[NSWindow alloc] initWithContentRect:s9xView.frame styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO];
@ -76,7 +77,7 @@ static NSWindowFrameAutosaveName const kMainWindowIdentifier = @"s9xMainWindow";
// Insert code here to tear down your application
}
- (void)setupKeyboard
- (void)setupDefaults
{
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
@ -140,6 +141,49 @@ static NSWindowFrameAutosaveName const kMainWindowIdentifier = @"s9xMainWindow";
[self setButtonCode:buttonCode forKeyCode:self.keys[control].integerValue player:player];
}
for ( S9xJoypad *joypad in [self listJoypads])
{
NSMutableDictionary *joypadPrefs = [[defaults objectForKey:kJoypadInputPrefs] mutableCopy];
if (joypadPrefs == nil)
{
joypadPrefs = [NSMutableDictionary new];
[defaults synchronize];
}
NSString *key = [self prefsKeyForVendorID:joypad.vendorID productID:joypad.productID index:joypad.index];
NSMutableDictionary *devicePrefs = [joypadPrefs[key] mutableCopy];
if (devicePrefs == nil)
{
devicePrefs = [NSMutableDictionary new];
for (S9xJoypadInput *input in [self.s9xEngine getInputsForVendorID:joypad.vendorID productID:joypad.productID index:joypad.index])
{
devicePrefs[@(input.buttonCode).stringValue] = [self prefValueForCookie:input.cookie value:input.value];
}
joypadPrefs[key] = devicePrefs;
[defaults setObject:joypadPrefs forKey:kJoypadInputPrefs];
[defaults synchronize];
}
else
{
[self.s9xEngine clearJoypadForVendorID:joypad.vendorID productID:joypad.productID index:joypad.index];
for (NSString *buttonCodeString in devicePrefs)
{
S9xButtonCode buttonCode = (S9xButtonCode)buttonCodeString.intValue;
NSString *str = devicePrefs[buttonCodeString];
uint32 cookie = 0;
int32 value = -1;
if ([self getValuesFromString:str cookie:&cookie value:&value])
{
[self setButton:buttonCode forVendorID:joypad.vendorID productID:joypad.productID index:joypad.index cookie:cookie value:value];
}
}
}
}
[self importKeySettings];
[self importGraphicsSettings];
[defaults synchronize];
@ -167,6 +211,133 @@ static NSWindowFrameAutosaveName const kMainWindowIdentifier = @"s9xMainWindow";
}
}
- (void)clearButton:(S9xButtonCode)button forPlayer:(int8)player
{
[self.s9xEngine clearButton:button forPlayer:player];
NSMutableDictionary *keyDict = [[NSUserDefaults.standardUserDefaults objectForKey:kKeyboardPrefs] mutableCopy];
[keyDict removeObjectForKey:@(button).stringValue];
[NSUserDefaults.standardUserDefaults setObject:[keyDict copy] forKey:kKeyboardPrefs];
[NSUserDefaults.standardUserDefaults synchronize];
}
- (NSArray<S9xJoypad *> *)listJoypads
{
return [self.s9xEngine listJoypads];
}
- (NSString *)prefsKeyForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index
{
return [NSString stringWithFormat:@"%@:%@:%@", @(vendorID).stringValue, @(productID).stringValue, @(index).stringValue];
}
- (BOOL)getValuesFromString:(NSString *)str vendorID:(uint32 *)vendorID productID:(uint32 *)productID index:(uint32 *)index
{
if (vendorID == NULL || productID == NULL || index == NULL)
{
return NO;
}
NSArray<NSString *> *components = [str componentsSeparatedByString:@":"];
if (components.count != 3)
{
return NO;
}
*vendorID = components[0].intValue;
*productID = components[1].intValue;
*index = components[2].intValue;
return YES;
}
- (NSString *)prefValueForCookie:(uint32)cookie value:(int32)value
{
return [NSString stringWithFormat:@"%@:%@", @(cookie).stringValue, @(value).stringValue];
}
- (BOOL)getValuesFromString:(NSString *)str cookie:(uint32 *)cookie value:(int32 *)value
{
if (cookie == NULL || value == NULL)
{
return NO;
}
NSArray<NSString *> *components = [str componentsSeparatedByString:@":"];
if (components.count != 2)
{
return NO;
}
*cookie = components.firstObject.intValue;
*value = components.lastObject.intValue;
return YES;
}
- (void)setPlayer:(int8)player forVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index
{
int8 oldPlayer = -1;
[self.s9xEngine setPlayer:player forVendorID:vendorID productID:productID index:index oldPlayer:&oldPlayer];
NSMutableDictionary *playersDict = [[NSUserDefaults.standardUserDefaults objectForKey:kJoypadPlayerPrefs] mutableCopy];
if (playersDict == nil)
{
playersDict = [NSMutableDictionary new];
}
if (oldPlayer >= 0 && player != oldPlayer)
{
[playersDict removeObjectForKey:@(oldPlayer).stringValue];
}
playersDict[@(player).stringValue] = [self prefsKeyForVendorID:vendorID productID:productID index:index];
[NSUserDefaults.standardUserDefaults setObject:[playersDict copy] forKey:kJoypadPlayerPrefs];
}
- (BOOL)setButton:(S9xButtonCode)button forVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index cookie:(uint32)cookie value:(int32)value
{
S9xButtonCode oldButton = (S9xButtonCode)-1;
BOOL result = [self.s9xEngine setButton:button forVendorID:vendorID productID:productID index:index cookie:cookie value:value oldButton:&oldButton];
NSMutableDictionary *prefsDict = [[NSUserDefaults.standardUserDefaults objectForKey:kJoypadInputPrefs] mutableCopy];
NSString *key = [self prefsKeyForVendorID:vendorID productID:productID index:index];
NSMutableDictionary *joypadDict = [prefsDict[key] mutableCopy];
if (result && button != oldButton)
{
[joypadDict removeObjectForKey:@(oldButton).stringValue];
}
joypadDict[@(button).stringValue] = [self prefValueForCookie:cookie value:value];
prefsDict[key] = [joypadDict copy];
[NSUserDefaults.standardUserDefaults setObject:[prefsDict copy] forKey:kJoypadInputPrefs];
return result;
}
- (void)clearJoypadForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index buttonCode:(S9xButtonCode)buttonCode
{
[self.s9xEngine clearJoypadForVendorID:vendorID productID:productID index:index buttonCode:buttonCode];
NSString *key = [self prefsKeyForVendorID:vendorID productID:productID index:index];
NSMutableDictionary *joypadsDict = [[NSUserDefaults.standardUserDefaults objectForKey:kJoypadInputPrefs] mutableCopy];
NSMutableDictionary *deviceDict = [joypadsDict[key] mutableCopy];
[deviceDict removeObjectForKey:@(buttonCode).stringValue];
joypadsDict[key] = deviceDict;
[NSUserDefaults.standardUserDefaults setObject:[joypadsDict copy] forKey:kJoypadInputPrefs];
[NSUserDefaults.standardUserDefaults synchronize];
}
- (NSString *)labelForVendorID:(uint32)vendorID productID:(uint32)productID cookie:(uint32)cookie value:(int32)value
{
return [self.s9xEngine labelForVendorID:vendorID productID:productID cookie:cookie value:value];
}
- (void)importRecentItems
{
const NSInteger maxRecents = 20;
@ -216,12 +387,16 @@ static NSWindowFrameAutosaveName const kMainWindowIdentifier = @"s9xMainWindow";
[NSUserDefaults.standardUserDefaults setBool:(data.length > 0 && ((char *)data.bytes)[0]) forKey:kShowFPSPref];
}
[self setShowFPS:[NSUserDefaults.standardUserDefaults boolForKey:kShowFPSPref]];
data = [self objectForPrefOSCode:'Vmod'];
if ( data != nil)
{
[NSUserDefaults.standardUserDefaults setInteger:((data.length >= 0 && ((char *)data.bytes)[0]) ? VIDEOMODE_SMOOTH : VIDEOMODE_BLOCKY) forKey:kVideoModePref];
}
[self setVideoMode:(int)[NSUserDefaults.standardUserDefaults integerForKey:kVideoModePref]];
}
- (id)objectForPrefOSCode:(uint32_t)osCode
@ -304,4 +479,33 @@ static NSWindowFrameAutosaveName const kMainWindowIdentifier = @"s9xMainWindow";
[self.prefsWindowController.window makeKeyAndOrderFront:self];
}
- (void)setVideoMode:(int)videoMode
{
[self.s9xEngine setVideoMode:videoMode];
[NSUserDefaults.standardUserDefaults setObject:@(videoMode) forKey:kVideoModePref];
[NSUserDefaults.standardUserDefaults synchronize];
}
- (void)setShowFPS:(BOOL)showFPS
{
[self.s9xEngine setShowFPS:showFPS];
[NSUserDefaults.standardUserDefaults setObject:@(showFPS) forKey:kShowFPSPref];
[NSUserDefaults.standardUserDefaults synchronize];
}
- (IBAction)resume:(id)sender
{
[self.s9xEngine resume];
}
- (BOOL)handleInput:(S9xJoypadInput *)input fromJoypad:(S9xJoypad *)joypad
{
if (NSApp.keyWindow == self.prefsWindowController.window)
{
return [((S9xPrefsViewController *) self.prefsWindowController.contentViewController) handleInput:input fromJoypad:joypad];
}
return NO;
}
@end

View File

@ -95,6 +95,18 @@
</items>
</menu>
</menuItem>
<menuItem title="Emulation" id="Kjk-eO-rPI">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Emulation" id="s2F-Pd-YT5">
<items>
<menuItem title="Resume" keyEquivalent="r" id="m8Y-5s-VGC">
<connections>
<action selector="resume:" target="-1" id="jxw-ZM-JXi"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">

View File

@ -20,11 +20,15 @@
#import <Cocoa/Cocoa.h>
@class S9xJoypadInput;
NS_ASSUME_NONNULL_BEGIN
@interface S9xButtonConfigTextField : NSSearchField<NSTextFieldDelegate>
@interface S9xButtonConfigTextField : NSSearchField<NSSearchFieldDelegate>
@property (nonatomic, assign) CGKeyCode keyCode;
@property (nonatomic, strong, nullable) S9xJoypadInput *joypadInput;
@property (nonatomic, assign) BOOL disableKeyboardInput;
@end

View File

@ -30,10 +30,21 @@
self.delegate = self;
self.placeholderString = @"";
[[self cell] setSearchButtonCell:nil];
NSButtonCell *cancelButton = [[self cell] cancelButtonCell];
cancelButton.target = self;
cancelButton.action = @selector(clearSearch:);
}
- (void)setKeyCode:(CGKeyCode)keyCode
{
if (keyCode == (CGKeyCode)-1)
{
_keyCode = keyCode;
self.stringValue = @"";
return;
}
NSString *stringValue = nil;
switch (keyCode)
@ -266,11 +277,19 @@
- (void)keyUp:(NSEvent *)event
{
[self setKeyCode:event.keyCode];
if (!self.disableKeyboardInput )
{
[self setKeyCode:event.keyCode];
}
}
- (void)flagsChanged:(NSEvent *)event
{
if (self.disableKeyboardInput)
{
return;
}
NSEventModifierFlags flags = event.modifierFlags;
if ( flags & NSEventModifierFlagShift )
@ -297,8 +316,27 @@
[self.currentEditor selectAll:self];
}
- (void)clearSearch:(id)sender
{
self.stringValue = @"";
if (self.disableKeyboardInput)
{
self.joypadInput = nil;
}
else
{
self.keyCode = -1;
}
}
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector
{
if (self.disableKeyboardInput)
{
return NO;
}
if (commandSelector == @selector(insertTab:))
{
[self setKeyCode:kVK_Tab];
@ -313,4 +351,9 @@
return NO;
}
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor
{
return !self.disableKeyboardInput;
}
@end

View File

@ -21,5 +21,7 @@
#import <Foundation/Foundation.h>
extern NSString * const kKeyboardPrefs;
extern NSString * const kJoypadInputPrefs;
extern NSString * const kJoypadPlayerPrefs;
extern NSString * const kShowFPSPref;
extern NSString * const kVideoModePref;

View File

@ -21,5 +21,7 @@
#import "S9xPrefsConstants.h"
NSString * const kKeyboardPrefs = @"KeyboardConfig";
NSString * const kJoypadInputPrefs = @"JoypadInputs";
NSString * const kJoypadPlayerPrefs = @"JoypadPlayers";
NSString * const kShowFPSPref = @"ShowFPS";
NSString * const kVideoModePref = @"VideoMode";

View File

@ -21,7 +21,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface S9xPrefsViewController : NSViewController
@interface S9xPrefsViewController : NSViewController<S9xInputDelegate>
@end

View File

@ -36,12 +36,58 @@
- (void)awakeFromNib
{
AppDelegate *appDelegate = (AppDelegate *)NSApp.delegate;
NSUInteger joypadIndex = 0;
for (S9xJoypad *joypad in [appDelegate listJoypads])
{
NSMenuItem *item = [NSMenuItem new];
item.title = joypad.name;
item.tag = joypadIndex++;
item.representedObject = joypad;
[self.devicePopUp.menu addItem:item];
}
[self selectDeviceForPlayer:0];
for (NSView *subview in self.view.subviews)
{
if ( [subview isKindOfClass:[S9xButtonConfigTextField class]] )
{
S9xButtonConfigTextField *field = (S9xButtonConfigTextField *)subview;
[field addObserver:self forKeyPath:@"keyCode" options:NSKeyValueObservingOptionNew context:NULL];
[field addObserver:self forKeyPath:@"joypadInput" options:NSKeyValueObservingOptionNew context:NULL];
}
}
}
- (void)selectDeviceForPlayer:(int8_t)player
{
AppDelegate *appDelegate = (AppDelegate *)NSApp.delegate;
NSString* joypadKey = [[NSUserDefaults.standardUserDefaults objectForKey:kJoypadPlayerPrefs] objectForKey:@(player).stringValue];
[self.devicePopUp selectItemAtIndex:0];
if (joypadKey != nil)
{
uint32 vendorID = 0;
uint32 productID = 0;
uint32 index = 0;
if ( [appDelegate getValuesFromString:joypadKey vendorID:&vendorID productID:&productID index:&index])
{
S9xJoypad *joypad = [S9xJoypad new];
joypad.vendorID = vendorID;
joypad.productID = productID;
joypad.index = index;
for (NSMenuItem *item in self.devicePopUp.menu.itemArray)
{
if ([joypad isEqual:item.representedObject])
{
[self.devicePopUp selectItem:item];
break;
}
}
}
}
}
@ -59,9 +105,10 @@
[self.videoModePopup selectItemAtIndex:index];
self.showFPSCheckbox.state = [NSUserDefaults.standardUserDefaults boolForKey:kShowFPSPref];
NSMutableDictionary<NSNumber *, NSNumber *> *controlsDict = [NSMutableDictionary new];
if (self.devicePopUp.selectedItem.tag < 0)
{
NSMutableDictionary<NSNumber *, NSNumber *> *controlsDict = [NSMutableDictionary new];
NSDictionary *keyboardDict = [NSUserDefaults.standardUserDefaults objectForKey:kKeyboardPrefs];
NSInteger playerNum = self.playerPopUp.selectedItem.tag;
@ -69,46 +116,182 @@
{
controlsDict[@(i)] = keyboardDict[@(i + playerNum).stringValue];
}
}
for (NSView *subview in self.view.subviews)
{
if ( [subview isKindOfClass:[S9xButtonConfigTextField class]] )
for (NSView *subview in self.view.subviews)
{
S9xButtonConfigTextField *field = (S9xButtonConfigTextField *)subview;
[field removeObserver:self forKeyPath:@"keyCode"];
NSNumber *keyCode = controlsDict[@(field.tag)];
if ( keyCode != nil )
if ([subview isKindOfClass:[S9xButtonConfigTextField class]])
{
field.keyCode = keyCode.intValue;
}
else
{
field.stringValue = @"";
}
S9xButtonConfigTextField *field = (S9xButtonConfigTextField *)subview;
[field addObserver:self forKeyPath:@"keyCode" options:NSKeyValueObservingOptionNew context:NULL];
[field removeObserver:self forKeyPath:@"keyCode"];
[field removeObserver:self forKeyPath:@"joypadInput"];
NSNumber *keyCode = controlsDict[@(field.tag)];
field.joypadInput = nil;
if ( keyCode != nil )
{
field.keyCode = keyCode.intValue;
}
else
{
field.keyCode = -1;
}
[field addObserver:self forKeyPath:@"keyCode" options:NSKeyValueObservingOptionNew context:NULL];
[field addObserver:self forKeyPath:@"joypadInput" options:NSKeyValueObservingOptionNew context:NULL];
field.disableKeyboardInput = NO;
}
}
}
else
{
AppDelegate *appDelegate = (AppDelegate *)NSApp.delegate;
S9xJoypad *joypad = self.devicePopUp.selectedItem.representedObject;
NSString *joypadKey = [appDelegate prefsKeyForVendorID:joypad.vendorID productID:joypad.productID index:joypad.index];
NSDictionary *joypadDIct = [[NSUserDefaults.standardUserDefaults objectForKey:kJoypadInputPrefs] objectForKey:joypadKey];
for (NSView *subview in self.view.subviews)
{
if ([subview isKindOfClass:[S9xButtonConfigTextField class]])
{
S9xButtonConfigTextField *textField = (S9xButtonConfigTextField *)subview;
[textField removeObserver:self forKeyPath:@"keyCode"];
[textField removeObserver:self forKeyPath:@"joypadInput"];
uint32 cookie = 0;
int32 value = 0;
S9xButtonCode buttonCode = (S9xButtonCode)textField.tag;
NSString *inputString = joypadDIct[@(buttonCode).stringValue];
textField.keyCode = -1;
if ([appDelegate getValuesFromString:inputString cookie:&cookie value:&value])
{
S9xJoypadInput *input = [S9xJoypadInput new];
input.cookie = cookie;
input.value = value;
input.buttonCode = buttonCode;
textField.joypadInput = input;
textField.stringValue = [appDelegate labelForVendorID:joypad.vendorID productID:joypad.productID cookie:cookie value:value];
}
else
{
textField.joypadInput = nil;
textField.stringValue = @"";
}
[textField addObserver:self forKeyPath:@"keyCode" options:NSKeyValueObservingOptionNew context:NULL];
[textField addObserver:self forKeyPath:@"joypadInput" options:NSKeyValueObservingOptionNew context:NULL];
textField.disableKeyboardInput = YES;
}
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ( [keyPath isEqualToString:@"keyCode"] )
if ([keyPath isEqualToString:@"keyCode"])
{
S9xButtonConfigTextField *field = (S9xButtonConfigTextField *)object;
S9xButtonCode buttonCode = (S9xButtonCode)field.tag;
uint16_t keyCode = field.keyCode;
int8_t player = self.playerPopUp.selectedItem.tag;
[((AppDelegate *) NSApp.delegate) setButtonCode:buttonCode forKeyCode:keyCode player:player];
if (keyCode != (CGKeyCode)-1)
{
[((AppDelegate *) NSApp.delegate) setButtonCode:buttonCode forKeyCode:keyCode player:player];
}
else
{
[((AppDelegate *) NSApp.delegate) clearButton:buttonCode forPlayer:player];
}
[NSUserDefaults.standardUserDefaults synchronize];
[self refresh];
}
else if ( [keyPath isEqualToString:@"joypadInput"])
{
S9xButtonConfigTextField *field = (S9xButtonConfigTextField *)object;
S9xButtonCode buttonCode = (S9xButtonCode)field.tag;
S9xJoypad *joypad = self.devicePopUp.selectedItem.representedObject;
if ([joypad isKindOfClass:[S9xJoypad class]])
{
S9xJoypadInput *input = field.joypadInput;
if (input != nil)
{
[((AppDelegate *)NSApp.delegate) setButton:buttonCode forVendorID:joypad.vendorID productID:joypad.productID index:joypad.index cookie:input.cookie value:input.value];
}
else
{
[((AppDelegate *)NSApp.delegate) clearJoypadForVendorID:joypad.vendorID productID:joypad.productID index:joypad.index buttonCode:buttonCode];
}
}
[NSUserDefaults.standardUserDefaults synchronize];
[self refresh];
}
}
- (IBAction)playerDropdownChanged:(NSPopUpButton *)sender
{
[self selectDeviceForPlayer:sender.selectedTag];
[self refresh];
}
- (IBAction)deviceDropdownChanged:(NSPopUpButton *)sender
{
if (sender.selectedTag >= 0)
{
AppDelegate *appDelegate = (AppDelegate *)NSApp.delegate;
S9xJoypad *joypad = sender.selectedItem.representedObject;
[appDelegate setPlayer:self.playerPopUp.selectedTag forVendorID:joypad.vendorID productID:joypad.productID index:joypad.index];
[NSUserDefaults.standardUserDefaults synchronize];
}
[self refresh];
}
- (IBAction)showFPS:(NSButton *)sender
{
AppDelegate *appDelegate = (AppDelegate *)NSApp.delegate;
[appDelegate setShowFPS:sender.state == NSOnState];
}
- (IBAction)setVideoMode:(NSPopUpButton *)sender
{
AppDelegate *appDelegate = (AppDelegate *)NSApp.delegate;
[appDelegate setVideoMode:(int)sender.selectedTag];
}
- (BOOL)handleInput:(S9xJoypadInput *)input fromJoypad:(S9xJoypad *)joypad
{
id firstResponder = self.view.window.firstResponder;
if ([firstResponder isFieldEditor])
{
firstResponder = [firstResponder delegate];
}
if ([firstResponder respondsToSelector:@selector(setJoypadInput:)])
{
S9xJoypad *currentJoypad = self.devicePopUp.selectedItem.representedObject;
if ([joypad isEqual:currentJoypad])
{
[firstResponder setJoypadInput:input];
return YES;
}
}
return NO;
}
@end

View File

@ -22,25 +22,28 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rxW-aA-Bai">
<rect key="frame" x="18" y="538" width="77" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Video Mode" id="XoT-95-aIF">
<rect key="frame" x="18" y="538" width="191" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Video Mode (Requires Restart)" id="XoT-95-aIF">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qNi-B0-E49">
<rect key="frame" x="99" y="532" width="85" height="25"/>
<rect key="frame" x="213" y="532" width="85" height="25"/>
<popUpButtonCell key="cell" type="push" title="Blocky" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Xg7-hz-RRs" id="IRO-Ia-Tlm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="VtI-7w-Yps">
<items>
<menuItem title="Blocky" state="on" id="Xg7-hz-RRs"/>
<menuItem title="Smooth" tag="1" id="ucR-ef-Wfk"/>
<menuItem title="Smooth" tag="2" id="ucR-ef-Wfk"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="setVideoMode:" target="-2" id="oj8-pk-Kdg"/>
</connections>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hfx-Lg-t9P">
<rect key="frame" x="18" y="503" width="472" height="25"/>
@ -60,6 +63,9 @@
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="playerDropdownChanged:" target="-2" id="P4K-gg-QUK"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qMp-14-9zW">
<rect key="frame" x="18" y="443" width="88" height="16"/>
@ -211,6 +217,9 @@
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="showFPS:" target="-2" id="CM2-bf-aFb"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TuE-fA-Shh">
<rect key="frame" x="230" y="353" width="151" height="16"/>
@ -311,6 +320,9 @@
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="deviceDropdownChanged:" target="-2" id="oH3-xl-IZF"/>
</connections>
</popUpButton>
<searchField wantsLayer="YES" verticalHuggingPriority="750" tag="1" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5wn-WM-n7f" customClass="S9xButtonConfigTextField">
<rect key="frame" x="112" y="410" width="100" height="22"/>

View File

@ -22,12 +22,117 @@
#ifndef _mac_joypad_h_
#define _mac_joypad_h_
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "mac-controls.h"
struct JoypadDevice {
uint16 vendorID;
uint16 productID;
uint32 index;
bool operator==(const struct JoypadDevice &o) const
{
return vendorID == o.vendorID && productID == o.productID && index == o.index;
}
bool operator<(const struct JoypadDevice &o) const
{
return vendorID < o.vendorID || productID < o.productID || index < o.index;
}
};
struct JoypadCookie {
struct JoypadDevice device;
uint32 cookie;
JoypadCookie() {}
struct JoypadCookie &operator=(const struct JoypadCookie &o)
{
device = o.device;
cookie = o.cookie;
return *this;
}
bool operator==(const struct JoypadCookie &o) const
{
return device == o.device && cookie == o.cookie;
}
bool operator<(const struct JoypadCookie &o) const
{
return device < o.device || cookie < o.cookie;
}
};
struct JoypadCookieInfo {
uint32 usage;
uint32 index;
int32 midpoint;
int32 min;
int32 max;
};
struct JoypadInput {
struct JoypadCookie cookie;
int32 value;
bool operator==(const struct JoypadInput &o) const
{
return cookie == o.cookie && value == o.value;
}
bool operator<(const struct JoypadInput &o) const
{
return cookie < o.cookie || value < o.value;
}
};
namespace std {
template <>
struct hash<struct JoypadDevice>
{
std::size_t operator()(const JoypadDevice& k) const
{
return k.vendorID ^ k.productID ^ k.index;
}
};
template <>
struct hash<struct JoypadCookie>
{
std::size_t operator()(const JoypadCookie& k) const
{
return std::hash<struct JoypadDevice>()(k.device) ^ k.cookie;
}
};
template <>
struct hash<struct JoypadInput>
{
std::size_t operator()(const JoypadInput& k) const
{
return std::hash<struct JoypadCookie>()(k.cookie) ^ k.value;
}
};
}
void SetUpHID (void);
void ReleaseHID (void);
void SetPlayerForJoypad(int8 playerNum, uint32 vendorID, uint32 productID, uint8 index, int8 *oldPlayerNum);
void SetButtonCodeForJoypadControl(uint32 vendorID, uint32 productID, uint8 index, uint32 cookie, int32 value, S9xButtonCode buttonCode, bool overwrite, S9xButtonCode *oldButtonCode);
std::unordered_set<struct JoypadDevice> ListJoypads (void);
std::string NameForDevice(struct JoypadDevice device);
void SetPlayerForJoypad(int8 playerNum, uint32 vendorID, uint32 productID, uint32 index, int8 *oldPlayerNum);
bool SetButtonCodeForJoypadControl(uint32 vendorID, uint32 productID, uint32 index, uint32 cookie, int32 value, S9xButtonCode buttonCode, bool overwrite, S9xButtonCode *oldButtonCode);
void ClearButtonCodeForJoypad(uint32 vendorID, uint32 productID, uint32 index, S9xButtonCode buttonCode);
void ClearJoypad(uint32 vendorID, uint32 productID, uint32 index);
std::unordered_map<struct JoypadInput, S9xButtonCode> GetJuypadButtons(uint32 vendorID, uint32 productID, uint32 index);
std::string LabelForInput(uint32 vendorID, uint32 productID, uint32 cookie, int32 value);
#endif

View File

@ -20,8 +20,6 @@
#include <map>
#include <unordered_map>
#include <unordered_set>
#include "port.h"
@ -60,107 +58,15 @@
typedef hu_device_t *pRecDevice;
typedef hu_element_t *pRecElement;
struct JoypadDevice {
uint16 vendorID;
uint16 productID;
uint32 index;
bool operator==(const struct JoypadDevice &o) const
{
return vendorID == o.vendorID && productID == o.productID && index == o.index;
}
bool operator<(const struct JoypadDevice &o) const
{
return vendorID < o.vendorID || productID < o.productID || index < o.index;
}
};
struct JoypadCookie {
struct JoypadDevice device;
uint32 cookie;
JoypadCookie() {}
struct JoypadCookie &operator=(const struct JoypadCookie &o)
{
device = o.device;
cookie = o.cookie;
return *this;
}
bool operator==(const struct JoypadCookie &o) const
{
return device == o.device && cookie == o.cookie;
}
bool operator<(const struct JoypadCookie &o) const
{
return device < o.device || cookie < o.cookie;
}
};
struct JoypadCookieInfo {
uint32 usage;
uint32 index;
int32 midpoint;
int32 min;
int32 max;
};
struct JoypadInput {
struct JoypadCookie cookie;
int32 value;
bool operator==(const struct JoypadInput &o) const
{
return cookie == o.cookie && value == o.value;
}
bool operator<(const struct JoypadInput &o) const
{
return cookie < o.cookie || value < o.value;
}
};
namespace std {
template <>
struct hash<struct JoypadDevice>
{
std::size_t operator()(const JoypadDevice& k) const
{
return k.vendorID ^ k.productID ^ k.index;
}
};
template <>
struct hash<struct JoypadCookie>
{
std::size_t operator()(const JoypadCookie& k) const
{
return std::hash<struct JoypadDevice>()(k.device) ^ k.cookie;
}
};
template <>
struct hash<struct JoypadInput>
{
std::size_t operator()(const JoypadInput& k) const
{
return std::hash<struct JoypadCookie>()(k.cookie) ^ k.value;
}
};
}
std::unordered_set<JoypadDevice> allDevices;
std::unordered_map<JoypadDevice, std::map<uint8, std::map<int8, S9xButtonCode>>> defaultAxes;
std::unordered_map<JoypadDevice, std::map<uint8, S9xButtonCode>> defaultButtons;
std::unordered_map<JoypadDevice, std::map<uint8, S9xButtonCode>> defaultHatValues;
// TODO: Hook these next two up
std::unordered_map<JoypadDevice, int8> playerNumByDevice;
std::unordered_map<uint32, int8> deviceIndexByPort;
std::unordered_map<JoypadCookie, JoypadCookieInfo> infoByCookie;
std::unordered_map<JoypadInput, S9xButtonCode> buttonCodeByJoypadInput;
std::unordered_map<JoypadDevice, std::string> namesByDevice;
@interface NSData (S9xHexString)
+(id)s9x_dataWithHexString:(NSString *)hex;
@ -191,6 +97,20 @@ std::unordered_map<JoypadInput, S9xButtonCode> buttonCodeByJoypadInput;
IOHIDManagerRef hidManager = NULL;
std::unordered_set<struct JoypadDevice> ListJoypads (void) {
return allDevices;
}
std::string NameForDevice(struct JoypadDevice device) {
auto it = namesByDevice.find(device);
if (it != namesByDevice.end())
{
return it->second;
}
return "";
}
void gamepadAction(void *inContext, IOReturn inResult, void *inSender, IOHIDValueRef v) {
os_unfair_lock_lock(&keyLock);
@ -244,6 +164,22 @@ void gamepadAction(void *inContext, IOReturn inResult, void *inSender, IOHIDValu
inputStruct.cookie = cookieStruct;
inputStruct.value = (int32_t)IOHIDValueGetIntegerValue(v);
S9xJoypad *objcJoypad = [S9xJoypad new];
objcJoypad.vendorID = deviceStruct.vendorID;
objcJoypad.productID = deviceStruct.productID;
objcJoypad.index = deviceStruct.index;
S9xJoypadInput *objcInput = [S9xJoypadInput new];
objcInput.cookie = inputStruct.cookie.cookie;
objcInput.value =inputStruct.value;
os_unfair_lock_unlock(&keyLock);
if ([inputDelegate handleInput:objcInput fromJoypad:objcJoypad])
{
return;
}
os_unfair_lock_lock(&keyLock);
struct JoypadInput oppositeInputStruct = inputStruct;
if (info.min != info.max)
@ -552,6 +488,7 @@ void AddDevice (IOHIDDeviceRef device)
{
NSNumber *vendor = (NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
NSNumber *product = (NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
NSString *name = (NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
NSMutableArray<NSDictionary *> *buttons = [NSMutableArray new];
NSMutableArray<NSDictionary *> *axes = [NSMutableArray new];
@ -570,6 +507,14 @@ void AddDevice (IOHIDDeviceRef device)
}
allDevices.insert(deviceStruct);
std::string s = std::string(name.UTF8String);
if (deviceStruct.index > 0)
{
s = s + " (" + std::to_string(deviceStruct.index + 1) + ")";
}
namesByDevice[deviceStruct] = s;
uint32_t port = ((NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDLocationIDKey))).unsignedIntValue;
deviceIndexByPort[port] = deviceStruct.index;
@ -662,7 +607,7 @@ void AddDevice (IOHIDDeviceRef device)
}
}
// TODO: Extend axisIndex into defaultAxes
info.usage = axisDict[@kIOHIDElementUsageKey].intValue;
info.index = axisIndex++;
infoByCookie[cookie] = info;
}
@ -698,6 +643,46 @@ void AddDevice (IOHIDDeviceRef device)
CFRelease(properties);
}
void ClearJoypad(uint32 vendorID, uint32 productID, uint32 index)
{
struct JoypadDevice device;
device.vendorID = vendorID;
device.productID = productID;
device.index = index;
for (auto it = buttonCodeByJoypadInput.begin(); it != buttonCodeByJoypadInput.end();)
{
if (it->first.cookie.device == device)
{
buttonCodeByJoypadInput.erase(it++);
}
else
{
++it;
}
}
}
std::unordered_map<struct JoypadInput, S9xButtonCode> GetJuypadButtons(uint32 vendorID, uint32 productID, uint32 index)
{
struct JoypadDevice device;
device.vendorID = vendorID;
device.productID = productID;
device.index = index;
std::unordered_map<struct JoypadInput, S9xButtonCode> joypadButtons;
for (auto it = buttonCodeByJoypadInput.begin(); it != buttonCodeByJoypadInput.end(); ++it)
{
if ( it->first.cookie.device == device)
{
joypadButtons[it->first] = it->second;
}
}
return joypadButtons;
}
void SetUpHID (void)
{
hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
@ -776,7 +761,7 @@ void ReleaseHID (void)
}
}
void SetPlayerForJoypad(int8 playerNum, uint32 vendorID, uint32 productID, uint8 index, int8 *oldPlayerNum)
void SetPlayerForJoypad(int8 playerNum, uint32 vendorID, uint32 productID, uint32 index, int8 *oldPlayerNum)
{
struct JoypadDevice device;
device.vendorID = vendorID;
@ -796,8 +781,13 @@ void SetPlayerForJoypad(int8 playerNum, uint32 vendorID, uint32 productID, uint8
playerNumByDevice[device] = playerNum;
}
void SetButtonCodeForJoypadControl(uint32 vendorID, uint32 productID, uint8 index, uint32 cookie, int32 value, S9xButtonCode buttonCode, bool overwrite, S9xButtonCode *oldButtonCode)
bool SetButtonCodeForJoypadControl(uint32 vendorID, uint32 productID, uint32 index, uint32 cookie, int32 value, S9xButtonCode buttonCode, bool overwrite, S9xButtonCode *oldButtonCode)
{
if (buttonCode < 0 || buttonCode >= kNumButtons)
{
return false;
}
if (oldButtonCode != NULL)
{
*oldButtonCode = (S9xButtonCode)-1;
@ -818,11 +808,11 @@ void SetButtonCodeForJoypadControl(uint32 vendorID, uint32 productID, uint8 inde
if ( info.min != info.max )
{
if (value < info.min)
if (value <= info.min)
{
value = info.min;
}
else if (value > info.max)
else if (value >= info.max)
{
value = info.max;
}
@ -846,8 +836,195 @@ void SetButtonCodeForJoypadControl(uint32 vendorID, uint32 productID, uint8 inde
*oldButtonCode = buttonCodeByJoypadInput[input];
}
for (auto it = buttonCodeByJoypadInput.begin(); it != buttonCodeByJoypadInput.end();)
{
if (it->second == buttonCode && it->first.cookie.device == device)
{
if (overwrite)
{
buttonCodeByJoypadInput.erase(it++);
}
else
{
++it;
}
}
else
{
++it;
}
}
if (overwrite)
{
buttonCodeByJoypadInput[input] = buttonCode;
}
return true;
}
void ClearButtonCodeForJoypad(uint32 vendorID, uint32 productID, uint32 index, S9xButtonCode buttonCode)
{
struct JoypadDevice device;
device.vendorID = vendorID;
device.productID = productID;
device.index = index;
for (auto it = buttonCodeByJoypadInput.begin(); it != buttonCodeByJoypadInput.end();)
{
if (it->first.cookie.device == device && it->second == buttonCode)
{
buttonCodeByJoypadInput.erase(it++);
}
else
{
++it;
}
}
}
std::string LabelForInput(uint32 vendorID, uint32 productID, uint32 cookie, int32 value)
{
struct JoypadDevice deviceStruct;
deviceStruct.productID = productID;
deviceStruct.vendorID = vendorID;
deviceStruct.index = 0;
struct JoypadCookie cookieStruct;
cookieStruct.device = deviceStruct;
cookieStruct.cookie = cookie;
auto it = infoByCookie.find(cookieStruct);
if (it != infoByCookie.end())
{
auto info = it->second;
switch(info.usage)
{
case kHIDUsage_GD_X:
{
if (value <= info.min)
{
return "X-";
}
else if (value >= info.max)
{
return "X+";
}
}
case kHIDUsage_GD_Y:
{
if (value <= info.min)
{
return "Y-";
}
else if (value >= info.max)
{
return "Y+";
}
}
case kHIDUsage_GD_Z:
{
if (value <= info.min)
{
return "Z-";
}
else if (value >= info.max)
{
return "Z+";
}
}
case kHIDUsage_GD_Rx:
{
if (value <= info.min)
{
return "Right X-";
}
else if (value >= info.max)
{
return "Right X+";
}
}
case kHIDUsage_GD_Ry:
{
if (value <= info.min)
{
return "Right Y-";
}
else if (value >= info.max)
{
return "Right Y+";
}
}
case kHIDUsage_GD_Rz:
{
if (value <= info.min)
{
return "Right Z-";
}
else if (value >= info.max)
{
return "Right Z+";
}
}
case kHIDUsage_GD_Hatswitch:
{
auto defaultIT = defaultHatValues.find(deviceStruct);
if (defaultIT != defaultHatValues.end())
{
auto hatDict = defaultIT->second;
auto hatIT = hatDict.find(value);
if ( hatIT != hatDict.end())
{
switch (hatIT->second)
{
case kUp:
return "D-Pad Up";
case kDown:
return "D-Pad Down";
case kLeft:
return "D-Pad Left";
case kRight:
return "D-Pad Right";
default:
break;
}
}
}
if (value == 1)
{
return "D-Pad Up";
}
else if (value == 2)
{
return "D-Pad Right";
}
else if (value == 4)
{
return "D-Pad Down";
}
else if (value == 8)
{
return "D-Pad Left";
}
}
default:
{
return std::string("Button " + std::to_string(info.index));
}
}
}
return std::to_string(cookie);
}

View File

@ -37,5 +37,6 @@ void InitKeyboard (void);
void DeinitKeyboard (void);
bool SetKeyCode(int16 keyCode, S9xButtonCode button, int8 player, int16 *oldKeyCode, S9xButtonCode *oldButton, int8 *oldPlayer);
void ClearKeyCode(S9xButtonCode buttonCode, int8 player);
#endif

View File

@ -81,3 +81,20 @@ bool SetKeyCode(int16 keyCode, S9xButtonCode buttonCode, int8 player, int16 *old
return true;
}
void ClearKeyCode(S9xButtonCode buttonCode, int8 player)
{
if (player < 0 || player >= MAC_MAX_PLAYERS || buttonCode < 0 || buttonCode >= kNumButtons)
{
return;
}
for ( int i = 0; i < MAC_NUM_KEYCODES; ++i)
{
struct S9xButton button = keyCodes[i];
if (button.player == player && button.buttonCode == buttonCode)
{
keyCodes[i] = { -1, -1 };
}
}
}

View File

@ -181,8 +181,29 @@ uint64 GetMicroseconds(void);
void CopyPressedKeys(uint8 keys[MAC_MAX_PLAYERS][kNumButtons], uint8 gamepadButtons[MAC_MAX_PLAYERS][kNumButtons]);
@interface S9xJoypad : NSObject
@property (nonatomic, assign) uint32 vendorID;
@property (nonatomic, assign) uint32 productID;
@property (nonatomic, assign) uint8 index;
@property (nonatomic, copy) NSString *name;
@end
@interface S9xJoypadInput : NSObject
@property (nonatomic, assign) uint32 cookie;
@property (nonatomic, assign) int32 value;
@property (nonatomic, assign) S9xButtonCode buttonCode;
@end
@protocol S9xInputDelegate <NSObject>
- (BOOL)handleInput:(S9xJoypadInput *)input fromJoypad:(S9xJoypad *)joypad;
@end
extern id<S9xInputDelegate> inputDelegate;
@interface S9xEngine : NSObject
@property (nonatomic, weak) id<S9xInputDelegate> inputDelegate;
- (void)start;
- (void)stop;
@ -192,9 +213,22 @@ void CopyPressedKeys(uint8 keys[MAC_MAX_PLAYERS][kNumButtons], uint8 gamepadButt
- (void)resume;
- (BOOL)setButton:(S9xButtonCode)button forKey:(int16)key player:(int8)player oldButton:(S9xButtonCode *)oldButton oldPlayer:(int8 *)oldPlayer oldKey:(int16 *)oldKey;
- (void)clearButton:(S9xButtonCode)button forPlayer:(int8)player;
- (NSArray<S9xJoypad *> *)listJoypads;
- (void)setPlayer:(int8)player forVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index oldPlayer:(int8 *)oldPlayer;
- (BOOL)setButton:(S9xButtonCode)button forVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index cookie:(uint32)cookie value:(int32)value oldButton:(S9xButtonCode *)oldButton;
- (void)clearJoypadForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index;
- (void)clearJoypadForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index buttonCode:(S9xButtonCode)buttonCode;
- (NSArray<S9xJoypadInput *> *)getInputsForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index;
- (NSString *)labelForVendorID:(uint32)vendorID productID:(uint32)productID cookie:(uint32)cookie value:(int32)value;
- (BOOL)loadROM:(NSURL *)fileURL;
- (void)setVideoMode:(int)videoMode;
- (void)setShowFPS:(BOOL)showFPS;
@end
#endif

View File

@ -174,6 +174,8 @@ CFStringRef multiCartPath[2];
IconRef macIconRef[118];
#endif
id<S9xInputDelegate> inputDelegate = nil;
typedef enum
{
ToggleBG0,
@ -2076,7 +2078,7 @@ static void ProcessInput (void)
bool8 keys[MAC_MAX_PLAYERS][kNumButtons];
bool8 gamepadButtons[MAC_MAX_PLAYERS][kNumButtons];
bool8 isok, fnbtn, altbtn, tcbtn;
static bool8 toggleff = false, lastTimeTT = false, lastTimeFn = false, ffUp = false, ffDown = false, ffUpSp = false, ffDownSp = false;
static bool8 toggleff = false, lastTimeTT = false, lastTimeFn = false, ffUp = false, ffDown = false;
if (rejectinput)
return;
@ -3018,6 +3020,101 @@ void QuitWithFatalError ( NSString *message)
pauseEmulation = false;
}
- (NSArray<S9xJoypad *> *)listJoypads
{
os_unfair_lock_lock(&keyLock);
NSMutableArray<S9xJoypad *> *joypads = [NSMutableArray new];
for (auto joypadStruct : ListJoypads())
{
S9xJoypad *joypad = [S9xJoypad new];
joypad.vendorID = joypadStruct.vendorID;
joypad.productID = joypadStruct.productID;
joypad.index = joypadStruct.index;
joypad.name = [[NSString alloc] initWithUTF8String:NameForDevice(joypadStruct).c_str()];
[joypads addObject:joypad];
}
[joypads sortUsingComparator:^NSComparisonResult(S9xJoypad *a, S9xJoypad *b)
{
NSComparisonResult result = [a.name compare:b.name];
if ( result == NSOrderedSame )
{
result = [@(a.vendorID) compare:@(b.vendorID)];
}
if ( result == NSOrderedSame )
{
result = [@(a.productID) compare:@(b.productID)];
}
if ( result == NSOrderedSame )
{
result = [@(a.index) compare:@(b.index)];
}
return result;
}];
os_unfair_lock_unlock(&keyLock);
return joypads;
}
- (void)setPlayer:(int8)player forVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index oldPlayer:(int8 *)oldPlayer
{
os_unfair_lock_lock(&keyLock);
SetPlayerForJoypad(player, vendorID, productID, index, oldPlayer);
os_unfair_lock_unlock(&keyLock);
}
- (BOOL)setButton:(S9xButtonCode)button forVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index cookie:(uint32)cookie value:(int32)value oldButton:(S9xButtonCode *)oldButton
{
BOOL result = NO;
os_unfair_lock_lock(&keyLock);
result = SetButtonCodeForJoypadControl(vendorID, productID, index, cookie, value, button, true, oldButton);
os_unfair_lock_unlock(&keyLock);
return result;
}
- (void)clearJoypadForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index
{
os_unfair_lock_lock(&keyLock);
ClearJoypad(vendorID, productID, index);
os_unfair_lock_unlock(&keyLock);
}
- (void)clearJoypadForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index buttonCode:(S9xButtonCode)buttonCode
{
os_unfair_lock_lock(&keyLock);
ClearButtonCodeForJoypad(vendorID, productID, index, buttonCode);
os_unfair_lock_unlock(&keyLock);
}
- (NSArray<S9xJoypadInput *> *)getInputsForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index
{
os_unfair_lock_lock(&keyLock);
NSMutableArray<S9xJoypadInput *> *inputs = [NSMutableArray new];
std::unordered_map<struct JoypadInput, S9xButtonCode> buttonCodeMap = GetJuypadButtons(vendorID, productID, index);
for (auto it = buttonCodeMap.begin(); it != buttonCodeMap.end(); ++it)
{
S9xJoypadInput *input = [S9xJoypadInput new];
input.cookie = it->first.cookie.cookie;
input.value = it->first.value;
input.buttonCode = it->second;
[inputs addObject:input];
}
os_unfair_lock_unlock(&keyLock);
return inputs;
}
- (NSString *)labelForVendorID:(uint32)vendorID productID:(uint32)productID cookie:(uint32)cookie value:(int32)value
{
return [NSString stringWithUTF8String:LabelForInput(vendorID, productID, cookie, value).c_str()];
}
- (BOOL)setButton:(S9xButtonCode)button forKey:(int16)key player:(int8)player oldButton:(S9xButtonCode *)oldButton oldPlayer:(int8 *)oldPlayer oldKey:(int16 *)oldKey
{
BOOL result = NO;
@ -3027,6 +3124,13 @@ void QuitWithFatalError ( NSString *message)
return result;
}
- (void)clearButton:(S9xButtonCode)button forPlayer:(int8)player
{
os_unfair_lock_lock(&keyLock);
ClearKeyCode(button, player);
os_unfair_lock_unlock(&keyLock);
}
- (BOOL)loadROM:(NSURL *)fileURL
{
if ( SNES9X_OpenCart(fileURL) )
@ -3041,4 +3145,45 @@ void QuitWithFatalError ( NSString *message)
return NO;
}
- (void)setShowFPS:(BOOL)showFPS
{
Settings.DisplayFrameRate = showFPS;
}
- (void)setVideoMode:(int)mode
{
os_unfair_lock_lock(&renderLock);
videoMode = mode;
os_unfair_lock_unlock(&renderLock);
}
@dynamic inputDelegate;
- (void)setInputDelegate:(id<S9xInputDelegate>)delegate
{
inputDelegate = delegate;
}
- (id<S9xInputDelegate>)inputDelegate
{
return inputDelegate;
}
@end
@implementation S9xJoypad
- (BOOL)isEqual:(id)object
{
if (![object isKindOfClass:[self class]])
{
return NO;
}
S9xJoypad *other = (S9xJoypad *)object;
return (self.vendorID == other.vendorID && self.productID == other.productID && self.index == other.index);
}
@end
@implementation S9xJoypadInput
@end