Local link cable and infrared emulation in the Cocoa port
This commit is contained in:
parent
88198e64f4
commit
60b8978762
@ -1,6 +1,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate>
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate, NSMenuDelegate>
|
||||
|
||||
@property IBOutlet NSWindow *preferencesWindow;
|
||||
@property (strong) IBOutlet NSView *graphicsTab;
|
||||
@ -10,6 +10,7 @@
|
||||
- (IBAction)showPreferences: (id) sender;
|
||||
- (IBAction)toggleDeveloperMode:(id)sender;
|
||||
- (IBAction)switchPreferencesTab:(id)sender;
|
||||
@property (weak) IBOutlet NSMenuItem *linkCableMenuItem;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -81,10 +81,29 @@
|
||||
if ([anItem action] == @selector(toggleDeveloperMode:)) {
|
||||
[(NSMenuItem *)anItem setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]];
|
||||
}
|
||||
|
||||
|
||||
if (anItem == self.linkCableMenuItem) {
|
||||
return [[NSDocumentController sharedDocumentController] documents].count > 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)menuNeedsUpdate:(NSMenu *)menu
|
||||
{
|
||||
NSMutableArray *items = [NSMutableArray array];
|
||||
NSDocument *currentDocument = [[NSDocumentController sharedDocumentController] currentDocument];
|
||||
|
||||
for (NSDocument *document in [[NSDocumentController sharedDocumentController] documents]) {
|
||||
if (document == currentDocument) continue;
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:document.displayName action:@selector(connectLinkCable:) keyEquivalent:@""];
|
||||
item.representedObject = document;
|
||||
item.image = [[NSWorkspace sharedWorkspace] iconForFile:document.fileURL.path];
|
||||
[item.image setSize:NSMakeSize(16, 16)];
|
||||
[items addObject:item];
|
||||
}
|
||||
menu.itemArray = items;
|
||||
}
|
||||
|
||||
- (IBAction) showPreferences: (id) sender
|
||||
{
|
||||
NSArray *objects;
|
||||
@ -109,4 +128,8 @@
|
||||
{
|
||||
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:notification.identifier display:YES];
|
||||
}
|
||||
|
||||
- (IBAction)nop:(id)sender
|
||||
{
|
||||
}
|
||||
@end
|
||||
|
@ -6,6 +6,7 @@
|
||||
@class GBCheatWindowController;
|
||||
|
||||
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
|
||||
@property (readonly) GB_gameboy_t *gb;
|
||||
@property (strong) IBOutlet GBView *view;
|
||||
@property (strong) IBOutlet NSTextView *consoleOutput;
|
||||
@property (strong) IBOutlet NSPanel *consoleWindow;
|
||||
@ -36,10 +37,12 @@
|
||||
@property (strong) IBOutlet NSBox *debuggerVerticalLine;
|
||||
@property (strong) IBOutlet NSPanel *cheatsWindow;
|
||||
@property (strong) IBOutlet GBCheatWindowController *cheatWindowController;
|
||||
@property (readonly) Document *partner;
|
||||
@property (readonly) bool isSlave;
|
||||
|
||||
-(uint8_t) readMemory:(uint16_t) addr;
|
||||
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
|
||||
-(void) performAtomicBlock: (void (^)())block;
|
||||
|
||||
-(void) connectLinkCable:(NSMenuItem *)sender;
|
||||
@end
|
||||
|
||||
|
220
Cocoa/Document.m
220
Cocoa/Document.m
@ -28,6 +28,7 @@ enum model {
|
||||
NSMutableAttributedString *pending_console_output;
|
||||
NSRecursiveLock *console_output_lock;
|
||||
NSTimer *console_output_timer;
|
||||
NSTimer *hex_timer;
|
||||
|
||||
bool fullScreen;
|
||||
bool in_sync_input;
|
||||
@ -47,7 +48,7 @@ enum model {
|
||||
bool oamUpdating;
|
||||
|
||||
NSMutableData *currentPrinterImageData;
|
||||
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy} accessory;
|
||||
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory;
|
||||
|
||||
bool rom_warning_issued;
|
||||
|
||||
@ -66,6 +67,12 @@ enum model {
|
||||
size_t audioBufferNeeded;
|
||||
|
||||
bool borderModeChanged;
|
||||
|
||||
/* Link cable*/
|
||||
Document *master;
|
||||
Document *slave;
|
||||
signed linkOffset;
|
||||
bool linkCableBit;
|
||||
}
|
||||
|
||||
@property GBAudioClient *audioClient;
|
||||
@ -81,6 +88,10 @@ enum model {
|
||||
- (void) gotNewSample:(GB_sample_t *)sample;
|
||||
- (void) rumbleChanged:(double)amp;
|
||||
- (void) loadBootROM:(GB_boot_rom_t)type;
|
||||
- (void)linkCableBitStart:(bool)bit;
|
||||
- (bool)linkCableBitEnd;
|
||||
- (void)infraredStateChanged:(bool)state;
|
||||
|
||||
@end
|
||||
|
||||
static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
|
||||
@ -160,6 +171,26 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
[self rumbleChanged:amp];
|
||||
}
|
||||
|
||||
|
||||
static void linkCableBitStart(GB_gameboy_t *gb, bool bit_to_send)
|
||||
{
|
||||
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
||||
[self linkCableBitStart:bit_to_send];
|
||||
}
|
||||
|
||||
static bool linkCableBitEnd(GB_gameboy_t *gb)
|
||||
{
|
||||
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
||||
return [self linkCableBitEnd];
|
||||
}
|
||||
|
||||
static void infraredStateChanged(GB_gameboy_t *gb, bool on, uint64_t cycles_since_last_update)
|
||||
{
|
||||
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
||||
[self infraredStateChanged:on];
|
||||
}
|
||||
|
||||
|
||||
@implementation Document
|
||||
{
|
||||
GB_gameboy_t gb;
|
||||
@ -264,6 +295,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
|
||||
GB_apu_set_sample_callback(&gb, audioCallback);
|
||||
GB_set_rumble_callback(&gb, rumbleCallback);
|
||||
GB_set_infrared_callback(&gb, infraredStateChanged);
|
||||
[self updateRumbleMode];
|
||||
}
|
||||
|
||||
@ -335,9 +367,8 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
[_view setRumble:amp];
|
||||
}
|
||||
|
||||
- (void) run
|
||||
- (void) preRun
|
||||
{
|
||||
running = true;
|
||||
GB_set_pixels_output(&gb, self.view.pixels);
|
||||
GB_set_sample_rate(&gb, 96000);
|
||||
self.audioClient = [[GBAudioClient alloc] initWithRendererBlock:^(UInt32 sampleRate, UInt32 nFrames, GB_sample_t *buffer) {
|
||||
@ -368,7 +399,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Mute"]) {
|
||||
[self.audioClient start];
|
||||
}
|
||||
NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
|
||||
hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
|
||||
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
|
||||
|
||||
/* Clear pending alarms, don't play alarms while playing */
|
||||
@ -388,19 +419,58 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (running) {
|
||||
if (rewind) {
|
||||
rewind = false;
|
||||
GB_rewind_pop(&gb);
|
||||
if (!GB_rewind_pop(&gb)) {
|
||||
rewind = self.view.isRewinding;
|
||||
}
|
||||
|
||||
static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
||||
{
|
||||
unsigned *ret = malloc(sizeof(*ret) * 0x100);
|
||||
for (unsigned i = 0; i < 0x100; i++) {
|
||||
ret[i] = i * frequency;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void) run
|
||||
{
|
||||
assert(!master);
|
||||
running = true;
|
||||
[self preRun];
|
||||
if (slave) {
|
||||
[slave preRun];
|
||||
unsigned *masterTable = multiplication_table_for_frequency(GB_get_clock_rate(&gb));
|
||||
unsigned *slaveTable = multiplication_table_for_frequency(GB_get_clock_rate(&slave->gb));
|
||||
while (running) {
|
||||
if (linkOffset <= 0) {
|
||||
linkOffset += slaveTable[GB_run(&gb)];
|
||||
}
|
||||
else {
|
||||
linkOffset -= masterTable[GB_run(&slave->gb)];
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_run(&gb);
|
||||
free(masterTable);
|
||||
free(slaveTable);
|
||||
[slave postRun];
|
||||
}
|
||||
else {
|
||||
while (running) {
|
||||
if (rewind) {
|
||||
rewind = false;
|
||||
GB_rewind_pop(&gb);
|
||||
if (!GB_rewind_pop(&gb)) {
|
||||
rewind = self.view.isRewinding;
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_run(&gb);
|
||||
}
|
||||
}
|
||||
}
|
||||
[self postRun];
|
||||
stopping = false;
|
||||
}
|
||||
|
||||
- (void)postRun
|
||||
{
|
||||
[hex_timer invalidate];
|
||||
[audioLock lock];
|
||||
memset(audioBuffer, 0, (audioBufferSize - audioBufferPosition) * sizeof(*audioBuffer));
|
||||
@ -421,7 +491,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([^)]+\\)|\\[[^\\]]+\\]" options:0 error:nil];
|
||||
friendlyName = [regex stringByReplacingMatchesInString:friendlyName options:0 range:NSMakeRange(0, [friendlyName length]) withTemplate:@""];
|
||||
friendlyName = [friendlyName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
|
||||
notification.title = [NSString stringWithFormat:@"%@ Played an Alarm", friendlyName];
|
||||
notification.informativeText = [NSString stringWithFormat:@"%@ requested your attention by playing a scheduled alarm", friendlyName];
|
||||
notification.identifier = self.fileName;
|
||||
@ -431,18 +501,31 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"GBNotificationsUsed"];
|
||||
}
|
||||
[_view setRumble:0];
|
||||
stopping = false;
|
||||
}
|
||||
|
||||
- (void) start
|
||||
{
|
||||
if (running) return;
|
||||
self.view.mouseHidingEnabled = (self.mainWindow.styleMask & NSFullScreenWindowMask) != 0;
|
||||
if (master) {
|
||||
[master start];
|
||||
return;
|
||||
}
|
||||
if (running) return;
|
||||
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
|
||||
}
|
||||
|
||||
- (void) stop
|
||||
{
|
||||
if (master) {
|
||||
if (!master->running) return;
|
||||
GB_debugger_set_disabled(&gb, true);
|
||||
if (GB_debugger_is_stopped(&gb)) {
|
||||
[self interruptDebugInputRead];
|
||||
}
|
||||
[master stop];
|
||||
GB_debugger_set_disabled(&gb, false);
|
||||
return;
|
||||
}
|
||||
if (!running) return;
|
||||
GB_debugger_set_disabled(&gb, true);
|
||||
if (GB_debugger_is_stopped(&gb)) {
|
||||
@ -519,6 +602,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
|
||||
- (IBAction)togglePause:(id)sender
|
||||
{
|
||||
if (master) {
|
||||
[master togglePause:sender];
|
||||
return;
|
||||
}
|
||||
if (running) {
|
||||
[self stop];
|
||||
}
|
||||
@ -749,6 +836,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
|
||||
- (void)close
|
||||
{
|
||||
[self disconnectLinkCable];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.width forKey:@"LastWindowWidth"];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:self.mainWindow.frame.size.height forKey:@"LastWindowHeight"];
|
||||
[self stop];
|
||||
@ -760,9 +848,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
{
|
||||
[self log:"^C\n"];
|
||||
GB_debugger_break(&gb);
|
||||
if (!running) {
|
||||
[self start];
|
||||
}
|
||||
[self start];
|
||||
[self.consoleWindow makeKeyAndOrderFront:nil];
|
||||
[self.consoleInput becomeFirstResponder];
|
||||
}
|
||||
@ -781,10 +867,13 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
|
||||
{
|
||||
if ([anItem action] == @selector(mute:)) {
|
||||
[(NSMenuItem*)anItem setState:!self.audioClient.isPlaying];
|
||||
[(NSMenuItem *)anItem setState:!self.audioClient.isPlaying];
|
||||
}
|
||||
else if ([anItem action] == @selector(togglePause:)) {
|
||||
[(NSMenuItem*)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))];
|
||||
if (master) {
|
||||
[(NSMenuItem *)anItem setState:(!master->running) || (GB_debugger_is_stopped(&gb)) || (GB_debugger_is_stopped(&gb))];
|
||||
}
|
||||
[(NSMenuItem *)anItem setState:(!running) || (GB_debugger_is_stopped(&gb))];
|
||||
return !GB_debugger_is_stopped(&gb);
|
||||
}
|
||||
else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) {
|
||||
@ -804,6 +893,10 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
else if ([anItem action] == @selector(connectWorkboy:)) {
|
||||
[(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy];
|
||||
}
|
||||
else if ([anItem action] == @selector(connectLinkCable:)) {
|
||||
[(NSMenuItem*)anItem setState:[(NSMenuItem *)anItem representedObject] == master ||
|
||||
[(NSMenuItem *)anItem representedObject] == slave];
|
||||
}
|
||||
else if ([anItem action] == @selector(toggleCheats:)) {
|
||||
[(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)];
|
||||
}
|
||||
@ -1090,6 +1183,9 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
{
|
||||
while (!GB_is_inited(&gb));
|
||||
bool was_running = running && !GB_debugger_is_stopped(&gb);
|
||||
if (master) {
|
||||
was_running |= master->running;
|
||||
}
|
||||
if (was_running) {
|
||||
[self stop];
|
||||
}
|
||||
@ -1700,6 +1796,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
|
||||
- (IBAction)disconnectAllAccessories:(id)sender
|
||||
{
|
||||
[self disconnectLinkCable];
|
||||
[self performAtomicBlock:^{
|
||||
accessory = GBAccessoryNone;
|
||||
GB_disconnect_serial(&gb);
|
||||
@ -1708,6 +1805,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
|
||||
- (IBAction)connectPrinter:(id)sender
|
||||
{
|
||||
[self disconnectLinkCable];
|
||||
[self performAtomicBlock:^{
|
||||
accessory = GBAccessoryPrinter;
|
||||
GB_connect_printer(&gb, printImage);
|
||||
@ -1716,6 +1814,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
|
||||
- (IBAction)connectWorkboy:(id)sender
|
||||
{
|
||||
[self disconnectLinkCable];
|
||||
[self performAtomicBlock:^{
|
||||
accessory = GBAccessoryWorkboy;
|
||||
GB_connect_workboy(&gb, setWorkboyTime, getWorkboyTime);
|
||||
@ -1832,4 +1931,83 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
{
|
||||
GB_set_cheats_enabled(&gb, !GB_cheats_enabled(&gb));
|
||||
}
|
||||
|
||||
- (void)disconnectLinkCable
|
||||
{
|
||||
bool wasRunning = self->running;
|
||||
Document *partner = master ?: slave;
|
||||
if (partner) {
|
||||
[self stop];
|
||||
partner->master = nil;
|
||||
partner->slave = nil;
|
||||
master = nil;
|
||||
slave = nil;
|
||||
if (wasRunning) {
|
||||
[partner start];
|
||||
[self start];
|
||||
}
|
||||
GB_set_turbo_mode(&gb, false, false);
|
||||
GB_set_turbo_mode(&partner->gb, false, false);
|
||||
partner->accessory = GBAccessoryNone;
|
||||
accessory = GBAccessoryNone;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connectLinkCable:(NSMenuItem *)sender
|
||||
{
|
||||
[self disconnectAllAccessories:sender];
|
||||
Document *partner = [sender representedObject];
|
||||
[partner disconnectAllAccessories:sender];
|
||||
|
||||
bool wasRunning = self->running;
|
||||
[self stop];
|
||||
[partner stop];
|
||||
GB_set_turbo_mode(&partner->gb, true, true);
|
||||
slave = partner;
|
||||
partner->master = self;
|
||||
linkOffset = 0;
|
||||
partner->accessory = GBAccessoryLinkCable;
|
||||
accessory = GBAccessoryLinkCable;
|
||||
GB_set_serial_transfer_bit_start_callback(&gb, linkCableBitStart);
|
||||
GB_set_serial_transfer_bit_start_callback(&partner->gb, linkCableBitStart);
|
||||
GB_set_serial_transfer_bit_end_callback(&gb, linkCableBitEnd);
|
||||
GB_set_serial_transfer_bit_end_callback(&partner->gb, linkCableBitEnd);
|
||||
if (wasRunning) {
|
||||
[self start];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)linkCableBitStart:(bool)bit
|
||||
{
|
||||
linkCableBit = bit;
|
||||
}
|
||||
|
||||
-(bool)linkCableBitEnd
|
||||
{
|
||||
bool ret = GB_serial_get_data_bit(&self.partner->gb);
|
||||
GB_serial_set_data_bit(&self.partner->gb, linkCableBit);
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)infraredStateChanged:(bool)state
|
||||
{
|
||||
if (self.partner) {
|
||||
GB_set_infrared_input(&self.partner->gb, state);
|
||||
}
|
||||
}
|
||||
|
||||
-(Document *)partner
|
||||
{
|
||||
return slave ?: master;
|
||||
}
|
||||
|
||||
- (bool)isSlave
|
||||
{
|
||||
return master;
|
||||
}
|
||||
|
||||
- (GB_gameboy_t *)gb
|
||||
{
|
||||
return &gb;
|
||||
}
|
||||
@end
|
||||
|
@ -59,6 +59,9 @@
|
||||
<view fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uqf-pe-VAF" customClass="GBView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="160" height="144"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<connections>
|
||||
<outlet property="document" destination="-2" id="Fvh-rD-z4r"/>
|
||||
</connections>
|
||||
</view>
|
||||
</subviews>
|
||||
</customView>
|
||||
@ -115,7 +118,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S">
|
||||
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" id="doS-dM-hnl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="591" height="376"/>
|
||||
@ -152,7 +155,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="YHx-TM-zIC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView ambiguous="YES" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" allowsNonContiguousLayout="YES" textCompletion="NO" spellingCorrection="YES" id="w0g-eK-jM4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="329" height="47"/>
|
||||
@ -186,7 +189,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" copiesOnScroll="NO" id="Cs9-3x-ATg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView ambiguous="YES" editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" baseWritingDirection="leftToRight" findStyle="bar" allowsNonContiguousLayout="YES" spellingCorrection="YES" id="JgV-7E-iwp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="329" height="328"/>
|
||||
@ -975,7 +978,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" id="mzf-yu-RID">
|
||||
<rect key="frame" x="1" y="0.0" width="398" height="274"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" headerView="pvX-uJ-qK5" id="tA3-8T-bxb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="398" height="249"/>
|
||||
|
@ -1,6 +1,7 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
@class Document;
|
||||
|
||||
typedef enum {
|
||||
GB_FRAME_BLENDING_MODE_DISABLED,
|
||||
@ -13,6 +14,7 @@ typedef enum {
|
||||
@interface GBView : NSView<JOYListener>
|
||||
- (void) flip;
|
||||
- (uint32_t *) pixels;
|
||||
@property (weak) IBOutlet Document *document;
|
||||
@property GB_gameboy_t *gb;
|
||||
@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode;
|
||||
@property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled;
|
||||
|
118
Cocoa/GBView.m
118
Cocoa/GBView.m
@ -5,6 +5,7 @@
|
||||
#import "GBViewMetal.h"
|
||||
#import "GBButtons.h"
|
||||
#import "NSString+StringForKey.h"
|
||||
#import "Document.h"
|
||||
|
||||
#define JOYSTICK_HIGH 0x4000
|
||||
#define JOYSTICK_LOW 0x3800
|
||||
@ -257,6 +258,9 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
{
|
||||
if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) {
|
||||
GB_set_clock_multiplier(_gb, analogClockMultiplier);
|
||||
if (self.document.partner) {
|
||||
GB_set_clock_multiplier(self.document.partner.gb, analogClockMultiplier);
|
||||
}
|
||||
if (analogClockMultiplier == 1.0) {
|
||||
analogClockMultiplierValid = false;
|
||||
}
|
||||
@ -265,10 +269,16 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
if (underclockKeyDown && clockMultiplier > 0.5) {
|
||||
clockMultiplier -= 1.0/16;
|
||||
GB_set_clock_multiplier(_gb, clockMultiplier);
|
||||
if (self.document.partner) {
|
||||
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
|
||||
}
|
||||
}
|
||||
if (!underclockKeyDown && clockMultiplier < 1.0) {
|
||||
clockMultiplier += 1.0/16;
|
||||
GB_set_clock_multiplier(_gb, clockMultiplier);
|
||||
if (self.document.partner) {
|
||||
GB_set_clock_multiplier(self.document.partner.gb, clockMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
current_buffer = (current_buffer + 1) % self.numberOfBuffers;
|
||||
@ -299,6 +309,9 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
if (self.document.partner) {
|
||||
player_count = 2;
|
||||
}
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
for (GBButton button = 0; button < GBButtonCount; button++) {
|
||||
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
|
||||
@ -308,13 +321,20 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
handled = true;
|
||||
switch (button) {
|
||||
case GBTurbo:
|
||||
GB_set_turbo_mode(_gb, true, self.isRewinding);
|
||||
if (self.document.isSlave) {
|
||||
GB_set_turbo_mode(self.document.partner.gb, true, false);
|
||||
}
|
||||
else {
|
||||
GB_set_turbo_mode(_gb, true, self.isRewinding);
|
||||
}
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
case GBRewind:
|
||||
self.isRewinding = true;
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
if (!self.document.partner) {
|
||||
self.isRewinding = true;
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case GBUnderclock:
|
||||
@ -323,7 +343,17 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
break;
|
||||
|
||||
default:
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
|
||||
if (self.document.partner) {
|
||||
if (player == 0) {
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, true);
|
||||
}
|
||||
else {
|
||||
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -351,6 +381,9 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
if (self.document.partner) {
|
||||
player_count = 2;
|
||||
}
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
for (GBButton button = 0; button < GBButtonCount; button++) {
|
||||
NSNumber *key = [defaults valueForKey:button_to_preference_name(button, player)];
|
||||
@ -360,7 +393,12 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
handled = true;
|
||||
switch (button) {
|
||||
case GBTurbo:
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
if (self.document.isSlave) {
|
||||
GB_set_turbo_mode(self.document.partner.gb, false, false);
|
||||
}
|
||||
else {
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
}
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
@ -374,7 +412,17 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
break;
|
||||
|
||||
default:
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
|
||||
if (self.document.partner) {
|
||||
if (player == 0) {
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, 0, false);
|
||||
}
|
||||
else {
|
||||
GB_set_key_state_for_player(self.document.partner.gb, (GB_key_t)button, 0, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)button, player, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -415,13 +463,11 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
|
||||
{
|
||||
if (![self.window isMainWindow]) return;
|
||||
if (controller != lastController) {
|
||||
[self setRumble:0];
|
||||
lastController = controller;
|
||||
}
|
||||
|
||||
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
if (self.document.partner) {
|
||||
player_count = 2;
|
||||
}
|
||||
|
||||
IOPMAssertionID assertionID;
|
||||
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
|
||||
@ -447,33 +493,63 @@ static const uint8_t workboy_vk_to_key[] = {
|
||||
usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3];
|
||||
}
|
||||
|
||||
GB_gameboy_t *effectiveGB = _gb;
|
||||
unsigned effectivePlayer = player;
|
||||
|
||||
if (player && self.document.partner) {
|
||||
effectiveGB = self.document.partner.gb;
|
||||
effectivePlayer = 0;
|
||||
if (controller != self.document.partner.view->lastController) {
|
||||
[self setRumble:0];
|
||||
self.document.partner.view->lastController = controller;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (controller != lastController) {
|
||||
[self setRumble:0];
|
||||
lastController = controller;
|
||||
}
|
||||
}
|
||||
|
||||
switch (usage) {
|
||||
|
||||
case JOYButtonUsageNone: break;
|
||||
case JOYButtonUsageA: GB_set_key_state_for_player(_gb, GB_KEY_A, player, button.isPressed); break;
|
||||
case JOYButtonUsageB: GB_set_key_state_for_player(_gb, GB_KEY_B, player, button.isPressed); break;
|
||||
case JOYButtonUsageA: GB_set_key_state_for_player(effectiveGB, GB_KEY_A, effectivePlayer, button.isPressed); break;
|
||||
case JOYButtonUsageB: GB_set_key_state_for_player(effectiveGB, GB_KEY_B, effectivePlayer, button.isPressed); break;
|
||||
case JOYButtonUsageC: break;
|
||||
case JOYButtonUsageStart:
|
||||
case JOYButtonUsageX: GB_set_key_state_for_player(_gb, GB_KEY_START, player, button.isPressed); break;
|
||||
case JOYButtonUsageX: GB_set_key_state_for_player(effectiveGB, GB_KEY_START, effectivePlayer, button.isPressed); break;
|
||||
case JOYButtonUsageSelect:
|
||||
case JOYButtonUsageY: GB_set_key_state_for_player(_gb, GB_KEY_SELECT, player, button.isPressed); break;
|
||||
case JOYButtonUsageY: GB_set_key_state_for_player(effectiveGB, GB_KEY_SELECT, effectivePlayer, button.isPressed); break;
|
||||
case JOYButtonUsageR2:
|
||||
case JOYButtonUsageL2:
|
||||
case JOYButtonUsageZ: {
|
||||
self.isRewinding = button.isPressed;
|
||||
if (button.isPressed) {
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
if (self.document.isSlave) {
|
||||
GB_set_turbo_mode(self.document.partner.gb, false, false);
|
||||
}
|
||||
else {
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JOYButtonUsageL1: GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break;
|
||||
case JOYButtonUsageL1: {
|
||||
if (self.document.isSlave) {
|
||||
GB_set_turbo_mode(self.document.partner.gb, button.isPressed, false); break;
|
||||
}
|
||||
else {
|
||||
GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break;
|
||||
}
|
||||
}
|
||||
|
||||
case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break;
|
||||
case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, button.isPressed); break;
|
||||
case JOYButtonUsageDPadRight: GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, button.isPressed); break;
|
||||
case JOYButtonUsageDPadUp: GB_set_key_state_for_player(_gb, GB_KEY_UP, player, button.isPressed); break;
|
||||
case JOYButtonUsageDPadDown: GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, button.isPressed); break;
|
||||
case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(effectiveGB, GB_KEY_LEFT, effectivePlayer, button.isPressed); break;
|
||||
case JOYButtonUsageDPadRight: GB_set_key_state_for_player(effectiveGB, GB_KEY_RIGHT, effectivePlayer, button.isPressed); break;
|
||||
case JOYButtonUsageDPadUp: GB_set_key_state_for_player(effectiveGB, GB_KEY_UP, effectivePlayer, button.isPressed); break;
|
||||
case JOYButtonUsageDPadDown: GB_set_key_state_for_player(effectiveGB, GB_KEY_DOWN, effectivePlayer, button.isPressed); break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -12,7 +12,11 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
|
||||
<connections>
|
||||
<outlet property="linkCableMenuItem" destination="V4S-Fo-xJK" id="KL9-3K-64i"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
@ -373,6 +377,17 @@
|
||||
<action selector="disconnectAllAccessories:" target="-1" id="5hY-9U-nRn"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Game Link Cable & Infrared" id="V4S-Fo-xJK">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Game Link Cable & Infrared" id="6sJ-Wz-QLj">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PMY-5j-25T"/>
|
||||
</connections>
|
||||
</menu>
|
||||
<connections>
|
||||
<action selector="nop:" target="Voe-Tx-rLC" id="Bpa-0C-lkN"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Game Boy Printer" id="zHR-Ha-pOR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
|
@ -803,9 +803,7 @@ void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callbac
|
||||
void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback);
|
||||
void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback);
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
|
||||
#endif
|
||||
void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier);
|
||||
|
||||
unsigned GB_get_screen_width(GB_gameboy_t *gb);
|
||||
|
Loading…
Reference in New Issue
Block a user