aboutsummaryrefslogtreecommitdiff
path: root/src/backends
diff options
context:
space:
mode:
Diffstat (limited to 'src/backends')
-rw-r--r--src/backends/dialog.rs47
-rw-r--r--src/backends/kdialog.rs152
-rw-r--r--src/backends/mod.rs10
-rw-r--r--src/backends/stdio.rs21
-rw-r--r--src/backends/zenity.rs59
5 files changed, 237 insertions, 52 deletions
diff --git a/src/backends/dialog.rs b/src/backends/dialog.rs
index e681caf..668cf57 100644
--- a/src/backends/dialog.rs
+++ b/src/backends/dialog.rs
@@ -3,7 +3,7 @@
use std::process;
-use crate::{Choice, Error, Input, Message, Password, Question, Result};
+use crate::{Choice, Error, FileSelection, Input, Message, Password, Question, Result};
/// The `dialog` backend.
///
@@ -19,11 +19,7 @@ pub struct Dialog {
impl Dialog {
/// Creates a new `Dialog` instance without configuration.
pub fn new() -> Dialog {
- Dialog {
- backtitle: None,
- height: "0".to_string(),
- width: "0".to_string(),
- }
+ Default::default()
}
/// Sets the backtitle for the dialog boxes.
@@ -87,6 +83,16 @@ impl AsRef<Dialog> for Dialog {
}
}
+impl Default for Dialog {
+ fn default() -> Self {
+ Dialog {
+ backtitle: None,
+ height: "0".to_string(),
+ width: "0".to_string(),
+ }
+ }
+}
+
fn require_success(status: process::ExitStatus) -> Result<()> {
if status.success() {
Ok(())
@@ -111,19 +117,17 @@ fn get_choice(status: process::ExitStatus) -> Result<Choice> {
fn get_stderr(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),
- 255 => Ok(None),
- _ => Err(Error::from(("dialog", output.status))),
- }
- } else {
- Err(Error::from(("dialog", output.status)))
+ .map(Some)
+ .map_err(Error::from)
+ } else if let Some(code) = output.status.code() {
+ match code {
+ 0 => Ok(None),
+ 1 => Ok(None),
+ 255 => Ok(None),
+ _ => Err(Error::from(("dialog", output.status))),
}
+ } else {
+ Err(Error::from(("dialog", output.status)))
}
}
@@ -156,4 +160,11 @@ impl super::Backend for Dialog {
self.execute(args, vec![], &question.title)
.and_then(|output| get_choice(output.status))
}
+
+ fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
+ let dir = file_selection.path_to_string().ok_or("path not valid")?;
+ let args = vec!["--fselect", &dir];
+ self.execute(args, vec![], &file_selection.title)
+ .and_then(get_stderr)
+ }
}
diff --git a/src/backends/kdialog.rs b/src/backends/kdialog.rs
new file mode 100644
index 0000000..21928f8
--- /dev/null
+++ b/src/backends/kdialog.rs
@@ -0,0 +1,152 @@
+// 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, FileSelection, FileSelectionMode, 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, Default)]
+pub struct KDialog {
+ icon: Option<String>,
+ // TODO: --dontagain
+}
+
+impl KDialog {
+ /// Creates a new `KDialog` instance without configuration.
+ pub fn new() -> KDialog {
+ Default::default()
+ }
+
+ /// 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(Error::from)
+ } 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))
+ }
+
+ fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
+ let dir = file_selection.path_to_string().ok_or("path not valid")?;
+ let option = match file_selection.mode {
+ FileSelectionMode::Open => "--getopenfilename",
+ FileSelectionMode::Save => "--getsavefilename",
+ };
+ let args = vec![option, &dir];
+ self.execute(args, &file_selection.title)
+ .and_then(get_stdout)
+ }
+}
diff --git a/src/backends/mod.rs b/src/backends/mod.rs
index 5ae3cef..09013b1 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;
@@ -21,7 +23,7 @@ use crate::Result;
/// backend and create an instance manually. To use a backend, pass it to the [`show_with`][]
/// method of a dialog box.
///
-/// [`default_backend`]: ../function.default_backend.html
+/// [`default_backend`]: ../fn.default_backend.html
/// [`show_with`]: ../trait.DialogBox.html#method.show_with
pub trait Backend {
/// Shows the given input dialog and returns the input.
@@ -35,11 +37,14 @@ pub trait Backend {
/// Shows the given question dialog and returns the choice.
fn show_question(&self, question: &super::Question) -> Result<super::Choice>;
+
+ /// Shows the given file selection dialog and returns the file name.
+ fn show_file_selection(&self, file_selection: &super::FileSelection) -> Result<Option<String>>;
}
pub(crate) fn is_available(name: &str) -> bool {
if let Ok(path) = env::var("PATH") {
- for part in path.split(":") {
+ for part in path.split(':') {
if path::Path::new(part).join(name).exists() {
return true;
}
@@ -51,6 +56,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/backends/stdio.rs b/src/backends/stdio.rs
index 6838ef1..627714a 100644
--- a/src/backends/stdio.rs
+++ b/src/backends/stdio.rs
@@ -3,19 +3,19 @@
use std::io::{self, Write};
-use crate::{Choice, Input, Message, Password, Question, Result};
+use crate::{Choice, FileSelection, Input, Message, Password, Question, Result};
/// The fallback backend using standard input and output.
///
/// This backend is intended as a fallback backend to use if no other backend is available. The
/// dialogs are printed to the standard output and user input is read from the standard input.
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct Stdio {}
impl Stdio {
/// Creates a new `Stdio` instance.
pub fn new() -> Stdio {
- Stdio {}
+ Default::default()
}
}
@@ -35,7 +35,7 @@ fn print_title(title: &Option<String>) {
fn read_input() -> Result<String> {
let mut input = String::new();
io::stdin().read_line(&mut input)?;
- Ok(input.trim_end_matches("\n").to_string())
+ Ok(input.trim_end_matches('\n').to_string())
}
fn parse_choice(input: &str) -> Choice {
@@ -86,4 +86,17 @@ impl super::Backend for Stdio {
io::stdout().flush()?;
Ok(parse_choice(&read_input()?))
}
+
+ fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
+ let dir = file_selection.path_to_string().ok_or("path not valid")?;
+ print_title(&file_selection.title);
+ print!("{} [{}]: ", file_selection.text, dir);
+ io::stdout().flush()?;
+ let result = read_input()?;
+ if result.starts_with('/') {
+ Ok(Some(result))
+ } else {
+ Ok(Some(dir + &result))
+ }
+ }
}
diff --git a/src/backends/zenity.rs b/src/backends/zenity.rs
index 85206b3..ac0f98f 100644
--- a/src/backends/zenity.rs
+++ b/src/backends/zenity.rs
@@ -3,12 +3,14 @@
use std::process;
-use crate::{Choice, Error, Input, Message, Password, Question, Result};
+use crate::{
+ Choice, Error, FileSelection, FileSelectionMode, Input, Message, Password, Question, Result,
+};
/// The `zenity` backend.
///
/// This backend uses the external `zenity` program to display GTK+ dialog boxes.
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct Zenity {
icon: Option<String>,
width: Option<String>,
@@ -19,17 +21,12 @@ pub struct Zenity {
impl Zenity {
/// Creates a new `Zenity` instance without configuration.
pub fn new() -> Zenity {
- Zenity {
- icon: None,
- width: None,
- height: None,
- timeout: None,
- }
+ Default::default()
}
/// Sets the icon of the dialog box.
///
- /// The icon can either be one of `error`, `info`, `question` or `warning, or the path to an
+ /// 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());
@@ -101,15 +98,13 @@ impl AsRef<Zenity> for Zenity {
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)))
+ } else if let Some(code) = status.code() {
+ match code {
+ 5 => Ok(()),
+ _ => Err(Error::from(("zenity", status))),
}
+ } else {
+ Err(Error::from(("zenity", status)))
}
}
@@ -130,18 +125,16 @@ 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 {
- 0 => Ok(None),
- 1 => Ok(None),
- 5 => Ok(None),
- _ => Err(Error::from(("zenity", output.status))),
- }
- } else {
- Err(Error::from(("zenity", output.status)))
+ .map_err(Error::from)
+ } 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)))
}
}
@@ -172,4 +165,14 @@ impl super::Backend for Zenity {
self.execute(args, &question.title)
.and_then(|output| get_choice(output.status))
}
+
+ fn show_file_selection(&self, file_selection: &FileSelection) -> Result<Option<String>> {
+ let dir = file_selection.path_to_string().ok_or("path not valid")?;
+ let mut args = vec!["--file-selection", "--filename", &dir];
+ if file_selection.mode == FileSelectionMode::Save {
+ args.push("--save");
+ }
+ self.execute(args, &file_selection.title)
+ .and_then(get_stdout)
+ }
}