use std::{
collections::HashMap,
pin::Pin,
process::Command,
rc::Rc,
task::{self, Poll},
time::Duration,
};
use anyhow::Result;
use async_trait::async_trait;
use derive_builder::Builder;
use tokio::time::{interval, Interval};
use tokio_stream::{Stream, StreamExt};
use crate::{
bar::{Event, EventResponse, PanelDrawInfo},
common::{draw_common, PanelCommon},
ipc::ChannelEndpoint,
remove_string_from_config, remove_uint_from_config, Attrs, PanelConfig,
PanelStream,
};
struct CustomStream {
interval: Option<Interval>,
fired: bool,
}
impl CustomStream {
const fn new(interval: Option<Interval>) -> Self {
Self {
interval,
fired: false,
}
}
}
impl Stream for CustomStream {
type Item = ();
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
) -> Poll<Option<Self::Item>> {
match &mut self.interval {
Some(ref mut interval) => interval.poll_tick(cx).map(|_| Some(())),
None => {
if self.fired {
Poll::Pending
} else {
self.fired = true;
Poll::Ready(Some(()))
}
}
}
}
}
#[derive(Builder, Debug)]
#[builder_struct_attr(allow(missing_docs))]
#[builder_impl_attr(allow(missing_docs))]
#[builder(pattern = "owned")]
pub struct Custom {
name: &'static str,
#[builder(default = r#"Command::new("echo")"#)]
command: Command,
#[builder(setter(strip_option))]
duration: Option<Duration>,
common: PanelCommon,
}
impl Custom {
fn draw(
&mut self,
cr: &Rc<cairo::Context>,
height: i32,
) -> Result<PanelDrawInfo> {
let output = self.command.output()?;
let text = self.common.formats[0]
.replace(
"%stdout%",
String::from_utf8_lossy(output.stdout.as_slice()).as_ref(),
)
.replace(
"%stderr%",
String::from_utf8_lossy(output.stderr.as_slice()).as_ref(),
);
draw_common(
cr,
text.trim(),
&self.common.attrs[0],
self.common.dependence,
self.common.images.clone(),
height,
)
}
}
#[async_trait(?Send)]
impl PanelConfig for Custom {
fn parse(
name: &'static str,
table: &mut HashMap<String, config::Value>,
_global: &config::Config,
) -> Result<Self> {
let builder = match (
remove_string_from_config("command", table),
remove_uint_from_config("interval", table),
) {
(Some(command), Some(duration)) => {
let mut cmd = Command::new("sh");
cmd.arg("-c").arg(command.as_str());
CustomBuilder::default()
.command(cmd)
.duration(Duration::from_secs(duration))
}
(Some(command), None) => {
let mut cmd = Command::new("sh");
cmd.arg("-c").arg(command.as_str());
CustomBuilder::default().command(cmd)
}
(None, Some(duration)) => {
CustomBuilder::default().duration(Duration::from_secs(duration))
}
(None, None) => CustomBuilder::default(),
};
let builder = builder.name(name);
Ok(builder
.common(PanelCommon::parse(
table,
&[""],
&["%stdout%"],
&[""],
&[],
)?)
.build()?)
}
fn props(&self) -> (&'static str, bool) {
(self.name, self.common.visible)
}
async fn run(
mut self: Box<Self>,
cr: Rc<cairo::Context>,
global_attrs: Attrs,
height: i32,
) -> Result<(PanelStream, Option<ChannelEndpoint<Event, EventResponse>>)>
{
for attr in &mut self.common.attrs {
attr.apply_to(&global_attrs);
}
Ok((
Box::pin(
CustomStream::new(self.duration.map(|d| interval(d)))
.map(move |_| self.draw(&cr, height)),
),
None,
))
}
}