1use std::{path::PathBuf, process::Command};
2
3use freedesktop_desktop_entry as fde;
4use mime::Mime;
5use url::Url;
6
7use crate::{Error, Result, exec::ExecParser};
8
9#[derive(Clone, Debug)]
11pub struct UrlHandlerApp {
12 pub appid: String,
13 pub name: String,
14 pub comment: Option<String>,
15 pub icon: fde::IconSource,
16 pub path: PathBuf,
17}
18
19impl UrlHandlerApp {
20 pub fn open_url(&self, url: Url) -> Result<u32> {
22 log::info!(
23 "Opening URL '{}' with application '{}'",
24 url,
25 self.path.display()
26 );
27
28 let locales = fde::get_languages_from_env();
29 let de = fde::DesktopEntry::from_path(self.path.clone(), Some(&locales))?;
30
31 let (cmd, args) = ExecParser::new(&de, &locales).parse_with_uris(&[url.as_str()])?;
32 log::debug!("Executing command: '{}' with args: {:?}", cmd, args);
33
34 let program = Command::new(cmd).args(args).spawn()?;
35
36 Ok(program.id())
37 }
38
39 pub fn handlers_for_scheme(
47 scheme: &str,
48 locales: Option<Vec<String>>,
49 search_paths: Option<Vec<PathBuf>>,
50 ) -> Result<Vec<Self>> {
51 let locales = locales.unwrap_or_else(fde::get_languages_from_env);
52 let search_paths = search_paths.unwrap_or_else(|| fde::default_paths().collect());
53
54 log::debug!(
55 "Searching for applications handling scheme '{}' in paths: {:?}",
56 scheme,
57 search_paths
58 );
59
60 let entries = fde::Iter::new(search_paths.into_iter()).entries(Some(&locales));
61 let scheme_handler_mime = format!("x-scheme-handler/{}", scheme)
62 .as_str()
63 .parse::<Mime>()?;
64
65 let apps = entries
66 .filter(|de| {
67 de.mime_type()
68 .is_some_and(|mime| mime.contains(&scheme_handler_mime.essence_str()))
69 })
70 .map(|entry| Self::from_desktop_entry(entry, &locales))
71 .collect::<Vec<_>>();
72
73 log::info!(
74 "Found {} applications with support for '{}'",
75 apps.len(),
76 scheme_handler_mime
77 );
78
79 if apps.is_empty() {
80 return Err(Error::NoHandlersFound(scheme.to_string()));
81 }
82
83 Ok(apps)
84 }
85
86 pub fn from_desktop_entry<L: AsRef<str>>(de: fde::DesktopEntry, locales: &[L]) -> Self {
93 let appid = de.appid.clone();
94 let name = de
95 .name(locales)
96 .map(|name| name.into())
97 .unwrap_or_else(|| appid.clone());
98
99 Self {
100 appid,
101 name,
102 comment: de.comment(locales).map(|comment| comment.into()),
103 icon: de
104 .icon()
105 .map(fde::IconSource::from_unknown)
106 .unwrap_or_default(),
107 path: de.path,
108 }
109 }
110
111 pub fn icon_path(&self, icon_size: u16) -> Option<std::path::PathBuf> {
112 log::debug!(
113 "Fetching icon for appid={} icon={:?}",
114 self.appid,
115 self.icon
116 );
117
118 match &self.icon {
119 fde::IconSource::Path(path) => Some(path.to_owned()),
120 fde::IconSource::Name(name) => freedesktop_icons::lookup(name)
121 .with_size(icon_size)
122 .with_cache()
123 .find(),
124 }
125 }
126}