use std::{
borrow::Cow,
io::{self, Write},
process::{Child, Command, Stdio},
sync::mpsc,
thread,
};
use cfg_if::cfg_if;
use crate::InputBox;
mod general;
pub use general::{Yad, Zenity};
#[cfg(target_os = "windows")]
mod windows;
use which::which;
#[cfg(target_os = "windows")]
pub use windows::PSScript;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
pub use macos::JXAScript;
#[cfg(target_os = "android")]
mod android;
#[cfg(target_os = "android")]
pub use android::Android;
#[cfg(target_os = "ios")]
mod ios;
#[cfg(target_os = "ios")]
pub use ios::IOS;
#[cfg(target_env = "ohos")]
mod ohos;
#[cfg(target_env = "ohos")]
pub use ohos::OHOS;
pub trait Backend {
fn execute_async(
&self,
input: &InputBox,
callback: Box<dyn FnOnce(io::Result<Option<String>>) + Send>,
) -> io::Result<()>;
fn execute(&self, input: &InputBox) -> io::Result<Option<String>> {
let (tx, rx) = mpsc::sync_channel(1);
self.execute_async(
input,
Box::new(move |result| {
let _ = tx.send(result);
}),
)?;
match rx.recv() {
Ok(result) => result,
Err(_) => Ok(None),
}
}
}
trait CommandBackend {
fn build_command<'a>(&self, input: &'a InputBox<'a>) -> (Command, Option<Cow<'a, str>>);
}
fn spawn_command((mut cmd, stdin): (Command, Option<Cow<str>>), quiet: bool) -> io::Result<Child> {
if stdin.is_some() {
cmd.stdin(Stdio::piped());
}
cmd.stdout(Stdio::piped());
cmd.stderr(if quiet {
Stdio::null()
} else {
Stdio::inherit()
});
let mut child = cmd.spawn()?;
if let Some(input) = stdin {
child.stdin.take().unwrap().write_all(input.as_bytes())?;
}
Ok(child)
}
fn wait_child(child: Child) -> io::Result<Option<String>> {
let output = child.wait_with_output();
output.map(|output| {
if output.status.success() {
Some(
String::from_utf8_lossy(&output.stdout)
.trim_end()
.to_string(),
)
} else {
None
}
})
}
impl<T: CommandBackend> Backend for T {
fn execute_async(
&self,
input: &InputBox,
callback: Box<dyn FnOnce(io::Result<Option<String>>) + Send>,
) -> io::Result<()> {
let child = spawn_command(self.build_command(input), input.quiet)?;
thread::spawn(move || {
callback(wait_child(child));
});
Ok(())
}
fn execute(&self, input: &InputBox) -> io::Result<Option<String>> {
let child = spawn_command(self.build_command(input), input.quiet)?;
wait_child(child)
}
}
pub fn default_backend() -> Box<dyn Backend> {
if which("yad").is_ok() {
return Box::new(Yad::default());
}
cfg_if! {
if #[cfg(target_os = "windows")] {
Box::new(PSScript::default())
} else if #[cfg(target_os = "macos")] {
Box::new(JXAScript::default())
} else if #[cfg(target_os = "android")] {
Box::new(Android::default())
} else if #[cfg(target_os = "ios")] {
Box::new(IOS::default())
} else if #[cfg(target_env = "ohos")] {
Box::new(OHOS::default())
} else {
Box::new(Zenity::default())
}
}
}