diff options
| author | Stephan Sokolow <http://www.ssokolow.com/ContactMe> | 2019-10-24 20:36:42 -0400 | 
|---|---|---|
| committer | Robin Krahl <robin.krahl@ireas.org> | 2019-10-25 22:32:45 +0200 | 
| commit | a23de9787bccde4b6244312f04cb7cfc3b528db6 (patch) | |
| tree | 06bee04d6b347ffec53493fcf8c9bbfc6c7eb93c | |
| parent | 10462c2c9f4ae2ffd3c32dd4628e0052067c15fa (diff) | |
| download | dialog-rs-a23de9787bccde4b6244312f04cb7cfc3b528db6.tar.gz dialog-rs-a23de9787bccde4b6244312f04cb7cfc3b528db6.tar.bz2  | |
Add a backend based on KDE's `kdialog`
| -rw-r--r-- | CHANGELOG.md | 3 | ||||
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | examples/backend-kdialog.rs | 18 | ||||
| -rw-r--r-- | src/backends/kdialog.rs | 143 | ||||
| -rw-r--r-- | src/backends/mod.rs | 3 | ||||
| -rw-r--r-- | src/lib.rs | 24 | 
6 files changed, 191 insertions, 5 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index edf65a1..c327be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- Add the `KDialog` backend (contributed by Stephan Sokolow). +  # v0.2.1 (2019-06-30)  - Fix the input and password dialogs for the `zenity` backend (thanks Silvano    Cortesi for the bug report): @@ -5,8 +5,9 @@ A Rust library for displaying dialog boxes using various backends.  [Documentation][]  Currently `dialog-rs` supports input, message, password and question dialogs. -It can use the `dialog` or `zenity` tools to display the dialog boxes.  If none -of these tools is available, the dialogs are printed to the standard output. +It can use the `dialog`, `kdialog`, or `zenity` tools to display the dialog +boxes.  If none of these tools is available, the dialogs are printed to the +standard output.  ## Example diff --git a/examples/backend-kdialog.rs b/examples/backend-kdialog.rs new file mode 100644 index 0000000..5c3fb46 --- /dev/null +++ b/examples/backend-kdialog.rs @@ -0,0 +1,18 @@ +// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org> +// SPDX-License-Identifier: MIT + +use dialog::backends; +use dialog::DialogBox; + +fn main() -> dialog::Result<()> { +    let mut backend = backends::KDialog::new(); + +    dialog::Message::new("This is a message.") +        .title("And this is a title:") +        .show_with(&backend)?; + +    backend.set_icon("error"); +    dialog::Message::new("This is an error message.") +        .title("Error") +        .show_with(&backend) +} diff --git a/src/backends/kdialog.rs b/src/backends/kdialog.rs new file mode 100644 index 0000000..153e4c5 --- /dev/null +++ b/src/backends/kdialog.rs @@ -0,0 +1,143 @@ +// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org> +// Copyright (C) 2019 Stephan Sokolow <http://www.ssokolow.com/ContactMe> +// SPDX-License-Identifier: MIT + +use std::process; + +use crate::{Choice, Error, Input, Message, Password, Question, Result}; + +/// Subprocess exit codes +/// +/// Note: `kdialog` doesn't have a fixed correspondence between button labels and status codes. +/// The following mappings occur: +/// +/// - Yes/No = `0`/`1` +/// - Yes/No/Cancel = `0`/`1`/`2` +/// - OK/Cancel = `0`/`1` +const OK: i32 = 0; +const CANCEL: i32 = 1; + +/// The `kdialog` backend. +/// +/// This backend uses the external `kdialog` program to display KDE dialog boxes. +#[derive(Debug)] +pub struct KDialog { +    icon: Option<String>, +    // TODO: --dontagain +} + +impl KDialog { +    /// Creates a new `KDialog` instance without configuration. +    pub fn new() -> KDialog { +        KDialog { icon: None } +    } + +    /// Sets the icon in the dialog box's titlebar and taskbar button. +    /// +    /// The icon can be either a name from the user's configured icon theme, such as `error` or +    /// `info` or the path to an image to use. +    /// +    /// The default image depends on the dialog type. +    pub fn set_icon(&mut self, icon: impl Into<String>) { +        self.icon = Some(icon.into()); +    } + +    pub(crate) fn is_available() -> bool { +        super::is_available("kdialog") +    } + +    fn execute(&self, args: Vec<&str>, title: &Option<String>) -> Result<process::Output> { +        let mut command = process::Command::new("kdialog"); + +        if let Some(ref icon) = self.icon { +            command.arg("--icon"); +            command.arg(icon); +        } +        if let Some(ref title) = title { +            command.arg("--title"); +            command.arg(title); +        } + +        command.args(args); +        command.output().map_err(Error::IoError) +    } +} + +impl AsRef<KDialog> for KDialog { +    fn as_ref(&self) -> &Self { +        self +    } +} + +fn require_success(status: process::ExitStatus) -> Result<()> { +    if status.success() { +        Ok(()) +    } else { +        if let Some(code) = status.code() { +            match code { +                CANCEL => Ok(()), +                _ => Err(Error::from(("kdialog", status))), +            } +        } else { +            Err(Error::from(("kdialog", status))) +        } +    } +} + +fn get_choice(status: process::ExitStatus) -> Result<Choice> { +    if let Some(code) = status.code() { +        match code { +            OK => Ok(Choice::Yes), +            CANCEL => Ok(Choice::No), +            _ => Err(Error::from(("kdialog", status))), +        } +    } else { +        Err(Error::from(("kdialog", status))) +    } +} + +fn get_stdout(output: process::Output) -> Result<Option<String>> { +    if output.status.success() { +        String::from_utf8(output.stdout) +            .map(|s| Some(s.trim_end_matches('\n').to_string())) +            .map_err(|err| Error::from(err)) +    } else { +        if let Some(code) = output.status.code() { +            match code { +                OK => Ok(None), +                CANCEL => Ok(None), +                _ => Err(Error::from(("kdialog", output.status))), +            } +        } else { +            Err(Error::from(("kdialog", output.status))) +        } +    } +} + +impl super::Backend for KDialog { +    fn show_input(&self, input: &Input) -> Result<Option<String>> { +        let mut args = vec!["--inputbox", &input.text]; +        if let Some(ref default) = input.default { +            args.push(default); +        } +        self.execute(args, &input.title).and_then(get_stdout) +    } + +    fn show_message(&self, message: &Message) -> Result<()> { +        let args = vec!["--msgbox", &message.text]; +        self.execute(args, &message.title) +            .and_then(|output| require_success(output.status)) +            .map(|_| ()) +    } + +    fn show_password(&self, password: &Password) -> Result<Option<String>> { +        let args = vec!["--password", &password.text]; +        self.execute(args, &password.title).and_then(get_stdout) +    } + +    fn show_question(&self, question: &Question) -> Result<Choice> { +        let args = vec!["--yesno", &question.text]; +        self.execute(args, &question.title) +            .and_then(|output| get_choice(output.status)) +    } +} diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 5ae3cef..d5dac1c 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -2,10 +2,12 @@  // SPDX-License-Identifier: MIT  mod dialog; +mod kdialog;  mod stdio;  mod zenity;  pub use crate::backends::dialog::Dialog; +pub use crate::backends::kdialog::KDialog;  pub use crate::backends::stdio::Stdio;  pub use crate::backends::zenity::Zenity; @@ -51,6 +53,7 @@ pub(crate) fn is_available(name: &str) -> bool {  pub(crate) fn from_str(s: &str) -> Option<Box<dyn Backend>> {      match s.to_lowercase().as_ref() {          "dialog" => Some(Box::new(Dialog::new())), +        "kdialog" => Some(Box::new(KDialog::new())),          "stdio" => Some(Box::new(Stdio::new())),          "zenity" => Some(Box::new(Zenity::new())),          _ => None, @@ -15,6 +15,8 @@  //! These dialog boxes can be displayed using various backends:  //! - [`Dialog`][]: uses `dialog` to display ncurses-based dialog boxes (requires the external  //!   `dialog` tool) +//! - [`KDialog`][]: uses `kdialog` to display Qt-based dialog boxes (requires the external +//!   `kdialog` tool)  //! - [`Stdio`][]: prints messages to the standard output and reads user input form standard input  //!   (intended as a fallback backend)  //! - [`Zenity`][]: uses `zenity` to display GTK-based dialog boxes (requires the external `zenity` @@ -72,6 +74,7 @@  //! [`Message`]: struct.Message.html  //! [`Password`]: struct.Password.html  //! [`Question`]: struct.Question.html +//! [`KDialog`]: backends/struct.KDialog.html  //! [`Stdio`]: backends/struct.Stdio.html  //! [`Zenity`]: backends/struct.Zenity.html  //! [`default_backend`]: fn.default_backend.html @@ -345,13 +348,16 @@ impl DialogBox for Question {  /// - If the `DIALOG` environment variable is set to a valid backend name, this backend is used.  ///   A valid backend name is the name of a struct in the `backends` module implementing the  ///   `Backend` trait in any case. -/// - If the `DISPLAY` environment variable is set, the first available backend from this list is -///   used: -///   - [`Zenity`][] +/// - If the `DISPLAY` environment variable is set, the following resolution algorithm is used: +///   - If the `XDG_CURRENT_DESKTOP` environment variable is set to `KDE`, [`KDialog`][] is used. +///   - Otherwise, the first available backend from this list is used: +///     - [`Zenity`][] +///     - [`KDialog`][]  /// - If the [`Dialog`][] backend is available, it is used.  /// - Otherwise, a [`Stdio`][] instance is returned.  ///  /// [`Dialog`]: backends/struct.Dialog.html +/// [`KDialog`]: backends/struct.KDialog.html  /// [`Stdio`]: backends/struct.Stdio.html  /// [`Zenity`]: backends/struct.Zenity.html  pub fn default_backend() -> Box<dyn backends::Backend> { @@ -363,9 +369,21 @@ pub fn default_backend() -> Box<dyn backends::Backend> {      if let Ok(display) = env::var("DISPLAY") {          if !display.is_empty() { +            // Prefer KDialog if the user is logged into a KDE session +            let kdialog_available = backends::KDialog::is_available(); +            if let Ok(desktop) = env::var("XDG_CURRENT_DESKTOP") { +                if kdialog_available && desktop == "KDE" { +                    return Box::new(backends::KDialog::new()); +                } +            } +              if backends::Zenity::is_available() {                  return Box::new(backends::Zenity::new());              } + +            if kdialog_available { +                return Box::new(backends::KDialog::new()); +            }          }      }  | 
