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