From 0b6b8427c81f60f6298491100ceaed3247649539 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Mon, 1 Nov 2021 11:12:52 +0200 Subject: [PATCH] [macOS] Add `create_instance` function to spawn editor copies. [macOS] Modify `create_project` function to detect and run app bundles using NSWorkspace to ensure app window is registered and activated correctly. --- core/core_bind.cpp | 14 +++++++++ core/core_bind.h | 1 + core/os/os.h | 1 + doc/classes/OS.xml | 14 +++++++-- editor/editor_node.cpp | 5 ++-- editor/editor_run.cpp | 2 +- editor/project_manager.cpp | 15 ++++------ main/main.cpp | 3 +- platform/osx/os_osx.h | 2 ++ platform/osx/os_osx.mm | 58 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 97 insertions(+), 18 deletions(-) diff --git a/core/core_bind.cpp b/core/core_bind.cpp index afd82939ca7..b1858c6b322 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -235,6 +235,19 @@ int OS::execute(const String &p_path, const Vector &p_arguments, Array r return exitcode; } +int OS::create_instance(const Vector &p_arguments) { + List args; + for (int i = 0; i < p_arguments.size(); i++) { + args.push_back(p_arguments[i]); + } + ::OS::ProcessID pid = 0; + Error err = ::OS::get_singleton()->create_instance(args, &pid); + if (err != OK) { + return -1; + } + return pid; +} + int OS::create_process(const String &p_path, const Vector &p_arguments) { List args; for (int i = 0; i < p_arguments.size(); i++) { @@ -537,6 +550,7 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path); ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr"), &OS::execute, DEFVAL(Array()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_process", "path", "arguments"), &OS::create_process); + ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance); ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill); ClassDB::bind_method(D_METHOD("shell_open", "uri"), &OS::shell_open); ClassDB::bind_method(D_METHOD("get_process_id"), &OS::get_process_id); diff --git a/core/core_bind.h b/core/core_bind.h index 010d1e24196..72865f583c5 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -165,6 +165,7 @@ public: String get_executable_path() const; int execute(const String &p_path, const Vector &p_arguments, Array r_output = Array(), bool p_read_stderr = false); int create_process(const String &p_path, const Vector &p_arguments); + int create_instance(const Vector &p_arguments); Error kill(int p_pid); Error shell_open(String p_uri); diff --git a/core/os/os.h b/core/os/os.h index f02f600c588..52bf731501f 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -151,6 +151,7 @@ public: virtual String get_executable_path() const; virtual Error execute(const String &p_path, const List &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) = 0; virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr) = 0; + virtual Error create_instance(const List &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); }; virtual Error kill(const ProcessID &p_pid) = 0; virtual int get_process_id() const; virtual void vibrate_handheld(int p_duration_ms = 500); diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 03a3ac053d0..f4d48f5db2f 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -31,12 +31,21 @@ [b]Note:[/b] This method is implemented on Linux, macOS and Windows. + + + + + Creates a new instance of Godot that runs independently. The [code]arguments[/code] are used in the given order and separated by a space. + If the process creation succeeds, the method will return the new process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). If the process creation fails, the method will return [code]-1[/code]. + [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. + + - Creates a new process that runs independently of Godot. It will not terminate if Godot terminates. The file specified in [code]path[/code] must exist and be executable. Platform path resolution will be used. The [code]arguments[/code] are used in the given order and separated by a space. + Creates a new process that runs independently of Godot. It will not terminate if Godot terminates. The path specified in [code]path[/code] must exist and be executable file or macOS .app bundle. Platform path resolution will be used. The [code]arguments[/code] are used in the given order and separated by a space. If the process creation succeeds, the method will return the new process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). If the process creation fails, the method will return [code]-1[/code]. For example, running another instance of the project: [codeblocks] @@ -49,7 +58,7 @@ [/codeblocks] See [method execute] if you wish to run an external command and retrieve the results. [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. - [b]Note:[/b] On macOS, sandboxed applications are limited to run only embedded helper executables, specified during export. + [b]Note:[/b] On macOS, sandboxed applications are limited to run only embedded helper executables, specified during export or system .app bundle, system .app bundles will ignore arguments. @@ -201,6 +210,7 @@ Returns the path to the current engine executable. + [b]Note:[/b] On macOS, always use [method create_instance] instead of relying on executable path. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 9edf0a24fc6..bb4a05efba1 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3059,7 +3059,7 @@ void EditorNode::_discard_changes(const String &p_str) { args.push_back(exec.get_base_dir()); args.push_back("--project-manager"); - Error err = OS::get_singleton()->create_process(exec, args); + Error err = OS::get_singleton()->create_instance(args); ERR_FAIL_COND(err); } break; } @@ -5420,8 +5420,7 @@ void EditorNode::_global_menu_new_window(const Variant &p_tag) { if (OS::get_singleton()->get_main_loop()) { List args; args.push_back("-p"); - String exec = OS::get_singleton()->get_executable_path(); - OS::get_singleton()->create_process(exec, args); + OS::get_singleton()->create_instance(args); } } diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 8a7ec9aa82f..d7daa0c750c 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -236,7 +236,7 @@ Error EditorRun::run(const String &p_scene, const String &p_custom_args, const L int instances = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_instances", 1); for (int i = 0; i < instances; i++) { OS::ProcessID pid = 0; - Error err = OS::get_singleton()->create_process(exec, args, &pid); + Error err = OS::get_singleton()->create_instance(args, &pid); ERR_FAIL_COND_V(err, err); pids.push_back(pid); } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index dc35e01a560..c7ab91a49bc 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1304,8 +1304,7 @@ void ProjectList::update_dock_menu() { void ProjectList::_global_menu_new_window(const Variant &p_tag) { List args; args.push_back("-p"); - String exec = OS::get_singleton()->get_executable_path(); - OS::get_singleton()->create_process(exec, args); + OS::get_singleton()->create_instance(args); } void ProjectList::_global_menu_open_project(const Variant &p_tag) { @@ -1315,8 +1314,7 @@ void ProjectList::_global_menu_open_project(const Variant &p_tag) { String conf = _projects[idx].path.plus_file("project.godot"); List args; args.push_back(conf); - String exec = OS::get_singleton()->get_executable_path(); - OS::get_singleton()->create_process(exec, args); + OS::get_singleton()->create_instance(args); } } @@ -2062,8 +2060,7 @@ void ProjectManager::_open_selected_projects() { args.push_back("--single-window"); } - String exec = OS::get_singleton()->get_executable_path(); - Error err = OS::get_singleton()->create_process(exec, args); + Error err = OS::get_singleton()->create_instance(args); ERR_FAIL_COND(err); } @@ -2148,8 +2145,7 @@ void ProjectManager::_run_project_confirm() { args.push_back("--disable-crash-handler"); } - String exec = OS::get_singleton()->get_executable_path(); - Error err = OS::get_singleton()->create_process(exec, args); + Error err = OS::get_singleton()->create_instance(args); ERR_FAIL_COND(err); } } @@ -2278,8 +2274,7 @@ void ProjectManager::_language_selected(int p_id) { void ProjectManager::_restart_confirm() { List args = OS::get_singleton()->get_cmdline_args(); - String exec = OS::get_singleton()->get_executable_path(); - Error err = OS::get_singleton()->create_process(exec, args); + Error err = OS::get_singleton()->create_instance(args); ERR_FAIL_COND(err); _dim_window(); diff --git a/main/main.cpp b/main/main.cpp index fe0f2693e4d..676fa7b8365 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2848,9 +2848,8 @@ void Main::cleanup(bool p_force) { if (OS::get_singleton()->is_restart_on_exit_set()) { //attempt to restart with arguments - String exec = OS::get_singleton()->get_executable_path(); List args = OS::get_singleton()->get_restart_on_exit_arguments(); - OS::get_singleton()->create_process(exec, args); + OS::get_singleton()->create_instance(args); OS::get_singleton()->set_restart_on_exit(false, List()); //clear list (uses memory) } diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index a52436a70ac..fc78fb28a8a 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -92,6 +92,8 @@ public: String get_locale() const override; virtual String get_executable_path() const override; + virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr) override; + virtual Error create_instance(const List &p_arguments, ProcessID *r_child_id = nullptr) override; virtual String get_unique_id() const override; //++ diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 489cbe074b9..307ab03c489 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -491,6 +491,64 @@ String OS_OSX::get_executable_path() const { } } +Error OS_OSX::create_instance(const List &p_arguments, ProcessID *r_child_id) { + // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly. + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname != nil) { + String path; + path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]); + return create_process(path, p_arguments, r_child_id); + } else { + return create_process(get_executable_path(), p_arguments, r_child_id); + } +} + +Error OS_OSX::create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id) { + if (@available(macOS 10.15, *)) { + // Use NSWorkspace if path is an .app bundle. + NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; + NSBundle *bundle = [NSBundle bundleWithURL:url]; + if (bundle) { + NSMutableArray *arguments = [[NSMutableArray alloc] init]; + for (const List::Element *E = p_arguments.front(); E; E = E->next()) { + [arguments addObject:[NSString stringWithUTF8String:E->get().utf8().get_data()]]; + } + NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init]; + [configuration setArguments:arguments]; + [configuration setCreatesNewApplicationInstance:YES]; + __block dispatch_semaphore_t lock = dispatch_semaphore_create(0); + __block Error err = ERR_TIMEOUT; + __block pid_t pid = 0; + [[NSWorkspace sharedWorkspace] openApplicationAtURL:url + configuration:configuration + completionHandler:^(NSRunningApplication *app, NSError *error) { + if (error) { + err = ERR_CANT_FORK; + NSLog(@"Failed to execute: %@", error.localizedDescription); + } else { + pid = [app processIdentifier]; + err = OK; + } + dispatch_semaphore_signal(lock); + }]; + dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch. + dispatch_release(lock); + + if (err == OK) { + if (r_child_id) { + *r_child_id = (ProcessID)pid; + } + } + + return err; + } else { + return OS_Unix::create_process(p_path, p_arguments, r_child_id); + } + } else { + return OS_Unix::create_process(p_path, p_arguments, r_child_id); + } +} + void OS_OSX::run() { force_quit = false;