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