You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tauri-plugins-workspace/plugins/opener/src/reveal_item_in_dir.rs

197 lines
6.0 KiB

// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::Path;
/// Reveal a path the system's default explorer.
///
/// ## Platform-specific:
///
/// - **Android / iOS:** Unsupported.
pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
let path = path.as_ref().canonicalize()?;
#[cfg(any(
windows,
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
return imp::reveal_item_in_dir(&path);
#[cfg(not(any(
windows,
target_os = "macos",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))]
Err(crate::Error::UnsupportedPlatform)
}
#[cfg(windows)]
mod imp {
use super::*;
use windows::{
core::{w, HSTRING, PCWSTR},
Win32::{
Foundation::ERROR_FILE_NOT_FOUND,
System::Com::CoInitialize,
UI::{
Shell::{
ILCreateFromPathW, ILFree, SHOpenFolderAndSelectItems, ShellExecuteExW,
SHELLEXECUTEINFOW,
},
WindowsAndMessaging::SW_SHOWNORMAL,
},
},
};
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
let file = dunce::simplified(path);
let _ = unsafe { CoInitialize(None) };
let dir = file
.parent()
.ok_or_else(|| crate::Error::NoParent(file.to_path_buf()))?;
let dir = HSTRING::from(dir);
let dir_item = unsafe { ILCreateFromPathW(&dir) };
let file_h = HSTRING::from(file);
let file_item = unsafe { ILCreateFromPathW(&file_h) };
unsafe {
if let Err(e) = SHOpenFolderAndSelectItems(dir_item, Some(&[file_item]), 0) {
// from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302
// On some systems, the above call mysteriously fails with "file not
// found" even though the file is there. In these cases, ShellExecute()
// seems to work as a fallback (although it won't select the file).
if e.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 {
let is_dir = file.is_dir();
let mut info = SHELLEXECUTEINFOW {
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as _,
nShow: SW_SHOWNORMAL.0,
lpFile: PCWSTR(dir.as_ptr()),
lpClass: if is_dir { w!("folder") } else { PCWSTR::null() },
lpVerb: if is_dir {
w!("explore")
} else {
PCWSTR::null()
},
..std::mem::zeroed()
};
ShellExecuteExW(&mut info).inspect_err(|_| {
ILFree(Some(dir_item));
ILFree(Some(file_item));
})?;
}
}
}
unsafe {
ILFree(Some(dir_item));
ILFree(Some(file_item));
}
Ok(())
}
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
mod imp {
use std::collections::HashMap;
use super::*;
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
let connection = zbus::blocking::Connection::session()?;
reveal_with_filemanager1(path, &connection)
.or_else(|_| reveal_with_open_uri_portal(path, &connection))
}
fn reveal_with_filemanager1(
path: &Path,
connection: &zbus::blocking::Connection,
) -> crate::Result<()> {
let uri = url::Url::from_file_path(path)
.map_err(|_| crate::Error::FailedToConvertPathToFileUrl)?;
#[zbus::proxy(
interface = "org.freedesktop.FileManager1",
default_service = "org.freedesktop.FileManager1",
default_path = "/org/freedesktop/FileManager1"
)]
trait FileManager1 {
async fn ShowItems(&self, name: Vec<&str>, arg2: &str) -> crate::Result<()>;
}
let proxy = FileManager1ProxyBlocking::new(connection)?;
proxy.ShowItems(vec![uri.as_str()], "")
}
fn reveal_with_open_uri_portal(
path: &Path,
connection: &zbus::blocking::Connection,
) -> crate::Result<()> {
let uri = url::Url::from_file_path(path)
.map_err(|_| crate::Error::FailedToConvertPathToFileUrl)?;
#[zbus::proxy(
interface = "org.freedesktop.portal.Desktop",
default_service = "org.freedesktop.portal.OpenURI",
default_path = "/org/freedesktop/portal/desktop"
)]
trait PortalDesktop {
async fn OpenDirectory(
&self,
arg1: &str,
name: &str,
arg3: HashMap<&str, &str>,
) -> crate::Result<()>;
}
let proxy = PortalDesktopProxyBlocking::new(connection)?;
proxy.OpenDirectory("", uri.as_str(), HashMap::new())
}
}
#[cfg(target_os = "macos")]
mod imp {
use super::*;
use objc2_app_kit::NSWorkspace;
use objc2_foundation::{NSArray, NSString, NSURL};
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
unsafe {
let path = path.to_string_lossy();
let path = NSString::from_str(&path);
let urls = vec![NSURL::fileURLWithPath(&path)];
let urls = NSArray::from_retained_slice(&urls);
let workspace = NSWorkspace::new();
workspace.activateFileViewerSelectingURLs(&urls);
}
Ok(())
}
}