use crate::{
image::Image,
ipc::{
channel::ChannelDataIpcQueue, CommandArg, CommandItem, Invoke, InvokeError, InvokeHandler,
},
manager::{webview::UriSchemeProtocol, AppManager, Asset},
plugin::{Plugin, PluginStore},
resources::ResourceTable,
runtime::{
window::{WebviewEvent as RuntimeWebviewEvent, WindowEvent as RuntimeWindowEvent},
ExitRequestedEventAction, RunEvent as RuntimeRunEvent,
},
sealed::{ManagerBase, RuntimeOrDispatch},
utils::config::Config,
utils::Env,
webview::PageLoadPayload,
Context, DeviceEventFilter, Emitter, EventLoopMessage, Listener, Manager, Monitor, Result,
Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window,
};
#[cfg(desktop)]
use crate::menu::{Menu, MenuEvent};
#[cfg(all(desktop, feature = "tray-icon"))]
use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId};
use raw_window_handle::HasDisplayHandle;
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
use tauri_macros::default_runtime;
#[cfg(desktop)]
use tauri_runtime::EventLoopProxy;
use tauri_runtime::{
dpi::{PhysicalPosition, PhysicalSize},
window::DragDropEvent,
RuntimeInitArgs,
};
use tauri_utils::{assets::AssetsIter, PackageInfo};
use serde::Serialize;
use std::{
borrow::Cow,
collections::HashMap,
fmt,
sync::{mpsc::Sender, Arc, MutexGuard},
};
use crate::{event::EventId, runtime::RuntimeHandle, Event, EventTarget};
#[cfg(target_os = "macos")]
use crate::ActivationPolicy;
pub(crate) mod plugin;
#[cfg(desktop)]
pub(crate) type GlobalMenuEventListener<T> = Box<dyn Fn(&T, crate::menu::MenuEvent) + Send + Sync>;
#[cfg(all(desktop, feature = "tray-icon"))]
pub(crate) type GlobalTrayIconEventListener<T> =
Box<dyn Fn(&T, crate::tray::TrayIconEvent) + Send + Sync>;
pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(&Window<R>, &WindowEvent) + Send + Sync>;
pub(crate) type GlobalWebviewEventListener<R> =
Box<dyn Fn(&Webview<R>, &WebviewEvent) + Send + Sync>;
pub type SetupHook<R> =
Box<dyn FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send>;
pub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
pub const RESTART_EXIT_CODE: i32 = i32::MAX;
#[derive(Debug)]
pub struct ExitRequestApi(Sender<ExitRequestedEventAction>);
impl ExitRequestApi {
pub fn prevent_exit(&self) {
self.0.send(ExitRequestedEventAction::Prevent).unwrap();
}
}
#[derive(Debug, Clone)]
pub struct CloseRequestApi(Sender<bool>);
impl CloseRequestApi {
pub fn prevent_close(&self) {
self.0.send(true).unwrap();
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum WindowEvent {
Resized(PhysicalSize<u32>),
Moved(PhysicalPosition<i32>),
#[non_exhaustive]
CloseRequested {
api: CloseRequestApi,
},
Destroyed,
Focused(bool),
#[non_exhaustive]
ScaleFactorChanged {
scale_factor: f64,
new_inner_size: PhysicalSize<u32>,
},
DragDrop(DragDropEvent),
ThemeChanged(Theme),
}
impl From<RuntimeWindowEvent> for WindowEvent {
fn from(event: RuntimeWindowEvent) -> Self {
match event {
RuntimeWindowEvent::Resized(size) => Self::Resized(size),
RuntimeWindowEvent::Moved(position) => Self::Moved(position),
RuntimeWindowEvent::CloseRequested { signal_tx } => Self::CloseRequested {
api: CloseRequestApi(signal_tx),
},
RuntimeWindowEvent::Destroyed => Self::Destroyed,
RuntimeWindowEvent::Focused(flag) => Self::Focused(flag),
RuntimeWindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
} => Self::ScaleFactorChanged {
scale_factor,
new_inner_size,
},
RuntimeWindowEvent::DragDrop(event) => Self::DragDrop(event),
RuntimeWindowEvent::ThemeChanged(theme) => Self::ThemeChanged(theme),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum WebviewEvent {
DragDrop(DragDropEvent),
}
impl From<RuntimeWebviewEvent> for WebviewEvent {
fn from(event: RuntimeWebviewEvent) -> Self {
match event {
RuntimeWebviewEvent::DragDrop(e) => Self::DragDrop(e),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum RunEvent {
Exit,
#[non_exhaustive]
ExitRequested {
code: Option<i32>,
api: ExitRequestApi,
},
#[non_exhaustive]
WindowEvent {
label: String,
event: WindowEvent,
},
#[non_exhaustive]
WebviewEvent {
label: String,
event: WebviewEvent,
},
Ready,
Resumed,
MainEventsCleared,
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", feature = "ios"))))]
Opened {
urls: Vec<url::Url>,
},
#[cfg(desktop)]
#[cfg_attr(docsrs, doc(cfg(desktop)))]
MenuEvent(crate::menu::MenuEvent),
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
TrayIconEvent(crate::tray::TrayIconEvent),
#[non_exhaustive]
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
Reopen {
has_visible_windows: bool,
},
}
impl From<EventLoopMessage> for RunEvent {
fn from(event: EventLoopMessage) -> Self {
match event {
#[cfg(desktop)]
EventLoopMessage::MenuEvent(e) => Self::MenuEvent(e),
#[cfg(all(desktop, feature = "tray-icon"))]
EventLoopMessage::TrayIconEvent(e) => Self::TrayIconEvent(e),
}
}
}
#[derive(Debug, Clone)]
pub struct AssetResolver<R: Runtime> {
manager: Arc<AppManager<R>>,
}
impl<R: Runtime> AssetResolver<R> {
pub fn get(&self, path: String) -> Option<Asset> {
#[cfg(dev)]
{
if let (Some(_), Some(crate::utils::config::FrontendDist::Directory(dist_path))) = (
&self.manager.config().build.dev_url,
&self.manager.config().build.frontend_dist,
) {
let asset_path = std::path::PathBuf::from(&path)
.components()
.filter(|c| !matches!(c, std::path::Component::RootDir))
.collect::<std::path::PathBuf>();
let asset_path = self
.manager
.config_parent()
.map(|p| p.join(dist_path).join(&asset_path))
.unwrap_or_else(|| dist_path.join(&asset_path));
return std::fs::read(asset_path).ok().map(|bytes| {
let mime_type = crate::utils::mime_type::MimeType::parse(&bytes, &path);
Asset {
bytes,
mime_type,
csp_header: None,
}
});
}
}
self.manager.get_asset(path).ok()
}
pub fn iter(&self) -> Box<AssetsIter<'_>> {
self.manager.assets.iter()
}
}
#[default_runtime(crate::Wry, wry)]
#[derive(Debug)]
pub struct AppHandle<R: Runtime> {
pub(crate) runtime_handle: R::Handle,
pub(crate) manager: Arc<AppManager<R>>,
}
#[cfg(feature = "wry")]
impl AppHandle<crate::Wry> {
pub fn create_tao_window<
F: FnOnce() -> (String, tauri_runtime_wry::TaoWindowBuilder) + Send + 'static,
>(
&self,
f: F,
) -> crate::Result<std::sync::Weak<tauri_runtime_wry::Window>> {
self.runtime_handle.create_tao_window(f).map_err(Into::into)
}
pub fn send_tao_window_event(
&self,
window_id: tauri_runtime_wry::TaoWindowId,
message: tauri_runtime_wry::WindowMessage,
) -> crate::Result<()> {
self
.runtime_handle
.send_event(tauri_runtime_wry::Message::Window(
self.runtime_handle.window_id(window_id),
message,
))
.map_err(Into::into)
}
}
impl<R: Runtime> Clone for AppHandle<R> {
fn clone(&self) -> Self {
Self {
runtime_handle: self.runtime_handle.clone(),
manager: self.manager.clone(),
}
}
}
impl<'de, R: Runtime> CommandArg<'de, R> for AppHandle<R> {
fn from_command(command: CommandItem<'de, R>) -> std::result::Result<Self, InvokeError> {
Ok(command.message.webview().app_handle)
}
}
impl<R: Runtime> AppHandle<R> {
pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
self
.runtime_handle
.run_on_main_thread(f)
.map_err(Into::into)
}
#[cfg_attr(feature = "tracing", tracing::instrument(name = "app::plugin::register", skip(plugin), fields(name = plugin.name())))]
pub fn plugin<P: Plugin<R> + 'static>(&self, plugin: P) -> crate::Result<()> {
let mut plugin = Box::new(plugin) as Box<dyn Plugin<R>>;
let mut store = self.manager().plugins.lock().unwrap();
store.initialize(&mut plugin, self, &self.config().plugins)?;
store.register(plugin);
Ok(())
}
pub fn remove_plugin(&self, plugin: &'static str) -> bool {
self.manager().plugins.lock().unwrap().unregister(plugin)
}
pub fn exit(&self, exit_code: i32) {
if let Err(e) = self.runtime_handle.request_exit(exit_code) {
log::error!("failed to exit: {}", e);
self.cleanup_before_exit();
std::process::exit(exit_code);
}
}
pub fn restart(&self) -> ! {
if self.runtime_handle.request_exit(RESTART_EXIT_CODE).is_err() {
self.cleanup_before_exit();
}
crate::process::restart(&self.env());
}
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
pub fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> crate::Result<()> {
self
.runtime_handle
.set_activation_policy(activation_policy)
.map_err(Into::into)
}
}
impl<R: Runtime> Manager<R> for AppHandle<R> {
fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
self.manager.resources_table()
}
}
impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
fn manager(&self) -> &AppManager<R> {
&self.manager
}
fn manager_owned(&self) -> Arc<AppManager<R>> {
self.manager.clone()
}
fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
}
fn managed_app_handle(&self) -> &AppHandle<R> {
self
}
}
#[default_runtime(crate::Wry, wry)]
pub struct App<R: Runtime> {
runtime: Option<R>,
setup: Option<SetupHook<R>>,
manager: Arc<AppManager<R>>,
handle: AppHandle<R>,
ran_setup: bool,
}
impl<R: Runtime> fmt::Debug for App<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("App")
.field("runtime", &self.runtime)
.field("manager", &self.manager)
.field("handle", &self.handle)
.finish()
}
}
impl<R: Runtime> Manager<R> for App<R> {
fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
self.manager.resources_table()
}
}
impl<R: Runtime> ManagerBase<R> for App<R> {
fn manager(&self) -> &AppManager<R> {
&self.manager
}
fn manager_owned(&self) -> Arc<AppManager<R>> {
self.manager.clone()
}
fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
if let Some(runtime) = self.runtime.as_ref() {
RuntimeOrDispatch::Runtime(runtime)
} else {
self.handle.runtime()
}
}
fn managed_app_handle(&self) -> &AppHandle<R> {
self.handle()
}
}
#[cfg(feature = "wry")]
impl App<crate::Wry> {
pub fn wry_plugin<P: tauri_runtime_wry::PluginBuilder<EventLoopMessage> + Send + 'static>(
&mut self,
plugin: P,
) where
<P as tauri_runtime_wry::PluginBuilder<EventLoopMessage>>::Plugin: Send,
{
self.handle.runtime_handle.plugin(plugin);
}
}
macro_rules! shared_app_impl {
($app: ty) => {
impl<R: Runtime> $app {
#[cfg(desktop)]
pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(
&self,
handler: F,
) {
self.manager.menu.on_menu_event(handler)
}
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
pub fn on_tray_icon_event<F: Fn(&AppHandle<R>, TrayIconEvent) + Send + Sync + 'static>(
&self,
handler: F,
) {
self.manager.tray.on_tray_icon_event(handler)
}
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
pub fn tray_by_id<'a, I>(&self, id: &'a I) -> Option<TrayIcon<R>>
where
I: ?Sized,
TrayIconId: PartialEq<&'a I>,
{
self.manager.tray.tray_by_id(id)
}
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
pub fn remove_tray_by_id<'a, I>(&self, id: &'a I) -> Option<TrayIcon<R>>
where
I: ?Sized,
TrayIconId: PartialEq<&'a I>,
{
self.manager.tray.remove_tray_by_id(id)
}
pub fn config(&self) -> &Config {
self.manager.config()
}
pub fn package_info(&self) -> &PackageInfo {
self.manager.package_info()
}
pub fn asset_resolver(&self) -> AssetResolver<R> {
AssetResolver {
manager: self.manager.clone(),
}
}
pub fn primary_monitor(&self) -> crate::Result<Option<Monitor>> {
Ok(match self.runtime() {
RuntimeOrDispatch::Runtime(h) => h.primary_monitor().map(Into::into),
RuntimeOrDispatch::RuntimeHandle(h) => h.primary_monitor().map(Into::into),
_ => unreachable!(),
})
}
pub fn monitor_from_point(&self, x: f64, y: f64) -> crate::Result<Option<Monitor>> {
Ok(match self.runtime() {
RuntimeOrDispatch::Runtime(h) => h.monitor_from_point(x, y).map(Into::into),
RuntimeOrDispatch::RuntimeHandle(h) => h.monitor_from_point(x, y).map(Into::into),
_ => unreachable!(),
})
}
pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {
Ok(match self.runtime() {
RuntimeOrDispatch::Runtime(h) => {
h.available_monitors().into_iter().map(Into::into).collect()
}
RuntimeOrDispatch::RuntimeHandle(h) => {
h.available_monitors().into_iter().map(Into::into).collect()
}
_ => unreachable!(),
})
}
pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {
Ok(match self.runtime() {
RuntimeOrDispatch::Runtime(h) => h.cursor_position()?,
RuntimeOrDispatch::RuntimeHandle(h) => h.cursor_position()?,
_ => unreachable!(),
})
}
pub fn set_theme(&self, theme: Option<Theme>) {
#[cfg(windows)]
for window in self.manager.windows().values() {
if let (Some(menu), Ok(hwnd)) = (window.menu(), window.hwnd()) {
let raw_hwnd = hwnd.0 as isize;
let _ = self.run_on_main_thread(move || {
let _ = unsafe {
menu.inner().set_theme_for_hwnd(
raw_hwnd,
theme
.map(crate::menu::map_to_menu_theme)
.unwrap_or(muda::MenuTheme::Auto),
)
};
});
};
}
match self.runtime() {
RuntimeOrDispatch::Runtime(h) => h.set_theme(theme),
RuntimeOrDispatch::RuntimeHandle(h) => h.set_theme(theme),
_ => unreachable!(),
}
}
pub fn default_window_icon(&self) -> Option<&Image<'_>> {
self.manager.window.default_icon.as_ref()
}
#[cfg(desktop)]
pub fn menu(&self) -> Option<Menu<R>> {
self.manager.menu.menu_lock().clone()
}
#[cfg(desktop)]
pub fn set_menu(&self, menu: Menu<R>) -> crate::Result<Option<Menu<R>>> {
let prev_menu = self.remove_menu()?;
self.manager.menu.insert_menu_into_stash(&menu);
self.manager.menu.menu_lock().replace(menu.clone());
#[cfg(not(target_os = "macos"))]
{
for window in self.manager.windows().values() {
let has_app_wide_menu = window.has_app_wide_menu() || window.menu().is_none();
if has_app_wide_menu {
window.set_menu(menu.clone())?;
window.menu_lock().replace(crate::window::WindowMenu {
is_app_wide: true,
menu: menu.clone(),
});
}
}
}
#[cfg(target_os = "macos")]
{
let menu_ = menu.clone();
self.run_on_main_thread(move || {
let _ = init_app_menu(&menu_);
})?;
}
Ok(prev_menu)
}
#[cfg(desktop)]
pub fn remove_menu(&self) -> crate::Result<Option<Menu<R>>> {
let menu = self.manager.menu.menu_lock().as_ref().cloned();
#[allow(unused_variables)]
if let Some(menu) = menu {
#[cfg(not(target_os = "macos"))]
{
for window in self.manager.windows().values() {
let has_app_wide_menu = window.has_app_wide_menu();
if has_app_wide_menu {
window.remove_menu()?;
*window.menu_lock() = None;
}
}
}
#[cfg(target_os = "macos")]
{
self.run_on_main_thread(move || {
menu.inner().remove_for_nsapp();
})?;
}
}
let prev_menu = self.manager.menu.menu_lock().take();
self
.manager
.remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id()));
Ok(prev_menu)
}
#[cfg(desktop)]
pub fn hide_menu(&self) -> crate::Result<()> {
#[cfg(not(target_os = "macos"))]
{
let is_app_menu_set = self.manager.menu.menu_lock().is_some();
if is_app_menu_set {
for window in self.manager.windows().values() {
if window.has_app_wide_menu() {
window.hide_menu()?;
}
}
}
}
Ok(())
}
#[cfg(desktop)]
pub fn show_menu(&self) -> crate::Result<()> {
#[cfg(not(target_os = "macos"))]
{
let is_app_menu_set = self.manager.menu.menu_lock().is_some();
if is_app_menu_set {
for window in self.manager.windows().values() {
if window.has_app_wide_menu() {
window.show_menu()?;
}
}
}
}
Ok(())
}
#[cfg(target_os = "macos")]
pub fn show(&self) -> crate::Result<()> {
match self.runtime() {
RuntimeOrDispatch::Runtime(r) => r.show(),
RuntimeOrDispatch::RuntimeHandle(h) => h.show()?,
_ => unreachable!(),
}
Ok(())
}
#[cfg(target_os = "macos")]
pub fn hide(&self) -> crate::Result<()> {
match self.runtime() {
RuntimeOrDispatch::Runtime(r) => r.hide(),
RuntimeOrDispatch::RuntimeHandle(h) => h.hide()?,
_ => unreachable!(),
}
Ok(())
}
pub fn cleanup_before_exit(&self) {
#[cfg(all(desktop, feature = "tray-icon"))]
self.manager.tray.icons.lock().unwrap().clear();
self.manager.resources_table().clear();
for (_, window) in self.manager.windows() {
window.resources_table().clear();
#[cfg(windows)]
let _ = window.hide();
}
for (_, webview) in self.manager.webviews() {
webview.resources_table().clear();
}
}
}
impl<R: Runtime> Listener<R> for $app {
fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
where
F: Fn(Event) + Send + 'static,
{
self.manager.listen(event.into(), EventTarget::App, handler)
}
fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
where
F: FnOnce(Event) + Send + 'static,
{
self.manager.once(event.into(), EventTarget::App, handler)
}
fn unlisten(&self, id: EventId) {
self.manager.unlisten(id)
}
}
impl<R: Runtime> Emitter<R> for $app {
fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
self.manager.emit(event, payload)
}
fn emit_to<I, S>(&self, target: I, event: &str, payload: S) -> Result<()>
where
I: Into<EventTarget>,
S: Serialize + Clone,
{
self.manager.emit_to(target, event, payload)
}
fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
where
S: Serialize + Clone,
F: Fn(&EventTarget) -> bool,
{
self.manager.emit_filter(event, payload, filter)
}
}
};
}
shared_app_impl!(App<R>);
shared_app_impl!(AppHandle<R>);
impl<R: Runtime> App<R> {
#[cfg_attr(
feature = "tracing",
tracing::instrument(name = "app::core_plugins::register")
)]
fn register_core_plugins(&self) -> crate::Result<()> {
self.handle.plugin(crate::path::plugin::init())?;
self.handle.plugin(crate::event::plugin::init())?;
self.handle.plugin(crate::window::plugin::init())?;
self.handle.plugin(crate::webview::plugin::init())?;
self.handle.plugin(crate::app::plugin::init())?;
self.handle.plugin(crate::resources::plugin::init())?;
self.handle.plugin(crate::image::plugin::init())?;
#[cfg(desktop)]
self.handle.plugin(crate::menu::plugin::init())?;
#[cfg(all(desktop, feature = "tray-icon"))]
self.handle.plugin(crate::tray::plugin::init())?;
Ok(())
}
pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
self.app_handle().run_on_main_thread(f)
}
pub fn handle(&self) -> &AppHandle<R> {
&self.handle
}
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
pub fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
if let Some(runtime) = self.runtime.as_mut() {
runtime.set_activation_policy(activation_policy);
} else {
let _ = self.app_handle().set_activation_policy(activation_policy);
}
}
pub fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {
self
.runtime
.as_mut()
.unwrap()
.set_device_event_filter(filter);
}
pub fn run<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, mut callback: F) {
let app_handle = self.handle().clone();
let manager = self.manager.clone();
self.runtime.take().unwrap().run(move |event| match event {
RuntimeRunEvent::Ready => {
if let Err(e) = setup(&mut self) {
panic!("Failed to setup app: {e}");
}
let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Ready, &manager);
callback(&app_handle, event);
}
RuntimeRunEvent::Exit => {
let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Exit, &manager);
callback(&app_handle, event);
app_handle.cleanup_before_exit();
}
_ => {
let event = on_event_loop_event(&app_handle, event, &manager);
callback(&app_handle, event);
}
});
}
#[cfg(desktop)]
pub fn run_iteration<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(&mut self, mut callback: F) {
let manager = self.manager.clone();
let app_handle = self.handle().clone();
if !self.ran_setup {
if let Err(e) = setup(self) {
panic!("Failed to setup app: {e}");
}
}
self.runtime.as_mut().unwrap().run_iteration(move |event| {
let event = on_event_loop_event(&app_handle, event, &manager);
callback(&app_handle, event);
})
}
}
#[allow(clippy::type_complexity)]
pub struct Builder<R: Runtime> {
#[cfg(any(windows, target_os = "linux"))]
runtime_any_thread: bool,
invoke_handler: Box<InvokeHandler<R>>,
pub(crate) invoke_initialization_script: String,
setup: SetupHook<R>,
on_page_load: Option<Arc<OnPageLoad<R>>>,
plugins: PluginStore<R>,
uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
state: StateManager,
#[cfg(desktop)]
menu: Option<Box<dyn FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send>>,
#[allow(unused)]
enable_macos_default_menu: bool,
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
webview_event_listeners: Vec<GlobalWebviewEventListener<R>>,
device_event_filter: DeviceEventFilter,
pub(crate) invoke_key: String,
}
#[derive(Template)]
#[default_template("../scripts/ipc-protocol.js")]
pub(crate) struct InvokeInitializationScript<'a> {
#[raw]
pub(crate) process_ipc_message_fn: &'a str,
pub(crate) os_name: &'a str,
pub(crate) fetch_channel_data_command: &'a str,
pub(crate) invoke_key: &'a str,
}
#[cfg(feature = "wry")]
#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
impl Default for Builder<crate::Wry> {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(feature = "wry"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "wry"))))]
impl<R: Runtime> Default for Builder<R> {
fn default() -> Self {
Self::new()
}
}
impl<R: Runtime> Builder<R> {
pub fn new() -> Self {
let invoke_key = crate::generate_invoke_key().unwrap();
Self {
#[cfg(any(windows, target_os = "linux"))]
runtime_any_thread: false,
setup: Box::new(|_| Ok(())),
invoke_handler: Box::new(|_| false),
invoke_initialization_script: InvokeInitializationScript {
process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN,
os_name: std::env::consts::OS,
fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
invoke_key: &invoke_key.clone(),
}
.render_default(&Default::default())
.unwrap()
.into_string(),
on_page_load: None,
plugins: PluginStore::default(),
uri_scheme_protocols: Default::default(),
state: StateManager::new(),
#[cfg(desktop)]
menu: None,
enable_macos_default_menu: true,
window_event_listeners: Vec::new(),
webview_event_listeners: Vec::new(),
device_event_filter: Default::default(),
invoke_key,
}
}
}
impl<R: Runtime> Builder<R> {
#[cfg(any(windows, target_os = "linux"))]
#[cfg_attr(docsrs, doc(cfg(any(windows, target_os = "linux"))))]
#[must_use]
pub fn any_thread(mut self) -> Self {
self.runtime_any_thread = true;
self
}
#[must_use]
pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
where
F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
{
self.invoke_handler = Box::new(invoke_handler);
self
}
#[must_use]
pub fn invoke_system(mut self, initialization_script: String) -> Self {
self.invoke_initialization_script =
initialization_script.replace("__INVOKE_KEY__", &format!("\"{}\"", self.invoke_key));
self
}
pub fn append_invoke_initialization_script(
mut self,
initialization_script: impl AsRef<str>,
) -> Self {
self
.invoke_initialization_script
.push_str(initialization_script.as_ref());
self
}
#[cfg_attr(
feature = "unstable",
doc = r####"
```
use tauri::Manager;
tauri::Builder::default()
.setup(|app| {
let main_window = app.get_window("main").unwrap();
main_window.set_title("Tauri!")?;
Ok(())
});
```
"####
)]
#[must_use]
pub fn setup<F>(mut self, setup: F) -> Self
where
F: FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send + 'static,
{
self.setup = Box::new(setup);
self
}
#[must_use]
pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
where
F: Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static,
{
self.on_page_load.replace(Arc::new(on_page_load));
self
}
#[must_use]
pub fn plugin<P: Plugin<R> + 'static>(mut self, plugin: P) -> Self {
self.plugins.register(Box::new(plugin));
self
}
#[must_use]
pub fn manage<T>(self, state: T) -> Self
where
T: Send + Sync + 'static,
{
let type_name = std::any::type_name::<T>();
assert!(
self.state.set(state),
"state for type '{type_name}' is already being managed",
);
self
}
#[must_use]
#[cfg(desktop)]
pub fn menu<F: FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send + 'static>(
mut self,
f: F,
) -> Self {
self.menu.replace(Box::new(f));
self
}
#[must_use]
pub fn enable_macos_default_menu(mut self, enable: bool) -> Self {
self.enable_macos_default_menu = enable;
self
}
#[must_use]
pub fn on_window_event<F: Fn(&Window<R>, &WindowEvent) + Send + Sync + 'static>(
mut self,
handler: F,
) -> Self {
self.window_event_listeners.push(Box::new(handler));
self
}
#[must_use]
pub fn on_webview_event<F: Fn(&Webview<R>, &WebviewEvent) + Send + Sync + 'static>(
mut self,
handler: F,
) -> Self {
self.webview_event_listeners.push(Box::new(handler));
self
}
#[must_use]
pub fn register_uri_scheme_protocol<
N: Into<String>,
T: Into<Cow<'static, [u8]>>,
H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>) -> http::Response<T>
+ Send
+ Sync
+ 'static,
>(
mut self,
uri_scheme: N,
protocol: H,
) -> Self {
self.uri_scheme_protocols.insert(
uri_scheme.into(),
Arc::new(UriSchemeProtocol {
protocol: Box::new(move |ctx, request, responder| {
responder.respond(protocol(ctx, request))
}),
}),
);
self
}
#[must_use]
pub fn register_asynchronous_uri_scheme_protocol<
N: Into<String>,
H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync + 'static,
>(
mut self,
uri_scheme: N,
protocol: H,
) -> Self {
self.uri_scheme_protocols.insert(
uri_scheme.into(),
Arc::new(UriSchemeProtocol {
protocol: Box::new(protocol),
}),
);
self
}
pub fn device_event_filter(mut self, filter: DeviceEventFilter) -> Self {
self.device_event_filter = filter;
self
}
#[allow(clippy::type_complexity, unused_mut)]
#[cfg_attr(
feature = "tracing",
tracing::instrument(name = "app::build", skip_all)
)]
pub fn build(mut self, context: Context<R>) -> crate::Result<App<R>> {
#[cfg(target_os = "macos")]
if self.menu.is_none() && self.enable_macos_default_menu {
self.menu = Some(Box::new(|app_handle| {
crate::menu::Menu::default(app_handle)
}));
}
let manager = Arc::new(AppManager::with_handlers(
context,
self.plugins,
self.invoke_handler,
self.on_page_load,
self.uri_scheme_protocols,
self.state,
self.window_event_listeners,
self.webview_event_listeners,
#[cfg(desktop)]
HashMap::new(),
self.invoke_initialization_script,
self.invoke_key,
));
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
let app_id = if manager.config.app.enable_gtk_app_id {
Some(manager.config.identifier.clone())
} else {
None
};
let runtime_args = RuntimeInitArgs {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
app_id,
#[cfg(windows)]
msg_hook: {
let menus = manager.menu.menus.clone();
Some(Box::new(move |msg| {
use windows::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, HACCEL, MSG};
unsafe {
let msg = msg as *const MSG;
for menu in menus.lock().unwrap().values() {
let translated =
TranslateAcceleratorW((*msg).hwnd, HACCEL(menu.inner().haccel() as _), msg);
if translated == 1 {
return true;
}
}
false
}
}))
},
};
#[cfg(any(windows, target_os = "linux"))]
let mut runtime = if self.runtime_any_thread {
R::new_any_thread(runtime_args)?
} else {
R::new(runtime_args)?
};
#[cfg(not(any(windows, target_os = "linux")))]
let mut runtime = R::new(runtime_args)?;
#[cfg(desktop)]
{
let proxy = runtime.create_proxy();
muda::MenuEvent::set_event_handler(Some(move |e: muda::MenuEvent| {
let _ = proxy.send_event(EventLoopMessage::MenuEvent(e.into()));
}));
#[cfg(feature = "tray-icon")]
{
let proxy = runtime.create_proxy();
tray_icon::TrayIconEvent::set_event_handler(Some(move |e: tray_icon::TrayIconEvent| {
let _ = proxy.send_event(EventLoopMessage::TrayIconEvent(e.into()));
}));
}
}
runtime.set_device_event_filter(self.device_event_filter);
let runtime_handle = runtime.handle();
#[allow(unused_mut)]
let mut app = App {
runtime: Some(runtime),
setup: Some(self.setup),
manager: manager.clone(),
handle: AppHandle {
runtime_handle,
manager,
},
ran_setup: false,
};
#[cfg(desktop)]
if let Some(menu) = self.menu {
let menu = menu(&app.handle)?;
app
.manager
.menu
.menus_stash_lock()
.insert(menu.id().clone(), menu.clone());
#[cfg(target_os = "macos")]
init_app_menu(&menu)?;
app.manager.menu.menu_lock().replace(menu);
}
app.register_core_plugins()?;
let env = Env::default();
app.manage(env);
app.manage(Scopes {
#[cfg(feature = "protocol-asset")]
asset_protocol: crate::scope::fs::Scope::new(
&app,
&app.config().app.security.asset_protocol.scope,
)?,
});
app.manage(ChannelDataIpcQueue::default());
app.handle.plugin(crate::ipc::channel::plugin())?;
#[cfg(windows)]
{
if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } =
&app.manager.config().bundle.windows.webview_install_mode
{
if let Ok(resource_dir) = app.path().resource_dir() {
std::env::set_var(
"WEBVIEW2_BROWSER_EXECUTABLE_FOLDER",
resource_dir.join(path),
);
} else {
#[cfg(debug_assertions)]
eprintln!(
"failed to resolve resource directory; fallback to the installed Webview2 runtime."
);
}
}
}
let handle = app.handle();
#[cfg(all(desktop, feature = "tray-icon"))]
{
let config = app.config();
if let Some(tray_config) = &config.app.tray_icon {
let mut tray =
TrayIconBuilder::with_id(tray_config.id.clone().unwrap_or_else(|| "main".into()))
.icon_as_template(tray_config.icon_as_template)
.menu_on_left_click(tray_config.menu_on_left_click);
if let Some(icon) = &app.manager.tray.icon {
tray = tray.icon(icon.clone());
}
if let Some(title) = &tray_config.title {
tray = tray.title(title);
}
if let Some(tooltip) = &tray_config.tooltip {
tray = tray.tooltip(tooltip);
}
tray.build(handle)?;
}
}
app.manager.initialize_plugins(handle)?;
Ok(app)
}
pub fn run(self, context: Context<R>) -> crate::Result<()> {
self.build(context)?.run(|_, _| {});
Ok(())
}
}
pub(crate) type UriSchemeResponderFn = Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>;
pub struct UriSchemeResponder(pub(crate) UriSchemeResponderFn);
impl UriSchemeResponder {
pub fn respond<T: Into<Cow<'static, [u8]>>>(self, response: http::Response<T>) {
let (parts, body) = response.into_parts();
(self.0)(http::Response::from_parts(parts, body.into()))
}
}
pub struct UriSchemeContext<'a, R: Runtime> {
pub(crate) app_handle: &'a AppHandle<R>,
pub(crate) webview_label: &'a str,
}
impl<'a, R: Runtime> UriSchemeContext<'a, R> {
pub fn app_handle(&self) -> &'a AppHandle<R> {
self.app_handle
}
pub fn webview_label(&self) -> &'a str {
self.webview_label
}
}
#[cfg(target_os = "macos")]
fn init_app_menu<R: Runtime>(menu: &Menu<R>) -> crate::Result<()> {
menu.inner().init_for_nsapp();
if let Some(window_menu) = menu.get(crate::menu::WINDOW_SUBMENU_ID) {
if let Some(m) = window_menu.as_submenu() {
m.set_as_windows_menu_for_nsapp()?;
}
}
if let Some(help_menu) = menu.get(crate::menu::HELP_SUBMENU_ID) {
if let Some(m) = help_menu.as_submenu() {
m.set_as_help_menu_for_nsapp()?;
}
}
Ok(())
}
impl<R: Runtime> HasDisplayHandle for AppHandle<R> {
fn display_handle(
&self,
) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
self.runtime_handle.display_handle()
}
}
impl<R: Runtime> HasDisplayHandle for App<R> {
fn display_handle(
&self,
) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
self.handle.display_handle()
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(name = "app::setup"))]
fn setup<R: Runtime>(app: &mut App<R>) -> crate::Result<()> {
app.ran_setup = true;
for window_config in app.config().app.windows.iter().filter(|w| w.create) {
WebviewWindowBuilder::from_config(app.handle(), window_config)?.build()?;
}
app.manager.assets.setup(app);
if let Some(setup) = app.setup.take() {
(setup)(app).map_err(|e| crate::Error::Setup(e.into()))?;
}
Ok(())
}
fn on_event_loop_event<R: Runtime>(
app_handle: &AppHandle<R>,
event: RuntimeRunEvent<EventLoopMessage>,
manager: &AppManager<R>,
) -> RunEvent {
if let RuntimeRunEvent::WindowEvent {
label,
event: RuntimeWindowEvent::Destroyed,
} = &event
{
manager.on_window_close(label);
}
let event = match event {
RuntimeRunEvent::Exit => RunEvent::Exit,
RuntimeRunEvent::ExitRequested { code, tx } => RunEvent::ExitRequested {
code,
api: ExitRequestApi(tx),
},
RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
label,
event: event.into(),
},
RuntimeRunEvent::WebviewEvent { label, event } => RunEvent::WebviewEvent {
label,
event: event.into(),
},
RuntimeRunEvent::Ready => {
#[cfg(all(dev, target_os = "macos"))]
{
use objc2::ClassType;
use objc2_app_kit::{NSApplication, NSImage};
use objc2_foundation::{MainThreadMarker, NSData};
if let Some(icon) = app_handle.manager.app_icon.clone() {
let mtm = unsafe { MainThreadMarker::new_unchecked() };
let app = NSApplication::sharedApplication(mtm);
let data = NSData::with_bytes(&icon);
let app_icon = NSImage::initWithData(NSImage::alloc(), &data).expect("creating icon");
unsafe { app.setApplicationIconImage(Some(&app_icon)) };
}
}
RunEvent::Ready
}
RuntimeRunEvent::Resumed => RunEvent::Resumed,
RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared,
RuntimeRunEvent::UserEvent(t) => {
match t {
#[cfg(desktop)]
EventLoopMessage::MenuEvent(ref e) => {
for listener in &*app_handle
.manager
.menu
.global_event_listeners
.lock()
.unwrap()
{
listener(app_handle, e.clone());
}
for (label, listener) in &*app_handle.manager.menu.event_listeners.lock().unwrap() {
if let Some(w) = app_handle.manager().get_window(label) {
listener(&w, e.clone());
}
}
}
#[cfg(all(desktop, feature = "tray-icon"))]
EventLoopMessage::TrayIconEvent(ref e) => {
for listener in &*app_handle
.manager
.tray
.global_event_listeners
.lock()
.unwrap()
{
listener(app_handle, e.clone());
}
for (id, listener) in &*app_handle.manager.tray.event_listeners.lock().unwrap() {
if e.id() == id {
if let Some(tray) = app_handle.tray_by_id(id) {
listener(&tray, e.clone());
}
}
}
}
}
#[allow(unreachable_code)]
t.into()
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
RuntimeRunEvent::Opened { urls } => RunEvent::Opened { urls },
#[cfg(target_os = "macos")]
RuntimeRunEvent::Reopen {
has_visible_windows,
} => RunEvent::Reopen {
has_visible_windows,
},
_ => unimplemented!(),
};
manager
.plugins
.lock()
.expect("poisoned plugin store")
.on_event(app_handle, &event);
event
}
#[cfg(test)]
mod tests {
#[test]
fn is_send_sync() {
crate::test_utils::assert_send::<super::AppHandle>();
crate::test_utils::assert_sync::<super::AppHandle>();
#[cfg(feature = "wry")]
{
crate::test_utils::assert_send::<super::AssetResolver<crate::Wry>>();
crate::test_utils::assert_sync::<super::AssetResolver<crate::Wry>>();
}
}
}