git.delta.rocks / remowt / refs/commits / 705d3cdac982

difftreelog

source

crates/ui-prompt/src/rofi.rs5.9 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 (i, s) in source.iter().enumerate() {31                if i != 0 {32                    out.push_str(" -> ");33                }34                out.push_str(&s.to_string());35            }36            out.push_str("</b>");37            out38        };39        cmd.args([40            "-dmenu",41            "-mesg",42            &mesg,43            "-sync",44            "-only-match",45            "-p",46            fixup_prompt(prompt),47            "-format",48            "i",49            "-markup-rows",50        ]);51        cmd.stdin(Stdio::piped());52        cmd.stdout(Stdio::piped());53        cmd.kill_on_drop(true);54        let mut child = cmd55            .spawn()56            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;5758        let mut stdin = child.stdin.take().expect("stdin is piped");59        for var in variants {60            stdin61                .write_all(var.replace('\n', " ").as_bytes())62                .await63                .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;64            stdin65                .write_all(b"\n")66                .await67                .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;68        }69        // write_all already flushes, just to be sure.70        let _ = stdin.flush().await;71        drop(stdin);7273        let out = child74            .wait_with_output()75            .await76            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;77        let stdout = out78            .stdout79            .strip_suffix(b"\n")80            .unwrap_or(&out.stdout)81            .to_owned();8283        let id: u32 = String::from_utf8(stdout)84            .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?85            .parse()86            .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?;87        if id as usize >= variants.len() {88            return Err(Error::InputError("invalid rofi response".to_owned()));89        }9091        Ok(id)92    }9394    async fn prompt_text(95        &self,96        echo: bool,97        prompt: &str,98        description: &str,99        source: &[Source],100    ) -> Result<String> {101        trace!("rofi text");102        let mut cmd = Command::new("rofi");103        let mesg = if source.is_empty() {104            description.to_owned()105        } else {106            let mut out = format!("{description}\n\n<b>Requested on ",);107            for (i, s) in source.iter().enumerate() {108                if i != 0 {109                    out.push_str(" -> ");110                }111                out.push_str(&s.to_string());112            }113            out.push_str("</b>");114            out115        };116        cmd.args(["-dmenu", "-mesg", &mesg, "-p", fixup_prompt(prompt)]);117        if !echo {118            cmd.arg("-password");119        }120        cmd.stdin(Stdio::null());121        cmd.stdout(Stdio::piped());122        cmd.kill_on_drop(true);123        let child = cmd124            .spawn()125            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;126127        let out = child128            .wait_with_output()129            .await130            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;131        let stdout = out132            .stdout133            .strip_suffix(b"\n")134            .unwrap_or(&out.stdout)135            .to_owned();136137        Ok(String::from_utf8_lossy(&stdout).to_string())138    }139140    async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {141        trace!("rofi display");142        let mut cmd = Command::new("rofi");143        let mut mesg = if source.is_empty() {144            description.to_owned()145        } else {146            let mut out = format!("{description}\n\n<b>Coming from ",);147            for s in source.iter() {148                out.push_str(&s.to_string());149            }150            out.push_str("</b>");151            out152        };153        if error {154            mesg.insert_str(0, "<span color=\"red\">");155            mesg.push_str("</span>");156        }157        cmd.args(["-e", &mesg, "-markup"]);158        cmd.stdin(Stdio::null());159        cmd.stdout(Stdio::null());160        cmd.kill_on_drop(true);161        let mut child = cmd162            .spawn()163            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;164165        child166            .wait()167            .await168            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;169170        Ok(())171    }172}173174#[cfg(test)]175mod tests {176    use std::borrow::Cow;177178    use crate::rofi::RofiPrompter;179    use crate::{PrependSourcePrompter, Prompter as _, Source};180181    #[tokio::test]182    async fn test() {183        let prompter = PrependSourcePrompter {184            prompter: RofiPrompter,185            source: vec![Source(Cow::Borrowed("ssh"))],186        };187        prompter188            .prompt_radio("Enable", "Polkit needs access", &[])189            .await190            .expect("rofi");191        prompter192            .prompt_text(false, "Password", "Polkit needs access", &[])193            .await194            .expect("rofi");195        prompter196            .display_text(true, "Polkit needs access", &[])197            .await198            .expect("rofi");199    }200}