Commit da1cd328 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik Committed by Commit Bot

Support org.freedesktop.FileManager1 interface for showing item in folder

This interface lets an application ask the file manager to scroll to and highlight
a specific file in its parent folder. It is supported by at least KDE's Dolphin
and Gnome's Nautilus and removes the need for filemanager-specific code.

In case the interface is not available or the call fails, the parent folder
is opened as before.

Bug: 352988
Change-Id: Id4e2b61cc77a12698fe6f02b307c9b1e815ab4ee
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1905947Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: default avatarThomas Anderson <thomasanderson@chromium.org>
Reviewed-by: default avatarRyo Hashimoto <hashimoto@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#716326}
parent bfc15e2d
......@@ -424,6 +424,11 @@ specific_include_rules = {
"ash_service_registry\.cc": [
"+ash/ash_service.h",
],
"platform_util_linux.cc": [
# The following is used to call the org.freedesktop.FileManager1
# DBus interface to highlight a file within its parent folder
"+dbus"
],
"platform_util_mac.mm": [
# The following is used to forward methods to an NSWindow in another
# process, via the views::Widget API.
......
......@@ -41,9 +41,6 @@ enum OpenOperationResult {
enum OpenItemType {
OPEN_FILE,
OPEN_FOLDER,
#if defined(OS_LINUX)
SHOW_ITEM_IN_FOLDER
#endif
};
// Callback used with OpenFile and OpenFolder.
......
......@@ -5,17 +5,21 @@
#include "chrome/browser/platform_util.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/version.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/platform_util_internal.h"
#include "components/dbus/thread_linux/dbus_thread_linux.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "url/gurl.h"
using content::BrowserThread;
......@@ -24,10 +28,88 @@ namespace platform_util {
namespace {
const char kNautilusKey[] = "nautilus.desktop";
const char kNautilusKeyExtended[] = "nautilus-folder-handler.desktop";
const char kNautilusCmd[] = "nautilus";
const char kSupportedNautilusVersion[] = "3.0.2";
const char kFreedesktopFileManagerName[] = "org.freedesktop.FileManager1";
const char kFreedesktopFileManagerPath[] = "/org/freedesktop/FileManager1";
const char kMethodShowItems[] = "ShowItems";
class ShowItemHelper : public content::NotificationObserver {
public:
static ShowItemHelper& GetInstance() {
static base::NoDestructor<ShowItemHelper> instance;
return *instance;
}
ShowItemHelper() {
registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
content::NotificationService::AllSources());
}
ShowItemHelper(const ShowItemHelper&) = delete;
ShowItemHelper& operator=(const ShowItemHelper&) = delete;
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
// The browser process is about to exit. Clean up while we still can.
if (bus_)
bus_->ShutdownOnDBusThreadAndBlock();
bus_.reset();
filemanager_proxy_ = nullptr;
}
void ShowItemInFolder(Profile* profile, 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<dbus::Bus>(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,
weak_ptr_factory_.GetWeakPtr(), profile, full_path));
}
private:
void ShowItemInFolderResponse(Profile* profile,
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.
OpenItem(profile, full_path.DirName(), OPEN_FOLDER,
OpenOperationCallback());
}
content::NotificationRegistrar registrar_;
scoped_refptr<dbus::Bus> bus_;
dbus::ObjectProxy* filemanager_proxy_ = nullptr;
base::WeakPtrFactory<ShowItemHelper> weak_ptr_factory_{this};
};
void RunCommand(const std::string& command,
const base::FilePath& working_directory,
......@@ -66,72 +148,6 @@ void XDGEmail(const std::string& email) {
RunCommand("xdg-email", base::FilePath(), email);
}
void ShowFileInNautilus(const base::FilePath& working_directory,
const std::string& path) {
RunCommand(kNautilusCmd, working_directory, path);
}
std::string GetNautilusVersion() {
std::string output;
std::string found_version;
base::CommandLine nautilus_cl((base::FilePath(kNautilusCmd)));
nautilus_cl.AppendArg("--version");
if (base::GetAppOutputAndError(nautilus_cl, &output)) {
// It is assumed that "nautilus --version" returns something like
// "GNOME nautilus 3.14.2". First, find the position of the first char of
// "nautilus " and skip the whole string to get the position of
// version in the |output| string.
size_t nautilus_position = output.find("nautilus ");
size_t version_position = nautilus_position + strlen("nautilus ");
if (nautilus_position != std::string::npos) {
found_version = output.substr(version_position);
base::TrimWhitespaceASCII(found_version,
base::TRIM_TRAILING,
&found_version);
}
}
return found_version;
}
bool CheckNautilusIsDefault() {
std::string file_browser;
base::CommandLine xdg_mime(base::FilePath("xdg-mime"));
xdg_mime.AppendArg("query");
xdg_mime.AppendArg("default");
xdg_mime.AppendArg("inode/directory");
bool success = base::GetAppOutputAndError(xdg_mime, &file_browser);
base::TrimWhitespaceASCII(file_browser,
base::TRIM_TRAILING,
&file_browser);
if (!success ||
(file_browser != kNautilusKey && file_browser != kNautilusKeyExtended))
return false;
const base::Version supported_version(kSupportedNautilusVersion);
DCHECK(supported_version.IsValid());
const base::Version current_version(GetNautilusVersion());
return current_version.IsValid() && current_version >= supported_version;
}
void ShowItem(Profile* profile,
const base::FilePath& full_path,
bool use_nautilus_file_browser) {
if (use_nautilus_file_browser) {
OpenItem(profile, full_path, SHOW_ITEM_IN_FOLDER, OpenOperationCallback());
} else {
// TODO(estade): It would be nice to be able to select the file in other
// file managers, but that probably requires extending xdg-open.
// For now just show the folder for non-Nautilus users.
OpenItem(profile, full_path.DirName(), OPEN_FOLDER,
OpenOperationCallback());
}
}
} // namespace
namespace internal {
......@@ -154,9 +170,6 @@ void PlatformOpenVerifiedItem(const base::FilePath& path, OpenItemType type) {
// time the application invoked by xdg-open inspects the path by name.
XDGOpen(path, ".");
break;
case SHOW_ITEM_IN_FOLDER:
ShowFileInNautilus(path.DirName(), path.value());
break;
}
}
......@@ -164,12 +177,7 @@ void PlatformOpenVerifiedItem(const base::FilePath& path, OpenItemType type) {
void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::PostTaskAndReplyWithResult(
FROM_HERE,
{base::ThreadPool(), base::WithBaseSyncPrimitives(), base::MayBlock(),
base::TaskPriority::USER_BLOCKING},
base::BindOnce(&CheckNautilusIsDefault),
base::BindOnce(&ShowItem, profile, full_path));
ShowItemHelper::GetInstance().ShowItemInFolder(profile, full_path);
}
void OpenExternal(Profile* profile, const GURL& url) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment