pytauri_core/plugins/notification/
mod.rs

1use std::{
2    error::Error,
3    fmt::{Debug, Display, Formatter},
4};
5
6use pyo3::{prelude::*, types::PyDict};
7use pyo3_utils::{
8    from_py_dict::{derive_from_py_dict, FromPyDict as _, NotRequired},
9    py_wrapper::{PyWrapper, PyWrapperT2},
10};
11use tauri_plugin_notification::{self as plugin, NotificationExt as _};
12
13use crate::{
14    ext_mod::{manager_method_impl, ImplManager},
15    tauri_runtime::Runtime,
16};
17
18#[derive(Debug)]
19struct PluginError(plugin::Error);
20
21impl Display for PluginError {
22    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23        Display::fmt(&self.0, f)
24    }
25}
26
27impl Error for PluginError {}
28
29impl From<PluginError> for PyErr {
30    fn from(value: PluginError) -> Self {
31        match value.0 {
32            plugin::Error::Io(e) => e.into(),
33        }
34    }
35}
36
37impl From<plugin::Error> for PluginError {
38    fn from(value: plugin::Error) -> Self {
39        Self(value)
40    }
41}
42
43/// See also: [tauri_plugin_notification::NotificationBuilder]
44#[non_exhaustive]
45pub struct NotificationBuilderArgs {
46    id: NotRequired<i32>,
47    channel_id: NotRequired<String>,
48    title: NotRequired<String>,
49    body: NotRequired<String>,
50    /* TODO: schedule */
51    large_body: NotRequired<String>,
52    summary: NotRequired<String>,
53    action_type_id: NotRequired<String>,
54    group: NotRequired<String>,
55    group_summary: bool,
56    sound: NotRequired<String>,
57    inbox_line: NotRequired<String>,
58    icon: NotRequired<String>,
59    large_icon: NotRequired<String>,
60    icon_color: NotRequired<String>,
61    /* TODO: attachment */
62    /* TODO: extra */
63    ongoing: bool,
64    auto_cancel: bool,
65    silent: bool,
66}
67
68derive_from_py_dict!(NotificationBuilderArgs {
69    #[default]
70    id,
71    #[default]
72    channel_id,
73    #[default]
74    title,
75    #[default]
76    body,
77    #[default]
78    large_body,
79    #[default]
80    summary,
81    #[default]
82    action_type_id,
83    #[default]
84    group,
85    #[default]
86    group_summary,
87    #[default]
88    sound,
89    #[default]
90    inbox_line,
91    #[default]
92    icon,
93    #[default]
94    large_icon,
95    #[default]
96    icon_color,
97    #[default]
98    ongoing,
99    #[default]
100    auto_cancel,
101    #[default]
102    silent,
103});
104
105impl NotificationBuilderArgs {
106    // TODO: Maybe we can upstream this to pyo3,
107    // so that we can directly use it as the type signature for `**kwargs`
108    fn from_kwargs(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult<Option<Self>> {
109        kwargs
110            .map(NotificationBuilderArgs::from_py_dict)
111            .transpose()
112    }
113
114    fn apply_to_builder(
115        self,
116        mut builder: plugin::NotificationBuilder<Runtime>,
117    ) -> plugin::NotificationBuilder<Runtime> {
118        let Self {
119            id,
120            channel_id,
121            title,
122            body,
123            large_body,
124            summary,
125            action_type_id,
126            group,
127            group_summary,
128            sound,
129            inbox_line,
130            icon,
131            large_icon,
132            icon_color,
133            ongoing,
134            auto_cancel,
135            silent,
136        } = self;
137
138        if let Some(id) = id.0 {
139            builder = builder.id(id);
140        }
141        if let Some(channel_id) = channel_id.0 {
142            builder = builder.channel_id(channel_id);
143        }
144        if let Some(title) = title.0 {
145            builder = builder.title(title);
146        }
147        if let Some(body) = body.0 {
148            builder = builder.body(body);
149        }
150        if let Some(large_body) = large_body.0 {
151            builder = builder.large_body(large_body);
152        }
153        if let Some(summary) = summary.0 {
154            builder = builder.summary(summary);
155        }
156        if let Some(action_type_id) = action_type_id.0 {
157            builder = builder.action_type_id(action_type_id);
158        }
159        if let Some(group) = group.0 {
160            builder = builder.group(group);
161        }
162        if group_summary {
163            builder = builder.group_summary();
164        }
165        if let Some(sound) = sound.0 {
166            builder = builder.sound(sound);
167        }
168        if let Some(inbox_line) = inbox_line.0 {
169            builder = builder.inbox_line(inbox_line);
170        }
171        if let Some(icon) = icon.0 {
172            builder = builder.icon(icon);
173        }
174        if let Some(large_icon) = large_icon.0 {
175            builder = builder.large_icon(large_icon);
176        }
177        if let Some(icon_color) = icon_color.0 {
178            builder = builder.icon_color(icon_color);
179        }
180        if ongoing {
181            builder = builder.ongoing();
182        }
183        if auto_cancel {
184            builder = builder.auto_cancel();
185        }
186        if silent {
187            builder = builder.silent();
188        }
189
190        builder
191    }
192}
193
194/// See also: [tauri_plugin_notification::NotificationBuilder]
195#[pyclass(frozen)]
196#[non_exhaustive]
197pub struct NotificationBuilder(pub PyWrapper<PyWrapperT2<plugin::NotificationBuilder<Runtime>>>);
198
199impl NotificationBuilder {
200    fn new(builder: plugin::NotificationBuilder<Runtime>) -> Self {
201        Self(PyWrapper::new2(builder))
202    }
203}
204
205#[pymethods]
206impl NotificationBuilder {
207    #[pyo3(signature = (**kwargs))]
208    fn show(&self, kwargs: Option<&Bound<'_, PyDict>>) -> PyResult<()> {
209        let args = NotificationBuilderArgs::from_kwargs(kwargs)?;
210
211        let mut builder = self.0.try_take_inner()??;
212
213        if let Some(args) = args {
214            builder = args.apply_to_builder(builder);
215        }
216
217        // PERF: it's short enough, so we don't release the GIL
218        builder
219            .show()
220            .map_err(PluginError::from)
221            .map_err(PyErr::from)
222    }
223}
224
225/// See also: [tauri_plugin_notification::NotificationExt]
226#[pyclass(frozen)]
227#[non_exhaustive]
228pub struct NotificationExt;
229
230/// The Implementers of [tauri_plugin_notification::NotificationExt].
231pub type ImplNotificationExt = ImplManager;
232
233#[pymethods]
234impl NotificationExt {
235    #[staticmethod]
236    fn builder(slf: ImplNotificationExt, py: Python<'_>) -> PyResult<NotificationBuilder> {
237        manager_method_impl!(py, &slf, |_py, manager| {
238            // PERF: it's short enough, so we don't release the GIL
239            let builder = manager.notification().builder();
240            Ok(NotificationBuilder::new(builder))
241        })?
242    }
243}
244
245/// See also: [tauri_plugin_notification]
246#[pymodule(submodule, gil_used = false)]
247pub mod notification {
248    #[pymodule_export]
249    pub use super::{NotificationBuilder, NotificationExt};
250
251    pub use super::{ImplNotificationExt, NotificationBuilderArgs};
252}