mod kdialog;
mod osascript;
mod powershell;
mod zenity;
use std::path::{Path, PathBuf};
use crate::outcome::Outcome;
use crate::request::Request;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Backend {
Kdialog,
Zenity,
Osascript,
Powershell,
}
pub(crate) fn probe() -> Option<(Backend, PathBuf)> {
let order: [(Backend, &str); 4] = if cfg!(target_os = "windows") {
[
(Backend::Powershell, powershell::PROGRAM),
(Backend::Kdialog, kdialog::PROGRAM),
(Backend::Zenity, zenity::PROGRAM),
(Backend::Osascript, osascript::PROGRAM),
]
} else if cfg!(target_os = "macos") {
[
(Backend::Osascript, osascript::PROGRAM),
(Backend::Kdialog, kdialog::PROGRAM),
(Backend::Zenity, zenity::PROGRAM),
(Backend::Powershell, powershell::PROGRAM),
]
} else {
[
(Backend::Kdialog, kdialog::PROGRAM),
(Backend::Zenity, zenity::PROGRAM),
(Backend::Osascript, osascript::PROGRAM),
(Backend::Powershell, powershell::PROGRAM),
]
};
for (backend, program) in order {
if let Some(path) = find_on_path(program) {
return Some((backend, path));
}
}
None
}
pub(crate) fn argv(backend: Backend, request: &Request<'_>) -> Vec<String> {
match backend {
Backend::Kdialog => kdialog::argv(request),
Backend::Zenity => zenity::argv(request),
Backend::Osascript => osascript::argv(request),
Backend::Powershell => powershell::argv(request),
}
}
pub(crate) fn env(backend: Backend, request: &Request<'_>) -> Vec<(String, String)> {
match backend {
Backend::Powershell => powershell::env(request),
_ => Vec::new(),
}
}
pub(crate) fn classify(
backend: Backend,
exit_code: Option<i32>,
stdout: &str,
stderr: &str,
) -> Outcome {
match backend {
Backend::Osascript => osascript::classify(exit_code, stdout, stderr),
Backend::Powershell => powershell::classify(exit_code, stdout, stderr),
_ => crate::outcome::classify(exit_code, stdout),
}
}
pub(crate) fn selected<'a>(choices: &[&'a str], current: Option<&'a str>) -> Option<&'a str> {
current
.filter(|current| choices.contains(current))
.or_else(|| choices.first().copied())
}
pub(crate) fn selected_many<'a>(choices: &[&'a str], current: &[&'a str]) -> Vec<&'a str> {
current
.iter()
.filter(|choice| choices.contains(choice))
.copied()
.collect()
}
pub(crate) fn decode_choices(backend: Backend, text: &str, choices: &[&str]) -> Vec<String> {
match backend {
Backend::Kdialog => kdialog::decode_choices(text, choices),
_ => text.lines().map(|line| line.to_string()).collect(),
}
}
pub(crate) fn path_text(path: &Path) -> String {
path.to_string_lossy().into_owned()
}
pub(crate) fn find_on_path(program: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path)
.map(|dir| dir.join(program))
.find(|candidate| is_executable(candidate))
}
fn is_executable(path: &Path) -> bool {
let Ok(metadata) = std::fs::metadata(path) else {
return false;
};
if !metadata.is_file() {
return false;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
metadata.permissions().mode() & 0o111 != 0
}
#[cfg(not(unix))]
{
true
}
}