Skip to main content

blocking_dialog/linux/
pick_files.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, BlockingPickFilesDialog, BlockingPickFilesDialogFilter};
6use raw_window_handle::HasWindowHandle;
7use std::path::PathBuf;
8use std::process::Command;
9
10fn parse_multi_select(raw: &str) -> Vec<PathBuf> {
11    raw.split('\n').map(PathBuf::from).collect()
12}
13
14fn get_kdialog_filter(filter: &[BlockingPickFilesDialogFilter]) -> String {
15    filter
16        .iter()
17        .map(|entry| {
18            format!(
19                "{} ({})",
20                entry.name,
21                entry
22                    .extensions
23                    .iter()
24                    .map(|ext| format!("*.{ext}"))
25                    .collect::<Vec<_>>()
26                    .join(" "),
27            )
28        })
29        .collect::<Vec<_>>()
30        .join(" ")
31}
32
33impl<'a, W: HasWindowHandle> BlockingPickFilesDialog<'a, W> {
34    pub fn show(&self) -> Result<Vec<PathBuf>, BlockingDialogError> {
35        if is_kdialog_available() {
36            let filter = get_kdialog_filter(&self.filter);
37
38            let mut args = vec!["--getopenfilename", "--title", self.title];
39
40            if self.multiple {
41                args.push("--multiple");
42                args.push("--separate-output");
43            }
44
45            if !self.filter.is_empty() {
46                args.push(":label1");
47                args.push(&filter);
48            }
49
50            let output = Command::new("kdialog").args(args).output()?;
51            let stdout = String::from_utf8_lossy(&output.stdout);
52            let trimmed = stdout.trim();
53
54            if trimmed.is_empty() {
55                Ok(Vec::new())
56            } else if self.multiple {
57                Ok(parse_multi_select(trimmed))
58            } else {
59                Ok(vec![PathBuf::from(trimmed)])
60            }
61        } else {
62            let mut filter_args = Vec::new();
63
64            for entry in self.filter {
65                let patterns = entry
66                    .extensions
67                    .iter()
68                    .map(|ext| format!("*.{ext}"))
69                    .collect::<Vec<_>>()
70                    .join(" ");
71                filter_args.push(format!("{} | {}", entry.name, patterns));
72            }
73
74            let mut args = vec!["--file-selection", "--title", self.title];
75
76            if self.multiple {
77                args.push("--multiple");
78                args.push("--separator");
79                args.push("\n")
80            }
81
82            for filter_arg in &filter_args {
83                args.push("--file-filter");
84                args.push(filter_arg.as_str());
85            }
86
87            let output = Command::new("zenity").args(args).output()?;
88            let stdout = String::from_utf8_lossy(&output.stdout);
89            let trimmed = stdout.trim();
90
91            if trimmed.is_empty() {
92                Ok(Vec::new())
93            } else if self.multiple {
94                Ok(parse_multi_select(trimmed))
95            } else {
96                Ok(vec![PathBuf::from(trimmed)])
97            }
98        }
99    }
100}