blob: 02b3d52af70cc5d56c5fcc5821bf57bf593cb08e [file] [log] [blame]
//! When working on this, there are a couple of things to test:
//! * Works in Flatpak packages (in Flatpak packages the OpenURI interface is used,
//! because FileManager1 is not available)
//! * Works with relative file paths
//! * Works with directories (and highlights them)
//! * Weird paths work: paths with spaces, unicode characters, non-unicode characters (e.g. `"\u{01}"`)
//! * Path to non-existent file generates an error for both implementations.
use crate::OpenError;
use dbus::arg::messageitem::MessageItem;
use dbus::arg::{Append, Variant};
use dbus::blocking::Connection;
use std::fs::File;
use std::path::Path;
use std::time::Duration;
use std::{error, fmt, io};
use url::Url;
const DBUS_TIMEOUT: Duration = Duration::from_secs(5);
// We should prefer the OpenURI interface, because it correctly handles runtimes such as Flatpak.
// However, OpenURI was broken in the original version of the interface (it did not highlight the items).
// This version is still in use by some distributions, which would result in degraded functionality for some users.
// That's why we're first trying to use the FileManager1 interface, falling back to the OpenURI interface.
// Source: https://chromium-review.googlesource.com/c/chromium/src/+/3009959
pub(crate) fn reveal_with_dbus(path: &Path) -> Result<(), OpenError> {
let connection = Connection::new_session().map_err(dbus_to_open_error)?;
reveal_with_filemanager1(path, &connection)
.or_else(|_| reveal_with_open_uri_portal(path, &connection))
}
fn reveal_with_filemanager1(path: &Path, connection: &Connection) -> Result<(), OpenError> {
let uri = path_to_uri(path)?;
let proxy = connection.with_proxy(
"org.freedesktop.FileManager1",
"/org/freedesktop/FileManager1",
DBUS_TIMEOUT,
);
proxy
.method_call(
"org.freedesktop.FileManager1",
"ShowItems",
(vec![uri.as_str()], ""),
)
.map_err(dbus_to_open_error)
}
fn reveal_with_open_uri_portal(path: &Path, connection: &Connection) -> Result<(), OpenError> {
let file = File::open(path).map_err(OpenError::Io)?;
let proxy = connection.with_proxy(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
DBUS_TIMEOUT,
);
proxy
.method_call(
"org.freedesktop.portal.OpenURI",
"OpenDirectory",
("", file, empty_vardict()),
)
.map_err(dbus_to_open_error)
}
fn empty_vardict() -> impl Append {
dbus::arg::Dict::<&'static str, Variant<MessageItem>, _>::new(std::iter::empty())
}
fn path_to_uri(path: &Path) -> Result<Url, OpenError> {
let path = path.canonicalize().map_err(OpenError::Io)?;
Url::from_file_path(path).map_err(|_| uri_to_open_error())
}
fn uri_to_open_error() -> OpenError {
OpenError::Io(io::Error::new(
io::ErrorKind::InvalidInput,
FilePathToUriError,
))
}
fn dbus_to_open_error(error: dbus::Error) -> OpenError {
OpenError::Io(io::Error::new(io::ErrorKind::Other, error))
}
#[derive(Debug)]
struct FilePathToUriError;
impl fmt::Display for FilePathToUriError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "The given file path could not be converted to a URI")
}
}
impl error::Error for FilePathToUriError {}