difftreelog
feat(ui-prompt) declarative bifrost endpoint
in: trunk
5 files changed
crates/ui-prompt/Cargo.tomldiffbeforeafterboth--- a/crates/ui-prompt/Cargo.toml
+++ b/crates/ui-prompt/Cargo.toml
@@ -5,11 +5,13 @@
[dependencies]
bifrostlink.workspace = true
-serde = "1.0.204"
+bifrostlink-macros.workspace = true
+serde.workspace = true
+serde_json.workspace = true
thiserror = "1.0.63"
-tokio = { version = "1.39.2", features = ["io-util", "macros", "process", "rt"] }
-tracing = "0.1.40"
-zbus = { version = "4.4.0", optional = true }
+tokio = { workspace = true, features = ["io-util", "macros", "process", "rt"] }
+tracing.workspace = true
+zbus = { workspace = true, optional = true }
[features]
default = ["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.rsdiffbeforeafterboth1use zbus::interface;2use zbus::{fdo, proxy};34use crate::Source;5use crate::{BlockingPrompter, Result};6use crate::{Error, Prompter};78pub struct DbusPrompterInterface<P>(pub P);9#[interface(name = "lach.PolkitInputHandler")]10impl<P: Prompter + Send + Sync + 'static> DbusPrompterInterface<P> {11 async fn prompt_radio(12 &self,13 prompt: &str,14 description: &str,15 source: Vec<Source>,16 ) -> fdo::Result<bool> {17 Ok(self.0.prompt_radio(prompt, description, &source).await?)18 }19 async fn prompt_text(20 &self,21 echo: bool,22 prompt: &str,23 description: &str,24 source: Vec<Source>,25 ) -> fdo::Result<String> {26 Ok(self27 .028 .prompt_text(echo, prompt, description, &source)29 .await?)30 }31 async fn display_text(32 &self,33 error: bool,34 description: &str,35 source: Vec<Source>,36 ) -> fdo::Result<()> {37 Ok(self.0.display_text(error, description, &source).await?)38 }39}4041#[proxy(interface = "lach.PolkitInputHandler")]42trait DbusPrompter {43 async fn prompt_enum(44 &self,45 prompt: &str,46 description: &str,47 variants: &[&str],48 source: &[Source],49 ) -> fdo::Result<u32>;50 async fn prompt_text(51 &self,52 echo: bool,53 prompt: &str,54 description: &str,55 source: &[Source],56 ) -> fdo::Result<String>;57 async fn display_text(58 &self,59 error: bool,60 description: &str,61 source: &[Source],62 ) -> fdo::Result<()>;63}6465impl Prompter for DbusPrompterProxy<'_> {66 async fn prompt_enum(67 &self,68 prompt: &str,69 description: &str,70 variants: &[&str],71 source: &[Source],72 ) -> Result<u32> {73 Ok(self74 .prompt_enum(prompt, description, variants, source)75 .await?)76 }7778 async fn prompt_text(79 &self,80 echo: bool,81 prompt: &str,82 description: &str,83 source: &[Source],84 ) -> Result<String> {85 Ok(self.prompt_text(echo, prompt, description, source).await?)86 }8788 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {89 Ok(self.display_text(error, description, source).await?)90 }91}92impl BlockingPrompter for DbusPrompterProxyBlocking<'_> {93 fn prompt_enum(94 &self,95 prompt: &str,96 description: &str,97 variants: &[&str],98 source: &[Source],99 ) -> Result<u32> {100 Ok(self.prompt_enum(prompt, description, variants, source)?)101 }102103 fn prompt_text(104 &self,105 echo: bool,106 prompt: &str,107 description: &str,108 source: &[Source],109 ) -> Result<String> {110 Ok(self.prompt_text(echo, prompt, description, source)?)111 }112113 fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {114 Ok(self.display_text(error, description, source)?)115 }116}117118impl From<fdo::Error> for Error {119 fn from(value: fdo::Error) -> Self {120 if matches!(value, fdo::Error::NoReply(_)) {121 return Self::Cancel;122 }123 Self::InputError(format!("{value}"))124 }125}126impl From<Error> for fdo::Error {127 fn from(value: Error) -> Self {128 match value {129 Error::Cancel => fdo::Error::NoReply("input was cancelled".to_owned()),130 Error::InputError(e) => fdo::Error::Failed(e),131 }132 }133}1use zbus::interface;2use zbus::{fdo, proxy};34use crate::Source;5use crate::{BlockingPrompter, Result};6use crate::{Error, Prompter};78pub struct DbusPrompterInterface<P>(pub P);910#[interface(name = "lach.PolkitInputHandler")]11impl<P: Prompter + Send + Sync + 'static> DbusPrompterInterface<P> {12 async fn prompt_radio(13 &self,14 prompt: &str,15 description: &str,16 source: Vec<Source>,17 ) -> fdo::Result<bool> {18 Ok(self.0.prompt_radio(prompt, description, &source).await?)19 }20 async fn prompt_text(21 &self,22 echo: bool,23 prompt: &str,24 description: &str,25 source: Vec<Source>,26 ) -> fdo::Result<String> {27 Ok(self28 .029 .prompt_text(echo, prompt, description, &source)30 .await?)31 }32 async fn display_text(33 &self,34 error: bool,35 description: &str,36 source: Vec<Source>,37 ) -> fdo::Result<()> {38 Ok(self.0.display_text(error, description, &source).await?)39 }40}4142#[proxy(interface = "lach.PolkitInputHandler")]43pub trait DbusPrompter {44 async fn prompt_enum(45 &self,46 prompt: &str,47 description: &str,48 variants: &[&str],49 source: &[Source],50 ) -> fdo::Result<u32>;51 async fn prompt_text(52 &self,53 echo: bool,54 prompt: &str,55 description: &str,56 source: &[Source],57 ) -> fdo::Result<String>;58 async fn display_text(59 &self,60 error: bool,61 description: &str,62 source: &[Source],63 ) -> fdo::Result<()>;64}6566impl Prompter for DbusPrompterProxy<'_> {67 async fn prompt_enum(68 &self,69 prompt: &str,70 description: &str,71 variants: &[&str],72 source: &[Source],73 ) -> Result<u32> {74 Ok(self75 .prompt_enum(prompt, description, variants, source)76 .await?)77 }7879 async fn prompt_text(80 &self,81 echo: bool,82 prompt: &str,83 description: &str,84 source: &[Source],85 ) -> Result<String> {86 Ok(self.prompt_text(echo, prompt, description, source).await?)87 }8889 async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {90 Ok(self.display_text(error, description, source).await?)91 }92}93impl BlockingPrompter for DbusPrompterProxyBlocking<'_> {94 fn prompt_enum(95 &self,96 prompt: &str,97 description: &str,98 variants: &[&str],99 source: &[Source],100 ) -> Result<u32> {101 Ok(self.prompt_enum(prompt, description, variants, source)?)102 }103104 fn prompt_text(105 &self,106 echo: bool,107 prompt: &str,108 description: &str,109 source: &[Source],110 ) -> Result<String> {111 Ok(self.prompt_text(echo, prompt, description, source)?)112 }113114 fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {115 Ok(self.display_text(error, description, source)?)116 }117}118119impl From<fdo::Error> for Error {120 fn from(value: fdo::Error) -> Self {121 if matches!(value, fdo::Error::NoReply(_)) {122 return Self::Cancel;123 }124 Self::InputError(format!("{value}"))125 }126}127impl From<Error> for fdo::Error {128 fn from(value: Error) -> Self {129 match value {130 Error::Cancel => fdo::Error::NoReply("input was cancelled".to_owned()),131 Error::Remote(e) => fdo::Error::NoReply(format!("remote error occured: {e}")),132 Error::InputError(e) => fdo::Error::Failed(e),133 }134 }135}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");
+ }
}