diff --git a/Cargo.lock b/Cargo.lock index fb3eda649..0e5d34288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,9 +2174,9 @@ checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" [[package]] name = "xdg-utils" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c275db8f4249f393a5c4de777ebeba37a5859bc2209a30fb66dc566dddd3ce" +checksum = "db9fefe62d5969721e2cfc529e6a760901cc0da422b6d67e7bfd18e69490dba6" [[package]] name = "xml-rs" diff --git a/melib/Cargo.toml b/melib/Cargo.toml index f14c5c00b..ab773f681 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -47,7 +47,7 @@ smol = "1.0.0" async-stream = "0.2.1" base64 = { version = "0.12.3", optional = true } flate2 = { version = "1.0.16", optional = true } -xdg-utils = "0.3.0" +xdg-utils = "^0.4.0" [features] default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard", "sqlite3", "smtp", "deflate_compression"] diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index dd97b1b70..ff2e8cc15 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -1910,17 +1910,21 @@ impl Component for MailView { } ContentType::Other { .. } => { let attachment_type = attachment.mime_type(); - let binary = query_default_app(&attachment_type); let filename = attachment.filename(); - if let Ok(binary) = binary { + if let Ok(command) = query_default_app(&attachment_type) { let p = create_temp_file( &decode(attachment, None), filename.as_ref().map(|s| s.as_str()), None, true, ); - match Command::new(&binary) - .arg(p.path()) + let (exec_cmd, argument) = desktop_exec_to_command( + &command, + p.path.display().to_string(), + false, + ); + match Command::new(&exec_cmd) + .arg(&argument) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() @@ -1932,9 +1936,8 @@ impl Component for MailView { Err(err) => { context.replies.push_back(UIEvent::StatusEvent( StatusEvent::DisplayMessage(format!( - "Failed to start {}: {}", - binary.display(), - err + "Failed to start `{} {}`: {}", + &exec_cmd, &argument, err )), )); } @@ -2508,3 +2511,50 @@ fn save_attachment(path: &std::path::Path, bytes: &[u8]) -> Result<()> { f.flush()?; Ok(()) } + +fn desktop_exec_to_command(command: &str, path: String, is_url: bool) -> (String, String) { + /* Purge unused field codes */ + let command = command + .replace("%i", "") + .replace("%c", "") + .replace("%k", ""); + if let Some(pos) = command.find("%f").or_else(|| command.find("%F")) { + (command[0..pos].trim().to_string(), path) + } else if let Some(pos) = command.find("%u").or_else(|| command.find("%U")) { + if is_url { + (command[0..pos].trim().to_string(), path) + } else { + ( + command[0..pos].trim().to_string(), + format!("file://{}", path), + ) + } + } else { + (command, path) + } +} + +/* +#[test] +fn test_desktop_exec() { + for cmd in [ + "ristretto %F", + "/usr/lib/firefox-esr/firefox-esr %u", + "/usr/bin/vlc --started-from-file %U", + "zathura %U", + ] + .iter() + { + println!( + "cmd = {} output = {:?}, is_url = false", + cmd, + desktop_exec_to_command(cmd, "/tmp/file".to_string(), false) + ); + println!( + "cmd = {} output = {:?}, is_url = true", + cmd, + desktop_exec_to_command(cmd, "www.example.com".to_string(), true) + ); + } +} +*/ diff --git a/src/components/mail/view/envelope.rs b/src/components/mail/view/envelope.rs index d84776570..897924706 100644 --- a/src/components/mail/view/envelope.rs +++ b/src/components/mail/view/envelope.rs @@ -433,43 +433,55 @@ impl Component for EnvelopeView { )); return true; } - ContentType::Other { ref name, .. } => { + ContentType::Other { .. } => { let attachment_type = u.mime_type(); - let binary = query_default_app(&attachment_type); - if let Ok(binary) = binary { + let filename = u.filename(); + if let Ok(command) = query_default_app(&attachment_type) { let p = create_temp_file( &decode(u, None), - name.as_ref().map(String::as_str), + filename.as_ref().map(|s| s.as_str()), None, true, ); - match Command::new(&binary) - .arg(p.path()) + let (exec_cmd, argument) = super::desktop_exec_to_command( + &command, + p.path.display().to_string(), + false, + ); + match Command::new(&exec_cmd) + .arg(&argument) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() { Ok(child) => { - context.children.push(child); context.temp_files.push(p); + context.children.push(child); } Err(err) => { context.replies.push_back(UIEvent::StatusEvent( StatusEvent::DisplayMessage(format!( - "Failed to start {}: {}", - binary.display(), - err + "Failed to start `{} {}`: {}", + &exec_cmd, &argument, err )), )); } } } else { context.replies.push_back(UIEvent::StatusEvent( - StatusEvent::DisplayMessage(format!( - "Couldn't find a default application for type {}", - attachment_type - )), - )); + StatusEvent::DisplayMessage(if let Some(filename) = filename.as_ref() { + format!( + "Couldn't find a default application for file {} (type {})", + filename, + attachment_type + ) + } else { + format!( + "Couldn't find a default application for type {}", + attachment_type + ) + }), + )); return true; } } diff --git a/src/components/mail/view/html.rs b/src/components/mail/view/html.rs index 1bc02e12d..873d0cae6 100644 --- a/src/components/mail/view/html.rs +++ b/src/components/mail/view/html.rs @@ -131,11 +131,12 @@ impl Component for HtmlView { } if let UIEvent::Input(Key::Char('v')) = event { - let binary = query_default_app("text/html"); - if let Ok(binary) = binary { + if let Ok(command) = query_default_app("text/html") { let p = create_temp_file(&self.bytes, None, None, true); - match Command::new(&binary) - .arg(p.path()) + let (exec_cmd, argument) = + super::desktop_exec_to_command(&command, p.path.display().to_string(), false); + match Command::new(&exec_cmd) + .arg(&argument) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() @@ -147,9 +148,8 @@ impl Component for HtmlView { Err(err) => { context.replies.push_back(UIEvent::StatusEvent( StatusEvent::DisplayMessage(format!( - "Failed to start {}: {}", - binary.display(), - err + "Failed to start `{} {}`: {}", + &exec_cmd, &argument, err )), )); }