[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:
parent
efbbd14af3
commit
0b6b8427c8
|
@ -235,6 +235,19 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r
|
||||||
return exitcode;
|
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) {
|
int OS::create_process(const String &p_path, const Vector<String> &p_arguments) {
|
||||||
List<String> args;
|
List<String> args;
|
||||||
for (int i = 0; i < p_arguments.size(); i++) {
|
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("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("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_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("kill", "pid"), &OS::kill);
|
||||||
ClassDB::bind_method(D_METHOD("shell_open", "uri"), &OS::shell_open);
|
ClassDB::bind_method(D_METHOD("shell_open", "uri"), &OS::shell_open);
|
||||||
ClassDB::bind_method(D_METHOD("get_process_id"), &OS::get_process_id);
|
ClassDB::bind_method(D_METHOD("get_process_id"), &OS::get_process_id);
|
||||||
|
|
|
@ -165,6 +165,7 @@ public:
|
||||||
String get_executable_path() const;
|
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 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_process(const String &p_path, const Vector<String> &p_arguments);
|
||||||
|
int create_instance(const Vector<String> &p_arguments);
|
||||||
Error kill(int p_pid);
|
Error kill(int p_pid);
|
||||||
Error shell_open(String p_uri);
|
Error shell_open(String p_uri);
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,7 @@ public:
|
||||||
virtual String get_executable_path() const;
|
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 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_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 Error kill(const ProcessID &p_pid) = 0;
|
||||||
virtual int get_process_id() const;
|
virtual int get_process_id() const;
|
||||||
virtual void vibrate_handheld(int p_duration_ms = 500);
|
virtual void vibrate_handheld(int p_duration_ms = 500);
|
||||||
|
|
|
@ -31,12 +31,21 @@
|
||||||
[b]Note:[/b] This method is implemented on Linux, macOS and Windows.
|
[b]Note:[/b] This method is implemented on Linux, macOS and Windows.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="create_process">
|
||||||
<return type="int" />
|
<return type="int" />
|
||||||
<argument index="0" name="path" type="String" />
|
<argument index="0" name="path" type="String" />
|
||||||
<argument index="1" name="arguments" type="PackedStringArray" />
|
<argument index="1" name="arguments" type="PackedStringArray" />
|
||||||
<description>
|
<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].
|
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:
|
For example, running another instance of the project:
|
||||||
[codeblocks]
|
[codeblocks]
|
||||||
|
@ -49,7 +58,7 @@
|
||||||
[/codeblocks]
|
[/codeblocks]
|
||||||
See [method execute] if you wish to run an external command and retrieve the results.
|
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] 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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="delay_msec" qualifiers="const">
|
<method name="delay_msec" qualifiers="const">
|
||||||
|
@ -201,6 +210,7 @@
|
||||||
<return type="String" />
|
<return type="String" />
|
||||||
<description>
|
<description>
|
||||||
Returns the path to the current engine executable.
|
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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="get_granted_permissions" qualifiers="const">
|
<method name="get_granted_permissions" qualifiers="const">
|
||||||
|
|
|
@ -3059,7 +3059,7 @@ void EditorNode::_discard_changes(const String &p_str) {
|
||||||
args.push_back(exec.get_base_dir());
|
args.push_back(exec.get_base_dir());
|
||||||
args.push_back("--project-manager");
|
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);
|
ERR_FAIL_COND(err);
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
@ -5420,8 +5420,7 @@ void EditorNode::_global_menu_new_window(const Variant &p_tag) {
|
||||||
if (OS::get_singleton()->get_main_loop()) {
|
if (OS::get_singleton()->get_main_loop()) {
|
||||||
List<String> args;
|
List<String> args;
|
||||||
args.push_back("-p");
|
args.push_back("-p");
|
||||||
String exec = OS::get_singleton()->get_executable_path();
|
OS::get_singleton()->create_instance(args);
|
||||||
OS::get_singleton()->create_process(exec, args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
int instances = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_instances", 1);
|
||||||
for (int i = 0; i < instances; i++) {
|
for (int i = 0; i < instances; i++) {
|
||||||
OS::ProcessID pid = 0;
|
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);
|
ERR_FAIL_COND_V(err, err);
|
||||||
pids.push_back(pid);
|
pids.push_back(pid);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1304,8 +1304,7 @@ void ProjectList::update_dock_menu() {
|
||||||
void ProjectList::_global_menu_new_window(const Variant &p_tag) {
|
void ProjectList::_global_menu_new_window(const Variant &p_tag) {
|
||||||
List<String> args;
|
List<String> args;
|
||||||
args.push_back("-p");
|
args.push_back("-p");
|
||||||
String exec = OS::get_singleton()->get_executable_path();
|
OS::get_singleton()->create_instance(args);
|
||||||
OS::get_singleton()->create_process(exec, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectList::_global_menu_open_project(const Variant &p_tag) {
|
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");
|
String conf = _projects[idx].path.plus_file("project.godot");
|
||||||
List<String> args;
|
List<String> args;
|
||||||
args.push_back(conf);
|
args.push_back(conf);
|
||||||
String exec = OS::get_singleton()->get_executable_path();
|
OS::get_singleton()->create_instance(args);
|
||||||
OS::get_singleton()->create_process(exec, args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2062,8 +2060,7 @@ void ProjectManager::_open_selected_projects() {
|
||||||
args.push_back("--single-window");
|
args.push_back("--single-window");
|
||||||
}
|
}
|
||||||
|
|
||||||
String exec = OS::get_singleton()->get_executable_path();
|
Error err = OS::get_singleton()->create_instance(args);
|
||||||
Error err = OS::get_singleton()->create_process(exec, args);
|
|
||||||
ERR_FAIL_COND(err);
|
ERR_FAIL_COND(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2148,8 +2145,7 @@ void ProjectManager::_run_project_confirm() {
|
||||||
args.push_back("--disable-crash-handler");
|
args.push_back("--disable-crash-handler");
|
||||||
}
|
}
|
||||||
|
|
||||||
String exec = OS::get_singleton()->get_executable_path();
|
Error err = OS::get_singleton()->create_instance(args);
|
||||||
Error err = OS::get_singleton()->create_process(exec, args);
|
|
||||||
ERR_FAIL_COND(err);
|
ERR_FAIL_COND(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2278,8 +2274,7 @@ void ProjectManager::_language_selected(int p_id) {
|
||||||
|
|
||||||
void ProjectManager::_restart_confirm() {
|
void ProjectManager::_restart_confirm() {
|
||||||
List<String> args = OS::get_singleton()->get_cmdline_args();
|
List<String> args = OS::get_singleton()->get_cmdline_args();
|
||||||
String exec = OS::get_singleton()->get_executable_path();
|
Error err = OS::get_singleton()->create_instance(args);
|
||||||
Error err = OS::get_singleton()->create_process(exec, args);
|
|
||||||
ERR_FAIL_COND(err);
|
ERR_FAIL_COND(err);
|
||||||
|
|
||||||
_dim_window();
|
_dim_window();
|
||||||
|
|
|
@ -2848,9 +2848,8 @@ void Main::cleanup(bool p_force) {
|
||||||
|
|
||||||
if (OS::get_singleton()->is_restart_on_exit_set()) {
|
if (OS::get_singleton()->is_restart_on_exit_set()) {
|
||||||
//attempt to restart with arguments
|
//attempt to restart with arguments
|
||||||
String exec = OS::get_singleton()->get_executable_path();
|
|
||||||
List<String> args = OS::get_singleton()->get_restart_on_exit_arguments();
|
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)
|
OS::get_singleton()->set_restart_on_exit(false, List<String>()); //clear list (uses memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,8 @@ public:
|
||||||
String get_locale() const override;
|
String get_locale() const override;
|
||||||
|
|
||||||
virtual String get_executable_path() 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; //++
|
virtual String get_unique_id() const override; //++
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
void OS_OSX::run() {
|
||||||
force_quit = false;
|
force_quit = false;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue