1use std::io::Write;
39use std::path::PathBuf;
40#[cfg(target_os = "macos")]
41use std::process::Command;
42
43use any_dialog::Dialogs;
44
45#[cfg(target_os = "macos")]
46const RAW_TEXT_SCRIPT: &[&str] = &[
47 "on run argv",
48 "set answer to display dialog (item 2 of argv) default answer (item 3 of argv) with title (item 1 of argv) giving up after 3",
49 "return text returned of answer",
50 "end run",
51];
52
53#[cfg(target_os = "macos")]
54const RAW_ALERT_SCRIPT: &[&str] = &[
55 "on run argv",
56 "display alert (item 1 of argv) message (item 2 of argv) giving up after 3",
57 "return \"\"",
58 "end run",
59];
60
61#[cfg(target_os = "macos")]
62const RAW_MESSAGE_SCRIPT: &[&str] = &[
63 "on run argv",
64 "display dialog (item 2 of argv) buttons {\"OK\"} default button \"OK\" with title (item 1 of argv) with icon (item 3 of argv as integer) giving up after 3",
65 "return \"\"",
66 "end run",
67];
68
69#[cfg(target_os = "macos")]
74const RAW_CONFIRM_SCRIPT: &[&str] = &[
75 "on run argv",
76 "set answer to display dialog (item 2 of argv) buttons {\"No\", \"Yes\"} default button \"Yes\" with title (item 1 of argv) giving up after 3",
77 "if button returned of answer is \"Yes\" then return \"yes\"",
78 "return \"no\"",
79 "end run",
80];
81
82fn main() {
83 let mut backend: Option<String> = None;
89 let mut mode: Vec<String> = Vec::new();
90 let mut args = std::env::args().skip(1);
91 while let Some(arg) = args.next() {
92 if arg == "--backend" {
93 backend = args.next();
94 } else {
95 mode.push(arg);
96 }
97 }
98 let dialogs = match backend.as_deref() {
99 None => Dialogs::new(),
100 Some("kdialog") => Dialogs::with_paths(Some(PathBuf::from("kdialog")), None, None, None),
101 Some("zenity") => Dialogs::with_paths(None, Some(PathBuf::from("zenity")), None, None),
102 Some("osascript") => {
103 Dialogs::with_paths(None, None, Some(PathBuf::from("osascript")), None)
104 }
105 Some("powershell") => {
106 Dialogs::with_paths(None, None, None, Some(PathBuf::from("powershell.exe")))
107 }
108 Some(other) => {
109 eprintln!(
110 "unknown backend {other:?}; expected kdialog, zenity, osascript, or powershell"
111 );
112 std::process::exit(2);
113 }
114 };
115 match mode.first().map(String::as_str) {
116 Some("--interactive") => interactive(&dialogs),
117 Some("--case") => {
118 let case = mode.get(1).map(String::as_str).unwrap_or_default();
119 crate_case(&dialogs, case);
120 }
121 _ => scripted(&dialogs),
122 }
123}
124
125fn scripted(dialogs: &Dialogs) {
126 println!("PROBE scripted mode");
127 println!(
128 "CRATE backend available: {} ({:?})",
129 dialogs.available(),
130 dialogs.backend()
131 );
132
133 #[cfg(not(target_os = "macos"))]
134 {
135 println!(
136 "RAW CASES: macOS only (the raw probes drive osascript directly); use --case or --interactive on this OS"
137 );
138 }
139
140 #[cfg(target_os = "macos")]
141 {
142 raw_case(
143 "text (giving up after 3)",
144 RAW_TEXT_SCRIPT,
145 &[
146 "any-dialog probe",
147 "Scripted text probe: this dialog dismisses itself",
148 "prefilled",
149 ],
150 );
151 raw_case(
152 "alert (giving up after 3)",
153 RAW_ALERT_SCRIPT,
154 &[
155 "any-dialog probe",
156 "Scripted alert probe: this alert dismisses itself",
157 ],
158 );
159 raw_case(
163 "message (giving up after 3)",
164 RAW_MESSAGE_SCRIPT,
165 &[
166 "any-dialog probe",
167 "Scripted message probe: this dialog dismisses itself",
168 "1",
169 ],
170 );
171 raw_case(
172 "confirm (giving up after 3)",
173 RAW_CONFIRM_SCRIPT,
174 &[
175 "any-dialog probe",
176 "Scripted confirm probe: this dialog dismisses itself",
177 ],
178 );
179 println!(
180 "RAW CASE choice, multi, color, open, save, folder: SKIPPED in scripted mode (the choose commands have no giving up after; covered by --case and --interactive)"
181 );
182 }
183}
184
185#[cfg(target_os = "macos")]
189fn raw_case(name: &str, script: &[&str], data: &[&str]) {
190 let mut args: Vec<String> = Vec::new();
191 for line in script {
192 args.push("-e".to_string());
193 args.push((*line).to_string());
194 }
195 args.extend(data.iter().map(|value| (*value).to_string()));
196
197 match Command::new("osascript").args(&args).output() {
198 Ok(output) => println!(
199 "RAW CASE {name}: exit={:?} stdout={:?} stderr={:?}",
200 output.status.code(),
201 String::from_utf8_lossy(&output.stdout),
202 String::from_utf8_lossy(&output.stderr),
203 ),
204 Err(error) => println!("RAW CASE {name}: could not run osascript: {error}"),
205 }
206 let _ = std::io::stdout().flush();
207}
208
209fn crate_case(dialogs: &Dialogs, case: &str) {
210 println!("CASE backend: {:?}", dialogs.backend());
211 match case {
212 "text" => {
213 let result = dialogs.prompt_text(
214 "any-dialog probe",
215 "Crate text probe: press Escape or click Cancel",
216 Some("prefilled"),
217 );
218 println!("CASE text => {result:?}");
219 }
220 "choice" => {
221 let result = dialogs.prompt_choice(
222 "any-dialog probe",
223 "Crate choice probe: pick one, or cancel",
224 &["alpha", "beta", "gamma"],
225 Some("beta"),
226 );
227 println!("CASE choice => {result:?}");
228 }
229 "password" => {
230 let result = dialogs.prompt_password(
231 "any-dialog probe",
232 "Crate password probe: type something, or cancel",
233 );
234 println!("CASE password => {result:?}");
235 }
236 "multi" => {
237 let result = dialogs.prompt_choices(
238 "any-dialog probe",
239 "Crate multi probe: check any, or cancel",
240 &["alpha", "beta", "gamma"],
241 &["beta"],
242 );
243 println!("CASE multi => {result:?}");
244 }
245 "color" => {
246 let result = dialogs.prompt_color("any-dialog probe", Some("#3366cc"));
247 println!("CASE color => {result:?}");
248 }
249 "open" => {
250 let result = dialogs.prompt_open_file("any-dialog probe", None, &[]);
251 println!("CASE open => {result:?}");
252 }
253 "open-multi" => {
254 let result = dialogs.prompt_open_files("any-dialog probe", None, &[]);
255 println!("CASE open-multi => {result:?}");
256 }
257 "save" => {
258 let result = dialogs.prompt_save_file("any-dialog probe", None, &[]);
259 println!("CASE save => {result:?}");
260 }
261 "folder" => {
262 let result = dialogs.prompt_folder("any-dialog probe", None);
263 println!("CASE folder => {result:?}");
264 }
265 "error" => {
266 let result =
267 dialogs.show_error("any-dialog probe", "Crate error probe: dismiss this alert");
268 println!("CASE error => {result:?}");
269 }
270 "info" => {
271 let result =
272 dialogs.show_info("any-dialog probe", "Crate info probe: dismiss this message");
273 println!("CASE info => {result:?}");
274 }
275 "warning" => {
276 let result = dialogs.show_warning(
277 "any-dialog probe",
278 "Crate warning probe: dismiss this message",
279 );
280 println!("CASE warning => {result:?}");
281 }
282 "confirm" => {
283 let result = dialogs.confirm(
284 "any-dialog probe",
285 "Crate confirm probe: answer, or dismiss",
286 );
287 println!("CASE confirm => {result:?}");
288 }
289 other => println!(
290 "CASE {other}: unknown case (want text, password, choice, multi, color, open, open-multi, save, folder, error, info, warning, or confirm)"
291 ),
292 }
293 let _ = std::io::stdout().flush();
294}
295
296fn interactive(dialogs: &Dialogs) {
297 println!("INTERACTIVE PROBE: work through the dialogs as they appear.");
298 println!(
299 "backend available: {} ({:?})",
300 dialogs.available(),
301 dialogs.backend()
302 );
303
304 step("1/24 text, OK path: type anything, then click OK");
305 let result = dialogs.prompt_text(
306 "any-dialog probe",
307 "Step 1: type anything and click OK",
308 Some("prefilled"),
309 );
310 println!("CASE text-ok => {result:?} (want Ok(Some(your text)))");
311
312 step("2/24 text, cancel path: click Cancel");
313 let result = dialogs.prompt_text("any-dialog probe", "Step 2: click Cancel", None);
314 println!("CASE text-cancel => {result:?} (want Ok(None))");
315
316 step("3/24 hostile text: leave the text as is, click OK");
317 let result = dialogs.prompt_text(
318 "Probe \"quotes\", commas",
319 "Step 3: quotes \"here\", commas, and ünicode in label and text",
320 Some("a\"b,c—d"),
321 );
322 println!("CASE text-hostile => {result:?} (want Ok(Some(\"a\\\"b,c—d\")))");
323
324 step("4/24 choice, pick gamma");
325 let result = dialogs.prompt_choice(
326 "any-dialog probe",
327 "Step 4: pick gamma and click OK",
328 &["alpha", "beta", "gamma"],
329 Some("beta"),
330 );
331 println!("CASE choice-pick => {result:?} (want Ok(Some(\"gamma\")))");
332
333 step("5/24 choice, cancel path: click Cancel");
334 let result = dialogs.prompt_choice(
335 "any-dialog probe",
336 "Step 5: click Cancel",
337 &["alpha", "beta", "gamma"],
338 None,
339 );
340 println!("CASE choice-cancel => {result:?} (want Ok(None))");
341
342 step("6/24 error alert: dismiss it");
343 let result = dialogs.show_error("any-dialog probe", "Step 6: dismiss this alert");
344 println!("CASE error => {result:?} (want Ok(()))");
345
346 step("7/24 info message: dismiss it");
347 let result = dialogs.show_info("any-dialog probe", "Step 7: dismiss this message");
348 println!("CASE info => {result:?} (want Ok(()))");
349
350 step("8/24 warning message: dismiss it");
351 let result = dialogs.show_warning("any-dialog probe", "Step 8: dismiss this message");
352 println!("CASE warning => {result:?} (want Ok(()))");
353
354 step("9/24 confirm, yes path: click Yes");
355 let result = dialogs.confirm("any-dialog probe", "Step 9: click Yes");
356 println!("CASE confirm-yes => {result:?} (want Ok(true))");
357
358 step("10/24 confirm, no path: click No");
359 let result = dialogs.confirm("any-dialog probe", "Step 10: click No");
360 println!("CASE confirm-no => {result:?} (want Ok(false))");
361
362 step("11/24 password, OK path: type a throwaway password, click OK");
363 let result = dialogs.prompt_password(
364 "any-dialog probe",
365 "Step 11: the typed text should be masked; type anything",
366 );
367 println!("CASE password-ok => {result:?} (want Ok(Some(your text)))");
368
369 step("12/24 password, cancel path: click Cancel");
370 let result = dialogs.prompt_password("any-dialog probe", "Step 12: click Cancel");
371 println!("CASE password-cancel => {result:?} (want Ok(None))");
372
373 step("13/24 multi, pick path: check alpha and gamma, uncheck beta, click OK");
374 let result = dialogs.prompt_choices(
375 "any-dialog probe",
376 "Step 13: check alpha and gamma only",
377 &["alpha", "beta", "gamma"],
378 &["beta"],
379 );
380 println!("CASE multi-pick => {result:?} (want Ok(Some([\"alpha\", \"gamma\"])))");
381
382 step("14/24 multi, cancel path: click Cancel");
383 let result = dialogs.prompt_choices(
384 "any-dialog probe",
385 "Step 14: click Cancel",
386 &["alpha", "beta", "gamma"],
387 &[],
388 );
389 println!("CASE multi-cancel => {result:?} (want Ok(None))");
390
391 step("15/24 color, OK path: pick any color, click OK");
392 let result = dialogs.prompt_color("any-dialog probe", Some("#3366cc"));
393 println!("CASE color-ok => {result:?} (want Ok(Some(\"#......\")))");
394
395 step("16/24 color, cancel path: click Cancel");
396 let result = dialogs.prompt_color("any-dialog probe", None);
397 println!("CASE color-cancel => {result:?} (want Ok(None))");
398
399 step("17/24 open file, OK path: pick any file");
400 let result = dialogs.prompt_open_file("any-dialog probe", None, &[]);
401 println!("CASE open-ok => {result:?} (want Ok(Some(path)))");
402
403 step("18/24 open file, cancel path: click Cancel");
404 let result = dialogs.prompt_open_file("any-dialog probe", None, &[]);
405 println!("CASE open-cancel => {result:?} (want Ok(None))");
406
407 step("19/24 open files, OK path: pick two or more files");
408 let result = dialogs.prompt_open_files("any-dialog probe", None, &[]);
409 println!("CASE open-multi-ok => {result:?} (want Ok(Some([paths])))");
410
411 step("20/24 open files, cancel path: click Cancel");
412 let result = dialogs.prompt_open_files("any-dialog probe", None, &[]);
413 println!("CASE open-multi-cancel => {result:?} (want Ok(None))");
414
415 step("21/24 save file, OK path: name the file and save");
416 let result = dialogs.prompt_save_file(
417 "any-dialog probe",
418 Some(std::path::Path::new("probe-output.txt")),
419 &[],
420 );
421 println!("CASE save-ok => {result:?} (want Ok(Some(path ending probe-output.txt)))");
422
423 step("22/24 save file, cancel path: click Cancel");
424 let result = dialogs.prompt_save_file("any-dialog probe", None, &[]);
425 println!("CASE save-cancel => {result:?} (want Ok(None))");
426
427 step("23/24 folder, OK path: pick any folder");
428 let result = dialogs.prompt_folder("any-dialog probe", None);
429 println!("CASE folder-ok => {result:?} (want Ok(Some(path)))");
430
431 step("24/24 folder, cancel path: click Cancel");
432 let result = dialogs.prompt_folder("any-dialog probe", None);
433 println!("CASE folder-cancel => {result:?} (want Ok(None))");
434
435 println!("INTERACTIVE PROBE done. Save this output with your OS and version.");
436}
437
438fn step(text: &str) {
439 println!("STEP {text}");
440 let _ = std::io::stdout().flush();
441}