Skip to main content

dialog/backends/
kdialog.rs

1// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
2// Copyright (C) 2019 Stephan Sokolow <http://www.ssokolow.com/ContactMe>
3// SPDX-License-Identifier: MIT
4
5use std::process;
6
7use crate::{
8    Choice, Error, FileSelection, FileSelectionMode, Input, Message, Password, Question, Result,
9};
10
11/// Subprocess exit codes
12///
13/// Note: `kdialog` doesn't have a fixed correspondence between button labels and status codes.
14/// The following mappings occur:
15///
16/// - Yes/No = `0`/`1`
17/// - Yes/No/Cancel = `0`/`1`/`2`
18/// - OK/Cancel = `0`/`1`
19const OK: i32 = 0;
20const CANCEL: i32 = 1;
21
22/// The `kdialog` backend.
23///
24/// This backend uses the external `kdialog` program to display KDE dialog boxes.
25#[derive(Debug, Default)]
26pub struct KDialog {
27    icon: Option<String>,
28    // TODO: --dontagain
29}
30
31impl KDialog {
32    /// Creates a new `KDialog` instance without configuration.
33    pub fn new() -> KDialog {
34        Default::default()
35    }
36
37    /// Sets the icon in the dialog box's titlebar and taskbar button.
38    ///
39    /// The icon can be either a name from the user's configured icon theme, such as `error` or
40    /// `info` or the path to an image to use.
41    ///
42    /// The default image depends on the dialog type.
43    pub fn set_icon(&mut self, icon: impl Into<String>) {
44        self.icon = Some(icon.into());
45    }
46
47    pub(crate) fn is_available() -> bool {
48        super::is_available("kdialog")
49    }
50
51    fn execute(&self, args: Vec<&str>, title: &Option<String>) -> Result<process::Output> {
52        let mut command = process::Command::new("kdialog");
53
54        if let Some(ref icon) = self.icon {
55            command.arg("--icon");
56            command.arg(icon);
57        }
58        if let Some(ref title) = title {
59            command.arg("--title");
60            command.arg(title);
61        }
62
63        command.args(args);
64        command.output().map_err(Error::IoError)
65    }
66}
67
68impl AsRef<KDialog> for KDialog {
69    fn as_ref(&self) -> &Self {
70        self
71    }
72}
73
74fn require_success(status: process::ExitStatus) -> Result<()> {
75    if status.success() {
76        Ok(())
77    } else if let Some(code) = status.code() {
78        match code {
79            CANCEL => Ok(()),
80            _ => Err(Error::from(("kdialog", status))),
81        }
82    } else {
83        Err(Error::from(("kdialog", status)))
84    }
85}
86
87fn get_choice(status: process::ExitStatus) -> Result<Choice> {
88    if let Some(code) = status.code() {
89        match code {
90            OK => Ok(Choice::Yes),
91            CANCEL => Ok(Choice::No),
92            _ => Err(Error::from(("kdialog", status))),
93        }
94    } else {
95        Err(Error::from(("kdialog", status)))
96    }
97}
98
99fn get_stdout(output: process::Output) -> Result<Option<String>> {
100    if output.status.success() {
101        String::from_utf8(output.stdout)
102            .map(|s| Some(s.trim_end_matches('\n').to_string()))
103            .map_err(Error::from)
104    } else if let Some(code) = output.status.code() {
105        match code {
106            OK => Ok(None),
107            CANCEL => Ok(None),
108            _ => Err(Error::from(("kdialog", output.status))),
109        }
110    } else {
111        Err(Error::from(("kdialog", output.status)))
112    }
113}
114
115impl super::Backend for KDialog {
116    fn show_input(&self, input: &Input) -> Result<Option<String>> {
117        let mut args = vec!["--inputbox", &input.text];
118        if let Some(ref default) = input.default {
119            args.push(default);
120        }
121        self.execute(args, &input.title).and_then(get_stdout)
122    }
123
124    fn show_message(&self, message: &Message) -> Result<()> {
125        let args = vec!["--msgbox", &message.text];
126        self.execute(args, &message.title)
127            .and_then(|output| require_success(output.status))
128            .map(|_| ())
129    }
130
131    fn show_password(&self, password: &Password) -> Result<Option<String>> {
132        let args = vec!["--password", &password.text];
133        self.execute(args, &password.title).and_then(get_stdout)
134    }
135
136    fn show_question(&self, question: &Question) -> Result<Choice> {
137        let args = vec!["--yesno", &question.text];
138        self.execute(args, &question.title)
139            .and_then(|output| get_choice(output.status))
140    }
141
142    fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
143        let dir = file_selection.path_to_string().ok_or("path not valid")?;
144        let option = match file_selection.mode {
145            FileSelectionMode::Open => "--getopenfilename",
146            FileSelectionMode::Save => "--getsavefilename",
147        };
148        let args = vec![option, &dir];
149        self.execute(args, &file_selection.title)
150            .and_then(get_stdout)
151    }
152}