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 " `/+++o${2}oooooooo${1}oooo/`".to_string(),
90 " ./${2}ooosssso++osssssso${1}+`".to_string(),
91 "${2} .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 " ${2}_,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\"' ${1}.${2} $$$$$$".to_string(),
109 " $$$$P d$' ${1},${2} $$$$P".to_string(),
110 " $$$$: $$$. ${1}-${2} ,d$$$$'".to_string(),
111 " $$$$; Y$b._ _,d$P'".to_string(),
112 " Y$$$$. ${1}`.${2}`\"Y$$$$$$$$P\"'".to_string(),
113 " `$$$$b ${1}\"-.__".to_string(),
114 " ${2}`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 " .:cccccccccccccccccccccccccc:.".to_string(),
128 " .;ccccccccccccc;${2}.:dddl:${1};ccccccc;.".to_string(),
129 " .:ccccccccccccc;${2}OWMKOOXMWd${1};ccccccc:.".to_string(),
130 ".:ccccccccccccc;${2}KMMc${1};cc;${2}xMMc${1};ccccccc:.".to_string(),
131 ",cccccccccccccc;${2}MMM.${1};cc;${2};WW:${1};cccccccc,".to_string(),
132 ":cccccccccccccc;${2}MMM.${1};cccccccccccccccc:".to_string(),
133 ":ccccccc;${2}oxOOOo${1};${2}MMM000k.${1};cccccccccccc:".to_string(),
134 "cccccc;${2}0MMKxdd:${1};${2}MMMkddc.${1};cccccccccccc;".to_string(),
135 "ccccc;${2}XMO'${1};cccc;${2}MMM.${1};cccccccccccccccc'".to_string(),
136 "ccccc;${2}MMo${1};ccccc;${2}MMW.${1};ccccccccccccccc;".to_string(),
137 "ccccc;${2}0MNc.${1}ccc${2}.xMMd${1};ccccccccccccccc;".to_string(),
138 "cccccc;${2}dNMWXXXWM0:${1};cccccccccccccc:,".to_string(),
139 "cccccccc;${2}.:odl:${1};cccccccccccccc:,.".to_string(),
140 "ccccccccccccccccccccccccccccc:'.".to_string(),
141 ":ccccccccccccccccccccccc:;,..".to_string(),
142 " ':cccccccccccccccc::;,.".to_string(),
143 ],
144
145 Some("nixos") => vec![
147 " ${1}▗▄▄▄ ${2}▗▄▄▄▄ ▄▄▄▖".to_string(),
148 " ${1}▜███▙ ${2}▜███▙ ▟███▛".to_string(),
149 " ${1}▜███▙ ${2}▜███▙▟███▛".to_string(),
150 " ${1}▜███▙ ${2}▜██████▛".to_string(),
151 " ${1}▟█████████████████▙ ${2}▜████▛ ${3}▟▙".to_string(),
152 " ${1}▟███████████████████▙ ${2}▜███▙ ${3}▟██▙".to_string(),
153 " ${6}▄▄▄▄▖ ${2}▜███▙ ${3}▟███▛".to_string(),
154 " ${6}▟███▛ ${2}▜██▛ ${3}▟███▛".to_string(),
155 " ${6}▟███▛ ${2}▜▛ ${3}▟███▛".to_string(),
156 "${6}▟███████████▛ ${3}▟██████████▙".to_string(),
157 "${6}▜██████████▛ ${3}▟███████████▛".to_string(),
158 " ${6}▟███▛ ${5}▟▙ ${3}▟███▛".to_string(),
159 " ${6}▟███▛ ${5}▟██▙ ${3}▟███▛".to_string(),
160 " ${6}▟███▛ ${5}▜███▙ ${3}▝▀▀▀▀".to_string(),
161 " ${6}▜██▛ ${5}▜███▙ ${4}▜██████████████████▛".to_string(),
162 " ${6}▜▛ ${5}▟████▙ ${4}▜████████████████▛".to_string(),
163 " ${5}▟██████▙ ${4}▜███▙".to_string(),
164 " ${5}▟███▛▜███▙ ${4}▜███▙".to_string(),
165 " ${5}▟███▛ ▜███▙ ${4}▜███▙".to_string(),
166 " ${5}▝▀▀▀ ▀▀▀▀▘ ${4}▀▀▀▘".to_string(),
167 ],
168
169 Some("ubuntu") => vec![
171 " ....".to_string(),
172 " ${2}.',:clooo: ${1}.:looooo:.".to_string(),
173 " ${2}.;looooooooc ${1}.oooooooooo'".to_string(),
174 " ${2}.;looooool:,''. ${1}:ooooooooooc".to_string(),
175 " ${2};looool;. ${1}'oooooooooo,".to_string(),
176 " ${2};clool' ${1}.cooooooc. ${2},,".to_string(),
177 " ${2}... ${1}...... ${2}.:oo,".to_string(),
178 " ${1}.;clol:,. ${2}.loooo'".to_string(),
179 " ${1}:ooooooooo, ${2}'ooool".to_string(),
180 "${1}'ooooooooooo. ${2}loooo.".to_string(),
181 "${1}'ooooooooool ${2}coooo.".to_string(),
182 " ${1},loooooooc. ${2}.loooo.".to_string(),
183 " ${1}.,;;;'. ${2};ooooc".to_string(),
184 " ${2}... ${2},ooool.".to_string(),
185 " ${2}.cooooc. ${1}..',,'. ${2}.cooo.".to_string(),
186 " ${2};ooooo:. ${1};oooooooc. ${2}:l.".to_string(),
187 " ${2}.coooooc,.. ${1}coooooooooo.".to_string(),
188 " ${2}.:ooooooolc:. ${1}.ooooooooooo'".to_string(),
189 " ${2}.':loooooo; ${1},oooooooooc".to_string(),
190 " ${2}..';::c' ${1}.;loooo:'".to_string(),
191 ],
192
193 Some("pop") => vec![
194 " /////////////".to_string(),
195 " /////////////////////".to_string(),
196 " ///////${2}*767${1}////////////////".to_string(),
197 " //////${2}7676767676*${1}//////////////".to_string(),
198 " /////${2}76767${1}//${2}7676767${1}//////////////".to_string(),
199 " /////${2}767676${1}///${2}*76767${1}///////////////".to_string(),
200 " ///////${2}767676${1}///${2}76767${1}.///${2}7676*${1}///////".to_string(),
201 "/////////${2}767676${1}//${2}76767${1}///${2}767676${1}////////".to_string(),
202 "//////////${2}76767676767${1}////${2}76767${1}/////////".to_string(),
203 "///////////${2}76767676${1}//////${2}7676${1}//////////".to_string(),
204 "////////////,${2}7676${1},///////${2}767${1}///////////".to_string(),
205 "/////////////*${2}7676${1}///////${2}76${1}////////////".to_string(),
206 "///////////////${2}7676${1}////////////////////".to_string(),
207 " ///////////////${2}7676${1}///${2}767${1}////////////".to_string(),
208 " //////////////////////${2}'${1}////////////".to_string(),
209 " //////${2}.7676767676767676767,${1}//////".to_string(),
210 " /////${2}767676767676767676767${1}/////".to_string(),
211 " ///////////////////////////".to_string(),
212 " /////////////////////".to_string(),
213 " /////////////".to_string(),
214 ],
215
216 Some("manjaro") => vec![
217 "██████████████████ ████████".to_string(),
218 "██████████████████ ████████".to_string(),
219 "██████████████████ ████████".to_string(),
220 "██████████████████ ████████".to_string(),
221 "████████ ████████".to_string(),
222 "████████ ████████ ████████".to_string(),
223 "████████ ████████ ████████".to_string(),
224 "████████ ████████ ████████".to_string(),
225 "████████ ████████ ████████".to_string(),
226 "████████ ████████ ████████".to_string(),
227 "████████ ████████ ████████".to_string(),
228 "████████ ████████ ████████".to_string(),
229 "████████ ████████ ████████".to_string(),
230 "████████ ████████ ████████".to_string(),
231 ],
232
233 Some("endeavouros") => vec![
234 " ${2}./${1}o${3}.".to_string(),
235 " ${2}./${1}sssso${3}-".to_string(),
236 " ${2}`:${1}osssssss+${3}-".to_string(),
237 " ${2}`:+${1}sssssssssso${3}/.".to_string(),
238 " ${2}`-/o${1}ssssssssssssso${3}/.".to_string(),
239 " ${2}`-/+${1}sssssssssssssssso${3}+:`".to_string(),
240 " ${2}`-:/+${1}sssssssssssssssssso${3}+/.".to_string(),
241 " ${2}`.://o${1}sssssssssssssssssssso${3}++-".to_string(),
242 " ${2}.://+${1}ssssssssssssssssssssssso${3}++:".to_string(),
243 " ${2}.:///o${1}ssssssssssssssssssssssssso${3}++:".to_string(),
244 " ${2}`:////${1}ssssssssssssssssssssssssssso${3}+++.".to_string(),
245 "${2}`-////+${1}ssssssssssssssssssssssssssso${3}++++-".to_string(),
246 " ${2}`..-+${1}oosssssssssssssssssssssssso${3}+++++/`".to_string(),
247 " ${3}./++++++++++++++++++++++++++++++/:.".to_string(),
248 " `:::::::::::::::::::::::::------``".to_string(),
249 ],
250
251 Some("opensuse") | Some("opensuse-leap") | Some("opensuse-tumbleweed") => vec![
252 " ${2}.;ldkO0000Okdl;.".to_string(),
253 " .;d00xl:^''''''^:ok00d;.".to_string(),
254 " .d00l' 'o00d.".to_string(),
255 " .d0Kd'${1} Okxol:;,. ${2}:O0d".to_string(),
256 " .OK${1}KKK0kOKKKKKKKKKKOxo:, ${2}lKO.".to_string(),
257 " ,0K${1}KKKKKKKKKKKKKKK0P^${2},,,${1}^dx:${2} ;00,".to_string(),
258 ".OK${1}KKKKKKKKKKKKKKKk'${2}.oOPPb.${1}'0k.${2} cKO.".to_string(),
259 ":KK${1}KKKKKKKKKKKKKKK: ${2}kKx..dd ${1}lKd${2} 'OK:".to_string(),
260 "dKK${1}KKKKKKKKKOx0KKKd ${2}^0KKKO' ${1}kKKc${2} dKd".to_string(),
261 "dKK${1}KKKKKKKKKK;.;oOKx,..${2}^${1}..;kKKK0.${2} dKd".to_string(),
262 ":KK${1}KKKKKKKKKK0o;...^cdxxOK0O/^^' ${2}.0K:".to_string(),
263 " kKK${1}KKKKKKKKKKKKK0x;,,......,;od ${2}lKk".to_string(),
264 " '0K${1}KKKKKKKKKKKKKKKKKKKK00KKOo^ ${2}c00'".to_string(),
265 " 'kK${1}KKOxddxkOO00000Okxoc;'' ${2}.dKk'".to_string(),
266 " l0Ko. .c00l'".to_string(),
267 " 'l0Kk:. .;xK0l'".to_string(),
268 " 'lkK0xl:;,,,,;:ldO0kl'".to_string(),
269 " '^:ldxkkkkxdl:^'".to_string(),
270 ],
271
272 Some("macos") => vec![
273 " ..'".to_string(),
274 " ,xNMM.".to_string(),
275 " .OMMMMo".to_string(),
276 " lMM\"".to_string(),
277 " .;loddo:. .olloddol;.".to_string(),
278 " cKMMMMMMMMMMNWMMMMMMMMMM0:".to_string(),
279 " ${2}.KMMMMMMMMMMMMMMMMMMMMMMMWd.".to_string(),
280 " XMMMMMMMMMMMMMMMMMMMMMMMX.".to_string(),
281 "${3};MMMMMMMMMMMMMMMMMMMMMMMM:".to_string(),
282 ":MMMMMMMMMMMMMMMMMMMMMMMM:".to_string(),
283 "${4}.MMMMMMMMMMMMMMMMMMMMMMMMX.".to_string(),
284 " kMMMMMMMMMMMMMMMMMMMMMMMMWd.".to_string(),
285 " ${5}'XMMMMMMMMMMMMMMMMMMMMMMMMMMk".to_string(),
286 " 'XMMMMMMMMMMMMMMMMMMMMMMMMK.".to_string(),
287 " ${6}kMMMMMMMMMMMMMMMMMMMMMMd".to_string(),
288 " ;KMMMMMMMWXXWMMMMMMMk.".to_string(),
289 " \"cooc*\" \"*coo'\"".to_string(),
290 ],
291
292 Some("windows") => vec![
293 "${1} ,.=:!!t3Z3z.,".to_string(),
294 " :tt:::tt333EE3".to_string(),
295 "${1} Et:::ztt33EEEL${2} @Ee., ..,".to_string(),
296 "${1} ;tt:::tt333EE7${2} ;EEEEEEttttt33#".to_string(),
297 "${1} :Et:::zt333EEQ.${2} $EEEEEttttt33QL".to_string(),
298 "${1} it::::tt333EEF${2} @EEEEEEttttt33F".to_string(),
299 "${1} ;3=*^```\"*4EEV${2} :EEEEEEttttt33@.".to_string(),
300 "${3} ,.=::::!t=., ${1}`${2} @EEEEEEtttz33QF".to_string(),
301 "${3} ;::::::::zt33)${2} \"4EEEtttji3P*".to_string(),
302 "${3} :t::::::::tt33.${4}:Z3z..${2} ``${4} ,..g.".to_string(),
303 "${3} i::::::::zt33F${4} AEEEtttt::::ztF".to_string(),
304 "${3} ;:::::::::t33V${4} ;EEEttttt::::t3".to_string(),
305 "${3} E::::::::zt33L${4} @EEEtttt::::z3F".to_string(),
306 "${3}{3=*^```\"*4E3)${4} ;EEEtttt:::::tZ`".to_string(),
307 "${3} `${4} :EEEEtttt::::z7".to_string(),
308 " \"VEzjt:;;z>*`".to_string(),
309 ],
310
311 _ => vec![
313 " ${2}.--.".to_string(),
314 " ${2}|${3}o${2}_${3}o${2} |".to_string(),
315 " ${2}|${3}:${2}_/ |".to_string(),
316 " ${2}// \\ \\".to_string(),
317 " (| | )".to_string(),
318 "/'\\_ _/`\\".to_string(),
319 "\\___)=(___/".to_string(),
320 ],
321 }
322}
323
324pub fn get_distro_colors(distro: Option<&str>) -> Vec<&'static str> {
326 let d = distro.map(|s| s.to_lowercase());
327 match d.as_deref() {
328 Some("arch") => vec!["\x1b[36m", "\x1b[37m"],
329 Some("debian") => vec!["\x1b[31m", "\x1b[37m"],
330 Some("fedora") => vec!["\x1b[34m", "\x1b[37m"],
331 Some("nixos") => vec![
332 "\x1b[34m", "\x1b[36m", "\x1b[34m", "\x1b[36m", "\x1b[34m", "\x1b[36m",
333 ],
334 Some("ubuntu") => vec!["\x1b[33m", "\x1b[31m"],
335 Some("pop") => vec!["\x1b[36m", "\x1b[37m"],
336 Some("manjaro") => vec!["\x1b[32m"],
337 Some("endeavouros") => vec!["\x1b[35m", "\x1b[31m", "\x1b[34m"],
338 Some("opensuse") | Some("opensuse-leap") | Some("opensuse-tumbleweed") => {
339 vec!["\x1b[32m", "\x1b[37m"]
340 }
341 Some("macos") => vec!["\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m", "\x1b[34m"],
342 Some("windows") => vec!["\x1b[36m"],
343 _ => vec!["\x1b[30m", "\x1b[37m", "\x1b[33m"], }
345}
346
347pub fn get_distro_logo_lines(distro: Option<&str>) -> Vec<String> {
349 let raw_lines = get_ascii_logo(distro);
350 let colors = get_distro_colors(distro);
351 let default_color = colors.first().copied().unwrap_or("\x1b[0m");
352
353 raw_lines
354 .into_iter()
355 .map(|line| {
356 let mut formatted = line;
357 for i in 1..=9 {
358 let color_val = colors.get(i - 1).copied().unwrap_or("\x1b[0m");
359 let placeholder = format!("${{{}}}", i);
360 formatted = formatted.replace(&placeholder, color_val);
361 let placeholder_short = format!("${}", i);
362 formatted = formatted.replace(&placeholder_short, color_val);
363 }
364 if !formatted.is_empty() {
365 format!("{}{}\x1b[0m", default_color, formatted)
366 } else {
367 formatted
368 }
369 })
370 .collect()
371}
372
373pub fn supports_kitty() -> bool {
375 std::env::var("TERM")
376 .map(|t| t == "xterm-kitty")
377 .unwrap_or(false)
378 || std::env::var("TERMINAL_EMULATOR")
379 .map(|t| t == "iterm-kitty" || t == "iTerm.app")
380 .unwrap_or(false)
381 || std::env::var("TERM_PROGRAM")
382 .map(|t| t == "rio")
383 .unwrap_or(false)
384}
385
386pub fn supports_iterm2() -> bool {
388 if let Ok(prog) = std::env::var("TERM_PROGRAM") {
389 if prog == "iTerm.app" || prog == "WezTerm" || prog == "rio" {
390 return true;
391 }
392 }
393 false
394}
395
396pub fn supports_sixel() -> bool {
398 if let Ok(term) = std::env::var("TERM") {
399 let term = term.to_lowercase();
400 if term.contains("sixel") || term.contains("foot") || term.contains("mlterm") {
401 return true;
402 }
403 }
404
405 if let Ok(prog) = std::env::var("TERM_PROGRAM") {
406 if prog == "WezTerm" || prog == "iTerm.app" || prog == "rio" {
407 return true;
408 }
409 }
410
411 if std::env::var("WT_SESSION").is_ok() {
412 return true;
413 }
414
415 false
416}
417
418pub fn chafa_available() -> bool {
420 std::process::Command::new("chafa")
421 .arg("--version")
422 .output()
423 .map(|o| o.status.success())
424 .unwrap_or(false)
425}
426
427fn write_temp_logo(bytes: &[u8]) -> std::io::Result<std::path::PathBuf> {
429 let temp_path = std::env::temp_dir().join(format!("retch_logo_{}.png", std::process::id()));
430 std::fs::write(&temp_path, bytes)?;
431 Ok(temp_path)
432}
433
434pub fn print_with_chafa(path: &std::path::Path) -> bool {
439 let output = std::process::Command::new("chafa")
440 .arg("--format")
441 .arg("symbols")
442 .arg("--size")
443 .arg("40x20")
444 .arg(path)
445 .output();
446
447 match output {
448 Ok(out) if out.status.success() => {
449 print!("{}", String::from_utf8_lossy(&out.stdout));
450 true
451 }
452 Ok(out) => {
453 eprintln!("warning: chafa failed with status: {}", out.status);
454 false
455 }
456 Err(e) => {
457 eprintln!("warning: failed to execute chafa: {}", e);
458 false
459 }
460 }
461}
462
463pub fn get_chafa_logo_lines(path: &std::path::Path) -> Option<Vec<String>> {
465 let output = std::process::Command::new("chafa")
466 .arg("--format")
467 .arg("symbols")
468 .arg("--size")
469 .arg("40x20")
470 .arg(path)
471 .output()
472 .ok()?;
473 if output.status.success() {
474 let content = String::from_utf8_lossy(&output.stdout);
475 Some(content.lines().map(|s| s.to_string()).collect())
476 } else {
477 None
478 }
479}
480
481pub fn print_distro_logo(distro: Option<&str>) {
486 print_distro_logo_with_ascii(distro, false, false);
487}
488
489pub fn print_distro_logo_with_ascii(distro: Option<&str>, ascii_only: bool, chafa_only: bool) {
497 if ascii_only {
498 let art = get_distro_logo_lines(distro);
500 for line in art {
501 println!("{}", line);
502 }
503 return;
504 }
505
506 let has_chafa = chafa_available();
507
508 if !chafa_only {
509 let supports_kitty = supports_kitty();
510 let supports_iterm2 = supports_iterm2();
511 let supports_sixel = supports_sixel();
512
513 #[cfg(feature = "graphics")]
515 if supports_kitty {
516 if let Some(bytes) = get_embedded_logo(distro) {
517 if !bytes.is_empty() {
518 print_graphical_logo(bytes);
519 return;
520 }
521 }
522 }
523
524 #[cfg(feature = "graphics")]
526 if supports_iterm2 {
527 if let Some(bytes) = get_embedded_logo(distro) {
528 if !bytes.is_empty() {
529 print_iterm2_logo(bytes);
530 return;
531 }
532 }
533 }
534
535 #[cfg(feature = "graphics")]
537 if supports_sixel {
538 if let Some(bytes) = get_embedded_logo(distro) {
539 if !bytes.is_empty() {
540 print_sixel_logo(bytes);
541 return;
542 }
543 }
544 }
545 }
546
547 if has_chafa {
549 if let Some(bytes) = get_embedded_logo(distro) {
550 if bytes.len() > 100 {
551 if let Ok(temp_path) = write_temp_logo(bytes) {
552 if print_with_chafa(&temp_path) {
553 let _ = std::fs::remove_file(&temp_path);
554 return;
555 }
556 let _ = std::fs::remove_file(&temp_path);
557 }
558 }
559 }
560 }
561
562 let art = get_distro_logo_lines(distro);
564 for line in art {
565 println!("{}", line);
566 }
567}
568
569#[cfg(feature = "graphics")]
571pub fn print_iterm2_logo(image_data: &[u8]) {
572 use base64::Engine;
573 let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
574 print!(
575 "\x1b]1337;File=inline=1;preserveAspectRatio=1:{}\x07",
576 encoded
577 );
578 println!(); }
580
581#[cfg(feature = "graphics")]
583pub fn print_iterm2_logo_from_path(path: &std::path::Path) {
584 if let Ok(bytes) = std::fs::read(path) {
585 print_iterm2_logo(&bytes);
586 } else {
587 println!("[Could not read logo for iTerm2 from {}]", path.display());
588 }
589}
590
591#[cfg(not(feature = "graphics"))]
593pub fn print_iterm2_logo(_image_data: &[u8]) {
594 println!("[iTerm2 logo support requires --features graphics]");
595}
596
597#[cfg(feature = "graphics")]
599pub fn print_graphical_logo(image_data: &[u8]) {
600 use base64::Engine;
601
602 let (width, height) = image::load_from_memory(image_data)
603 .map(|img| (img.width(), img.height()))
604 .unwrap_or((0, 0));
605
606 let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
607
608 if width > 0 && height > 0 {
609 println!("\x1b_Gf=100,s={},v={},a=T;{}\x1b\\", width, height, encoded);
610 } else {
611 println!("\x1b_Gf=100,a=T;{}", encoded);
612 }
613}
614
615#[cfg(feature = "graphics")]
617pub fn print_sixel_logo(image_data: &[u8]) {
618 if let Ok(img) = image::load_from_memory(image_data) {
619 let rgba = img.to_rgba8();
620 let (width, height) = rgba.dimensions();
621 print_sixel_rgba(rgba.as_raw(), width, height);
622 }
623}
624
625#[cfg(feature = "graphics")]
627pub fn print_sixel_rgba(rgba: &[u8], width: u32, height: u32) {
628 use icy_sixel::SixelImage;
629
630 match SixelImage::try_from_rgba(rgba.to_vec(), width as usize, height as usize) {
631 Ok(sixel_img) => match sixel_img.encode() {
632 Ok(sixel_str) => {
633 print!("{}", sixel_str);
634 }
635 Err(e) => eprintln!("[Sixel Encoding Error: {}]", e),
636 },
637 Err(e) => eprintln!("[Sixel Creation Error: {}]", e),
638 }
639}
640
641#[cfg(not(feature = "graphics"))]
643pub fn print_graphical_logo(_image_data: &[u8]) {
644 println!("[Graphical logo support requires --features graphics]");
645}
646
647#[cfg(not(feature = "graphics"))]
649pub fn print_sixel_logo(_image_data: &[u8]) {
650 println!("[Sixel logo support requires --features graphics]");
651}
652
653#[cfg(feature = "graphics")]
655pub fn print_graphical_logo_from_path(path: &std::path::Path) {
656 use image::ImageFormat;
657 match image::open(path) {
658 Ok(img) => {
659 let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
660 let mut png_data = Vec::new();
661 if resized
662 .write_to(&mut std::io::Cursor::new(&mut png_data), ImageFormat::Png)
663 .is_ok()
664 {
665 print_graphical_logo(&png_data);
666 } else {
667 println!("[Failed to encode logo as PNG]");
668 }
669 }
670 Err(_) => {
671 println!("[Could not load graphical logo from {}]", path.display());
672 }
673 }
674}
675
676#[cfg(feature = "graphics")]
678pub fn print_sixel_logo_from_path(path: &std::path::Path) {
679 match image::open(path) {
680 Ok(img) => {
681 let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
682 let rgba = resized.to_rgba8();
683 let (width, height) = rgba.dimensions();
684 print_sixel_rgba(rgba.as_raw(), width, height);
685 }
686 Err(_) => {
687 println!("[Could not load logo for Sixel from {}]", path.display());
688 }
689 }
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695
696 #[test]
697 fn test_get_ascii_logo_arch() {
698 let logo = get_ascii_logo(Some("arch"));
699 assert!(!logo.is_empty());
700 assert!(logo[0].contains("`"));
701 }
702
703 #[test]
704 fn test_get_ascii_logo_unknown() {
705 let logo = get_ascii_logo(Some("unknown_distro"));
706 assert!(!logo.is_empty());
707 assert!(logo
709 .iter()
710 .any(|line| line.contains("o${2}_${3}o") || line.contains("o_o")));
711 }
712
713 #[test]
714 fn test_get_ascii_logo_none() {
715 let logo = get_ascii_logo(None);
716 assert!(!logo.is_empty());
717 }
718
719 static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
720
721 struct EnvGuard {
722 _mutex_guard: std::sync::MutexGuard<'static, ()>,
723 old_vars: std::collections::HashMap<&'static str, Option<String>>,
724 }
725
726 impl EnvGuard {
727 fn new(vars_to_mock: &[&'static str]) -> Self {
728 let guard = ENV_LOCK.lock().unwrap();
729 let mut old_vars = std::collections::HashMap::new();
730 for var in vars_to_mock {
731 old_vars.insert(*var, std::env::var(var).ok());
732 }
733 EnvGuard {
734 _mutex_guard: guard,
735 old_vars,
736 }
737 }
738 }
739
740 impl Drop for EnvGuard {
741 fn drop(&mut self) {
742 for (var, value) in &self.old_vars {
743 if let Some(val) = value {
744 std::env::set_var(var, val);
745 } else {
746 std::env::remove_var(var);
747 }
748 }
749 }
750 }
751
752 #[test]
753 fn test_supports_kitty_heuristics() {
754 let _guard = EnvGuard::new(&["TERM", "TERMINAL_EMULATOR", "TERM_PROGRAM"]);
755
756 std::env::set_var("TERM", "xterm-kitty");
758 std::env::remove_var("TERMINAL_EMULATOR");
759 std::env::remove_var("TERM_PROGRAM");
760 assert!(supports_kitty());
761
762 std::env::remove_var("TERM");
764 std::env::set_var("TERMINAL_EMULATOR", "iterm-kitty");
765 assert!(supports_kitty());
766
767 std::env::set_var("TERMINAL_EMULATOR", "iTerm.app");
769 assert!(supports_kitty());
770
771 std::env::remove_var("TERMINAL_EMULATOR");
773 std::env::set_var("TERM_PROGRAM", "rio");
774 assert!(supports_kitty());
775
776 std::env::remove_var("TERM_PROGRAM");
778 assert!(!supports_kitty());
779 }
780
781 #[test]
782 fn test_supports_iterm2_heuristics() {
783 let _guard = EnvGuard::new(&["TERM_PROGRAM"]);
784
785 std::env::set_var("TERM_PROGRAM", "iTerm.app");
787 assert!(supports_iterm2());
788
789 std::env::set_var("TERM_PROGRAM", "WezTerm");
791 assert!(supports_iterm2());
792
793 std::env::set_var("TERM_PROGRAM", "rio");
795 assert!(supports_iterm2());
796
797 std::env::set_var("TERM_PROGRAM", "Apple_Terminal");
799 assert!(!supports_iterm2());
800
801 std::env::remove_var("TERM_PROGRAM");
803 assert!(!supports_iterm2());
804 }
805
806 #[test]
807 fn test_supports_sixel_heuristics() {
808 let _guard = EnvGuard::new(&["TERM", "TERM_PROGRAM", "WT_SESSION"]);
809
810 std::env::remove_var("TERM");
812 std::env::remove_var("TERM_PROGRAM");
813 std::env::remove_var("WT_SESSION");
814 assert!(!supports_sixel());
815
816 std::env::set_var("TERM", "xterm-sixel");
818 assert!(supports_sixel());
819
820 std::env::set_var("TERM", "foot");
822 assert!(supports_sixel());
823
824 std::env::set_var("TERM", "MLTerm");
826 assert!(supports_sixel());
827
828 std::env::remove_var("TERM");
830 std::env::set_var("TERM_PROGRAM", "WezTerm");
831 assert!(supports_sixel());
832
833 std::env::set_var("TERM_PROGRAM", "iTerm.app");
835 assert!(supports_sixel());
836
837 std::env::set_var("TERM_PROGRAM", "rio");
839 assert!(supports_sixel());
840
841 std::env::remove_var("TERM_PROGRAM");
843 std::env::set_var("WT_SESSION", "active");
844 assert!(supports_sixel());
845 }
846
847 #[test]
848 fn test_get_embedded_logo() {
849 let logo = get_embedded_logo(Some("arch"));
850 assert!(logo.is_some());
851 let logo = get_embedded_logo(Some("pop"));
852 assert!(logo.is_some());
853 let logo = get_embedded_logo(Some("manjaro"));
854 assert!(logo.is_some());
855 let logo = get_embedded_logo(Some("endeavouros"));
856 assert!(logo.is_some());
857 let logo = get_embedded_logo(Some("opensuse"));
858 assert!(logo.is_some());
859 let logo = get_embedded_logo(Some("opensuse-leap"));
860 assert!(logo.is_some());
861 let logo = get_embedded_logo(Some("opensuse-tumbleweed"));
862 assert!(logo.is_some());
863 let logo = get_embedded_logo(Some("macos"));
864 assert!(logo.is_some());
865 let logo = get_embedded_logo(Some("windows"));
866 assert!(logo.is_some());
867 let logo = get_embedded_logo(None);
868 assert!(logo.is_some());
869 }
870
871 #[test]
872 fn test_get_ascii_logo_new_distros() {
873 let pop = get_ascii_logo(Some("pop"));
874 assert!(!pop.is_empty());
875 assert!(pop.iter().any(|line| line.contains("767")));
876
877 let manjaro = get_ascii_logo(Some("manjaro"));
878 assert!(!manjaro.is_empty());
879 assert!(manjaro.iter().any(|line| line.contains("████████")));
880
881 let endeavouros = get_ascii_logo(Some("endeavouros"));
882 assert!(!endeavouros.is_empty());
883 assert!(endeavouros.iter().any(|line| line.contains("ssso")));
884
885 let opensuse = get_ascii_logo(Some("opensuse"));
886 assert!(!opensuse.is_empty());
887 assert!(opensuse.iter().any(|line| line.contains("O0000Ok")));
888
889 let macos = get_ascii_logo(Some("macos"));
890 assert!(!macos.is_empty());
891 assert!(macos
892 .iter()
893 .any(|line| line.contains("cKMMMMMMMMMMNWMMMMMMMMMM0")));
894
895 let windows = get_ascii_logo(Some("windows"));
896 assert!(!windows.is_empty());
897 assert!(windows
898 .iter()
899 .any(|line| line.contains("AEEEtttt::::ztF") || line.contains("tt:::tt333EE3")));
900 }
901}