From a317ce75a6564f090d1c8cbd7feb21a9d7d92c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Thu, 20 Mar 2025 09:21:36 +0200 Subject: [PATCH] [macOS] Replace custom main loop with `[NSApp run]` and `CFRunLoop` observer. --- platform/macos/display_server_macos.h | 1 + platform/macos/display_server_macos.mm | 31 +++-- platform/macos/godot_application_delegate.h | 2 +- platform/macos/godot_application_delegate.mm | 74 ++++++---- platform/macos/godot_main_macos.mm | 28 +--- platform/macos/os_macos.h | 20 ++- platform/macos/os_macos.mm | 137 +++++++++++-------- 7 files changed, 167 insertions(+), 126 deletions(-) diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 02d32f8755f..abce3bac06d 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -441,6 +441,7 @@ public: virtual Key keyboard_get_label_from_physical(Key p_keycode) const override; virtual void show_emoji_and_symbol_picker() const override; + void _process_events(bool p_pump); virtual void process_events() override; virtual void force_process_and_drop_events() override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index e835bdb0822..e3f474cd64c 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -1901,6 +1901,11 @@ DisplayServer::WindowID DisplayServerMacOS::create_sub_window(WindowMode p_mode, void DisplayServerMacOS::show_window(WindowID p_id) { WindowData &wd = windows[p_id]; + if (p_id == MAIN_WINDOW_ID) { + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + static_cast(OS::get_singleton())->activate(); + } + popup_open(p_id); if ([wd.window_object isMiniaturized]) { return; @@ -3204,20 +3209,26 @@ void DisplayServerMacOS::show_emoji_and_symbol_picker() const { } void DisplayServerMacOS::process_events() { + _process_events(true); +} + +void DisplayServerMacOS::_process_events(bool p_pump) { ERR_FAIL_COND(!Thread::is_main_thread()); - while (true) { - NSEvent *event = [NSApp - nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; + if (p_pump) { + while (true) { + NSEvent *event = [NSApp + nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; - if (event == nil) { - break; + if (event == nil) { + break; + } + + [NSApp sendEvent:event]; } - - [NSApp sendEvent:event]; } // Process "menu_callback"s. diff --git a/platform/macos/godot_application_delegate.h b/platform/macos/godot_application_delegate.h index 3a57b88f174..c5c3a07b574 100644 --- a/platform/macos/godot_application_delegate.h +++ b/platform/macos/godot_application_delegate.h @@ -36,8 +36,8 @@ #import @interface GodotApplicationDelegate : NSObject +- (void)activate; - (void)forceUnbundledWindowActivationHackStep1; - (void)forceUnbundledWindowActivationHackStep2; - (void)forceUnbundledWindowActivationHackStep3; -- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; @end diff --git a/platform/macos/godot_application_delegate.mm b/platform/macos/godot_application_delegate.mm index 2f69993a16e..a3ee8c73c31 100644 --- a/platform/macos/godot_application_delegate.mm +++ b/platform/macos/godot_application_delegate.mm @@ -124,7 +124,13 @@ } } -- (void)applicationDidFinishLaunching:(NSNotification *)notice { +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + static_cast(OS::get_singleton())->start_main(); +} + +- (void)activate { + [NSApp activateIgnoringOtherApps:YES]; + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; const char *bundled_id = getenv("__CFBundleIdentifier"); NSString *nsbundleid_env = [NSString stringWithUTF8String:(bundled_id != nullptr) ? bundled_id : ""]; @@ -139,11 +145,6 @@ - (id)init { self = [super init]; - - NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager]; - [aem setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; - [aem setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEOpenDocuments]; - return self; } @@ -152,36 +153,45 @@ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:@"AppleColorPreferencesChangedNotification" object:nil]; } -- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { +- (void)application:(NSApplication *)application openURLs:(NSArray *)urls { OS_MacOS *os = (OS_MacOS *)OS::get_singleton(); - if (!event || !os) { + if (!os) { return; } - List args; - if (([event eventClass] == kInternetEventClass) && ([event eventID] == kAEGetURL)) { - // Opening URL scheme. - NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - args.push_back(vformat("--uri=\"%s\"", String::utf8([url UTF8String]))); - } - - if (([event eventClass] == kCoreEventClass) && ([event eventID] == kAEOpenDocuments)) { - // Opening file association. - NSAppleEventDescriptor *files = [event paramDescriptorForKeyword:keyDirectObject]; - if (files) { - NSInteger count = [files numberOfItems]; - for (NSInteger i = 1; i <= count; i++) { - NSURL *url = [NSURL URLWithString:[[files descriptorAtIndex:i] stringValue]]; - args.push_back(String::utf8([url.path UTF8String])); - } + for (NSURL *url in urls) { + if ([url isFileURL]) { + args.push_back(String::utf8([url.path UTF8String])); + } else { + args.push_back(vformat("--uri=\"%s\"", String::utf8([url.absoluteString UTF8String]))); } } - if (!args.is_empty()) { if (os->get_main_loop()) { // Application is already running, open a new instance with the URL/files as command line arguments. os->create_instance(args); - } else { + } else if (os->get_cmd_argc() == 0) { + // Application is just started, add to the list of command line arguments and continue. + os->set_cmdline_platform_args(args); + } + } +} + +- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames { + OS_MacOS *os = (OS_MacOS *)OS::get_singleton(); + if (!os) { + return; + } + List args; + for (NSString *filename in filenames) { + NSURL *url = [NSURL URLWithString:filename]; + args.push_back(String::utf8([url.path UTF8String])); + } + if (!args.is_empty()) { + if (os->get_main_loop()) { + // Application is already running, open a new instance with the URL/files as command line arguments. + os->create_instance(args); + } else if (os->get_cmd_argc() == 0) { // Application is just started, add to the list of command line arguments and continue. os->set_cmdline_platform_args(args); } @@ -220,11 +230,23 @@ } } +- (void)applicationWillTerminate:(NSNotification *)notification { + OS_MacOS *os = (OS_MacOS *)OS::get_singleton(); + if (os) { + os->cleanup(); + exit(os->get_exit_code()); + } +} + - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); if (ds) { ds->send_window_event(ds->get_window(DisplayServerMacOS::MAIN_WINDOW_ID), DisplayServerMacOS::WINDOW_EVENT_CLOSE_REQUEST); } + OS_MacOS *os = (OS_MacOS *)OS::get_singleton(); + if (!os || os->os_should_terminate()) { + return NSTerminateNow; + } return NSTerminateCancel; } diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm index c3a6449564e..ba341d1bb07 100644 --- a/platform/macos/godot_main_macos.mm +++ b/platform/macos/godot_main_macos.mm @@ -59,36 +59,12 @@ int main(int argc, char **argv) { } } - OS_MacOS os; - Error err; + OS_MacOS os(argv[0], argc - first_arg, &argv[first_arg]); // We must override main when testing is enabled. TEST_MAIN_OVERRIDE - @autoreleasepool { - err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); - } - - if (err != OK) { - if (err == ERR_HELP) { // Returned by --help and --version, so success. - return EXIT_SUCCESS; - } - return EXIT_FAILURE; - } - - int ret; - @autoreleasepool { - ret = Main::start(); - } - if (ret == EXIT_SUCCESS) { - os.run(); - } else { - os.set_exit_code(EXIT_FAILURE); - } - - @autoreleasepool { - Main::cleanup(); - } + os.run(); // Note: This function will never return. return os.get_exit_code(); } diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index e00394acbdb..b9cb61ae9c0 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -40,6 +40,13 @@ #include "servers/audio_server.h" class OS_MacOS : public OS_Unix { + const char *execpath = nullptr; + int argc = 0; + char **argv = nullptr; + + id delegate = nullptr; + bool should_terminate = false; + JoypadApple *joypad_apple = nullptr; #ifdef COREAUDIO_ENABLED @@ -51,7 +58,7 @@ class OS_MacOS : public OS_Unix { CrashHandler crash_handler; - CFRunLoopObserverRef pre_wait_observer; + CFRunLoopObserverRef pre_wait_observer = nil; MainLoop *main_loop = nullptr; @@ -64,6 +71,8 @@ class OS_MacOS : public OS_Unix { static _FORCE_INLINE_ String get_framework_executable(const String &p_path); static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context); + void terminate(); + protected: virtual void initialize_core() override; virtual void initialize() override; @@ -131,8 +140,13 @@ public: virtual String get_system_ca_certificates() override; virtual OS::PreferredTextureFormat get_preferred_texture_format() const override; - void run(); + void run(); // Runs macOS native event loop. + void start_main(); // Initializes and runs Godot main loop. + void activate(); + void cleanup(); + bool os_should_terminate() const { return should_terminate; } + int get_cmd_argc() const { return argc; } - OS_MacOS(); + OS_MacOS(const char *p_execpath, int p_argc, char **p_argv); ~OS_MacOS(); }; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index fcdf5bc0d80..47397614846 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -47,14 +47,20 @@ #include void OS_MacOS::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) { - // Prevent main loop from sleeping and redraw window during modal popup display. - // Do not redraw when rendering is done from the separate thread, it will conflict with the OpenGL context updates. + OS_MacOS *os = static_cast(OS::get_singleton()); - DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); - if (get_singleton()->get_main_loop() && ds && !get_singleton()->is_separate_thread_rendering_enabled() && !ds->get_is_resizing()) { - Main::force_redraw(); - if (!Main::is_iterating()) { // Avoid cyclic loop. - Main::iteration(); + @autoreleasepool { + @try { + if (DisplayServer::get_singleton()) { + static_cast(DisplayServer::get_singleton())->_process_events(false); // Get rid of pending events. + } + os->joypad_apple->process_joypads(); + + if (Main::iteration()) { + os->terminate(); + } + } @catch (NSException *exception) { + ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String)); } } @@ -823,36 +829,69 @@ OS::PreferredTextureFormat OS_MacOS::get_preferred_texture_format() const { } void OS_MacOS::run() { - if (!main_loop) { - return; - } - - @autoreleasepool { - main_loop->initialize(); - } - - bool quit = false; - while (!quit) { - @autoreleasepool { - @try { - if (DisplayServer::get_singleton()) { - DisplayServer::get_singleton()->process_events(); // Get rid of pending events. - } - joypad_apple->process_joypads(); - - if (Main::iteration()) { - quit = true; - } - } @catch (NSException *exception) { - ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String)); - } - } - } - - main_loop->finalize(); + [NSApp run]; } -OS_MacOS::OS_MacOS() { +void OS_MacOS::start_main() { + Error err; + @autoreleasepool { + err = Main::setup(execpath, argc, argv); + } + + if (err == OK) { + int ret; + @autoreleasepool { + ret = Main::start(); + } + if (ret == EXIT_SUCCESS) { + if (main_loop) { + @autoreleasepool { + main_loop->initialize(); + } + pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); + CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); + return; + } + } else { + set_exit_code(EXIT_FAILURE); + } + } else if (err == ERR_HELP) { // Returned by --help and --version, so success. + set_exit_code(EXIT_SUCCESS); + } else { + set_exit_code(EXIT_FAILURE); + } + + terminate(); +} + +void OS_MacOS::activate() { + [delegate activate]; +} + +void OS_MacOS::terminate() { + if (pre_wait_observer) { + CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); + CFRelease(pre_wait_observer); + pre_wait_observer = nil; + } + + should_terminate = true; + [NSApp terminate:nil]; +} + +void OS_MacOS::cleanup() { + if (main_loop) { + main_loop->finalize(); + @autoreleasepool { + Main::cleanup(); + } + } +} + +OS_MacOS::OS_MacOS(const char *p_execpath, int p_argc, char **p_argv) { + execpath = p_execpath; + argc = p_argc; + argv = p_argv; if (is_sandboxed()) { // Load security-scoped bookmarks, request access, remove stale or invalid bookmarks. NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; @@ -886,7 +925,7 @@ OS_MacOS::OS_MacOS() { [GodotApplication sharedApplication]; // In case we are unbundled, make us a proper UI application. - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; // Menu bar setup must go between sharedApplication above and // finishLaunching below, in order to properly emulate the behavior @@ -894,35 +933,13 @@ OS_MacOS::OS_MacOS() { NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""]; [NSApp setMainMenu:main_menu]; - [NSApp finishLaunching]; - id delegate = [[GodotApplicationDelegate alloc] init]; + delegate = [[GodotApplicationDelegate alloc] init]; ERR_FAIL_NULL(delegate); [NSApp setDelegate:delegate]; [NSApp registerUserInterfaceItemSearchHandler:delegate]; - - pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); - CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); - - // Process application:openFile: event. - while (true) { - NSEvent *event = [NSApp - nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - - if (event == nil) { - break; - } - - [NSApp sendEvent:event]; - } - - [NSApp activateIgnoringOtherApps:YES]; } OS_MacOS::~OS_MacOS() { - CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); - CFRelease(pre_wait_observer); + // NOP }