difftreelog
feat(ui-prompt) declarative bifrost endpoint
in: trunk
5 files changed
crates/ui-prompt/Cargo.tomldiffbeforeafterboth556[dependencies]6[dependencies]7bifrostlink.workspace = true7bifrostlink.workspace = true8bifrostlink-macros.workspace = true8serde = "1.0.204"9serde.workspace = true10serde_json.workspace = true9thiserror = "1.0.63"11thiserror = "1.0.63"10tokio = { version = "1.39.2", features = ["io-util", "macros", "process", "rt"] }12tokio = { workspace = true, features = ["io-util", "macros", "process", "rt"] }11tracing = "0.1.40"13tracing.workspace = true12zbus = { version = "4.4.0", optional = true }14zbus = { workspace = true, optional = true }131514[features]16[features]15default = ["dbus"]17default = ["dbus"]crates/ui-prompt/src/bifrost.rsdiffbeforeafterboth--- a/crates/ui-prompt/src/bifrost.rs
+++ b/crates/ui-prompt/src/bifrost.rs
@@ -1,72 +1,73 @@
-use bifrostlink::error::ErrorT;
-use bifrostlink::{request, AddressT, Rpc};
+use bifrostlink::{Config, Rpc};
+use bifrostlink_macros::endpoints;
use serde::{Deserialize, Serialize};
use crate::{Error, Prompter, Source};
-pub struct BifrostPrompter<A: AddressT, E: ErrorT> {
- pub address: A,
- pub rpc: Rpc<A, E>,
-}
+pub struct PromptEndpoints<P>(pub P);
-#[derive(Serialize, Deserialize)]
-struct EnumRequest {
- prompt: String,
- description: String,
- variants: Vec<String>,
- source: Vec<Source>,
-}
-#[derive(Serialize, Deserialize)]
-struct EnumResponse {
- value: u32,
-}
-request!(EnumRequest => EnumResponse);
+#[endpoints(ns = 2)]
+impl<P> PromptEndpoints<P>
+where
+ P: Prompter + Send + Sync + 'static,
+{
+ #[endpoints(id = 1, cancel)]
+ async fn prompt_enum(
+ &self,
+ prompt: String,
+ description: String,
+ variants: Vec<String>,
+ source: Vec<Source>,
+ ) -> Result<u32, Error> {
+ let variants: Vec<&str> = variants.iter().map(|v| v.as_str()).collect();
+ self.0
+ .prompt_enum(&prompt, &description, &variants, &source)
+ .await
+ }
-#[derive(Serialize, Deserialize)]
-struct TextRequest {
- echo: bool,
- prompt: String,
- description: String,
- source: Vec<Source>,
-}
-#[derive(Serialize, Deserialize)]
-struct TextResponse {
- value: String,
-}
-request!(TextRequest => TextResponse);
+ #[endpoints(id = 2, cancel)]
+ async fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: String,
+ description: String,
+ source: Vec<Source>,
+ ) -> Result<String, Error> {
+ self.0
+ .prompt_text(echo, &prompt, &description, &source)
+ .await
+ }
-#[derive(Serialize, Deserialize)]
-struct DisplayRequest {
- error: bool,
- description: String,
- source: Vec<Source>,
+ #[endpoints(id = 3, cancel)]
+ async fn display_text(
+ &self,
+ error: bool,
+ description: String,
+ source: Vec<Source>,
+ ) -> Result<(), Error> {
+ self.0.display_text(error, &description, &source).await
+ }
}
-request!(DisplayRequest => ());
-impl<A: AddressT, E: ErrorT> Prompter for BifrostPrompter<A, E>
+impl<C: Config> Prompter for PromptEndpointsClient<C>
where
- crate::Error: From<E>,
+ Error: ToString,
{
async fn prompt_enum(
&self,
prompt: &str,
description: &str,
variants: &[&str],
- source: &[crate::Source],
+ source: &[Source],
) -> crate::Result<u32> {
- let res = self
- .rpc
- .request(
- self.address.clone(),
- &EnumRequest {
- prompt: prompt.to_owned(),
- description: description.to_owned(),
- variants: variants.into_iter().map(|v| (*v).to_owned()).collect(),
- source: source.to_vec(),
- },
- )
- .await?;
- Ok(res.value)
+ self.prompt_enum(
+ prompt.to_owned(),
+ description.to_owned(),
+ variants.iter().map(|v| (*v).to_owned()).collect(),
+ source.to_vec(),
+ )
+ .await
+ .map_err(|e| Error::Remote(e.to_string()))?
}
async fn prompt_text(
@@ -74,93 +75,35 @@
echo: bool,
prompt: &str,
description: &str,
- source: &[crate::Source],
+ source: &[Source],
) -> crate::Result<String> {
- let res = self
- .rpc
- .request(
- self.address.clone(),
- &TextRequest {
- echo,
- prompt: prompt.to_owned(),
- description: description.to_owned(),
- source: source.to_vec(),
- },
- )
- .await?;
- Ok(res.value)
+ self.prompt_text(
+ echo,
+ prompt.to_owned(),
+ description.to_owned(),
+ source.to_vec(),
+ )
+ .await
+ .map_err(|e| Error::Remote(e.to_string()))?
}
async fn display_text(
&self,
error: bool,
description: &str,
- source: &[crate::Source],
+ source: &[Source],
) -> crate::Result<()> {
- self.rpc
- .request(
- self.address.clone(),
- &DisplayRequest {
- error,
- description: description.to_owned(),
- source: source.to_vec(),
- },
- )
- .await?;
- Ok(())
+ self.display_text(error, description.to_owned(), source.to_vec())
+ .await
+ .map_err(|e| Error::Remote(e.to_string()))?
}
}
-pub fn handle_bifrost_prompts<
- P: Prompter + Clone + 'static,
- A: AddressT,
- E: ErrorT + From<Error>,
->(
- rpc: &Rpc<A, E>,
- prompt: P,
-) {
- rpc.register_request_handler(true, {
- let prompt = prompt.clone();
- move |_addr, req: EnumRequest| {
- let prompt = prompt.clone();
- async move {
- let i = prompt
- .prompt_enum(
- &req.prompt,
- &req.description,
- &req.variants.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
- &req.source,
- )
- .await?;
-
- Ok(EnumResponse { value: i })
- }
- }
- });
- rpc.register_request_handler(true, {
- let prompt = prompt.clone();
- move |_addr, req: TextRequest| {
- let prompt = prompt.clone();
- async move {
- let i = prompt
- .prompt_text(req.echo, &req.prompt, &req.description, &req.source)
- .await?;
-
- Ok(TextResponse { value: i })
- }
- }
- });
- rpc.register_request_handler(true, {
- let prompt = prompt.clone();
- move |_addr, req: DisplayRequest| {
- let prompt = prompt.clone();
- async move {
- prompt
- .display_text(req.error, &req.description, &req.source)
- .await?;
-
- Ok(())
- }
- }
- });
+pub fn serve_prompts<P, C>(rpc: &mut Rpc<C>, prompt: P)
+where
+ P: Prompter + Send + Sync + 'static,
+ C: Config,
+ C::Error: From<Error>,
+{
+ PromptEndpoints(prompt).register_endpoints(rpc);
}
crates/ui-prompt/src/dbus.rsdiffbeforeafterboth--- a/crates/ui-prompt/src/dbus.rs
+++ b/crates/ui-prompt/src/dbus.rs
@@ -6,128 +6,130 @@
use crate::{Error, Prompter};
pub struct DbusPrompterInterface<P>(pub P);
+
#[interface(name = "lach.PolkitInputHandler")]
impl<P: Prompter + Send + Sync + 'static> DbusPrompterInterface<P> {
- async fn prompt_radio(
- &self,
- prompt: &str,
- description: &str,
- source: Vec<Source>,
- ) -> fdo::Result<bool> {
- Ok(self.0.prompt_radio(prompt, description, &source).await?)
- }
- async fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: Vec<Source>,
- ) -> fdo::Result<String> {
- Ok(self
- .0
- .prompt_text(echo, prompt, description, &source)
- .await?)
- }
- async fn display_text(
- &self,
- error: bool,
- description: &str,
- source: Vec<Source>,
- ) -> fdo::Result<()> {
- Ok(self.0.display_text(error, description, &source).await?)
- }
+ async fn prompt_radio(
+ &self,
+ prompt: &str,
+ description: &str,
+ source: Vec<Source>,
+ ) -> fdo::Result<bool> {
+ Ok(self.0.prompt_radio(prompt, description, &source).await?)
+ }
+ async fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: Vec<Source>,
+ ) -> fdo::Result<String> {
+ Ok(self
+ .0
+ .prompt_text(echo, prompt, description, &source)
+ .await?)
+ }
+ async fn display_text(
+ &self,
+ error: bool,
+ description: &str,
+ source: Vec<Source>,
+ ) -> fdo::Result<()> {
+ Ok(self.0.display_text(error, description, &source).await?)
+ }
}
#[proxy(interface = "lach.PolkitInputHandler")]
-trait DbusPrompter {
- async fn prompt_enum(
- &self,
- prompt: &str,
- description: &str,
- variants: &[&str],
- source: &[Source],
- ) -> fdo::Result<u32>;
- async fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> fdo::Result<String>;
- async fn display_text(
- &self,
- error: bool,
- description: &str,
- source: &[Source],
- ) -> fdo::Result<()>;
+pub trait DbusPrompter {
+ async fn prompt_enum(
+ &self,
+ prompt: &str,
+ description: &str,
+ variants: &[&str],
+ source: &[Source],
+ ) -> fdo::Result<u32>;
+ async fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> fdo::Result<String>;
+ async fn display_text(
+ &self,
+ error: bool,
+ description: &str,
+ source: &[Source],
+ ) -> fdo::Result<()>;
}
impl Prompter for DbusPrompterProxy<'_> {
- async fn prompt_enum(
- &self,
- prompt: &str,
- description: &str,
- variants: &[&str],
- source: &[Source],
- ) -> Result<u32> {
- Ok(self
- .prompt_enum(prompt, description, variants, source)
- .await?)
- }
+ async fn prompt_enum(
+ &self,
+ prompt: &str,
+ description: &str,
+ variants: &[&str],
+ source: &[Source],
+ ) -> Result<u32> {
+ Ok(self
+ .prompt_enum(prompt, description, variants, source)
+ .await?)
+ }
- async fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> Result<String> {
- Ok(self.prompt_text(echo, prompt, description, source).await?)
- }
+ async fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> Result<String> {
+ Ok(self.prompt_text(echo, prompt, description, source).await?)
+ }
- async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
- Ok(self.display_text(error, description, source).await?)
- }
+ async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
+ Ok(self.display_text(error, description, source).await?)
+ }
}
impl BlockingPrompter for DbusPrompterProxyBlocking<'_> {
- fn prompt_enum(
- &self,
- prompt: &str,
- description: &str,
- variants: &[&str],
- source: &[Source],
- ) -> Result<u32> {
- Ok(self.prompt_enum(prompt, description, variants, source)?)
- }
+ fn prompt_enum(
+ &self,
+ prompt: &str,
+ description: &str,
+ variants: &[&str],
+ source: &[Source],
+ ) -> Result<u32> {
+ Ok(self.prompt_enum(prompt, description, variants, source)?)
+ }
- fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> Result<String> {
- Ok(self.prompt_text(echo, prompt, description, source)?)
- }
+ fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> Result<String> {
+ Ok(self.prompt_text(echo, prompt, description, source)?)
+ }
- fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
- Ok(self.display_text(error, description, source)?)
- }
+ fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
+ Ok(self.display_text(error, description, source)?)
+ }
}
impl From<fdo::Error> for Error {
- fn from(value: fdo::Error) -> Self {
- if matches!(value, fdo::Error::NoReply(_)) {
- return Self::Cancel;
- }
- Self::InputError(format!("{value}"))
- }
+ fn from(value: fdo::Error) -> Self {
+ if matches!(value, fdo::Error::NoReply(_)) {
+ return Self::Cancel;
+ }
+ Self::InputError(format!("{value}"))
+ }
}
impl From<Error> for fdo::Error {
- fn from(value: Error) -> Self {
- match value {
- Error::Cancel => fdo::Error::NoReply("input was cancelled".to_owned()),
- Error::InputError(e) => fdo::Error::Failed(e),
- }
- }
+ fn from(value: Error) -> Self {
+ match value {
+ Error::Cancel => fdo::Error::NoReply("input was cancelled".to_owned()),
+ Error::Remote(e) => fdo::Error::NoReply(format!("remote error occured: {e}")),
+ Error::InputError(e) => fdo::Error::Failed(e),
+ }
+ }
}
crates/ui-prompt/src/lib.rsdiffbeforeafterboth--- a/crates/ui-prompt/src/lib.rs
+++ b/crates/ui-prompt/src/lib.rs
@@ -3,16 +3,18 @@
use std::future::Future;
use std::result;
+pub mod bifrost;
pub mod dbus;
pub mod rofi;
-pub mod bifrost;
-#[derive(thiserror::Error, Debug)]
+#[derive(thiserror::Error, Debug, serde::Serialize, serde::Deserialize)]
pub enum Error {
- #[error("user has cancelled input")]
- Cancel,
- #[error("input error: {0}")]
- InputError(String),
+ #[error("user has cancelled input")]
+ Cancel,
+ #[error("input error: {0}")]
+ InputError(String),
+ #[error("unknown remote error: {0}")]
+ Remote(String),
}
pub type Result<T, E = Error> = result::Result<T, E>;
@@ -21,179 +23,179 @@
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Source(pub Cow<'static, str>);
impl fmt::Display for Source {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "<u>{}</u>", self.0)
- }
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "<u>{}</u>", self.0)
+ }
}
pub trait Prompter: Send + Sync {
- fn prompt_radio(
- &self,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> impl Future<Output = Result<bool>> + Send {
- let fut = self.prompt_enum(prompt, description, &["No", "Yes"], source);
- async { fut.await.map(|v| v == 1) }
- }
- fn prompt_enum(
- &self,
- prompt: &str,
- description: &str,
- variants: &[&str],
- source: &[Source],
- ) -> impl Future<Output = Result<u32>> + Send;
- fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> impl Future<Output = Result<String>> + Send;
- fn display_text(
- &self,
- error: bool,
- description: &str,
- source: &[Source],
- ) -> impl Future<Output = Result<()>> + Send;
+ fn prompt_radio(
+ &self,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> impl Future<Output = Result<bool>> + Send {
+ let fut = self.prompt_enum(prompt, description, &["No", "Yes"], source);
+ async { fut.await.map(|v| v == 1) }
+ }
+ fn prompt_enum(
+ &self,
+ prompt: &str,
+ description: &str,
+ variants: &[&str],
+ source: &[Source],
+ ) -> impl Future<Output = Result<u32>> + Send;
+ fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> impl Future<Output = Result<String>> + Send;
+ fn display_text(
+ &self,
+ error: bool,
+ description: &str,
+ source: &[Source],
+ ) -> impl Future<Output = Result<()>> + Send;
}
pub trait BlockingPrompter {
- fn prompt_radio(&self, prompt: &str, description: &str, source: &[Source]) -> Result<bool> {
- self.prompt_enum(prompt, description, &["No", "Yes"], source)
- .map(|v| v == 1)
- }
- fn prompt_enum(
- &self,
- prompt: &str,
- description: &str,
- variants: &[&str],
- source: &[Source],
- ) -> Result<u32>;
- fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> Result<String>;
- fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()>;
+ fn prompt_radio(&self, prompt: &str, description: &str, source: &[Source]) -> Result<bool> {
+ self.prompt_enum(prompt, description, &["No", "Yes"], source)
+ .map(|v| v == 1)
+ }
+ fn prompt_enum(
+ &self,
+ prompt: &str,
+ description: &str,
+ variants: &[&str],
+ source: &[Source],
+ ) -> Result<u32>;
+ fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> Result<String>;
+ fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()>;
}
impl<P> Prompter for &P
where
- P: Prompter,
+ P: Prompter,
{
- fn prompt_radio(
- &self,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> impl Future<Output = Result<bool>> + Send {
- (*self).prompt_radio(prompt, description, source)
- }
+ fn prompt_radio(
+ &self,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> impl Future<Output = Result<bool>> + Send {
+ (*self).prompt_radio(prompt, description, source)
+ }
- fn prompt_enum(
- &self,
- prompt: &str,
- description: &str,
- variants: &[&str],
- source: &[Source],
- ) -> impl Future<Output = Result<u32>> + Send {
- (*self).prompt_enum(prompt, description, variants, source)
- }
+ fn prompt_enum(
+ &self,
+ prompt: &str,
+ description: &str,
+ variants: &[&str],
+ source: &[Source],
+ ) -> impl Future<Output = Result<u32>> + Send {
+ (*self).prompt_enum(prompt, description, variants, source)
+ }
- fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> impl Future<Output = Result<String>> + Send {
- (*self).prompt_text(echo, prompt, description, source)
- }
+ fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> impl Future<Output = Result<String>> + Send {
+ (*self).prompt_text(echo, prompt, description, source)
+ }
- fn display_text(
- &self,
- error: bool,
- description: &str,
- source: &[Source],
- ) -> impl Future<Output = Result<()>> + Send {
- (*self).display_text(error, description, source)
- }
+ fn display_text(
+ &self,
+ error: bool,
+ description: &str,
+ source: &[Source],
+ ) -> impl Future<Output = Result<()>> + Send {
+ (*self).display_text(error, description, source)
+ }
}
pub struct PrependSourcePrompter<P> {
- pub prompter: P,
- pub source: Vec<Source>,
- pub description: String,
+ pub prompter: P,
+ pub source: Vec<Source>,
+ pub description: String,
}
impl<P> PrependSourcePrompter<P> {
- fn source(&self, input: &[Source]) -> Vec<Source> {
- let mut out = self.source.clone();
- out.extend(input.iter().cloned());
- out
- }
- fn description(&self, input: &str) -> String {
- if self.description.is_empty() {
- input.to_owned()
- } else if input.is_empty() {
- self.description.to_owned()
- } else {
- format!("{input}\n\n{}", self.description)
- }
- }
+ fn source(&self, input: &[Source]) -> Vec<Source> {
+ let mut out = self.source.clone();
+ out.extend(input.iter().cloned());
+ out
+ }
+ fn description(&self, input: &str) -> String {
+ if self.description.is_empty() {
+ input.to_owned()
+ } else if input.is_empty() {
+ self.description.to_owned()
+ } else {
+ format!("{input}\n\n{}", self.description)
+ }
+ }
}
impl<P> Prompter for PrependSourcePrompter<P>
where
- P: Prompter + Sync,
+ P: Prompter + Sync,
{
- async fn prompt_radio(
- &self,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> Result<bool> {
- self.prompter
- .prompt_radio(prompt, &self.description(description), &self.source(source))
- .await
- }
+ async fn prompt_radio(
+ &self,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> Result<bool> {
+ self.prompter
+ .prompt_radio(prompt, &self.description(description), &self.source(source))
+ .await
+ }
- async fn prompt_enum(
- &self,
- prompt: &str,
- description: &str,
- variants: &[&str],
- source: &[Source],
- ) -> Result<u32> {
- self.prompter
- .prompt_enum(
- prompt,
- dbg!(&self.description(description)),
- variants,
- &self.source(source),
- )
- .await
- }
+ async fn prompt_enum(
+ &self,
+ prompt: &str,
+ description: &str,
+ variants: &[&str],
+ source: &[Source],
+ ) -> Result<u32> {
+ self.prompter
+ .prompt_enum(
+ prompt,
+ &self.description(description),
+ variants,
+ &self.source(source),
+ )
+ .await
+ }
- async fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> Result<String> {
- self.prompter
- .prompt_text(
- echo,
- prompt,
- &self.description(description),
- &self.source(source),
- )
- .await
- }
+ async fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> Result<String> {
+ self.prompter
+ .prompt_text(
+ echo,
+ prompt,
+ &self.description(description),
+ &self.source(source),
+ )
+ .await
+ }
- async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
- self.prompter
- .display_text(error, &self.description(description), &self.source(source))
- .await
- }
+ async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
+ self.prompter
+ .display_text(error, &self.description(description), &self.source(source))
+ .await
+ }
}
crates/ui-prompt/src/rofi.rsdiffbeforeafterboth--- a/crates/ui-prompt/src/rofi.rs
+++ b/crates/ui-prompt/src/rofi.rs
@@ -6,195 +6,203 @@
use crate::{Error, Prompter, Result, Source};
+#[derive(Clone)]
pub struct RofiPrompter;
fn fixup_prompt(prompt: &str) -> &str {
- // Rofi always appends such suffix
- prompt.strip_suffix(": ").unwrap_or(prompt)
+ // Rofi always appends such suffix
+ prompt.strip_suffix(": ").unwrap_or(prompt)
}
+fn rofi_command() -> Command {
+ Command::new(option_env!("ROFI").unwrap_or("rofi"))
+}
+
impl Prompter for RofiPrompter {
- async fn prompt_enum(
- &self,
- prompt: &str,
- description: &str,
- variants: &[&str],
- source: &[Source],
- ) -> Result<u32> {
- trace!("rofi radio");
- let mut cmd = Command::new("rofi");
- let mesg = if source.is_empty() {
- description.to_owned()
- } else {
- let mut out = format!("{description}\n\n<b>Requested on ",);
- for (i, s) in source.iter().enumerate() {
- if i != 0 {
- out.push_str(" -> ");
- }
- out.push_str(&s.to_string());
- }
- out.push_str("</b>");
- out
- };
- cmd.args([
- "-dmenu",
- "-mesg",
- &mesg,
- "-sync",
- "-only-match",
- "-p",
- fixup_prompt(prompt),
- "-format",
- "i",
- "-markup-rows",
- ]);
- cmd.stdin(Stdio::piped());
- cmd.stdout(Stdio::piped());
- cmd.kill_on_drop(true);
- let mut child = cmd
- .spawn()
- .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
+ async fn prompt_enum(
+ &self,
+ prompt: &str,
+ description: &str,
+ variants: &[&str],
+ source: &[Source],
+ ) -> Result<u32> {
+ trace!("rofi radio");
+ let mut cmd = rofi_command();
+ let mesg = if source.is_empty() {
+ description.to_owned()
+ } else {
+ let mut out = format!("{description}\n\n<b>Requested on ",);
+ for (i, s) in source.iter().enumerate() {
+ if i != 0 {
+ out.push_str(" -> ");
+ }
+ out.push_str(&s.to_string());
+ }
+ out.push_str("</b>");
+ out
+ };
+ cmd.args([
+ "-dmenu",
+ "-mesg",
+ &mesg,
+ "-sync",
+ "-only-match",
+ "-p",
+ fixup_prompt(prompt),
+ "-format",
+ "i",
+ "-markup-rows",
+ ]);
+ cmd.stdin(Stdio::piped());
+ cmd.stdout(Stdio::piped());
+ cmd.kill_on_drop(true);
+ let mut child = cmd
+ .spawn()
+ .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
- let mut stdin = child.stdin.take().expect("stdin is piped");
- for var in variants {
- stdin
- .write_all(var.replace('\n', " ").as_bytes())
- .await
- .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;
- stdin
- .write_all(b"\n")
- .await
- .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;
- }
- // write_all already flushes, just to be sure.
- let _ = stdin.flush().await;
- drop(stdin);
+ let mut stdin = child.stdin.take().expect("stdin is piped");
+ for var in variants {
+ stdin
+ .write_all(var.replace('\n', " ").as_bytes())
+ .await
+ .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;
+ stdin
+ .write_all(b"\n")
+ .await
+ .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;
+ }
+ // write_all already flushes, just to be sure.
+ let _ = stdin.flush().await;
+ drop(stdin);
- let out = child
- .wait_with_output()
- .await
- .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
- let stdout = out
- .stdout
- .strip_suffix(b"\n")
- .unwrap_or(&out.stdout)
- .to_owned();
+ let out = child
+ .wait_with_output()
+ .await
+ .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
+ let stdout = out
+ .stdout
+ .strip_suffix(b"\n")
+ .unwrap_or(&out.stdout)
+ .to_owned();
- let id: u32 = String::from_utf8(stdout)
- .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?
- .parse()
- .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?;
- if id as usize >= variants.len() {
- return Err(Error::InputError("invalid rofi response".to_owned()));
- }
+ let id: u32 = String::from_utf8(stdout)
+ .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?
+ .parse()
+ .map_err(|e| Error::InputError(format!("rofi produced invalid output: {e}")))?;
+ if id as usize >= variants.len() {
+ return Err(Error::InputError("invalid rofi response".to_owned()));
+ }
- Ok(id)
- }
+ Ok(id)
+ }
- async fn prompt_text(
- &self,
- echo: bool,
- prompt: &str,
- description: &str,
- source: &[Source],
- ) -> Result<String> {
- trace!("rofi text");
- let mut cmd = Command::new("rofi");
- let mesg = if source.is_empty() {
- description.to_owned()
- } else {
- let mut out = format!("{description}\n\n<b>Requested on ",);
- for (i, s) in source.iter().enumerate() {
- if i != 0 {
- out.push_str(" -> ");
- }
- out.push_str(&s.to_string());
- }
- out.push_str("</b>");
- out
- };
- cmd.args(["-dmenu", "-mesg", &mesg, "-p", fixup_prompt(prompt)]);
- if !echo {
- cmd.arg("-password");
- }
- cmd.stdin(Stdio::null());
- cmd.stdout(Stdio::piped());
- cmd.kill_on_drop(true);
- let child = cmd
- .spawn()
- .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
+ async fn prompt_text(
+ &self,
+ echo: bool,
+ prompt: &str,
+ description: &str,
+ source: &[Source],
+ ) -> Result<String> {
+ trace!("rofi text");
+ let mut cmd = rofi_command();
+ let mesg = if source.is_empty() {
+ description.to_owned()
+ } else {
+ let mut out = format!("{description}\n\n<b>Requested on ",);
+ for (i, s) in source.iter().enumerate() {
+ if i != 0 {
+ out.push_str(" -> ");
+ }
+ out.push_str(&s.to_string());
+ }
+ out.push_str("</b>");
+ out
+ };
+ cmd.args(["-dmenu", "-mesg", &mesg, "-p", fixup_prompt(prompt)]);
+ if !echo {
+ cmd.arg("-password");
+ }
+ cmd.stdin(Stdio::null());
+ cmd.stdout(Stdio::piped());
+ cmd.kill_on_drop(true);
+ let child = cmd
+ .spawn()
+ .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
- let out = child
- .wait_with_output()
- .await
- .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
- let stdout = out
- .stdout
- .strip_suffix(b"\n")
- .unwrap_or(&out.stdout)
- .to_owned();
+ let out = child
+ .wait_with_output()
+ .await
+ .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
+ let stdout = out
+ .stdout
+ .strip_suffix(b"\n")
+ .unwrap_or(&out.stdout)
+ .to_owned();
- Ok(String::from_utf8_lossy(&stdout).to_string())
- }
+ Ok(String::from_utf8_lossy(&stdout).to_string())
+ }
- async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
- trace!("rofi display");
- let mut cmd = Command::new("rofi");
- let mut mesg = if source.is_empty() {
- description.to_owned()
- } else {
- let mut out = format!("{description}\n\n<b>Coming from ",);
- for s in source.iter() {
- out.push_str(&s.to_string());
- }
- out.push_str("</b>");
- out
- };
- if error {
- mesg.insert_str(0, "<span color=\"red\">");
- mesg.push_str("</span>");
- }
- cmd.args(["-e", &mesg, "-markup"]);
- cmd.stdin(Stdio::null());
- cmd.stdout(Stdio::null());
- cmd.kill_on_drop(true);
- let mut child = cmd
- .spawn()
- .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
+ async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
+ trace!("rofi display");
+ let mut cmd = rofi_command();
+ let mut mesg = if source.is_empty() {
+ description.to_owned()
+ } else {
+ let mut out = format!("{description}\n\n<b>Coming from ",);
+ for s in source.iter() {
+ out.push_str(&s.to_string());
+ }
+ out.push_str("</b>");
+ out
+ };
+ if error {
+ mesg.insert_str(0, "<span color=\"red\">");
+ mesg.push_str("</span>");
+ }
+ cmd.args(["-e", &mesg, "-markup"]);
+ cmd.stdin(Stdio::null());
+ cmd.stdout(Stdio::null());
+ cmd.kill_on_drop(true);
+ let mut child = cmd
+ .spawn()
+ .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
- child
- .wait()
- .await
- .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
+ child
+ .wait()
+ .await
+ .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
- Ok(())
- }
+ Ok(())
+ }
}
#[cfg(test)]
mod tests {
- use std::borrow::Cow;
+ use std::borrow::Cow;
- use crate::rofi::RofiPrompter;
- use crate::{PrependSourcePrompter, Prompter as _, Source};
+ use crate::rofi::RofiPrompter;
+ use crate::{PrependSourcePrompter, Prompter as _, Source};
- #[tokio::test]
- async fn test() {
- let prompter = PrependSourcePrompter {
- prompter: RofiPrompter,
- source: vec![Source(Cow::Borrowed("ssh"))],
- };
- prompter
- .prompt_radio("Enable", "Polkit needs access", &[])
- .await
- .expect("rofi");
- prompter
- .prompt_text(false, "Password", "Polkit needs access", &[])
- .await
- .expect("rofi");
- prompter
- .display_text(true, "Polkit needs access", &[])
- .await
- .expect("rofi");
- }
+ // #[tokio::test]
+ #[tokio::test]
+ #[ignore = "interactive"]
+ async fn test() {
+ let prompter = PrependSourcePrompter {
+ prompter: RofiPrompter,
+ description: "test".to_owned(),
+ source: vec![Source(Cow::Borrowed("ssh"))],
+ };
+ prompter
+ .prompt_radio("Enable", "Polkit needs access", &[])
+ .await
+ .expect("rofi");
+ prompter
+ .prompt_text(false, "Password", "Polkit needs access", &[])
+ .await
+ .expect("rofi");
+ prompter
+ .display_text(true, "Polkit needs access", &[])
+ .await
+ .expect("rofi");
+ }
}