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