1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use crate::config::Xywh;
use anyhow::{bail, Result};
use std::io::Write;
use std::process::Child;
use std::process::Command;
use std::process::Stdio;

#[derive(Debug)]
pub enum UeInstanceState {
    New,
    Child(Child),
    /// Permanent Error
    Error,
}

impl PartialEq for UeInstanceState {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Child(_), Self::Child(_)) => true,
            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
        }
    }
}

impl UeInstanceState {
    /// unwrap value in [`UeInstanceState::Child`], panicing if not that variant
    fn unwrap_child_mut(&mut self) -> &mut Child {
        if let Self::Child(v) = self {
            return v;
        }
        unreachable!()
    }
}

/// Run `ueberzug` commands
///
/// If there is a permanent error (like `ueberzug` not being installed), will silently ignore all commands after initial error
#[derive(Debug)]
pub struct UeInstance {
    ueberzug: UeInstanceState,
}

impl Default for UeInstance {
    fn default() -> Self {
        Self {
            ueberzug: UeInstanceState::New,
        }
    }
}

impl UeInstance {
    pub fn draw_cover_ueberzug(
        &mut self,
        url: &str,
        draw_xywh: &Xywh,
        use_sixel: bool,
    ) -> Result<()> {
        if draw_xywh.width <= 1 || draw_xywh.height <= 1 {
            return Ok(());
        }

        // Ueberzug takes an area given in chars and fits the image to
        // that area (from the top left).
        //   draw_offset.y += (draw_size.y - size.y) - (draw_size.y - size.y) / 2;
        let cmd = format!("{{\"action\":\"add\",\"scaler\":\"forced_cover\",\"identifier\":\"cover\",\"x\":{},\"y\":{},\"width\":{},\"height\":{},\"path\":\"{}\"}}\n",
        // let cmd = format!("{{\"action\":\"add\",\"scaler\":\"fit_contain\",\"identifier\":\"cover\",\"x\":{},\"y\":{},\"width\":{},\"height\":{},\"path\":\"{}\"}}\n",
        // TODO: right now the y position of ueberzug is not consistent, and could be a 0.5 difference
                // draw_xywh.x, draw_xywh.y-1,
                draw_xywh.x, draw_xywh.y,//-1 + (draw_xywh.width-draw_xywh.height) % 2,
                draw_xywh.width,draw_xywh.height/2,//+ (draw_xywh.width-draw_xywh.height)%2,
                url,
            );

        // debug!(
        //     "draw_xywh.x = {}, draw_xywh.y = {}, draw_wyxh.width = {}, draw_wyxh.height = {}",
        //     draw_xywh.x, draw_xywh.y, draw_xywh.width, draw_xywh.height,
        // );
        if use_sixel {
            self.run_ueberzug_cmd_sixel(&cmd).map_err(map_err)?;
        } else {
            self.run_ueberzug_cmd(&cmd).map_err(map_err)?;
        };

        Ok(())
    }

    pub fn clear_cover_ueberzug(&mut self) -> Result<()> {
        let cmd = "{\"action\": \"remove\", \"identifier\": \"cover\"}\n";
        self.run_ueberzug_cmd(cmd).map_err(map_err)?;
        Ok(())
    }

    fn run_ueberzug_cmd(&mut self, cmd: &str) -> Result<()> {
        // error!("using x11 output for ueberzugpp");

        let ueberzug = match self.ueberzug {
            UeInstanceState::New => self.spawn_cmd(
                Command::new("ueberzug")
                    .args(["layer", "--silent"])
                    .stdin(Stdio::piped())
                    .stdout(Stdio::piped()),
            )?,
            UeInstanceState::Child(ref mut v) => v,
            UeInstanceState::Error => return on_error(),
        };

        let stdin = ueberzug.stdin.as_mut().unwrap();
        stdin.write_all(cmd.as_bytes())?;

        Ok(())
    }

    fn run_ueberzug_cmd_sixel(&mut self, cmd: &str) -> Result<()> {
        // error!("using sixel output for ueberzugpp");

        let ueberzug = match self.ueberzug {
            UeInstanceState::New => {
                self.spawn_cmd(
                    Command::new("ueberzug")
                        .args(["layer", "--silent"])
                        // .args(["layer", "--silent", "--no-cache", "--output", "sixel"])
                        // .args(["layer", "--sixel"])
                        // .args(["--sixel"])
                        .stdin(Stdio::piped())
                        .stdout(Stdio::piped()),
                )?
            }
            UeInstanceState::Child(ref mut v) => v,
            UeInstanceState::Error => return on_error(),
        };

        let stdin = ueberzug.stdin.as_mut().unwrap();
        stdin.write_all(cmd.as_bytes())?;

        Ok(())
    }

    /// Spawn the given `cmd`, and set `self.ueberzug` and return a reference to the child for direct use
    ///
    /// On fail, also set `set.ueberzug` to [`UeInstanceState::Error`]
    fn spawn_cmd(&mut self, cmd: &mut Command) -> Result<&mut Child> {
        match cmd.spawn() {
            Ok(child) => {
                self.ueberzug = UeInstanceState::Child(child);
                return Ok(self.ueberzug.unwrap_child_mut());
            }
            Err(err) => {
                if err.kind() == std::io::ErrorKind::NotFound {
                    self.ueberzug = UeInstanceState::Error;
                }
                bail!(err)
            }
        }
    }
}

/// Small helper to always print a message and return a consistent return
#[inline]
#[allow(clippy::unnecessary_wraps)]
fn on_error() -> Result<()> {
    trace!("Not re-trying ueberzug, because it has a permanent error!");

    Ok(())
}

/// Map a given error to include extra context
#[inline]
#[allow(clippy::needless_pass_by_value)]
fn map_err(err: anyhow::Error) -> anyhow::Error {
    err.context("Failed to run Ueberzug")
}