Skip to main content

blocking_dialog/linux/
save_file.rs

1// SPDX-FileCopyrightText: 2026 Manuel Quarneti <mq1@ik.me>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use super::is_kdialog_available;
5use crate::{BlockingDialogError, BlockingPickFilesDialogFilter, BlockingSaveFileDialog};
6use raw_window_handle::HasWindowHandle;
7use std::path::PathBuf;
8use std::process::Command;
9
10fn get_kdialog_filter(filter: &[BlockingPickFilesDialogFilter]) -> String {
11    filter
12        .iter()
13        .map(|entry| {
14            format!(
15                "{} ({})",
16                entry.name,
17                entry
18                    .extensions
19                    .iter()
20                    .map(|ext| format!("*.{ext}"))
21                    .collect::<Vec<_>>()
22                    .join(" "),
23            )
24        })
25        .collect::<Vec<_>>()
26        .join(" ")
27}
28
29impl<'a, W: HasWindowHandle> BlockingSaveFileDialog<'a, W> {
30    pub fn show(&self) -> Result<Option<PathBuf>, BlockingDialogError> {
31        if is_kdialog_available() {
32            let filter = get_kdialog_filter(self.filter);
33
34            let mut args = vec!["--getsavefilename", "--title", self.title];
35
36            if let Some(default_filename) = self.default_filename {
37                args.push(default_filename);
38            }
39
40            if !self.filter.is_empty() {
41                args.push(":label1");
42                args.push(&filter);
43            }
44
45            let output = Command::new("kdialog").args(args).output()?;
46            let stdout = String::from_utf8_lossy(&output.stdout);
47            let trimmed = stdout.trim();
48
49            if trimmed.is_empty() {
50                Ok(None)
51            } else {
52                Ok(Some(PathBuf::from(trimmed)))
53            }
54        } else {
55            let mut filter_args = Vec::new();
56
57            for entry in self.filter {
58                let patterns = entry
59                    .extensions
60                    .iter()
61                    .map(|ext| format!("*.{ext}"))
62                    .collect::<Vec<_>>()
63                    .join(" ");
64                filter_args.push(format!("{} | {}", entry.name, patterns));
65            }
66
67            let mut args = vec!["--file-selection", "--save", "--title", self.title];
68
69            if let Some(default_filename) = self.default_filename {
70                args.push("--filename");
71                args.push(default_filename);
72            }
73
74            for filter_arg in &filter_args {
75                args.push("--file-filter");
76                args.push(filter_arg.as_str());
77            }
78
79            let output = Command::new("zenity").args(args).output()?;
80            let stdout = String::from_utf8_lossy(&output.stdout);
81            let trimmed = stdout.trim();
82
83            if trimmed.is_empty() {
84                Ok(None)
85            } else {
86                Ok(Some(PathBuf::from(trimmed)))
87            }
88        }
89    }
90}