diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backends/mod.rs | 2 | ||||
-rw-r--r-- | src/backends/zenity.rs | 165 |
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)) + } +} |