Skip to main content

fm/io/
image_adapter.rs

1use std::env::var;
2
3use anyhow::Result;
4use ratatui::layout::Rect;
5
6use crate::common::{is_in_path, CHAFA, UEBERZUG};
7use crate::config::{get_prefered_imager, Imagers};
8use crate::io::{user_has_x11, Chafa, InlineImage, Ueberzug};
9use crate::log_info;
10use crate::modes::DisplayedImage;
11
12const COMPATIBLES: [&str; 4] = [
13    "WEZTERM_EXECUTABLE",
14    "WARP_HONOR_PS1",
15    "TABBY_CONFIG_DIRECTORY",
16    "VSCODE_INJECTION",
17];
18
19/// What image adapter is used ?
20/// - Unable means no supported image adapter. ie. image can't be displayed.
21/// - Ueberzug if it's installed,
22/// - InlineImage if the terminal emulator supports it.
23#[derive(Default)]
24pub enum ImageAdapter {
25    #[default]
26    Unable,
27    Ueberzug(Ueberzug),
28    InlineImage(InlineImage),
29    Chafa(Chafa),
30}
31
32impl ImageAdapter {
33    /// Returns a compatible `ImageAdapter` from environement and installed programs.
34    /// We first look for some terminal emulators variable set at launch.
35    /// If we detect a "Inline Images Protocol compatible", we use it.
36    /// Else, we check for the executable ueberzug and the X11 capacity,
37    /// Else we can't display the image.
38    pub fn detect() -> Self {
39        let Some(prefered_imager) = get_prefered_imager() else {
40            return Self::Unable;
41        };
42
43        match prefered_imager.imager {
44            Imagers::Disabled => Self::Unable,
45            Imagers::Inline => {
46                for variable in COMPATIBLES {
47                    if var(variable).is_ok() {
48                        log_info!(
49                            "detected Inline Image Protocol compatible terminal from {variable}"
50                        );
51                        return Self::InlineImage(InlineImage::default());
52                    }
53                }
54                Self::try_ueberzug()
55            }
56            Imagers::Chafa => Self::try_chafa(),
57            Imagers::Ueberzug => Self::try_ueberzug(),
58        }
59    }
60
61    fn try_ueberzug() -> Self {
62        if is_in_path(UEBERZUG) && user_has_x11() {
63            log_info!("detected ueberzug");
64            Self::Ueberzug(Ueberzug::default())
65        } else {
66            log_info!("unable to display image");
67            Self::Unable
68        }
69    }
70
71    fn try_chafa() -> Self {
72        if is_in_path(CHAFA) {
73            log_info!("detected chafa");
74            Self::Chafa(Chafa::default())
75        } else {
76            log_info!("unable to display image");
77            Self::Unable
78        }
79    }
80}
81
82/// Methods used to display images :
83/// - `draw` asks the adapter to do the drawing,
84/// - `clear` erases an image from its path,
85/// - `clear_all` erases all drawed images.
86pub trait ImageDisplayer {
87    fn draw(&mut self, image: &DisplayedImage, rect: Rect) -> Result<()>;
88    fn clear(&mut self, image: &DisplayedImage) -> Result<()>;
89    fn clear_all(&mut self) -> Result<()>;
90}
91
92impl ImageDisplayer for ImageAdapter {
93    fn draw(&mut self, image: &DisplayedImage, rect: Rect) -> Result<()> {
94        match self {
95            Self::Unable => Ok(()),
96            Self::Ueberzug(ueberzug) => ueberzug.draw(image, rect),
97            Self::InlineImage(inline_image) => inline_image.draw(image, rect),
98            Self::Chafa(chafa) => chafa.draw(image, rect),
99        }
100    }
101
102    fn clear(&mut self, image: &DisplayedImage) -> Result<()> {
103        match self {
104            Self::Unable => Ok(()),
105            Self::Ueberzug(ueberzug) => ueberzug.clear(image),
106            Self::InlineImage(inline_image) => inline_image.clear(image),
107            Self::Chafa(chafa) => chafa.clear(image),
108        }
109    }
110
111    fn clear_all(&mut self) -> Result<()> {
112        match self {
113            Self::Unable => Ok(()),
114            Self::Ueberzug(ueberzug) => ueberzug.clear_all(),
115            Self::InlineImage(inline_image) => inline_image.clear_all(),
116            Self::Chafa(chafa) => chafa.clear_all(),
117        }
118    }
119}