i3status_rs/blocks/service_status.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
//! Display the status of a service
//!
//! Right now only `systemd` is supported.
//!
//! # Configuration
//!
//! Key | Values | Default
//! ----|--------|--------
//! `driver` | Which init system is running the service. Available drivers are: `"systemd"` | `"systemd"`
//! `service` | The name of the service | **Required**
//! `active_format` | A string to customise the output of this block. See below for available placeholders. | `" $service active "`
//! `inactive_format` | A string to customise the output of this block. See below for available placeholders. | `" $service inactive "`
//! `active_state` | A valid [`State`] | [`State::Idle`]
//! `inactive_state` | A valid [`State`] | [`State::Critical`]
//!
//! Placeholder | Value | Type | Unit
//! ---------------|---------------------------|--------|-----
//! `service` | The name of the service | Text | -
//!
//! # Example
//!
//! Example using an icon:
//!
//! ```toml
//! [[block]]
//! block = "service_status"
//! service = "cups"
//! active_format = " ^icon_tea "
//! inactive_format = " no ^icon_tea "
//! ```
//!
//! Example overriding the default `inactive_state`:
//!
//! ```toml
//! [[block]]
//! block = "service_status"
//! service = "shadow"
//! active_format = ""
//! inactive_format = " Integrity of password and group files failed "
//! inactive_state = "Warning"
//! ```
//!
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 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.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>,
active_state_changed: PropertyStream<'static, String>,
}
impl SystemdDriver {
async fn new(service: String) -> Result<Self> {
let dbus_conn = 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")
// For each byte...
.bytes()
.map(|b| {
if b.is_ascii_alphanumeric() {
// Just use the character as a string
char::from(b).to_string()
} else {
// Otherwise use the hex representation of the byte preceded by an underscore
format!("_{b:02x}")
}
})
.collect::<String>();
let path = format!("/org/freedesktop/systemd1/unit/{encoded_service}");
let proxy = UnitProxy::builder(&dbus_conn)
.path(path)
.error("Could not set path")?
.build()
.await
.error("Failed to create UnitProxy")?;
Ok(Self {
active_state_changed: proxy.receive_active_state_changed().await,
proxy,
})
}
}
#[async_trait]
impl Driver for SystemdDriver {
async fn is_active(&self) -> Result<bool> {
self.proxy
.active_state()
.await
.error("Could not get active_state")
.map(|state| state == "active")
}
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>;
}