use std::{
borrow::Cow,
convert::Infallible,
error::Error,
fmt::{Debug, Display},
};
use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyString, IntoPyObject};
use pyo3_utils::py_wrapper::{PyWrapper, PyWrapperT0};
use crate::{
ext_mod::{
image::Image,
menu::{Menu, MenuEvent},
plugin::Plugin,
tray::{TrayIcon, TrayIconEvent},
window::Monitor,
ConfigInto, PhysicalPositionF64, Theme,
},
tauri_runtime::Runtime,
utils::{delegate_inner, PyResultExt as _},
};
pub(crate) type TauriAppHandle = tauri::AppHandle<Runtime>;
pub(crate) fn debug_assert_app_handle_py_is_rs(
py_app_handle: &Py<AppHandle>,
rs_app_handle: &TauriAppHandle,
) {
if cfg!(debug_assertions) {
let py_app_handle = py_app_handle.get().0.inner_ref();
let py_app_handle_global = py_app_handle.py_app_handle();
let rs_app_handle_global = rs_app_handle.py_app_handle();
debug_assert!(
py_app_handle_global.is(rs_app_handle_global),
"AppHandle pyobject instance is not the same as the rust instance"
);
}
}
#[pyclass(frozen)]
#[non_exhaustive]
pub struct AppHandle(pub PyWrapper<PyWrapperT0<TauriAppHandle>>);
impl AppHandle {
fn new(app_handle: TauriAppHandle) -> Self {
Self(PyWrapper::new0(app_handle))
}
}
#[pymethods]
impl AppHandle {
fn run_on_main_thread(&self, py: Python<'_>, handler: PyObject) -> PyResult<()> {
py.allow_threads(|| {
delegate_inner!(self, run_on_main_thread, move || {
Python::with_gil(|py| {
let handler = handler.bind(py);
let result = handler.call0();
result.unwrap_unraisable_py_result(py, Some(handler), || {
"Python exception occurred in `AppHandle::run_on_main_thread`"
});
})
})
})
}
fn plugin(&self, py: Python<'_>, plugin: Py<Plugin>) -> PyResult<()> {
py.allow_threads(|| {
let plugin = plugin.get().into_tauri()??;
delegate_inner!(self, plugin_boxed, plugin)
})
}
fn remove_plugin(&self, py: Python<'_>, plugin: Cow<'_, str>) -> bool {
py.allow_threads(|| self.0.inner_ref().remove_plugin(&plugin))
}
fn exit(&self, py: Python<'_>, exit_code: i32) {
py.allow_threads(|| self.0.inner_ref().exit(exit_code))
}
fn restart(&self, py: Python<'_>) {
let _: Infallible = py.allow_threads(|| self.0.inner_ref().restart());
}
fn request_restart(&self, py: Python<'_>) {
py.allow_threads(|| self.0.inner_ref().request_restart())
}
#[cfg(target_os = "macos")]
fn set_dock_visibility(&self, py: Python<'_>, visible: bool) -> PyResult<()> {
py.allow_threads(|| delegate_inner!(self, set_dock_visibility, visible))
}
pub(crate) fn on_menu_event(slf: Py<Self>, py: Python<'_>, handler: PyObject) {
let moved_slf = slf.clone_ref(py);
py.allow_threads(|| {
slf.get()
.0
.inner_ref()
.on_menu_event(move |_app_handle, menu_event| {
Python::with_gil(|py| {
let app_handle: &Py<Self> = &moved_slf;
debug_assert_app_handle_py_is_rs(app_handle, _app_handle);
let menu_event: Bound<'_, MenuEvent> =
MenuEvent::intern(py, &menu_event.id.0);
let handler = handler.bind(py);
let result = handler.call1((app_handle, menu_event));
result.unwrap_unraisable_py_result(py, Some(handler), || {
"Python exception occurred in `AppHandle::on_menu_event` handler"
});
})
})
})
}
fn on_tray_icon_event(slf: Py<Self>, py: Python<'_>, handler: PyObject) {
let moved_slf = slf.clone_ref(py);
py.allow_threads(|| {
slf.get()
.0
.inner_ref()
.on_tray_icon_event(move |_app_handle, tray_icon_event| {
Python::with_gil(|py| {
let app_handle: &Py<Self> = &moved_slf;
debug_assert_app_handle_py_is_rs(app_handle, _app_handle);
let tray_icon_event: TrayIconEvent =
TrayIconEvent::from_tauri(py, &tray_icon_event)
.expect("Failed to convert rust `TrayIconEvent` to pyobject");
let handler = handler.bind(py);
let result = handler.call1((app_handle, tray_icon_event));
result.unwrap_unraisable_py_result(py, Some(handler), || {
"Python exception occurred in `AppHandle::on_tray_icon_event` handler"
});
})
})
})
}
fn tray_by_id(&self, py: Python<'_>, id: &str) -> Option<TrayIcon> {
py.allow_threads(|| self.0.inner_ref().tray_by_id(id).map(TrayIcon::new))
}
fn remove_tray_by_id(&self, py: Python<'_>, id: &str) -> Option<TrayIcon> {
py.allow_threads(|| self.0.inner_ref().remove_tray_by_id(id).map(TrayIcon::new))
}
fn config<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let slf = self.0.inner_ref();
ConfigInto::new(Cow::Borrowed(slf.config())).into_pyobject(py)
}
fn primary_monitor(&self, py: Python<'_>) -> PyResult<Option<Monitor>> {
let monitor = py.allow_threads(|| delegate_inner!(self, primary_monitor,))?;
let monitor = monitor.map(|m| Monitor::from_tauri(py, m)).transpose()?;
Ok(monitor)
}
fn monitor_from_point(&self, py: Python<'_>, x: f64, y: f64) -> PyResult<Option<Monitor>> {
let monitor = py.allow_threads(|| delegate_inner!(self, monitor_from_point, x, y))?;
let monitor = monitor.map(|m| Monitor::from_tauri(py, m)).transpose()?;
Ok(monitor)
}
fn available_monitors(&self, py: Python<'_>) -> PyResult<Vec<Monitor>> {
let monitors = py.allow_threads(|| delegate_inner!(self, available_monitors,))?;
let monitors = monitors
.into_iter()
.map(|m| Monitor::from_tauri(py, m))
.collect::<PyResult<Vec<_>>>()?;
Ok(monitors)
}
fn cursor_position(&self, py: Python<'_>) -> PyResult<PhysicalPositionF64> {
let position = py.allow_threads(|| delegate_inner!(self, cursor_position,))?;
PhysicalPositionF64::from_tauri(py, position)
}
fn set_theme(&self, py: Python<'_>, theme: Option<Theme>) {
py.allow_threads(|| self.0.inner_ref().set_theme(theme.map(Into::into)))
}
fn default_window_icon(&self, py: Python<'_>) -> Option<Image> {
self.0
.inner_ref()
.default_window_icon()
.map(|icon| Image::from_tauri(py, icon))
}
fn menu(&self, py: Python<'_>) -> Option<Menu> {
py.allow_threads(|| self.0.inner_ref().menu().map(Menu::new))
}
fn set_menu(&self, py: Python<'_>, menu: Py<Menu>) -> PyResult<Option<Menu>> {
py.allow_threads(|| {
let menu = menu.get().0.inner_ref().clone();
let returned_menu = delegate_inner!(self, set_menu, menu)?;
PyResult::Ok(returned_menu.map(Menu::new))
})
}
fn remove_menu(&self, py: Python<'_>) -> PyResult<Option<Menu>> {
py.allow_threads(|| {
let returned_menu = delegate_inner!(self, remove_menu,)?;
PyResult::Ok(returned_menu.map(Menu::new))
})
}
fn hide_menu(&self, py: Python<'_>) -> PyResult<()> {
py.allow_threads(|| delegate_inner!(self, hide_menu,))
}
fn show_menu(&self, py: Python<'_>) -> PyResult<()> {
py.allow_threads(|| delegate_inner!(self, show_menu,))
}
fn cleanup_before_exit(&self, py: Python<'_>) {
py.allow_threads(|| self.0.inner_ref().cleanup_before_exit())
}
fn invoke_key<'py>(&self, py: Python<'py>) -> Bound<'py, PyString> {
PyString::intern(py, self.0.inner_ref().invoke_key())
}
}
#[derive(Debug)]
pub struct PyAppHandleStateError;
impl Display for PyAppHandleStateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Failed to get `PyAppHandle` from state, maybe this app was not created by pytauri"
)
}
}
impl Error for PyAppHandleStateError {}
impl From<PyAppHandleStateError> for PyErr {
fn from(value: PyAppHandleStateError) -> Self {
PyRuntimeError::new_err(value.to_string())
}
}
pub type PyAppHandleStateResult<T> = Result<T, PyAppHandleStateError>;
mod sealed {
use super::*;
pub trait SealedPyAppHandleExt {}
impl<T: tauri::Manager<Runtime>> SealedPyAppHandleExt for T {}
pub(super) struct PyAppHandle(pub(crate) Py<AppHandle>);
}
use sealed::{PyAppHandle, SealedPyAppHandleExt};
pub trait PyAppHandleExt: tauri::Manager<Runtime> + SealedPyAppHandleExt {
fn py_app_handle(&self) -> &Py<AppHandle> {
self.try_py_app_handle().unwrap()
}
fn try_py_app_handle(&self) -> PyAppHandleStateResult<&Py<AppHandle>> {
let state = self
.try_state::<PyAppHandle>()
.ok_or(PyAppHandleStateError)?;
Ok(&state.inner().0)
}
fn get_or_init_py_app_handle(&self, py: Python<'_>) -> PyResult<&Py<AppHandle>> {
match self.try_py_app_handle() {
Ok(py_app_handle) => Ok(py_app_handle),
Err(_) => {
let py_app_handle = AppHandle::new(self.app_handle().to_owned());
let py_app_handle = py_app_handle.into_pyobject(py)?.unbind();
let not_yet_managed = self.manage::<PyAppHandle>(PyAppHandle(py_app_handle));
debug_assert!(
not_yet_managed,
"`PyAppHandle` is private, so it is impossible for other crates to manage it, \
and for self crate, it should be initialized only once."
);
Ok(self
.try_py_app_handle()
.expect("`PyAppHandle` has already been initialized, so this never fail"))
}
}
}
}
impl<T: tauri::Manager<Runtime>> PyAppHandleExt for T {}