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