diff --git a/Cargo.lock b/Cargo.lock index dcbc2f30..2240faaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6584,6 +6584,7 @@ dependencies = [ name = "tauri-plugin-opener" version = "2.0.0" dependencies = [ + "dunce", "open", "regex", "schemars", @@ -6594,6 +6595,7 @@ dependencies = [ "thiserror", "url", "urlpattern", + "windows 0.54.0", ] [[package]] @@ -7942,6 +7944,16 @@ dependencies = [ "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.56.0" @@ -7971,6 +7983,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.56.0" diff --git a/plugins/opener/Cargo.toml b/plugins/opener/Cargo.toml index f450e15d..bb3ecfab 100644 --- a/plugins/opener/Cargo.toml +++ b/plugins/opener/Cargo.toml @@ -38,3 +38,16 @@ open = { version = "5", features = ["shellexecute-on-windows"] } urlpattern = { workspace = true } regex = "1" url = { workspace = true } + +[target."cfg(windows)".dependencies] +dunce = { workspace = true } + +[target."cfg(windows)".dependencies.windows] +version = "0.54" +features = [ + "Win32_Foundation", + "Win32_UI_Shell_Common", + "Win32_UI_WindowsAndMessaging", + "Win32_System_Com", + "Win32_System_Registry", +] diff --git a/plugins/opener/src/error.rs b/plugins/opener/src/error.rs index 943305d6..50676df6 100644 --- a/plugins/opener/src/error.rs +++ b/plugins/opener/src/error.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use std::path::PathBuf; + use serde::{Serialize, Serializer}; #[derive(Debug, thiserror::Error)] @@ -19,6 +21,15 @@ pub enum Error { UnknownProgramName(String), #[error("Not allowed to open {0}")] NotAllowed(String), + /// API not supported on the current platform + #[error("API not supported on the current platform")] + UnsupportedPlatform, + #[error(transparent)] + #[cfg(windows)] + Win32Error(#[from] windows::core::Error), + /// Path doesn't have a parent. + #[error("Path doesn't have a parent: {0}")] + NoParent(PathBuf), } impl Serialize for Error { diff --git a/plugins/opener/src/lib.rs b/plugins/opener/src/lib.rs index 94d06851..5a40ec7e 100644 --- a/plugins/opener/src/lib.rs +++ b/plugins/opener/src/lib.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use std::path::Path; + use tauri::{ plugin::{Builder, TauriPlugin}, AppHandle, Manager, Runtime, @@ -19,10 +21,14 @@ mod error; mod open; mod scope; mod scope_entry; +mod show_item_in_dir; pub use error::Error; type Result = std::result::Result; +pub use open::{open, Program}; +pub use show_item_in_dir::show_item_in_dir; + pub struct Opener { #[allow(dead_code)] app: AppHandle, @@ -44,6 +50,10 @@ impl Opener { .run_mobile_plugin("open", path.into()) .map_err(Into::into) } + + pub fn show_item_in_dir>(&self, p: P) -> Result<()> { + show_item_in_dir::show_item_in_dir(p) + } } /// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the opener APIs. diff --git a/plugins/opener/src/show_item_in_dir.rs b/plugins/opener/src/show_item_in_dir.rs new file mode 100644 index 00000000..96719120 --- /dev/null +++ b/plugins/opener/src/show_item_in_dir.rs @@ -0,0 +1,120 @@ +use std::path::Path; +use std::path::PathBuf; + +/// Show +/// +/// ## Platform-specific: +/// +/// - **Android / iOS:** Unsupported. +pub fn show_item_in_dir>(p: P) -> crate::Result<()> { + let p = p.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::show_item_in_dir(p); + + #[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 show_item_in_dir(p: PathBuf) -> crate::Result<()> { + let file = dunce::simplified(&p); + + 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(PCWSTR::from_raw(dir.as_ptr())) }; + + let file_h = HSTRING::from(file); + let file_item = unsafe { ILCreateFromPathW(PCWSTR::from_raw(file_h.as_ptr())) }; + + unsafe { + if let Err(e) = SHOpenFolderAndSelectItems(dir_item, Some(&[file_item]), 0) { + if e.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 { + let is_dir = std::fs::metadata(file).map(|f| f.is_dir()).unwrap_or(false); + let mut info = SHELLEXECUTEINFOW { + cbSize: std::mem::size_of::() as _, + nShow: SW_SHOWNORMAL.0, + lpVerb: if is_dir { + w!("explore") + } else { + PCWSTR::null() + }, + lpClass: if is_dir { w!("folder") } else { PCWSTR::null() }, + lpFile: PCWSTR(file_h.as_ptr()), + ..std::mem::zeroed() + }; + + ShellExecuteExW(&mut info)?; + } + } + } + + 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 super::*; + + pub fn show_item_in_dir(p: PathBuf) -> crate::Result<()> { + Ok(()) + } +} + +#[cfg(target_os = "macos")] +mod imp { + use super::*; + + pub fn show_item_in_dir(p: PathBuf) -> crate::Result<()> {} +}