diff --git a/shell/common/platform_util_linux.cc b/shell/common/platform_util_linux.cc index df2a744b0e01..231e0ad0e8c9 100644 --- a/shell/common/platform_util_linux.cc +++ b/shell/common/platform_util_linux.cc @@ -10,15 +10,87 @@ #include "base/environment.h" #include "base/files/file_util.h" #include "base/nix/xdg_util.h" +#include "base/no_destructor.h" #include "base/process/kill.h" #include "base/process/launch.h" +#include "components/dbus/thread_linux/dbus_thread_linux.h" +#include "content/public/browser/browser_thread.h" +#include "dbus/bus.h" +#include "dbus/message.h" +#include "dbus/object_proxy.h" #include "ui/gtk/gtk_util.h" #include "url/gurl.h" #define ELECTRON_TRASH "ELECTRON_TRASH" +namespace platform_util { +void OpenFolder(const base::FilePath& full_path); +} + namespace { +const char kFreedesktopFileManagerName[] = "org.freedesktop.FileManager1"; +const char kFreedesktopFileManagerPath[] = "/org/freedesktop/FileManager1"; + +const char kMethodShowItems[] = "ShowItems"; + +class ShowItemHelper { + public: + static ShowItemHelper& GetInstance() { + static base::NoDestructor instance; + return *instance; + } + + ShowItemHelper() {} + + ShowItemHelper(const ShowItemHelper&) = delete; + ShowItemHelper& operator=(const ShowItemHelper&) = delete; + + void ShowItemInFolder(const base::FilePath& full_path) { + if (!bus_) { + // Sets up the D-Bus connection. + dbus::Bus::Options bus_options; + bus_options.bus_type = dbus::Bus::SESSION; + bus_options.connection_type = dbus::Bus::PRIVATE; + bus_options.dbus_task_runner = dbus_thread_linux::GetTaskRunner(); + bus_ = base::MakeRefCounted(bus_options); + } + + if (!filemanager_proxy_) { + filemanager_proxy_ = + bus_->GetObjectProxy(kFreedesktopFileManagerName, + dbus::ObjectPath(kFreedesktopFileManagerPath)); + } + + dbus::MethodCall show_items_call(kFreedesktopFileManagerName, + kMethodShowItems); + dbus::MessageWriter writer(&show_items_call); + + writer.AppendArrayOfStrings( + {"file://" + full_path.value()}); // List of file(s) to highlight. + writer.AppendString({}); // startup-id + + filemanager_proxy_->CallMethod( + &show_items_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + base::BindOnce(&ShowItemHelper::ShowItemInFolderResponse, + base::Unretained(this), full_path)); + } + + private: + void ShowItemInFolderResponse(const base::FilePath& full_path, + dbus::Response* response) { + if (response) + return; + + LOG(ERROR) << "Error calling " << kMethodShowItems; + // If the FileManager1 call fails, at least open the parent folder. + platform_util::OpenFolder(full_path.DirName()); + } + + scoped_refptr bus_; + dbus::ObjectProxy* filemanager_proxy_ = nullptr; +}; + // Descriptions pulled from https://linux.die.net/man/1/xdg-open std::string GetErrorDescription(int error_code) { switch (error_code) { @@ -36,9 +108,11 @@ std::string GetErrorDescription(int error_code) { } bool XDGUtil(const std::vector& argv, + const base::FilePath& working_directory, const bool wait_for_exit, platform_util::OpenCallback callback) { base::LaunchOptions options; + options.current_directory = working_directory; options.allow_new_privs = true; // xdg-open can fall back on mailcap which eventually might plumb through // to a command that needs a terminal. Set the environment variable telling @@ -62,14 +136,16 @@ bool XDGUtil(const std::vector& argv, return true; } -bool XDGOpen(const std::string& path, +bool XDGOpen(const base::FilePath& working_directory, + const std::string& path, const bool wait_for_exit, platform_util::OpenCallback callback) { - return XDGUtil({"xdg-open", path}, wait_for_exit, std::move(callback)); + return XDGUtil({"xdg-open", path}, working_directory, wait_for_exit, + std::move(callback)); } bool XDGEmail(const std::string& email, const bool wait_for_exit) { - return XDGUtil({"xdg-email", email}, wait_for_exit, + return XDGUtil({"xdg-email", email}, base::FilePath(), wait_for_exit, platform_util::OpenCallback()); } @@ -78,16 +154,20 @@ bool XDGEmail(const std::string& email, const bool wait_for_exit) { namespace platform_util { void ShowItemInFolder(const base::FilePath& full_path) { - base::FilePath dir = full_path.DirName(); - if (!base::DirectoryExists(dir)) - return; - - XDGOpen(dir.value(), false, platform_util::OpenCallback()); + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + ShowItemHelper::GetInstance().ShowItemInFolder(full_path); } void OpenPath(const base::FilePath& full_path, OpenCallback callback) { // This is async, so we don't care about the return value. - XDGOpen(full_path.value(), true, std::move(callback)); + XDGOpen(full_path.DirName(), full_path.value(), true, std::move(callback)); +} + +void OpenFolder(const base::FilePath& full_path) { + if (!base::DirectoryExists(full_path)) + return; + + XDGOpen(full_path.DirName(), ".", false, platform_util::OpenCallback()); } void OpenExternal(const GURL& url, @@ -99,7 +179,8 @@ void OpenExternal(const GURL& url, bool success = XDGEmail(url.spec(), false); std::move(callback).Run(success ? "" : "Failed to open path"); } else { - bool success = XDGOpen(url.spec(), false, platform_util::OpenCallback()); + bool success = XDGOpen(base::FilePath(), url.spec(), false, + platform_util::OpenCallback()); std::move(callback).Run(success ? "" : "Failed to open path"); } } @@ -133,7 +214,7 @@ bool MoveItemToTrash(const base::FilePath& full_path, bool delete_on_fail) { argv = {"gio", "trash", filename}; } - return XDGUtil(argv, true, platform_util::OpenCallback()); + return XDGUtil(argv, base::FilePath(), true, platform_util::OpenCallback()); } void Beep() {