1use crate::{
2 vault::{
3 BlockingNotification, Hint, NOTIFICATION_EVENT, Notification, NotificationManager, Timeout,
4 Urgency,
5 },
6 wayland_adapter::SpellWin,
7};
8use smithay_client_toolkit::reexports::calloop::channel::{self, Sender};
9use std::{cmp::Ordering, collections::HashMap};
10use tracing::{info, warn};
11use zbus::{fdo::Error as BusError, interface, object_server::SignalEmitter, zvariant::Value};
12
13pub fn set_notification(win: &SpellWin, ui: Box<dyn NotificationManager>) {
16 let (sender, rx) = channel::channel::<NotifyEvent>();
17 let layer_name = win.layer_name.clone();
20 let sender_cl = sender.clone();
21 std::thread::spawn(move || {
22 let rt = tokio::runtime::Builder::new_current_thread()
23 .enable_all()
24 .build()
25 .unwrap();
26 rt.block_on(async move {
27 let _ = notification_service_enter(sender_cl, layer_name).await;
29 });
30 });
31
32 let _ = NOTIFICATION_EVENT.set(BlockingNotification);
33 let _ = win
34 .loop_handle
35 .clone()
36 .insert_source(rx, move |event, _, _| match event {
37 channel::Event::Msg(msg) => match msg {
38 NotifyEvent::Noti(notification) => {
39 if let Err(err) = ui.new_notification(notification) {
40 warn!("{:?}", err);
41 }
42 }
43 NotifyEvent::NotificationClosed(id) => {
44 if let Err(err) = ui.close_notification(id) {
45 warn!(" Error closing notification with id {} : {:?}", id, err);
46 }
47 }
48 },
49 channel::Event::Closed => info!("Notification Channel to async thread is closed!"),
50 });
51}
52
53pub(crate) enum NotifyEvent {
54 Noti(Notification),
55 NotificationClosed(u32),
56}
57
58async fn notification_service_enter(
59 sender: Sender<NotifyEvent>,
60 layer_name: String,
61) -> zbus::fdo::Result<()> {
62 let conn = zbus::Connection::session().await?;
63 conn.object_server()
64 .at(
65 "/org/freedesktop/Notifications",
66 NotificationHandler {
67 sender: sender.clone(),
68 layer_name,
69 next_id: 1,
70 notifications: Vec::new(),
71 },
72 )
73 .await?;
74 info!("Object server is setup");
75 if let Err(err) = conn.request_name("org.freedesktop.Notifications").await {
76 warn!("Error When creating notification crate {:?}", err);
77 }
78 info!("Notification service is live with the provided name");
79 std::future::pending::<()>().await;
80 Ok(())
81}
82
83pub(crate) struct NotificationHandler {
84 pub(crate) sender: Sender<NotifyEvent>,
85 pub(crate) layer_name: String,
86 pub(crate) next_id: u32,
87 pub(crate) notifications: Vec<Notification>,
88}
89
90#[interface(name = "org.freedesktop.Notifications", proxy(gen_blocking = false,))]
91impl NotificationHandler {
92 async fn get_capabilities(&self) -> Result<Vec<String>, BusError> {
93 info!("capabilities called");
94 Ok(vec![
97 "actions",
98 "body",
99 "body-images",
100 "icon-static",
101 "persistence",
102 ]
103 .iter()
104 .map(|x| x.to_string())
105 .collect())
106 }
107
108 async fn notify(
109 &mut self,
110 app_name: String,
111 replaces_id: u32,
112 app_icon: String,
113 summary: String,
114 body: String,
115 actions: Vec<String>,
116 hints: HashMap<String, zbus::zvariant::Value<'_>>,
117 expire_timeout: i32,
118 ) -> Result<u32, BusError> {
119 info!("Notifcation event received");
120 let notification = Notification {
121 id: replaces_id,
122 appname: app_name,
123 summary,
124 subtitle: None,
125 body,
126 icon: app_icon,
127 hints: hints
128 .into_iter()
129 .map(|(key, value)| {
130 let val: Hint = match key.as_str() {
131 "action-icons" => {
132 if let Value::Bool(x) = value {
133 Hint::ActionIcons(x)
134 } else {
135 Hint::Invalid
136 }
137 }
138 "category" => {
139 if let Value::Str(x) = value {
140 Hint::Category(x.to_string())
141 } else {
142 Hint::Invalid
143 }
144 }
145 "desktop-entry" => {
146 if let Value::Str(x) = value {
147 Hint::DesktopEntry(x.to_string())
148 } else {
149 Hint::Invalid
150 }
151 }
152 "image-data" => Hint::Invalid,
153 "image_data" => Hint::Invalid,
154 "image-path" => {
155 if let Value::Str(x) = value {
156 Hint::ImagePath(x.to_string())
157 } else {
158 Hint::Invalid
159 }
160 }
161 "image_path" => {
162 if let Value::Str(x) = value {
163 Hint::ImagePath(x.to_string())
164 } else {
165 Hint::Invalid
166 }
167 }
168 "icon_data" => Hint::Invalid,
169 "resident" => {
170 if let Value::Bool(x) = value {
171 Hint::Resident(x)
172 } else {
173 Hint::Invalid
174 }
175 }
176 "sound-file" => {
177 if let Value::Str(x) = value {
178 Hint::SoundFile(x.to_string())
179 } else {
180 Hint::Invalid
181 }
182 }
183 "sound-name" => {
184 if let Value::Str(x) = value {
185 Hint::SoundName(x.to_string())
186 } else {
187 Hint::Invalid
188 }
189 }
190 "suppress-sound" => {
191 if let Value::Bool(x) = value {
192 Hint::SuppressSound(x)
193 } else {
194 Hint::Invalid
195 }
196 }
197 "transient" => {
198 if let Value::Bool(x) = value {
199 Hint::Transient(x)
200 } else {
201 Hint::Invalid
202 }
203 }
204 "x" => {
205 if let Value::I32(x) = value {
206 Hint::X(x)
207 } else {
208 Hint::Invalid
209 }
210 }
211 "y" => {
212 if let Value::I32(x) = value {
213 Hint::Y(x)
214 } else {
215 Hint::Invalid
216 }
217 }
218 "urgency" => {
219 if let Value::U8(x) = value {
220 Hint::Urgency(match x {
221 0 => Urgency::Low,
222 1 => Urgency::Normal,
223 2 => Urgency::Critical,
224 _ => Urgency::Normal,
225 })
226 } else {
227 Hint::Invalid
228 }
229 }
230 err => {
231 warn!("Invalid hint passed with key: {}", err);
232 Hint::Invalid
233 }
234 };
235 val
236 })
237 .collect(),
238 actions,
239 timeout: match expire_timeout.cmp(&0) {
240 Ordering::Equal => Timeout::Never,
241 Ordering::Greater => Timeout::Milliseconds(expire_timeout),
242 Ordering::Less => Timeout::Default,
243 },
244 };
245 let _ = self
246 .sender
247 .clone()
248 .send(NotifyEvent::Noti(notification.clone()));
249 self.notifications.push(notification);
250 if replaces_id == 0 {
251 let id = self.next_id;
252 self.next_id = self.next_id.wrapping_add(1);
253 if self.next_id == 0 {
254 self.next_id = 1;
255 }
256 Ok(id)
257 } else {
258 Ok(replaces_id)
259 }
260 }
261
262 async fn close_notification(
263 &self,
264 #[zbus(signal_emitter)] emitter: SignalEmitter<'_>,
265 id: u32,
266 ) -> Result<(), BusError> {
267 emitter.notification_closed(id, 4).await?;
268 if let Err(err) = self
269 .sender
270 .clone()
271 .send(NotifyEvent::NotificationClosed(id))
272 {
273 warn!("Error calling CloseNotification: {err}")
274 }
275 Ok(())
276 }
277
278 async fn get_server_information(&self) -> Result<(String, String, String, String), BusError> {
279 Ok((
280 "SpellNC-".to_string() + self.layer_name.as_str(),
281 "VimYoung".to_string(),
282 "0.0.1".to_string(),
283 "1.3".to_string(),
284 ))
285 }
286
287 #[zbus(signal)]
288 async fn notification_closed(
289 emitter: &SignalEmitter<'_>,
290 id: u32,
291 reason: u32,
292 ) -> zbus::Result<()>;
293
294 #[zbus(signal)]
295 async fn action_invoked(emitter: &SignalEmitter<'_>) -> zbus::Result<()>;
296}