Merge pull request #107113 from mihe/macos-open-in-program

Add `OS::open_with_program` for opening files/directories with a specific program on macOS
This commit is contained in:
Rémi Verschelde
2025-06-05 13:13:49 +02:00
7 changed files with 83 additions and 3 deletions

View File

@@ -437,6 +437,14 @@ int OS::create_instance(const Vector<String> &p_arguments) {
return pid;
}
Error OS::open_with_program(const String &p_program_path, const Vector<String> &p_paths) {
List<String> 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<String> &p_arguments, bool p_open_console) {
List<String> 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));

View File

@@ -221,6 +221,7 @@ public:
Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking = true);
int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
int create_instance(const Vector<String> &p_arguments);
Error open_with_program(const String &p_program_path, const Vector<String> &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);

View File

@@ -191,6 +191,7 @@ public:
virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) { return Dictionary(); }
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 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 open_with_program(const String &p_program_path, const List<String> &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;

View File

@@ -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.
</description>
</method>
<method name="open_with_program">
<return type="int" enum="Error" />
<param index="0" name="program_path" type="String" />
<param index="1" name="paths" type="PackedStringArray" />
<description>
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.
</description>
</method>
<method name="read_buffer_from_stdin">
<return type="PackedByteArray" />
<param index="0" name="buffer_size" type="int" default="1024" />

View File

@@ -2204,9 +2204,9 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
if (external_program.is_empty()) {
OS::get_singleton()->shell_open(file);
} else {
List<String> args;
args.push_back(file);
OS::get_singleton()->create_process(external_program, args);
List<String> paths;
paths.push_back(file);
OS::get_singleton()->open_with_program(external_program, paths);
}
} break;

View File

@@ -115,6 +115,7 @@ public:
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, bool p_open_console = false) override;
virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
virtual Error open_with_program(const String &p_program_path, const List<String> &p_paths) override;
virtual bool is_process_running(const ProcessID &p_pid) const override;
virtual String get_unique_id() const override;

View File

@@ -831,6 +831,64 @@ Error OS_MacOS::create_instance(const List<String> &p_arguments, ProcessID *r_ch
}
}
Error OS_MacOS::open_with_program(const String &p_program_path, const List<String> &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) {