PlatformMacos.mm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // Copyright 2023 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DolphinNoGUI/Platform.h"
  4. #include "Common/MsgHandler.h"
  5. #include "Core/Config/MainSettings.h"
  6. #include "Core/Core.h"
  7. #include "Core/State.h"
  8. #include "Core/System.h"
  9. #include "VideoCommon/Present.h"
  10. #include "VideoCommon/RenderBase.h"
  11. #include <AppKit/AppKit.h>
  12. #include <Carbon/Carbon.h>
  13. #include <Foundation/Foundation.h>
  14. #include <array>
  15. #include <chrono>
  16. #include <climits>
  17. #include <cstdio>
  18. #include <cstring>
  19. #include <thread>
  20. @interface Application : NSApplication
  21. @property Platform* platform;
  22. - (void)shutdown;
  23. - (void)togglePause;
  24. - (void)saveScreenShot;
  25. - (void)loadLastSaved;
  26. - (void)undoLoadState;
  27. - (void)undoSaveState;
  28. - (void)loadState:(id)sender;
  29. - (void)saveState:(id)sender;
  30. @end
  31. @implementation Application
  32. - (void)shutdown;
  33. {
  34. [self platform]->RequestShutdown();
  35. [self stop:nil];
  36. }
  37. - (void)togglePause
  38. {
  39. auto& system = Core::System::GetInstance();
  40. if (Core::GetState(system) == Core::State::Running)
  41. Core::SetState(system, Core::State::Paused);
  42. else
  43. Core::SetState(system, Core::State::Running);
  44. }
  45. - (void)saveScreenShot
  46. {
  47. Core::SaveScreenShot();
  48. }
  49. - (void)loadLastSaved
  50. {
  51. State::LoadLastSaved(Core::System::GetInstance());
  52. }
  53. - (void)undoLoadState
  54. {
  55. State::UndoLoadState(Core::System::GetInstance());
  56. }
  57. - (void)undoSaveState
  58. {
  59. State::UndoSaveState(Core::System::GetInstance());
  60. }
  61. - (void)loadState:(id)sender
  62. {
  63. State::Load(Core::System::GetInstance(), [sender tag]);
  64. }
  65. - (void)saveState:(id)sender
  66. {
  67. State::Save(Core::System::GetInstance(), [sender tag]);
  68. }
  69. @end
  70. @interface AppDelegate : NSObject <NSApplicationDelegate>
  71. @property(readonly) Platform* platform;
  72. - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender;
  73. - (id)initWithPlatform:(Platform*)platform;
  74. @end
  75. @implementation AppDelegate
  76. - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender
  77. {
  78. return YES;
  79. }
  80. - (id)initWithPlatform:(Platform*)platform
  81. {
  82. self = [super init];
  83. if (self)
  84. {
  85. _platform = platform;
  86. }
  87. return self;
  88. }
  89. @end
  90. @interface WindowDelegate : NSObject <NSWindowDelegate>
  91. - (void)windowDidResize:(NSNotification*)notification;
  92. @end
  93. @implementation WindowDelegate
  94. - (void)windowDidResize:(NSNotification*)notification
  95. {
  96. if (g_presenter)
  97. g_presenter->ResizeSurface();
  98. }
  99. @end
  100. namespace
  101. {
  102. class PlatformMacOS : public Platform
  103. {
  104. public:
  105. ~PlatformMacOS() override;
  106. bool Init() override;
  107. void SetTitle(const std::string& title) override;
  108. void MainLoop() override;
  109. WindowSystemInfo GetWindowSystemInfo() const override;
  110. private:
  111. void ProcessEvents();
  112. void UpdateWindowPosition();
  113. void HandleSaveStates(NSUInteger key, NSUInteger flags);
  114. void SetupMenu();
  115. NSRect m_window_rect;
  116. NSWindow* m_window;
  117. NSMenu* menuBar;
  118. AppDelegate* m_app_delegate;
  119. WindowDelegate* m_window_delegate;
  120. int m_window_x = Config::Get(Config::MAIN_RENDER_WINDOW_XPOS);
  121. int m_window_y = Config::Get(Config::MAIN_RENDER_WINDOW_YPOS);
  122. unsigned int m_window_width = Config::Get(Config::MAIN_RENDER_WINDOW_WIDTH);
  123. unsigned int m_window_height = Config::Get(Config::MAIN_RENDER_WINDOW_HEIGHT);
  124. bool m_window_fullscreen = Config::Get(Config::MAIN_FULLSCREEN);
  125. };
  126. PlatformMacOS::~PlatformMacOS()
  127. {
  128. [m_window close];
  129. }
  130. bool PlatformMacOS::Init()
  131. {
  132. [Application sharedApplication];
  133. m_app_delegate = [[AppDelegate alloc] initWithPlatform:this];
  134. [NSApp setDelegate:m_app_delegate];
  135. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  136. [NSApp setPlatform:this];
  137. [Application.sharedApplication finishLaunching];
  138. unsigned long styleMask =
  139. NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
  140. m_window_rect = CGRectMake(m_window_x, m_window_y, m_window_width, m_window_height);
  141. m_window = [NSWindow alloc];
  142. m_window = [m_window initWithContentRect:m_window_rect
  143. styleMask:styleMask
  144. backing:NSBackingStoreBuffered
  145. defer:NO];
  146. m_window_delegate = [[WindowDelegate alloc] init];
  147. [m_window setDelegate:m_window_delegate];
  148. NSNotificationCenter* c = [NSNotificationCenter defaultCenter];
  149. [c addObserver:NSApp
  150. selector:@selector(shutdown)
  151. name:NSWindowWillCloseNotification
  152. object:m_window];
  153. if (m_window == nil)
  154. {
  155. NSLog(@"Window is %@\n", m_window);
  156. return false;
  157. }
  158. if (Config::Get(Config::MAIN_SHOW_CURSOR) == Config::ShowCursor::Never)
  159. [NSCursor hide];
  160. if (Config::Get(Config::MAIN_FULLSCREEN))
  161. {
  162. m_window_fullscreen = true;
  163. [m_window toggleFullScreen:m_window];
  164. }
  165. [m_window makeKeyAndOrderFront:NSApp];
  166. [m_window makeMainWindow];
  167. [NSApp activateIgnoringOtherApps:YES];
  168. [m_window setTitle:@"Dolphin-emu-nogui"];
  169. SetupMenu();
  170. return true;
  171. }
  172. void PlatformMacOS::SetTitle(const std::string& title)
  173. {
  174. @autoreleasepool
  175. {
  176. NSWindow* window = m_window;
  177. NSString* str = [NSString stringWithUTF8String:title.c_str()];
  178. dispatch_async(dispatch_get_main_queue(), ^{
  179. [window setTitle:str];
  180. });
  181. }
  182. }
  183. void PlatformMacOS::MainLoop()
  184. {
  185. while (IsRunning())
  186. {
  187. UpdateRunningFlag();
  188. Core::HostDispatchJobs(Core::System::GetInstance());
  189. ProcessEvents();
  190. UpdateWindowPosition();
  191. }
  192. }
  193. WindowSystemInfo PlatformMacOS::GetWindowSystemInfo() const
  194. {
  195. @autoreleasepool
  196. {
  197. WindowSystemInfo wsi;
  198. wsi.type = WindowSystemType::MacOS;
  199. wsi.render_window = (void*)CFBridgingRetain([m_window contentView]);
  200. wsi.render_surface = wsi.render_window;
  201. return wsi;
  202. }
  203. }
  204. void PlatformMacOS::ProcessEvents()
  205. {
  206. @autoreleasepool
  207. {
  208. NSDate* expiration = [NSDate dateWithTimeIntervalSinceNow:1];
  209. NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
  210. untilDate:expiration
  211. inMode:NSDefaultRunLoopMode
  212. dequeue:YES];
  213. [NSApp sendEvent:event];
  214. // Need to update if m_window becomes fullscreen
  215. m_window_fullscreen = [m_window styleMask] & NSWindowStyleMaskFullScreen;
  216. if ([m_window isMainWindow])
  217. {
  218. m_window_focus = true;
  219. if (Config::Get(Config::MAIN_SHOW_CURSOR) == Config::ShowCursor::Never &&
  220. Core::GetState(Core::System::GetInstance()) != Core::State::Paused)
  221. {
  222. [NSCursor unhide];
  223. }
  224. }
  225. else
  226. {
  227. m_window_focus = false;
  228. if (Config::Get(Config::MAIN_SHOW_CURSOR) == Config::ShowCursor::Never)
  229. [NSCursor hide];
  230. }
  231. }
  232. }
  233. void PlatformMacOS::UpdateWindowPosition()
  234. {
  235. if (m_window_fullscreen)
  236. return;
  237. NSRect win = [m_window frame];
  238. m_window_x = win.origin.x;
  239. m_window_y = win.origin.y;
  240. m_window_width = win.size.width;
  241. m_window_height = win.size.height;
  242. }
  243. void PlatformMacOS::SetupMenu()
  244. {
  245. @autoreleasepool
  246. {
  247. menuBar = [NSMenu new];
  248. NSMenu* appMenu = [NSMenu new];
  249. NSMenu* stateMenu = [[NSMenu alloc] initWithTitle:@"States"];
  250. NSMenu* loadStateMenu = [[NSMenu alloc] initWithTitle:@"Load"];
  251. NSMenu* saveStateMenu = [[NSMenu alloc] initWithTitle:@"Save"];
  252. NSMenu* miscMenu = [[NSMenu alloc] initWithTitle:@"Misc"];
  253. NSMenuItem* appMenuItem = [NSMenuItem new];
  254. NSMenuItem* miscMenuItem = [NSMenuItem new];
  255. NSMenuItem* stateMenuItem = [NSMenuItem new];
  256. NSMenuItem* loadStateItem = [[NSMenuItem alloc] initWithTitle:@"Load"
  257. action:nil
  258. keyEquivalent:@""];
  259. NSMenuItem* saveStateItem = [[NSMenuItem alloc] initWithTitle:@"Save"
  260. action:nil
  261. keyEquivalent:@""];
  262. [menuBar addItem:appMenuItem];
  263. [menuBar addItem:stateMenuItem];
  264. [menuBar addItem:miscMenuItem];
  265. // Quit
  266. NSString* quitTitle = [@"Quit " stringByAppendingString:@"dolphin-emu-nogui"];
  267. NSMenuItem* quitMenuItem = [[NSMenuItem alloc] initWithTitle:quitTitle
  268. action:@selector(shutdown)
  269. keyEquivalent:@"q"];
  270. // Fullscreen
  271. NSString* fullScreenItemTitle = @"Toggle Fullscreen";
  272. NSMenuItem* fullScreenItem = [[NSMenuItem alloc] initWithTitle:fullScreenItemTitle
  273. action:@selector(toggleFullScreen:)
  274. keyEquivalent:@"f"];
  275. [fullScreenItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
  276. // Screenshot
  277. NSString* ScreenShotTitle = @"Take Screenshot";
  278. unichar c = NSF9FunctionKey;
  279. NSString* f9 = [NSString stringWithCharacters:&c length:1];
  280. NSMenuItem* ScreenShotItem = [[NSMenuItem alloc] initWithTitle:ScreenShotTitle
  281. action:@selector(saveScreenShot)
  282. keyEquivalent:f9];
  283. [ScreenShotItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
  284. // Pause game
  285. NSString* pauseTitle = @"Toggle pause";
  286. c = NSF10FunctionKey;
  287. NSString* f10 = [NSString stringWithCharacters:&c length:1];
  288. NSMenuItem* pauseItem = [[NSMenuItem alloc] initWithTitle:pauseTitle
  289. action:@selector(togglePause)
  290. keyEquivalent:f10];
  291. [pauseItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
  292. // Load last save
  293. NSString* loadLastTitle = @"Load Last Saved";
  294. c = NSF11FunctionKey;
  295. NSString* f11 = [NSString stringWithCharacters:&c length:1];
  296. NSMenuItem* loadLastItem = [[NSMenuItem alloc] initWithTitle:loadLastTitle
  297. action:@selector(loadLastSaved)
  298. keyEquivalent:f11];
  299. [loadLastItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
  300. // Undo Load State
  301. NSString* undoLoadTitle = @"Undo Load";
  302. c = NSF12FunctionKey;
  303. NSString* f12 = [NSString stringWithCharacters:&c length:1];
  304. NSMenuItem* undoLoadItem = [[NSMenuItem alloc] initWithTitle:undoLoadTitle
  305. action:@selector(undoLoadState)
  306. keyEquivalent:f12];
  307. [undoLoadItem setKeyEquivalentModifierMask:NSEventModifierFlagShift];
  308. // Undo Save State
  309. NSString* undoSaveTitle = @"Undo Save";
  310. NSMenuItem* undoSaveItem = [[NSMenuItem alloc] initWithTitle:undoSaveTitle
  311. action:@selector(undoSaveState)
  312. keyEquivalent:f12];
  313. [undoSaveItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
  314. // Load and Save States
  315. for (unichar i = NSF1FunctionKey; i <= NSF8FunctionKey; i++)
  316. {
  317. NSInteger stateNum = i - NSF1FunctionKey + 1;
  318. NSString* lstateTitle = [NSString stringWithFormat:@"Load State %ld", (long)stateNum];
  319. c = i;
  320. NSString* t = [NSString stringWithCharacters:&c length:1];
  321. NSMenuItem* lstateItem = [[NSMenuItem alloc] initWithTitle:lstateTitle
  322. action:@selector(loadState:)
  323. keyEquivalent:t];
  324. [lstateItem setTag:stateNum];
  325. [lstateItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction];
  326. [loadStateMenu addItem:lstateItem];
  327. NSString* sstateTitle = [NSString stringWithFormat:@"Save State %ld", (long)stateNum];
  328. c = i;
  329. NSMenuItem* sstateItem = [[NSMenuItem alloc] initWithTitle:sstateTitle
  330. action:@selector(saveState:)
  331. keyEquivalent:t];
  332. [sstateItem setKeyEquivalentModifierMask:NSEventModifierFlagShift];
  333. [sstateItem setTag:stateNum];
  334. [saveStateMenu addItem:sstateItem];
  335. }
  336. // App Main menu
  337. [appMenu addItem:quitMenuItem];
  338. // State Menu
  339. [loadStateItem setSubmenu:loadStateMenu];
  340. [saveStateItem setSubmenu:saveStateMenu];
  341. [stateMenu addItem:loadLastItem];
  342. [stateMenu addItem:undoLoadItem];
  343. [stateMenu addItem:undoSaveItem];
  344. [stateMenu addItem:loadStateItem];
  345. [stateMenu addItem:saveStateItem];
  346. // Misc Menu
  347. [miscMenu addItem:fullScreenItem];
  348. [miscMenu addItem:ScreenShotItem];
  349. [miscMenu addItem:pauseItem];
  350. [appMenuItem setSubmenu:appMenu];
  351. [stateMenuItem setSubmenu:stateMenu];
  352. [miscMenuItem setSubmenu:miscMenu];
  353. [NSApp setMainMenu:menuBar];
  354. }
  355. }
  356. } // namespace
  357. std::unique_ptr<Platform> Platform::CreateMacOSPlatform()
  358. {
  359. return std::make_unique<PlatformMacOS>();
  360. }