1#[cfg(feature = "graphics")]
21pub fn get_embedded_logo(distro: Option<&str>) -> Option<&'static [u8]> {
22 let d = distro.map(|s| s.to_lowercase());
23 match d.as_deref() {
24 Some("arch") => Some(include_bytes!("../assets/logos/arch.png")),
25 Some("debian") => Some(include_bytes!("../assets/logos/debian.png")),
26 Some("fedora") => Some(include_bytes!("../assets/logos/fedora.png")),
27 Some("nixos") => Some(include_bytes!("../assets/logos/nixos.png")),
28 Some("ubuntu") => Some(include_bytes!("../assets/logos/ubuntu.png")),
29 Some("pop") => Some(include_bytes!("../assets/logos/pop.png")),
30 Some("manjaro") => Some(include_bytes!("../assets/logos/manjaro.png")),
31 Some("endeavouros") => Some(include_bytes!("../assets/logos/endeavouros.png")),
32 Some("opensuse") | Some("opensuse-leap") | Some("opensuse-tumbleweed") => {
33 Some(include_bytes!("../assets/logos/opensuse.png"))
34 }
35 Some("macos") => Some(include_bytes!("../assets/logos/macos.png")),
36 Some("windows") => Some(include_bytes!("../assets/logos/windows.png")),
37 _ => Some(include_bytes!("../assets/logos/tux.png")),
38 }
39}
40
41#[cfg(not(feature = "graphics"))]
43pub fn get_embedded_logo(_distro: Option<&str>) -> Option<&'static [u8]> {
44 Some(include_bytes!("../assets/logos/tux.png"))
45}
46
47pub fn detect_distro() -> Option<String> {
49 #[cfg(target_os = "macos")]
50 {
51 Some("macos".to_string())
52 }
53 #[cfg(target_os = "windows")]
54 {
55 Some("windows".to_string())
56 }
57 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
58 {
59 if let Ok(content) = std::fs::read_to_string("/etc/os-release") {
60 for line in content.lines() {
61 if line.starts_with("ID=") {
62 let id = line.trim_start_matches("ID=").trim_matches('"');
63 return Some(id.to_string());
64 }
65 }
66 }
67 None
68 }
69}
70
71pub fn get_ascii_logo(distro: Option<&str>) -> Vec<String> {
75 let d = distro.map(|s| s.to_lowercase());
76
77 match d.as_deref() {
78 Some("arch") => vec![
80 " -`".to_string(),
81 " .o+`".to_string(),
82 " `ooo/".to_string(),
83 " `+oooo:".to_string(),
84 " `+oooooo:".to_string(),
85 " -+oooooo+:".to_string(),
86 " `/:-:++oooo+:".to_string(),
87 " `/++++/+++++++:".to_string(),
88 " `/++++++++++++++:".to_string(),
89 " `/+++ooooooooooooo/`".to_string(),
90 " ./ooosssso++osssssso+`".to_string(),
91 " .oossssso-````/ossssss+`".to_string(),
92 " -osssssso. :ssssssso.".to_string(),
93 " :osssssss/ osssso+++.".to_string(),
94 " /ossssssss/ +ssssooo/-".to_string(),
95 " `/ossssso+/:- -:/+osssso+-".to_string(),
96 " `+sso+:-` `.-/+oso:".to_string(),
97 "`++:. `-/+/".to_string(),
98 ".` `".to_string(),
99 ],
100
101 Some("debian") => vec![
103 " _,met$$$$$$$$$$gg.".to_string(),
104 " ,g$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P.".to_string(),
105 " ,g$$$$P\"\" \"\"\"Y$$$$.\".".to_string(),
106 " ,$$$$P' `$$$$$$.".to_string(),
107 "',$$$$P ,ggs. `$$$$b:".to_string(),
108 "`d$$$$' ,$P\"' . $$$$$$".to_string(),
109 " $$$$P d$' , $$$$P".to_string(),
110 " $$$$: $$$. - ,d$$$$'".to_string(),
111 " $$$$; Y$b._ _,d$P'".to_string(),
112 " Y$$$$. .`\"Y$$$$$$$$P\"'".to_string(),
113 " `$$$$b \"-.__".to_string(),
114 " `Y$$$$b".to_string(),
115 " `Y$$$$.".to_string(),
116 " `$$$$b.".to_string(),
117 " `Y$$$$b.".to_string(),
118 " `\"Y$$b._".to_string(),
119 " `\"\"\"\"".to_string(),
120 ],
121
122 Some("fedora") => vec![
124 " .',;::::;,'.".to_string(),
125 " .';:cccccccccccc:;,.".to_string(),
126 " .;cccccccccccccccccccccc;.".to_string(),
127 " .:cccccccccccccccccccccc:.".to_string(),
128 " .;ccccccccccccc;.:dddl:.;ccccccc;.".to_string(),
129 " .:ccccccccccccc;OWMKOOXMWd;ccccccc:.".to_string(),
130 ".:ccccccccccccc;KMMc;cc;xMMc;ccccccc:.".to_string(),
131 ",cccccccccccccc;MMM.;cc;;WW:;cccccccc,".to_string(),
132 ":cccccccccccccc;MMM.;cccccccccccccccc:".to_string(),
133 ":ccccccc;oxOOOo;MMM000k.;cccccccccccc:".to_string(),
134 "cccccc;0MMKxdd:;MMMkddc.;cccccccccccc;".to_string(),
135 "ccccc;XMO';cccc;MMM.;cccccccccccccccc'".to_string(),
136 "ccccc;MMo;ccccc;MMW.;ccccccccccccccc;".to_string(),
137 "ccccc;0MNc.ccc.xMMd;ccccccccccccccc;".to_string(),
138 "cccccc;dNMWXXXWM0:;cccccccccccccc:,".to_string(),
139 "cccccccc;.:odl:.;cccccccccccccc:,.".to_string(),
140 "ccccccccccccccccccccccccccccc:'.".to_string(),
141 ":ccccccccccccccccccccccc:;,..".to_string(),
142 ],
143
144 Some("nixos") => vec![
146 " ::::. ':::::".to_string(),
147 " ':::::::::. ':::::::::".to_string(),
148 " .::::::::::::: ::::::::::::.".to_string(),
149 " :::::::::::' ::::::::::::".to_string(),
150 " .::::::::::' :::::::::::.".to_string(),
151 " :::::::::: :::::::::::".to_string(),
152 " ::::::::: :::::::::::".to_string(),
153 " :::::::: ::::::::::".to_string(),
154 " '::::::. '::::::::'".to_string(),
155 " '::::: '::::::'".to_string(),
156 " '::: '::::'".to_string(),
157 " ': ':".to_string(),
158 ],
159
160 Some("ubuntu") => vec![
162 " ....".to_string(),
163 " ....,,:::::ccccclllclllcc:::;,,....".to_string(),
164 " ..,;::cccllllllllllllllllllllllllcc::;..".to_string(),
165 " ..,;::cccllllllllllllllllllllllllllllcc::;..".to_string(),
166 " ..,;::cccllllllllllllllllllllllllllllllllcc::;..".to_string(),
167 " ..,;::cccllllllllllllllllllllllllllllllllllcc::;..".to_string(),
168 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
169 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
170 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
171 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
172 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
173 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
174 ],
175
176 Some("pop") => vec![
177 "\x1b[37m /////////////\x1b[0m".to_string(),
178 "\x1b[37m /////////////////////\x1b[0m".to_string(),
179 "\x1b[37m ///////\x1b[36m*767\x1b[37m////////////////\x1b[0m".to_string(),
180 "\x1b[37m //////\x1b[36m7676767676*\x1b[37m//////////////\x1b[0m".to_string(),
181 "\x1b[37m /////\x1b[36m76767\x1b[37m//\x1b[36m7676767\x1b[37m//////////////\x1b[0m".to_string(),
182 "\x1b[37m /////\x1b[36m767676\x1b[37m///\x1b[36m*76767\x1b[37m///////////////\x1b[0m".to_string(),
183 "\x1b[37m ///////\x1b[36m767676\x1b[37m///\x1b[36m76767\x1b[37m.\x1b[36m///7676*\x1b[37m///////\x1b[0m".to_string(),
184 "\x1b[37m/////////\x1b[36m767676\x1b[37m//\x1b[36m76767\x1b[37m///\x1b[36m767676\x1b[37m////////\x1b[0m".to_string(),
185 "\x1b[37m//////////\x1b[36m767676767676\x1b[37m////\x1b[36m76767\x1b[37m/////////\x1b[0m".to_string(),
186 "\x1b[37m///////////\x1b[36m767676767\x1b[37m//////\x1b[36m7676\x1b[37m//////////\x1b[0m".to_string(),
187 "\x1b[37m////////////,\x1b[36m7676\x1b[37m,///////\x1b[36m767\x1b[37m///////////\x1b[0m".to_string(),
188 "\x1b[37m/////////////*\x1b[36m7676\x1b[37m///////\x1b[36m76\x1b[37m////////////\x1b[0m".to_string(),
189 "\x1b[37m///////////////\x1b[36m7676\x1b[37m////////////////////\x1b[0m".to_string(),
190 "\x1b[37m ///////////////\x1b[36m7676\x1b[37m///\x1b[36m767\x1b[37m////////////\x1b[0m".to_string(),
191 "\x1b[37m //////////////////////\x1b[36m'\x1b[37m////////////\x1b[0m".to_string(),
192 "\x1b[37m //////\x1b[36m.7676767676767676767,\x1b[37m//////\x1b[0m".to_string(),
193 "\x1b[37m /////\x1b[36m767676767676767676767\x1b[37m/////\x1b[0m".to_string(),
194 "\x1b[37m ///////////////////////////\x1b[0m".to_string(),
195 "\x1b[37m /////////////////////\x1b[0m".to_string(),
196 "\x1b[37m /////////////\x1b[0m".to_string(),
197 ],
198
199 Some("manjaro") => vec![
200 "\x1b[32m██████████████████ ████████\x1b[0m".to_string(),
201 "\x1b[32m██████████████████ ████████\x1b[0m".to_string(),
202 "\x1b[32m██████████████████ ████████\x1b[0m".to_string(),
203 "\x1b[32m██████████████████ ████████\x1b[0m".to_string(),
204 "\x1b[32m████████ ████████\x1b[0m".to_string(),
205 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
206 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
207 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
208 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
209 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
210 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
211 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
212 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
213 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
214 "\x1b[32m████████ ████████ ████████\x1b[0m".to_string(),
215 ],
216
217 Some("endeavouros") => vec![
218 " \x1b[35m./\x1b[31mo\x1b[34m.\x1b[0m".to_string(),
219 " \x1b[35m./\x1b[31msssso\x1b[34m-\x1b[0m".to_string(),
220 " \x1b[35m`:\x1b[31mosssssss+\x1b[34m-\x1b[0m".to_string(),
221 " \x1b[35m`:+\x1b[31msssssssssso\x1b[34m/.\x1b[0m".to_string(),
222 " \x1b[35m`-/o\x1b[31mssssssssssssso\x1b[34m/.\x1b[0m".to_string(),
223 " \x1b[35m`-/+\x1b[31msssssssssssssssso\x1b[34m+:`\x1b[0m".to_string(),
224 " \x1b[35m`-:/+\x1b[31msssssssssssssssssso\x1b[34m+/.\x1b[0m".to_string(),
225 " \x1b[35m`.://o\x1b[31msssssssssssssssssssso\x1b[34m++-\x1b[0m".to_string(),
226 " \x1b[35m.://+\x1b[31mssssssssssssssssssssssso\x1b[34m++:\x1b[0m".to_string(),
227 " \x1b[35m.:///o\x1b[31mssssssssssssssssssssssssso\x1b[34m++:\x1b[0m".to_string(),
228 " \x1b[35m`:////\x1b[31mssssssssssssssssssssssssssso\x1b[34m+++.\x1b[0m".to_string(),
229 "\x1b[35m`-////+\x1b[31mssssssssssssssssssssssssssso\x1b[34m++++-\x1b[0m".to_string(),
230 " \x1b[35m`..-+\x1b[31moosssssssssssssssssssssssso\x1b[34m+++++/`\x1b[0m".to_string(),
231 " \x1b[34m./++++++++++++++++++++++++++++++/:.\x1b[0m".to_string(),
232 " \x1b[34m`:::::::::::::::::::::::::------``\x1b[0m".to_string(),
233 " \x1b[34m`:::::::::::::::::::::::::------``\x1b[0m".to_string(),
234 ],
235
236 Some("opensuse") | Some("opensuse-leap") | Some("opensuse-tumbleweed") => vec![
237 "\x1b[32m .;ldkO0000Okdl;.\x1b[0m".to_string(),
238 "\x1b[32m .;d00xl:^''''''^:ok00d;.\x1b[0m".to_string(),
239 "\x1b[32m .d00l' 'o00d.\x1b[0m".to_string(),
240 "\x1b[32m .d0Kd'\x1b[37m Okxol:;,. \x1b[32m:O0d\x1b[0m".to_string(),
241 "\x1b[32m .OK\x1b[37mKKK0kOKKKKKKKKKKOxo:,\x1b[32m lKO.\x1b[0m".to_string(),
242 "\x1b[32m ,0K\x1b[37mKKKKKKKKKKKKKKK0P^\x1b[32m,,,\x1b[37m^dx:\x1b[32m ;00,\x1b[0m".to_string(),
243 "\x1b[32m.OK\x1b[37mKKKKKKKKKKKKKKKk'\x1b[32m.oOPPb.\x1b[37m'0k.\x1b[32m cKO.\x1b[0m".to_string(),
244 "\x1b[32m:KK\x1b[37mKKKKKKKKKKKKKKK: \x1b[32mkKx..dd \x1b[37mlKd\x1b[32m 'OK:\x1b[0m".to_string(),
245 "\x1b[32mdKK\x1b[37mKKKKKKKKKOx0KKKd \x1b[32m^0KKKO' \x1b[37mkKKc\x1b[32m dKd\x1b[0m".to_string(),
246 "\x1b[32mdKK\x1b[37mKKKKKKKKKK;.;oOKx,..\x1b[32m^\x1b[37m..;kKKK0.\x1b[32m dKd\x1b[0m".to_string(),
247 "\x1b[32m:KK\x1b[37mKKKKKKKKKK0o;...^cdxxOK0O/^^' \x1b[32m.0K:\x1b[0m".to_string(),
248 "\x1b[32m kKK\x1b[37mKKKKKKKKKKKKK0x;,,......,;od \x1b[32mlKk\x1b[0m".to_string(),
249 "\x1b[32m '0K\x1b[37mKKKKKKKKKKKKKKKKKKKK00KKOo^ \x1b[32mc00'\x1b[0m".to_string(),
250 "\x1b[32m 'kK\x1b[37mKKOxddxkOO00000Okxoc;'' \x1b[32m.dKk'\x1b[0m".to_string(),
251 "\x1b[32m l0Ko. .c00l'\x1b[0m".to_string(),
252 "\x1b[32m 'l0Kk:. .;xK0l'\x1b[0m".to_string(),
253 "\x1b[32m 'lkK0xl:;,,,,;:ldO0kl'\x1b[0m".to_string(),
254 "\x1b[32m '^:ldxkkkkxdl:^'\x1b[0m".to_string(),
255 ],
256
257 Some("macos") => vec![
258 "\x1b[32m c.'\x1b[0m".to_string(),
259 "\x1b[32m ,xNMM.\x1b[0m".to_string(),
260 "\x1b[32m .OMMMMo\x1b[0m".to_string(),
261 "\x1b[32m lMM\"\x1b[0m".to_string(),
262 "\x1b[32m .;loddo:. .olloddol;.\x1b[0m".to_string(),
263 "\x1b[32m cKMMMMMMMMMMNWMMMMMMMMMM0:\x1b[0m".to_string(),
264 "\x1b[33m .KMMMMMMMMMMMMMMMMMMMMMMMWd.\x1b[0m".to_string(),
265 "\x1b[33m XMMMMMMMMMMMMMMMMMMMMMMMX.\x1b[0m".to_string(),
266 "\x1b[31m;MMMMMMMMMMMMMMMMMMMMMMMM:\x1b[0m".to_string(),
267 "\x1b[31m:MMMMMMMMMMMMMMMMMMMMMMMM:\x1b[0m".to_string(),
268 "\x1b[31m.MMMMMMMMMMMMMMMMMMMMMMMMX.\x1b[0m".to_string(),
269 "\x1b[31m kMMMMMMMMMMMMMMMMMMMMMMMMWd.\x1b[0m".to_string(),
270 "\x1b[35m'XMMMMMMMMMMMMMMMMMMMMMMMMMMk\x1b[0m".to_string(),
271 "\x1b[35m 'XMMMMMMMMMMMMMMMMMMMMMMMMK.\x1b[0m".to_string(),
272 "\x1b[34m kMMMMMMMMMMMMMMMMMMMMMMd\x1b[0m".to_string(),
273 "\x1b[34m ;KMMMMMMMWXXWMMMMMMMk.\x1b[0m".to_string(),
274 "\x1b[34m \"cooc*\" \"*coo'\"\x1b[0m".to_string(),
275 ],
276
277 Some("windows") => vec![
278 "\x1b[36m #################### ####################\x1b[0m".to_string(),
279 "\x1b[36m #################### ####################\x1b[0m".to_string(),
280 "\x1b[36m #################### ####################\x1b[0m".to_string(),
281 "\x1b[36m #################### ####################\x1b[0m".to_string(),
282 "\x1b[36m #################### ####################\x1b[0m".to_string(),
283 "\x1b[36m #################### ####################\x1b[0m".to_string(),
284 "\x1b[36m #################### ####################\x1b[0m".to_string(),
285 "".to_string(),
286 "\x1b[36m #################### ####################\x1b[0m".to_string(),
287 "\x1b[36m #################### ####################\x1b[0m".to_string(),
288 "\x1b[36m #################### ####################\x1b[0m".to_string(),
289 "\x1b[36m #################### ####################\x1b[0m".to_string(),
290 "\x1b[36m #################### ####################\x1b[0m".to_string(),
291 "\x1b[36m #################### ####################\x1b[0m".to_string(),
292 "\x1b[36m #################### ####################\x1b[0m".to_string(),
293 ],
294
295 _ => vec![
297 " .--.".to_string(),
298 " |o_o |".to_string(),
299 " |:_/ |".to_string(),
300 " // \\ \\".to_string(),
301 " (| | )".to_string(),
302 "/'\\_ _/`\\".to_string(),
303 "\\___)=(___/".to_string(),
304 ],
305 }
306}
307
308pub fn supports_kitty() -> bool {
310 std::env::var("TERM")
311 .map(|t| t == "xterm-kitty")
312 .unwrap_or(false)
313 || std::env::var("TERMINAL_EMULATOR")
314 .map(|t| t == "iterm-kitty" || t == "iTerm.app")
315 .unwrap_or(false)
316 || std::env::var("TERM_PROGRAM")
317 .map(|t| t == "rio")
318 .unwrap_or(false)
319}
320
321pub fn supports_iterm2() -> bool {
323 if let Ok(prog) = std::env::var("TERM_PROGRAM") {
324 if prog == "iTerm.app" || prog == "WezTerm" || prog == "rio" {
325 return true;
326 }
327 }
328 false
329}
330
331pub fn supports_sixel() -> bool {
333 if let Ok(term) = std::env::var("TERM") {
334 let term = term.to_lowercase();
335 if term.contains("sixel") || term.contains("foot") || term.contains("mlterm") {
336 return true;
337 }
338 }
339
340 if let Ok(prog) = std::env::var("TERM_PROGRAM") {
341 if prog == "WezTerm" || prog == "iTerm.app" || prog == "rio" {
342 return true;
343 }
344 }
345
346 if std::env::var("WT_SESSION").is_ok() {
347 return true;
348 }
349
350 false
351}
352
353pub fn chafa_available() -> bool {
355 std::process::Command::new("chafa")
356 .arg("--version")
357 .output()
358 .map(|o| o.status.success())
359 .unwrap_or(false)
360}
361
362fn write_temp_logo(bytes: &[u8]) -> std::io::Result<std::path::PathBuf> {
364 let temp_path = std::env::temp_dir().join(format!("retch_logo_{}.png", std::process::id()));
365 std::fs::write(&temp_path, bytes)?;
366 Ok(temp_path)
367}
368
369pub fn print_with_chafa(path: &std::path::Path) -> bool {
374 let output = std::process::Command::new("chafa")
375 .arg("--format")
376 .arg("symbols")
377 .arg("--size")
378 .arg("40x20")
379 .arg(path)
380 .output();
381
382 match output {
383 Ok(out) if out.status.success() => {
384 print!("{}", String::from_utf8_lossy(&out.stdout));
385 true
386 }
387 Ok(out) => {
388 eprintln!("warning: chafa failed with status: {}", out.status);
389 false
390 }
391 Err(e) => {
392 eprintln!("warning: failed to execute chafa: {}", e);
393 false
394 }
395 }
396}
397
398pub fn print_distro_logo(distro: Option<&str>) {
403 print_distro_logo_with_ascii(distro, false);
404}
405
406pub fn print_distro_logo_with_ascii(distro: Option<&str>, ascii_only: bool) {
411 if ascii_only {
412 let art = get_ascii_logo(distro);
414 for line in art {
415 println!("{}", line);
416 }
417 return;
418 }
419
420 let supports_kitty = supports_kitty();
421 let supports_iterm2 = supports_iterm2();
422 let supports_sixel = supports_sixel();
423 let has_chafa = chafa_available();
424
425 #[cfg(feature = "graphics")]
427 if supports_kitty {
428 if let Some(bytes) = get_embedded_logo(distro) {
429 if !bytes.is_empty() {
430 print_graphical_logo(bytes);
431 return;
432 }
433 }
434 }
435
436 #[cfg(feature = "graphics")]
438 if supports_iterm2 {
439 if let Some(bytes) = get_embedded_logo(distro) {
440 if !bytes.is_empty() {
441 print_iterm2_logo(bytes);
442 return;
443 }
444 }
445 }
446
447 #[cfg(feature = "graphics")]
449 if supports_sixel {
450 if let Some(bytes) = get_embedded_logo(distro) {
451 if !bytes.is_empty() {
452 print_sixel_logo(bytes);
453 return;
454 }
455 }
456 }
457
458 if has_chafa {
460 if let Some(bytes) = get_embedded_logo(distro) {
461 if bytes.len() > 100 {
462 if let Ok(temp_path) = write_temp_logo(bytes) {
463 if print_with_chafa(&temp_path) {
464 let _ = std::fs::remove_file(&temp_path);
465 return;
466 }
467 let _ = std::fs::remove_file(&temp_path);
468 }
469 }
470 }
471 }
472
473 let art = get_ascii_logo(distro);
475 for line in art {
476 println!("{}", line);
477 }
478}
479
480#[cfg(feature = "graphics")]
482pub fn print_iterm2_logo(image_data: &[u8]) {
483 use base64::Engine;
484 let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
485 print!(
486 "\x1b]1337;File=inline=1;preserveAspectRatio=1:{}\x07",
487 encoded
488 );
489 println!(); }
491
492#[cfg(feature = "graphics")]
494pub fn print_iterm2_logo_from_path(path: &std::path::Path) {
495 if let Ok(bytes) = std::fs::read(path) {
496 print_iterm2_logo(&bytes);
497 } else {
498 println!("[Could not read logo for iTerm2 from {}]", path.display());
499 }
500}
501
502#[cfg(not(feature = "graphics"))]
504pub fn print_iterm2_logo(_image_data: &[u8]) {
505 println!("[iTerm2 logo support requires --features graphics]");
506}
507
508#[cfg(feature = "graphics")]
510pub fn print_graphical_logo(image_data: &[u8]) {
511 use base64::Engine;
512
513 let (width, height) = image::load_from_memory(image_data)
514 .map(|img| (img.width(), img.height()))
515 .unwrap_or((0, 0));
516
517 let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
518
519 if width > 0 && height > 0 {
520 println!("\x1b_Gf=100,s={},v={},a=T;{}\x1b\\", width, height, encoded);
521 } else {
522 println!("\x1b_Gf=100,a=T;{}", encoded);
523 }
524}
525
526#[cfg(feature = "graphics")]
528pub fn print_sixel_logo(image_data: &[u8]) {
529 if let Ok(img) = image::load_from_memory(image_data) {
530 let rgba = img.to_rgba8();
531 let (width, height) = rgba.dimensions();
532 print_sixel_rgba(rgba.as_raw(), width, height);
533 }
534}
535
536#[cfg(feature = "graphics")]
538pub fn print_sixel_rgba(rgba: &[u8], width: u32, height: u32) {
539 use icy_sixel::SixelImage;
540
541 match SixelImage::try_from_rgba(rgba.to_vec(), width as usize, height as usize) {
542 Ok(sixel_img) => match sixel_img.encode() {
543 Ok(sixel_str) => {
544 print!("{}", sixel_str);
545 }
546 Err(e) => eprintln!("[Sixel Encoding Error: {}]", e),
547 },
548 Err(e) => eprintln!("[Sixel Creation Error: {}]", e),
549 }
550}
551
552#[cfg(not(feature = "graphics"))]
554pub fn print_graphical_logo(_image_data: &[u8]) {
555 println!("[Graphical logo support requires --features graphics]");
556}
557
558#[cfg(not(feature = "graphics"))]
560pub fn print_sixel_logo(_image_data: &[u8]) {
561 println!("[Sixel logo support requires --features graphics]");
562}
563
564#[cfg(feature = "graphics")]
566pub fn print_graphical_logo_from_path(path: &std::path::Path) {
567 use image::ImageFormat;
568 match image::open(path) {
569 Ok(img) => {
570 let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
571 let mut png_data = Vec::new();
572 if resized
573 .write_to(&mut std::io::Cursor::new(&mut png_data), ImageFormat::Png)
574 .is_ok()
575 {
576 print_graphical_logo(&png_data);
577 } else {
578 println!("[Failed to encode logo as PNG]");
579 }
580 }
581 Err(_) => {
582 println!("[Could not load graphical logo from {}]", path.display());
583 }
584 }
585}
586
587#[cfg(feature = "graphics")]
589pub fn print_sixel_logo_from_path(path: &std::path::Path) {
590 match image::open(path) {
591 Ok(img) => {
592 let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
593 let rgba = resized.to_rgba8();
594 let (width, height) = rgba.dimensions();
595 print_sixel_rgba(rgba.as_raw(), width, height);
596 }
597 Err(_) => {
598 println!("[Could not load logo for Sixel from {}]", path.display());
599 }
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606
607 #[test]
608 fn test_get_ascii_logo_arch() {
609 let logo = get_ascii_logo(Some("arch"));
610 assert!(!logo.is_empty());
611 assert!(logo[0].contains("`"));
612 }
613
614 #[test]
615 fn test_get_ascii_logo_unknown() {
616 let logo = get_ascii_logo(Some("unknown_distro"));
617 assert!(!logo.is_empty());
618 assert!(logo.iter().any(|line| line.contains("o_o")));
620 }
621
622 #[test]
623 fn test_get_ascii_logo_none() {
624 let logo = get_ascii_logo(None);
625 assert!(!logo.is_empty());
626 }
627
628 static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
629
630 struct EnvGuard {
631 _mutex_guard: std::sync::MutexGuard<'static, ()>,
632 old_vars: std::collections::HashMap<&'static str, Option<String>>,
633 }
634
635 impl EnvGuard {
636 fn new(vars_to_mock: &[&'static str]) -> Self {
637 let guard = ENV_LOCK.lock().unwrap();
638 let mut old_vars = std::collections::HashMap::new();
639 for var in vars_to_mock {
640 old_vars.insert(*var, std::env::var(var).ok());
641 }
642 EnvGuard {
643 _mutex_guard: guard,
644 old_vars,
645 }
646 }
647 }
648
649 impl Drop for EnvGuard {
650 fn drop(&mut self) {
651 for (var, value) in &self.old_vars {
652 if let Some(val) = value {
653 std::env::set_var(var, val);
654 } else {
655 std::env::remove_var(var);
656 }
657 }
658 }
659 }
660
661 #[test]
662 fn test_supports_kitty_heuristics() {
663 let _guard = EnvGuard::new(&["TERM", "TERMINAL_EMULATOR", "TERM_PROGRAM"]);
664
665 std::env::set_var("TERM", "xterm-kitty");
667 std::env::remove_var("TERMINAL_EMULATOR");
668 std::env::remove_var("TERM_PROGRAM");
669 assert!(supports_kitty());
670
671 std::env::remove_var("TERM");
673 std::env::set_var("TERMINAL_EMULATOR", "iterm-kitty");
674 assert!(supports_kitty());
675
676 std::env::set_var("TERMINAL_EMULATOR", "iTerm.app");
678 assert!(supports_kitty());
679
680 std::env::remove_var("TERMINAL_EMULATOR");
682 std::env::set_var("TERM_PROGRAM", "rio");
683 assert!(supports_kitty());
684
685 std::env::remove_var("TERM_PROGRAM");
687 assert!(!supports_kitty());
688 }
689
690 #[test]
691 fn test_supports_iterm2_heuristics() {
692 let _guard = EnvGuard::new(&["TERM_PROGRAM"]);
693
694 std::env::set_var("TERM_PROGRAM", "iTerm.app");
696 assert!(supports_iterm2());
697
698 std::env::set_var("TERM_PROGRAM", "WezTerm");
700 assert!(supports_iterm2());
701
702 std::env::set_var("TERM_PROGRAM", "rio");
704 assert!(supports_iterm2());
705
706 std::env::set_var("TERM_PROGRAM", "Apple_Terminal");
708 assert!(!supports_iterm2());
709
710 std::env::remove_var("TERM_PROGRAM");
712 assert!(!supports_iterm2());
713 }
714
715 #[test]
716 fn test_supports_sixel_heuristics() {
717 let _guard = EnvGuard::new(&["TERM", "TERM_PROGRAM", "WT_SESSION"]);
718
719 std::env::remove_var("TERM");
721 std::env::remove_var("TERM_PROGRAM");
722 std::env::remove_var("WT_SESSION");
723 assert!(!supports_sixel());
724
725 std::env::set_var("TERM", "xterm-sixel");
727 assert!(supports_sixel());
728
729 std::env::set_var("TERM", "foot");
731 assert!(supports_sixel());
732
733 std::env::set_var("TERM", "MLTerm");
735 assert!(supports_sixel());
736
737 std::env::remove_var("TERM");
739 std::env::set_var("TERM_PROGRAM", "WezTerm");
740 assert!(supports_sixel());
741
742 std::env::set_var("TERM_PROGRAM", "iTerm.app");
744 assert!(supports_sixel());
745
746 std::env::set_var("TERM_PROGRAM", "rio");
748 assert!(supports_sixel());
749
750 std::env::remove_var("TERM_PROGRAM");
752 std::env::set_var("WT_SESSION", "active");
753 assert!(supports_sixel());
754 }
755
756 #[test]
757 fn test_get_embedded_logo() {
758 let logo = get_embedded_logo(Some("arch"));
759 assert!(logo.is_some());
760 let logo = get_embedded_logo(Some("pop"));
761 assert!(logo.is_some());
762 let logo = get_embedded_logo(Some("manjaro"));
763 assert!(logo.is_some());
764 let logo = get_embedded_logo(Some("endeavouros"));
765 assert!(logo.is_some());
766 let logo = get_embedded_logo(Some("opensuse"));
767 assert!(logo.is_some());
768 let logo = get_embedded_logo(Some("opensuse-leap"));
769 assert!(logo.is_some());
770 let logo = get_embedded_logo(Some("opensuse-tumbleweed"));
771 assert!(logo.is_some());
772 let logo = get_embedded_logo(Some("macos"));
773 assert!(logo.is_some());
774 let logo = get_embedded_logo(Some("windows"));
775 assert!(logo.is_some());
776 let logo = get_embedded_logo(None);
777 assert!(logo.is_some());
778 }
779
780 #[test]
781 fn test_get_ascii_logo_new_distros() {
782 let pop = get_ascii_logo(Some("pop"));
783 assert!(!pop.is_empty());
784 assert!(pop.iter().any(|line| line.contains("767")));
785
786 let manjaro = get_ascii_logo(Some("manjaro"));
787 assert!(!manjaro.is_empty());
788 assert!(manjaro.iter().any(|line| line.contains("████████")));
789
790 let endeavouros = get_ascii_logo(Some("endeavouros"));
791 assert!(!endeavouros.is_empty());
792 assert!(endeavouros.iter().any(|line| line.contains("ssso")));
793
794 let opensuse = get_ascii_logo(Some("opensuse"));
795 assert!(!opensuse.is_empty());
796 assert!(opensuse.iter().any(|line| line.contains("O0000Ok")));
797
798 let macos = get_ascii_logo(Some("macos"));
799 assert!(!macos.is_empty());
800 assert!(macos
801 .iter()
802 .any(|line| line.contains("cKMMMMMMMMMMNWMMMMMMMMMM0")));
803
804 let windows = get_ascii_logo(Some("windows"));
805 assert!(!windows.is_empty());
806 assert!(windows
807 .iter()
808 .any(|line| line.contains("#################### ####################")));
809 }
810}