-
Notifications
You must be signed in to change notification settings - Fork 287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[shell] Show in finder/explorer #999
Comments
So the main problem here indeed is cross-platform support. Linux is basically ruled out, since On the other hand, windows and macos support is fairly easy: // These are rust commands, not tauri ones btw
// Windows
Command::new("explorer")
.args(["/select,", "\"path\\to\\file""]) // The comma after select is not a typo
.spawn()
.unwrap();
// Alternatively there is a win32 api for this iirc which needs to be used for longer file paths i think.
// macOS
Command::new( "open" )
.args(["-R", "path/to/file"]) // i don't have a mac so not 100% sure
.spawn()
.unwrap(); All that said, i think this would be better suited in a tauri plugin, especially because of linux. |
Thanks @FabianLars . Your solution would work even with the JS shell API as well right? I can try these out, and with a fallback (see below) this should work OK for linux as well.
IIUC there's a broader discussion about small core (with an extensive plugin system) vs big functional core, which I will not weigh in on here :) Just for reference, here's some prior art that could be good to know: Electron has a Here's their Linux version: So I guess the answer is it works, but not with a command only. However, they have a sensible fallback (just open the directory without selecting anything). For my case, this fallback is certainly better than omitting the feature altogether. |
Hopefully, yes.
Well, i don't want to dig in that discussion either (although generally i think it makes sense to not integrate everything into the core), but for me the main selling point of plugins is that it can be implemented regardless of tauri's release cycle and feature-freeze.
Ah yeah, totally forgot about the dbus option (probably cause we don't use that anywhere yet), but of course this is |
We use dbus indirectly in the notifications via notify-rust. |
A viable workaround command for Linux is
This command was suggested by Eduardo Trápani at this Stack Exchange question. The specification of the related D-Bus interface can be found here. I think that it is worth noting that, at least on my Debian Unstable KDE desktop, which uses Dolphin 22.08.1 as a file manager, the command fails for paths that contain a comma because |
https://gitlab.freedesktop.org/dbus/dbus/-/issues/76 there's even a patch to allow the change! I do wonder how electron/chromium does it. I tried to dig into their code base months ago and stopped as soon as I found out dbus was used in someway. |
Okay I wrote a generalized Tauri command (Rust 1.58+) with some leeway on Linux. tauri command codeuse std::fs;
#[cfg(target_os = "linux")]
use std::{fs::metadata, path::PathBuf};
use std::path::PathBuf;
use std::process::Command;
#[cfg(target_os = "linux")]
use fork::{daemon, Fork}; // dep: fork = "0.1"
#[tauri::command]
fn show_in_folder(path: String) {
#[cfg(target_os = "windows")]
{
Command::new("explorer")
.args(["/select,", &path]) // The comma after select is not a typo
.spawn()
.unwrap();
}
#[cfg(target_os = "linux")]
{
if path.contains(",") {
// see https://gitlab.freedesktop.org/dbus/dbus/-/issues/76
let new_path = match metadata(&path).unwrap().is_dir() {
true => path,
false => {
let mut path2 = PathBuf::from(path);
path2.pop();
path2.into_os_string().into_string().unwrap()
}
};
Command::new("xdg-open")
.arg(&new_path)
.spawn()
.unwrap();
} else {
if let Ok(Fork::Child) = daemon(false, false) {
Command::new("dbus-send")
.args(["--session", "--dest=org.freedesktop.FileManager1", "--type=method_call",
"/org/freedesktop/FileManager1", "org.freedesktop.FileManager1.ShowItems",
format!("array:string:\"file://{path}\"").as_str(), "string:\"\""])
.spawn()
.unwrap();
}
}
}
#[cfg(target_os = "macos")]
{
Command::new("open")
.args(["-R", &path])
.spawn()
.unwrap();
}
} main.rs maintauri::Builder::default()
.invoke_handler(tauri::generate_handler![YOUR_OTHER_DEFINED_CMDS, show_in_folder]) Client Side Usageimport { tauri } from '@tauri-apps/api';
async function show_in_folder(path) {
await tauri.invoke('show_in_folder', {path});
} |
@elibroftw for the windows implementation, I generally dislike this method as it has a weird behavior of expanding the navigation pane. I personally prefer to use the raw win32 API |
I've used this command multiple times with my music player and not once has it expanded the navigation panel. The github desktop implementation however always expands the navigation panel. Yes the native code is probably better but this is a hack. |
@elibroftw, when I try to compile your code, I get the following error: error: cannot find macro `__cmd__OTHER_CMDS` in this scope Any ideas? |
@aleph1 Remove |
2024 Update.I encountered an error using Command::new on Linux Mint, so here is the implementation via the dbus crate. I'm going to try to create a PR to the Tauri shell API during reading week. The DX and UX can be better for everyone. Cargo.toml[target.'cfg(target_os = "linux")'.dependencies]
dbus = "0.9" util_commands.rsuse std::process::Command;
// State is used by linux
use tauri::{Manager, State};
#[cfg(not(target_os = "windows"))]
use std::path::PathBuf;
#[cfg(target_os = "linux")]
use crate::DbusState;
#[cfg(target_os = "linux")]
use std::time::Duration;
#[cfg(target_os = "linux")]
#[tauri::command]
pub fn show_item_in_folder(path: String, dbus_state: State<DbusState>) -> Result<(), String> {
let dbus_guard = dbus_state.0.lock().map_err(|e| e.to_string())?;
// see https://gitlab.freedesktop.org/dbus/dbus/-/issues/76
if dbus_guard.is_none() || path.contains(",") {
let mut path_buf = PathBuf::from(&path);
let new_path = match path_buf.is_dir() {
true => path,
false => {
path_buf.pop();
path_buf.into_os_string().into_string().unwrap()
}
};
Command::new("xdg-open")
.arg(&new_path)
.spawn()
.map_err(|e| format!("{e:?}"))?;
} else {
// https://docs.rs/dbus/latest/dbus/
let dbus = dbus_guard.as_ref().unwrap();
let proxy = dbus.with_proxy(
"org.freedesktop.FileManager1",
"/org/freedesktop/FileManager1",
Duration::from_secs(5),
);
let (_,): (bool,) = proxy
.method_call(
"org.freedesktop.FileManager1",
"ShowItems",
(vec![format!("file://{path}")], ""),
)
.map_err(|e| e.to_string())?;
}
Ok(())
}
#[cfg(not(target_os = "linux"))]
#[tauri::command]
pub fn show_item_in_folder(path: String) -> Result<(), String> {
#[cfg(target_os = "windows")]
{
Command::new("explorer")
.args(["/select,", &path]) // The comma after select is not a typo
.spawn()
.map_err(|e| e.to_string())?;
}
#[cfg(target_os = "macos")]
{
let path_buf = PathBuf::from(&path);
if path_buf.is_dir() {
Command::new("open")
.args([&path])
.spawn()
.map_err(|e| e.to_string())?;
} else {
Command::new("open")
.args(["-R", &path])
.spawn()
.map_err(|e| e.to_string())?;
}
}
Ok(())
} main.rs#[cfg(target_os = "linux")]
pub struct DbusState(Mutex<Option<dbus::blocking::SyncConnection>>);
mod show_in_folder;
use show_in_folder::{show_item_in_folder};
// in main ...
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
show_item_in_folder
])
.setup(|app| {
#[cfg(target_os = "linux")]
app.manage(DbusState(Mutex::new(dbus::blocking::SyncConnection::new_session().ok())));
}) JavaScript Usageimport { tauri } from '@tauri-apps/api';
async function show_in_folder(path) {
await tauri.invoke('show_in_folder', {path});
} English OS localization strings I'm using in frontend. Looking for thoughts "revealFile_win32": "Show in File Explorer",
"revealFile_darwin": "Reveal in Finder",
"revealFile_linux": "Show in folder", |
.method_call(
"org.freedesktop.FileManager1",
"ShowItems",
(vec![path], ""),
) should be .method_call(
"org.freedesktop.FileManager1",
"ShowItems",
(vec![format!("file://{}", path)], ""),
) |
I used to use file:// but still yet to find a case where it works with file:// but not without. I'll change it though. |
Ah makes sense... For me, it doesn't work without the |
Docs say it must be a URI so I was just playing around with if that was a hard requirement or not. |
For anyone who look for open file selected on you can use the crate showfile Setup cargo add showfile Usage fn main() {
let path = "/some/path/to/file";
showfile::show_path_in_file_manager(path);
} It works with
and |
Describe the problem
The
shell.open
API can be used to open local files (in their respective app) or opening a directory in the finder/explorer-equivalent. In addition to this, I need to "show in finder", i.e. open a directory with a set of files/directories selected.Describe the solution you'd like
At a minimum, a function that accepts one file or directory and opens its containing directory with that file/dir selected. Ideally, multiple files can be provided, but I think this may be harder to do cross-platform.
Alternatives considered
No response
Additional context
If API additions are difficult, I'm also interested in workarounds.
The text was updated successfully, but these errors were encountered: