From b9c3cfb7f2393f1b28377134e6d5dc59cb20e87c Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Mon, 30 Mar 2020 23:04:16 -0500 Subject: [PATCH 1/5] macOS: Added missing check for FEAT_HAS_SERIAL_TTY --- reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm b/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm index cc26d7073c..b86603a740 100644 --- a/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm +++ b/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm @@ -76,7 +76,9 @@ - (void)mainSetup { if (reicast_init(0, NULL) != 0) { [self alertAndTerminateWithMessage:@"Reicast initialization failed"]; } + #if FEAT_HAS_SERIAL_TTY _removePTYSymlink = common_serial_pty_setup(); + #endif [self setupUIThread]; } From 2f1c0ec826974abab6e09337a679081fb62957d9 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Tue, 31 Mar 2020 19:14:47 -0500 Subject: [PATCH 2/5] macOS: Added basic console output window With Mac apps, it's a bit of a pain to view the console output of an app. You have to open the Console application--different from Terminal--and filter by your app name or you have to run the raw binary in Terminal which has other issues like missing images in the UI or missing libraries. Many people don't know about this, so I added a "Show Console" option that allows you to view the redirected stderr/stdout in a window while still printing stderr/stdout normally (so it's viewable in the Console app or if you run the raw binary from Terminal). --- .../emulator-osx/emulator-osx/AppDelegate.h | 11 +- .../emulator-osx/emulator-osx/AppDelegate.mm | 129 ++++++++++++++++-- .../emulator-osx/ConsoleViewController.h | 15 ++ .../emulator-osx/ConsoleViewController.m | 67 +++++++++ .../emulator-osx/emulator-osx/EmuGLView.m | 1 - .../emulator-osx/emulator-osx/MainMenu.h | 16 +++ .../emulator-osx/emulator-osx/MainMenu.m | 118 ++++++++++++++++ .../apple/emulator-osx/emulator-osx/main.m | 81 +---------- .../reicast-osx.xcodeproj/project.pbxproj | 26 ++-- 9 files changed, 362 insertions(+), 102 deletions(-) create mode 100644 reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.h create mode 100644 reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.m create mode 100644 reicast/apple/emulator-osx/emulator-osx/MainMenu.h create mode 100644 reicast/apple/emulator-osx/emulator-osx/MainMenu.m diff --git a/reicast/apple/emulator-osx/emulator-osx/AppDelegate.h b/reicast/apple/emulator-osx/emulator-osx/AppDelegate.h index b9d1838533..08c3224902 100644 --- a/reicast/apple/emulator-osx/emulator-osx/AppDelegate.h +++ b/reicast/apple/emulator-osx/emulator-osx/AppDelegate.h @@ -5,12 +5,21 @@ #import #import "EmuGLView.h" +#import "ConsoleViewController.h" @interface AppDelegate : NSObject -@property (strong) NSWindow *window; +// Main UI display window +@property (strong) NSWindow *mainWindow; @property (strong) EmuGLView *glView; +// Console window +@property (strong) NSWindow *consoleWindow; +@property (strong) ConsoleViewController *consoleViewController; + + (AppDelegate *)sharedInstance; +- (void)showConsoleWindow:(NSMenuItem *)menuItem; +- (void)hideConsoleWindow:(NSMenuItem *)menuItem; + @end diff --git a/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm b/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm index b86603a740..f6cd019090 100644 --- a/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm +++ b/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm @@ -4,6 +4,7 @@ #include "license/bsd" #import "AppDelegate.h" +#import "MainMenu.h" #import "libswirl.h" #import "gui/gui_renderer.h" @@ -23,9 +24,17 @@ static CGSize _glViewSize; static void gl_resize(); +@interface AppDelegate() +@property (strong) NSTextStorage *consoleTextStorage; +@end + @implementation AppDelegate { NSThread *_uiThread; BOOL _removePTYSymlink; + + // Console redirection + dispatch_source_t _stdoutSource; + dispatch_source_t _stderrSource; } #pragma mark - Delegate Methods - @@ -55,20 +64,66 @@ - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sende // TODO: BEN This works while in BIOS or game, but doesn't correctly update the DPI for ImGUI - (void)windowDidChangeBackingProperties:(NSNotification *)notification { // Handle DPI changes dragging between monitors - _backingScaleFactor = _window.screen.backingScaleFactor; + _backingScaleFactor = _mainWindow.screen.backingScaleFactor; [self setupScreenDPI]; gl_resize(); } +- (void)windowWillClose:(NSNotification *)notification { + if (notification.object == _mainWindow) { + // Make sure to close the console window if we're closing Reicast + [_consoleWindow close]; + } else if (notification.object == _consoleWindow) { + _consoleWindow = nil; + _consoleViewController = nil; + + // Change menu item + NSMenuItem *menuItem = [MainMenu toggleConsoleMenuItem]; + menuItem.title = @"Show Console"; + menuItem.action = @selector(showConsoleWindow:); + } +} + - (void)emuGLViewIsResizing:(EmuGLView *)emuGLView { // Smoother rendering while resizing the window _glViewSize = emuGLView.frame.size; gl_resize(); } +#pragma mark - Action Methods - + +- (void)showConsoleWindow:(NSMenuItem *)menuItem { + if (!_consoleWindow) { + // Create the ConsoleViewController + _consoleViewController = [[ConsoleViewController alloc] initWithTextStorage:_consoleTextStorage]; + + // Create the Window + NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + _consoleWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 640, 480) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; + _consoleWindow.delegate = self; + _consoleWindow.contentViewController = _consoleViewController; + _consoleWindow.releasedWhenClosed = NO; + _consoleWindow.title = @"Console Output"; + [_consoleWindow makeKeyAndOrderFront:_consoleWindow]; + [_consoleWindow setFrameOrigin:NSMakePoint(NSMaxX(_mainWindow.frame) + 50, NSMinY(_mainWindow.frame))]; + [_mainWindow makeKeyAndOrderFront:_mainWindow]; + + // Change menu item + menuItem.title = @"Hide Console"; + menuItem.action = @selector(hideConsoleWindow:); + } +} + +- (void)hideConsoleWindow:(NSMenuItem *)menuItem { + if (_consoleWindow) { + [_consoleWindow close]; + } +} + #pragma mark - Setup Methods - - (void)mainSetup { + [self captureConsoleOutput]; [self setupWindow]; [self setupScreenDPI]; [self setupPaths]; @@ -82,6 +137,58 @@ - (void)mainSetup { [self setupUIThread]; } +- (void)captureConsoleOutput { + // Create the console text storage + _consoleTextStorage = [[NSTextStorage alloc] init]; + + // Block to consolidate common code + void (^handleConsoleEvent)(NSPipe*, void*, int) = ^void(NSPipe *pipe, void *buffer, int fdOrig) { + // Read from stderr/out + ssize_t readResult = 0; + do { + errno = 0; + readResult = read(pipe.fileHandleForReading.fileDescriptor, buffer, 4096); + } while (readResult == -1 && errno == EINTR); + + if(readResult > 0) { + // There was output, so write it to the console window's text storage + NSString* string = [[NSString alloc] initWithBytesNoCopy:buffer length:readResult encoding:NSUTF8StringEncoding freeWhenDone:NO]; + NSDictionary *attributes = @{NSForegroundColorAttributeName: [NSColor textColor]}; + NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:string attributes:attributes]; + dispatch_sync(dispatch_get_main_queue(),^{ + [self.consoleTextStorage appendAttributedString:attributedString]; + }); + + // Write to normal console output as well + write(fdOrig, buffer, readResult); + } + }; + + // Read buffers + static char stderrBuffer[4096]; + static char stdoutBuffer[4096]; + + // Duplicate original file descriptors to allow writing to the standard console + int stderrOrig = dup(fileno(stderr)); + int stdoutOrig = dup(fileno(stdout)); + + // Create the pipes and divert the standard outputs + NSPipe *stderrPipe = [NSPipe pipe]; + NSPipe *stdoutPipe = [NSPipe pipe]; + dup2(stderrPipe.fileHandleForWriting.fileDescriptor, fileno(stderr)); + dup2(stdoutPipe.fileHandleForWriting.fileDescriptor, fileno(stdout)); + + // Set up dispatch event handlers to listen for output + _stderrSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, stderrPipe.fileHandleForReading.fileDescriptor, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); + _stdoutSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, stdoutPipe.fileHandleForReading.fileDescriptor, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); + dispatch_source_set_event_handler(_stderrSource, ^{ handleConsoleEvent(stderrPipe, &stderrBuffer, stderrOrig); }); + dispatch_source_set_event_handler(_stdoutSource, ^{ handleConsoleEvent(stdoutPipe, &stdoutBuffer, stdoutOrig); }); + + // Start listening + dispatch_resume(_stderrSource); + dispatch_resume(_stdoutSource); +} + - (void)setupWindow { // Create the OpenGL view and context // TODO: BEN See why background color is red when resizing window tall (it's likely due to default GL framebuffer color) @@ -90,21 +197,21 @@ - (void)setupWindow { // Create the Window NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; - _window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 640, 480) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - _window.delegate = self; - _window.contentView = _glView; - _window.acceptsMouseMovedEvents = YES; - [_window makeKeyAndOrderFront:_window]; - [_window center]; + _mainWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 640, 480) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; + _mainWindow.delegate = self; + _mainWindow.contentView = _glView; + _mainWindow.acceptsMouseMovedEvents = YES; + [_mainWindow makeKeyAndOrderFront:_mainWindow]; + [_mainWindow center]; // Set initial scale factor and view size - _backingScaleFactor = _window.screen.backingScaleFactor; + _backingScaleFactor = _mainWindow.screen.backingScaleFactor; _glViewSize = _glView.bounds.size; } - (void)setupScreenDPI { // Calculate screen DPI - NSScreen *screen = _window.screen; + NSScreen *screen = _mainWindow.screen; NSDictionary *description = [screen deviceDescription]; NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; // Must multiply by the scale factor to account for retina displays @@ -252,7 +359,7 @@ static void gl_resize() { } void* libPvr_GetRenderTarget() { - return (__bridge void*)_sharedInstance.window; + return (__bridge void*)_sharedInstance.mainWindow; } void* libPvr_GetRenderSurface() { @@ -270,7 +377,7 @@ int push_vmu_screen(u8* buffer) { int darw_printf(const wchar* text,...) { va_list args; - wchar temp[2048]; + static wchar temp[2048]; va_start(args, text); vsprintf(temp, text, args); va_end(args); diff --git a/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.h b/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.h new file mode 100644 index 0000000000..c7090976ae --- /dev/null +++ b/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.h @@ -0,0 +1,15 @@ +// +// ConsoleViewController.h +// reicast-osx +// +// Created by Benjamin Baron on 3/31/20. +// Copyright © 2020 reicast. All rights reserved. +// + +#import + +@interface ConsoleViewController : NSViewController + +- (instancetype)initWithTextStorage:(NSTextStorage *)textStorage; + +@end diff --git a/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.m b/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.m new file mode 100644 index 0000000000..05e6e55ccb --- /dev/null +++ b/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.m @@ -0,0 +1,67 @@ +// +// ConsoleViewController.m +// reicast-osx +// +// Created by Benjamin Baron on 3/31/20. +// Copyright © 2020 reicast. All rights reserved. +// + +#import "ConsoleViewController.h" + +@implementation ConsoleViewController { + NSTextStorage *_textStorage; + NSTextContainer *_textContainer; + NSLayoutManager *_layoutManager; + NSTextView *_textView; + NSScrollView *_scrollView; +} + +- (instancetype)initWithTextStorage:(NSTextStorage *)textStorage { + self = [super initWithNibName:nil bundle:nil]; + _textStorage = textStorage; + _textStorage.delegate = self; + _textContainer = [[NSTextContainer alloc] init]; + _layoutManager = [[NSLayoutManager alloc] init]; + [_textStorage addLayoutManager:_layoutManager]; + [_layoutManager addTextContainer:_textContainer]; + return self; +} + +- (void)loadView { + self.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 640, 480)]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + _textContainer.containerSize = NSMakeSize(FLT_MAX, FLT_MAX); + _textContainer.widthTracksTextView = NO; + + _textView = [[NSTextView alloc] initWithFrame:NSZeroRect textContainer:_textContainer]; + _textView.minSize = NSZeroSize; + _textView.maxSize = NSMakeSize(FLT_MAX, FLT_MAX); + _textView.verticallyResizable = YES; + _textView.horizontallyResizable = YES; + _textView.frame = (NSRect){.origin = NSZeroPoint, .size = _scrollView.contentSize}; + _textView.editable = NO; + _textView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + _textView.textContainerInset = NSMakeSize(3, 5); + + _scrollView = [[NSScrollView alloc] initWithFrame:self.view.frame]; + _scrollView.borderType = NSNoBorder; + _scrollView.hasVerticalScroller = YES; + _scrollView.hasHorizontalScroller = YES; + _scrollView.documentView = _textView; + _scrollView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [self.view addSubview:_scrollView]; +} + +- (void)dealloc { + [_textStorage removeLayoutManager:_layoutManager]; + _textStorage.delegate = nil; +} + +- (void)textStorage:(NSTextStorage *)textStorage didProcessEditing:(NSTextStorageEditActions)editedMask range:(NSRange)editedRange changeInLength:(NSInteger)delta { + [_textView scrollToEndOfDocument:nil]; +} + +@end diff --git a/reicast/apple/emulator-osx/emulator-osx/EmuGLView.m b/reicast/apple/emulator-osx/emulator-osx/EmuGLView.m index 9fb64b55bb..a080726a60 100644 --- a/reicast/apple/emulator-osx/emulator-osx/EmuGLView.m +++ b/reicast/apple/emulator-osx/emulator-osx/EmuGLView.m @@ -14,7 +14,6 @@ @implementation EmuGLView { - (instancetype)initWithFrame:(NSRect)frameRect { self = [super initWithFrame:frameRect]; - NSLog(@"initWithFrame"); NSOpenGLPixelFormatAttribute attrs[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, 24, diff --git a/reicast/apple/emulator-osx/emulator-osx/MainMenu.h b/reicast/apple/emulator-osx/emulator-osx/MainMenu.h new file mode 100644 index 0000000000..62752fca9f --- /dev/null +++ b/reicast/apple/emulator-osx/emulator-osx/MainMenu.h @@ -0,0 +1,16 @@ +// +// MainMenu.h +// reicast-osx +// +// Created by Benjamin Baron on 3/31/20. +// Copyright © 2020 reicast. All rights reserved. +// + +#import + +@interface MainMenu : NSObject + ++ (void)create; ++ (NSMenuItem *)toggleConsoleMenuItem; + +@end diff --git a/reicast/apple/emulator-osx/emulator-osx/MainMenu.m b/reicast/apple/emulator-osx/emulator-osx/MainMenu.m new file mode 100644 index 0000000000..40218cf84c --- /dev/null +++ b/reicast/apple/emulator-osx/emulator-osx/MainMenu.m @@ -0,0 +1,118 @@ +// +// MainMenu.m +// reicast-osx +// +// Created by Benjamin Baron on 3/31/20. +// Copyright © 2020 reicast. All rights reserved. +// + +#import "MainMenu.h" +#import "AppDelegate.h" + +static NSMenuItem *_toggleConsoleMenuItem; + +@implementation MainMenu + ++ (void)create { + NSApplication *app = [NSApplication sharedApplication]; + + // Create Main Menu without a XIB file + NSString *appName = [[NSProcessInfo processInfo] processName]; + NSMenu *menuBar = [[NSMenu alloc] init]; + [app setMainMenu:menuBar]; + + // App Menu + // + NSMenuItem *appMenuItem = [[NSMenuItem alloc] init]; + [menuBar addItem:appMenuItem]; + // Submenu + NSMenu *appMenu = [[NSMenu alloc] init]; + [appMenuItem setSubmenu:appMenu]; + // About + NSString *aboutTitle = [NSString stringWithFormat:@"About %@", appName]; + [appMenu addItem:[[NSMenuItem alloc] initWithTitle:aboutTitle action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]]; + [appMenu addItem:[NSMenuItem separatorItem]]; + // Services + NSMenuItem *servicesItem = [[NSMenuItem alloc] initWithTitle:@"Services" action:nil keyEquivalent:@""]; + [servicesItem setSubmenu:[app servicesMenu]]; + [appMenu addItem:servicesItem]; + [appMenu addItem:[NSMenuItem separatorItem]]; + // Hide + NSString *hideTitle = [NSString stringWithFormat:@"Hide %@", appName]; + [appMenu addItem:[[NSMenuItem alloc] initWithTitle:hideTitle action:@selector(hide:) keyEquivalent:@"h"]]; + NSMenuItem *hideOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [hideOthersItem setKeyEquivalentModifierMask:NSEventModifierFlagOption]; + [appMenu addItem:hideOthersItem]; + [appMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]]; + [appMenu addItem:[NSMenuItem separatorItem]]; + // Quit + [appMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]]; + + // File Menu + // + NSMenuItem *fileMenuItem = [[NSMenuItem alloc] init]; + [menuBar addItem:fileMenuItem]; + // Submenu + NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"]; + [fileMenuItem setSubmenu:fileMenu]; + // Open + [fileMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open..." action:@selector(openDocument:) keyEquivalent:@"o"]]; + // Open Recent + // TODO: Re-create Open Recent menu manually + //NSMenuItem *openRecentItem = [[NSMenuItem alloc] initWithTitle:@"Open Recent" action:nil keyEquivalent:@""]; + //[openRecentItem addSubmenu:[app openRecet]] + //[fileMenu addItem:[NSMenuItem separatorItem]]; + // Close + [fileMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"]]; + + // Edit Menu + // + NSMenuItem *editMenuItem = [[NSMenuItem alloc] init]; + [menuBar addItem:editMenuItem]; + // Submenu + NSMenu *editMenu = [[NSMenu alloc] initWithTitle:@"Edit"]; + [editMenuItem setSubmenu:editMenu]; + // Copy + [editMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"]]; + + // Window Menu + // + NSMenuItem *windowMenuItem = [[NSMenuItem alloc] init]; + [menuBar addItem:windowMenuItem]; + // Submenu + NSMenu *windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + [windowMenuItem setSubmenu:windowMenu]; + // Show Console + _toggleConsoleMenuItem = [[NSMenuItem alloc] initWithTitle:@"Show Console" action:@selector(showConsoleWindow:) keyEquivalent:@"C"]; + [_toggleConsoleMenuItem setTarget:[AppDelegate sharedInstance]]; + [windowMenu addItem:_toggleConsoleMenuItem]; + [windowMenu addItem:[NSMenuItem separatorItem]]; + // Minimize + NSMenuItem *minimizeItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [minimizeItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; + [windowMenu addItem:minimizeItem]; + // Zoom + [windowMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""]]; + [fileMenu addItem:[NSMenuItem separatorItem]]; + // Bring All to Front + [windowMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""]]; + + // Help Menu + // + NSMenuItem *helpMenuItem = [[NSMenuItem alloc] init]; + [menuBar addItem:helpMenuItem]; + // Submenu + NSMenu *helpMenu = [[NSMenu alloc] initWithTitle:@"Help"]; + [helpMenuItem setSubmenu:helpMenu]; + // Reicast Help + NSString *reicastHelpTitle = [NSString stringWithFormat:@"%@ Help", appName]; + NSMenuItem *reicastHelpMenuItem = [[NSMenuItem alloc] initWithTitle:reicastHelpTitle action:@selector(showHelp:) keyEquivalent:@"?"]; + [reicastHelpMenuItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; + [helpMenu addItem:reicastHelpMenuItem]; +} + ++ (NSMenuItem *)toggleConsoleMenuItem { + return _toggleConsoleMenuItem; +} + +@end diff --git a/reicast/apple/emulator-osx/emulator-osx/main.m b/reicast/apple/emulator-osx/emulator-osx/main.m index a626e38642..94248d409a 100644 --- a/reicast/apple/emulator-osx/emulator-osx/main.m +++ b/reicast/apple/emulator-osx/emulator-osx/main.m @@ -5,6 +5,7 @@ #import #import "AppDelegate.h" +#import "MainMenu.h" int main(int argc, char *argv[]) { @autoreleasepool { @@ -13,84 +14,8 @@ int main(int argc, char *argv[]) { [app setActivationPolicy:NSApplicationActivationPolicyRegular]; [app setDelegate:(id)[[AppDelegate alloc] init]]; - // Create Main Menu without a XIB file - NSString *appName = [[NSProcessInfo processInfo] processName]; - NSMenu *menuBar = [[NSMenu alloc] init]; - [app setMainMenu:menuBar]; - - // App Menu - // - NSMenuItem *appMenuItem = [[NSMenuItem alloc] init]; - [menuBar addItem:appMenuItem]; - // Submenu - NSMenu *appMenu = [[NSMenu alloc] init]; - [appMenuItem setSubmenu:appMenu]; - // About - NSString *aboutTitle = [NSString stringWithFormat:@"About %@", appName]; - [appMenu addItem:[[NSMenuItem alloc] initWithTitle:aboutTitle action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]]; - [appMenu addItem:[NSMenuItem separatorItem]]; - // Services - NSMenuItem *servicesItem = [[NSMenuItem alloc] initWithTitle:@"Services" action:nil keyEquivalent:@""]; - [servicesItem setSubmenu:[app servicesMenu]]; - [appMenu addItem:servicesItem]; - [appMenu addItem:[NSMenuItem separatorItem]]; - // Hide - NSString *hideTitle = [NSString stringWithFormat:@"Hide %@", appName]; - [appMenu addItem:[[NSMenuItem alloc] initWithTitle:hideTitle action:@selector(hide:) keyEquivalent:@"h"]]; - NSMenuItem *hideOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - [hideOthersItem setKeyEquivalentModifierMask:NSEventModifierFlagOption]; - [appMenu addItem:hideOthersItem]; - [appMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]]; - [appMenu addItem:[NSMenuItem separatorItem]]; - // Quit - [appMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]]; - - // File Menu - // - NSMenuItem *fileMenuItem = [[NSMenuItem alloc] init]; - [menuBar addItem:fileMenuItem]; - // Submenu - NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"]; - [fileMenuItem setSubmenu:fileMenu]; - // Open - [fileMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Open..." action:@selector(openDocument:) keyEquivalent:@"o"]]; - // Open Recent - // TODO: Re-create Open Recent menu manually - //NSMenuItem *openRecentItem = [[NSMenuItem alloc] initWithTitle:@"Open Recent" action:nil keyEquivalent:@""]; - //[openRecentItem addSubmenu:[app openRecet]] - //[fileMenu addItem:[NSMenuItem separatorItem]]; - // Close - [fileMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Close" action:@selector(performClose:) keyEquivalent:@"w"]]; - - // Window Menu - // - NSMenuItem *windowMenuItem = [[NSMenuItem alloc] init]; - [menuBar addItem:windowMenuItem]; - // Submenu - NSMenu *windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; - [windowMenuItem setSubmenu:windowMenu]; - // Minimize - NSMenuItem *minimizeItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; - [minimizeItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; - [windowMenu addItem:minimizeItem]; - // Zoom - [windowMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""]]; - [fileMenu addItem:[NSMenuItem separatorItem]]; - // Bring All to Front - [windowMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""]]; - - // Help Menu - // - NSMenuItem *helpMenuItem = [[NSMenuItem alloc] init]; - [menuBar addItem:helpMenuItem]; - // Submenu - NSMenu *helpMenu = [[NSMenu alloc] initWithTitle:@"Help"]; - [helpMenuItem setSubmenu:helpMenu]; - // Reicast Help - NSString *reicastHelpTitle = [NSString stringWithFormat:@"%@ Help", appName]; - NSMenuItem *reicastHelpMenuItem = [[NSMenuItem alloc] initWithTitle:reicastHelpTitle action:@selector(showHelp:) keyEquivalent:@"?"]; - [reicastHelpMenuItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; - [helpMenu addItem:reicastHelpMenuItem]; + // Create the Main Menu + [MainMenu create]; // Start the Application [app activateIgnoringOtherApps:YES]; diff --git a/reicast/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj b/reicast/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj index e9b8dc98df..59cad8be48 100644 --- a/reicast/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj +++ b/reicast/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj @@ -337,6 +337,8 @@ 5317CF4A24086456008A1850 /* posix_vmem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5312CC8B24081FE500C67C85 /* posix_vmem.cpp */; }; 5317CF4B24086456008A1850 /* context.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5312CC8D24081FE500C67C85 /* context.cpp */; }; 5317CF4C24086456008A1850 /* nixprof.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5312CC9024081FE500C67C85 /* nixprof.cpp */; }; + 5337C6512433CF3D0054BA28 /* ConsoleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5337C6502433CF3D0054BA28 /* ConsoleViewController.m */; }; + 5337C6542433E94B0054BA28 /* MainMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 5337C6532433E94B0054BA28 /* MainMenu.m */; }; 537E069D242699A10011E92A /* dsp_helpers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 537E069C242699A10011E92A /* dsp_helpers.cpp */; }; 5381D6D5240887FD0078E797 /* tacore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5381D65A240887FC0078E797 /* tacore.cpp */; }; 5381D6D8240887FD0078E797 /* xbrz.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5381D663240887FC0078E797 /* xbrz.cpp */; }; @@ -1145,6 +1147,10 @@ 5317CF4524086353008A1850 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 5317CF4724086368008A1850 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; 5317CF4E2408674A008A1850 /* libomp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libomp.dylib; path = ../../../../../../../usr/local/Cellar/libomp/9.0.1/lib/libomp.dylib; sourceTree = ""; }; + 5337C64F2433CF3D0054BA28 /* ConsoleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConsoleViewController.h; sourceTree = ""; }; + 5337C6502433CF3D0054BA28 /* ConsoleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ConsoleViewController.m; sourceTree = ""; }; + 5337C6522433E94B0054BA28 /* MainMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MainMenu.h; sourceTree = ""; }; + 5337C6532433E94B0054BA28 /* MainMenu.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MainMenu.m; sourceTree = ""; }; 537E069C242699A10011E92A /* dsp_helpers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dsp_helpers.cpp; sourceTree = ""; }; 5381D65A240887FC0078E797 /* tacore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tacore.cpp; sourceTree = ""; }; 5381D65B240887FC0078E797 /* tacore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tacore.h; sourceTree = ""; }; @@ -3076,30 +3082,26 @@ 84A388B51B1CDD3E000166C0 /* reicast-osx */ = { isa = PBXGroup; children = ( - 84A388BA1B1CDD3E000166C0 /* Images.xcassets */, + 53836183240EDBE2006AE5B4 /* main.m */, + 5337C6522433E94B0054BA28 /* MainMenu.h */, + 5337C6532433E94B0054BA28 /* MainMenu.m */, 5312C07224081C3800C67C85 /* AppDelegate.h */, 5312C07324081C3800C67C85 /* AppDelegate.mm */, 5312C07124080F0200C67C85 /* EmuGLView.h */, 5312C06F24080EDA00C67C85 /* EmuGLView.m */, + 5337C64F2433CF3D0054BA28 /* ConsoleViewController.h */, + 5337C6502433CF3D0054BA28 /* ConsoleViewController.m */, AE60BD9F22256E2500FA8A5B /* OSXKeyboardDevice.hpp */, AE60BDA02225725800FA8A5B /* OSXGamepadDevice.hpp */, 84B7BF841B72821900F9733F /* input.h */, 84B7BF821B727AD700F9733F /* input.mm */, - 84A388B61B1CDD3E000166C0 /* Supporting Files */, + 84A388BA1B1CDD3E000166C0 /* Images.xcassets */, + 84A388B71B1CDD3E000166C0 /* Info.plist */, ); name = "reicast-osx"; path = "emulator-osx"; sourceTree = ""; }; - 84A388B61B1CDD3E000166C0 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 84A388B71B1CDD3E000166C0 /* Info.plist */, - 53836183240EDBE2006AE5B4 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; 84A388C61B1CDD3F000166C0 /* reicast-osxTests */ = { isa = PBXGroup; children = ( @@ -3288,6 +3290,7 @@ 5312D02624081FE600C67C85 /* lctype.c in Sources */, 5312CF6B24081FE600C67C85 /* sgc_if.cpp in Sources */, 5317CF3E24083020008A1850 /* CustomTexture.cpp in Sources */, + 5337C6542433E94B0054BA28 /* MainMenu.m in Sources */, 5312D04E24081FE700C67C85 /* lpc.c in Sources */, 5312CFA024081FE600C67C85 /* gui_settings_advanced.cpp in Sources */, 5312D0FD2408216000C67C85 /* inftrees.c in Sources */, @@ -3346,6 +3349,7 @@ 5312D0FE2408216000C67C85 /* uncompr.c in Sources */, 5317CF4124083020008A1850 /* glesrend.cpp in Sources */, 5312D07424081FE700C67C85 /* zip_error.c in Sources */, + 5337C6512433CF3D0054BA28 /* ConsoleViewController.m in Sources */, 5381D6FA240887FD0078E797 /* pico_dns_client.c in Sources */, 5312D0FB2408216000C67C85 /* infback.c in Sources */, 5312D0A024081FE700C67C85 /* 7zFile.c in Sources */, From afaa74153e221dc4b83f4663802bfc0babc189b2 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Tue, 31 Mar 2020 20:59:06 -0500 Subject: [PATCH 3/5] macOS: Added regex filtering to console output --- .../emulator-osx/emulator-osx/AppDelegate.mm | 2 +- .../emulator-osx/ConsoleViewController.h | 4 +- .../emulator-osx/ConsoleViewController.m | 74 ++++++++++++++++++- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm b/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm index f6cd019090..2cb32868e9 100644 --- a/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm +++ b/reicast/apple/emulator-osx/emulator-osx/AppDelegate.mm @@ -153,7 +153,7 @@ - (void)captureConsoleOutput { if(readResult > 0) { // There was output, so write it to the console window's text storage NSString* string = [[NSString alloc] initWithBytesNoCopy:buffer length:readResult encoding:NSUTF8StringEncoding freeWhenDone:NO]; - NSDictionary *attributes = @{NSForegroundColorAttributeName: [NSColor textColor]}; + NSDictionary *attributes = [ConsoleViewController defaultTextAttributes]; NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:string attributes:attributes]; dispatch_sync(dispatch_get_main_queue(),^{ [self.consoleTextStorage appendAttributedString:attributedString]; diff --git a/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.h b/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.h index c7090976ae..9d778e5da4 100644 --- a/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.h +++ b/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.h @@ -8,7 +8,9 @@ #import -@interface ConsoleViewController : NSViewController +@interface ConsoleViewController : NSViewController + ++ (NSDictionary *)defaultTextAttributes; - (instancetype)initWithTextStorage:(NSTextStorage *)textStorage; diff --git a/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.m b/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.m index 05e6e55ccb..8a96c3f109 100644 --- a/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.m +++ b/reicast/apple/emulator-osx/emulator-osx/ConsoleViewController.m @@ -14,6 +14,14 @@ @implementation ConsoleViewController { NSLayoutManager *_layoutManager; NSTextView *_textView; NSScrollView *_scrollView; + + NSTextField *_filterTextField; + BOOL _shouldFilter; + NSTextStorage *_filteredTextStorage; +} + ++ (NSDictionary *)defaultTextAttributes { + return @{NSForegroundColorAttributeName: [NSColor textColor]}; } - (instancetype)initWithTextStorage:(NSTextStorage *)textStorage { @@ -33,6 +41,18 @@ - (void)loadView { - (void)viewDidLoad { [super viewDidLoad]; + + NSRect frame = self.view.frame; + CGFloat inset = 5; + + CGFloat filterTextHeight = 25; + NSRect filterTextFrame = NSMakeRect(inset, NSHeight(frame) - filterTextHeight - inset, NSWidth(frame) - (inset * 2), filterTextHeight); + _filterTextField = [[NSTextField alloc] initWithFrame:filterTextFrame]; + _filterTextField.delegate = self; + _filterTextField.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin; + _filterTextField.placeholderString = @"Filter (Supports Regex)"; + [self.view addSubview:_filterTextField]; + _textContainer.containerSize = NSMakeSize(FLT_MAX, FLT_MAX); _textContainer.widthTracksTextView = NO; @@ -44,9 +64,10 @@ - (void)viewDidLoad { _textView.frame = (NSRect){.origin = NSZeroPoint, .size = _scrollView.contentSize}; _textView.editable = NO; _textView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - _textView.textContainerInset = NSMakeSize(3, 5); + _textView.textContainerInset = NSMakeSize(inset, inset); - _scrollView = [[NSScrollView alloc] initWithFrame:self.view.frame]; + NSRect scrollViewFrame = NSMakeRect(inset, inset, NSWidth(filterTextFrame), NSHeight(frame) - NSHeight(filterTextFrame) - (inset * 3)); + _scrollView = [[NSScrollView alloc] initWithFrame:scrollViewFrame]; _scrollView.borderType = NSNoBorder; _scrollView.hasVerticalScroller = YES; _scrollView.hasHorizontalScroller = YES; @@ -61,6 +82,55 @@ - (void)dealloc { } - (void)textStorage:(NSTextStorage *)textStorage didProcessEditing:(NSTextStorageEditActions)editedMask range:(NSRange)editedRange changeInLength:(NSInteger)delta { + if (_shouldFilter) { + [self filterConsole:_filterTextField.stringValue]; + } else { + [_textView scrollToEndOfDocument:nil]; + } +} + +- (void)controlTextDidChange:(NSNotification *)notification { + NSString *regexString = _filterTextField.stringValue; + BOOL shouldFilter = regexString.length > 0; + if (shouldFilter && !_shouldFilter) { + // Start filtering + _filteredTextStorage = [[NSTextStorage alloc] init]; + [_textStorage removeLayoutManager:_layoutManager]; + [_filteredTextStorage addLayoutManager:_layoutManager]; + } else if (!shouldFilter && _shouldFilter) { + // Stop filtering + [_filteredTextStorage removeLayoutManager:_layoutManager]; + [_textStorage addLayoutManager:_layoutManager]; + _filteredTextStorage = nil; + } + _shouldFilter = shouldFilter; + + if (_shouldFilter) { + [self filterConsole:regexString]; + } +} + +- (void)filterConsole:(NSString *)regexString { + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + NSFont *boldFont = [fontManager convertFont:_textStorage.font toHaveTrait:NSBoldFontMask]; + NSDictionary *attributes = [self.class defaultTextAttributes]; + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"" attributes:attributes]; + + [_textStorage.string enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) { + NSRange range = [line rangeOfString:regexString options:NSRegularExpressionSearch | NSCaseInsensitiveSearch]; + if (range.location != NSNotFound) { + // Add the line and bold the match + NSMutableAttributedString *attributedLine = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n", line] attributes:attributes]; + + // Highlight the match + [attributedLine addAttribute:NSFontAttributeName value:boldFont range:range]; + [attributedLine addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:range]; + [attributedLine addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:range]; + [attributedString appendAttributedString:attributedLine]; + } + }]; + + [_filteredTextStorage setAttributedString:attributedString]; [_textView scrollToEndOfDocument:nil]; } From 459356786f829b2b2f94e48c1c455a5e7547bca4 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Wed, 1 Apr 2020 16:09:13 -0500 Subject: [PATCH 4/5] macOS: Fixed cmake build with new console window --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78b9b4dd34..e0c8ed5d7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,7 +237,9 @@ elseif(${HOST_OS} EQUAL ${OS_DARWIN}) ${d_osx}/main.m ${d_osx}/input.mm ${d_osx}/AppDelegate.mm + ${d_osx}/ConsoleViewController.m ${d_osx}/EmuGLView.m + ${d_osx}/MainMenu.m ) list(APPEND osd_SRCS ${objc_SRCS} From 183ce7abedd2e2d0854c6c0f2b7433967424a685 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Wed, 1 Apr 2020 18:45:25 -0500 Subject: [PATCH 5/5] Added comment regarding serial fix, fixed indents --- libswirl/hw/sh4/modules/scif.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/libswirl/hw/sh4/modules/scif.cpp b/libswirl/hw/sh4/modules/scif.cpp index 09686bf746..fee1050e8b 100644 --- a/libswirl/hw/sh4/modules/scif.cpp +++ b/libswirl/hw/sh4/modules/scif.cpp @@ -43,14 +43,16 @@ int SerialReadData(u8* buffer, size_t nbytes) { } void SerialWriteData(u8 data) { - if (settings.debug.SerialConsole) { - putc(data, stdout); - } + if (settings.debug.SerialConsole) { + putc(data, stdout); + } #if FEAT_HAS_SERIAL_TTY - if (pty_master != -1) { - while(write(pty_master, &data, 1) != 1 && errno != EAGAIN) - printf("SERIAL: PTY write failed, %s\n", strerror(errno)); - } + if (pty_master != -1) { + while(write(pty_master, &data, 1) != 1 && errno != EAGAIN) { + // Some error other than "resource temporarily unavailable" aka "buffer is full" aka "nothing is consuming it" occurred during write + printf("SERIAL: PTY write failed, %s\n", strerror(errno)); + } + } #endif }