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