#![warn(missing_docs, rust_2018_idioms)]
#![cfg_attr(doc_cfg, feature(doc_cfg))]
#[cfg(target_os = "macos")]
#[doc(hidden)]
pub use embed_plist;
pub use error::Error;
#[cfg(shell_scope)]
#[doc(hidden)]
pub use regex;
pub use tauri_macros::{command, generate_handler};
pub mod api;
pub(crate) mod app;
pub mod async_runtime;
pub mod command;
mod endpoints;
mod error;
mod event;
mod hooks;
mod manager;
mod pattern;
pub mod plugin;
pub mod window;
use tauri_runtime as runtime;
pub mod scope;
mod state;
#[cfg(updater)]
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
pub mod updater;
pub use tauri_utils as utils;
#[cfg(feature = "wry")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))]
pub type Wry = tauri_runtime_wry::Wry<EventLoopMessage>;
pub type Result<T> = std::result::Result<T, Error>;
pub type SyncTask = Box<dyn FnOnce() + Send>;
use serde::Serialize;
use std::{collections::HashMap, fmt, sync::Arc};
pub use runtime::http;
#[cfg(target_os = "macos")]
#[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
pub use runtime::{menu::NativeImage, ActivationPolicy};
#[cfg(feature = "system-tray")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
pub use {
self::app::tray::{SystemTrayEvent, SystemTrayHandle},
self::runtime::{
menu::{SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu},
SystemTray,
},
};
pub use {
self::app::WindowMenuEvent,
self::event::{Event, EventHandler},
self::runtime::menu::{AboutMetadata, CustomMenuItem, Menu, MenuEntry, MenuItem, Submenu},
self::window::menu::MenuEvent,
};
pub use {
self::app::{
App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, PathResolver,
RunEvent, WindowEvent,
},
self::hooks::{
Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokePayload, InvokeResolver,
InvokeResponder, InvokeResponse, OnPageLoad, PageLoadPayload, SetupHook,
},
self::manager::Asset,
self::runtime::{
webview::WebviewAttributes,
window::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
CursorIcon, FileDropEvent,
},
RunIteration, UserAttentionType,
},
self::state::{State, StateManager},
self::utils::{
assets::Assets,
config::{Config, WindowUrl},
Env, PackageInfo, Theme,
},
self::window::{Monitor, Window, WindowBuilder},
scope::*,
};
#[cfg(feature = "clipboard")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "clipboard")))]
pub use self::runtime::ClipboardManager;
#[cfg(feature = "global-shortcut")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "global-shortcut")))]
pub use self::runtime::GlobalShortcutManager;
#[cfg(updater)]
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
#[derive(Debug, Clone)]
pub enum UpdaterEvent {
UpdateAvailable {
body: String,
date: Option<time::OffsetDateTime>,
version: String,
},
Pending,
DownloadProgress {
chunk_length: usize,
content_length: Option<u64>,
},
Downloaded,
Updated,
AlreadyUpToDate,
Error(String),
}
#[cfg(updater)]
impl UpdaterEvent {
pub(crate) fn status_message(self) -> &'static str {
match self {
Self::Pending => updater::EVENT_STATUS_PENDING,
Self::Downloaded => updater::EVENT_STATUS_DOWNLOADED,
Self::Updated => updater::EVENT_STATUS_SUCCESS,
Self::AlreadyUpToDate => updater::EVENT_STATUS_UPTODATE,
Self::Error(_) => updater::EVENT_STATUS_ERROR,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone)]
pub enum EventLoopMessage {
#[cfg(updater)]
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
Updater(UpdaterEvent),
}
pub trait Runtime: runtime::Runtime<EventLoopMessage> {}
impl<W: runtime::Runtime<EventLoopMessage>> Runtime for W {}
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;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Icon {
#[cfg(any(feature = "icon-ico", feature = "icon-png"))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "icon-ico", feature = "icon-png"))))]
File(std::path::PathBuf),
#[cfg(any(feature = "icon-ico", feature = "icon-png"))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "icon-ico", feature = "icon-png"))))]
Raw(Vec<u8>),
Rgba {
rgba: Vec<u8>,
width: u32,
height: u32,
},
}
impl TryFrom<Icon> for runtime::Icon {
type Error = Error;
fn try_from(icon: Icon) -> Result<Self> {
#[allow(irrefutable_let_patterns)]
if let Icon::Rgba {
rgba,
width,
height,
} = icon
{
Ok(Self {
rgba,
width,
height,
})
} else {
#[cfg(not(any(feature = "icon-ico", feature = "icon-png")))]
panic!("unexpected Icon variant");
#[cfg(any(feature = "icon-ico", feature = "icon-png"))]
{
let bytes = match icon {
Icon::File(p) => std::fs::read(p)?,
Icon::Raw(r) => r,
Icon::Rgba { .. } => unreachable!(),
};
let extension = infer::get(&bytes)
.expect("could not determine icon extension")
.extension();
match extension {
#[cfg(feature = "icon-ico")]
"ico" => {
let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes))?;
let entry = &icon_dir.entries()[0];
Ok(Self {
rgba: entry.decode()?.rgba_data().to_vec(),
width: entry.width(),
height: entry.height(),
})
}
#[cfg(feature = "icon-png")]
"png" => {
let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
let mut reader = decoder.read_info()?;
let mut buffer = Vec::new();
while let Ok(Some(row)) = reader.next_row() {
buffer.extend(row.data());
}
Ok(Self {
rgba: buffer,
width: reader.info().width,
height: reader.info().height,
})
}
_ => panic!(
"image `{}` extension not supported; please file a Tauri feature request. `png` or `ico` icons are supported with the `icon-png` and `icon-ico` feature flags",
extension
),
}
}
}
}
}
pub struct Context<A: Assets> {
pub(crate) config: Config,
pub(crate) assets: Arc<A>,
pub(crate) default_window_icon: Option<Icon>,
pub(crate) app_icon: Option<Vec<u8>>,
pub(crate) system_tray_icon: Option<Icon>,
pub(crate) package_info: PackageInfo,
pub(crate) _info_plist: (),
pub(crate) pattern: Pattern,
#[cfg(shell_scope)]
pub(crate) shell_scope: scope::ShellScopeConfig,
}
impl<A: Assets> fmt::Debug for Context<A> {
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("system_tray_icon", &self.system_tray_icon)
.field("package_info", &self.package_info)
.field("pattern", &self.pattern);
#[cfg(shell_scope)]
d.field("shell_scope", &self.shell_scope);
d.finish()
}
}
impl<A: Assets> Context<A> {
#[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) -> Arc<A> {
self.assets.clone()
}
#[inline(always)]
pub fn assets_mut(&mut self) -> &mut Arc<A> {
&mut self.assets
}
#[inline(always)]
pub fn default_window_icon(&self) -> Option<&Icon> {
self.default_window_icon.as_ref()
}
#[inline(always)]
pub fn default_window_icon_mut(&mut self) -> &mut Option<Icon> {
&mut self.default_window_icon
}
#[inline(always)]
pub fn system_tray_icon(&self) -> Option<&Icon> {
self.system_tray_icon.as_ref()
}
#[inline(always)]
pub fn system_tray_icon_mut(&mut self) -> &mut Option<Icon> {
&mut self.system_tray_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
}
#[cfg(shell_scope)]
#[inline(always)]
pub fn allowed_commands(&self) -> &scope::ShellScopeConfig {
&self.shell_scope
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
pub fn new(
config: Config,
assets: Arc<A>,
default_window_icon: Option<Icon>,
app_icon: Option<Vec<u8>>,
system_tray_icon: Option<Icon>,
package_info: PackageInfo,
info_plist: (),
pattern: Pattern,
#[cfg(shell_scope)] shell_scope: scope::ShellScopeConfig,
) -> Self {
Self {
config,
assets,
default_window_icon,
app_icon,
system_tray_icon,
package_info,
_info_plist: info_plist,
pattern,
#[cfg(shell_scope)]
shell_scope,
}
}
}
pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
fn app_handle(&self) -> AppHandle<R> {
self.managed_app_handle()
}
fn config(&self) -> Arc<Config> {
self.manager().config()
}
fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
self.manager().emit_filter(event, None, payload, |_| true)
}
fn emit_to<S: Serialize + Clone>(&self, label: &str, event: &str, payload: S) -> Result<()> {
self
.manager()
.emit_filter(event, None, payload, |w| label == w.label())
}
fn listen_global<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
where
F: Fn(Event) + Send + 'static,
{
self.manager().listen(event.into(), None, handler)
}
fn once_global<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
where
F: FnOnce(Event) + Send + 'static,
{
self.manager().once(event.into(), None, handler)
}
fn trigger_global(&self, event: &str, data: Option<String>) {
self.manager().trigger(event, None, data)
}
fn unlisten(&self, handler_id: EventHandler) {
self.manager().unlisten(handler_id)
}
fn get_window(&self, label: &str) -> Option<Window<R>> {
self.manager().get_window(label)
}
fn windows(&self) -> HashMap<String, Window<R>> {
self.manager().windows()
}
fn manage<T>(&self, state: T) -> bool
where
T: Send + Sync + 'static,
{
self.manager().state().set(state)
}
fn state<T>(&self) -> State<'_, T>
where
T: Send + Sync + 'static,
{
self
.manager()
.inner
.state
.try_get()
.expect("state() called before manage() for given type")
}
fn try_state<T>(&self) -> Option<State<'_, T>>
where
T: Send + Sync + 'static,
{
self.manager().inner.state.try_get()
}
fn env(&self) -> Env {
self.state::<Env>().inner().clone()
}
fn fs_scope(&self) -> FsScope {
self.state::<Scopes>().inner().fs.clone()
}
#[cfg(protocol_asset)]
fn asset_protocol_scope(&self) -> FsScope {
self.state::<Scopes>().inner().asset_protocol.clone()
}
#[cfg(shell_scope)]
fn shell_scope(&self) -> ShellScope {
self.state::<Scopes>().inner().shell.clone()
}
}
pub(crate) mod sealed {
use super::Runtime;
use crate::{app::AppHandle, manager::WindowManager};
pub enum RuntimeOrDispatch<'r, R: Runtime> {
Runtime(&'r R),
RuntimeHandle(R::Handle),
Dispatch(R::Dispatcher),
}
pub trait ManagerBase<R: Runtime> {
fn manager(&self) -> &WindowManager<R>;
fn runtime(&self) -> RuntimeOrDispatch<'_, R>;
fn managed_app_handle(&self) -> AppHandle<R>;
}
}
#[cfg(test)]
pub mod test;
#[cfg(test)]
mod tests {
use cargo_toml::Manifest;
use once_cell::sync::OnceCell;
use std::{env::var, fs::read_to_string, path::PathBuf};
static MANIFEST: OnceCell<Manifest> = OnceCell::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 {} is not documented", f);
}
}
}
#[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 {} was checked in the alias build step but it does not exist in core/tauri/Cargo.toml",
checked_feature
);
}
}
}
#[test]
fn all_allowlist_features_are_aliased() {
let manifest = get_manifest();
let all_modules = manifest
.features
.iter()
.find(|(f, _)| f.as_str() == "api-all")
.map(|(_, enabled)| enabled)
.expect("api-all feature must exist");
let checked_features = CHECKED_FEATURES.split(',').collect::<Vec<&str>>();
assert!(
checked_features.contains(&"api-all"),
"`api-all` is not aliased"
);
let allowed = [
"fs-extract-api",
"http-api",
"http-multipart",
"process-command-api",
"process-relaunch-dangerous-allow-symlink-macos",
"window-data-url",
];
for module_all_feature in all_modules {
let module = module_all_feature.replace("-all", "");
assert!(
checked_features.contains(&module_all_feature.as_str()),
"`{}` is not aliased",
module
);
let module_prefix = format!("{}-", module);
let module_features = manifest
.features
.iter()
.map(|(f, _)| f)
.filter(|f| f.starts_with(&module_prefix));
for module_feature in module_features {
assert!(
allowed.contains(&module_feature.as_str())
|| checked_features.contains(&module_feature.as_str()),
"`{}` is not aliased",
module_feature
);
}
}
}
}
#[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 {
format!("{}-run-dummy-task", task);
};
crate::async_runtime::spawn(dummy_task);
}
}
}