1#[cfg(feature = "graphics")]
9pub fn get_embedded_logo(distro: Option<&str>) -> Option<&'static [u8]> {
10 let d = distro.map(|s| s.to_lowercase());
11 match d.as_deref() {
12 Some("arch") => Some(include_bytes!("../assets/logos/arch.png")),
13 Some("debian") => Some(include_bytes!("../assets/logos/debian.png")),
14 Some("fedora") => Some(include_bytes!("../assets/logos/fedora.png")),
15 Some("nixos") => Some(include_bytes!("../assets/logos/nixos.png")),
16 Some("ubuntu") => Some(include_bytes!("../assets/logos/ubuntu.png")),
17 _ => Some(include_bytes!("../assets/logos/tux.png")),
18 }
19}
20
21pub fn detect_distro() -> Option<String> {
22 if let Ok(content) = std::fs::read_to_string("/etc/os-release") {
23 for line in content.lines() {
24 if line.starts_with("ID=") {
25 let id = line.trim_start_matches("ID=").trim_matches('"');
26 return Some(id.to_string());
27 }
28 }
29 }
30 None
31}
32
33pub fn get_ascii_logo(distro: Option<&str>) -> Vec<String> {
34 let d = distro.map(|s| s.to_lowercase());
35
36 match d.as_deref() {
37 Some("arch") => vec![
39 " -`".to_string(),
40 " .o+`".to_string(),
41 " `ooo/".to_string(),
42 " `+oooo:".to_string(),
43 " `+oooooo:".to_string(),
44 " -+oooooo+:".to_string(),
45 " `/:-:++oooo+:".to_string(),
46 " `/++++/+++++++:".to_string(),
47 " `/++++++++++++++:".to_string(),
48 " `/+++ooooooooooooo/`".to_string(),
49 " ./ooosssso++osssssso+`".to_string(),
50 " .oossssso-````/ossssss+`".to_string(),
51 " -osssssso. :ssssssso.".to_string(),
52 " :osssssss/ osssso+++.".to_string(),
53 " /ossssssss/ +ssssooo/-".to_string(),
54 " `/ossssso+/:- -:/+osssso+-".to_string(),
55 " `+sso+:-` `.-/+oso:".to_string(),
56 "`++:. `-/+/".to_string(),
57 ".` `".to_string(),
58 ],
59
60 Some("debian") => vec![
62 " _,met$$$$$$$$$$gg.".to_string(),
63 " ,g$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P.".to_string(),
64 " ,g$$$$P\"\" \"\"\"Y$$$$.\".".to_string(),
65 " ,$$$$P' `$$$$$$.".to_string(),
66 "',$$$$P ,ggs. `$$$$b:".to_string(),
67 "`d$$$$' ,$P\"' . $$$$$$".to_string(),
68 " $$$$P d$' , $$$$P".to_string(),
69 " $$$$: $$$. - ,d$$$$'".to_string(),
70 " $$$$; Y$b._ _,d$P'".to_string(),
71 " Y$$$$. .`\"Y$$$$$$$$P\"'".to_string(),
72 " `$$$$b \"-.__".to_string(),
73 " `Y$$$$b".to_string(),
74 " `Y$$$$.".to_string(),
75 " `$$$$b.".to_string(),
76 " `Y$$$$b.".to_string(),
77 " `\"Y$$b._".to_string(),
78 " `\"\"\"\"".to_string(),
79 ],
80
81 Some("fedora") => vec![
83 " .',;::::;,'.".to_string(),
84 " .';:cccccccccccc:;,.".to_string(),
85 " .;cccccccccccccccccccccc;.".to_string(),
86 " .:cccccccccccccccccccccccccc:.".to_string(),
87 " .;ccccccccccccc;.:dddl:.;ccccccc;.".to_string(),
88 " .:ccccccccccccc;OWMKOOXMWd;ccccccc:.".to_string(),
89 ".:ccccccccccccc;KMMc;cc;xMMc;ccccccc:.".to_string(),
90 ",cccccccccccccc;MMM.;cc;;WW:;cccccccc,".to_string(),
91 ":cccccccccccccc;MMM.;cccccccccccccccc:".to_string(),
92 ":ccccccc;oxOOOo;MMM000k.;cccccccccccc:".to_string(),
93 "cccccc;0MMKxdd:;MMMkddc.;cccccccccccc;".to_string(),
94 "ccccc;XMO';cccc;MMM.;cccccccccccccccc'".to_string(),
95 "ccccc;MMo;ccccc;MMW.;ccccccccccccccc;".to_string(),
96 "ccccc;0MNc.ccc.xMMd;ccccccccccccccc;".to_string(),
97 "cccccc;dNMWXXXWM0:;cccccccccccccc:,".to_string(),
98 "cccccccc;.:odl:.;cccccccccccccc:,.".to_string(),
99 "ccccccccccccccccccccccccccccc:'.".to_string(),
100 ":ccccccccccccccccccccccc:;,..".to_string(),
101 " ':cccccccccccccccc::;,.".to_string(),
102 ],
103
104 Some("ubuntu") => vec![
106 " ....".to_string(),
107 " .',:clooo: .:looooo:.".to_string(),
108 " .;looooooooc .oooooooooo'".to_string(),
109 " .;looooool:,''. :ooooooooooc".to_string(),
110 " ;looool;. 'oooooooooo,".to_string(),
111 " ;clool' .cooooooc. ,,".to_string(),
112 " ... ...... .:oo,".to_string(),
113 " .;clol:,. .loooo'".to_string(),
114 ":ooooooooo, 'ooool".to_string(),
115 "'ooooooooooo. loooo.".to_string(),
116 "'ooooooooool coooo.".to_string(),
117 " ,loooooooc. .loooo.".to_string(),
118 " .,;;;'. ;ooooc".to_string(),
119 " ... ,ooool.".to_string(),
120 " .cooooc. ..',,'. .cooo.".to_string(),
121 " ;ooooo:. ;oooooooc. :l.".to_string(),
122 " .coooooc,.. coooooooooo.".to_string(),
123 " .:ooooooolc:. .ooooooooooo'".to_string(),
124 " .':loooooo; ,oooooooooc".to_string(),
125 " ..';::c' .;loooo:'".to_string(),
126 ],
127
128 Some("nixos") => vec![
130 " \\ \\ /".to_string(),
131 " \\ \\ /".to_string(),
132 " \\ \\ /".to_string(),
133 " \\ \\ /".to_string(),
134 " \\ \\ /".to_string(),
135 " \\ \\ /".to_string(),
136 " \\ \\ /".to_string(),
137 " \\ \\ /".to_string(),
138 " \\ \\ /".to_string(),
139 " \\ \\ /".to_string(),
140 " \\ \\ /".to_string(),
141 " \\ \\ /".to_string(),
142 " \\ \\ /".to_string(),
143 " \\ \\ /".to_string(),
144 " \\ \\ /".to_string(),
145 " \\ \\ /".to_string(),
146 " \\ \\ /".to_string(),
147 " \\ \\ /".to_string(),
148 " \\ \\ /".to_string(),
149 ],
150
151 _ => vec![
153 " .--.".to_string(),
154 " |o_o |".to_string(),
155 " |:_/ |".to_string(),
156 " // \\ \\".to_string(),
157 " (| | )".to_string(),
158 "'/_ _/'".to_string(),
159 " |___|".to_string(),
160 ],
161 }
162}
163
164pub fn print_ascii_logo(logo: &[String]) {
165 for line in logo {
166 println!("{}", line);
167 }
168}
169
170pub fn supports_graphical_logo() -> bool {
171 if let Ok(term) = std::env::var("TERM") {
172 if term.contains("kitty") {
173 return true;
174 }
175 }
176 if let Ok(program) = std::env::var("TERM_PROGRAM") {
177 if program.to_lowercase().contains("iterm") {
178 return true;
179 }
180 }
181 false
182}
183
184pub fn chafa_available() -> bool {
186 std::process::Command::new("chafa")
187 .arg("--version")
188 .output()
189 .map(|o| o.status.success())
190 .unwrap_or(false)
191}
192
193fn write_temp_logo(bytes: &[u8]) -> std::io::Result<std::path::PathBuf> {
195 let temp_path = std::env::temp_dir().join(format!("retch_logo_{}.png", std::process::id()));
196 std::fs::write(&temp_path, bytes)?;
197 Ok(temp_path)
198}
199
200pub fn print_with_chafa(path: &std::path::Path) -> bool {
202 let output = std::process::Command::new("chafa")
203 .arg("--format")
204 .arg("symbols")
205 .arg("--size")
206 .arg("40x20")
207 .arg(path)
208 .output();
209
210 match output {
211 Ok(out) if out.status.success() => {
212 print!("{}", String::from_utf8_lossy(&out.stdout));
213 true
214 }
215 _ => false,
216 }
217}
218
219pub fn print_distro_logo(distro: Option<&str>) {
224 print_distro_logo_with_ascii(distro, false);
225}
226
227pub fn print_distro_logo_with_ascii(distro: Option<&str>, ascii_only: bool) {
229 if ascii_only {
230 let art = get_ascii_logo(distro);
232 for line in art {
233 println!("{}", line);
234 }
235 return;
236 }
237
238 let supports_graphics = supports_graphical_logo();
239 let has_chafa = chafa_available();
240
241 #[cfg(feature = "graphics")]
243 if supports_graphics {
244 if let Some(bytes) = get_embedded_logo(distro) {
245 if !bytes.is_empty() {
246 print_graphical_logo(bytes);
247 return;
248 }
249 }
250 }
251
252 if has_chafa {
254 if let Some(bytes) = get_embedded_logo(distro) {
255 if bytes.len() > 100 {
256 if let Ok(temp_path) = write_temp_logo(bytes) {
257 if print_with_chafa(&temp_path) {
258 let _ = std::fs::remove_file(&temp_path);
259 return;
260 }
261 let _ = std::fs::remove_file(&temp_path);
262 }
263 }
264 }
265 }
266
267 let art = get_ascii_logo(distro);
269 for line in art {
270 println!("{}", line);
271 }
272}
273
274#[cfg(feature = "graphics")]
275pub fn print_graphical_logo(image_data: &[u8]) {
276 use base64::Engine;
277
278 let (width, height) = image::load_from_memory(image_data)
279 .map(|img| (img.width(), img.height()))
280 .unwrap_or((0, 0));
281
282 let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
283
284 if width > 0 && height > 0 {
285 println!("\x1b_Gf=100,s={},v={},a=T;{}\x1b\\", width, height, encoded);
286 } else {
287 println!("\x1b_Gf=100,a=T;{}", encoded);
288 }
289}
290
291#[cfg(not(feature = "graphics"))]
292pub fn print_graphical_logo(_image_data: &[u8]) {
293 println!("[Graphical logo support requires --features graphics]");
294}
295
296#[cfg(feature = "graphics")]
297use image::ImageFormat;
298
299#[cfg(feature = "graphics")]
300pub fn print_graphical_logo_from_path(path: &std::path::Path) {
301 match image::open(path) {
302 Ok(img) => {
303 let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
304 let mut png_data = Vec::new();
305 if resized
306 .write_to(&mut std::io::Cursor::new(&mut png_data), ImageFormat::Png)
307 .is_ok()
308 {
309 print_graphical_logo(&png_data);
310 } else {
311 println!("[Failed to encode logo as PNG]");
312 }
313 }
314 Err(_) => {
315 println!("[Could not load graphical logo from {}]", path.display());
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_get_ascii_logo_arch() {
326 let logo = get_ascii_logo(Some("arch"));
327 assert!(!logo.is_empty());
328 assert!(logo[0].contains("`"));
329 }
330
331 #[test]
332 fn test_get_ascii_logo_unknown() {
333 let logo = get_ascii_logo(Some("unknown_distro"));
334 assert!(!logo.is_empty());
335 assert!(logo.iter().any(|line| line.contains("o_o")));
337 }
338
339 #[test]
340 fn test_get_ascii_logo_none() {
341 let logo = get_ascii_logo(None);
342 assert!(!logo.is_empty());
343 }
344}