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            entry
15                .extensions
16                .iter()
17                .map(|ext| format!("*.{ext}"))
18                .collect::<Vec<_>>()
19                .join(" ")
20        })
21        .collect::<Vec<_>>()
22        .join(" ")
23}
24
25impl<'a, W: HasWindowHandle> BlockingSaveFileDialog<'a, W> {
26    pub fn show(&self) -> Result<Option<PathBuf>, BlockingDialogError> {
27        if is_kdialog_available() {
28            let filter = get_kdialog_filter(self.filter);
29
30            let mut args = vec!["--getsavefilename", "--title", self.title];
31
32            if let Some(default_filename) = self.default_filename {
33                args.push(default_filename);
34            }
35
36            if !self.filter.is_empty() {
37                args.push("");
38                args.push(&filter);
39            }
40
41            let output = Command::new("kdialog").args(args).output()?;
42            let stdout = String::from_utf8_lossy(&output.stdout);
43            let trimmed = stdout.trim();
44
45            if trimmed.is_empty() {
46                Ok(None)
47            } else {
48                Ok(Some(PathBuf::from(trimmed)))
49            }
50        } else {
51            let mut filter_args = Vec::new();
52
53            for entry in self.filter {
54                let patterns = entry
55                    .extensions
56                    .iter()
57                    .map(|ext| format!("*.{ext}"))
58                    .collect::<Vec<_>>()
59                    .join(" ");
60                filter_args.push(format!("{} | {}", entry.name, patterns));
61            }
62
63            let mut args = vec!["--file-selection", "--save", "--title", self.title];
64
65            if let Some(default_filename) = self.default_filename {
66                args.push("--filename");
67                args.push(default_filename);
68            }
69
70            for filter_arg in &filter_args {
71                args.push("--file-filter");
72                args.push(filter_arg.as_str());
73            }
74
75            let output = Command::new("zenity").args(args).output()?;
76            let stdout = String::from_utf8_lossy(&output.stdout);
77            let trimmed = stdout.trim();
78
79            if trimmed.is_empty() {
80                Ok(None)
81            } else {
82                Ok(Some(PathBuf::from(trimmed)))
83            }
84        }
85    }
86}