fm/io/
image_adapter.rs

1use std::env::var;
2
3use anyhow::Result;
4use ratatui::layout::Rect;
5
6use crate::common::{is_in_path, UEBERZUG};
7use crate::config::{get_prefered_imager, Imagers};
8use crate::io::{user_has_x11, 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}
30
31impl ImageAdapter {
32    /// Returns a compatible `ImageAdapter` from environement and installed programs.
33    /// We first look for some terminal emulators variable set at launch.
34    /// If we detect a "Inline Images Protocol compatible", we use it.
35    /// Else, we check for the executable ueberzug and the X11 capacity,
36    /// Else we can't display the image.
37    pub fn detect() -> Self {
38        let Some(prefered_imager) = get_prefered_imager() else {
39            return Self::Unable;
40        };
41
42        match prefered_imager.imager {
43            Imagers::Disabled => Self::Unable,
44            Imagers::Inline => {
45                for variable in COMPATIBLES {
46                    if var(variable).is_ok() {
47                        log_info!(
48                            "detected Inline Image Protocol compatible terminal from {variable}"
49                        );
50                        return Self::InlineImage(InlineImage::default());
51                    }
52                }
53                Self::try_ueberzug()
54            }
55            Imagers::Ueberzug => Self::try_ueberzug(),
56        }
57    }
58
59    fn try_ueberzug() -> Self {
60        if is_in_path(UEBERZUG) && user_has_x11() {
61            log_info!("detected ueberzug");
62            Self::Ueberzug(Ueberzug::default())
63        } else {
64            log_info!("unable to display image");
65            Self::Unable
66        }
67    }
68}
69
70/// Methods used to display images :
71/// - `draw` asks the adapter to do the drawing,
72/// - `clear` erases an image from its path,
73/// - `clear_all` erases all drawed images.
74pub trait ImageDisplayer {
75    fn draw(&mut self, image: &DisplayedImage, rect: Rect) -> Result<()>;
76    fn clear(&mut self, image: &DisplayedImage) -> Result<()>;
77    fn clear_all(&mut self) -> Result<()>;
78}
79
80impl ImageDisplayer for ImageAdapter {
81    fn draw(&mut self, image: &DisplayedImage, rect: Rect) -> Result<()> {
82        match self {
83            Self::Unable => Ok(()),
84            Self::Ueberzug(ueberzug) => ueberzug.draw(image, rect),
85            Self::InlineImage(inline_image) => inline_image.draw(image, rect),
86        }
87    }
88
89    fn clear(&mut self, image: &DisplayedImage) -> Result<()> {
90        match self {
91            Self::Unable => Ok(()),
92            Self::Ueberzug(ueberzug) => ueberzug.clear(image),
93            Self::InlineImage(inline_image) => inline_image.clear(image),
94        }
95    }
96
97    fn clear_all(&mut self) -> Result<()> {
98        match self {
99            Self::Unable => Ok(()),
100            Self::Ueberzug(ueberzug) => ueberzug.clear_all(),
101            Self::InlineImage(inline_image) => inline_image.clear_all(),
102        }
103    }
104}