i3status_rs/blocks/
service_status.rs1use super::prelude::*;
45use zbus::proxy::PropertyStream;
46
47#[derive(Deserialize, Debug, Default)]
48#[serde(deny_unknown_fields, default)]
49pub struct Config {
50 pub driver: DriverType,
51 pub service: String,
52 pub active_format: FormatConfig,
53 pub inactive_format: FormatConfig,
54 pub active_state: Option<State>,
55 pub inactive_state: Option<State>,
56}
57
58#[derive(Deserialize, Debug, SmartDefault)]
59#[serde(rename_all = "snake_case")]
60pub enum DriverType {
61 #[default]
62 Systemd,
63}
64
65pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
66 let active_format = config.active_format.with_default(" $service active ")?;
67 let inactive_format = config.inactive_format.with_default(" $service inactive ")?;
68
69 let active_state = config.active_state.unwrap_or(State::Idle);
70 let inactive_state = config.inactive_state.unwrap_or(State::Critical);
71
72 let mut driver: Box<dyn Driver> = match config.driver {
73 DriverType::Systemd => Box::new(SystemdDriver::new(config.service.clone()).await?),
74 };
75
76 loop {
77 let service_active_state = driver.is_active().await?;
78
79 let mut widget = Widget::new();
80
81 if service_active_state {
82 widget.state = active_state;
83 widget.set_format(active_format.clone());
84 } else {
85 widget.state = inactive_state;
86 widget.set_format(inactive_format.clone());
87 };
88
89 widget.set_values(map! {
90 "service" =>Value::text(config.service.clone()),
91 });
92
93 api.set_widget(widget)?;
94
95 driver.wait_for_change().await?;
96 }
97}
98
99#[async_trait]
100trait Driver {
101 async fn is_active(&self) -> Result<bool>;
102 async fn wait_for_change(&mut self) -> Result<()>;
103}
104
105struct SystemdDriver {
106 proxy: UnitProxy<'static>,
107 active_state_changed: PropertyStream<'static, String>,
108}
109
110impl SystemdDriver {
111 async fn new(service: String) -> Result<Self> {
112 let dbus_conn = new_system_dbus_connection().await?;
113
114 if !service.is_ascii() {
115 return Err(Error::new(format!(
116 "service name \"{service}\" must only contain ASCII characters"
117 )));
118 }
119 let encoded_service = format!("{service}.service")
120 .bytes()
122 .map(|b| {
123 if b.is_ascii_alphanumeric() {
124 char::from(b).to_string()
126 } else {
127 format!("_{b:02x}")
129 }
130 })
131 .collect::<String>();
132
133 let path = format!("/org/freedesktop/systemd1/unit/{encoded_service}");
134
135 let proxy = UnitProxy::builder(&dbus_conn)
136 .path(path)
137 .error("Could not set path")?
138 .build()
139 .await
140 .error("Failed to create UnitProxy")?;
141
142 Ok(Self {
143 active_state_changed: proxy.receive_active_state_changed().await,
144 proxy,
145 })
146 }
147}
148
149#[async_trait]
150impl Driver for SystemdDriver {
151 async fn is_active(&self) -> Result<bool> {
152 self.proxy
153 .active_state()
154 .await
155 .error("Could not get active_state")
156 .map(|state| state == "active")
157 }
158
159 async fn wait_for_change(&mut self) -> Result<()> {
160 self.active_state_changed.next().await;
161 Ok(())
162 }
163}
164
165#[zbus::proxy(
166 interface = "org.freedesktop.systemd1.Unit",
167 default_service = "org.freedesktop.systemd1"
168)]
169trait Unit {
170 #[zbus(property)]
171 fn active_state(&self) -> zbus::Result<String>;
172}