1use crate::xywh::Xywh;
2use anyhow::Context;
3use anyhow::{bail, Result};
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 Self {
49 ueberzug: UeInstanceState::New,
50 }
51 }
52}
53
54impl UeInstance {
55 pub fn draw_cover_ueberzug(
56 &mut self,
57 url: &str,
58 draw_xywh: &Xywh,
59 use_sixel: bool,
60 ) -> Result<()> {
61 if draw_xywh.width <= 1 || draw_xywh.height <= 1 {
62 return Ok(());
63 }
64
65 let cmd = format!("{{\"action\":\"add\",\"scaler\":\"forced_cover\",\"identifier\":\"cover\",\"x\":{},\"y\":{},\"width\":{},\"height\":{},\"path\":\"{}\"}}\n",
69 draw_xywh.x, draw_xywh.y,draw_xywh.width,draw_xywh.height/2,url,
75 );
76
77 if use_sixel {
82 self.run_ueberzug_cmd_sixel(&cmd).map_err(map_err)?;
83 } else {
84 self.run_ueberzug_cmd(&cmd).map_err(map_err)?;
85 };
86
87 Ok(())
88 }
89
90 pub fn clear_cover_ueberzug(&mut self) -> Result<()> {
91 let cmd = "{\"action\": \"remove\", \"identifier\": \"cover\"}\n";
92 self.run_ueberzug_cmd(cmd)
93 .map_err(map_err)
94 .context("clear_cover")?;
95 Ok(())
96 }
97
98 fn run_ueberzug_cmd(&mut self, cmd: &str) -> Result<()> {
99 let Some(ueberzug) = self.try_wait_spawn(["layer"])? else {
100 return Ok(());
101 };
102
103 let stdin = ueberzug.stdin.as_mut().unwrap();
104 stdin
105 .write_all(cmd.as_bytes())
106 .context("ueberzug command writing")?;
107
108 Ok(())
109 }
110
111 fn run_ueberzug_cmd_sixel(&mut self, cmd: &str) -> Result<()> {
112 let Some(ueberzug) = self.try_wait_spawn(
115 ["layer"],
116 )?
120 else {
121 return Ok(());
122 };
123
124 let stdin = ueberzug.stdin.as_mut().unwrap();
125 stdin
126 .write_all(cmd.as_bytes())
127 .context("ueberzug command writing")?;
128
129 Ok(())
130 }
131
132 fn spawn_cmd<I, S>(&mut self, args: I) -> Result<&mut Child>
136 where
137 I: IntoIterator<Item = S>,
138 S: AsRef<OsStr>,
139 {
140 let mut cmd = Command::new("ueberzug");
141 cmd.args(args)
142 .stdin(Stdio::piped())
143 .stdout(Stdio::inherit()) .stderr(Stdio::piped());
145
146 match cmd.spawn() {
147 Ok(child) => {
148 self.ueberzug = UeInstanceState::Child(child);
149 Ok(self.ueberzug.unwrap_child_mut())
150 }
151 Err(err) => {
152 if err.kind() == std::io::ErrorKind::NotFound {
153 self.ueberzug = UeInstanceState::Error;
154 }
155 bail!(err)
156 }
157 }
158 }
159
160 fn try_wait_spawn<I, S>(&mut self, args: I) -> Result<Option<&mut Child>>
164 where
165 I: IntoIterator<Item = S>,
166 S: AsRef<OsStr>,
167 {
168 let child = match self.ueberzug {
169 UeInstanceState::New => self.spawn_cmd(args)?,
170 UeInstanceState::Child(ref mut v) => v,
171 UeInstanceState::Error => {
172 trace!("Not re-trying ueberzug, because it has a permanent error!");
173
174 return Ok(None);
175 }
176 };
177
178 if let Some(exit_status) = child.try_wait()? {
179 let mut stderr_buf = String::new();
180 child
181 .stderr
182 .as_mut()
183 .map(|v| v.read_to_string(&mut stderr_buf));
184
185 self.ueberzug = UeInstanceState::Error;
187
188 if stderr_buf.is_empty() {
189 stderr_buf.push_str("<empty>");
190 }
191
192 #[cfg(not(target_family = "unix"))]
194 {
195 bail!(
196 "ueberzug command closed unexpectedly, (code {:?}), stderr:\n{}",
197 exit_status.code(),
198 stderr_buf
199 );
200 }
201 #[cfg(target_family = "unix")]
202 {
203 use std::os::unix::process::ExitStatusExt as _;
204 bail!(
205 "ueberzug command closed unexpectedly, (code {:?}, signal {:?}), stderr:\n{}",
206 exit_status.code(),
207 exit_status.signal(),
208 stderr_buf
209 );
210 }
211 }
212
213 Ok(Some(self.ueberzug.unwrap_child_mut()))
216 }
217}
218
219#[inline]
221fn map_err(err: anyhow::Error) -> anyhow::Error {
222 err.context("Failed to run Ueberzug")
223}