427 lines
13 KiB
Plaintext
427 lines
13 KiB
Plaintext
// Copyright 2023 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DolphinNoGUI/Platform.h"
|
|
|
|
#include "Common/MsgHandler.h"
|
|
#include "Core/Config/MainSettings.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/State.h"
|
|
#include "Core/System.h"
|
|
#include "VideoCommon/Present.h"
|
|
#include "VideoCommon/RenderBase.h"
|
|
|
|
#include <AppKit/AppKit.h>
|
|
#include <Carbon/Carbon.h>
|
|
#include <Foundation/Foundation.h>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <climits>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <thread>
|
|
|
|
@interface Application : NSApplication
|
|
@property Platform* platform;
|
|
- (void)shutdown;
|
|
- (void)togglePause;
|
|
- (void)saveScreenShot;
|
|
- (void)loadLastSaved;
|
|
- (void)undoLoadState;
|
|
- (void)undoSaveState;
|
|
- (void)loadState:(id)sender;
|
|
- (void)saveState:(id)sender;
|
|
@end
|
|
|
|
@implementation Application
|
|
- (void)shutdown;
|
|
{
|
|
[self platform]->RequestShutdown();
|
|
[self stop:nil];
|
|
}
|
|
|
|
- (void)togglePause
|
|
{
|
|
if (Core::GetState() == Core::State::Running)
|
|
Core::SetState(Core::State::Paused);
|
|
else
|
|
Core::SetState(Core::State::Running);
|
|
}
|
|
|
|
- (void)saveScreenShot
|
|
{
|
|
Core::SaveScreenShot();
|
|
}
|
|
|
|
- (void)loadLastSaved
|
|
{
|
|
State::LoadLastSaved(Core::System::GetInstance());
|
|
}
|
|
|
|
- (void)undoLoadState
|
|
{
|
|
State::UndoLoadState(Core::System::GetInstance());
|
|
}
|
|
|
|
- (void)undoSaveState
|
|
{
|
|
State::UndoSaveState(Core::System::GetInstance());
|
|
}
|
|
|
|
- (void)loadState:(id)sender
|
|
{
|
|
State::Load(Core::System::GetInstance(), [sender tag]);
|
|
}
|
|
|
|
- (void)saveState:(id)sender
|
|
{
|
|
State::Save(Core::System::GetInstance(), [sender tag]);
|
|
}
|
|
@end
|
|
|
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
|
|
|
@property(readonly) Platform* platform;
|
|
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender;
|
|
- (id)initWithPlatform:(Platform*)platform;
|
|
@end
|
|
|
|
@implementation AppDelegate
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (id)initWithPlatform:(Platform*)platform
|
|
{
|
|
self = [super init];
|
|
if (self)
|
|
{
|
|
_platform = platform;
|
|
}
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
@interface WindowDelegate : NSObject <NSWindowDelegate>
|
|
|
|
- (void)windowDidResize:(NSNotification*)notification;
|
|
@end
|
|
|
|
@implementation WindowDelegate
|
|
|
|
- (void)windowDidResize:(NSNotification*)notification
|
|
{
|
|
if (g_presenter)
|
|
g_presenter->ResizeSurface();
|
|
}
|
|
@end
|
|
|
|
namespace
|
|
{
|
|
class PlatformMacOS : public Platform
|
|
{
|
|
public:
|
|
~PlatformMacOS() override;
|
|
|
|
bool Init() override;
|
|
void SetTitle(const std::string& title) override;
|
|
void MainLoop() override;
|
|
|
|
WindowSystemInfo GetWindowSystemInfo() const override;
|
|
|
|
private:
|
|
void ProcessEvents();
|
|
void UpdateWindowPosition();
|
|
void HandleSaveStates(NSUInteger key, NSUInteger flags);
|
|
void SetupMenu();
|
|
|
|
NSRect m_window_rect;
|
|
NSWindow* m_window;
|
|
NSMenu* menuBar;
|
|
AppDelegate* m_app_delegate;
|
|
WindowDelegate* m_window_delegate;
|
|
|
|
int m_window_x = Config::Get(Config::MAIN_RENDER_WINDOW_XPOS);
|
|
int m_window_y = Config::Get(Config::MAIN_RENDER_WINDOW_YPOS);
|
|
unsigned int m_window_width = Config::Get(Config::MAIN_RENDER_WINDOW_WIDTH);
|
|
unsigned int m_window_height = Config::Get(Config::MAIN_RENDER_WINDOW_HEIGHT);
|
|
bool m_window_fullscreen = Config::Get(Config::MAIN_FULLSCREEN);
|
|
};
|
|
|
|
PlatformMacOS::~PlatformMacOS()
|
|
{
|
|
[m_window close];
|
|
}
|
|
|
|
bool PlatformMacOS::Init()
|
|
{
|
|
[Application sharedApplication];
|
|
|
|
m_app_delegate = [[AppDelegate alloc] initWithPlatform:this];
|
|
[NSApp setDelegate:m_app_delegate];
|
|
|
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
[NSApp setPlatform:this];
|
|
[Application.sharedApplication finishLaunching];
|
|
|
|
unsigned long styleMask =
|
|
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
|
|
|
|
m_window_rect = CGRectMake(m_window_x, m_window_y, m_window_width, m_window_height);
|
|
m_window = [NSWindow alloc];
|
|
m_window = [m_window initWithContentRect:m_window_rect
|
|
styleMask:styleMask
|
|
backing:NSBackingStoreBuffered
|
|
defer:NO];
|
|
m_window_delegate = [[WindowDelegate alloc] init];
|
|
[m_window setDelegate:m_window_delegate];
|
|
|
|
NSNotificationCenter* c = [NSNotificationCenter defaultCenter];
|
|
[c addObserver:NSApp
|
|
selector:@selector(shutdown)
|
|
name:NSWindowWillCloseNotification
|
|
object:m_window];
|
|
|
|
if (m_window == nil)
|
|
{
|
|
NSLog(@"Window is %@\n", m_window);
|
|
return false;
|
|
}
|
|
|
|
if (Config::Get(Config::MAIN_SHOW_CURSOR) == Config::ShowCursor::Never)
|
|
[NSCursor hide];
|
|
|
|
if (Config::Get(Config::MAIN_FULLSCREEN))
|
|
{
|
|
m_window_fullscreen = true;
|
|
[m_window toggleFullScreen:m_window];
|
|
}
|
|
|
|
[m_window makeKeyAndOrderFront:NSApp];
|
|
[m_window makeMainWindow];
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
[m_window setTitle:@"Dolphin-emu-nogui"];
|
|
|
|
SetupMenu();
|
|
|
|
return true;
|
|
}
|
|
|
|
void PlatformMacOS::SetTitle(const std::string& title)
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
NSWindow* window = m_window;
|
|
NSString* str = [NSString stringWithUTF8String:title.c_str()];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[window setTitle:str];
|
|
});
|
|
}
|
|
}
|
|
|
|
void PlatformMacOS::MainLoop()
|
|
{
|
|
while (IsRunning())
|
|
{
|
|
UpdateRunningFlag();
|
|
Core::HostDispatchJobs(Core::System::GetInstance());
|
|
ProcessEvents();
|
|
UpdateWindowPosition();
|
|
}
|
|
}
|
|
|
|
WindowSystemInfo PlatformMacOS::GetWindowSystemInfo() const
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
WindowSystemInfo wsi;
|
|
wsi.type = WindowSystemType::MacOS;
|
|
wsi.render_window = (void*)CFBridgingRetain([m_window contentView]);
|
|
wsi.render_surface = wsi.render_window;
|
|
return wsi;
|
|
}
|
|
}
|
|
|
|
void PlatformMacOS::ProcessEvents()
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
NSDate* expiration = [NSDate dateWithTimeIntervalSinceNow:1];
|
|
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
|
untilDate:expiration
|
|
inMode:NSDefaultRunLoopMode
|
|
dequeue:YES];
|
|
|
|
[NSApp sendEvent:event];
|
|
|
|
// Need to update if m_window becomes fullscreen
|
|
m_window_fullscreen = [m_window styleMask] & NSWindowStyleMaskFullScreen;
|
|
|
|
if ([m_window isMainWindow])
|
|
{
|
|
m_window_focus = true;
|
|
if (Config::Get(Config::MAIN_SHOW_CURSOR) == Config::ShowCursor::Never &&
|
|
Core::GetState() != Core::State::Paused)
|
|
[NSCursor unhide];
|
|
}
|
|
else
|
|
{
|
|
m_window_focus = false;
|
|
if (Config::Get(Config::MAIN_SHOW_CURSOR) == Config::ShowCursor::Never)
|
|
[NSCursor hide];
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlatformMacOS::UpdateWindowPosition()
|
|
{
|
|
if (m_window_fullscreen)
|
|
return;
|
|
|
|
NSRect win = [m_window frame];
|
|
m_window_x = win.origin.x;
|
|
m_window_y = win.origin.y;
|
|
m_window_width = win.size.width;
|
|
m_window_height = win.size.height;
|
|
}
|
|
|
|
void PlatformMacOS::SetupMenu()
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
menuBar = [NSMenu new];
|
|
|
|
NSMenu* appMenu = [NSMenu new];
|
|
NSMenu* stateMenu = [[NSMenu alloc] initWithTitle:@"States"];
|
|
NSMenu* loadStateMenu = [[NSMenu alloc] initWithTitle:@"Load"];
|
|
NSMenu* saveStateMenu = [[NSMenu alloc] initWithTitle:@"Save"];
|
|
NSMenu* miscMenu = [[NSMenu alloc] initWithTitle:@"Misc"];
|
|
|
|
NSMenuItem* appMenuItem = [NSMenuItem new];
|
|
NSMenuItem* miscMenuItem = [NSMenuItem new];
|
|
NSMenuItem* stateMenuItem = [NSMenuItem new];
|
|
NSMenuItem* loadStateItem = [[NSMenuItem alloc] initWithTitle:@"Load"
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
NSMenuItem* saveStateItem = [[NSMenuItem alloc] initWithTitle:@"Save"
|
|
action:nil
|
|
keyEquivalent:@""];
|
|
[menuBar addItem:appMenuItem];
|
|
[menuBar addItem:stateMenuItem];
|
|
[menuBar addItem:miscMenuItem];
|
|
|
|
// Quit
|
|
NSString* quitTitle = [@"Quit " stringByAppendingString:@"dolphin-emu-nogui"];
|
|
NSMenuItem* quitMenuItem = [[NSMenuItem alloc] initWithTitle:quitTitle
|
|
action:@selector(shutdown)
|
|
keyEquivalent:@"q"];
|
|
|
|
// Fullscreen
|
|
NSString* fullScreenItemTitle = @"Toggle Fullscreen";
|
|
NSMenuItem* fullScreenItem = [[NSMenuItem alloc] initWithTitle:fullScreenItemTitle
|
|
action:@selector(toggleFullScreen:)
|
|
keyEquivalent:@"f"];
|
|
[fullScreenItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
|
|
|
|
// Screenshot
|
|
NSString* ScreenShotTitle = @"Take Screenshot";
|
|
unichar c = NSF9FunctionKey;
|
|
NSString* f9 = [NSString stringWithCharacters:&c length:1];
|
|
NSMenuItem* ScreenShotItem = [[NSMenuItem alloc] initWithTitle:ScreenShotTitle
|
|
action:@selector(saveScreenShot)
|
|
keyEquivalent:f9];
|
|
[ScreenShotItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
|
|
|
|
// Pause game
|
|
NSString* pauseTitle = @"Toggle pause";
|
|
c = NSF10FunctionKey;
|
|
NSString* f10 = [NSString stringWithCharacters:&c length:1];
|
|
NSMenuItem* pauseItem = [[NSMenuItem alloc] initWithTitle:pauseTitle
|
|
action:@selector(togglePause)
|
|
keyEquivalent:f10];
|
|
[pauseItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
|
|
|
|
// Load last save
|
|
NSString* loadLastTitle = @"Load Last Saved";
|
|
c = NSF11FunctionKey;
|
|
NSString* f11 = [NSString stringWithCharacters:&c length:1];
|
|
NSMenuItem* loadLastItem = [[NSMenuItem alloc] initWithTitle:loadLastTitle
|
|
action:@selector(loadLastSaved)
|
|
keyEquivalent:f11];
|
|
[loadLastItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
|
|
|
|
// Undo Load State
|
|
NSString* undoLoadTitle = @"Undo Load";
|
|
c = NSF12FunctionKey;
|
|
NSString* f12 = [NSString stringWithCharacters:&c length:1];
|
|
NSMenuItem* undoLoadItem = [[NSMenuItem alloc] initWithTitle:undoLoadTitle
|
|
action:@selector(undoLoadState)
|
|
keyEquivalent:f12];
|
|
[undoLoadItem setKeyEquivalentModifierMask:NSEventModifierFlagShift];
|
|
|
|
// Undo Save State
|
|
NSString* undoSaveTitle = @"Undo Save";
|
|
NSMenuItem* undoSaveItem = [[NSMenuItem alloc] initWithTitle:undoSaveTitle
|
|
action:@selector(undoSaveState)
|
|
keyEquivalent:f12];
|
|
[undoSaveItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
|
|
|
|
// Load and Save States
|
|
for (unichar i = NSF1FunctionKey; i <= NSF8FunctionKey; i++)
|
|
{
|
|
NSInteger stateNum = i - NSF1FunctionKey + 1;
|
|
NSString* lstateTitle = [NSString stringWithFormat:@"Load State %ld", (long)stateNum];
|
|
c = i;
|
|
NSString* t = [NSString stringWithCharacters:&c length:1];
|
|
NSMenuItem* lstateItem = [[NSMenuItem alloc] initWithTitle:lstateTitle
|
|
action:@selector(loadState:)
|
|
keyEquivalent:t];
|
|
[lstateItem setTag:stateNum];
|
|
[lstateItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
|
|
[loadStateMenu addItem:lstateItem];
|
|
|
|
NSString* sstateTitle = [NSString stringWithFormat:@"Save State %ld", (long)stateNum];
|
|
c = i;
|
|
NSMenuItem* sstateItem = [[NSMenuItem alloc] initWithTitle:sstateTitle
|
|
action:@selector(saveState:)
|
|
keyEquivalent:t];
|
|
[sstateItem setKeyEquivalentModifierMask:NSEventModifierFlagShift];
|
|
[sstateItem setTag:stateNum];
|
|
[saveStateMenu addItem:sstateItem];
|
|
}
|
|
|
|
// App Main menu
|
|
[appMenu addItem:quitMenuItem];
|
|
|
|
// State Menu
|
|
[loadStateItem setSubmenu:loadStateMenu];
|
|
[saveStateItem setSubmenu:saveStateMenu];
|
|
|
|
[stateMenu addItem:loadLastItem];
|
|
[stateMenu addItem:undoLoadItem];
|
|
[stateMenu addItem:undoSaveItem];
|
|
[stateMenu addItem:loadStateItem];
|
|
[stateMenu addItem:saveStateItem];
|
|
|
|
// Misc Menu
|
|
[miscMenu addItem:fullScreenItem];
|
|
[miscMenu addItem:ScreenShotItem];
|
|
[miscMenu addItem:pauseItem];
|
|
|
|
[appMenuItem setSubmenu:appMenu];
|
|
[stateMenuItem setSubmenu:stateMenu];
|
|
[miscMenuItem setSubmenu:miscMenu];
|
|
|
|
[NSApp setMainMenu:menuBar];
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<Platform> Platform::CreateMacOSPlatform()
|
|
{
|
|
return std::make_unique<PlatformMacOS>();
|
|
}
|