any_dialog/backends/
mod.rs1mod kdialog;
17mod osascript;
18mod powershell;
19mod zenity;
20
21use std::path::{Path, PathBuf};
22
23use crate::outcome::Outcome;
24use crate::request::Request;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum Backend {
29 Kdialog,
30 Zenity,
31 Osascript,
32 Powershell,
33}
34
35pub(crate) fn probe() -> Option<(Backend, PathBuf)> {
40 let order: [(Backend, &str); 4] = if cfg!(target_os = "windows") {
41 [
42 (Backend::Powershell, powershell::PROGRAM),
43 (Backend::Kdialog, kdialog::PROGRAM),
44 (Backend::Zenity, zenity::PROGRAM),
45 (Backend::Osascript, osascript::PROGRAM),
46 ]
47 } else if cfg!(target_os = "macos") {
48 [
49 (Backend::Osascript, osascript::PROGRAM),
50 (Backend::Kdialog, kdialog::PROGRAM),
51 (Backend::Zenity, zenity::PROGRAM),
52 (Backend::Powershell, powershell::PROGRAM),
53 ]
54 } else {
55 [
56 (Backend::Kdialog, kdialog::PROGRAM),
57 (Backend::Zenity, zenity::PROGRAM),
58 (Backend::Osascript, osascript::PROGRAM),
59 (Backend::Powershell, powershell::PROGRAM),
60 ]
61 };
62 for (backend, program) in order {
63 if let Some(path) = find_on_path(program) {
64 return Some((backend, path));
65 }
66 }
67 None
68}
69
70pub(crate) fn argv(backend: Backend, request: &Request<'_>) -> Vec<String> {
72 match backend {
73 Backend::Kdialog => kdialog::argv(request),
74 Backend::Zenity => zenity::argv(request),
75 Backend::Osascript => osascript::argv(request),
76 Backend::Powershell => powershell::argv(request),
77 }
78}
79
80pub(crate) fn env(backend: Backend, request: &Request<'_>) -> Vec<(String, String)> {
84 match backend {
85 Backend::Powershell => powershell::env(request),
86 _ => Vec::new(),
87 }
88}
89
90pub(crate) fn classify(
95 backend: Backend,
96 exit_code: Option<i32>,
97 stdout: &str,
98 stderr: &str,
99) -> Outcome {
100 match backend {
101 Backend::Osascript => osascript::classify(exit_code, stdout, stderr),
102 Backend::Powershell => powershell::classify(exit_code, stdout, stderr),
103 _ => crate::outcome::classify(exit_code, stdout),
104 }
105}
106
107pub(crate) fn selected<'a>(choices: &[&'a str], current: Option<&'a str>) -> Option<&'a str> {
111 current
112 .filter(|current| choices.contains(current))
113 .or_else(|| choices.first().copied())
114}
115
116pub(crate) fn selected_many<'a>(choices: &[&'a str], current: &[&'a str]) -> Vec<&'a str> {
119 current
120 .iter()
121 .filter(|choice| choices.contains(choice))
122 .copied()
123 .collect()
124}
125
126pub(crate) fn decode_choices(backend: Backend, text: &str, choices: &[&str]) -> Vec<String> {
131 match backend {
132 Backend::Kdialog => kdialog::decode_choices(text, choices),
133 _ => text.lines().map(|line| line.to_string()).collect(),
134 }
135}
136
137pub(crate) fn path_text(path: &Path) -> String {
141 path.to_string_lossy().into_owned()
142}
143
144pub(crate) fn find_on_path(program: &str) -> Option<PathBuf> {
146 let path = std::env::var_os("PATH")?;
147 std::env::split_paths(&path)
148 .map(|dir| dir.join(program))
149 .find(|candidate| is_executable(candidate))
150}
151
152fn is_executable(path: &Path) -> bool {
153 let Ok(metadata) = std::fs::metadata(path) else {
154 return false;
155 };
156 if !metadata.is_file() {
157 return false;
158 }
159 #[cfg(unix)]
160 {
161 use std::os::unix::fs::PermissionsExt;
162 metadata.permissions().mode() & 0o111 != 0
163 }
164 #[cfg(not(unix))]
165 {
166 true
167 }
168}