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 && !de.id().eq_ignore_ascii_case("kairo")
71 })
72 .map(|entry| Self::from_desktop_entry(entry, &locales))
73 .collect::<Vec<_>>();
74
75 log::info!(
76 "Found {} applications with support for '{}'",
77 apps.len(),
78 scheme_handler_mime
79 );
80
81 if apps.is_empty() {
82 return Err(Error::NoHandlersFound(scheme.to_string()));
83 }
84
85 Ok(apps)
86 }
87
88 pub fn from_desktop_entry<L: AsRef<str>>(de: fde::DesktopEntry, locales: &[L]) -> Self {
95 let appid = de.appid.clone();
96 let name = de
97 .name(locales)
98 .map(|name| name.into())
99 .unwrap_or_else(|| appid.clone());
100
101 Self {
102 appid,
103 name,
104 comment: de.comment(locales).map(|comment| comment.into()),
105 icon: de
106 .icon()
107 .map(fde::IconSource::from_unknown)
108 .unwrap_or_default(),
109 path: de.path,
110 }
111 }
112
113 pub fn icon_path(&self, icon_size: u16) -> Option<std::path::PathBuf> {
114 log::debug!(
115 "Fetching icon for appid={} icon={:?}",
116 self.appid,
117 self.icon
118 );
119
120 match &self.icon {
121 fde::IconSource::Path(path) => Some(path.to_owned()),
122 fde::IconSource::Name(name) => freedesktop_icons::lookup(name)
123 .with_size(icon_size)
124 .with_cache()
125 .find(),
126 }
127 }
128}