git.delta.rocks / remowt / refs/commits / 61cfb317f078

difftreelog

fix rofi cancellation

xwvqqwwvYaroslav Bolyukin6 days agoparent: #023a07f.patch.diff
in: trunk

2 files changed

modifiedcrates/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 {
modifiedcrates/remowt-ui-prompt/src/rofi.rsdiffbeforeafterboth
before · crates/remowt-ui-prompt/src/rofi.rs
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			"-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}
after · crates/remowt-ui-prompt/src/rofi.rs
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}