i3status_rs/blocks/
notify.rs1use super::prelude::*;
65use tokio::{join, try_join};
66use zbus::proxy::PropertyStream;
67
68const ICON_ON: &str = "bell";
69const ICON_OFF: &str = "bell-slash";
70
71#[derive(Deserialize, Debug, Default)]
72#[serde(deny_unknown_fields, default)]
73pub struct Config {
74 pub driver: DriverType,
75 pub format: FormatConfig,
76}
77
78#[derive(Deserialize, Debug, SmartDefault)]
79#[serde(rename_all = "lowercase")]
80pub enum DriverType {
81 #[default]
82 Dunst,
83 SwayNC,
84}
85
86pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
87 let mut actions = api.get_actions()?;
88 api.set_default_actions(&[(MouseButton::Left, None, "toggle_paused")])?;
89
90 let format = config.format.with_default(" $icon ")?;
91
92 let mut driver: Box<dyn Driver> = match config.driver {
93 DriverType::Dunst => Box::new(DunstDriver::new().await?),
94 DriverType::SwayNC => Box::new(SwayNCDriver::new().await?),
95 };
96
97 loop {
98 let (is_paused, notification_count, history_count) = try_join!(
99 driver.is_paused(),
100 driver.notification_count(),
101 driver.history_count()
102 )?;
103
104 let mut widget = Widget::new().with_format(format.clone());
105 widget.set_values(map!(
106 "icon" => Value::icon(if is_paused { ICON_OFF } else { ICON_ON }),
107 [if notification_count != 0] "notification_count" => Value::number(notification_count),
108 [if history_count != 0] "history_count" => Value::number(history_count),
109 [if is_paused] "paused" => Value::flag(),
110 ));
111 widget.state = if notification_count == 0 {
112 State::Idle
113 } else {
114 State::Info
115 };
116 api.set_widget(widget)?;
117
118 select! {
119 x = driver.wait_for_change() => x?,
120 Some(action) = actions.recv() => match action.as_ref() {
121 "toggle_paused" => {
122 driver.set_paused(!is_paused).await?;
123 }
124 "show" => {
125 driver.notification_show().await?;
126 }
127 "show_all" => {
128 driver.notification_show_all().await?;
129 }
130 _ => (),
131 }
132 }
133 }
134}
135
136#[async_trait]
137trait Driver {
138 async fn is_paused(&self) -> Result<bool>;
139 async fn set_paused(&self, paused: bool) -> Result<()>;
140 async fn notification_show(&self) -> Result<()>;
141 async fn history_count(&self) -> Result<u32>;
142 async fn notification_show_all(&self) -> Result<()>;
143 async fn notification_count(&self) -> Result<u32>;
144 async fn wait_for_change(&mut self) -> Result<()>;
145}
146
147struct DunstDriver {
148 proxy: DunstDbusProxy<'static>,
149 paused_changes: PropertyStream<'static, bool>,
150 displayed_length_changes: PropertyStream<'static, u32>,
151 waiting_length_changes: PropertyStream<'static, u32>,
152}
153
154impl DunstDriver {
155 async fn new() -> Result<Self> {
156 let dbus_conn = new_dbus_connection().await?;
157 let proxy = DunstDbusProxy::new(&dbus_conn)
158 .await
159 .error("Failed to create DunstDbusProxy")?;
160 Ok(Self {
161 paused_changes: proxy.receive_paused_changed().await,
162 displayed_length_changes: proxy.receive_displayed_length_changed().await,
163 waiting_length_changes: proxy.receive_waiting_length_changed().await,
164 proxy,
165 })
166 }
167}
168
169#[async_trait]
170impl Driver for DunstDriver {
171 async fn is_paused(&self) -> Result<bool> {
172 self.proxy.paused().await.error("Failed to get 'paused'")
173 }
174
175 async fn set_paused(&self, paused: bool) -> Result<()> {
176 self.proxy
177 .set_paused(paused)
178 .await
179 .error("Failed to set 'paused'")
180 }
181
182 async fn notification_show(&self) -> Result<()> {
183 self.proxy
184 .notification_show()
185 .await
186 .error("Could not call 'NotificationShow'")
187 }
188
189 async fn notification_show_all(&self) -> Result<()> {
190 for _ in 0..self.history_count().await? {
191 self.notification_show().await?;
192 }
193 Ok(())
194 }
195
196 async fn history_count(&self) -> Result<u32> {
197 let history_length = self
198 .proxy
199 .history_length()
200 .await
201 .error("Failed to get property")?;
202
203 Ok(history_length)
204 }
205
206 async fn notification_count(&self) -> Result<u32> {
207 let (displayed_length, waiting_length) =
208 try_join!(self.proxy.displayed_length(), self.proxy.waiting_length())
209 .error("Failed to get property")?;
210
211 Ok(displayed_length + waiting_length)
212 }
213
214 async fn wait_for_change(&mut self) -> Result<()> {
215 select! {
216 _ = self.paused_changes.next() => {}
217 _ = self.displayed_length_changes.next() => {}
218 _ = self.waiting_length_changes.next() => {}
219 }
220 Ok(())
221 }
222}
223
224#[zbus::proxy(
225 interface = "org.dunstproject.cmd0",
226 default_service = "org.freedesktop.Notifications",
227 default_path = "/org/freedesktop/Notifications"
228)]
229
230trait DunstDbus {
231 #[zbus(property, name = "paused")]
232 fn paused(&self) -> zbus::Result<bool>;
233 #[zbus(property, name = "paused")]
234 fn set_paused(&self, value: bool) -> zbus::Result<()>;
235 fn notification_show(&self) -> zbus::Result<()>;
236 #[zbus(property, name = "historyLength")]
237 fn history_length(&self) -> zbus::Result<u32>;
238 #[zbus(property, name = "displayedLength")]
239 fn displayed_length(&self) -> zbus::Result<u32>;
240 #[zbus(property, name = "waitingLength")]
241 fn waiting_length(&self) -> zbus::Result<u32>;
242}
243struct SwayNCDriver {
244 proxy: SwayNCDbusProxy<'static>,
245 changes: SubscribeStream,
246 changes_v2: SubscribeV2Stream,
247}
248
249impl SwayNCDriver {
250 async fn new() -> Result<Self> {
251 let dbus_conn = new_dbus_connection().await?;
252 let proxy = SwayNCDbusProxy::new(&dbus_conn)
253 .await
254 .error("Failed to create SwayNCDbusProxy")?;
255 Ok(Self {
256 changes: proxy
257 .receive_subscribe()
258 .await
259 .error("Failed to create SubscribeStream")?,
260 changes_v2: proxy
261 .receive_subscribe_v2()
262 .await
263 .error("Failed to create SubscribeV2Stream")?,
264 proxy,
265 })
266 }
267}
268
269#[async_trait]
270impl Driver for SwayNCDriver {
271 async fn is_paused(&self) -> Result<bool> {
272 let (is_dnd, is_inhibited) = join!(self.proxy.get_dnd(), self.proxy.is_inhibited());
273
274 is_dnd
275 .error("Failed to call 'GetDnd'")
276 .map(|is_dnd| is_dnd || is_inhibited.unwrap_or_default())
277 }
278
279 async fn set_paused(&self, paused: bool) -> Result<()> {
280 if paused {
281 self.proxy.set_dnd(paused).await
282 } else {
283 join!(self.proxy.set_dnd(paused), self.proxy.clear_inhibitors()).0
284 }
285 .error("Failed to call 'SetDnd'")
286 }
287
288 async fn notification_show(&self) -> Result<()> {
289 self.proxy
290 .toggle_visibility()
291 .await
292 .error("Failed to call 'ToggleVisibility'")
293 }
294
295 async fn notification_show_all(&self) -> Result<()> {
296 self.notification_show().await
297 }
298
299 async fn history_count(&self) -> Result<u32> {
300 self.notification_count().await
301 }
302
303 async fn notification_count(&self) -> Result<u32> {
304 self.proxy
305 .notification_count()
306 .await
307 .error("Failed to call 'NotificationCount'")
308 }
309
310 async fn wait_for_change(&mut self) -> Result<()> {
311 select! {
312 _ = self.changes.next() => (),
313 _ = self.changes_v2.next() => (),
314 }
315 Ok(())
316 }
317}
318
319#[zbus::proxy(
320 interface = "org.erikreider.swaync.cc",
321 default_service = "org.freedesktop.Notifications",
322 default_path = "/org/erikreider/swaync/cc"
323)]
324trait SwayNCDbus {
325 fn get_dnd(&self) -> zbus::Result<bool>;
326 fn set_dnd(&self, value: bool) -> zbus::Result<()>;
327 fn toggle_visibility(&self) -> zbus::Result<()>;
328 fn notification_count(&self) -> zbus::Result<u32>;
329 #[zbus(signal)]
330 fn subscribe(&self, count: u32, dnd: bool, cc_open: bool) -> zbus::Result<()>;
331
332 fn is_inhibited(&self) -> zbus::Result<bool>;
334 fn clear_inhibitors(&self) -> zbus::Result<bool>;
335 #[zbus(signal)]
337 fn subscribe_v2(
338 &self,
339 count: u32,
340 dnd: bool,
341 cc_open: bool,
342 inhibited: bool,
343 ) -> zbus::Result<()>;
344}