Browse Source

Implement and expose OS::shell_show_in_file_manager()

Daylily-Zeleen 2 years ago
parent
commit
b12ced0a26

+ 10 - 0
core/core_bind.cpp

@@ -257,6 +257,15 @@ Error OS::shell_open(String p_uri) {
 	return ::OS::get_singleton()->shell_open(p_uri);
 	return ::OS::get_singleton()->shell_open(p_uri);
 }
 }
 
 
+Error OS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
+	if (p_path.begins_with("res://")) {
+		WARN_PRINT("Attempting to explore file path with the \"res://\" protocol. Use `ProjectSettings.globalize_path()` to convert a Godot-specific path to a system path before opening it with `OS.shell_show_in_file_manager()`.");
+	} else if (p_path.begins_with("user://")) {
+		WARN_PRINT("Attempting to explore file path with the \"user://\" protocol. Use `ProjectSettings.globalize_path()` to convert a Godot-specific path to a system path before opening it with `OS.shell_show_in_file_manager()`.");
+	}
+	return ::OS::get_singleton()->shell_show_in_file_manager(p_path, p_open_folder);
+}
+
 String OS::read_string_from_stdin() {
 String OS::read_string_from_stdin() {
 	return ::OS::get_singleton()->get_stdin_string();
 	return ::OS::get_singleton()->get_stdin_string();
 }
 }
@@ -549,6 +558,7 @@ void OS::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
 	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("shell_show_in_file_manager", "file_or_dir_path", "open_folder"), &OS::shell_show_in_file_manager, DEFVAL(true));
 	ClassDB::bind_method(D_METHOD("is_process_running", "pid"), &OS::is_process_running);
 	ClassDB::bind_method(D_METHOD("is_process_running", "pid"), &OS::is_process_running);
 	ClassDB::bind_method(D_METHOD("get_process_id"), &OS::get_process_id);
 	ClassDB::bind_method(D_METHOD("get_process_id"), &OS::get_process_id);
 
 

+ 1 - 0
core/core_bind.h

@@ -152,6 +152,7 @@ public:
 	int create_instance(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);
+	Error shell_show_in_file_manager(String p_path, bool p_open_folder = true);
 
 
 	bool is_process_running(int p_pid) const;
 	bool is_process_running(int p_pid) const;
 	int get_process_id() const;
 	int get_process_id() const;

+ 9 - 0
core/os/os.cpp

@@ -281,6 +281,15 @@ Error OS::shell_open(String p_uri) {
 	return ERR_UNAVAILABLE;
 	return ERR_UNAVAILABLE;
 }
 }
 
 
