difftreelog
fix rofi cancellation
in: trunk
2 files changed
crates/remowt-client/src/subprocess.rsdiffbeforeafterboth--- a/crates/remowt-client/src/subprocess.rs
+++ b/crates/remowt-client/src/subprocess.rs
@@ -64,12 +64,12 @@
drop(stdin);
let drain_out = async move {
if let Some(s) = stdout {
- drain_to_tracing(s, "<child stdout>".to_owned(), false).await;
+ let _ = drain_to_tracing(s, "<child stdout>".to_owned(), false).await;
}
};
let drain_err = async move {
if let Some(s) = stderr {
- drain_to_tracing(s, "<child stderr>".to_owned(), true).await;
+ let _ = drain_to_tracing(s, "<child stderr>".to_owned(), true).await;
}
};
let wait = async move {
crates/remowt-ui-prompt/src/rofi.rsdiffbeforeafterboth1use std::process::Stdio;23use tokio::io::AsyncWriteExt;4use tokio::process::Command;5use tracing::trace;67use crate::{Error, Prompter, Result, Source};89#[derive(Clone)]10pub struct RofiPrompter;1112fn fixup_prompt(prompt: &str) -> &str {13 // Rofi always appends such suffix14 prompt.strip_suffix(": ").unwrap_or(prompt)15}1617fn rofi_command() -> Command {18 Command::new(option_env!("ROFI").unwrap_or("rofi"))19}2021impl Prompter for RofiPrompter {22 async fn prompt_enum(23 &self,24 prompt: &str,25 description: &str,26 variants: &[&str],27 source: &[Source],28 ) -> Result<u32> {29 trace!("rofi radio");30 let mut cmd = rofi_command();31 let mesg = if source.is_empty() {32 description.to_owned()33 } else {34 let mut out = format!("{description}\n\n<b>Requested on ",);35 for (i, s) in source.iter().enumerate() {36 if i != 0 {37 out.push_str(" -> ");38 }39 out.push_str(&s.to_string());40 }41 out.push_str("</b>");42 out43 };44 cmd.args([45 "-dmenu",46 "-mesg",47 &mesg,48 "-sync",49 "-only-match",50 "-p",51 fixup_prompt(prompt),52 "-format",53 "i",54 "-markup-rows",55 ]);56 cmd.stdin(Stdio::piped());57 cmd.stdout(Stdio::piped());58 cmd.kill_on_drop(true);59 let mut child = cmd60 .spawn()61 .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;6263 let mut stdin = child.stdin.take().expect("stdin is piped");64 for var in variants {65 stdin66 .write_all(var.replace('\n', " ").as_bytes())67 .await68 .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;69 stdin70 .write_all(b"\n")71 .await72 .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;73 }74 // write_all already flushes, just to be sure.75 let _ = stdin.flush().await;76 drop(stdin);7778 let out = child79 .wait_with_output()80 .await81 .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;82 let stdout = out83 .stdout84 .strip_suffix(b"\n")85 .unwrap_or(&out.stdout)86 .to_owned();8788 let id: u32 = String::from_utf8(stdout)89 .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?90 .parse()91 .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?;92 if id as usize >= variants.len() {93 return Err(Error::InputError("invalid rofi response".to_owned()));94 }9596 Ok(id)97 }9899 async fn prompt_text(100 &self,101 echo: bool,102 prompt: &str,103 description: &str,104 source: &[Source],105 ) -> Result<String> {106 trace!("rofi text");107 let mut cmd = rofi_command();108 let mesg = if source.is_empty() {109 description.to_owned()110 } else {111 let mut out = format!("{description}\n\n<b>Requested on ",);112 for (i, s) in source.iter().enumerate() {113 if i != 0 {114 out.push_str(" -> ");115 }116 out.push_str(&s.to_string());117 }118 out.push_str("</b>");119 out120 };121 cmd.args(["-dmenu", "-mesg", &mesg, "-p", fixup_prompt(prompt)]);122 if !echo {123 cmd.arg("-password");124 }125 cmd.stdin(Stdio::null());126 cmd.stdout(Stdio::piped());127 cmd.kill_on_drop(true);128 let child = cmd129 .spawn()130 .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;131132 let out = child133 .wait_with_output()134 .await135 .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;136 let stdout = out137 .stdout138 .strip_suffix(b"\n")139 .unwrap_or(&out.stdout)140 .to_owned();141142 Ok(String::from_utf8_lossy(&stdout).to_string())143 }144145 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {146 trace!("rofi display");147 let mut cmd = rofi_command();148 let mut mesg = if source.is_empty() {149 description.to_owned()150 } else {151 let mut out = format!("{description}\n\n<b>Coming from ",);152 for s in source.iter() {153 out.push_str(&s.to_string());154 }155 out.push_str("</b>");156 out157 };158 if error {159 mesg.insert_str(0, "<span color=\"red\">");160 mesg.push_str("</span>");161 }162 cmd.args(["-e", &mesg, "-markup"]);163 cmd.stdin(Stdio::null());164 cmd.stdout(Stdio::null());165 cmd.kill_on_drop(true);166 let mut child = cmd167 .spawn()168 .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;169170 child171 .wait()172 .await173 .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;174175 Ok(())176 }177}178179#[cfg(test)]180mod tests {181 use std::borrow::Cow;182183 use crate::rofi::RofiPrompter;184 use crate::{PrependSourcePrompter, Prompter as _, Source};185186 // #[tokio::test]187 #[tokio::test]188 #[ignore = "interactive"]189 async fn test() {190 let prompter = PrependSourcePrompter {191 prompter: RofiPrompter,192 description: "test".to_owned(),193 source: vec![Source(Cow::Borrowed("ssh"))],194 };195 prompter196 .prompt_radio("Enable", "Polkit needs access", &[])197 .await198 .expect("rofi");199 prompter200 .prompt_text(false, "Password", "Polkit needs access", &[])201 .await202 .expect("rofi");203 prompter204 .display_text(true, "Polkit needs access", &[])205 .await206 .expect("rofi");207 }208}1use std::process::Stdio;23use tokio::io::AsyncWriteExt;4use tokio::process::Command;5use tracing::trace;67use crate::{Error, Prompter, Result, Source};89#[derive(Clone)]10pub struct RofiPrompter;1112fn fixup_prompt(prompt: &str) -> &str {13 // Rofi always appends such suffix14 prompt.strip_suffix(": ").unwrap_or(prompt)15}1617fn rofi_command() -> Command {18 Command::new(option_env!("ROFI").unwrap_or("rofi"))19}2021impl Prompter for RofiPrompter {22 async fn prompt_enum(23 &self,24 prompt: &str,25 description: &str,26 variants: &[&str],27 source: &[Source],28 ) -> Result<u32> {29 trace!("rofi radio");30 let mut cmd = rofi_command();31 let mesg = if source.is_empty() {32 description.to_owned()33 } else {34 let mut out = format!("{description}\n\n<b>Requested on ",);35 for (i, s) in source.iter().enumerate() {36 if i != 0 {37 out.push_str(" -> ");38 }39 out.push_str(&s.to_string());40 }41 out.push_str("</b>");42 out43 };44 cmd.args([45 "-dmenu",46 "-mesg",47 &mesg,48 "-sync",49 "-no-custom",50 "-p",51 fixup_prompt(prompt),52 "-format",53 "i",54 "-markup-rows",55 ]);56 cmd.stdin(Stdio::piped());57 cmd.stdout(Stdio::piped());58 cmd.kill_on_drop(true);59 let mut child = cmd60 .spawn()61 .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;6263 let mut stdin = child.stdin.take().expect("stdin is piped");64 for var in variants {65 stdin66 .write_all(var.replace('\n', " ").as_bytes())67 .await68 .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;69 stdin70 .write_all(b"\n")71 .await72 .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;73 }74 // write_all already flushes, just to be sure.75 let _ = stdin.flush().await;76 drop(stdin);7778 let out = child79 .wait_with_output()80 .await81 .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;82 match out.status.code() {83 Some(0) => {}84 Some(1) => return Err(Error::Cancel),85 other => {86 return Err(Error::InputError(format!(87 "rofi exited with status {other:?}"88 )));89 }90 }91 let stdout = out92 .stdout93 .strip_suffix(b"\n")94 .unwrap_or(&out.stdout)95 .to_owned();9697 let id: u32 = String::from_utf8(stdout)98 .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?99 .parse()100 .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?;101 if id as usize >= variants.len() {102 return Err(Error::InputError("invalid rofi response".to_owned()));103 }104105 Ok(id)106 }107108 async fn prompt_text(109 &self,110 echo: bool,111 prompt: &str,112 description: &str,113 source: &[Source],114 ) -> Result<String> {115 trace!("rofi text");116 let mut cmd = rofi_command();117 let mesg = if source.is_empty() {118 description.to_owned()119 } else {120 let mut out = format!("{description}\n\n<b>Requested on ",);121 for (i, s) in source.iter().enumerate() {122 if i != 0 {123 out.push_str(" -> ");124 }125 out.push_str(&s.to_string());126 }127 out.push_str("</b>");128 out129 };130 cmd.args(["-dmenu", "-mesg", &mesg, "-p", fixup_prompt(prompt)]);131 if !echo {132 cmd.arg("-password");133 }134 cmd.stdin(Stdio::null());135 cmd.stdout(Stdio::piped());136 cmd.kill_on_drop(true);137 let child = cmd138 .spawn()139 .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;140141 let out = child142 .wait_with_output()143 .await144 .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;145 match out.status.code() {146 Some(0) => {}147 Some(1) => return Err(Error::Cancel),148 other => {149 return Err(Error::InputError(format!(150 "rofi exited with status {other:?}"151 )));152 }153 }154 let stdout = out155 .stdout156 .strip_suffix(b"\n")157 .unwrap_or(&out.stdout)158 .to_owned();159160 Ok(String::from_utf8_lossy(&stdout).to_string())161 }162163 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {164 trace!("rofi display");165 let mut cmd = rofi_command();166 let mut mesg = if source.is_empty() {167 description.to_owned()168 } else {169 let mut out = format!("{description}\n\n<b>Coming from ",);170 for s in source.iter() {171 out.push_str(&s.to_string());172 }173 out.push_str("</b>");174 out175 };176 if error {177 mesg.insert_str(0, "<span color=\"red\">");178 mesg.push_str("</span>");179 }180 cmd.args(["-e", &mesg, "-markup"]);181 cmd.stdin(Stdio::null());182 cmd.stdout(Stdio::null());183 cmd.kill_on_drop(true);184 let mut child = cmd185 .spawn()186 .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;187188 child189 .wait()190 .await191 .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;192193 Ok(())194 }195}196197#[cfg(test)]198mod tests {199 use std::borrow::Cow;200201 use crate::rofi::RofiPrompter;202 use crate::{PrependSourcePrompter, Prompter as _, Source};203204 // #[tokio::test]205 #[tokio::test]206 #[ignore = "interactive"]207 async fn test() {208 let prompter = PrependSourcePrompter {209 prompter: RofiPrompter,210 description: "test".to_owned(),211 source: vec![Source(Cow::Borrowed("ssh"))],212 };213 prompter214 .prompt_radio("Enable", "Polkit needs access", &[])215 .await216 .expect("rofi");217 prompter218 .prompt_text(false, "Password", "Polkit needs access", &[])219 .await220 .expect("rofi");221 prompter222 .display_text(true, "Polkit needs access", &[])223 .await224 .expect("rofi");225 }226}