windows_toast/
toastify.rs

1use crate::{h, ActivationType, Crop, Dismissed, Duration, HashMap, Result, Scenario, TAG};
2use windows::{
3    core::{IInspectable, Interface, HRESULT, HSTRING},
4    Data::Xml::Dom::{IXmlNode, XmlDocument},
5    Foundation::TypedEventHandler,
6    UI::Notifications::{
7        NotificationData, ToastActivatedEventArgs, ToastDismissalReason, ToastDismissedEventArgs,
8        ToastFailedEventArgs, ToastNotification, ToastNotificationManager, ToastNotifier,
9    },
10};
11
12pub type OnActivated = Box<dyn FnMut(HashMap<String, String>) + Send + 'static>;
13pub type OnFailed = Box<dyn FnMut(HRESULT) + Send + 'static>;
14pub type OnDismissed = Box<dyn FnMut(Dismissed) + Send + 'static>;
15
16pub struct Toastify {
17    document: XmlDocument,
18    bind: IXmlNode,
19    bind_toast: IXmlNode,
20    notify_data: NotificationData,
21}
22
23impl Toastify {
24    pub fn create() -> Result<Self> {
25        let document = XmlDocument::new()?;
26        let notify_data = NotificationData::new()?;
27
28        notify_data.SetSequenceNumber(0)?;
29
30        let xml_default = include_str!("../assets/xml_default.txt");
31        document.LoadXml(&HSTRING::from(xml_default))?;
32
33        let bind = document.SelectSingleNode(h!("//binding"))?;
34
35        let toast = document.SelectSingleNode(h!("/toast"))?;
36        let bind_toast = document.SelectSingleNode(h!("/toast"))?;
37
38        let actions = document.CreateElement(h!("actions"))?;
39
40        toast.AppendChild(&actions)?;
41        Ok(Self {
42            document,
43            bind,
44            bind_toast,
45            notify_data,
46        })
47    }
48
49    pub fn add_text(&mut self, text: &str) -> Result<()> {
50        let text_elm = self.document.CreateElement(h!("text"))?;
51
52        text_elm.SetInnerText(h!(text))?;
53
54        self.bind.AppendChild(&text_elm)?;
55        Ok(())
56    }
57
58    pub fn add_icon(&mut self, icon: &str, crop: Crop) -> Result<()> {
59        let image_elm = self.document.CreateElement(h!("image"))?;
60
61        for (key, value) in [
62            ("placement", "appLogoOverride"),
63            ("hint-crop", crop.as_str()),
64            ("src", icon),
65        ] {
66            image_elm.SetAttribute(h!(key), h!(value))?;
67        }
68
69        self.bind.AppendChild(&image_elm)?;
70        Ok(())
71    }
72
73    pub fn add_image(&mut self, image: &str) -> Result<()> {
74        let image_elm = self.document.CreateElement(h!("image"))?;
75
76        image_elm.SetAttribute(h!("src"), h!(image))?;
77
78        self.bind.AppendChild(&image_elm)?;
79        Ok(())
80    }
81
82    pub fn add_hero(&mut self, hero: &str) -> Result<()> {
83        let hero_elm = self.document.CreateElement(h!("image"))?;
84
85        for (key, value) in [("placement", "hero"), ("src", hero)] {
86            hero_elm.SetAttribute(h!(key), h!(value))?;
87        }
88
89        self.bind.AppendChild(&hero_elm)?;
90        Ok(())
91    }
92
93    pub fn add_launch(&mut self, on_click: &str) -> Result<()> {
94        let launch_elm = self.document.CreateAttribute(h!("launch"))?;
95
96        let launch = IInspectable::try_from(on_click)?;
97        launch_elm.SetNodeValue(&launch)?;
98
99        self.bind_toast.Attributes()?.SetNamedItem(&launch_elm)?;
100        Ok(())
101    }
102
103    pub fn add_scenario(&mut self, scenario: Scenario) -> Result<()> {
104        let scenario_elm = self.document.CreateAttribute(h!("scenario"))?;
105
106        let launch = IInspectable::try_from(scenario.as_str())?;
107        scenario_elm.SetNodeValue(&launch)?;
108
109        self.bind_toast.Attributes()?.SetNamedItem(&scenario_elm)?;
110        Ok(())
111    }
112
113    pub fn add_audio(&mut self, audio: &str, silent: bool) -> Result<()> {
114        let audio_elm = self.document.CreateElement(h!("audio"))?;
115
116        audio_elm.SetAttribute(h!("src"), h!(audio))?;
117        audio_elm.SetAttribute(h!("silent"), h!(silent.to_string()))?;
118
119        self.bind_toast.AppendChild(&audio_elm)?;
120        Ok(())
121    }
122
123    pub fn add_button(
124        &mut self,
125        title: &str,
126        arg: &str,
127        activation_type: ActivationType,
128        image_uri: &str,
129    ) -> Result<()> {
130        let actions = self.document.SelectSingleNode(h!("//actions"))?;
131        let button_elm = self.document.CreateElement(h!("action"))?;
132
133        for (key, value) in [
134            ("activationType", activation_type.as_str()),
135            ("arguments", arg),
136            ("content", title),
137            ("imageUri", image_uri),
138        ] {
139            button_elm.SetAttribute(h!(key), h!(value))?;
140        }
141
142        actions.AppendChild(&button_elm)?;
143        Ok(())
144    }
145
146    pub fn add_context_button(&mut self, title: &str, arg: &str) -> Result<()> {
147        let actions = self.document.SelectSingleNode(h!("//actions"))?;
148        let context_button_elm = self.document.CreateElement(h!("action"))?;
149
150        for (key, value) in [
151            ("arguments", arg),
152            ("content", title),
153            ("placement", "contextMenu"),
154        ] {
155            context_button_elm.SetAttribute(h!(key), h!(value))?;
156        }
157
158        actions.AppendChild(&context_button_elm)?;
159        Ok(())
160    }
161
162    pub fn add_input(&mut self, input: &str, id: &str) -> Result<()> {
163        let actions = self.document.SelectSingleNode(h!("//actions"))?;
164        let input_elm = self.document.CreateElement(h!("input"))?;
165
166        for (key, value) in [("type", "text"), ("placeHolderContent", input), ("id", id)] {
167            input_elm.SetAttribute(crate::h!(key), h!(value))?;
168        }
169
170        actions.AppendChild(&input_elm)?;
171        Ok(())
172    }
173
174    pub fn add_textbox(
175        &mut self,
176        input: &str,
177        id: &str,
178        title_box: &str,
179        arg_box: &str,
180        image_uri_box: &str,
181    ) -> Result<()> {
182        self.add_input(input, id)?;
183
184        let actions = self.document.SelectSingleNode(h!("//actions"))?;
185        let button_elm = self.document.CreateElement(h!("action"))?;
186
187        for (key, value) in [
188            ("hint-inputId", id),
189            ("arguments", arg_box),
190            ("content", title_box),
191            ("imageUri", image_uri_box),
192        ] {
193            button_elm.SetAttribute(h!(key), h!(value))?;
194        }
195
196        actions.AppendChild(&button_elm)?;
197        Ok(())
198    }
199
200    pub fn add_selection(&mut self, items: Vec<&str>, id: &str) -> Result<()> {
201        let actions = self.document.SelectSingleNode(h!("//actions"))?;
202        let input_elm = self.document.CreateElement(h!("input"))?;
203
204        for (key, value) in [
205            ("id", id),
206            ("type", "selection"),
207            ("defaultInput", items[0]),
208        ] {
209            input_elm.SetAttribute(h!(key), h!(value))?;
210        }
211
212        for item in items {
213            let selection_elm = self.document.CreateElement(h!("selection"))?;
214            for (key, value) in [("id", item), ("content", item)] {
215                selection_elm.SetAttribute(h!(key), h!(value))?;
216
217                input_elm.AppendChild(&selection_elm)?;
218            }
219        }
220
221        actions.AppendChild(&input_elm)?;
222        Ok(())
223    }
224
225    pub fn add_progress(
226        &mut self,
227        title: &str,
228        status: &str,
229        value: &str,
230        value_string: &str,
231    ) -> Result<()> {
232        let progress_elm = self.document.CreateElement(h!("progress"))?;
233
234        for (key, value) in [
235            ("title", "{title}"),
236            ("status", "{status}"),
237            ("value", "{value}"),
238            ("valueStringOverride", "{value_string}"),
239        ] {
240            progress_elm.SetAttribute(h!(key), h!(value))?;
241        }
242
243        let values = self.notify_data.Values()?;
244
245        for (key, value) in [
246            ("title", title),
247            ("status", status),
248            ("value", value),
249            ("value_string", value_string),
250        ] {
251            values.Insert(h!(key), h!(value))?;
252        }
253
254        self.bind.AppendChild(&progress_elm)?;
255        Ok(())
256    }
257
258    pub fn add_data(&mut self, args: HashMap<String, String>) -> Result<()> {
259        let values = self.notify_data.Values()?;
260
261        for (key, value) in args.iter() {
262            values.Insert(h!(key), h!(value))?;
263        }
264
265        Ok(())
266    }
267
268    pub fn add_duration(&mut self, duration: Duration) -> Result<()> {
269        let duration_elm = self.document.CreateAttribute(h!("duration"))?;
270
271        let duration = IInspectable::try_from(duration.as_str())?;
272        duration_elm.SetNodeValue(&duration)?;
273
274        self.bind_toast.Attributes()?.SetNamedItem(&duration_elm)?;
275        Ok(())
276    }
277}
278
279impl Toastify {
280    pub fn show(
281        &self,
282        id: &str,
283        tag: Option<&str>,
284        group: Option<&str>,
285        remote_id: Option<&str>,
286        on_activated: Option<OnActivated>,
287        on_dismissed: Option<OnDismissed>,
288        on_failed: Option<OnFailed>,
289    ) -> Result<(ToastNotification, ToastNotifier)> {
290        let manager = ToastNotificationManager::CreateToastNotifierWithId(h!(id))?;
291        let toast = ToastNotification::CreateToastNotification(&self.document)?;
292
293        if let Some(tag) = tag {
294            toast.SetTag(h!(tag))?;
295        } else {
296            toast.SetTag(h!(TAG))?;
297        }
298        if let Some(group) = group {
299            toast.SetGroup(h!(group))?;
300        }
301        if let Some(remote_id) = remote_id {
302            toast.SetRemoteId(h!(remote_id))?;
303        }
304
305        toast.SetData(&self.notify_data)?;
306
307        if let Some(mut on_activated) = on_activated {
308            let handler = TypedEventHandler::new(move |_, args: &Option<IInspectable>| {
309                if let Some(args) = args {
310                    let args = args.cast::<ToastActivatedEventArgs>()?;
311                    let user_input = args.UserInput()?;
312
313                    let mut hash_map: HashMap<String, String> = HashMap::new();
314
315                    for keyvalue in user_input {
316                        let key = keyvalue.Key()?;
317                        let value: HSTRING = keyvalue.Value()?.try_into()?;
318
319                        hash_map.insert(key.to_string(), value.to_string());
320                    }
321
322                    hash_map.insert("argument".to_string(), args.Arguments()?.to_string());
323                    on_activated(hash_map);
324                }
325                Ok(())
326            });
327
328            toast.Activated(&handler)?;
329        }
330
331        if let Some(mut on_dismissed) = on_dismissed {
332            let handler =
333                TypedEventHandler::new(move |_, args: &Option<ToastDismissedEventArgs>| {
334                    if let Some(args) = args {
335                        let reason = match args.Reason()? {
336                            ToastDismissalReason::UserCanceled => Dismissed::UserCanceled,
337                            ToastDismissalReason::ApplicationHidden => Dismissed::ApplicationHidden,
338                            ToastDismissalReason::TimedOut => Dismissed::TimedOut,
339                            _ => Dismissed::None,
340                        };
341
342                        on_dismissed(reason);
343                    }
344                    Ok(())
345                });
346
347            toast.Dismissed(&handler)?;
348        }
349
350        if let Some(mut on_failed) = on_failed {
351            let handler = TypedEventHandler::new(move |_, args: &Option<ToastFailedEventArgs>| {
352                if let Some(args) = args {
353                    let error_code = args.ErrorCode()?;
354                    on_failed(error_code);
355                }
356                Ok(())
357            });
358
359            toast.Failed(&handler)?;
360        }
361
362        manager.Show(&toast)?;
363        Ok((toast, manager))
364    }
365
366    pub fn show_xml(&self, id: &str, xml: &str) -> Result<(ToastNotification, ToastNotifier)> {
367        let document = XmlDocument::new()?;
368
369        document.LoadXml(h!(xml))?;
370
371        let manager = ToastNotificationManager::CreateToastNotifierWithId(h!(id))?;
372        let toast = ToastNotification::CreateToastNotification(&document)?;
373
374        toast.SetTag(h!(TAG))?;
375        toast.SetData(&self.notify_data)?;
376
377        manager.Show(&toast)?;
378        Ok((toast, manager))
379    }
380}