Skip to main content

retch_cli/
logo.rs

1// SPDX-FileCopyrightText: 2026 Ken Tobias
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! ASCII and graphical logo definitions and rendering.
5//!
6//! Contains embedded distro logos and logic for rendering them
7//! as text or images (e.g., Sixel, Kitty, iTerm).
8
9// Exact Fastfetch ASCII logos (intact, unmodified)
10// Source: https://github.com/fastfetch-cli/fastfetch/src/logo/ascii/
11
12// Embedded distro logos (PNG)
13// Place real logos in assets/logos/<distro>.png
14// Example: assets/logos/arch.png, assets/logos/fedora.png, assets/logos/tux.png
15
16/// Returns the raw PNG bytes for an embedded distro logo.
17///
18/// If the distro is not recognized, it falls back to the Tux (Linux) logo.
19/// Only available when the `graphics` feature is enabled.
20#[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/// Fallback for non-graphics build to provide Tux bytes for Chafa if needed.
42#[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
47/// Attempts to detect the current operating system distribution.
48pub 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
71///// Returns a list of strings representing the ASCII art for a given distro with color placeholders.
72///
73/// All ASCII logos are sourced from or compatible with Fastfetch.
74pub 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") => {
79            let logo = include_str!("../assets/logos/arch.txt");
80            logo.lines().map(|s| s.to_string()).collect()
81        }
82        Some("debian") => {
83            let logo = include_str!("../assets/logos/debian.txt");
84            logo.lines().map(|s| s.to_string()).collect()
85        }
86        Some("fedora") => {
87            let logo = include_str!("../assets/logos/fedora.txt");
88            logo.lines().map(|s| s.to_string()).collect()
89        }
90        Some("nixos") => {
91            let logo = include_str!("../assets/logos/nixos.txt");
92            logo.lines().map(|s| s.to_string()).collect()
93        }
94        Some("ubuntu") => {
95            let logo = include_str!("../assets/logos/ubuntu.txt");
96            logo.lines().map(|s| s.to_string()).collect()
97        }
98        Some("pop") => {
99            let logo = include_str!("../assets/logos/pop.txt");
100            logo.lines().map(|s| s.to_string()).collect()
101        }
102        Some("manjaro") => {
103            let logo = include_str!("../assets/logos/manjaro.txt");
104            logo.lines().map(|s| s.to_string()).collect()
105        }
106        Some("endeavouros") => {
107            let logo = include_str!("../assets/logos/endeavouros.txt");
108            logo.lines().map(|s| s.to_string()).collect()
109        }
110        Some("opensuse") | Some("opensuse-leap") | Some("opensuse-tumbleweed") => {
111            let logo = include_str!("../assets/logos/opensuse.txt");
112            logo.lines().map(|s| s.to_string()).collect()
113        }
114        Some("macos") => {
115            let logo = include_str!("../assets/logos/macos.txt");
116            logo.lines().map(|s| s.to_string()).collect()
117        }
118        Some("windows") => {
119            let logo = include_str!("../assets/logos/windows.txt");
120            logo.lines().map(|s| s.to_string()).collect()
121        }
122
123        // Fallback: Tux (Linux)
124        _ => {
125            let logo = include_str!("../assets/logos/tux.txt");
126            logo.lines().map(|s| s.to_string()).collect()
127        }
128    }
129}
130
131/// Returns dynamic ANSI color arrays for a given distribution.
132pub fn get_distro_colors(distro: Option<&str>) -> Vec<&'static str> {
133    let d = distro.map(|s| s.to_lowercase());
134    match d.as_deref() {
135        Some("arch") => vec!["\x1b[36m", "\x1b[37m"],
136        Some("debian") => vec!["\x1b[31m", "\x1b[37m"],
137        Some("fedora") => vec!["\x1b[34m", "\x1b[37m"],
138        Some("nixos") => vec![
139            "\x1b[34m", "\x1b[36m", "\x1b[34m", "\x1b[36m", "\x1b[34m", "\x1b[36m",
140        ],
141        Some("ubuntu") => vec!["\x1b[33m", "\x1b[31m"],
142        Some("pop") => vec!["\x1b[36m", "\x1b[37m"],
143        Some("manjaro") => vec!["\x1b[32m"],
144        Some("endeavouros") => vec!["\x1b[35m", "\x1b[31m", "\x1b[34m"],
145        Some("opensuse") | Some("opensuse-leap") | Some("opensuse-tumbleweed") => {
146            vec!["\x1b[32m", "\x1b[37m"]
147        }
148        Some("macos") => vec!["\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m", "\x1b[34m"],
149        Some("windows") => vec!["\x1b[36m"],
150        _ => vec!["\x1b[30m", "\x1b[37m", "\x1b[33m"], // Tux
151    }
152}
153
154/// Interpolates placeholders `${1}`...`${9}` or `$1`...`$9` with dynamic ANSI colors and appends a reset at the end.
155pub fn get_distro_logo_lines(distro: Option<&str>) -> Vec<String> {
156    let raw_lines = get_ascii_logo(distro);
157    let colors = get_distro_colors(distro);
158    let default_color = colors.first().copied().unwrap_or("\x1b[0m");
159
160    raw_lines
161        .into_iter()
162        .map(|line| {
163            let mut formatted = line;
164            for i in 1..=9 {
165                let color_val = colors.get(i - 1).copied().unwrap_or("\x1b[0m");
166                let placeholder = format!("${{{}}}", i);
167                formatted = formatted.replace(&placeholder, color_val);
168                let placeholder_short = format!("${}", i);
169                formatted = formatted.replace(&placeholder_short, color_val);
170            }
171            if !formatted.is_empty() {
172                format!("{}{}\x1b[0m", default_color, formatted)
173            } else {
174                formatted
175            }
176        })
177        .collect()
178}
179
180/// Checks if the terminal supports the Kitty inline image protocol.
181pub fn supports_kitty() -> bool {
182    std::env::var("TERM")
183        .map(|t| t == "xterm-kitty")
184        .unwrap_or(false)
185        || std::env::var("TERMINAL_EMULATOR")
186            .map(|t| t == "iterm-kitty" || t == "iTerm.app")
187            .unwrap_or(false)
188        || std::env::var("TERM_PROGRAM")
189            .map(|t| t == "rio")
190            .unwrap_or(false)
191}
192
193/// Checks if the terminal supports the iTerm2 inline image protocol.
194pub fn supports_iterm2() -> bool {
195    if let Ok(prog) = std::env::var("TERM_PROGRAM") {
196        if prog == "iTerm.app" || prog == "WezTerm" || prog == "rio" {
197            return true;
198        }
199    }
200    false
201}
202
203/// Checks if the terminal supports Sixel graphics (heuristic based on environment).
204pub fn supports_sixel() -> bool {
205    if let Ok(term) = std::env::var("TERM") {
206        let term = term.to_lowercase();
207        if term.contains("sixel") || term.contains("foot") || term.contains("mlterm") {
208            return true;
209        }
210    }
211
212    if let Ok(prog) = std::env::var("TERM_PROGRAM") {
213        if prog == "WezTerm" || prog == "iTerm.app" || prog == "rio" {
214            return true;
215        }
216    }
217
218    if std::env::var("WT_SESSION").is_ok() {
219        return true;
220    }
221
222    false
223}
224
225/// Checks if the `chafa` command-line tool is available in the system path.
226pub fn chafa_available() -> bool {
227    std::process::Command::new("chafa")
228        .arg("--version")
229        .output()
230        .map(|o| o.status.success())
231        .unwrap_or(false)
232}
233
234/// Write embedded logo bytes to a temporary file and return the path.
235fn write_temp_logo(bytes: &[u8]) -> std::io::Result<std::path::PathBuf> {
236    let temp_path = std::env::temp_dir().join(format!("retch_logo_{}.png", std::process::id()));
237    std::fs::write(&temp_path, bytes)?;
238    Ok(temp_path)
239}
240
241/// Attempts to render an image using the `chafa` utility.
242///
243/// Chafa renders images using high-quality Unicode symbols, providing a
244/// good graphical fallback for many terminal emulators.
245pub fn print_with_chafa(path: &std::path::Path) -> bool {
246    let output = std::process::Command::new("chafa")
247        .arg("--format")
248        .arg("symbols")
249        .arg("--size")
250        .arg("40x20")
251        .arg(path)
252        .output();
253
254    match output {
255        Ok(out) if out.status.success() => {
256            print!("{}", String::from_utf8_lossy(&out.stdout));
257            true
258        }
259        Ok(out) => {
260            eprintln!("warning: chafa failed with status: {}", out.status);
261            false
262        }
263        Err(e) => {
264            eprintln!("warning: failed to execute chafa: {}", e);
265            false
266        }
267    }
268}
269
270/// Attempts to get Chafa output as a list of lines.
271pub fn get_chafa_logo_lines(path: &std::path::Path) -> Option<Vec<String>> {
272    let output = std::process::Command::new("chafa")
273        .arg("--format")
274        .arg("symbols")
275        .arg("--size")
276        .arg("40x20")
277        .arg(path)
278        .output()
279        .ok()?;
280    if output.status.success() {
281        let content = String::from_utf8_lossy(&output.stdout);
282        Some(content.lines().map(|s| s.to_string()).collect())
283    } else {
284        None
285    }
286}
287
288/// Print logo for a distro following the strict priority:
289/// 1. Real graphic logo (if terminal supports it and embedded logo exists)
290/// 2. Chafa high-quality symbols (if chafa is available)
291/// 3. Real Fastfetch ASCII logo (always available)
292pub fn print_distro_logo(distro: Option<&str>) {
293    print_distro_logo_with_ascii(distro, false, false);
294}
295
296/// Renders the distribution logo with options to force ASCII or Chafa mode.
297///
298/// This is the primary entry point for logo rendering, handling the entire
299/// priority chain from high-res graphics down to text-based ASCII.
300/// - `ascii_only`: skip all graphical protocols and render the ASCII art directly.
301/// - `chafa_only`: skip Kitty/iTerm2/Sixel and go straight to Chafa (falls back
302///   to ASCII if Chafa is unavailable). `ascii_only` takes precedence.
303pub fn print_distro_logo_with_ascii(distro: Option<&str>, ascii_only: bool, chafa_only: bool) {
304    if ascii_only {
305        // Force ASCII path
306        let art = get_distro_logo_lines(distro);
307        for line in art {
308            println!("{}", line);
309        }
310        return;
311    }
312
313    let has_chafa = chafa_available();
314
315    if !chafa_only {
316        let supports_kitty = supports_kitty();
317        let supports_iterm2 = supports_iterm2();
318        let supports_sixel = supports_sixel();
319
320        // 1. Try embedded graphical logo (Kitty)
321        #[cfg(feature = "graphics")]
322        if supports_kitty {
323            if let Some(bytes) = get_embedded_logo(distro) {
324                if !bytes.is_empty() {
325                    print_graphical_logo(bytes);
326                    return;
327                }
328            }
329        }
330
331        // 2. Try embedded graphical logo (iTerm2)
332        #[cfg(feature = "graphics")]
333        if supports_iterm2 {
334            if let Some(bytes) = get_embedded_logo(distro) {
335                if !bytes.is_empty() {
336                    print_iterm2_logo(bytes);
337                    return;
338                }
339            }
340        }
341
342        // 3. Try embedded graphical logo (Sixel)
343        #[cfg(feature = "graphics")]
344        if supports_sixel {
345            if let Some(bytes) = get_embedded_logo(distro) {
346                if !bytes.is_empty() {
347                    print_sixel_logo(bytes);
348                    return;
349                }
350            }
351        }
352    }
353
354    // 4. Try chafa using embedded distro logo
355    if has_chafa {
356        if let Some(bytes) = get_embedded_logo(distro) {
357            if bytes.len() > 100 {
358                if let Ok(temp_path) = write_temp_logo(bytes) {
359                    if print_with_chafa(&temp_path) {
360                        let _ = std::fs::remove_file(&temp_path);
361                        return;
362                    }
363                    let _ = std::fs::remove_file(&temp_path);
364                }
365            }
366        }
367    }
368
369    // 5. Final fallback: Real Fastfetch ASCII logo
370    let art = get_distro_logo_lines(distro);
371    for line in art {
372        println!("{}", line);
373    }
374}
375
376/// Renders a raw image buffer using the iTerm2 inline image protocol.
377#[cfg(feature = "graphics")]
378pub fn print_iterm2_logo(image_data: &[u8]) {
379    use base64::Engine;
380    let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
381    print!(
382        "\x1b]1337;File=inline=1;preserveAspectRatio=1:{}\x07",
383        encoded
384    );
385    println!(); // iTerm2 typically needs a newline after the logo
386}
387
388/// Loads an image from a file and prints it using the iTerm2 protocol.
389#[cfg(feature = "graphics")]
390pub fn print_iterm2_logo_from_path(path: &std::path::Path) {
391    if let Ok(bytes) = std::fs::read(path) {
392        print_iterm2_logo(&bytes);
393    } else {
394        println!("[Could not read logo for iTerm2 from {}]", path.display());
395    }
396}
397
398/// Placeholder for iTerm2 logo rendering when the `graphics` feature is disabled.
399#[cfg(not(feature = "graphics"))]
400pub fn print_iterm2_logo(_image_data: &[u8]) {
401    println!("[iTerm2 logo support requires --features graphics]");
402}
403
404/// Renders a raw image buffer using the Kitty graphics protocol.
405#[cfg(feature = "graphics")]
406pub fn print_graphical_logo(image_data: &[u8]) {
407    use base64::Engine;
408
409    let (width, height) = image::load_from_memory(image_data)
410        .map(|img| (img.width(), img.height()))
411        .unwrap_or((0, 0));
412
413    let encoded = base64::engine::general_purpose::STANDARD.encode(image_data);
414
415    if width > 0 && height > 0 {
416        println!("\x1b_Gf=100,s={},v={},a=T;{}\x1b\\", width, height, encoded);
417    } else {
418        println!("\x1b_Gf=100,a=T;{}", encoded);
419    }
420}
421
422/// Renders a raw image buffer (e.g. PNG bytes) using the Sixel graphics protocol.
423#[cfg(feature = "graphics")]
424pub fn print_sixel_logo(image_data: &[u8]) {
425    if let Ok(img) = image::load_from_memory(image_data) {
426        let rgba = img.to_rgba8();
427        let (width, height) = rgba.dimensions();
428        print_sixel_rgba(rgba.as_raw(), width, height);
429    }
430}
431
432/// Renders raw RGBA pixels using the Sixel graphics protocol.
433#[cfg(feature = "graphics")]
434pub fn print_sixel_rgba(rgba: &[u8], width: u32, height: u32) {
435    use icy_sixel::SixelImage;
436
437    match SixelImage::try_from_rgba(rgba.to_vec(), width as usize, height as usize) {
438        Ok(sixel_img) => match sixel_img.encode() {
439            Ok(sixel_str) => {
440                print!("{}", sixel_str);
441            }
442            Err(e) => eprintln!("[Sixel Encoding Error: {}]", e),
443        },
444        Err(e) => eprintln!("[Sixel Creation Error: {}]", e),
445    }
446}
447
448/// Placeholder for graphical logo rendering when the `graphics` feature is disabled.
449#[cfg(not(feature = "graphics"))]
450pub fn print_graphical_logo(_image_data: &[u8]) {
451    println!("[Graphical logo support requires --features graphics]");
452}
453
454/// Placeholder for sixel logo rendering when the `graphics` feature is disabled.
455#[cfg(not(feature = "graphics"))]
456pub fn print_sixel_logo(_image_data: &[u8]) {
457    println!("[Sixel logo support requires --features graphics]");
458}
459
460/// Loads an image from a file, resizes it, and prints it using the graphics protocol.
461#[cfg(feature = "graphics")]
462pub fn print_graphical_logo_from_path(path: &std::path::Path) {
463    use image::ImageFormat;
464    match image::open(path) {
465        Ok(img) => {
466            let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
467            let mut png_data = Vec::new();
468            if resized
469                .write_to(&mut std::io::Cursor::new(&mut png_data), ImageFormat::Png)
470                .is_ok()
471            {
472                print_graphical_logo(&png_data);
473            } else {
474                println!("[Failed to encode logo as PNG]");
475            }
476        }
477        Err(_) => {
478            println!("[Could not load graphical logo from {}]", path.display());
479        }
480    }
481}
482
483/// Loads an image from a file, resizes it, and prints it using the Sixel protocol.
484#[cfg(feature = "graphics")]
485pub fn print_sixel_logo_from_path(path: &std::path::Path) {
486    match image::open(path) {
487        Ok(img) => {
488            let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
489            let rgba = resized.to_rgba8();
490            let (width, height) = rgba.dimensions();
491            print_sixel_rgba(rgba.as_raw(), width, height);
492        }
493        Err(_) => {
494            println!("[Could not load logo for Sixel from {}]", path.display());
495        }
496    }
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502
503    #[test]
504    fn test_get_ascii_logo_arch() {
505        let logo = get_ascii_logo(Some("arch"));
506        assert!(!logo.is_empty());
507        assert!(logo[0].contains("`"));
508    }
509
510    #[test]
511    fn test_get_ascii_logo_unknown() {
512        let logo = get_ascii_logo(Some("unknown_distro"));
513        assert!(!logo.is_empty());
514        // Should fall back to Tux
515        assert!(logo
516            .iter()
517            .any(|line| line.contains("o${2}_${3}o") || line.contains("o_o")));
518    }
519
520    #[test]
521    fn test_get_ascii_logo_none() {
522        let logo = get_ascii_logo(None);
523        assert!(!logo.is_empty());
524    }
525
526    static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
527
528    struct EnvGuard {
529        _mutex_guard: std::sync::MutexGuard<'static, ()>,
530        old_vars: std::collections::HashMap<&'static str, Option<String>>,
531    }
532
533    impl EnvGuard {
534        fn new(vars_to_mock: &[&'static str]) -> Self {
535            let guard = ENV_LOCK.lock().unwrap();
536            let mut old_vars = std::collections::HashMap::new();
537            for var in vars_to_mock {
538                old_vars.insert(*var, std::env::var(var).ok());
539            }
540            EnvGuard {
541                _mutex_guard: guard,
542                old_vars,
543            }
544        }
545    }
546
547    impl Drop for EnvGuard {
548        fn drop(&mut self) {
549            for (var, value) in &self.old_vars {
550                if let Some(val) = value {
551                    std::env::set_var(var, val);
552                } else {
553                    std::env::remove_var(var);
554                }
555            }
556        }
557    }
558
559    #[test]
560    fn test_supports_kitty_heuristics() {
561        let _guard = EnvGuard::new(&["TERM", "TERMINAL_EMULATOR", "TERM_PROGRAM"]);
562
563        // Test TERM=xterm-kitty
564        std::env::set_var("TERM", "xterm-kitty");
565        std::env::remove_var("TERMINAL_EMULATOR");
566        std::env::remove_var("TERM_PROGRAM");
567        assert!(supports_kitty());
568
569        // Test TERMINAL_EMULATOR=iterm-kitty
570        std::env::remove_var("TERM");
571        std::env::set_var("TERMINAL_EMULATOR", "iterm-kitty");
572        assert!(supports_kitty());
573
574        // Test TERMINAL_EMULATOR=iTerm.app
575        std::env::set_var("TERMINAL_EMULATOR", "iTerm.app");
576        assert!(supports_kitty());
577
578        // Test TERM_PROGRAM=rio
579        std::env::remove_var("TERMINAL_EMULATOR");
580        std::env::set_var("TERM_PROGRAM", "rio");
581        assert!(supports_kitty());
582
583        // Test clear env -> false
584        std::env::remove_var("TERM_PROGRAM");
585        assert!(!supports_kitty());
586    }
587
588    #[test]
589    fn test_supports_iterm2_heuristics() {
590        let _guard = EnvGuard::new(&["TERM_PROGRAM"]);
591
592        // Test TERM_PROGRAM=iTerm.app
593        std::env::set_var("TERM_PROGRAM", "iTerm.app");
594        assert!(supports_iterm2());
595
596        // Test TERM_PROGRAM=WezTerm
597        std::env::set_var("TERM_PROGRAM", "WezTerm");
598        assert!(supports_iterm2());
599
600        // Test TERM_PROGRAM=rio
601        std::env::set_var("TERM_PROGRAM", "rio");
602        assert!(supports_iterm2());
603
604        // Test TERM_PROGRAM=Apple_Terminal
605        std::env::set_var("TERM_PROGRAM", "Apple_Terminal");
606        assert!(!supports_iterm2());
607
608        // Test clear env -> false
609        std::env::remove_var("TERM_PROGRAM");
610        assert!(!supports_iterm2());
611    }
612
613    #[test]
614    fn test_supports_sixel_heuristics() {
615        let _guard = EnvGuard::new(&["TERM", "TERM_PROGRAM", "WT_SESSION"]);
616
617        // Clear all to start fresh
618        std::env::remove_var("TERM");
619        std::env::remove_var("TERM_PROGRAM");
620        std::env::remove_var("WT_SESSION");
621        assert!(!supports_sixel());
622
623        // Test TERM=xterm-sixel
624        std::env::set_var("TERM", "xterm-sixel");
625        assert!(supports_sixel());
626
627        // Test TERM=foot
628        std::env::set_var("TERM", "foot");
629        assert!(supports_sixel());
630
631        // Test TERM=mlterm (case variations)
632        std::env::set_var("TERM", "MLTerm");
633        assert!(supports_sixel());
634
635        // Reset TERM, test TERM_PROGRAM=WezTerm
636        std::env::remove_var("TERM");
637        std::env::set_var("TERM_PROGRAM", "WezTerm");
638        assert!(supports_sixel());
639
640        // Test TERM_PROGRAM=iTerm.app
641        std::env::set_var("TERM_PROGRAM", "iTerm.app");
642        assert!(supports_sixel());
643
644        // Test TERM_PROGRAM=rio
645        std::env::set_var("TERM_PROGRAM", "rio");
646        assert!(supports_sixel());
647
648        // Reset TERM_PROGRAM, test WT_SESSION
649        std::env::remove_var("TERM_PROGRAM");
650        std::env::set_var("WT_SESSION", "active");
651        assert!(supports_sixel());
652    }
653
654    #[test]
655    fn test_get_embedded_logo() {
656        let logo = get_embedded_logo(Some("arch"));
657        assert!(logo.is_some());
658        let logo = get_embedded_logo(Some("pop"));
659        assert!(logo.is_some());
660        let logo = get_embedded_logo(Some("manjaro"));
661        assert!(logo.is_some());
662        let logo = get_embedded_logo(Some("endeavouros"));
663        assert!(logo.is_some());
664        let logo = get_embedded_logo(Some("opensuse"));
665        assert!(logo.is_some());
666        let logo = get_embedded_logo(Some("opensuse-leap"));
667        assert!(logo.is_some());
668        let logo = get_embedded_logo(Some("opensuse-tumbleweed"));
669        assert!(logo.is_some());
670        let logo = get_embedded_logo(Some("macos"));
671        assert!(logo.is_some());
672        let logo = get_embedded_logo(Some("windows"));
673        assert!(logo.is_some());
674        let logo = get_embedded_logo(None);
675        assert!(logo.is_some());
676    }
677
678    #[test]
679    fn test_get_ascii_logo_new_distros() {
680        let pop = get_ascii_logo(Some("pop"));
681        assert!(!pop.is_empty());
682        assert!(pop.iter().any(|line| line.contains("767")));
683
684        let manjaro = get_ascii_logo(Some("manjaro"));
685        assert!(!manjaro.is_empty());
686        assert!(manjaro.iter().any(|line| line.contains("████████")));
687
688        let endeavouros = get_ascii_logo(Some("endeavouros"));
689        assert!(!endeavouros.is_empty());
690        assert!(endeavouros.iter().any(|line| line.contains("ssso")));
691
692        let opensuse = get_ascii_logo(Some("opensuse"));
693        assert!(!opensuse.is_empty());
694        assert!(opensuse.iter().any(|line| line.contains("O0000Ok")));
695
696        let macos = get_ascii_logo(Some("macos"));
697        assert!(!macos.is_empty());
698        assert!(macos
699            .iter()
700            .any(|line| line.contains("cKMMMMMMMMMMNWMMMMMMMMMM0")));
701
702        let windows = get_ascii_logo(Some("windows"));
703        assert!(!windows.is_empty());
704        assert!(windows
705            .iter()
706            .any(|line| line.contains("AEEEtttt::::ztF") || line.contains("tt:::tt333EE3")));
707    }
708}