[macOS] Replace custom main loop with [NSApp run]
and CFRunLoop
observer.
This commit is contained in:
parent
0595bb8a42
commit
a317ce75a6
@ -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;
|
||||
|
||||
|
@ -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_MacOS *>(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.
|
||||
|
@ -36,8 +36,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate>
|
||||
- (void)activate;
|
||||
- (void)forceUnbundledWindowActivationHackStep1;
|
||||
- (void)forceUnbundledWindowActivationHackStep2;
|
||||
- (void)forceUnbundledWindowActivationHackStep3;
|
||||
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
|
||||
@end
|
||||
|
@ -124,7 +124,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notice {
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
static_cast<OS_MacOS *>(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<NSURL *> *)urls {
|
||||
OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
|
||||
if (!event || !os) {
|
||||
if (!os) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> 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<NSString *> *)filenames {
|
||||
OS_MacOS *os = (OS_MacOS *)OS::get_singleton();
|
||||
if (!os) {
|
||||
return;
|
||||
}
|
||||
List<String> 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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -47,14 +47,20 @@
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
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_MacOS *>(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<DisplayServerMacOS *>(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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user