summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStephan Sokolow <http://www.ssokolow.com/ContactMe>2019-10-24 20:36:42 -0400
committerRobin Krahl <robin.krahl@ireas.org>2019-10-25 22:32:45 +0200
commita23de9787bccde4b6244312f04cb7cfc3b528db6 (patch)
tree06bee04d6b347ffec53493fcf8c9bbfc6c7eb93c /src
parent10462c2c9f4ae2ffd3c32dd4628e0052067c15fa (diff)
downloaddialog-rs-a23de9787bccde4b6244312f04cb7cfc3b528db6.tar.gz
dialog-rs-a23de9787bccde4b6244312f04cb7cfc3b528db6.tar.bz2
Add a backend based on KDE's `kdialog`
Diffstat (limited to 'src')
-rw-r--r--src/backends/kdialog.rs143
-rw-r--r--src/backends/mod.rs3
-rw-r--r--src/lib.rs24
3 files changed, 167 insertions, 3 deletions
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,
diff --git a/src/lib.rs b/src/lib.rs
index f4ac763..61be22b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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());
+ }
}
}