瀏覽代碼

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
Rémi Verschelde 3 月之前
父節點
當前提交
bb9d6d0d02
共有 7 個文件被更改,包括 83 次插入3 次删除
  1. 9 0
      core/core_bind.cpp
  2. 1 0
      core/core_bind.h
  3. 1 0
      core/os/os.h
  4. 10 0
      doc/classes/OS.xml
  5. 3 3
      editor/filesystem_dock.cpp
  6. 1 0
      platform/macos/os_macos.h
  7. 58 0
      platform/macos/os_macos.mm

+ 9 - 0
core/core_bind.cpp

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

+ 1 - 0
core/core_bind.h

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

+ 1 - 0
core/os/os.h

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

+ 10 - 0
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.
 			</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" />

+ 3 - 3
editor/filesystem_dock.cpp

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

+ 1 - 0
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<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;

+ 58 - 0
platform/macos/os_macos.mm

@@ -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) {