1#[cfg(feature = "graphics")]
13pub fn get_embedded_logo(distro: Option<&str>) -> Option<&'static [u8]> {
14 let d = distro.map(|s| s.to_lowercase());
15 match d.as_deref() {
16 Some("arch") => Some(include_bytes!("../assets/logos/arch.png")),
17 Some("debian") => Some(include_bytes!("../assets/logos/debian.png")),
18 Some("fedora") => Some(include_bytes!("../assets/logos/fedora.png")),
19 Some("nixos") => Some(include_bytes!("../assets/logos/nixos.png")),
20 Some("ubuntu") => Some(include_bytes!("../assets/logos/ubuntu.png")),
21 _ => Some(include_bytes!("../assets/logos/tux.png")),
22 }
23}
24
25#[cfg(not(feature = "graphics"))]
27pub fn get_embedded_logo(_distro: Option<&str>) -> Option<&'static [u8]> {
28 Some(include_bytes!("../assets/logos/tux.png"))
29}
30
31pub fn detect_distro() -> Option<String> {
33 if let Ok(content) = std::fs::read_to_string("/etc/os-release") {
34 for line in content.lines() {
35 if line.starts_with("ID=") {
36 let id = line.trim_start_matches("ID=").trim_matches('"');
37 return Some(id.to_string());
38 }
39 }
40 }
41 None
42}
43
44pub fn get_ascii_logo(distro: Option<&str>) -> Vec<String> {
48 let d = distro.map(|s| s.to_lowercase());
49
50 match d.as_deref() {
51 Some("arch") => vec![
53 " -`".to_string(),
54 " .o+`".to_string(),
55 " `ooo/".to_string(),
56 " `+oooo:".to_string(),
57 " `+oooooo:".to_string(),
58 " -+oooooo+:".to_string(),
59 " `/:-:++oooo+:".to_string(),
60 " `/++++/+++++++:".to_string(),
61 " `/++++++++++++++:".to_string(),
62 " `/+++ooooooooooooo/`".to_string(),
63 " ./ooosssso++osssssso+`".to_string(),
64 " .oossssso-````/ossssss+`".to_string(),
65 " -osssssso. :ssssssso.".to_string(),
66 " :osssssss/ osssso+++.".to_string(),
67 " /ossssssss/ +ssssooo/-".to_string(),
68 " `/ossssso+/:- -:/+osssso+-".to_string(),
69 " `+sso+:-` `.-/+oso:".to_string(),
70 "`++:. `-/+/".to_string(),
71 ".` `".to_string(),
72 ],
73
74 Some("debian") => vec![
76 " _,met$$$$$$$$$$gg.".to_string(),
77 " ,g$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P.".to_string(),
78 " ,g$$$$P\"\" \"\"\"Y$$$$.\".".to_string(),
79 " ,$$$$P' `$$$$$$.".to_string(),
80 "',$$$$P ,ggs. `$$$$b:".to_string(),
81 "`d$$$$' ,$P\"' . $$$$$$".to_string(),
82 " $$$$P d$' , $$$$P".to_string(),
83 " $$$$: $$$. - ,d$$$$'".to_string(),
84 " $$$$; Y$b._ _,d$P'".to_string(),
85 " Y$$$$. .`\"Y$$$$$$$$P\"'".to_string(),
86 " `$$$$b \"-.__".to_string(),
87 " `Y$$$$b".to_string(),
88 " `Y$$$$.".to_string(),
89 " `$$$$b.".to_string(),
90 " `Y$$$$b.".to_string(),
91 " `\"Y$$b._".to_string(),
92 " `\"\"\"\"".to_string(),
93 ],
94
95 Some("fedora") => vec![
97 " .',;::::;,'.".to_string(),
98 " .';:cccccccccccc:;,.".to_string(),
99 " .;cccccccccccccccccccccc;.".to_string(),
100 " .:cccccccccccccccccccccc:.".to_string(),
101 " .;ccccccccccccc;.:dddl:.;ccccccc;.".to_string(),
102 " .:ccccccccccccc;OWMKOOXMWd;ccccccc:.".to_string(),
103 ".:ccccccccccccc;KMMc;cc;xMMc;ccccccc:.".to_string(),
104 ",cccccccccccccc;MMM.;cc;;WW:;cccccccc,".to_string(),
105 ":cccccccccccccc;MMM.;cccccccccccccccc:".to_string(),
106 ":ccccccc;oxOOOo;MMM000k.;cccccccccccc:".to_string(),
107 "cccccc;0MMKxdd:;MMMkddc.;cccccccccccc;".to_string(),
108 "ccccc;XMO';cccc;MMM.;cccccccccccccccc'".to_string(),
109 "ccccc;MMo;ccccc;MMW.;ccccccccccccccc;".to_string(),
110 "ccccc;0MNc.ccc.xMMd;ccccccccccccccc;".to_string(),
111 "cccccc;dNMWXXXWM0:;cccccccccccccc:,".to_string(),
112 "cccccccc;.:odl:.;cccccccccccccc:,.".to_string(),
113 "ccccccccccccccccccccccccccccc:'.".to_string(),
114 ":ccccccccccccccccccccccc:;,..".to_string(),
115 ],
116
117 Some("nixos") => vec![
119 " ::::. ':::::".to_string(),
120 " ':::::::::. ':::::::::".to_string(),
121 " .::::::::::::: ::::::::::::.".to_string(),
122 " :::::::::::' ::::::::::::".to_string(),
123 " .::::::::::' :::::::::::.".to_string(),
124 " :::::::::: :::::::::::".to_string(),
125 " ::::::::: :::::::::::".to_string(),
126 " :::::::: ::::::::::".to_string(),
127 " '::::::. '::::::::'".to_string(),
128 " '::::: '::::::'".to_string(),
129 " '::: '::::'".to_string(),
130 " ': ':".to_string(),
131 ],
132
133 Some("ubuntu") => vec![
135 " ....".to_string(),
136 " ....,,:::::ccccclllclllcc:::;,,....".to_string(),
137 " ..,;::cccllllllllllllllllllllllllcc::;..".to_string(),
138 " ..,;::cccllllllllllllllllllllllllllllcc::;..".to_string(),
139 " ..,;::cccllllllllllllllllllllllllllllllllcc::;..".to_string(),
140 " ..,;::cccllllllllllllllllllllllllllllllllllcc::;..".to_string(),
141 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
142 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
143 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
144 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
145 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
146 "..,;::cccllllllllllllllllllllllllllllllllllllcc::;.".to_string(),
147 ],
148
149 _ => vec![
151 " .--.".to_string(),
152 " |o_o |".to_string(),
153 " |:_/ |".to_string(),
154 " // \\ \\".to_string(),
155 " (| | )".to_string(),
156 "/'\\_ _/`\\".to_string(),
157 "\\___)=(___/".to_string(),
158 ],
159 }
160}
161
162pub fn supports_kitty() -> bool {
164 std::env::var("TERM")
165 .map(|t| t == "xterm-kitty")
166 .unwrap_or(false)
167 || std::env::var("TERMINAL_EMULATOR")
168 .map(|t| t == "iterm-kitty" || t == "iTerm.app")
169 .unwrap_or(false)
170 || std::env::var("TERM_PROGRAM")
171 .map(|t| t == "rio")
172 .unwrap_or(false)
173}
174
175pub fn supports_iterm2() -> bool {
177 if let Ok(prog) = std::env::var("TERM_PROGRAM") {
178 if prog == "iTerm.app" || prog == "WezTerm" || prog == "rio" {
179 return true;
180 }
181 }
182 false
183}
184
185pub fn supports_sixel() -> bool {
187 if let Ok(term) = std::env::var("TERM") {
188 let term = term.to_lowercase();
189 if term.contains("sixel") || term.contains("foot") || term.contains("mlterm") {
190 return true;
191 }
192 }
193
194 if let Ok(prog) = std::env::var("TERM_PROGRAM") {
195 if prog == "WezTerm" || prog == "iTerm.app" || prog == "rio" {
196 return true;
197 }
198 }
199
200 if std::env::var("WT_SESSION").is_ok() {
201 return true;
202 }
203
204 false
205}
206
207pub fn chafa_available() -> bool {
209 std::process::Command::new("chafa")
210 .arg("--version")
211 .output()
212 .map(|o| o.status.success())
213 .unwrap_or(false)
214}
215
216fn write_temp_logo(bytes: &[u8]) -> std::io::Result<std::path::PathBuf> {
218 let temp_path = std::env::temp_dir().join(format!("retch_logo_{}.png", std::process::id()));
219 std::fs::write(&temp_path, bytes)?;
220 Ok(temp_path)
221}
222
223pub fn print_with_chafa(path: &std::path::Path) -> bool {
228 let output = std::process::Command::new("chafa")
229 .arg("--format")
230 .arg("symbols")
231 .arg("--size")
232 .arg("40x20")
233 .arg(path)
234 .output();
235
236 match output {
237 Ok(out) if out.status.success() => {
238 print!("{}", String::from_utf8_lossy(&out.stdout));
239 true
240 }
241 _ => false,
242 }
243}
244
245pub fn print_distro_logo(distro: Option<&str>) {
250 print_distro_logo_with_ascii(distro, false);
251}
252
253pub fn print_distro_logo_with_ascii(distro: Option<&str>, ascii_only: bool) {
258 if ascii_only {
259 let art = get_ascii_logo(distro);
261 for line in art {
262 println!("{}", line);
263 }
264 return;
265 }
266
267 let supports_kitty = supports_kitty();
268 let supports_iterm2 = supports_iterm2();
269 let supports_sixel = supports_sixel();
270 let has_chafa = chafa_available();
271
272 #[cfg(feature = "graphics")]
274 if supports_kitty {
275 if let Some(bytes) = get_embedded_logo(distro) {
276 if !bytes.is_empty() {
277 print_graphical_logo(bytes);
278 return;
279 }
280 }
281 }
282
283 #[cfg(feature = "graphics")]
285 if supports_iterm2 {
286 if let Some(bytes) = get_embedded_logo(distro) {
287 if !bytes.is_empty() {
288 print_iterm2_logo(bytes);
289 return;
290 }
291 }
292 }
293
294 #[cfg(feature = "graphics")]
296 if supports_sixel {
297 if let Some(bytes) = get_embedded_logo(distro) {
298 if !bytes.is_empty() {
299 print_sixel_logo(bytes);
300 return;
301 }
302 }
303 }
304
305 if has_chafa {
307 if let Some(bytes) = get_embedded_logo(distro) {
308 if bytes.len() > 100 {
309 if let Ok(temp_path) = write_temp_logo(bytes) {
310 if print_with_chafa(&temp_path) {
311 let _ = std::fs::remove_file(&temp_path);
312 return;
313 }
314 let _ = std::fs::remove_file(&temp_path);
315 }
316 }
317 }
318 }
319
320 let art = get_ascii_logo(distro);
322 for line in art {
323 println!("{}", line);
324 }
325}
326
327#[cfg(feature = "graphics")]
329pub fn print_iterm2_logo(image_data: &[u8]) {
330 use base64::Engine;
331 let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
332 print!(
333 "\x1b]1337;File=inline=1;preserveAspectRatio=1:{}\x07",
334 encoded
335 );
336 println!(); }
338
339#[cfg(feature = "graphics")]
341pub fn print_iterm2_logo_from_path(path: &std::path::Path) {
342 if let Ok(bytes) = std::fs::read(path) {
343 print_iterm2_logo(&bytes);
344 } else {
345 println!("[Could not read logo for iTerm2 from {}]", path.display());
346 }
347}
348
349#[cfg(not(feature = "graphics"))]
351pub fn print_iterm2_logo(_image_data: &[u8]) {
352 println!("[iTerm2 logo support requires --features graphics]");
353}
354
355#[cfg(feature = "graphics")]
357pub fn print_graphical_logo(image_data: &[u8]) {
358 use base64::Engine;
359
360 let (width, height) = image::load_from_memory(image_data)
361 .map(|img| (img.width(), img.height()))
362 .unwrap_or((0, 0));
363
364 let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
365
366 if width > 0 && height > 0 {
367 println!("\x1b_Gf=100,s={},v={},a=T;{}\x1b\\", width, height, encoded);
368 } else {
369 println!("\x1b_Gf=100,a=T;{}", encoded);
370 }
371}
372
373#[cfg(feature = "graphics")]
375pub fn print_sixel_logo(image_data: &[u8]) {
376 if let Ok(img) = image::load_from_memory(image_data) {
377 let rgba = img.to_rgba8();
378 let (width, height) = rgba.dimensions();
379 print_sixel_rgba(rgba.as_raw(), width, height);
380 }
381}
382
383#[cfg(feature = "graphics")]
385pub fn print_sixel_rgba(rgba: &[u8], width: u32, height: u32) {
386 use icy_sixel::SixelImage;
387
388 match SixelImage::try_from_rgba(rgba.to_vec(), width as usize, height as usize) {
389 Ok(sixel_img) => match sixel_img.encode() {
390 Ok(sixel_str) => {
391 print!("{}", sixel_str);
392 }
393 Err(e) => eprintln!("[Sixel Encoding Error: {}]", e),
394 },
395 Err(e) => eprintln!("[Sixel Creation Error: {}]", e),
396 }
397}
398
399#[cfg(not(feature = "graphics"))]
401pub fn print_graphical_logo(_image_data: &[u8]) {
402 println!("[Graphical logo support requires --features graphics]");
403}
404
405#[cfg(not(feature = "graphics"))]
407pub fn print_sixel_logo(_image_data: &[u8]) {
408 println!("[Sixel logo support requires --features graphics]");
409}
410
411#[cfg(feature = "graphics")]
413pub fn print_graphical_logo_from_path(path: &std::path::Path) {
414 use image::ImageFormat;
415 match image::open(path) {
416 Ok(img) => {
417 let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
418 let mut png_data = Vec::new();
419 if resized
420 .write_to(&mut std::io::Cursor::new(&mut png_data), ImageFormat::Png)
421 .is_ok()
422 {
423 print_graphical_logo(&png_data);
424 } else {
425 println!("[Failed to encode logo as PNG]");
426 }
427 }
428 Err(_) => {
429 println!("[Could not load graphical logo from {}]", path.display());
430 }
431 }
432}
433
434#[cfg(feature = "graphics")]
436pub fn print_sixel_logo_from_path(path: &std::path::Path) {
437 match image::open(path) {
438 Ok(img) => {
439 let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
440 let rgba = resized.to_rgba8();
441 let (width, height) = rgba.dimensions();
442 print_sixel_rgba(rgba.as_raw(), width, height);
443 }
444 Err(_) => {
445 println!("[Could not load logo for Sixel from {}]", path.display());
446 }
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 #[test]
455 fn test_get_ascii_logo_arch() {
456 let logo = get_ascii_logo(Some("arch"));
457 assert!(!logo.is_empty());
458 assert!(logo[0].contains("`"));
459 }
460
461 #[test]
462 fn test_get_ascii_logo_unknown() {
463 let logo = get_ascii_logo(Some("unknown_distro"));
464 assert!(!logo.is_empty());
465 assert!(logo.iter().any(|line| line.contains("o_o")));
467 }
468
469 #[test]
470 fn test_get_ascii_logo_none() {
471 let logo = get_ascii_logo(None);
472 assert!(!logo.is_empty());
473 }
474}