git.delta.rocks / remowt / refs/commits / 2499daa8100a

difftreelog

source

crates/ui-prompt/src/rofi.rs5.7 KiBsourcehistory
1use std::process::Stdio;23use tokio::io::AsyncWriteExt;4use tokio::process::Command;5use tracing::trace;67use crate::{Error, Prompter, Result, Source};89pub struct RofiPrompter;1011fn fixup_prompt(prompt: &str) -> &str {12    // Rofi always appends such suffix13    prompt.strip_suffix(": ").unwrap_or(prompt)14}1516impl Prompter for RofiPrompter {17    async fn prompt_enum(18        &self,19        prompt: &str,20        description: &str,21        variants: &[&str],22        source: &[Source],23    ) -> Result<u32> {24        trace!("rofi radio");25        let mut cmd = Command::new("rofi");26        let mesg = if source.is_empty() {27            description.to_owned()28        } else {29            let mut out = format!("{description}\n\n<b>Requested on ",);30            for s in source.iter() {31                out.push_str(&s.to_string());32            }33            out.push_str("</b>");34            out35        };36        cmd.args([37            "-dmenu",38            "-mesg",39            &mesg,40            "-sync",41            "-only-match",42            "-p",43            fixup_prompt(prompt),44            "-format",45            "i",46        ]);47        cmd.stdin(Stdio::piped());48        cmd.stdout(Stdio::piped());49        cmd.kill_on_drop(true);50        let mut child = cmd51            .spawn()52            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;5354        let mut stdin = child.stdin.take().expect("stdin is piped");55        for var in variants {56            stdin57                .write_all(var.replace('\n', " ").as_bytes())58                .await59                .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;60            stdin61                .write_all(b"\n")62                .await63                .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;64        }65        // write_all already flushes, just to be sure.66        let _ = stdin.flush().await;67        drop(stdin);6869        let out = child70            .wait_with_output()71            .await72            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;73        let stdout = out74            .stdout75            .strip_suffix(b"\n")76            .unwrap_or(&out.stdout)77            .to_owned();7879        let id: u32 = String::from_utf8(stdout)80            .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?81            .parse()82            .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?;83        if id as usize >= variants.len() {84            return Err(Error::InputError("invalid rofi response".to_owned()));85        }8687        Ok(id)88    }8990    async fn prompt_text(91        &self,92        echo: bool,93        prompt: &str,94        description: &str,95        source: &[Source],96    ) -> Result<String> {97        trace!("rofi text");98        let mut cmd = Command::new("rofi");99        let mesg = if source.is_empty() {100            description.to_owned()101        } else {102            let mut out = format!("{description}\n\n<b>Requested on ",);103            for s in source.iter() {104                out.push_str(&s.to_string());105            }106            out.push_str("</b>");107            out108        };109        cmd.args(["-dmenu", "-mesg", &mesg, "-p", fixup_prompt(prompt)]);110        if !echo {111            cmd.arg("-password");112        }113        cmd.stdin(Stdio::null());114        cmd.stdout(Stdio::piped());115        cmd.kill_on_drop(true);116        let child = cmd117            .spawn()118            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;119120        let out = child121            .wait_with_output()122            .await123            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;124        let stdout = out125            .stdout126            .strip_suffix(b"\n")127            .unwrap_or(&out.stdout)128            .to_owned();129130        Ok(String::from_utf8_lossy(&stdout).to_string())131    }132133    async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {134        trace!("rofi display");135        let mut cmd = Command::new("rofi");136        let mut mesg = if source.is_empty() {137            description.to_owned()138        } else {139            let mut out = format!("{description}\n\n<b>Coming from ",);140            for s in source.iter() {141                out.push_str(&s.to_string());142            }143            out.push_str("</b>");144            out145        };146        if error {147            mesg.insert_str(0, "<span color=\"red\">");148            mesg.push_str("</span>");149        }150        cmd.args(["-e", &mesg, "-markup"]);151        cmd.stdin(Stdio::null());152        cmd.stdout(Stdio::null());153        cmd.kill_on_drop(true);154        let mut child = cmd155            .spawn()156            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;157158        child159            .wait()160            .await161            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;162163        Ok(())164    }165}166167#[cfg(test)]168mod tests {169    use std::borrow::Cow;170171    use crate::rofi::RofiPrompter;172    use crate::{PrependSourcePrompter, Prompter as _, Source};173174    #[tokio::test]175    async fn test() {176        let prompter = PrependSourcePrompter {177            prompter: RofiPrompter,178            source: vec![Source(Cow::Borrowed("ssh"))],179        };180        prompter181            .prompt_radio("Enable", "Polkit needs access", &[])182            .await183            .expect("rofi");184        prompter185            .prompt_text(false, "Password", "Polkit needs access", &[])186            .await187            .expect("rofi");188        prompter189            .display_text(true, "Polkit needs access", &[])190            .await191            .expect("rofi");192    }193}