use super::prelude::*;
use zbus::proxy::PropertyStream;
#[derive(Deserialize, Debug, Default)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
pub driver: DriverType,
pub service: String,
pub user: bool,
pub active_format: FormatConfig,
pub inactive_format: FormatConfig,
pub active_state: Option<State>,
pub inactive_state: Option<State>,
}
#[derive(Deserialize, Debug, SmartDefault)]
#[serde(rename_all = "snake_case")]
pub enum DriverType {
#[default]
Systemd,
}
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let active_format = config.active_format.with_default(" $service active ")?;
let inactive_format = config.inactive_format.with_default(" $service inactive ")?;
let active_state = config.active_state.unwrap_or(State::Idle);
let inactive_state = config.inactive_state.unwrap_or(State::Critical);
let mut driver: Box<dyn Driver> = match config.driver {
DriverType::Systemd => {
Box::new(SystemdDriver::new(config.user, config.service.clone()).await?)
}
};
loop {
let service_active_state = driver.is_active().await?;
let mut widget = Widget::new();
if service_active_state {
widget.state = active_state;
widget.set_format(active_format.clone());
} else {
widget.state = inactive_state;
widget.set_format(inactive_format.clone());
};
widget.set_values(map! {
"service" =>Value::text(config.service.clone()),
});
api.set_widget(widget)?;
driver.wait_for_change().await?;
}
}
#[async_trait]
trait Driver {
async fn is_active(&self) -> Result<bool>;
async fn wait_for_change(&mut self) -> Result<()>;
}
struct SystemdDriver {
proxy: UnitProxy<'static>,
service_proxy: ServiceProxy<'static>,
active_state_changed: PropertyStream<'static, String>,
}
impl SystemdDriver {
async fn new(user: bool, service: String) -> Result<Self> {
let dbus_conn = if user {
new_dbus_connection().await?
} else {
new_system_dbus_connection().await?
};
if !service.is_ascii() {
return Err(Error::new(format!(
"service name \"{service}\" must only contain ASCII characters"
)));
}
let encoded_service = format!("{service}.service")
.bytes()
.map(|b| {
if b.is_ascii_alphanumeric() {
char::from(b).to_string()
} else {
format!("_{b:02x}")
}
})
.collect::<String>();
let path = format!("/org/freedesktop/systemd1/unit/{encoded_service}");
let proxy = UnitProxy::builder(&dbus_conn)
.path(path.clone())
.error("Could not set path")?
.build()
.await
.error("Failed to create UnitProxy")?;
let service_proxy = ServiceProxy::builder(&dbus_conn)
.path(path)
.error("Could not set path")?
.build()
.await
.error("Failed to create ServiceProxy")?;
Ok(Self {
active_state_changed: proxy.receive_active_state_changed().await,
proxy,
service_proxy,
})
}
}
#[async_trait]
impl Driver for SystemdDriver {
async fn is_active(&self) -> Result<bool> {
let active_state = self
.proxy
.active_state()
.await
.error("Could not get active_state")?;
Ok(match &*active_state {
"active" => true,
"activating" => {
let service_type = self
.service_proxy
.type_()
.await
.error("Could not get service type")?;
service_type == "oneshot"
}
_ => false,
})
}
async fn wait_for_change(&mut self) -> Result<()> {
self.active_state_changed.next().await;
Ok(())
}
}
#[zbus::proxy(
interface = "org.freedesktop.systemd1.Unit",
default_service = "org.freedesktop.systemd1"
)]
trait Unit {
#[zbus(property)]
fn active_state(&self) -> zbus::Result<String>;
}
#[zbus::proxy(
interface = "org.freedesktop.systemd1.Service",
default_service = "org.freedesktop.systemd1"
)]
trait Service {
#[zbus(property, name = "Type")]
fn type_(&self) -> zbus::Result<String>;
}