cosmic_files/
mime_icon.rs1use cosmic::widget::icon;
4use mime_guess::Mime;
5use rustc_hash::FxHashMap;
6use std::fs;
7use std::path::Path;
8use std::sync::{LazyLock, Mutex};
9
10pub const FALLBACK_MIME_ICON: &str = "text-x-generic";
11
12#[derive(Debug, Eq, Hash, PartialEq)]
13struct MimeIconKey {
14 mime: Mime,
15 size: u16,
16}
17
18#[derive(Default)]
19pub struct MimeIconCache {
20 #[allow(dead_code)]
21 cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
22 #[cfg(unix)]
23 pub shared_mime_info: xdg_mime::SharedMimeInfo,
24}
25
26impl MimeIconCache {
27 #[cfg(not(unix))]
28 pub fn get(&mut self, _key: MimeIconKey) -> Option<icon::Handle> {
29 None
30 }
31
32 #[cfg(unix)]
33 fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
34 self.cache
35 .entry(key)
36 .or_insert_with_key(|key| {
37 #[cfg(unix)]
38 let mut icon_names = self.shared_mime_info.lookup_icon_names(&key.mime);
39 #[cfg(not(unix))]
40 let mut icon_names = match guess_generic_icon_name(&key.mime) {
41 Some(name) => vec![name],
42 None => vec![],
43 };
44 if icon_names.is_empty() {
45 return None;
46 }
47 let icon_name = icon_names.remove(0);
48 let mut named = icon::from_name(icon_name).prefer_svg(true).size(key.size);
49 if !icon_names.is_empty() {
50 let fallback_names =
51 icon_names.into_iter().map(std::borrow::Cow::from).collect();
52 named = named.fallback(Some(icon::IconFallback::Names(fallback_names)));
53 }
54 Some(named.handle())
55 })
56 .clone()
57 }
58}
59
60pub static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
61 LazyLock::new(|| Mutex::new(MimeIconCache::default()));
62
63#[cfg(not(unix))]
64pub fn mime_for_path(
65 path: impl AsRef<Path>,
66 _metadata_opt: Option<&fs::Metadata>,
67 _remote: bool,
68) -> Mime {
69 mime_guess::from_path(path).first_or_octet_stream()
70}
71
72#[cfg(unix)]
73pub fn mime_for_path(
74 path: impl AsRef<Path>,
75 _metadata_opt: Option<&fs::Metadata>,
76 _remote: bool,
77) -> Mime {
78 let path = path.as_ref();
79
80 #[cfg(unix)]
81 {
82 let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
83 let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type();
85 gb.zero_size(false);
86 if remote {
87 if let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) {
88 gb.file_name(file_name);
89 }
90 } else {
91 gb.path(path);
92 }
93 if let Some(metadata) = metadata_opt {
94 gb.metadata(metadata.clone());
95 }
96 let guess = gb.guess();
97 let guessed_mime = guess.mime_type();
98
99 fn is_special_mime(mime: &Mime) -> bool {
102 *mime == "inode/directory"
103 || *mime == "inode/symlink"
104 || *mime == "application/x-zerosize"
105 }
106
107 if guess.uncertain() && (remote || !is_special_mime(guessed_mime)) {
111 mime_guess::from_path(path).first_or_octet_stream()
113 } else {
114 guessed_mime.clone()
115 }
116 }
117 #[cfg(not(unix))]
118 {
119 mime_guess::from_path(path).first_or_octet_stream()
120 }
121}
122
123#[allow(dead_code)]
124fn guess_generic_icon_name(mime: &Mime) -> Option<&'static str> {
125 let ty = mime.type_().as_str();
126 let sub = mime.subtype().as_str();
127
128 match ty {
129 "text" => Some("text-x-generic"),
130 "image" => Some("image-x-generic"),
131 "audio" => Some("audio-x-generic"),
132 "video" => Some("video-x-generic"),
133 "application" => match sub {
134 "pdf" => Some("application-pdf"),
135 "zip" | "x-7z-compressed" | "x-rar-compressed" | "x-xz" | "x-bzip2" => {
136 Some("package-x-generic")
137 }
138 "json" | "xml" | "x-yaml" => Some("text-x-generic"),
139 _ => Some("application-x-executable"), },
141 _ => None,
142 }
143}
144
145pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle {
146 let mut mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
147 match mime_icon_cache.get(MimeIconKey { mime, size }) {
148 Some(handle) => handle,
149 None => icon::from_name(FALLBACK_MIME_ICON)
150 .prefer_svg(true)
151 .size(size)
152 .handle(),
153 }
154}
155
156#[cfg(not(unix))]
157pub fn parent_mime_types(_mime: &Mime) -> Option<Vec<Mime>> {
158 None
159}
160
161#[cfg(unix)]
162pub fn parent_mime_types(mime: &Mime) -> Option<Vec<Mime>> {
163 let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
164 mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
165}