#![doc(
html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
)]
#![warn(missing_docs, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(target_os = "ios")]
#[macro_export]
macro_rules! ios_plugin_binding {
($fn_name: ident) => {
tauri::swift_rs::swift!(fn $fn_name() -> *const ::std::ffi::c_void);
}
}
#[cfg(target_os = "macos")]
#[doc(hidden)]
pub use embed_plist;
pub use error::{Error, Result};
use ipc::{RuntimeAuthority, RuntimeCapability};
pub use resources::{Resource, ResourceId, ResourceTable};
#[cfg(target_os = "ios")]
#[doc(hidden)]
pub use swift_rs;
pub use tauri_macros::include_image;
#[cfg(mobile)]
pub use tauri_macros::mobile_entry_point;
pub use tauri_macros::{command, generate_handler};
use tauri_utils::assets::AssetsIter;
pub use url::Url;
pub(crate) mod app;
pub mod async_runtime;
mod error;
mod event;
pub mod ipc;
mod manager;
mod pattern;
pub mod plugin;
pub(crate) mod protocol;
mod resources;
mod vibrancy;
pub mod webview;
pub mod window;
use tauri_runtime as runtime;
pub mod image;
#[cfg(target_os = "ios")]
mod ios;
#[cfg(desktop)]
#[cfg_attr(docsrs, doc(cfg(desktop)))]
pub mod menu;
pub mod path;
pub mod process;
pub mod scope;
mod state;
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
pub mod tray;
pub use tauri_utils as utils;
pub use http;
#[cfg(feature = "wry")]
#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
pub type Wry = tauri_runtime_wry::Wry<EventLoopMessage>;
#[cfg(feature = "wry")]
#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
pub type WryHandle = tauri_runtime_wry::WryHandle<EventLoopMessage>;
#[cfg(all(feature = "wry", target_os = "android"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "wry", target_os = "android"))))]
#[doc(hidden)]
#[macro_export]
macro_rules! android_binding {
($domain:ident, $app_name:ident, $main:ident, $wry:path) => {
use $wry::{
android_setup,
prelude::{JClass, JNIEnv, JString},
};
::tauri::wry::android_binding!($domain, $app_name, $wry);
::tauri::tao::android_binding!(
$domain,
$app_name,
WryActivity,
android_setup,
$main,
::tauri::tao
);
::tauri::tao::platform::android::prelude::android_fn!(
app_tauri,
plugin,
PluginManager,
handlePluginResponse,
[i32, JString, JString],
);
::tauri::tao::platform::android::prelude::android_fn!(
app_tauri,
plugin,
PluginManager,
sendChannelData,
[i64, JString],
);
#[allow(non_snake_case)]
pub fn handlePluginResponse(
mut env: JNIEnv,
_: JClass,
id: i32,
success: JString,
error: JString,
) {
::tauri::handle_android_plugin_response(&mut env, id, success, error);
}
#[allow(non_snake_case)]
pub fn sendChannelData(mut env: JNIEnv, _: JClass, id: i64, data: JString) {
::tauri::send_channel_data(&mut env, id, data);
}
};
}
#[cfg(all(feature = "wry", target_os = "android"))]
#[doc(hidden)]
pub use plugin::mobile::{handle_android_plugin_response, send_channel_data};
#[cfg(all(feature = "wry", target_os = "android"))]
#[doc(hidden)]
pub use tauri_runtime_wry::{tao, wry};
pub type SyncTask = Box<dyn FnOnce() + Send>;
use serde::Serialize;
use std::{
borrow::Cow,
collections::HashMap,
fmt::{self, Debug},
sync::MutexGuard,
};
use utils::assets::{AssetKey, CspHash, EmbeddedAssets};
#[cfg(feature = "wry")]
#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
pub use tauri_runtime_wry::webview_version;
#[cfg(target_os = "macos")]
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
pub use runtime::ActivationPolicy;
#[cfg(target_os = "macos")]
pub use self::utils::TitleBarStyle;
use self::event::EventName;
pub use self::event::{Event, EventId, EventTarget};
use self::manager::EmitPayload;
pub use {
self::app::{
App, AppHandle, AssetResolver, Builder, CloseRequestApi, ExitRequestApi, RunEvent,
UriSchemeContext, UriSchemeResponder, WebviewEvent, WindowEvent, RESTART_EXIT_CODE,
},
self::manager::Asset,
self::runtime::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
window::{CursorIcon, DragDropEvent, WindowSizeConstraints},
DeviceEventFilter, Rect, UserAttentionType,
},
self::state::{State, StateManager},
self::utils::{
config::{Config, WebviewUrl},
Env, PackageInfo, Theme,
},
self::webview::{Webview, WebviewWindow, WebviewWindowBuilder},
self::window::{Monitor, Window},
scope::*,
};
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
pub use {self::webview::WebviewBuilder, self::window::WindowBuilder};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[cfg(target_os = "ios")]
#[doc(hidden)]
pub fn log_stdout() {
use std::{
ffi::CString,
fs::File,
io::{BufRead, BufReader},
os::unix::prelude::*,
thread,
};
let mut logpipe: [RawFd; 2] = Default::default();
unsafe {
libc::pipe(logpipe.as_mut_ptr());
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
libc::dup2(logpipe[1], libc::STDERR_FILENO);
}
thread::spawn(move || unsafe {
let file = File::from_raw_fd(logpipe[0]);
let mut reader = BufReader::new(file);
let mut buffer = String::new();
loop {
buffer.clear();
if let Ok(len) = reader.read_line(&mut buffer) {
if len == 0 {
break;
} else if let Ok(msg) = CString::new(buffer.as_bytes())
.map_err(|_| ())
.and_then(|c| c.into_string().map_err(|_| ()))
{
log::info!("{}", msg);
}
}
}
});
}
#[derive(Debug, Clone)]
pub enum EventLoopMessage {
#[cfg(desktop)]
MenuEvent(menu::MenuEvent),
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
TrayIconEvent(tray::TrayIconEvent),
}
pub trait Runtime: runtime::Runtime<EventLoopMessage> {}
pub trait RuntimeHandle: runtime::RuntimeHandle<EventLoopMessage> {}
impl<W: runtime::Runtime<EventLoopMessage>> Runtime for W {}
impl<R: runtime::RuntimeHandle<EventLoopMessage>> RuntimeHandle for R {}
pub use tauri_macros::generate_context;
#[macro_export]
macro_rules! tauri_build_context {
() => {
include!(concat!(env!("OUT_DIR"), "/tauri-build-context.rs"))
};
}
pub use pattern::Pattern;
pub const fn is_dev() -> bool {
!cfg!(feature = "custom-protocol")
}
pub trait Assets<R: Runtime>: Send + Sync + 'static {
fn setup(&self, app: &App<R>) {
let _ = app;
}
fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>>;
fn iter(&self) -> Box<tauri_utils::assets::AssetsIter<'_>>;
fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_>;
}
impl<R: Runtime> Assets<R> for EmbeddedAssets {
fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
EmbeddedAssets::get(self, key)
}
fn iter(&self) -> Box<AssetsIter<'_>> {
EmbeddedAssets::iter(self)
}
fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
EmbeddedAssets::csp_hashes(self, html_path)
}
}
#[tauri_macros::default_runtime(Wry, wry)]
pub struct Context<R: Runtime> {
pub(crate) config: Config,
#[cfg(dev)]
pub(crate) config_parent: Option<std::path::PathBuf>,
pub assets: Box<dyn Assets<R>>,
pub(crate) default_window_icon: Option<image::Image<'static>>,
pub(crate) app_icon: Option<Vec<u8>>,
#[cfg(all(desktop, feature = "tray-icon"))]
pub(crate) tray_icon: Option<image::Image<'static>>,
pub(crate) package_info: PackageInfo,
pub(crate) pattern: Pattern,
pub(crate) runtime_authority: RuntimeAuthority,
pub(crate) plugin_global_api_scripts: Option<&'static [&'static str]>,
}
impl<R: Runtime> fmt::Debug for Context<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Context");
d.field("config", &self.config)
.field("default_window_icon", &self.default_window_icon)
.field("app_icon", &self.app_icon)
.field("package_info", &self.package_info)
.field("pattern", &self.pattern)
.field("plugin_global_api_scripts", &self.plugin_global_api_scripts);
#[cfg(all(desktop, feature = "tray-icon"))]
d.field("tray_icon", &self.tray_icon);
d.finish()
}
}
impl<R: Runtime> Context<R> {
#[inline(always)]
pub fn config(&self) -> &Config {
&self.config
}
#[inline(always)]
pub fn config_mut(&mut self) -> &mut Config {
&mut self.config
}
#[inline(always)]
pub fn assets(&self) -> &dyn Assets<R> {
self.assets.as_ref()
}
#[inline(always)]
pub fn set_assets(&mut self, assets: Box<dyn Assets<R>>) -> Box<dyn Assets<R>> {
std::mem::replace(&mut self.assets, assets)
}
#[inline(always)]
pub fn default_window_icon(&self) -> Option<&image::Image<'_>> {
self.default_window_icon.as_ref()
}
#[inline(always)]
pub fn set_default_window_icon(&mut self, icon: Option<image::Image<'static>>) {
self.default_window_icon = icon;
}
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
#[inline(always)]
pub fn tray_icon(&self) -> Option<&image::Image<'_>> {
self.tray_icon.as_ref()
}
#[cfg(all(desktop, feature = "tray-icon"))]
#[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
#[inline(always)]
pub fn set_tray_icon(&mut self, icon: Option<image::Image<'static>>) {
self.tray_icon = icon;
}
#[inline(always)]
pub fn package_info(&self) -> &PackageInfo {
&self.package_info
}
#[inline(always)]
pub fn package_info_mut(&mut self) -> &mut PackageInfo {
&mut self.package_info
}
#[inline(always)]
pub fn pattern(&self) -> &Pattern {
&self.pattern
}
#[doc(hidden)]
#[inline(always)]
pub fn runtime_authority_mut(&mut self) -> &mut RuntimeAuthority {
&mut self.runtime_authority
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
pub fn new(
config: Config,
assets: Box<dyn Assets<R>>,
default_window_icon: Option<image::Image<'static>>,
app_icon: Option<Vec<u8>>,
package_info: PackageInfo,
pattern: Pattern,
runtime_authority: RuntimeAuthority,
plugin_global_api_scripts: Option<&'static [&'static str]>,
) -> Self {
Self {
config,
#[cfg(dev)]
config_parent: None,
assets,
default_window_icon,
app_icon,
#[cfg(all(desktop, feature = "tray-icon"))]
tray_icon: None,
package_info,
pattern,
runtime_authority,
plugin_global_api_scripts,
}
}
#[cfg(dev)]
#[doc(hidden)]
pub fn with_config_parent(&mut self, config_parent: impl AsRef<std::path::Path>) {
self
.config_parent
.replace(config_parent.as_ref().to_owned());
}
}
pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
fn app_handle(&self) -> &AppHandle<R> {
self.managed_app_handle()
}
fn config(&self) -> &Config {
self.manager().config()
}
fn package_info(&self) -> &PackageInfo {
self.manager().package_info()
}
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
fn get_window(&self, label: &str) -> Option<Window<R>> {
self.manager().get_window(label)
}
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
fn get_focused_window(&self) -> Option<Window<R>> {
self.manager().get_focused_window()
}
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
fn windows(&self) -> HashMap<String, Window<R>> {
self.manager().windows()
}
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
fn get_webview(&self, label: &str) -> Option<Webview<R>> {
self.manager().get_webview(label)
}
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
fn webviews(&self) -> HashMap<String, Webview<R>> {
self.manager().webviews()
}
fn get_webview_window(&self, label: &str) -> Option<WebviewWindow<R>> {
self.manager().get_webview(label).and_then(|webview| {
let window = webview.window();
if window.is_webview_window() {
Some(WebviewWindow { window, webview })
} else {
None
}
})
}
fn webview_windows(&self) -> HashMap<String, WebviewWindow<R>> {
self
.manager()
.webviews()
.into_iter()
.filter_map(|(label, webview)| {
let window = webview.window();
if window.is_webview_window() {
Some((label, WebviewWindow { window, webview }))
} else {
None
}
})
.collect::<HashMap<_, _>>()
}
fn manage<T>(&self, state: T) -> bool
where
T: Send + Sync + 'static,
{
self.manager().state().set(state)
}
#[deprecated(
since = "2.3.0",
note = "This method is unsafe, since it can cause dangling references."
)]
fn unmanage<T>(&self) -> Option<T>
where
T: Send + Sync + 'static,
{
unsafe { self.manager().state().unmanage() }
}
fn state<T>(&self) -> State<'_, T>
where
T: Send + Sync + 'static,
{
self.manager().state.try_get().unwrap_or_else(|| {
panic!(
"state() called before manage() for {}",
std::any::type_name::<T>()
)
})
}
fn try_state<T>(&self) -> Option<State<'_, T>>
where
T: Send + Sync + 'static,
{
self.manager().state.try_get()
}
fn resources_table(&self) -> MutexGuard<'_, ResourceTable>;
fn env(&self) -> Env {
self.state::<Env>().inner().clone()
}
#[cfg(feature = "protocol-asset")]
fn asset_protocol_scope(&self) -> scope::fs::Scope {
self.state::<Scopes>().inner().asset_protocol.clone()
}
fn path(&self) -> &crate::path::PathResolver<R> {
self.state::<crate::path::PathResolver<R>>().inner()
}
fn add_capability(&self, capability: impl RuntimeCapability) -> Result<()> {
self
.manager()
.runtime_authority
.lock()
.unwrap()
.add_capability(capability)
}
}
pub trait Listener<R: Runtime>: sealed::ManagerBase<R> {
fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
where
F: Fn(Event) + Send + 'static;
fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
where
F: FnOnce(Event) + Send + 'static;
fn unlisten(&self, id: EventId);
fn listen_any<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::Any, handler)
}
fn once_any<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::Any, handler)
}
}
pub trait Emitter<R: Runtime>: sealed::ManagerBase<R> {
fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
let event = EventName::new(event)?;
let payload = EmitPayload::Serialize(&payload);
self.manager().emit(event, payload)
}
fn emit_str(&self, event: &str, payload: String) -> Result<()> {
let event = EventName::new(event)?;
let payload = EmitPayload::<()>::Str(payload);
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,
{
let event = EventName::new(event)?;
let payload = EmitPayload::Serialize(&payload);
self.manager().emit_to(target.into(), event, payload)
}
fn emit_str_to<I>(&self, target: I, event: &str, payload: String) -> Result<()>
where
I: Into<EventTarget>,
{
let event = EventName::new(event)?;
let payload = EmitPayload::<()>::Str(payload);
self.manager().emit_to(target.into(), event, payload)
}
fn emit_filter<S, F>(&self, event: &str, payload: S, filter: F) -> Result<()>
where
S: Serialize + Clone,
F: Fn(&EventTarget) -> bool,
{
let event = EventName::new(event)?;
let payload = EmitPayload::Serialize(&payload);
self.manager().emit_filter(event, payload, filter)
}
fn emit_str_filter<F>(&self, event: &str, payload: String, filter: F) -> Result<()>
where
F: Fn(&EventTarget) -> bool,
{
let event = EventName::new(event)?;
let payload = EmitPayload::<()>::Str(payload);
self.manager().emit_filter(event, payload, filter)
}
}
pub(crate) mod sealed {
use super::Runtime;
use crate::{app::AppHandle, manager::AppManager};
use std::sync::Arc;
pub enum RuntimeOrDispatch<'r, R: Runtime> {
Runtime(&'r R),
RuntimeHandle(R::Handle),
Dispatch(R::WindowDispatcher),
}
pub trait ManagerBase<R: Runtime> {
fn manager(&self) -> &AppManager<R>;
fn manager_owned(&self) -> Arc<AppManager<R>>;
fn runtime(&self) -> RuntimeOrDispatch<'_, R>;
fn managed_app_handle(&self) -> &AppHandle<R>;
}
}
struct UnsafeSend<T>(T);
unsafe impl<T> Send for UnsafeSend<T> {}
impl<T> UnsafeSend<T> {
fn take(self) -> T {
self.0
}
}
#[allow(unused)]
macro_rules! run_main_thread {
($handle:ident, $ex:expr) => {{
use std::sync::mpsc::channel;
let (tx, rx) = channel();
let task = move || {
let f = $ex;
let _ = tx.send(f());
};
$handle
.run_on_main_thread(task)
.and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))
}};
}
#[allow(unused)]
pub(crate) use run_main_thread;
#[cfg(any(test, feature = "test"))]
#[cfg_attr(docsrs, doc(cfg(feature = "test")))]
pub mod test;
#[cfg(feature = "specta")]
const _: () = {
use specta::{datatype::DataType, function::FunctionArg, TypeMap};
impl<T: Send + Sync + 'static> FunctionArg for crate::State<'_, T> {
fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
None
}
}
impl<R: crate::Runtime> FunctionArg for crate::AppHandle<R> {
fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
None
}
}
impl<R: crate::Runtime> FunctionArg for crate::Window<R> {
fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
None
}
}
impl<R: crate::Runtime> FunctionArg for crate::Webview<R> {
fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
None
}
}
impl<R: crate::Runtime> FunctionArg for crate::WebviewWindow<R> {
fn to_datatype(_: &mut TypeMap) -> Option<DataType> {
None
}
}
};
#[cfg(test)]
mod tests {
use cargo_toml::Manifest;
use std::{env::var, fs::read_to_string, path::PathBuf, sync::OnceLock};
static MANIFEST: OnceLock<Manifest> = OnceLock::new();
const CHECKED_FEATURES: &str = include_str!(concat!(env!("OUT_DIR"), "/checked_features"));
fn get_manifest() -> &'static Manifest {
MANIFEST.get_or_init(|| {
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest")
})
}
#[test]
fn features_are_documented() {
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs");
for f in get_manifest().features.keys() {
if !(f.starts_with("__") || f == "default" || lib_code.contains(&format!("*{f}**"))) {
panic!("Feature {f} is not documented");
}
}
}
#[test]
fn aliased_features_exist() {
let checked_features = CHECKED_FEATURES.split(',');
let manifest = get_manifest();
for checked_feature in checked_features {
if !manifest.features.iter().any(|(f, _)| f == checked_feature) {
panic!(
"Feature {checked_feature} was checked in the alias build step but it does not exist in crates/tauri/Cargo.toml"
);
}
}
}
}
#[cfg(test)]
mod test_utils {
use proptest::prelude::*;
pub fn assert_send<T: Send>() {}
pub fn assert_sync<T: Sync>() {}
#[allow(dead_code)]
pub fn assert_not_allowlist_error<T>(res: anyhow::Result<T>) {
if let Err(e) = res {
assert!(!e.to_string().contains("not on the allowlist"));
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10000))]
#[test]
fn check_spawn_task(task in "[a-z]+") {
let dummy_task = async move {
let _ = format!("{task}-run-dummy-task");
};
crate::async_runtime::spawn(dummy_task);
}
}
}
mod z85 {
const TABLE: &[u8; 85] =
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";
pub fn encode(bytes: &[u8]) -> String {
assert_eq!(bytes.len() % 4, 0);
let mut buf = String::with_capacity(bytes.len() * 5 / 4);
for chunk in bytes.chunks_exact(4) {
let mut chars = [0u8; 5];
let mut chunk = u32::from_be_bytes(chunk.try_into().unwrap()) as usize;
for byte in chars.iter_mut().rev() {
*byte = TABLE[chunk % 85];
chunk /= 85;
}
buf.push_str(std::str::from_utf8(&chars).unwrap());
}
buf
}
#[cfg(test)]
mod tests {
#[test]
fn encode() {
assert_eq!(
super::encode(&[0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B]),
"HelloWorld"
);
}
}
}
pub(crate) fn generate_invoke_key() -> Result<String> {
let mut bytes = [0u8; 16];
getrandom::getrandom(&mut bytes)?;
Ok(z85::encode(&bytes))
}