summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backends/mod.rs2
-rw-r--r--src/backends/zenity.rs165
2 files changed, 167 insertions, 0 deletions
diff --git a/src/backends/mod.rs b/src/backends/mod.rs
index 1d555e3..b9cecd0 100644
--- a/src/backends/mod.rs
+++ b/src/backends/mod.rs
@@ -2,8 +2,10 @@
// SPDX-License-Identifier: MIT
mod dialog;
+mod zenity;
pub use crate::backends::dialog::Dialog;
+pub use crate::backends::zenity::Zenity;
use crate::Result;
diff --git a/src/backends/zenity.rs b/src/backends/zenity.rs
new file mode 100644
index 0000000..a90c3da
--- /dev/null
+++ b/src/backends/zenity.rs
@@ -0,0 +1,165 @@
+// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+use std::process;
+
+use crate::{Choice, Error, Input, Message, Password, Question, Result};
+
+/// The `zenity` backend.
+///
+/// This backend uses the external `zenity` program to display GTK+ dialog boxes.
+#[derive(Debug)]
+pub struct Zenity {
+ icon: Option<String>,
+ width: Option<String>,
+ height: Option<String>,
+ timeout: Option<String>,
+}
+
+impl Zenity {
+ /// Creates a new `Zenity` instance without configuration.
+ pub fn new() -> Zenity {
+ Zenity {
+ icon: None,
+ width: None,
+ height: None,
+ timeout: None,
+ }
+ }
+
+ /// Sets the icon of the dialog box.
+ ///
+ /// The icon can either be one of `error`, `info`, `question` or `warning, 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());
+ }
+
+ /// Sets the height of the dialog boxes.
+ ///
+ /// The height is given in pixels. The actual height of the dialog box might be higher than
+ /// the given height if the content would not fit otherwise.
+ pub fn set_height(&mut self, height: u32) {
+ self.height = Some(height.to_string());
+ }
+
+ /// Sets the width of the dialog boxes.
+ ///
+ /// The width is given in pixels. The actual width of the dialog box might be higher than the
+ /// given width if the content would not fit otherwise.
+ pub fn set_width(&mut self, width: u32) {
+ self.width = Some(width.to_string());
+ }
+
+ /// Sets the timout of the dialog boxes (in seconds).
+ ///
+ /// After the timeout, the dialog box is closed. The timeout is handled like a cancel event.
+ /// Per default, there is no timeout.
+ pub fn set_timeout(&mut self, timeout: u32) {
+ self.timeout = Some(timeout.to_string());
+ }
+
+ fn execute(&self, args: Vec<&str>, title: &Option<String>) -> Result<process::Output> {
+ let mut command = process::Command::new("zenity");
+
+ if let Some(ref icon) = self.icon {
+ command.arg("--window-icon");
+ command.arg(icon);
+ }
+ if let Some(ref width) = self.width {
+ command.arg("--width");
+ command.arg(width);
+ }
+ if let Some(ref height) = self.height {
+ command.arg("--height");
+ command.arg(height);
+ }
+ if let Some(ref timeout) = self.timeout {
+ command.arg("--timeout");
+ command.arg(timeout);
+ }
+ if let Some(ref title) = title {
+ command.arg("--title");
+ command.arg(title);
+ }
+
+ command.args(args);
+ command.output().map_err(Error::IoError)
+ }
+}
+
+fn require_success(status: process::ExitStatus) -> Result<()> {
+ if status.success() {
+ Ok(())
+ } else {
+ if let Some(code) = status.code() {
+ match code {
+ 5 => Ok(()),
+ _ => Err(Error::from(("zenity", status))),
+ }
+ } else {
+ Err(Error::from(("zenity", status)))
+ }
+ }
+}
+
+fn get_choice(status: process::ExitStatus) -> Result<Choice> {
+ if let Some(code) = status.code() {
+ match code {
+ 0 => Ok(Choice::Yes),
+ 1 => Ok(Choice::No),
+ 5 => Ok(Choice::Cancel),
+ _ => Err(Error::from(("zenity", status))),
+ }
+ } else {
+ Err(Error::from(("zenity", status)))
+ }
+}
+
+fn get_stdout(output: process::Output) -> Result<Option<String>> {
+ if output.status.success() {
+ String::from_utf8(output.stderr)
+ .map(|s| Some(s))
+ .map_err(|err| Error::from(err))
+ } else {
+ if let Some(code) = output.status.code() {
+ match code {
+ 0 => Ok(None),
+ 1 => Ok(None),
+ 5 => Ok(None),
+ _ => Err(Error::from(("zenity", output.status))),
+ }
+ } else {
+ Err(Error::from(("zenity", output.status)))
+ }
+ }
+}
+
+impl super::Backend for Zenity {
+ fn show_input(&self, input: &Input) -> Result<Option<String>> {
+ let mut args = vec!["--entry", "--text", &input.text];
+ if let Some(ref default) = input.default {
+ args.push("--entry-text");
+ args.push(default);
+ }
+ self.execute(args, &input.title).and_then(get_stdout)
+ }
+
+ fn show_message(&self, message: &Message) -> Result<()> {
+ let args = vec!["--info", "--text", &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"];
+ self.execute(args, &password.title).and_then(get_stdout)
+ }
+
+ fn show_question(&self, question: &Question) -> Result<Choice> {
+ let args = vec!["--question", "--text", &question.text];
+ self.execute(args, &question.title)
+ .and_then(|output| get_choice(output.status))
+ }
+}