+Error OS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
+	if (!p_path.begins_with("file://")) {
+		p_path = String("file://") + p_path;
+	}
+	if (!p_path.ends_with("/")) {
+		p_path = p_path.get_base_dir();
+	}
+	return shell_open(p_path);
+}
 // implement these with the canvas?
 // implement these with the canvas?
 
 
 uint64_t OS::get_static_memory_usage() const {
 uint64_t OS::get_static_memory_usage() const {

+ 1 - 0
core/os/os.h

@@ -163,6 +163,7 @@ public:
 	virtual void vibrate_handheld(int p_duration_ms = 500) {}
 	virtual void vibrate_handheld(int p_duration_ms = 500) {}
 
 
 	virtual Error shell_open(String p_uri);
 	virtual Error shell_open(String p_uri);
+	virtual Error shell_show_in_file_manager(String p_path, bool p_open_folder = true);
 	virtual Error set_cwd(const String &p_cwd);
 	virtual Error set_cwd(const String &p_cwd);
 
 
 	virtual bool has_environment(const String &p_var) const = 0;
 	virtual bool has_environment(const String &p_var) const = 0;

+ 11 - 0
doc/classes/OS.xml

@@ -639,6 +639,17 @@
 				[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux, macOS and Windows.
 				[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux, macOS and Windows.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="shell_show_in_file_manager">
+			<return type="int" enum="Error" />
+			<param index="0" name="file_or_dir_path" type="String" />
+			<param index="1" name="open_folder" type="bool" default="true" />
+			<description>
+				Requests the OS to open file manager, then navigate to the given [param file_or_dir_path] and select the target file or folder.
+				If [param file_or_dir_path] is a valid directory path, and [param open_folder] is [code]true[/code], the method will open explorer and enter the target folder without selecting anything.
+				Use [method ProjectSettings.globalize_path] to convert a [code]res://[/code] or [code]user://[/code] path into a system path for use with this method.
+				[b]Note:[/b] Currently this method is only implemented on Windows. On other platforms, it will fallback to [method shell_open] with a directory path for [param file_or_dir_path].
+			</description>
+		</method>
 		<method name="unset_environment" qualifiers="const">
 		<method name="unset_environment" qualifiers="const">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="variable" type="String" />
 			<param index="0" name="variable" type="String" />

+ 5 - 5
editor/editor_node.cpp

@@ -2995,10 +2995,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
 		case RUN_USER_DATA_FOLDER: {
 		case RUN_USER_DATA_FOLDER: {
 			// Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved.
 			// Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved.
 			OS::get_singleton()->ensure_user_data_dir();
 			OS::get_singleton()->ensure_user_data_dir();
-			OS::get_singleton()->shell_open(String("file://") + OS::get_singleton()->get_user_data_dir());
+			OS::get_singleton()->shell_show_in_file_manager(OS::get_singleton()->get_user_data_dir(), true);
 		} break;
 		} break;
 		case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: {
 		case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: {
-			OS::get_singleton()->shell_open("file://" + ProjectSettings::get_singleton()->get_resource_path().path_join("android"));
+			OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->get_resource_path().path_join("android"), true);
 		} break;
 		} break;
 		case FILE_QUIT:
 		case FILE_QUIT:
 		case RUN_PROJECT_MANAGER:
 		case RUN_PROJECT_MANAGER:
@@ -3070,10 +3070,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
 			editor_settings_dialog->popup_edit_settings();
 			editor_settings_dialog->popup_edit_settings();
 		} break;
 		} break;
 		case SETTINGS_EDITOR_DATA_FOLDER: {
 		case SETTINGS_EDITOR_DATA_FOLDER: {
-			OS::get_singleton()->shell_open(String("file://") + EditorPaths::get_singleton()->get_data_dir());
+			OS::get_singleton()->shell_show_in_file_manager(EditorPaths::get_singleton()->get_data_dir(), true);
 		} break;
 		} break;
 		case SETTINGS_EDITOR_CONFIG_FOLDER: {
 		case SETTINGS_EDITOR_CONFIG_FOLDER: {
-			OS::get_singleton()->shell_open(String("file://") + EditorPaths::get_singleton()->get_config_dir());
+			OS::get_singleton()->shell_show_in_file_manager(EditorPaths::get_singleton()->get_config_dir(), true);
 		} break;
 		} break;
 		case SETTINGS_MANAGE_EXPORT_TEMPLATES: {
 		case SETTINGS_MANAGE_EXPORT_TEMPLATES: {
 			export_template_manager->popup_manager();
 			export_template_manager->popup_manager();
@@ -3176,7 +3176,7 @@ void EditorNode::_screenshot(bool p_use_utc) {
 	NodePath path = String("user://") + name;
 	NodePath path = String("user://") + name;
 	_save_screenshot(path);
 	_save_screenshot(path);
 	if (EDITOR_GET("interface/editor/automatically_open_screenshots")) {
 	if (EDITOR_GET("interface/editor/automatically_open_screenshots")) {
-		OS::get_singleton()->shell_open(String("file://") + ProjectSettings::get_singleton()->globalize_path(path));
+		OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(path), true);
 	}
 	}
 }
 }
 
 

+ 1 - 1
editor/export/export_template_manager.cpp

@@ -620,7 +620,7 @@ void ExportTemplateManager::_installed_table_button_cbk(Object *p_item, int p_co
 
 
 void ExportTemplateManager::_open_template_folder(const String &p_version) {
 void ExportTemplateManager::_open_template_folder(const String &p_version) {
 	const String &templates_dir = EditorPaths::get_singleton()->get_export_templates_dir();
 	const String &templates_dir = EditorPaths::get_singleton()->get_export_templates_dir();
-	OS::get_singleton()->shell_open("file://" + templates_dir.path_join(p_version));
+	OS::get_singleton()->shell_show_in_file_manager(templates_dir.path_join(p_version), true);
 }
 }
 
 
 void ExportTemplateManager::popup_manager() {
 void ExportTemplateManager::popup_manager() {

+ 1 - 4
editor/filesystem_dock.cpp

@@ -1881,11 +1881,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
 				fpath = p_selected[0];
 				fpath = p_selected[0];
 			}
 			}
 
 
-			if (!fpath.ends_with("/")) {
-				fpath = fpath.get_base_dir();
-			}
 			String dir = ProjectSettings::get_singleton()->globalize_path(fpath);
 			String dir = ProjectSettings::get_singleton()->globalize_path(fpath);
-			OS::get_singleton()->shell_open(String("file://") + dir);
+			OS::get_singleton()->shell_show_in_file_manager(dir, true);
 		} break;
 		} break;
 
 
 		case FILE_OPEN_EXTERNAL: {
 		case FILE_OPEN_EXTERNAL: {

+ 1 - 4
editor/gui/editor_file_dialog.cpp

@@ -713,11 +713,8 @@ void EditorFileDialog::_item_menu_id_pressed(int p_option) {
 				// Specific item was clicked. Open folders directly, or the folder containing a selected file.
 				// Specific item was clicked. Open folders directly, or the folder containing a selected file.
 				Dictionary item_meta = item_list->get_item_metadata(idx);
 				Dictionary item_meta = item_list->get_item_metadata(idx);
 				path = ProjectSettings::get_singleton()->globalize_path(item_meta["path"]);
 				path = ProjectSettings::get_singleton()->globalize_path(item_meta["path"]);
-				if (!item_meta["dir"]) {
-					path = path.get_base_dir();
-				}
 			}
 			}
-			OS::get_singleton()->shell_open(String("file://") + path);
+			OS::get_singleton()->shell_show_in_file_manager(path, true);
 		} break;
 		} break;
 	}
 	}
 }
 }

