[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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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; //++
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue