Skip to main content

spell_framework/vault/
notification_manager.rs

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
13/// It is an internal function used in the expansion of [`cast_spell`](crate::cast_spell) macro
14/// if the macro has a notification instance to run.
15pub fn set_notification(win: &SpellWin, ui: Box<dyn NotificationManager>) {
16    let (sender, rx) = channel::channel::<NotifyEvent>();
17    // let (sender_async, rx_async) = channel::channel::<NotifyEvent>();
18    // NOTIFICATION_EVENT.get_or_init(|| sender_async);
19    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            //TODO handle and report the error here
28            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        // body-markup will be implemented in the future maybe. icon-multi is not
95        // added since slint doen't yet support animated images.
96        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}