diff --git a/core/core_bind.cpp b/core/core_bind.cpp index c7cd797f4d23..50587bb4026a 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -257,6 +257,15 @@ Error OS::shell_open(String 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() { return ::OS::get_singleton()->get_stdin_string(); } @@ -553,6 +562,7 @@ void OS::_bind_methods() { 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("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("get_process_id"), &OS::get_process_id); diff --git a/core/core_bind.h b/core/core_bind.h index c7d597562aca..4138a854407c 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -152,6 +152,7 @@ public: int create_instance(const Vector &p_arguments); Error kill(int p_pid); 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; int get_process_id() const; diff --git a/core/os/os.cpp b/core/os/os.cpp index 49b47c6c120c..c82f87c731d5 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -281,6 +281,15 @@ Error OS::shell_open(String p_uri) { 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? uint64_t OS::get_static_memory_usage() const { diff --git a/core/os/os.h b/core/os/os.h index 076a9db55c36..3248330c74c0 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -163,6 +163,7 @@ public: virtual void vibrate_handheld(int p_duration_ms = 500) {} 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 bool has_environment(const String &p_var) const = 0; diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 12c3e573e343..a30c541b7184 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -649,6 +649,17 @@ [b]Note:[/b] This method is implemented on Android, iOS, Web, Linux, macOS and Windows. + + + + + + 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]. + + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index f2f12857999d..58356a45c43b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2995,10 +2995,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { 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. 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; 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; case FILE_QUIT: 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(); } break; 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; 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; case SETTINGS_MANAGE_EXPORT_TEMPLATES: { export_template_manager->popup_manager(); @@ -3176,7 +3176,7 @@ void EditorNode::_screenshot(bool p_use_utc) { NodePath path = String("user://") + name; _save_screenshot(path); 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); } } diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index 725dc1d6bba6..e551b0531af6 100644 --- a/editor/export/export_template_manager.cpp +++ b/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) { 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() { diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 066e8cb84ef1..445a54e3462b 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1881,11 +1881,8 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected fpath = p_selected[0]; } - if (!fpath.ends_with("/")) { - fpath = fpath.get_base_dir(); - } 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; case FILE_OPEN_EXTERNAL: { diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 62e05207998c..a7ddb984e027 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/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. Dictionary item_meta = item_list->get_item_metadata(idx); 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; } } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 4dd2237b4ffd..40c05707a6a4 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1829,7 +1829,7 @@ void ProjectList::_favorite_pressed(Node *p_hb) { } 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() { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index b237c94072e8..5a749a9d6f54 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1351,6 +1351,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 { const _WinLocale *wl = &_win_locales[0]; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 5dee83769b49..960c3f30a9a5 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -214,6 +214,7 @@ public: virtual String get_unique_id() const 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();