+ 1 - 1
editor/project_manager.cpp

@@ -1827,7 +1827,7 @@ void ProjectList::_favorite_pressed(Node *p_hb) {
 }
 }
 
 
 void ProjectList::_show_project(const String &p_path) {
 void ProjectList::_show_project(const String &p_path) {
-	OS::get_singleton()->shell_open(String("file://") + p_path);
+	OS::get_singleton()->shell_show_in_file_manager(p_path, true);
 }
 }
 
 
 void ProjectList::_bind_methods() {
 void ProjectList::_bind_methods() {

+ 45 - 0
platform/windows/os_windows.cpp

@@ -1313,6 +1313,51 @@ Error OS_Windows::shell_open(String p_uri) {
 	}
 	}
 }
 }
 
 
+Error OS_Windows::shell_show_in_file_manager(String p_path, bool p_open_folder) {
+	p_path = p_path.trim_suffix("file://");
+
+	bool open_folder = false;
+	if (DirAccess::dir_exists_absolute(p_path) && p_open_folder) {
+		open_folder = true;
+	}
+
+	if (p_path.begins_with("\"")) {
+		p_path = String("\"") + p_path;
+	}
+	if (p_path.ends_with("\"")) {
+		p_path = p_path + String("\"");
+	}
+	p_path = p_path.replace("/", "\\");
+
+	INT_PTR ret = OK;
+	if (open_folder) {
+		ret = (INT_PTR)ShellExecuteW(nullptr, nullptr, L"explorer.exe", LPCWSTR(p_path.utf16().get_data()), nullptr, SW_SHOWNORMAL);
+	} else {
+		ret = (INT_PTR)ShellExecuteW(nullptr, nullptr, L"explorer.exe", LPCWSTR((String("/select,") + p_path).utf16().get_data()), nullptr, SW_SHOWNORMAL);
+	}
+
+	if (ret > 32) {
+		return OK;
+	} else {
+		switch (ret) {
+			case ERROR_FILE_NOT_FOUND:
+			case SE_ERR_DLLNOTFOUND:
+				return ERR_FILE_NOT_FOUND;
+			case ERROR_PATH_NOT_FOUND:
+				return ERR_FILE_BAD_PATH;
+			case ERROR_BAD_FORMAT:
+				return ERR_FILE_CORRUPT;
+			case SE_ERR_ACCESSDENIED:
+				return ERR_UNAUTHORIZED;
+			case 0:
+			case SE_ERR_OOM:
+				return ERR_OUT_OF_MEMORY;
+			default:
+				return FAILED;
+		}
+	}
+}
+
 String OS_Windows::get_locale() const {
 String OS_Windows::get_locale() const {
 	const _WinLocale *wl = &_win_locales[0];
 	const _WinLocale *wl = &_win_locales[0];
 
 

+ 1 - 0
platform/windows/os_windows.h

@@ -212,6 +212,7 @@ public:
 	virtual String get_unique_id() const override;
 	virtual String get_unique_id() const override;
 
 
 	virtual Error shell_open(String p_uri) override;
 	virtual Error shell_open(String p_uri) override;
+	virtual Error shell_show_in_file_manager(String p_path, bool p_open_folder) override;
 
 
 	void run();
 	void run();