1use crate::xywh::Xywh;
2use anyhow::Context;
3use anyhow::{Result, bail};
4use std::ffi::OsStr;
5use std::io::Read as _;
6use std::io::Write;
7use std::process::Child;
8use std::process::Command;
9use std::process::Stdio;
10
11#[derive(Debug)]
12pub enum UeInstanceState {
13 New,
14 Child(Child),
15 Error,
17}
18
19impl PartialEq for UeInstanceState {
20 fn eq(&self, other: &Self) -> bool {
21 match (self, other) {
22 (Self::Child(_), Self::Child(_)) => true,
23 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
24 }
25 }
26}
27
28impl UeInstanceState {
29 fn unwrap_child_mut(&mut self) -> &mut Child {
31 if let Self::Child(v) = self {
32 return v;
33 }
34 unreachable!()
35 }
36}
37
38#[derive(Debug)]
42pub struct UeInstance {
43 ueberzug: UeInstanceState,
44}
45
46impl Default for UeInstance {
47 fn default() -> Self {
48 info!("Potentially using ueberzug");
49
50 Self {
51 ueberzug: UeInstanceState::New,
52 }
53 }
54}
55
56impl UeInstance {
57 pub fn draw_cover_ueberzug(
58 &mut self,
59 url: &str,
60 draw_xywh: &Xywh,
61 use_sixel: bool,
62 ) -> Result<()> {
63 if draw_xywh.width <= 1 || draw_xywh.height <= 1 {
64 return Ok(());
65 }
66
67 let cmd = format!(
71 "{{\"action\":\"add\",\"scaler\":\"forced_cover\",\"identifier\":\"cover\",\"x\":{},\"y\":{},\"width\":{},\"height\":{},\"path\":\"{}\"}}\n",
72 draw_xywh.x,
76 draw_xywh.y, draw_xywh.width,
78 draw_xywh.height / 2, url,
80 );
81
82 if use_sixel {
87 self.run_ueberzug_cmd_sixel(&cmd).map_err(map_err)?;
88 } else {
89 self.run_ueberzug_cmd(&cmd).map_err(map_err)?;
90 }
91
92 Ok(())
93 }
94
95 pub fn clear_cover_ueberzug(&mut self) -> Result<()> {
96 let cmd = "{\"action\": \"remove\", \"identifier\": \"cover\"}\n";
97 self.run_ueberzug_cmd(cmd)
98 .map_err(map_err)
99 .context("clear_cover")?;
100 Ok(())
101 }
102
103 fn run_ueberzug_cmd(&mut self, cmd: &str) -> Result<()> {
104 let Some(ueberzug) = self.try_wait_spawn(["layer", "--silent"])? else {
105 return Ok(());
106 };
107
108 let stdin = ueberzug.stdin.as_mut().unwrap();
109 stdin
110 .write_all(cmd.as_bytes())
111 .context("ueberzug command writing")?;
112
113 Ok(())
114 }
115
116 fn run_ueberzug_cmd_sixel(&mut self, cmd: &str) -> Result<()> {
117 let Some(ueberzug) = self.try_wait_spawn(
120 ["layer", "--silent"],
121 )?
125 else {
126 return Ok(());
127 };
128
129 let stdin = ueberzug.stdin.as_mut().unwrap();
130 stdin
131 .write_all(cmd.as_bytes())
132 .context("ueberzug command writing")?;
133
134 Ok(())
135 }
136
137 fn spawn_cmd<I, S>(&mut self, args: I) -> Result<&mut Child>
141 where
142 I: IntoIterator<Item = S>,
143 S: AsRef<OsStr>,
144 {
145 let mut cmd = Command::new("ueberzug");
146 cmd.args(args)
147 .stdin(Stdio::piped())
148 .stdout(Stdio::inherit()) .stderr(Stdio::piped());
150
151 match cmd.spawn() {
152 Ok(child) => {
153 self.ueberzug = UeInstanceState::Child(child);
154 Ok(self.ueberzug.unwrap_child_mut())
155 }
156 Err(err) => {
157 if err.kind() == std::io::ErrorKind::NotFound {
158 self.ueberzug = UeInstanceState::Error;
159 }
160 bail!(err)
161 }
162 }
163 }
164
165 fn try_wait_spawn<I, S>(&mut self, args: I) -> Result<Option<&mut Child>>
169 where
170 I: IntoIterator<Item = S>,
171 S: AsRef<OsStr>,
172 {
173 let child = match self.ueberzug {
174 UeInstanceState::New => self.spawn_cmd(args)?,
175 UeInstanceState::Child(ref mut v) => v,
176 UeInstanceState::Error => {
177 trace!("Not re-trying ueberzug, because it has a permanent error!");
178
179 return Ok(None);
180 }
181 };
182
183 if let Some(exit_status) = child.try_wait()? {
184 let mut stderr_buf = String::new();
185 child
186 .stderr
187 .as_mut()
188 .map(|v| v.read_to_string(&mut stderr_buf));
189
190 self.ueberzug = UeInstanceState::Error;
192
193 if stderr_buf.is_empty() {
194 stderr_buf.push_str("<empty>");
195 }
196
197 #[cfg(not(target_family = "unix"))]
199 {
200 bail!(
201 "ueberzug command closed unexpectedly, (code {:?}), stderr:\n{}",
202 exit_status.code(),
203 stderr_buf
204 );
205 }
206 #[cfg(target_family = "unix")]
207 {
208 use std::os::unix::process::ExitStatusExt as _;
209 bail!(
210 "ueberzug command closed unexpectedly, (code {:?}, signal {:?}), stderr:\n{}",
211 exit_status.code(),
212 exit_status.signal(),
213 stderr_buf
214 );
215 }
216 }
217
218 Ok(Some(self.ueberzug.unwrap_child_mut()))
221 }
222}
223
224#[inline]
226fn map_err(err: anyhow::Error) -> anyhow::Error {
227 err.context("Failed to run Ueberzug")
228}