From 8117af03b17c194e5112ed8c83b299b5466368c1 Mon Sep 17 00:00:00 2001 From: Michael Buckley Date: Sun, 10 Nov 2019 13:02:21 -0800 Subject: [PATCH] COnfigurable joypad controls and video options --- macosx/Snes9x/AppDelegate.h | 18 +- macosx/Snes9x/AppDelegate.m | 208 ++++++++++++- macosx/Snes9x/Base.lproj/MainMenu.xib | 12 + macosx/Snes9x/S9xButtonConfigTextField.h | 6 +- macosx/Snes9x/S9xButtonConfigTextField.m | 45 ++- macosx/Snes9x/S9xPrefsConstants.h | 2 + macosx/Snes9x/S9xPrefsConstants.m | 2 + macosx/Snes9x/S9xPrefsViewController.h | 2 +- macosx/Snes9x/S9xPrefsViewController.m | 225 ++++++++++++-- macosx/Snes9x/S9xPrefsViewController.xib | 20 +- macosx/mac-joypad.h | 109 ++++++- macosx/mac-joypad.mm | 377 +++++++++++++++++------ macosx/mac-keyboard.h | 1 + macosx/mac-keyboard.mm | 17 + macosx/mac-os.h | 34 ++ macosx/mac-os.mm | 147 ++++++++- 16 files changed, 1091 insertions(+), 134 deletions(-) diff --git a/macosx/Snes9x/AppDelegate.h b/macosx/Snes9x/AppDelegate.h index 5ccb09d2..3d171eb7 100644 --- a/macosx/Snes9x/AppDelegate.h +++ b/macosx/Snes9x/AppDelegate.h @@ -20,9 +20,25 @@ #import -@interface AppDelegate : NSObject +@interface AppDelegate : NSObject - (void)setButtonCode:(S9xButtonCode)buttonCode forKeyCode:(int16)keyCode player:(int8)player; +- (void)clearButton:(S9xButtonCode)button forPlayer:(int8)player; + +- (NSArray *)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 diff --git a/macosx/Snes9x/AppDelegate.m b/macosx/Snes9x/AppDelegate.m index b2c0c7e8..86274e1d 100644 --- a/macosx/Snes9x/AppDelegate.m +++ b/macosx/Snes9x/AppDelegate.m @@ -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 *)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 *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 *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 diff --git a/macosx/Snes9x/Base.lproj/MainMenu.xib b/macosx/Snes9x/Base.lproj/MainMenu.xib index b9bf5b86..106573e8 100644 --- a/macosx/Snes9x/Base.lproj/MainMenu.xib +++ b/macosx/Snes9x/Base.lproj/MainMenu.xib @@ -95,6 +95,18 @@ + + + + + + + + + + + + diff --git a/macosx/Snes9x/S9xButtonConfigTextField.h b/macosx/Snes9x/S9xButtonConfigTextField.h index 369cedc9..cd868031 100644 --- a/macosx/Snes9x/S9xButtonConfigTextField.h +++ b/macosx/Snes9x/S9xButtonConfigTextField.h @@ -20,11 +20,15 @@ #import +@class S9xJoypadInput; + NS_ASSUME_NONNULL_BEGIN -@interface S9xButtonConfigTextField : NSSearchField +@interface S9xButtonConfigTextField : NSSearchField @property (nonatomic, assign) CGKeyCode keyCode; +@property (nonatomic, strong, nullable) S9xJoypadInput *joypadInput; +@property (nonatomic, assign) BOOL disableKeyboardInput; @end diff --git a/macosx/Snes9x/S9xButtonConfigTextField.m b/macosx/Snes9x/S9xButtonConfigTextField.m index a10bafc4..00eea7ec 100644 --- a/macosx/Snes9x/S9xButtonConfigTextField.m +++ b/macosx/Snes9x/S9xButtonConfigTextField.m @@ -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 diff --git a/macosx/Snes9x/S9xPrefsConstants.h b/macosx/Snes9x/S9xPrefsConstants.h index 9bbcb872..ad6351d0 100644 --- a/macosx/Snes9x/S9xPrefsConstants.h +++ b/macosx/Snes9x/S9xPrefsConstants.h @@ -21,5 +21,7 @@ #import extern NSString * const kKeyboardPrefs; +extern NSString * const kJoypadInputPrefs; +extern NSString * const kJoypadPlayerPrefs; extern NSString * const kShowFPSPref; extern NSString * const kVideoModePref; diff --git a/macosx/Snes9x/S9xPrefsConstants.m b/macosx/Snes9x/S9xPrefsConstants.m index 16c4d0b7..c4228317 100644 --- a/macosx/Snes9x/S9xPrefsConstants.m +++ b/macosx/Snes9x/S9xPrefsConstants.m @@ -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"; diff --git a/macosx/Snes9x/S9xPrefsViewController.h b/macosx/Snes9x/S9xPrefsViewController.h index 9e71a884..bc3b908c 100644 --- a/macosx/Snes9x/S9xPrefsViewController.h +++ b/macosx/Snes9x/S9xPrefsViewController.h @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface S9xPrefsViewController : NSViewController +@interface S9xPrefsViewController : NSViewController @end diff --git a/macosx/Snes9x/S9xPrefsViewController.m b/macosx/Snes9x/S9xPrefsViewController.m index 1cb0d6ce..1900d371 100644 --- a/macosx/Snes9x/S9xPrefsViewController.m +++ b/macosx/Snes9x/S9xPrefsViewController.m @@ -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 *controlsDict = [NSMutableDictionary new]; if (self.devicePopUp.selectedItem.tag < 0) { + NSMutableDictionary *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 *)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 diff --git a/macosx/Snes9x/S9xPrefsViewController.xib b/macosx/Snes9x/S9xPrefsViewController.xib index 81feda5d..60d60bed 100644 --- a/macosx/Snes9x/S9xPrefsViewController.xib +++ b/macosx/Snes9x/S9xPrefsViewController.xib @@ -22,25 +22,28 @@ - - + + - + - + + + + @@ -60,6 +63,9 @@ + + + @@ -211,6 +217,9 @@ + + + @@ -311,6 +320,9 @@ + + + diff --git a/macosx/mac-joypad.h b/macosx/mac-joypad.h index ecd64a76..1f77a39c 100755 --- a/macosx/mac-joypad.h +++ b/macosx/mac-joypad.h @@ -22,12 +22,117 @@ #ifndef _mac_joypad_h_ #define _mac_joypad_h_ +#include +#include +#include + #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 + { + std::size_t operator()(const JoypadDevice& k) const + { + return k.vendorID ^ k.productID ^ k.index; + } + }; + + template <> + struct hash + { + std::size_t operator()(const JoypadCookie& k) const + { + return std::hash()(k.device) ^ k.cookie; + } + }; + + template <> + struct hash + { + std::size_t operator()(const JoypadInput& k) const + { + return std::hash()(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 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 GetJuypadButtons(uint32 vendorID, uint32 productID, uint32 index); + +std::string LabelForInput(uint32 vendorID, uint32 productID, uint32 cookie, int32 value); #endif diff --git a/macosx/mac-joypad.mm b/macosx/mac-joypad.mm index aa98f062..83dcef35 100755 --- a/macosx/mac-joypad.mm +++ b/macosx/mac-joypad.mm @@ -20,8 +20,6 @@ #include -#include -#include #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 - { - std::size_t operator()(const JoypadDevice& k) const - { - return k.vendorID ^ k.productID ^ k.index; - } - }; - - template <> - struct hash - { - std::size_t operator()(const JoypadCookie& k) const - { - return std::hash()(k.device) ^ k.cookie; - } - }; - - template <> - struct hash - { - std::size_t operator()(const JoypadInput& k) const - { - return std::hash()(k.cookie) ^ k.value; - } - }; -} - std::unordered_set allDevices; std::unordered_map>> defaultAxes; std::unordered_map> defaultButtons; std::unordered_map> defaultHatValues; -// TODO: Hook these next two up std::unordered_map playerNumByDevice; std::unordered_map deviceIndexByPort; std::unordered_map infoByCookie; std::unordered_map buttonCodeByJoypadInput; +std::unordered_map namesByDevice; @interface NSData (S9xHexString) +(id)s9x_dataWithHexString:(NSString *)hex; @@ -191,6 +97,20 @@ std::unordered_map buttonCodeByJoypadInput; IOHIDManagerRef hidManager = NULL; +std::unordered_set 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 *buttons = [NSMutableArray new]; NSMutableArray *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 GetJuypadButtons(uint32 vendorID, uint32 productID, uint32 index) +{ + struct JoypadDevice device; + device.vendorID = vendorID; + device.productID = productID; + device.index = index; + + std::unordered_map 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); } diff --git a/macosx/mac-keyboard.h b/macosx/mac-keyboard.h index 891b58bd..23f16c4b 100755 --- a/macosx/mac-keyboard.h +++ b/macosx/mac-keyboard.h @@ -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 diff --git a/macosx/mac-keyboard.mm b/macosx/mac-keyboard.mm index 78e016cd..e6073864 100755 --- a/macosx/mac-keyboard.mm +++ b/macosx/mac-keyboard.mm @@ -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 }; + } + } +} diff --git a/macosx/mac-os.h b/macosx/mac-os.h index 9bdb05b9..15d55fd9 100644 --- a/macosx/mac-os.h +++ b/macosx/mac-os.h @@ -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 +- (BOOL)handleInput:(S9xJoypadInput *)input fromJoypad:(S9xJoypad *)joypad; +@end + +extern id inputDelegate; + @interface S9xEngine : NSObject +@property (nonatomic, weak) id 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 *)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 *)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 diff --git a/macosx/mac-os.mm b/macosx/mac-os.mm index bc753fbd..e814ad2d 100644 --- a/macosx/mac-os.mm +++ b/macosx/mac-os.mm @@ -174,6 +174,8 @@ CFStringRef multiCartPath[2]; IconRef macIconRef[118]; #endif +id 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 *)listJoypads +{ + os_unfair_lock_lock(&keyLock); + NSMutableArray *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 *)getInputsForVendorID:(uint32)vendorID productID:(uint32)productID index:(uint32)index +{ + os_unfair_lock_lock(&keyLock); + NSMutableArray *inputs = [NSMutableArray new]; + std::unordered_map 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)delegate +{ + inputDelegate = delegate; +} + +- (id)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