[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.
This commit is contained in:
bruvzg 2021-11-01 11:12:52 +02:00
parent efbbd14af3
commit 0b6b8427c8
10 changed files with 97 additions and 18 deletions

View File

@ -235,6 +235,19 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r
return exitcode;
}
int OS::create_instance(const Vector<String> &p_arguments) {
List<String> 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<String> &p_arguments) {
List<String> 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);

View File

@ -165,6 +165,7 @@ public:
String get_executable_path() const;
int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false);
int create_process(const String &p_path, const Vector<String> &p_arguments);
int create_instance(const Vector<String> &p_arguments);
Error kill(int p_pid);
Error shell_open(String p_uri);

View File

@ -151,6 +151,7 @@ public:
virtual String get_executable_path() const;
virtual Error execute(const String &p_path, const List<String> &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<String> &p_arguments, ProcessID *r_child_id = nullptr) = 0;
virtual Error create_instance(const List<String> &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);

View File

@ -31,12 +31,21 @@
[b]Note:[/b] This method is implemented on Linux, macOS and Windows.
</description>
</method>
<method name="create_instance">
<return type="int" />
<argument index="0" name="arguments" type="PackedStringArray" />
<description>
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.
</description>
</method>
<method name="create_process">
<return type="int" />
<argument index="0" name="path" type="String" />
<argument index="1" name="arguments" type="PackedStringArray" />
<description>
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.
</description>
</method>
<method name="delay_msec" qualifiers="const">
@ -201,6 +210,7 @@
<return type="String" />
<description>
Returns the path to the current engine executable.
[b]Note:[/b] On macOS, always use [method create_instance] instead of relying on executable path.
</description>
</method>
<method name="get_granted_permissions" qualifiers="const">

View File

@ -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<String> 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);
}
}

View File

@ -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);
}

View File

@ -1304,8 +1304,7 @@ void ProjectList::update_dock_menu() {
void ProjectList::_global_menu_new_window(const Variant &p_tag) {
List<String> 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<String> 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<String> 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();

View File

@ -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<String> 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<String>()); //clear list (uses memory)
}

View File

@ -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<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
virtual String get_unique_id() const override; //++

View File

@ -491,6 +491,64 @@ String OS_OSX::get_executable_path() const {
}
}
Error OS_OSX::create_instance(const List<String> &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<String> &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<String>::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;