native-dialog 0.9.6

A library to display dialogs. Supports GNU/Linux, BSD Unix, macOS and Windows.
Documentation
use std::ffi::OsString;
use std::process::{Command, Output};

use ascii::AsAsciiStr;

use super::version::Version;
use crate::{Error, Result};

pub enum BackendKind {
    KDialog,
    Zenity,
    Yad,
}

pub struct Backend {
    pub command: Command,
    pub kind: BackendKind,
}

impl Backend {
    pub fn new() -> Result<Backend> {
        let has_display = match std::env::var("DISPLAY") {
            Ok(display) => !display.is_empty(),
            _ => false,
        };

        let candidates = match std::env::var("XDG_CURRENT_DESKTOP").as_deref() {
            Ok("KDE") if has_display => [Self::new_kdialog, Self::new_zenity, Self::new_yad],
            Ok("GNOME") => [Self::new_zenity, Self::new_kdialog, Self::new_yad],
            _ => [Self::new_yad, Self::new_kdialog, Self::new_zenity],
        };

        for candidate in candidates {
            if let Some(command) = candidate() {
                return Ok(command);
            }
        }

        Err(Error::MissingDep)
    }

    fn new_kdialog() -> Option<Backend> {
        let path = which::which("kdialog").ok()?;
        let command = Command::new(path);

        Some(Self {
            command,
            kind: BackendKind::KDialog,
        })
    }

    fn new_zenity() -> Option<Backend> {
        let path = which::which("zenity").ok()?;
        let command = Command::new(path);

        Some(Self {
            command,
            kind: BackendKind::Zenity,
        })
    }

    fn new_yad() -> Option<Backend> {
        let path = which::which("yad").ok()?;
        let command = Command::new(path);

        Some(Self {
            command,
            kind: BackendKind::Yad,
        })
    }

    pub fn version(&self) -> Option<Version> {
        let program = self.command.get_program();
        let output = Command::new(program).arg("--version").output().ok()?;
        let stdout = output.stdout.as_ascii_str().ok()?.to_string();

        let mut parts = stdout.split_whitespace();
        match self.kind {
            BackendKind::KDialog => parts.next().and_then(Version::parse),
            BackendKind::Zenity => parts.next().and_then(Version::parse),
            BackendKind::Yad => parts.last().and_then(Version::parse),
        }
    }

    pub fn exec(mut self) -> Result<Option<Vec<u8>>> {
        let program = self.command.get_program().to_os_string();
        let output = self.command.output()?;

        Self::inspect(program, output)
    }

    #[cfg(feature = "async")]
    pub async fn spawn(self) -> Result<Option<Vec<u8>>> {
        use async_process::Command as AsyncCommand;

        let program = self.command.get_program().to_os_string();
        let output = AsyncCommand::from(self.command).output().await?;

        Self::inspect(program, output)
    }

    fn inspect(program: OsString, output: Output) -> Result<Option<Vec<u8>>> {
        match output.status.code() {
            Some(0) => Ok(Some(output.stdout)),
            Some(_) => Ok(None),
            None => Err(Error::Killed(program)),
        }
    }
}