Files
upstream-tauri/crates/tauri/src/app.rs
upstream-mirror feda81bf2f
Some checks failed
Audit / audit-js (push) Failing after 0s
bench / bench (map[os:ubuntu-latest runner:xvfb-run --auto-servernum target:x86_64-unknown-linux-gnu], nightly) (push) Failing after 0s
covector version or publish / run-integration-tests (ubuntu-latest) (push) Failing after 0s
Audit / audit-rust (push) Failing after 0s
test `@tauri-apps/cli` / test (ubuntu-latest) (push) Failing after 0s
test `tauri-cli` / test (map[os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
deploy schema worker / deploy (push) Failing after 0s
lint rust / clippy (map[cargo:cargo include: os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
lint rust / clippy (map[cargo:cross include:--package api os:ubuntu-latest target:aarch64-linux-android]) (push) Failing after 0s
supply chain health status / check rust dependencies with cargo vet (push) Failing after 0s
test `@tauri-apps/api` e2e (mobile) / android (push) Failing after 0s
test `@tauri-apps/api` e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
Udeps / changes (push) Failing after 0s
Udeps / setup (push) Has been skipped
Udeps / udeps (push) Has been skipped
bench / bench (map[os:macos-latest runner: target:aarch64-apple-darwin], nightly) (push) Has been cancelled
bench / bench (map[os:windows-latest runner: target:x86_64-pc-windows-msvc], nightly) (push) Has been cancelled
covector version or publish / run-integration-tests (macos-latest) (push) Has been cancelled
covector version or publish / run-integration-tests (windows-latest) (push) Has been cancelled
covector version or publish / version-or-publish (push) Has been cancelled
test `@tauri-apps/cli` / test (macos-latest) (push) Has been cancelled
test `tauri-cli` / test (map[args:--no-default-features --features native-tls-vendored os:windows-latest restore-only:true target:aarch64-pc-windows-msvc]) (push) Has been cancelled
test `tauri-cli` / test (map[os:macos-latest target:x86_64-apple-darwin]) (push) Has been cancelled
test `tauri-cli` / test (map[os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:macos-latest target:aarch64-apple-darwin]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include:--package api os:macos-latest target:aarch64-apple-ios]) (push) Has been cancelled
test `@tauri-apps/api` e2e (mobile) / ios (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
test `@tauri-apps/cli` / test (windows-latest) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
snapshot of https://github.com/tauri-apps/tauri.git (dev, 2026-09-23; blobs > 131072B omitted)
2026-09-23 17:00:42 +05:00

2843 lines
92 KiB
Rust

// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
Context, DeviceEventFilter, Emitter, EventLoopMessage, EventName, Listener, Manager, Monitor,
Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window,
image::Image,
ipc::{
CallbackFn, CommandArg, CommandItem, Invoke, InvokeError, InvokeHandler, InvokeResponseBody,
channel::ChannelDataIpcQueue,
},
manager::{AppManager, Asset, webview::UriSchemeProtocol},
plugin::{Plugin, PluginStore},
resources::ResourceTable,
runtime::{
ExitRequestedEventAction, RunEvent as RuntimeRunEvent,
window::{WebviewEvent as RuntimeWebviewEvent, WindowEvent as RuntimeWindowEvent},
},
sealed::{ManagerBase, RuntimeOrDispatch},
utils::{Env, config::Config},
webview::PageLoadPayload,
};
#[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::{DefaultTemplate, Template, default_template};
use tauri_macros::default_runtime;
#[cfg(desktop)]
use tauri_runtime::EventLoopProxy;
use tauri_runtime::{
RuntimeInitArgs,
dpi::{PhysicalPosition, PhysicalSize},
window::DragDropEvent,
};
use tauri_utils::{PackageInfo, assets::AssetsIter};
use std::{
borrow::Cow,
collections::HashMap,
fmt,
sync::{Arc, Mutex, MutexGuard, atomic, mpsc::Sender},
thread::ThreadId,
time::Duration,
};
use crate::{Event, EventTarget, event::EventId, runtime::RuntimeHandle};
#[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>;
/// A closure that is run when the Tauri application is setting up.
pub type SetupHook<R> =
Box<dyn FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send>;
/// A closure that is run every time a page starts or finishes loading.
pub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
/// A closure that is run when the web content process terminates.
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub type OnWebContentProcessTerminate<R> = dyn Fn(&Webview<R>) + Send + Sync + 'static;
pub type ChannelInterceptor<R> =
Box<dyn Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static>;
/// The exit code on [`RunEvent::ExitRequested`] when [`AppHandle#method.restart`] is called.
pub const RESTART_EXIT_CODE: i32 = i32::MAX;
/// Api exposed on the `ExitRequested` event.
#[derive(Debug, Clone)]
pub struct ExitRequestApi {
tx: Sender<ExitRequestedEventAction>,
code: Option<i32>,
}
impl ExitRequestApi {
/// Prevents the app from exiting.
///
/// **Note:** This is ignored when using [`AppHandle#method.restart`].
pub fn prevent_exit(&self) {
if self.code != Some(RESTART_EXIT_CODE) {
self.tx.send(ExitRequestedEventAction::Prevent).unwrap();
}
}
}
/// Api exposed on the `CloseRequested` event.
#[derive(Debug, Clone)]
pub struct CloseRequestApi(Sender<bool>);
impl CloseRequestApi {
/// Prevents the window from being closed.
pub fn prevent_close(&self) {
self.0.send(true).unwrap();
}
}
/// An event from a window.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum WindowEvent {
/// The size of the window has changed. Contains the client area's new dimensions.
Resized(PhysicalSize<u32>),
/// The position of the window has changed. Contains the window's new position.
Moved(PhysicalPosition<i32>),
/// The window has been requested to close.
#[non_exhaustive]
CloseRequested {
/// An API modify the behavior of the close requested event.
api: CloseRequestApi,
},
/// The window has been destroyed.
Destroyed,
/// The window gained or lost focus.
///
/// The parameter is true if the window has gained focus, and false if it has lost focus.
Focused(bool),
/// The window's scale factor has changed.
///
/// The following user actions can cause DPI changes:
///
/// - Changing the display's resolution.
/// - Changing the display's scale factor (e.g. in Control Panel on Windows).
/// - Moving the window to a display with a different scale factor.
#[non_exhaustive]
ScaleFactorChanged {
/// The new scale factor.
scale_factor: f64,
/// The window inner size.
new_inner_size: PhysicalSize<u32>,
},
/// An event associated with the drag and drop action.
DragDrop(DragDropEvent),
/// The system window theme has changed. Only delivered if the window [`theme`](`crate::window::WindowBuilder#method.theme`) is `None`.
///
/// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme.
///
/// ## Platform-specific
///
/// - **Linux**: Not supported.
ThemeChanged(Theme),
/// Emitted when the application has been suspended.
///
/// ## Platform-specific
///
/// - **Android**: This is triggered by `onPause` method of the Activity.
/// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate.
/// - **Linux / macOS / Windows**: Unsupported.
#[cfg(mobile)]
Suspended,
/// Emitted when the application has been resumed.
///
/// ## Platform-specific
///
/// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation.
/// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate.
/// - **Linux / macOS / Windows**: Unsupported.
#[cfg(mobile)]
Resumed,
}
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),
#[cfg(mobile)]
RuntimeWindowEvent::Suspended => Self::Suspended,
#[cfg(mobile)]
RuntimeWindowEvent::Resumed => Self::Resumed,
}
}
}
/// An event from a window.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum WebviewEvent {
/// An event associated with the drag and drop action.
DragDrop(DragDropEvent),
}
impl From<RuntimeWebviewEvent> for WebviewEvent {
fn from(event: RuntimeWebviewEvent) -> Self {
match event {
RuntimeWebviewEvent::DragDrop(e) => Self::DragDrop(e),
}
}
}
/// An application event, triggered from the event loop.
///
/// See [`App::run`](crate::App#method.run) for usage examples.
#[derive(Debug)]
#[non_exhaustive]
pub enum RunEvent {
/// Event loop is exiting.
Exit,
/// The app is about to exit
#[non_exhaustive]
ExitRequested {
/// Exit code.
/// [`Option::None`] when the exit is requested by user interaction,
/// [`Option::Some`] when requested programmatically via [`AppHandle#method.exit`] and [`AppHandle#method.restart`].
code: Option<i32>,
/// Event API
api: ExitRequestApi,
},
/// An event associated with a window.
#[non_exhaustive]
WindowEvent {
/// The window label.
label: String,
/// The detailed event.
event: WindowEvent,
},
/// An event associated with a webview.
#[non_exhaustive]
WebviewEvent {
/// The window label.
label: String,
/// The detailed event.
event: WebviewEvent,
},
/// Application ready.
Ready,
/// Sent if the event loop is being resumed.
Resumed,
/// Emitted when all of the event loop's input events have been processed and redraw processing is about to begin.
///
/// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the "main body" of your event loop.
MainEventsCleared,
/// Emitted when the user wants to open the specified resource with the app.
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
#[cfg_attr(
docsrs,
doc(cfg(any(target_os = "macos", target_os = "ios", target_os = "android")))
)]
Opened {
/// The URL of the resources that is being open.
urls: Vec<url::Url>,
},
/// An event from a menu item, could be on the window menu bar, application menu bar (on macOS) or tray icon menu.
#[cfg(desktop)]
#[cfg_attr(docsrs, doc(cfg(desktop)))]
MenuEvent(crate::menu::MenuEvent),
/// An event from a tray icon.
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
TrayIconEvent(crate::tray::TrayIconEvent),
/// Emitted when the NSApplicationDelegate's applicationShouldHandleReopen gets called
#[non_exhaustive]
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
Reopen {
/// Indicates whether the NSApplication object found any visible windows in your application.
has_visible_windows: bool,
},
/// Emitted when a scene is requested by the system.
///
/// This event is emitted when a scene is requested by the system.
/// Scenes created by [`Window::new`] are not emitted with this event.
/// It is also not emitted for the main scene.
#[cfg(target_os = "ios")]
#[cfg_attr(docsrs, doc(cfg(target_os = "ios")))]
SceneRequested {
/// Scene that was requested by the system.
scene: objc2::rc::Retained<objc2_ui_kit::UIScene>,
/// Options that were used to request the scene.
///
/// This lets you determine why the scene was requested.
options: objc2::rc::Retained<objc2_ui_kit::UISceneConnectionOptions>,
},
}
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),
}
}
}
/// The asset resolver is a helper to access the [`crate::Assets`] interface.
#[derive(Debug, Clone)]
pub struct AssetResolver<R: Runtime> {
manager: Arc<AppManager<R>>,
}
impl<R: Runtime> AssetResolver<R> {
/// Gets the app asset associated with the given path.
///
/// By default it tries to infer your application's URL scheme in production by checking if all webviews
/// were configured with [`crate::webview::WebviewBuilder::use_https_scheme`] or `tauri.conf.json > app > windows > useHttpsScheme`.
/// If you are resolving an asset for a webview with a more dynamic configuration, see [`AssetResolver::get_for_scheme`].
///
/// In production, this resolves to the embedded asset bundled in the app executable
/// which contains your frontend assets in [`frontendDist`](https://v2.tauri.app/reference/config/#frontenddist) during build time.
///
/// In dev mode, if [`devUrl`](https://v2.tauri.app/reference/config/#devurl) is set, we don't bundle the assets to reduce re-builds,
/// and this will fall back to read from `frontendDist` directly.
/// Note that the dist directory must exist so you might need to build your frontend assets first.
pub fn get(&self, path: String) -> Option<Asset> {
let use_https_scheme = self
.manager
.webviews()
.values()
.all(|webview| webview.use_https_scheme());
self.get_for_scheme(path, use_https_scheme)
}
/// Same as [`AssetResolver::get`] but resolves the custom protocol scheme based on a parameter.
///
/// - `use_https_scheme`: If `true` when using [`Pattern::Isolation`](crate::Pattern::Isolation),
/// the csp header will contain `https://tauri.localhost` instead of `http://tauri.localhost`
pub fn get_for_scheme(&self, path: String, use_https_scheme: bool) -> Option<Asset> {
#[cfg(dev)]
{
// We don't bundle the assets when in dev mode and `devUrl` is set, so fall back to read from `frontendDist` directly
// TODO: Maybe handle `FrontendDist::Files` as well
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, use_https_scheme).ok()
}
/// Iterate on all assets.
pub fn iter(&self) -> Box<AssetsIter<'_>> {
self.manager.assets.iter()
}
}
/// A handle to the currently running application.
///
/// This type implements [`Manager`] which allows for manipulation of global application items.
#[default_runtime(crate::Wry, wry)]
#[derive(Debug)]
pub struct AppHandle<R: Runtime> {
pub(crate) runtime_handle: R::Handle,
pub(crate) manager: Arc<AppManager<R>>,
event_loop: Arc<Mutex<EventLoop>>,
}
/// Not the real event loop, only contains the main thread id of the event loop
#[derive(Debug)]
struct EventLoop {
main_thread_id: ThreadId,
}
/// APIs specific to the wry runtime.
#[cfg(feature = "wry")]
impl AppHandle<crate::Wry> {
/// Create a new tao window using a callback. The event loop must be running at this point.
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)
}
/// Sends a window message to the event loop.
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)
}
}
#[cfg(target_vendor = "apple")]
impl<R: Runtime> AppHandle<R> {
/// Fetches all Data Store Identifiers by this app
///
/// Needs to be called from Main Thread
pub async fn fetch_data_store_identifiers(&self) -> crate::Result<Vec<[u8; 16]>> {
let (tx, rx) = tokio::sync::oneshot::channel::<Result<Vec<[u8; 16]>, tauri_runtime::Error>>();
let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
let runtime_handle = self.runtime_handle.clone();
self.run_on_main_thread(move || {
let cloned_lock = lock.clone();
if let Err(err) = runtime_handle.fetch_data_store_identifiers(move |ids| {
if let Some(tx) = cloned_lock.lock().unwrap().take() {
let _ = tx.send(Ok(ids));
}
}) {
if let Some(tx) = lock.lock().unwrap().take() {
let _ = tx.send(Err(err));
}
}
})?;
rx.await?.map_err(Into::into)
}
/// Deletes a Data Store of this app
///
/// Needs to be called from Main Thread
pub async fn remove_data_store(&self, uuid: [u8; 16]) -> crate::Result<()> {
let (tx, rx) = tokio::sync::oneshot::channel::<Result<(), tauri_runtime::Error>>();
let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
let runtime_handle = self.runtime_handle.clone();
self.run_on_main_thread(move || {
let cloned_lock = lock.clone();
if let Err(err) = runtime_handle.remove_data_store(uuid, move |result| {
if let Some(tx) = cloned_lock.lock().unwrap().take() {
let _ = tx.send(result);
}
}) {
if let Some(tx) = lock.lock().unwrap().take() {
let _ = tx.send(Err(err));
}
}
})?;
rx.await?.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(),
event_loop: self.event_loop.clone(),
}
}
}
impl<'de, R: Runtime> CommandArg<'de, R> for AppHandle<R> {
/// Grabs the [`Window`] from the [`CommandItem`] and returns the associated [`AppHandle`]. This will never fail.
fn from_command(command: CommandItem<'de, R>) -> std::result::Result<Self, InvokeError> {
Ok(command.message.webview().app_handle)
}
}
impl<R: Runtime> AppHandle<R> {
/// Runs the given closure on the main thread.
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)
}
/// Adds a Tauri application plugin.
/// This function can be used to register a plugin that is loaded dynamically e.g. after login.
/// For plugins that are created when the app is started, prefer [`Builder::plugin`].
///
/// See [`Builder::plugin`] for more information.
///
/// # Examples
///
/// ```
/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};
///
/// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {
/// PluginBuilder::new("dummy").build()
/// }
///
/// tauri::Builder::default()
/// .setup(move |app| {
/// let handle = app.handle().clone();
/// std::thread::spawn(move || {
/// handle.plugin(init_plugin());
/// });
///
/// Ok(())
/// });
/// ```
pub fn plugin<P: Plugin<R> + 'static>(&self, plugin: P) -> crate::Result<()> {
self.plugin_boxed(Box::new(plugin))
}
/// Adds a Tauri application plugin.
///
/// This method is similar to [`Self::plugin`],
/// but accepts a boxed trait object instead of a generic type.
#[cfg_attr(feature = "tracing", tracing::instrument(name = "app::plugin::register", skip(plugin), fields(name = plugin.name())))]
pub fn plugin_boxed(&self, mut plugin: Box<dyn Plugin<R>>) -> crate::Result<()> {
let mut store = self.manager().plugins.lock().unwrap();
store.initialize(&mut plugin, self, &self.config().plugins)?;
store.register(plugin);
Ok(())
}
/// Removes the plugin with the given name.
///
/// # Examples
///
/// ```
/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin, Plugin}, Runtime};
///
/// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {
/// PluginBuilder::new("dummy").build()
/// }
///
/// let plugin = init_plugin();
/// // `.name()` requires the `Plugin` trait import
/// let plugin_name = plugin.name();
/// tauri::Builder::default()
/// .plugin(plugin)
/// .setup(move |app| {
/// let handle = app.handle().clone();
/// std::thread::spawn(move || {
/// handle.remove_plugin(plugin_name);
/// });
///
/// Ok(())
/// });
/// ```
pub fn remove_plugin(&self, plugin: &str) -> bool {
self.manager().plugins.lock().unwrap().unregister(plugin)
}
/// Exits the app by triggering [`RunEvent::ExitRequested`] and [`RunEvent::Exit`].
///
/// ## Platform-specific
///
/// - **Android**: Unless the exit is prevented, the activity is finished instead of the process being exited directly,
/// so the app closes with the system transition and the activity lifecycle callbacks run.
/// [`RunEvent::Exit`] is triggered once the activity is destroyed, and `exit_code` is not used as the process exit code.
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);
}
}
/// Finishes the Android activity, and any other activity of its task, so the app closes gracefully.
#[cfg(target_os = "android")]
pub(crate) fn finish_activity(&self) -> crate::Result<()> {
// the app plugin is a core plugin, so its Android handle is always managed
self
.state::<crate::app::plugin::AppPlugin<R>>()
.0
.run_mobile_plugin::<()>("exit", ())?;
Ok(())
}
/// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`](crate::RESTART_EXIT_CODE) and [`RunEvent::Exit`].
///
/// When this function is called on the main thread, we cannot guarantee the delivery of those events,
/// so we skip them and directly restart the process.
///
/// If you want to trigger them reliably, use [`Self::request_restart`] instead
pub fn restart(&self) -> ! {
if self.event_loop.lock().unwrap().main_thread_id == std::thread::current().id() {
log::debug!("restart triggered on the main thread");
self.cleanup_before_exit();
crate::process::restart(&self.env());
} else {
log::debug!("restart triggered from a separate thread");
// we're running on a separate thread, so we must trigger the exit request and wait for it to finish
self
.manager
.restart_on_exit
.store(true, atomic::Ordering::Relaxed);
// We'll be restarting when we receive the next `RuntimeRunEvent::Exit` event in `App::run` if this call succeed
match self.runtime_handle.request_exit(RESTART_EXIT_CODE) {
Ok(()) => loop {
std::thread::sleep(Duration::MAX);
},
Err(e) => {
log::error!("failed to request exit: {e}");
self.cleanup_before_exit();
crate::process::restart(&self.env());
}
}
}
}
/// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`] and [`RunEvent::Exit`].
pub fn request_restart(&self) {
self
.manager
.restart_on_exit
.store(true, atomic::Ordering::Relaxed);
// We'll be restarting when we receive the next `RuntimeRunEvent::Exit` event in `App::run` if this call succeed
if self.runtime_handle.request_exit(RESTART_EXIT_CODE).is_err() {
self.cleanup_before_exit();
crate::process::restart(&self.env());
}
}
/// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default.
///
/// # Examples
/// ```,no_run
/// tauri::Builder::default()
/// .setup(move |app| {
/// #[cfg(target_os = "macos")]
/// app.handle().set_activation_policy(tauri::ActivationPolicy::Accessory);
/// Ok(())
/// });
/// ```
#[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)
}
/// Sets the dock visibility for the application.
///
/// # Examples
/// ```,no_run
/// tauri::Builder::default()
/// .setup(move |app| {
/// #[cfg(target_os = "macos")]
/// app.handle().set_dock_visibility(false);
/// Ok(())
/// });
/// ```
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
pub fn set_dock_visibility(&self, visible: bool) -> crate::Result<()> {
self
.runtime_handle
.set_dock_visibility(visible)
.map_err(Into::into)
}
/// Change the device event filter mode.
///
/// See [App::set_device_event_filter] for details.
///
/// ## Platform-specific
///
/// See [App::set_device_event_filter] for details.
pub fn set_device_event_filter(&self, filter: DeviceEventFilter) {
self.runtime_handle.set_device_event_filter(filter);
}
/// Whether the application supports multiple windows.
#[cfg(target_os = "ios")]
pub fn supports_multiple_windows(&self) -> bool {
let (tx, rx) = std::sync::mpsc::channel();
let _ = self.run_on_main_thread(move || {
let mtm = objc2::MainThreadMarker::new().unwrap();
let ui_application = objc2_ui_kit::UIApplication::sharedApplication(mtm);
tx.send(ui_application.supportsMultipleScenes()).unwrap();
});
rx.recv().unwrap()
}
}
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
}
#[cfg(target_os = "android")]
fn activity_name(&self) -> Option<crate::Result<String>> {
None
}
#[cfg(target_os = "ios")]
fn scene_identifier(&self) -> Option<crate::Result<String>> {
None
}
}
/// The instance of the currently running application.
///
/// This type implements [`Manager`] which allows for manipulation of global application items.
#[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(target_os = "android")]
fn activity_name(&self) -> Option<crate::Result<String>> {
None
}
#[cfg(target_os = "ios")]
fn scene_identifier(&self) -> Option<crate::Result<String>> {
None
}
}
/// APIs specific to the wry runtime.
#[cfg(feature = "wry")]
impl App<crate::Wry> {
/// Adds a [`tauri_runtime_wry::Plugin`] using its [`tauri_runtime_wry::PluginBuilder`].
///
/// # Stability
///
/// This API is unstable.
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 {
/// Registers a global menu event listener.
#[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)
}
/// Registers a global tray icon menu event listener.
#[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)
}
/// Gets a tray icon using the provided id.
#[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(self.app_handle(), id)
}
/// Removes a tray icon using the provided id from tauri's internal state and returns it.
///
/// Note that dropping the returned icon, may cause the tray icon to disappear
/// if it wasn't cloned somewhere else or referenced by JS.
#[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(self.app_handle(), id)
}
/// Gets the app's configuration, defined on the `tauri.conf.json` file.
pub fn config(&self) -> &Config {
self.manager.config()
}
/// Gets the app's package information.
pub fn package_info(&self) -> &PackageInfo {
self.manager.package_info()
}
/// The application's asset resolver.
pub fn asset_resolver(&self) -> AssetResolver<R> {
AssetResolver {
manager: self.manager.clone(),
}
}
/// Returns the primary monitor of the system.
///
/// Returns None if it can't identify any monitor as a primary one.
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!(),
})
}
/// Returns the monitor that contains the given point.
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!(),
})
}
/// Returns the list of all the monitors available on the system.
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!(),
})
}
/// Get the cursor position relative to the top-left hand corner of the desktop.
///
/// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.
/// If the user uses a desktop with multiple monitors,
/// the top-left hand corner of the desktop is the top-left hand corner of the main monitor on Windows and macOS
/// or the top-left of the leftmost monitor on X11.
///
/// The coordinates can be negative if the top-left hand corner of the window is outside of the visible screen region.
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!(),
})
}
/// Sets the app theme.
///
/// ## Platform-specific
///
/// - **iOS / Android:** Unsupported.
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!(),
}
}
/// Returns the default window icon.
pub fn default_window_icon(&self) -> Option<&Image<'_>> {
self.manager.window.default_icon.as_ref()
}
/// Returns the app-wide menu.
#[cfg(desktop)]
pub fn menu(&self) -> Option<Menu<R>> {
self.manager.menu.menu_lock().clone()
}
/// Sets the app-wide menu and returns the previous one.
///
/// If a window was not created with an explicit menu or had one set explicitly,
/// this menu will be assigned to it.
#[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());
// set it on all windows that don't have one or previously had the app-wide menu
#[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(),
});
}
}
}
// set it app-wide for macos
#[cfg(target_os = "macos")]
{
let menu_ = menu.clone();
self.run_on_main_thread(move || {
let _ = init_app_menu(&menu_);
})?;
}
Ok(prev_menu)
}
/// Remove the app-wide menu and returns it.
///
/// If a window was not created with an explicit menu or had one set explicitly,
/// this will remove the menu from it.
#[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 {
// remove from windows that have the app-wide 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;
}
}
}
// remove app-wide for macos
#[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)
}
/// Hides the app-wide menu from windows that have it.
///
/// If a window was not created with an explicit menu or had one set explicitly,
/// this will hide the menu from it.
///
/// ## Platform-specific:
///
/// - **macOS:** Unsupported.
#[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(())
}
/// Shows the app-wide menu for windows that have it.
///
/// If a window was not created with an explicit menu or had one set explicitly,
/// this will show the menu for it.
///
/// ## Platform-specific:
///
/// - **macOS:** Unsupported.
#[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(())
}
/// Shows the application, but does not automatically focus it.
#[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(())
}
/// Hides the application.
#[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(())
}
/// Runs necessary cleanup tasks before exiting the process.
/// **You should always exit the tauri app immediately after this function returns and not use any tauri-related APIs.**
pub fn cleanup_before_exit(&self) {
// run plugin cleanup hooks first so plugins can still use the app resources (e.g. stop sidecars)
// cleanup is best-effort, so a plugin store poisoned by an earlier panic must not abort it
self
.manager
.plugins
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.cleanup_before_exit(self.app_handle());
#[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();
}
}
/// Gets the invoke key that must be referenced when using [`crate::webview::InvokeRequest`].
///
/// # Security
///
/// DO NOT expose this key to third party scripts as might grant access to the backend from external URLs and iframes.
pub fn invoke_key(&self) -> &str {
self.manager.invoke_key()
}
/// Whether the application supports multiple windows.
#[cfg(desktop)]
pub fn supports_multiple_windows(&self) -> bool {
true
}
/// Whether the application supports multiple windows.
#[cfg(target_os = "android")]
pub fn supports_multiple_windows(&self) -> bool {
let runtime_handle = match self.runtime() {
RuntimeOrDispatch::Runtime(runtime) => runtime.handle(),
RuntimeOrDispatch::RuntimeHandle(handle) => handle,
_ => unreachable!(),
};
let (tx, rx) = std::sync::mpsc::channel();
runtime_handle.run_on_android_context(move |env, _activity, _webview| {
let supports = (|| {
let version_class = env.find_class("android/os/Build$VERSION")?;
let sdk = env
.get_static_field(version_class, "SDK_INT", "I")?
.i()
.unwrap_or_default();
crate::Result::Ok(sdk >= 32)
})()
.unwrap_or(false);
let _ = tx.send(supports);
});
rx.recv().unwrap_or(false)
}
}
impl<R: Runtime> Listener<R> for $app {
/// Listen to an event on this app.
///
/// # Examples
///
/// ```
/// use tauri::Listener;
///
/// tauri::Builder::default()
/// .setup(|app| {
/// app.listen("component-loaded", move |event| {
/// println!("window just loaded a component");
/// });
///
/// Ok(())
/// });
/// ```
fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
where
F: Fn(Event) + Send + 'static,
{
let event = EventName::new(event.into()).unwrap();
self.manager.listen(event, EventTarget::App, handler)
}
/// Listen to an event on this app only once.
///
/// See [`Self::listen`] for more information.
fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
where
F: FnOnce(Event) + Send + 'static,
{
let event = EventName::new(event.into()).unwrap();
self.manager.once(event, EventTarget::App, handler)
}
/// Unlisten to an event on this app.
///
/// # Examples
///
/// ```
/// use tauri::Listener;
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let handler = app.listen("component-loaded", move |event| {
/// println!("app just loaded a component");
/// });
///
/// // stop listening to the event when you do not need it anymore
/// app.unlisten(handler);
///
/// Ok(())
/// });
/// ```
fn unlisten(&self, id: EventId) {
self.manager.unlisten(id)
}
}
impl<R: Runtime> Emitter<R> for $app {}
};
}
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))?;
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(())
}
/// Runs the given closure on the main thread.
pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
self.app_handle().run_on_main_thread(f)
}
/// Gets a handle to the application instance.
pub fn handle(&self) -> &AppHandle<R> {
&self.handle
}
/// Whether the application supports multiple windows.
#[cfg(target_os = "ios")]
pub fn supports_multiple_windows(&self) -> bool {
let mtm = objc2::MainThreadMarker::new().unwrap();
let ui_application = objc2_ui_kit::UIApplication::sharedApplication(mtm);
ui_application.supportsMultipleScenes()
}
/// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default.
///
/// # Examples
/// ```,no_run
/// tauri::Builder::default()
/// .setup(move |app| {
/// #[cfg(target_os = "macos")]
/// app.set_activation_policy(tauri::ActivationPolicy::Accessory);
/// Ok(())
/// });
/// ```
#[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);
}
}
/// Sets the dock visibility for the application.
///
/// # Examples
/// ```,no_run
/// tauri::Builder::default()
/// .setup(move |app| {
/// #[cfg(target_os = "macos")]
/// app.set_dock_visibility(false);
/// Ok(())
/// });
/// ```
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
pub fn set_dock_visibility(&mut self, visible: bool) {
if let Some(runtime) = self.runtime.as_mut() {
runtime.set_dock_visibility(visible);
} else {
let _ = self.app_handle().set_dock_visibility(visible);
}
}
/// Change the device event filter mode.
///
/// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`]
/// will ignore them by default for unfocused windows on Windows. This method allows changing
/// the filter to explicitly capture them again.
///
/// ## Platform-specific
///
/// - ** Linux / macOS / iOS / Android**: Unsupported.
///
/// # Examples
/// ```,no_run
/// let mut app = tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("error while building tauri application");
/// app.set_device_event_filter(tauri::DeviceEventFilter::Always);
/// app.run(|_app_handle, _event| {});
/// ```
///
/// [`tao`]: https://crates.io/crates/tao
pub fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {
self
.runtime
.as_mut()
.unwrap()
.set_device_event_filter(filter);
}
/// Runs the application.
///
/// This function never returns. When the application finishes, the process is exited directly using [`std::process::exit`].
/// See [`run_return`](Self::run_return) if you need to run code after the application event loop exits.
///
/// # Panics
///
/// This function will panic if the setup-function supplied in [`Builder::setup`] fails.
///
/// # Examples
/// ```,no_run
/// let app = tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("error while building tauri application");
/// app.run(|_app_handle, event| match event {
/// tauri::RunEvent::ExitRequested { api, .. } => {
/// api.prevent_exit();
/// }
/// _ => {}
/// });
/// ```
pub fn run<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, callback: F) {
self.handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
self
.runtime
.take()
.unwrap()
.run(self.make_run_event_loop_callback(callback));
}
/// Runs the application, returning its intended exit code.
///
/// Note when using [`AppHandle::restart`] and [`AppHandle::request_restart`],
/// this function will handle the restart request, exit and restart the app without returning
///
/// ## Platform-specific
///
/// - **iOS**: Unsupported. The application will fallback to [`run`](Self::run).
///
/// # Panics
///
/// This function will panic if the setup-function supplied in [`Builder::setup`] fails.
///
/// # Examples
/// ```,no_run
/// let app = tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("error while building tauri application");
/// let exit_code = app
/// .run_return(|_app_handle, event| match event {
/// tauri::RunEvent::ExitRequested { api, .. } => {
/// api.prevent_exit();
/// }
/// _ => {}
/// });
///
/// std::process::exit(exit_code);
/// ```
pub fn run_return<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, callback: F) -> i32 {
self.handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
self
.runtime
.take()
.unwrap()
.run_return(self.make_run_event_loop_callback(callback))
}
fn make_run_event_loop_callback<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(
mut self,
mut callback: F,
) -> impl FnMut(RuntimeRunEvent<EventLoopMessage>) {
// whether the activity is being finished to fulfill an exit request the app accepted
#[cfg(target_os = "android")]
let mut finishing_activity = false;
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(self.handle(), RuntimeRunEvent::Ready, self.manager());
callback(self.handle(), event);
}
RuntimeRunEvent::Exit => {
let event = on_event_loop_event(self.handle(), RuntimeRunEvent::Exit, self.manager());
callback(self.handle(), event);
self.cleanup_before_exit();
if self.manager.restart_on_exit.load(atomic::Ordering::Relaxed) {
crate::process::restart(&self.env());
}
}
// On Android, exiting the process while the activity is still on screen skips the close transition
// and flashes a blank screen, so a programmatic exit finishes the activity instead and the event loop
// exits through the regular window destroyed path.
#[cfg(target_os = "android")]
RuntimeRunEvent::ExitRequested {
code: Some(code),
tx,
} if code != RESTART_EXIT_CODE && !self.manager.windows().is_empty() => {
let (app_tx, app_rx) = std::sync::mpsc::channel();
let event = on_event_loop_event(
self.handle(),
RuntimeRunEvent::ExitRequested {
code: Some(code),
tx: app_tx,
},
self.manager(),
);
callback(self.handle(), event);
if matches!(app_rx.try_recv(), Ok(ExitRequestedEventAction::Prevent)) {
let _ = tx.send(ExitRequestedEventAction::Prevent);
} else {
match self.handle().finish_activity() {
Ok(()) => {
finishing_activity = true;
// keep the event loop running until the activity is destroyed
let _ = tx.send(ExitRequestedEventAction::Prevent);
}
// fall back to exiting the process directly
Err(e) => log::error!("failed to finish the activity: {e}"),
}
}
}
#[cfg(target_os = "android")]
RuntimeRunEvent::ExitRequested { code: None, .. } if finishing_activity => {
// the app already accepted this exit when it was requested
}
_ => {
let event = on_event_loop_event(self.handle(), event, self.manager());
callback(self.handle(), event);
}
}
}
/// Runs an iteration of the runtime event loop and immediately return.
///
/// Note that when using this API, app cleanup is not automatically done.
/// The cleanup calls [`App::cleanup_before_exit`] so you may want to call that function before exiting the application.
///
/// # Examples
/// ```no_run
/// use tauri::Manager;
///
/// let mut app = tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("error while building tauri application");
///
/// loop {
/// app.run_iteration(|_app, _event| {});
/// if app.webview_windows().is_empty() {
/// app.cleanup_before_exit();
/// break;
/// }
/// }
/// ```
#[cfg(desktop)]
#[deprecated(
note = "When called in a loop (as suggested by the name), this function will busy-loop. To re-gain control of control flow after the app has exited, use `App::run_return` instead."
)]
pub fn run_iteration<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(&mut self, mut callback: F) {
if !self.ran_setup {
if let Err(e) = setup(self) {
panic!("Failed to setup app: {e}");
}
}
let app_handle = self.handle().clone();
app_handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
self.runtime.as_mut().unwrap().run_iteration(move |event| {
let event = on_event_loop_event(&app_handle, event, app_handle.manager());
callback(&app_handle, event);
})
}
}
/// Builds a Tauri application.
///
/// # Examples
/// ```,no_run
/// tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("error while running tauri application");
/// ```
#[allow(clippy::type_complexity)]
pub struct Builder<R: Runtime> {
/// A flag indicating that the runtime must be started on an environment that supports the event loop not on the main thread.
#[cfg(any(windows, target_os = "linux"))]
runtime_any_thread: bool,
/// The JS message handler.
invoke_handler: Box<InvokeHandler<R>>,
/// The script that initializes the `window.__TAURI_INTERNALS__.postMessage` function.
pub(crate) invoke_initialization_script: String,
channel_interceptor: Option<ChannelInterceptor<R>>,
/// The setup hook.
setup: SetupHook<R>,
/// Page load hook.
on_page_load: Option<Arc<OnPageLoad<R>>>,
/// Permission request hook.
on_permission_request: Option<Arc<crate::webview::PermissionRequestHandler<R>>>,
/// Web content process termination hook.
#[cfg(any(target_os = "macos", target_os = "ios"))]
on_web_content_process_terminate: Option<Arc<OnWebContentProcessTerminate<R>>>,
/// All passed plugins
plugins: PluginStore<R>,
/// The webview protocols available to all windows.
uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
/// App state.
state: StateManager,
/// A closure that returns the menu set to all windows.
#[cfg(desktop)]
menu: Option<Box<dyn FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send>>,
/// Menu event listeners for any menu event.
#[cfg(desktop)]
menu_event_listeners: Vec<GlobalMenuEventListener<AppHandle<R>>>,
/// Tray event listeners for any tray icon event.
#[cfg(all(desktop, feature = "tray-icon"))]
tray_icon_event_listeners: Vec<GlobalTrayIconEventListener<AppHandle<R>>>,
/// Enable macOS default menu creation.
#[allow(unused)]
enable_macos_default_menu: bool,
/// Whether to activate the application when launched while another application is active.
#[cfg(target_os = "macos")]
activate_ignoring_other_apps: bool,
/// Window event handlers that listens to all windows.
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
/// Webview event handlers that listens to all webviews.
webview_event_listeners: Vec<GlobalWebviewEventListener<R>>,
/// The device event filter.
device_event_filter: DeviceEventFilter,
pub(crate) invoke_key: String,
}
#[derive(Template)]
#[default_template("../scripts/ipc-protocol.js")]
pub(crate) struct InvokeInitializationScript<'a> {
/// The function that processes the IPC message.
#[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,
}
/// Make `Wry` the default `Runtime` for `Builder`
#[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> {
/// Creates a new App builder.
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(),
channel_interceptor: None,
on_page_load: None,
on_permission_request: None,
#[cfg(any(target_os = "macos", target_os = "ios"))]
on_web_content_process_terminate: None,
plugins: PluginStore::default(),
uri_scheme_protocols: Default::default(),
state: StateManager::new(),
#[cfg(desktop)]
menu: None,
#[cfg(desktop)]
menu_event_listeners: Vec::new(),
#[cfg(all(desktop, feature = "tray-icon"))]
tray_icon_event_listeners: Vec::new(),
enable_macos_default_menu: true,
#[cfg(target_os = "macos")]
activate_ignoring_other_apps: true,
window_event_listeners: Vec::new(),
webview_event_listeners: Vec::new(),
device_event_filter: Default::default(),
invoke_key,
}
}
}
impl<R: Runtime> Builder<R> {
/// Builds a new Tauri application running on any thread, bypassing the main thread requirement.
///
/// ## Platform-specific
///
/// - **macOS:** on macOS the application *must* be executed on the main thread, so this function is not exposed.
#[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
}
/// Defines the JS message handler callback.
///
/// # Examples
/// ```
/// #[tauri::command]
/// fn command_1() -> String {
/// return "hello world".to_string();
/// }
/// tauri::Builder::default()
/// .invoke_handler(tauri::generate_handler![
/// command_1,
/// // etc...
/// ]);
/// ```
#[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
}
/// Defines a custom JS message system.
///
/// The `initialization_script` is a script that initializes `window.__TAURI_INTERNALS__.postMessage`.
/// That function must take the `(message: object, options: object)` arguments and send it to the backend.
///
/// Additionally, the script must include a `__INVOKE_KEY__` token that is replaced with a value that must be sent with the IPC payload
/// to check the integrity of the message by the [`crate::WebviewWindow::on_message`] API, e.g.
///
/// ```js
/// const invokeKey = __INVOKE_KEY__;
/// fetch('my-impl://command', {
/// headers: {
/// 'Tauri-Invoke-Key': invokeKey,
/// }
/// })
/// ```
///
/// Note that the implementation details is up to your implementation.
#[must_use]
pub fn invoke_system(mut self, initialization_script: impl AsRef<str>) -> Self {
self.invoke_initialization_script = initialization_script
.as_ref()
.replace("__INVOKE_KEY__", &format!("\"{}\"", self.invoke_key));
self
}
/// Registers a channel interceptor that can overwrite the default channel implementation.
///
/// If the event has been consumed, it must return `true`.
///
/// The channel automatically orders the messages, so the third closure argument represents the message number.
/// The payload expected by the channel receiver is in the form of `{ id: usize, message: T }`.
pub fn channel_interceptor<
F: Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static,
>(
mut self,
interceptor: F,
) -> Self {
self.channel_interceptor.replace(Box::new(interceptor));
self
}
/// Append a custom initialization script.
///
/// Allow to append custom initialization script instead of replacing entire invoke system.
///
/// # Examples
///
/// ```
/// let custom_script = r#"
/// // A custom call system bridge build on top of tauri invoke system.
/// async function invoke(cmd, args = {}) {
/// if (!args) args = {};
///
/// let prefix = "";
///
/// if (args?.__module) {
/// prefix = `plugin:hybridcall.${args.__module}|`;
/// }
///
/// const command = `${prefix}tauri_${cmd}`;
///
/// const invoke = window.__TAURI_INTERNALS__.invoke;
///
/// return invoke(command, args).then(result => {
/// if (window.build.debug) {
/// console.log(`call: ${command}`);
/// console.log(`args: ${JSON.stringify(args)}`);
/// console.log(`return: ${JSON.stringify(result)}`);
/// }
///
/// return result;
/// });
/// }
/// "#;
///
/// tauri::Builder::default()
/// .append_invoke_initialization_script(custom_script);
/// ```
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
}
/// Defines the setup hook.
///
/// # Examples
#[cfg_attr(
feature = "unstable",
doc = r####"
```
use tauri::Manager;
tauri::Builder::default()
.setup(|app| {
let main_window = app.get_webview_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
}
/// Defines the page load hook.
#[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
}
/// Defines a closure to be executed when a permission is requested.
///
/// The handler receives the [`crate::webview::PermissionKind`] and should return
/// the desired [`crate::webview::PermissionResponse`].
///
/// This is not called if a [`crate::webview::WebviewBuilder::on_permission_request`]
/// is set on the webview and returned a response other than [`crate::webview::PermissionResponse::Default`].
///
/// > [!NOTE]
/// > This handler only triggers for new permission requests. If the user has already
/// > allowed or denied a permission persistently within the webview, the browser
/// > will use the saved preference instead of calling this handler.
///
/// ## Platform-specific:
///
/// - **Windows**: Fully supported via WebView2's PermissionRequested event.
/// - **macOS / iOS**: Fully supported via WKUIDelegate's requestMediaCapturePermission.
/// - **Linux**: Fully supported via WebKitGTK's permission-request signal.
/// - **Android**: Supported via JNI bridge for geolocation, microphone, camera,
/// protected media, and MIDI requests. Android runtime permissions may still
/// trigger native OS prompts before access is granted.
///
/// # Examples
///
/// ```rust,no_run
/// use tauri::webview::{PermissionKind, PermissionResponse};
/// tauri::Builder::default()
/// .on_permission_request(|_, kind| match kind {
/// PermissionKind::Geolocation => PermissionResponse::Allow,
/// PermissionKind::Notifications => PermissionResponse::Allow,
/// _ => PermissionResponse::Default,
/// });
/// ```
#[must_use]
pub fn on_permission_request<F>(mut self, on_permission_request: F) -> Self
where
F: Fn(Webview<R>, crate::webview::PermissionKind) -> crate::webview::PermissionResponse
+ Send
+ Sync
+ 'static,
{
self
.on_permission_request
.replace(Arc::new(on_permission_request));
self
}
/// Defines the web content process termination hook.
///
/// ## Platform-specific
///
/// - **Linux / Windows / Android:** Unsupported.
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[must_use]
pub fn on_web_content_process_terminate<F>(mut self, on_web_content_process_terminate: F) -> Self
where
F: Fn(&Webview<R>) + Send + Sync + 'static,
{
self
.on_web_content_process_terminate
.replace(Arc::new(on_web_content_process_terminate));
self
}
/// Adds a Tauri application plugin.
///
/// A plugin is created using the [`crate::plugin::Builder`] struct.Check its documentation for more information.
///
/// # Examples
///
/// ```
/// mod plugin {
/// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, RunEvent, Runtime};
///
/// // this command can be called in the frontend using `invoke('plugin:window|do_something')`.
/// #[tauri::command]
/// async fn do_something<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {
/// println!("command called");
/// Ok(())
/// }
/// pub fn init<R: Runtime>() -> TauriPlugin<R> {
/// PluginBuilder::new("window")
/// .setup(|app, api| {
/// // initialize the plugin here
/// Ok(())
/// })
/// .on_event(|app, event| {
/// match event {
/// RunEvent::Ready => {
/// println!("app is ready");
/// }
/// RunEvent::WindowEvent { label, event, .. } => {
/// println!("window {} received an event: {:?}", label, event);
/// }
/// _ => (),
/// }
/// })
/// .invoke_handler(tauri::generate_handler![do_something])
/// .build()
/// }
/// }
///
/// tauri::Builder::default()
/// .plugin(plugin::init());
/// ```
#[must_use]
pub fn plugin<P: Plugin<R> + 'static>(self, plugin: P) -> Self {
self.plugin_boxed(Box::new(plugin))
}
/// Adds a Tauri application plugin.
///
/// This method is similar to [`Self::plugin`],
/// but accepts a boxed trait object instead of a generic type.
#[must_use]
pub fn plugin_boxed(mut self, plugin: Box<dyn Plugin<R>>) -> Self {
self.plugins.register(plugin);
self
}
/// Add `state` to the state managed by the application.
///
/// This method can be called any number of times as long as each call
/// refers to a different `T`.
///
/// Managed state can be retrieved by any command handler via the
/// [`crate::State`] guard. In particular, if a value of type `T`
/// is managed by Tauri, adding `State<T>` to the list of arguments in a
/// command handler instructs Tauri to retrieve the managed value.
/// Additionally, [`state`](crate::Manager#method.state) can be used to retrieve the value manually.
///
/// # Panics
///
/// Panics if state of type `T` is already being managed.
///
/// # Mutability
///
/// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability:
///
/// ```,no_run
/// use std::{collections::HashMap, sync::Mutex};
/// use tauri::State;
/// // here we use Mutex to achieve interior mutability
/// struct Storage {
/// store: Mutex<HashMap<u64, String>>,
/// }
/// struct Connection;
/// struct DbConnection {
/// db: Mutex<Option<Connection>>,
/// }
///
/// #[tauri::command]
/// fn connect(connection: State<DbConnection>) {
/// // initialize the connection, mutating the state with interior mutability
/// *connection.db.lock().unwrap() = Some(Connection {});
/// }
///
/// #[tauri::command]
/// fn storage_insert(key: u64, value: String, storage: State<Storage>) {
/// // mutate the storage behind the Mutex
/// storage.store.lock().unwrap().insert(key, value);
/// }
///
/// tauri::Builder::default()
/// .manage(Storage { store: Default::default() })
/// .manage(DbConnection { db: Default::default() })
/// .invoke_handler(tauri::generate_handler![connect, storage_insert])
/// // on an actual app, remove the string argument
/// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("error while running tauri application");
/// ```
///
/// # Examples
///
/// ```,no_run
/// use tauri::State;
///
/// struct MyInt(isize);
/// struct MyString(String);
///
/// #[tauri::command]
/// fn int_command(state: State<MyInt>) -> String {
/// format!("The stateful int is: {}", state.0)
/// }
///
/// #[tauri::command]
/// fn string_command<'r>(state: State<'r, MyString>) {
/// println!("state: {}", state.inner().0);
/// }
///
/// tauri::Builder::default()
/// .manage(MyInt(10))
/// .manage(MyString("Hello, managed state!".to_string()))
/// .invoke_handler(tauri::generate_handler![int_command, string_command])
/// // on an actual app, remove the string argument
/// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("error while running tauri application");
/// ```
#[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
}
/// Sets the menu to use on all windows.
///
/// # Examples
/// ```
/// use tauri::menu::{Menu, MenuItem, PredefinedMenuItem, Submenu};
///
/// tauri::Builder::default()
/// .menu(|handle| Menu::with_items(handle, &[
/// &Submenu::with_items(
/// handle,
/// "File",
/// true,
/// &[
/// &PredefinedMenuItem::close_window(handle, None)?,
/// #[cfg(target_os = "macos")]
/// &MenuItem::new(handle, "Hello", true, None::<&str>)?,
/// ],
/// )?
/// ]));
/// ```
#[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
}
/// Registers an event handler for any menu event.
///
/// # Examples
/// ```
/// use tauri::menu::*;
///
/// tauri::Builder::default()
/// .on_menu_event(|app, event| {
/// if event.id() == "quit" {
/// app.exit(0);
/// }
/// });
/// ```
#[must_use]
#[cfg(desktop)]
pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(
mut self,
f: F,
) -> Self {
self.menu_event_listeners.push(Box::new(f));
self
}
/// Registers an event handler for any tray icon event.
///
/// # Examples
/// ```
/// use tauri::Manager;
///
/// tauri::Builder::default()
/// .on_tray_icon_event(|app, event| {
/// let tray = app.tray_by_id(event.id()).expect("can't find tray icon");
/// let _ = tray.set_visible(false);
/// });
/// ```
#[must_use]
#[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>(
mut self,
f: F,
) -> Self {
self.tray_icon_event_listeners.push(Box::new(f));
self
}
/// Enable or disable the default menu on macOS. Enabled by default.
///
/// # Examples
/// ```
/// tauri::Builder::default()
/// .enable_macos_default_menu(false);
/// ```
#[must_use]
pub fn enable_macos_default_menu(mut self, enable: bool) -> Self {
self.enable_macos_default_menu = enable;
self
}
/// Configures whether the application should activate when launched while another application
/// is active.
///
/// By default, the application ignores other applications and activates when launched.
///
/// If `false`, the application activates only if no other application is currently active.
/// If `true`, the application activates regardless.
///
/// This only affects the initial activation at launch. Explicitly focusing a window later
/// (e.g. via [`Window::set_focus`](crate::window::Window::set_focus)) still activates the
/// application regardless of this setting.
///
/// # Examples
/// ```,no_run
/// tauri::Builder::default()
/// .activate_ignoring_other_apps(false);
/// ```
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
#[must_use]
pub fn activate_ignoring_other_apps(mut self, ignore: bool) -> Self {
self.activate_ignoring_other_apps = ignore;
self
}
/// Registers a window event handler for all windows.
///
/// # Examples
/// ```
/// tauri::Builder::default()
/// .on_window_event(|window, event| match event {
/// tauri::WindowEvent::Focused(focused) => {
/// // hide window whenever it loses focus
/// if !focused {
/// window.hide().unwrap();
/// }
/// }
/// _ => {}
/// });
/// ```
#[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
}
/// Registers a webview event handler for all webviews.
///
/// # Examples
/// ```
/// tauri::Builder::default()
/// .on_webview_event(|window, event| match event {
/// tauri::WebviewEvent::DragDrop(event) => {
/// println!("{:?}", event);
/// }
/// _ => {}
/// });
/// ```
#[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
}
/// Registers a URI scheme protocol available to all webviews.
///
/// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
/// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
/// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
///
/// # Arguments
///
/// * `uri_scheme` The URI scheme to register, such as `example`.
/// * `protocol` the protocol associated with the given URI scheme. It's a function that takes a request and returns a response.
///
/// # Examples
/// ```
/// tauri::Builder::default()
/// .register_uri_scheme_protocol("app-files", |_ctx, request| {
/// // skip leading `/`
/// if let Ok(data) = std::fs::read(&request.uri().path()[1..]) {
/// http::Response::builder()
/// .body(data)
/// .unwrap()
/// } else {
/// http::Response::builder()
/// .status(http::StatusCode::BAD_REQUEST)
/// .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
/// .body("failed to read file".as_bytes().to_vec())
/// .unwrap()
/// }
/// });
/// ```
///
/// # Warning
///
/// Pages loaded from a custom protocol will have a different Origin on different platforms.
/// Servers which enforce CORS will need to add the exact same Origin header (or `*`) in `Access-Control-Allow-Origin`
/// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
/// different Origin headers across platforms:
///
/// - macOS, iOS and Linux: `<scheme_name>://localhost/<path>` (so it will be `my-scheme://localhost/path/to/page).
/// - Windows and Android: `http://<scheme_name>.localhost/<path>` by default (so it will be `http://my-scheme.localhost/path/to/page`).
/// To use `https` instead of `http`, use [`super::webview::WebviewBuilder::use_https_scheme`].
#[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_handler: H,
) -> Self {
self.uri_scheme_protocols.insert(
uri_scheme.into(),
Arc::new(UriSchemeProtocol {
handler: Box::new(move |ctx, request, responder| {
responder.respond(protocol_handler(ctx, request))
}),
}),
);
self
}
/// Similar to [`Self::register_uri_scheme_protocol`] but with an asynchronous responder that allows you
/// to process the request in a separate thread and respond asynchronously.
///
/// # Arguments
///
/// * `uri_scheme` The URI scheme to register, such as `example`.
/// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
///
/// # Examples
/// ```
/// tauri::Builder::default()
/// .register_asynchronous_uri_scheme_protocol("app-files", |_ctx, request, responder| {
/// // skip leading `/`
/// let path = request.uri().path()[1..].to_string();
/// std::thread::spawn(move || {
/// if let Ok(data) = std::fs::read(path) {
/// responder.respond(
/// http::Response::builder()
/// .body(data)
/// .unwrap()
/// );
/// } else {
/// responder.respond(
/// http::Response::builder()
/// .status(http::StatusCode::BAD_REQUEST)
/// .header(http::header::CONTENT_TYPE, mime::TEXT_PLAIN.essence_str())
/// .body("failed to read file".as_bytes().to_vec())
/// .unwrap()
/// );
/// }
/// });
/// });
/// ```
///
/// # Warning
///
/// Pages loaded from a custom protocol will have a different Origin on different platforms.
/// Servers which enforce CORS will need to add the exact same Origin header (or `*`) in `Access-Control-Allow-Origin`
/// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
/// different Origin headers across platforms:
///
/// - macOS, iOS and Linux: `<scheme_name>://localhost/<path>` (so it will be `my-scheme://localhost/path/to/page).
/// - Windows and Android: `http://<scheme_name>.localhost/<path>` by default (so it will be `http://my-scheme.localhost/path/to/page`).
/// To use `https` instead of `http`, use [`super::webview::WebviewBuilder::use_https_scheme`].
#[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_handler: H,
) -> Self {
self.uri_scheme_protocols.insert(
uri_scheme.into(),
Arc::new(UriSchemeProtocol {
handler: Box::new(protocol_handler),
}),
);
self
}
/// Change the device event filter mode.
///
/// Since the DeviceEvent capture can lead to high CPU usage for unfocused windows, [`tao`]
/// will ignore them by default for unfocused windows on Windows. This method allows changing
/// the filter to explicitly capture them again.
///
/// ## Platform-specific
///
/// - ** Linux / macOS / iOS / Android**: Unsupported.
///
/// # Examples
/// ```,no_run
/// tauri::Builder::default()
/// .device_event_filter(tauri::DeviceEventFilter::Always);
/// ```
///
/// [`tao`]: https://crates.io/crates/tao
pub fn device_event_filter(mut self, filter: DeviceEventFilter) -> Self {
self.device_event_filter = filter;
self
}
/// Builds the application.
///
/// This creates the underlying webview runtime (the event loop), registers the core plugins and
/// initializes everything defined on the builder, but it does not start the application:
/// call [`App::run`] or [`App::run_return`] on the returned value to do so, or use [`Self::run`]
/// to do both at once.
///
/// # Errors
///
/// Returns an error if the application cannot be initialized, in particular when:
///
/// - the webview runtime (event loop) cannot be created, for instance when it is created outside
/// the main thread, when the display server cannot be reached or when the WebView2 runtime is
/// missing on Windows;
/// - the `app > security > assetProtocol > scope` patterns in the configuration are invalid;
/// - the app menu defined with [`Self::menu`] cannot be created;
/// - the tray icon defined in the configuration cannot be created;
/// - a plugin fails to initialize, i.e. its [`Plugin::initialize`](crate::plugin::Plugin::initialize)
/// hook returns an error, which is also the case when a plugin is registered twice.
///
/// # Examples
///
/// ```rust,no_run
/// tauri::Builder::default()
/// // on an actual app, remove the string argument
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
/// .expect("error while building tauri application")
/// .run(|_app_handle, _event| {});
/// ```
#[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.on_permission_request,
#[cfg(any(target_os = "macos", target_os = "ios"))]
self.on_web_content_process_terminate,
self.uri_scheme_protocols,
self.state,
#[cfg(desktop)]
self.menu_event_listeners,
#[cfg(all(desktop, feature = "tray-icon"))]
self.tray_icon_event_listeners,
self.window_event_listeners,
self.webview_event_listeners,
#[cfg(desktop)]
HashMap::new(),
self.invoke_initialization_script,
self.channel_interceptor,
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::{HACCEL, MSG, TranslateAcceleratorW};
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
}
}))
},
};
// The env var must be set before the Runtime is created so that GetAvailableBrowserVersionString picks it up.
#[cfg(windows)]
{
if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } =
&manager.config.bundle.windows.webview_install_mode
{
if let Some(exe_dir) = crate::utils::platform::current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
{
// SAFETY: Always safe on Windows
unsafe { std::env::set_var("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", exe_dir.join(path)) };
} else {
#[cfg(debug_assertions)]
eprintln!(
"failed to resolve resource directory; fallback to the installed Webview2 runtime."
);
}
}
}
#[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(target_os = "macos")]
runtime.set_activate_ignoring_other_apps(self.activate_ignoring_other_apps);
#[cfg(desktop)]
{
// setup menu event handler
let proxy = runtime.create_proxy();
muda::MenuEvent::set_event_handler(Some(move |e: muda::MenuEvent| {
let _ = proxy.send_event(EventLoopMessage::MenuEvent(e.into()));
}));
// setup tray event handler
#[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();
let app = App {
runtime: Some(runtime),
setup: Some(self.setup),
manager: manager.clone(),
handle: AppHandle {
runtime_handle,
manager,
event_loop: Arc::new(Mutex::new(EventLoop {
main_thread_id: std::thread::current().id(),
})),
},
ran_setup: false,
};
app.register_core_plugins()?;
#[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);
}
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())?;
let handle = app.handle();
// initialize default tray icon if defined
#[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)
.show_menu_on_left_click(tray_config.show_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)
}
/// Builds the configured application and runs it.
///
/// This is a shorthand for [`Self::build`] followed by [`App::run`].
/// For more flexibility, consider using those functions manually.
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>;
/// Async uri scheme protocol responder.
pub struct UriSchemeResponder(pub(crate) UriSchemeResponderFn);
impl UriSchemeResponder {
/// Resolves the request with the given response.
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()))
}
}
/// Uri scheme protocol context
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> {
/// Get a reference to an [`AppHandle`].
pub fn app_handle(&self) -> &'a AppHandle<R> {
self.app_handle
}
/// Get the webview label that made the uri scheme request.
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, code },
},
RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
label,
event: event.into(),
},
RuntimeRunEvent::WebviewEvent { label, event } => RunEvent::WebviewEvent {
label,
event: event.into(),
},
RuntimeRunEvent::Ready => {
// set the app icon in development
#[cfg(all(dev, target_os = "macos"))]
{
use objc2::{AllocAnyThread, MainThreadMarker};
use objc2_app_kit::{NSApplication, NSImage};
use objc2_foundation::NSData;
if let Some(icon) = app_handle.manager.app_icon.clone() {
// TODO: Enable this check.
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", target_os = "android"))]
RuntimeRunEvent::Opened { urls } => RunEvent::Opened { urls },
#[cfg(target_os = "macos")]
RuntimeRunEvent::Reopen {
has_visible_windows,
} => RunEvent::Reopen {
has_visible_windows,
},
#[cfg(target_os = "ios")]
RuntimeRunEvent::SceneRequested { scene, options } => {
RunEvent::SceneRequested { scene, options }
}
_ => 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>>();
}
}
}