diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 87d70aaf6f..c08ae987b0 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -437,6 +437,14 @@ int OS::create_instance(const Vector &p_arguments) { return pid; } +Error OS::open_with_program(const String &p_program_path, const Vector &p_paths) { + List paths; + for (const String &path : p_paths) { + paths.push_back(path); + } + return ::OS::get_singleton()->open_with_program(p_program_path, paths); +} + int OS::create_process(const String &p_path, const Vector &p_arguments, bool p_open_console) { List args; for (const String &arg : p_arguments) { @@ -755,6 +763,7 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments", "blocking"), &OS::execute_with_pipe, DEFVAL(true)); ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance); + ClassDB::bind_method(D_METHOD("open_with_program", "program_path", "paths"), &OS::open_with_program); 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_show_in_file_manager", "file_or_dir_path", "open_folder"), &OS::shell_show_in_file_manager, DEFVAL(true)); diff --git a/core/core_bind.h b/core/core_bind.h index b1a014dad4..9d6e3ee5b3 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -221,6 +221,7 @@ public: Dictionary execute_with_pipe(const String &p_path, const Vector &p_arguments, bool p_blocking = true); int create_process(const String &p_path, const Vector &p_arguments, bool p_open_console = false); int create_instance(const Vector &p_arguments); + Error open_with_program(const String &p_program_path, const Vector &p_paths); Error kill(int p_pid); Error shell_open(const String &p_uri); Error shell_show_in_file_manager(const String &p_path, bool p_open_folder = true); diff --git a/core/os/os.h b/core/os/os.h index 530859acdc..bbd1571738 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -191,6 +191,7 @@ public: virtual Dictionary execute_with_pipe(const String &p_path, const List &p_arguments, bool p_blocking = true) { return Dictionary(); } virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 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 open_with_program(const String &p_program_path, const List &p_paths) { return create_process(p_program_path, p_paths); } virtual Error kill(const ProcessID &p_pid) = 0; virtual int get_process_id() const; virtual bool is_process_running(const ProcessID &p_pid) const = 0; diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 3c5f3f0d2f..b1c47d0c76 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -730,6 +730,16 @@ [b]Note:[/b] On the Web platform, using MIDI input requires a browser permission to be granted first. This permission request is performed when calling [method open_midi_inputs]. The browser will refrain from processing MIDI input until the user accepts the permission request. + + + + + + Opens one or more files/directories with the specified application. The [param program_path] specifies the path to the application to use for opening the files, and [param paths] contains an array of file/directory paths to open. + [b]Note:[/b] This method is mostly only relevant for macOS, where opening files using [method create_process] might fail. On other platforms, this falls back to using [method create_process]. + [b]Note:[/b] On macOS, [param program_path] should ideally be the path to an [code].app[/code] bundle. + + diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index bd216436b2..43e9dffc75 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -2204,9 +2204,9 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected if (external_program.is_empty()) { OS::get_singleton()->shell_open(file); } else { - List args; - args.push_back(file); - OS::get_singleton()->create_process(external_program, args); + List paths; + paths.push_back(file); + OS::get_singleton()->open_with_program(external_program, paths); } } break; diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index 0618d0fafb..caaeb72fa4 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -115,6 +115,7 @@ public: virtual String get_executable_path() const override; virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_instance(const List &p_arguments, ProcessID *r_child_id = nullptr) override; + virtual Error open_with_program(const String &p_program_path, const List &p_paths) override; virtual bool is_process_running(const ProcessID &p_pid) const override; virtual String get_unique_id() const override; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 2c4c79e1b5..cdaa18f40f 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -831,6 +831,64 @@ Error OS_MacOS::create_instance(const List &p_arguments, ProcessID *r_ch } } +Error OS_MacOS::open_with_program(const String &p_program_path, const List &p_paths) { + NSURL *app_url = [NSURL fileURLWithPath:@(p_program_path.utf8().get_data())]; + if (!app_url) { + return ERR_INVALID_PARAMETER; + } + + NSBundle *bundle = [NSBundle bundleWithURL:app_url]; + if (!bundle) { + return OS_Unix::create_process(p_program_path, p_paths); + } + + NSMutableArray *urls_to_open = [[NSMutableArray alloc] init]; + for (const String &path : p_paths) { + NSURL *file_url = [NSURL fileURLWithPath:@(path.utf8().get_data())]; + if (file_url) { + [urls_to_open addObject:file_url]; + } + } + + if ([urls_to_open count] == 0) { + return ERR_INVALID_PARAMETER; + } + +#if defined(__x86_64__) + if (@available(macOS 10.15, *)) { +#endif + NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init]; + [configuration setCreatesNewApplicationInstance:NO]; + __block dispatch_semaphore_t lock = dispatch_semaphore_create(0); + __block Error err = ERR_TIMEOUT; + + [[NSWorkspace sharedWorkspace] openURLs:urls_to_open + withApplicationAtURL:app_url + configuration:configuration + completionHandler:^(NSRunningApplication *app, NSError *error) { + if (error) { + err = ERR_CANT_FORK; + NSLog(@"Failed to open paths: %@", error.localizedDescription); + } else { + err = OK; + } + dispatch_semaphore_signal(lock); + }]; + dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch. + + return err; +#if defined(__x86_64__) + } else { + NSError *error = nullptr; + [[NSWorkspace sharedWorkspace] openURLs:urls_to_open withApplicationAtURL:app_url options:NSWorkspaceLaunchDefault configuration:@{} error:&error]; + if (error) { + return ERR_CANT_FORK; + } + return OK; + } +#endif +} + bool OS_MacOS::is_process_running(const ProcessID &p_pid) const { NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:(pid_t)p_pid]; if (!app) {