pytauri_core/ext_mod_impl/lib/
app.rs

1use pyo3::prelude::*;
2use pyo3_utils::{
3    py_wrapper::{PyWrapper, PyWrapperT2},
4    ungil::UnsafeUngilExt,
5};
6
7use crate::{
8    ext_mod::{
9        debug_assert_app_handle_py_is_rs, AppHandle, PyAppHandleExt as _, RunEvent, TauriAppHandle,
10    },
11    tauri_runtime::Runtime,
12    utils::PyResultExt as _,
13};
14
15pub(crate) type TauriApp = tauri::App<Runtime>;
16
17#[pyclass(frozen, unsendable)]
18#[non_exhaustive]
19pub struct App(pub PyWrapper<PyWrapperT2<TauriApp>>);
20
21impl App {
22    #[cfg(feature = "__private")]
23    pub fn try_build(py: Python<'_>, app: TauriApp) -> PyResult<Self> {
24        // remember to initialize the global singleton [PyAppHandle], see it's doc
25        app.get_or_init_py_app_handle(py)?;
26        Ok(Self(PyWrapper::new2(app)))
27    }
28
29    fn py_cb_to_rs_cb(
30        callback: PyObject,
31        app_handle: Py<AppHandle>,
32    ) -> impl FnMut(&TauriAppHandle, tauri::RunEvent) {
33        move |_app_handle, run_event| {
34            let py_app_handle: &Py<AppHandle> = &app_handle;
35            debug_assert_app_handle_py_is_rs(&app_handle, _app_handle);
36
37            Python::with_gil(|py| {
38                let py_run_event: RunEvent = RunEvent::from_tauri(py, run_event)
39                    // TODO: maybe we should only `write_unraisable` and log it instead of `panic` here?
40                    .expect("Failed to convert rust `RunEvent` to pyobject");
41
42                let callback = callback.bind(py);
43                let result = callback.call1((py_app_handle, py_run_event));
44                // `panic` allows Python to exit `app.run()`,
45                // otherwise the Python main thread will be blocked by `app.run()`
46                // and unable to raise an error.
47                result.unwrap_unraisable_py_result(py, Some(callback), || {
48                    "Python exception occurred in `App` run callback"
49                });
50            })
51        }
52    }
53
54    fn noop_callback(_: &TauriAppHandle, _: tauri::RunEvent) {}
55}
56
57#[pymethods]
58impl App {
59    #[pyo3(signature = (callback = None, /))]
60    fn run(&self, py: Python<'_>, callback: Option<PyObject>) -> PyResult<()> {
61        let app = self.0.try_take_inner()??;
62        let py_app_handle = app.py_app_handle().clone_ref(py);
63        unsafe {
64            // `tauri::App` does not hold the GIL, so this is safe
65            py.allow_threads_unsend(app, move |app| {
66                match callback {
67                    Some(callback) => app.run(Self::py_cb_to_rs_cb(callback, py_app_handle)),
68                    None => app.run(Self::noop_callback),
69                }
70                Ok(())
71            })
72        }
73    }
74
75    #[pyo3(signature = (callback = None, /))]
76    fn run_return(&self, py: Python<'_>, callback: Option<PyObject>) -> PyResult<i32> {
77        let app = self.0.try_take_inner()??;
78        let py_app_handle = app.py_app_handle().clone_ref(py);
79        unsafe {
80            // `tauri::App` does not hold the GIL, so this is safe
81            py.allow_threads_unsend(app, move |app| {
82                let exit_code = match callback {
83                    Some(callback) => app.run_return(Self::py_cb_to_rs_cb(callback, py_app_handle)),
84                    None => app.run_return(Self::noop_callback),
85                };
86                Ok(exit_code)
87            })
88        }
89    }
90
91    #[expect(deprecated)]
92    #[pyo3(signature = (callback = None, /))]
93    fn run_iteration(&self, py: Python<'_>, callback: Option<PyObject>) -> PyResult<()> {
94        let app = self.0.try_lock_inner_mut()??;
95        let py_app_handle = app.py_app_handle().clone_ref(py);
96        unsafe {
97            // `&mut tauri::App` does not hold the GIL, so this is safe
98            py.allow_threads_unsend(app, |mut app| {
99                match callback {
100                    Some(callback) => {
101                        app.run_iteration(Self::py_cb_to_rs_cb(callback, py_app_handle))
102                    }
103                    None => app.run_iteration(Self::noop_callback),
104                }
105                Ok(())
106            })
107        }
108    }
109
110    fn cleanup_before_exit(&self, py: Python<'_>) -> PyResult<()> {
111        // `self: &App` does not hold the GIL, so this is safe
112        unsafe {
113            py.allow_threads_unsend(self, |slf| {
114                let app = slf.0.try_lock_inner_ref()??;
115                app.cleanup_before_exit();
116                Ok(())
117            })
118        }
119    }
120
121    fn handle(&self, py: Python<'_>) -> PyResult<Py<AppHandle>> {
122        let app = self.0.try_lock_inner_ref()??;
123        // TODO, PERF: release the GIL?
124        let app_handle = app.py_app_handle().clone_ref(py);
125        Ok(app_handle)
126    }
127}