Merge pull request #3959 from tauri-apps/perf/improve-binary-size

This commit is contained in:
Lucas Fernandes Nogueira 2022-04-25 06:52:57 -07:00 committed by GitHub
commit 4a405065c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1153 additions and 563 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
Reduce the amount of generated code for the API endpoints.

View File

@ -0,0 +1,7 @@
---
"tauri": patch
"tauri-runtime": minor
"tauri-runtime-wry": minor
---
**Breaking change::* Added the `clipboard` Cargo feature.

View File

@ -0,0 +1,7 @@
---
"tauri": patch
"tauri-runtime": minor
"tauri-runtime-wry": minor
---
**Breaking change::* Added the `global-shortcut` Cargo feature.

View File

@ -24,6 +24,7 @@ exclude = [
# default to small, optimized workspace release binaries
[profile.release]
strip = true
panic = "abort"
codegen-units = 1
lto = true

View File

@ -56,6 +56,7 @@ fn map_core_assets(
if path.extension() == Some(OsStr::new("html")) {
let mut document = parse_html(String::from_utf8_lossy(input).into_owned());
#[allow(clippy::collapsible_if)]
if csp {
#[cfg(target_os = "linux")]
::tauri_utils::html::inject_csp_token(&mut document);

View File

@ -2,35 +2,115 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use heck::ToSnakeCase;
use heck::{ToLowerCamelCase, ToSnakeCase};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote, quote_spanned};
use syn::{
parse::{Parse, ParseStream},
parse_quote,
spanned::Spanned,
Data, DeriveInput, Error, Fields, FnArg, Ident, ItemFn, LitStr, Pat, Token,
Data, DeriveInput, Error, Fields, Ident, ItemFn, LitStr, Token,
};
pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
pub(crate) fn generate_command_enum(mut input: DeriveInput) -> TokenStream {
let mut deserialize_functions = TokenStream2::new();
let mut errors = TokenStream2::new();
input.attrs.push(parse_quote!(#[allow(dead_code)]));
match &mut input.data {
Data::Enum(data_enum) => {
for variant in &mut data_enum.variants {
let mut feature: Option<Ident> = None;
let mut error_message: Option<String> = None;
for attr in &variant.attrs {
if attr.path.is_ident("cmd") {
let r = attr
.parse_args_with(|input: ParseStream| {
if let Ok(f) = input.parse::<Ident>() {
feature.replace(f);
input.parse::<Token![,]>()?;
let error_message_raw: LitStr = input.parse()?;
error_message.replace(error_message_raw.value());
}
Ok(quote!())
})
.unwrap_or_else(syn::Error::into_compile_error);
errors.extend(r);
}
}
if let Some(f) = feature {
let error_message = if let Some(e) = error_message {
let e = e.to_string();
quote!(#e)
} else {
quote!("This API is not enabled in the allowlist.")
};
let deserialize_function_name = quote::format_ident!("__{}_deserializer", variant.ident);
deserialize_functions.extend(quote! {
#[cfg(not(#f))]
#[allow(non_snake_case)]
fn #deserialize_function_name<'de, D, T>(deserializer: D) -> ::std::result::Result<T, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
::std::result::Result::Err(::serde::de::Error::custom(crate::Error::ApiNotAllowlisted(#error_message.into()).to_string()))
}
});
let deserialize_function_name = deserialize_function_name.to_string();
variant
.attrs
.push(parse_quote!(#[cfg_attr(not(#f), serde(deserialize_with = #deserialize_function_name))]));
}
}
}
_ => {
return Error::new(
Span::call_site(),
"`command_enum` is only implemented for enums",
)
.to_compile_error()
.into()
}
};
TokenStream::from(quote! {
#errors
#input
#deserialize_functions
})
}
pub(crate) fn generate_run_fn(input: DeriveInput) -> TokenStream {
let name = &input.ident;
let data = &input.data;
let mut errors = TokenStream2::new();
let mut is_async = false;
let attrs = input.attrs;
for attr in attrs {
if attr.path.is_ident("cmd") {
let _ = attr.parse_args_with(|input: ParseStream| {
while let Some(token) = input.parse()? {
if let TokenTree::Ident(ident) = token {
is_async |= ident == "async";
let r = attr
.parse_args_with(|input: ParseStream| {
if let Ok(token) = input.parse::<Ident>() {
is_async = token == "async";
}
}
Ok(())
});
Ok(quote!())
})
.unwrap_or_else(syn::Error::into_compile_error);
errors.extend(r);
}
}
let maybe_await = if is_async { quote!(.await) } else { quote!() };
let maybe_async = if is_async { quote!(async) } else { quote!() };
@ -43,6 +123,30 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
for variant in &data_enum.variants {
let variant_name = &variant.ident;
let mut feature = None;
for attr in &variant.attrs {
if attr.path.is_ident("cmd") {
let r = attr
.parse_args_with(|input: ParseStream| {
if let Ok(f) = input.parse::<Ident>() {
feature.replace(f);
input.parse::<Token![,]>()?;
let _: LitStr = input.parse()?;
}
Ok(quote!())
})
.unwrap_or_else(syn::Error::into_compile_error);
errors.extend(r);
}
}
let maybe_feature_check = if let Some(f) = feature {
quote!(#[cfg(#f)])
} else {
quote!()
};
let (fields_in_variant, variables) = match &variant.fields {
Fields::Unit => (quote_spanned! { variant.span() => }, quote!()),
Fields::Unnamed(fields) => {
@ -73,9 +177,13 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
variant_execute_function_name.set_span(variant_name.span());
matcher.extend(quote_spanned! {
variant.span() => #name::#variant_name #fields_in_variant => #name::#variant_execute_function_name(context, #variables)#maybe_await.map(Into::into),
variant.span() => #maybe_feature_check #name::#variant_name #fields_in_variant => #name::#variant_execute_function_name(context, #variables)#maybe_await.map(Into::into),
});
}
matcher.extend(quote! {
_ => Err(crate::error::into_anyhow("API not in the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)")),
});
}
_ => {
return Error::new(
@ -90,7 +198,8 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics #name #ty_generics #where_clause {
#errors
impl #impl_generics #name #ty_generics #where_clause {
pub #maybe_async fn run<R: crate::Runtime>(self, context: crate::endpoints::InvokeContext<R>) -> super::Result<crate::endpoints::InvokeResponse> {
match self {
#matcher
@ -105,26 +214,25 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
/// Attributes for the module enum variant handler.
pub struct HandlerAttributes {
allowlist: Ident,
error_message: String,
}
impl Parse for HandlerAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let allowlist = input.parse()?;
input.parse::<Token![,]>()?;
let raw: LitStr = input.parse()?;
let error_message = raw.value();
Ok(Self {
allowlist,
error_message,
allowlist: input.parse()?,
})
}
}
pub enum AllowlistCheckKind {
Runtime,
Serde,
}
pub struct HandlerTestAttributes {
allowlist: Ident,
error_message: String,
is_async: bool,
allowlist_check_kind: AllowlistCheckKind,
}
impl Parse for HandlerTestAttributes {
@ -133,60 +241,48 @@ impl Parse for HandlerTestAttributes {
input.parse::<Token![,]>()?;
let error_message_raw: LitStr = input.parse()?;
let error_message = error_message_raw.value();
let _ = input.parse::<Token![,]>();
let is_async = input
.parse::<Ident>()
.map(|i| i == "async")
.unwrap_or_default();
let allowlist_check_kind =
if let (Ok(_), Ok(i)) = (input.parse::<Token![,]>(), input.parse::<Ident>()) {
if i == "runtime" {
AllowlistCheckKind::Runtime
} else {
AllowlistCheckKind::Serde
}
} else {
AllowlistCheckKind::Serde
};
Ok(Self {
allowlist,
error_message,
is_async,
allowlist_check_kind,
})
}
}
pub fn command_handler(attributes: HandlerAttributes, function: ItemFn) -> TokenStream2 {
let allowlist = attributes.allowlist;
let error_message = attributes.error_message.as_str();
let signature = function.sig.clone();
quote!(
#[cfg(#allowlist)]
#function
#[cfg(not(#allowlist))]
#[allow(unused_variables)]
#[allow(unused_mut)]
#signature {
Err(anyhow::anyhow!(crate::Error::ApiNotAllowlisted(#error_message.to_string()).to_string()))
}
)
}
pub fn command_test(attributes: HandlerTestAttributes, function: ItemFn) -> TokenStream2 {
let allowlist = attributes.allowlist;
let is_async = attributes.is_async;
let error_message = attributes.error_message.as_str();
let signature = function.sig.clone();
let test_name = function.sig.ident.clone();
let mut args = quote!();
for arg in &function.sig.inputs {
if let FnArg::Typed(t) = arg {
if let Pat::Ident(i) = &*t.pat {
let ident = &i.ident;
args.extend(quote!(#ident,))
}
}
}
let response = if is_async {
quote!(crate::async_runtime::block_on(
super::Cmd::#test_name(crate::test::mock_invoke_context(), #args)
))
} else {
quote!(super::Cmd::#test_name(crate::test::mock_invoke_context(), #args))
let enum_variant_name = function.sig.ident.to_string().to_lower_camel_case();
let response = match attributes.allowlist_check_kind {
AllowlistCheckKind::Runtime => {
let test_name = function.sig.ident.clone();
quote!(super::Cmd::#test_name(crate::test::mock_invoke_context()))
}
AllowlistCheckKind::Serde => quote! {
serde_json::from_str::<super::Cmd>(&format!(r#"{{ "cmd": "{}", "data": null }}"#, #enum_variant_name))
},
};
quote!(
@ -194,6 +290,7 @@ pub fn command_test(attributes: HandlerTestAttributes, function: ItemFn) -> Toke
#function
#[cfg(not(#allowlist))]
#[allow(unused_variables)]
#[quickcheck_macros::quickcheck]
#signature {
if let Err(e) = #response {

View File

@ -76,6 +76,14 @@ pub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStre
runtime::default_runtime(attributes, input).into()
}
/// Prepares the command module enum.
#[doc(hidden)]
#[proc_macro_derive(CommandModule, attributes(cmd))]
pub fn derive_command_module(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
command_module::generate_run_fn(input)
}
/// Adds a `run` method to an enum (one of the tauri endpoint modules).
/// The `run` method takes a `tauri::endpoints::InvokeContext`
/// and returns a `tauri::Result<tauri::endpoints::InvokeResponse>`.
@ -83,10 +91,10 @@ pub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStre
/// passing the the context and the variant's fields as arguments.
/// That function must also return the same `Result<InvokeResponse>`.
#[doc(hidden)]
#[proc_macro_derive(CommandModule, attributes(cmd))]
pub fn derive_command_module(input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn command_enum(_: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
command_module::generate_run_fn(input)
command_module::generate_command_enum(input)
}
#[doc(hidden)]

View File

@ -41,3 +41,5 @@ macos-private-api = [
objc-exception = [ "wry/objc-exception" ]
gtk-tray = [ "wry/gtk-tray" ]
ayatana-tray = [ "wry/ayatana-tray" ]
global-shortcut = [ "tauri-runtime/global-shortcut" ]
clipboard = [ "tauri-runtime/clipboard" ]

View File

@ -0,0 +1,62 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! Clipboard implementation.
use crate::{getter, Context, Message};
use std::sync::{
mpsc::{channel, Sender},
Arc, Mutex,
};
use tauri_runtime::{ClipboardManager, Result, UserEvent};
pub use wry::application::clipboard::Clipboard;
#[derive(Debug, Clone)]
pub enum ClipboardMessage {
WriteText(String, Sender<()>),
ReadText(Sender<Option<String>>),
}
#[derive(Debug, Clone)]
pub struct ClipboardManagerWrapper<T: UserEvent> {
pub context: Context<T>,
}
// SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`.
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl<T: UserEvent> Sync for ClipboardManagerWrapper<T> {}
impl<T: UserEvent> ClipboardManager for ClipboardManagerWrapper<T> {
fn read_text(&self) -> Result<Option<String>> {
let (tx, rx) = channel();
getter!(self, rx, Message::Clipboard(ClipboardMessage::ReadText(tx)))
}
fn write_text<V: Into<String>>(&mut self, text: V) -> Result<()> {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::Clipboard(ClipboardMessage::WriteText(text.into(), tx))
)?;
Ok(())
}
}
pub fn handle_clipboard_message(
message: ClipboardMessage,
clipboard_manager: &Arc<Mutex<Clipboard>>,
) {
match message {
ClipboardMessage::WriteText(text, tx) => {
clipboard_manager.lock().unwrap().write_text(text);
tx.send(()).unwrap();
}
ClipboardMessage::ReadText(tx) => tx
.send(clipboard_manager.lock().unwrap().read_text())
.unwrap(),
}
}

View File

@ -0,0 +1,164 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! Global shortcut implementation.
use std::{
collections::HashMap,
fmt,
sync::{
mpsc::{channel, Sender},
Arc, Mutex,
},
};
use crate::{getter, Context, Message};
use tauri_runtime::{Error, GlobalShortcutManager, Result, UserEvent};
pub use wry::application::global_shortcut::ShortcutManager as WryShortcutManager;
use wry::application::{
accelerator::{Accelerator, AcceleratorId},
global_shortcut::GlobalShortcut,
};
pub type GlobalShortcutListeners = Arc<Mutex<HashMap<AcceleratorId, Box<dyn Fn() + Send>>>>;
#[derive(Debug, Clone)]
pub enum GlobalShortcutMessage {
IsRegistered(Accelerator, Sender<bool>),
Register(Accelerator, Sender<Result<GlobalShortcutWrapper>>),
Unregister(GlobalShortcutWrapper, Sender<Result<()>>),
UnregisterAll(Sender<Result<()>>),
}
#[derive(Debug, Clone)]
pub struct GlobalShortcutWrapper(GlobalShortcut);
// SAFETY: usage outside of main thread is guarded, we use the event loop on such cases.
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for GlobalShortcutWrapper {}
/// Wrapper around [`WryShortcutManager`].
#[derive(Clone)]
pub struct GlobalShortcutManagerHandle<T: UserEvent> {
pub context: Context<T>,
pub shortcuts: Arc<Mutex<HashMap<String, (AcceleratorId, GlobalShortcutWrapper)>>>,
pub listeners: GlobalShortcutListeners,
}
// SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`.
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl<T: UserEvent> Sync for GlobalShortcutManagerHandle<T> {}
impl<T: UserEvent> fmt::Debug for GlobalShortcutManagerHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GlobalShortcutManagerHandle")
.field("context", &self.context)
.field("shortcuts", &self.shortcuts)
.finish()
}
}
impl<T: UserEvent> GlobalShortcutManager for GlobalShortcutManagerHandle<T> {
fn is_registered(&self, accelerator: &str) -> Result<bool> {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::IsRegistered(
accelerator.parse().expect("invalid accelerator"),
tx
))
)
}
fn register<F: Fn() + Send + 'static>(&mut self, accelerator: &str, handler: F) -> Result<()> {
let wry_accelerator: Accelerator = accelerator.parse().expect("invalid accelerator");
let id = wry_accelerator.clone().id();
let (tx, rx) = channel();
let shortcut = getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::Register(wry_accelerator, tx))
)??;
self.listeners.lock().unwrap().insert(id, Box::new(handler));
self
.shortcuts
.lock()
.unwrap()
.insert(accelerator.into(), (id, shortcut));
Ok(())
}
fn unregister_all(&mut self) -> Result<()> {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::UnregisterAll(tx))
)??;
self.listeners.lock().unwrap().clear();
self.shortcuts.lock().unwrap().clear();
Ok(())
}
fn unregister(&mut self, accelerator: &str) -> Result<()> {
if let Some((accelerator_id, shortcut)) = self.shortcuts.lock().unwrap().remove(accelerator) {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::Unregister(shortcut, tx))
)??;
self.listeners.lock().unwrap().remove(&accelerator_id);
}
Ok(())
}
}
pub fn handle_global_shortcut_message(
message: GlobalShortcutMessage,
global_shortcut_manager: &Arc<Mutex<WryShortcutManager>>,
) {
match message {
GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.is_registered(&accelerator),
)
.unwrap(),
GlobalShortcutMessage::Register(accelerator, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.register(accelerator)
.map(GlobalShortcutWrapper)
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
GlobalShortcutMessage::Unregister(shortcut, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.unregister(shortcut.0)
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
GlobalShortcutMessage::UnregisterAll(tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.unregister_all()
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
}
}

View File

@ -16,9 +16,8 @@ use tauri_runtime::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
CursorIcon, DetachedWindow, FileDropEvent, JsEventListenerKey, PendingWindow, WindowEvent,
},
ClipboardManager, Dispatch, Error, EventLoopProxy, ExitRequestedEventAction,
GlobalShortcutManager, Result, RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType,
UserEvent, WindowIcon,
Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Result, RunEvent, RunIteration,
Runtime, RuntimeHandle, UserAttentionType, UserEvent, WindowIcon,
};
use tauri_runtime::window::MenuEvent;
@ -44,8 +43,6 @@ use tauri_utils::{config::WindowConfig, Theme};
use uuid::Uuid;
use wry::{
application::{
accelerator::{Accelerator, AcceleratorId},
clipboard::Clipboard,
dpi::{
LogicalPosition as WryLogicalPosition, LogicalSize as WryLogicalSize,
PhysicalPosition as WryPhysicalPosition, PhysicalSize as WryPhysicalSize,
@ -55,7 +52,6 @@ use wry::{
event_loop::{
ControlFlow, EventLoop, EventLoopProxy as WryEventLoopProxy, EventLoopWindowTarget,
},
global_shortcut::{GlobalShortcut, ShortcutManager as WryShortcutManager},
menu::{
AboutMetadata as WryAboutMetadata, CustomMenuItem as WryCustomMenuItem, MenuBar,
MenuId as WryMenuId, MenuItem as WryMenuItem, MenuItemAttributes as WryMenuItemAttributes,
@ -109,13 +105,21 @@ mod system_tray;
#[cfg(feature = "system-tray")]
use system_tray::*;
#[cfg(feature = "global-shortcut")]
mod global_shortcut;
#[cfg(feature = "global-shortcut")]
use global_shortcut::*;
#[cfg(feature = "clipboard")]
mod clipboard;
#[cfg(feature = "clipboard")]
use clipboard::*;
type WebContextStore = Arc<Mutex<HashMap<Option<PathBuf>, WebContext>>>;
// window
type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
type WindowEventListenersMap = Arc<Mutex<HashMap<Uuid, WindowEventHandler>>>;
type WindowEventListeners = Arc<Mutex<HashMap<WebviewId, WindowEventListenersMap>>>;
// global shortcut
type GlobalShortcutListeners = Arc<Mutex<HashMap<AcceleratorId, Box<dyn Fn() + Send>>>>;
// menu
pub type MenuEventHandler = Box<dyn Fn(&MenuEvent) + Send>;
pub type MenuEventListeners = Arc<Mutex<HashMap<WebviewId, WindowMenuEventListeners>>>;
@ -134,10 +138,13 @@ impl WebviewIdStore {
}
}
#[macro_export]
macro_rules! getter {
($self: ident, $rx: expr, $message: expr) => {{
send_user_message(&$self.context, $message)?;
$rx.recv().map_err(|_| Error::FailedToReceiveMessage)
$crate::send_user_message(&$self.context, $message)?;
$rx
.recv()
.map_err(|_| $crate::Error::FailedToReceiveMessage)
}};
}
@ -156,7 +163,9 @@ fn send_user_message<T: UserEvent>(context: &Context<T>, message: Message<T>) ->
UserMessageContext {
webview_id_map: context.webview_id_map.clone(),
window_event_listeners: &context.window_event_listeners,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: context.main_thread.global_shortcut_manager.clone(),
#[cfg(feature = "clipboard")]
clipboard_manager: context.main_thread.clipboard_manager.clone(),
menu_event_listeners: &context.menu_event_listeners,
windows: context.main_thread.windows.clone(),
@ -175,7 +184,7 @@ fn send_user_message<T: UserEvent>(context: &Context<T>, message: Message<T>) ->
}
#[derive(Clone)]
struct Context<T: UserEvent> {
pub struct Context<T: UserEvent> {
webview_id_map: WebviewIdStore,
main_thread_id: ThreadId,
proxy: WryEventLoopProxy<Message<T>>,
@ -236,7 +245,9 @@ impl<T: UserEvent> Context<T> {
struct DispatcherMainThreadContext<T: UserEvent> {
window_target: EventLoopWindowTarget<Message<T>>,
web_context: WebContextStore,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
#[cfg(feature = "clipboard")]
clipboard_manager: Arc<Mutex<Clipboard>>,
windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
#[cfg(feature = "system-tray")]
@ -461,119 +472,6 @@ impl From<NativeImage> for NativeImageWrapper {
}
}
#[derive(Debug, Clone)]
pub struct GlobalShortcutWrapper(GlobalShortcut);
// SAFETY: usage outside of main thread is guarded, we use the event loop on such cases.
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for GlobalShortcutWrapper {}
/// Wrapper around [`WryShortcutManager`].
#[derive(Clone)]
pub struct GlobalShortcutManagerHandle<T: UserEvent> {
context: Context<T>,
shortcuts: Arc<Mutex<HashMap<String, (AcceleratorId, GlobalShortcutWrapper)>>>,
listeners: GlobalShortcutListeners,
}
// SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`.
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl<T: UserEvent> Sync for GlobalShortcutManagerHandle<T> {}
impl<T: UserEvent> fmt::Debug for GlobalShortcutManagerHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GlobalShortcutManagerHandle")
.field("context", &self.context)
.field("shortcuts", &self.shortcuts)
.finish()
}
}
impl<T: UserEvent> GlobalShortcutManager for GlobalShortcutManagerHandle<T> {
fn is_registered(&self, accelerator: &str) -> Result<bool> {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::IsRegistered(
accelerator.parse().expect("invalid accelerator"),
tx
))
)
}
fn register<F: Fn() + Send + 'static>(&mut self, accelerator: &str, handler: F) -> Result<()> {
let wry_accelerator: Accelerator = accelerator.parse().expect("invalid accelerator");
let id = wry_accelerator.clone().id();
let (tx, rx) = channel();
let shortcut = getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::Register(wry_accelerator, tx))
)??;
self.listeners.lock().unwrap().insert(id, Box::new(handler));
self
.shortcuts
.lock()
.unwrap()
.insert(accelerator.into(), (id, shortcut));
Ok(())
}
fn unregister_all(&mut self) -> Result<()> {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::UnregisterAll(tx))
)??;
self.listeners.lock().unwrap().clear();
self.shortcuts.lock().unwrap().clear();
Ok(())
}
fn unregister(&mut self, accelerator: &str) -> Result<()> {
if let Some((accelerator_id, shortcut)) = self.shortcuts.lock().unwrap().remove(accelerator) {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::GlobalShortcut(GlobalShortcutMessage::Unregister(shortcut, tx))
)??;
self.listeners.lock().unwrap().remove(&accelerator_id);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ClipboardManagerWrapper<T: UserEvent> {
context: Context<T>,
}
// SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`.
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl<T: UserEvent> Sync for ClipboardManagerWrapper<T> {}
impl<T: UserEvent> ClipboardManager for ClipboardManagerWrapper<T> {
fn read_text(&self) -> Result<Option<String>> {
let (tx, rx) = channel();
getter!(self, rx, Message::Clipboard(ClipboardMessage::ReadText(tx)))
}
fn write_text<V: Into<String>>(&mut self, text: V) -> Result<()> {
let (tx, rx) = channel();
getter!(
self,
rx,
Message::Clipboard(ClipboardMessage::WriteText(text.into(), tx))
)?;
Ok(())
}
}
/// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`WindowIcon`].
pub struct WryIcon(WryWindowIcon);
@ -1162,20 +1060,6 @@ pub enum TrayMessage {
Close,
}
#[derive(Debug, Clone)]
pub enum GlobalShortcutMessage {
IsRegistered(Accelerator, Sender<bool>),
Register(Accelerator, Sender<Result<GlobalShortcutWrapper>>),
Unregister(GlobalShortcutWrapper, Sender<Result<()>>),
UnregisterAll(Sender<Result<()>>),
}
#[derive(Debug, Clone)]
pub enum ClipboardMessage {
WriteText(String, Sender<()>),
ReadText(Sender<Option<String>>),
}
pub type CreateWebviewClosure<T> = Box<
dyn FnOnce(&EventLoopWindowTarget<Message<T>>, &WebContextStore) -> Result<WindowWrapper> + Send,
>;
@ -1192,7 +1076,9 @@ pub enum Message<T: 'static> {
Box<dyn FnOnce() -> (String, WryWindowBuilder) + Send>,
Sender<Result<Weak<Window>>>,
),
#[cfg(feature = "global-shortcut")]
GlobalShortcut(GlobalShortcutMessage),
#[cfg(feature = "clipboard")]
Clipboard(ClipboardMessage),
UserEvent(T),
}
@ -1204,7 +1090,9 @@ impl<T: UserEvent> Clone for Message<T> {
Self::Webview(i, m) => Self::Webview(*i, m.clone()),
#[cfg(feature = "system-tray")]
Self::Tray(m) => Self::Tray(m.clone()),
#[cfg(feature = "global-shortcut")]
Self::GlobalShortcut(m) => Self::GlobalShortcut(m.clone()),
#[cfg(feature = "clipboard")]
Self::Clipboard(m) => Self::Clipboard(m.clone()),
Self::UserEvent(t) => Self::UserEvent(t.clone()),
_ => unimplemented!(),
@ -1680,10 +1568,17 @@ impl<T: UserEvent> EventLoopProxy<T> for EventProxy<T> {
/// A Tauri [`Runtime`] wrapper around wry.
pub struct Wry<T: UserEvent> {
main_thread_id: ThreadId,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager_handle: GlobalShortcutManagerHandle<T>,
#[cfg(feature = "clipboard")]
clipboard_manager: Arc<Mutex<Clipboard>>,
#[cfg(feature = "clipboard")]
clipboard_manager_handle: ClipboardManagerWrapper<T>,
event_loop: EventLoop<Message<T>>,
windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
webview_id_map: WebviewIdStore,
@ -1698,18 +1593,24 @@ impl<T: UserEvent> fmt::Debug for Wry<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Wry");
d.field("main_thread_id", &self.main_thread_id)
.field("global_shortcut_manager", &self.global_shortcut_manager)
.field(
"global_shortcut_manager_handle",
&self.global_shortcut_manager_handle,
)
.field("clipboard_manager", &self.clipboard_manager)
.field("clipboard_manager_handle", &self.clipboard_manager_handle)
.field("event_loop", &self.event_loop)
.field("windows", &self.windows)
.field("web_context", &self.web_context);
#[cfg(feature = "system-tray")]
d.field("tray_context", &self.tray_context);
#[cfg(feature = "global-shortcut")]
d.field("global_shortcut_manager", &self.global_shortcut_manager)
.field(
"global_shortcut_manager_handle",
&self.global_shortcut_manager_handle,
);
#[cfg(feature = "clipboard")]
d.field("clipboard_manager", &self.clipboard_manager)
.field("clipboard_manager_handle", &self.clipboard_manager_handle);
d.finish()
}
}
@ -1790,11 +1691,15 @@ impl<T: UserEvent> RuntimeHandle<T> for WryHandle<T> {
impl<T: UserEvent> Wry<T> {
fn init(event_loop: EventLoop<Message<T>>) -> Result<Self> {
let proxy = event_loop.create_proxy();
let main_thread_id = current_thread().id();
let web_context = WebContextStore::default();
#[cfg(feature = "global-shortcut")]
let global_shortcut_manager = Arc::new(Mutex::new(WryShortcutManager::new(&event_loop)));
#[cfg(feature = "clipboard")]
let clipboard_manager = Arc::new(Mutex::new(Clipboard::new()));
let windows = Arc::new(Mutex::new(HashMap::default()));
let webview_id_map = WebviewIdStore::default();
let window_event_listeners = WindowEventListeners::default();
@ -1803,16 +1708,19 @@ impl<T: UserEvent> Wry<T> {
#[cfg(feature = "system-tray")]
let tray_context = TrayContext::default();
#[allow(unused_variables)]
let event_loop_context = Context {
webview_id_map: webview_id_map.clone(),
main_thread_id,
proxy,
proxy: event_loop.create_proxy(),
window_event_listeners: window_event_listeners.clone(),
menu_event_listeners: menu_event_listeners.clone(),
main_thread: DispatcherMainThreadContext {
window_target: event_loop.deref().clone(),
web_context: web_context.clone(),
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: global_shortcut_manager.clone(),
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
windows: windows.clone(),
#[cfg(feature = "system-tray")]
@ -1820,21 +1728,32 @@ impl<T: UserEvent> Wry<T> {
},
};
#[cfg(feature = "global-shortcut")]
let global_shortcut_listeners = GlobalShortcutListeners::default();
#[cfg(feature = "clipboard")]
#[allow(clippy::redundant_clone)]
let clipboard_manager_handle = ClipboardManagerWrapper {
context: event_loop_context.clone(),
};
Ok(Self {
main_thread_id,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager_handle: GlobalShortcutManagerHandle {
context: event_loop_context,
shortcuts: Default::default(),
listeners: global_shortcut_listeners,
},
#[cfg(feature = "clipboard")]
clipboard_manager,
#[cfg(feature = "clipboard")]
clipboard_manager_handle,
event_loop,
windows,
webview_id_map,
@ -1850,10 +1769,16 @@ impl<T: UserEvent> Wry<T> {
impl<T: UserEvent> Runtime<T> for Wry<T> {
type Dispatcher = WryDispatcher<T>;
type Handle = WryHandle<T>;
#[cfg(feature = "global-shortcut")]
type GlobalShortcutManager = GlobalShortcutManagerHandle<T>;
#[cfg(feature = "clipboard")]
type ClipboardManager = ClipboardManagerWrapper<T>;
#[cfg(feature = "system-tray")]
type TrayHandler = SystemTrayHandle<T>;
type EventLoopProxy = EventProxy<T>;
fn new() -> Result<Self> {
@ -1886,7 +1811,9 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
main_thread: DispatcherMainThreadContext {
window_target: self.event_loop.deref().clone(),
web_context: self.web_context.clone(),
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: self.global_shortcut_manager.clone(),
#[cfg(feature = "clipboard")]
clipboard_manager: self.clipboard_manager.clone(),
windows: self.windows.clone(),
#[cfg(feature = "system-tray")]
@ -1896,10 +1823,12 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
}
}
#[cfg(feature = "global-shortcut")]
fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager {
self.global_shortcut_manager_handle.clone()
}
#[cfg(feature = "clipboard")]
fn clipboard_manager(&self) -> Self::ClipboardManager {
self.clipboard_manager_handle.clone()
}
@ -1920,7 +1849,9 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
main_thread: DispatcherMainThreadContext {
window_target: self.event_loop.deref().clone(),
web_context: self.web_context.clone(),
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: self.global_shortcut_manager.clone(),
#[cfg(feature = "clipboard")]
clipboard_manager: self.clipboard_manager.clone(),
windows: self.windows.clone(),
#[cfg(feature = "system-tray")]
@ -2021,8 +1952,13 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
let menu_event_listeners = self.menu_event_listeners.clone();
#[cfg(feature = "system-tray")]
let tray_context = self.tray_context.clone();
#[cfg(feature = "global-shortcut")]
let global_shortcut_manager = self.global_shortcut_manager.clone();
#[cfg(feature = "global-shortcut")]
let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
#[cfg(feature = "clipboard")]
let clipboard_manager = self.clipboard_manager.clone();
let mut iteration = RunIteration::default();
@ -2043,8 +1979,11 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
windows: windows.clone(),
webview_id_map: webview_id_map.clone(),
window_event_listeners: &window_event_listeners,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: global_shortcut_manager.clone(),
#[cfg(feature = "global-shortcut")]
global_shortcut_manager_handle: &global_shortcut_manager_handle,
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
menu_event_listeners: &menu_event_listeners,
#[cfg(feature = "system-tray")]
@ -2063,10 +2002,16 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
let web_context = self.web_context;
let window_event_listeners = self.window_event_listeners.clone();
let menu_event_listeners = self.menu_event_listeners.clone();
#[cfg(feature = "system-tray")]
let tray_context = self.tray_context;
#[cfg(feature = "global-shortcut")]
let global_shortcut_manager = self.global_shortcut_manager.clone();
#[cfg(feature = "global-shortcut")]
let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
#[cfg(feature = "clipboard")]
let clipboard_manager = self.clipboard_manager.clone();
self.event_loop.run(move |event, event_loop, control_flow| {
@ -2079,8 +2024,11 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
webview_id_map: webview_id_map.clone(),
windows: windows.clone(),
window_event_listeners: &window_event_listeners,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: global_shortcut_manager.clone(),
#[cfg(feature = "global-shortcut")]
global_shortcut_manager_handle: &global_shortcut_manager_handle,
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
menu_event_listeners: &menu_event_listeners,
#[cfg(feature = "system-tray")]
@ -2097,8 +2045,11 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> {
webview_id_map: WebviewIdStore,
windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
window_event_listeners: &'a WindowEventListeners,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager_handle: &'a GlobalShortcutManagerHandle<T>,
#[cfg(feature = "clipboard")]
clipboard_manager: Arc<Mutex<Clipboard>>,
menu_event_listeners: &'a MenuEventListeners,
#[cfg(feature = "system-tray")]
@ -2108,7 +2059,9 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> {
struct UserMessageContext<'a> {
webview_id_map: WebviewIdStore,
window_event_listeners: &'a WindowEventListeners,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
#[cfg(feature = "clipboard")]
clipboard_manager: Arc<Mutex<Clipboard>>,
menu_event_listeners: &'a MenuEventListeners,
windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
@ -2126,7 +2079,9 @@ fn handle_user_message<T: UserEvent>(
webview_id_map,
window_event_listeners,
menu_event_listeners,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager,
#[cfg(feature = "clipboard")]
clipboard_manager,
windows,
#[cfg(feature = "system-tray")]
@ -2427,53 +2382,12 @@ fn handle_user_message<T: UserEvent>(
tray_context.items.lock().unwrap().clear();
}
},
Message::GlobalShortcut(message) => match message {
GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.is_registered(&accelerator),
)
.unwrap(),
GlobalShortcutMessage::Register(accelerator, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.register(accelerator)
.map(GlobalShortcutWrapper)
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
GlobalShortcutMessage::Unregister(shortcut, tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.unregister(shortcut.0)
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
GlobalShortcutMessage::UnregisterAll(tx) => tx
.send(
global_shortcut_manager
.lock()
.unwrap()
.unregister_all()
.map_err(|e| Error::GlobalShortcut(Box::new(e))),
)
.unwrap(),
},
Message::Clipboard(message) => match message {
ClipboardMessage::WriteText(text, tx) => {
clipboard_manager.lock().unwrap().write_text(text);
tx.send(()).unwrap();
}
ClipboardMessage::ReadText(tx) => tx
.send(clipboard_manager.lock().unwrap().read_text())
.unwrap(),
},
#[cfg(feature = "global-shortcut")]
Message::GlobalShortcut(message) => {
handle_global_shortcut_message(message, &global_shortcut_manager)
}
#[cfg(feature = "clipboard")]
Message::Clipboard(message) => handle_clipboard_message(message, &clipboard_manager),
Message::UserEvent(_) => (),
}
@ -2495,8 +2409,11 @@ fn handle_event_loop<T: UserEvent>(
webview_id_map,
windows,
window_event_listeners,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager_handle,
#[cfg(feature = "clipboard")]
clipboard_manager,
menu_event_listeners,
#[cfg(feature = "system-tray")]
@ -2523,6 +2440,7 @@ fn handle_event_loop<T: UserEvent>(
callback(RunEvent::Exit);
}
#[cfg(feature = "global-shortcut")]
Event::GlobalShortcutEvent(accelerator_id) => {
for (id, handler) in &*global_shortcut_manager_handle.listeners.lock().unwrap() {
if accelerator_id == *id {
@ -2701,7 +2619,9 @@ fn handle_event_loop<T: UserEvent>(
UserMessageContext {
webview_id_map,
window_event_listeners,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager,
#[cfg(feature = "clipboard")]
clipboard_manager,
menu_event_listeners,
windows,

View File

@ -46,3 +46,5 @@ gtk = { version = "0.15", features = [ "v3_20" ] }
devtools = [ ]
system-tray = [ ]
macos-private-api = [ ]
global-shortcut = [ ]
clipboard = [ ]

View File

@ -127,6 +127,7 @@ pub enum Error {
#[error("failed to get monitor")]
FailedToGetMonitor,
/// Global shortcut error.
#[cfg(feature = "global-shortcut")]
#[error(transparent)]
GlobalShortcut(Box<dyn std::error::Error + Send + Sync>),
#[error("Invalid header name: {0}")]
@ -294,6 +295,7 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
}
/// A global shortcut manager.
#[cfg(feature = "global-shortcut")]
pub trait GlobalShortcutManager: Debug + Clone + Send + Sync {
/// Whether the application has registered the given `accelerator`.
fn is_registered(&self, accelerator: &str) -> Result<bool>;
@ -309,6 +311,7 @@ pub trait GlobalShortcutManager: Debug + Clone + Send + Sync {
}
/// Clipboard manager.
#[cfg(feature = "clipboard")]
pub trait ClipboardManager: Debug + Clone + Send + Sync {
/// Writes the text into the clipboard as plain text.
fn write_text<T: Into<String>>(&mut self, text: T) -> Result<()>;
@ -327,8 +330,10 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
/// The runtime handle type.
type Handle: RuntimeHandle<T, Runtime = Self>;
/// The global shortcut manager type.
#[cfg(feature = "global-shortcut")]
type GlobalShortcutManager: GlobalShortcutManager;
/// The clipboard manager type.
#[cfg(feature = "clipboard")]
type ClipboardManager: ClipboardManager;
/// The tray handler type.
#[cfg(feature = "system-tray")]
@ -351,9 +356,11 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
fn handle(&self) -> Self::Handle;
/// Gets the global shortcut manager.
#[cfg(feature = "global-shortcut")]
fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager;
/// Gets the clipboard manager.
#[cfg(feature = "clipboard")]
fn clipboard_manager(&self) -> Self::ClipboardManager;
/// Create a new webview window.

View File

@ -104,7 +104,8 @@ version = "0.30.0"
features = [ "Win32_Foundation" ]
[build-dependencies]
cfg_aliases = "0.1.1"
heck = "0.4"
once_cell = "1.10"
[dev-dependencies]
mockito = "0.31"
@ -141,6 +142,8 @@ shell-open-api = [ "open", "regex", "tauri-macros/shell-scope" ]
fs-extract-api = [ "zip" ]
reqwest-client = [ "reqwest", "bytes" ]
process-command-api = [ "shared_child", "os_pipe" ]
global-shortcut = [ "tauri-runtime/global-shortcut", "tauri-runtime-wry/global-shortcut" ]
clipboard = [ "tauri-runtime/clipboard", "tauri-runtime-wry/clipboard" ]
dialog = [ "rfd" ]
notification = [ "notify-rust" ]
cli = [ "clap" ]
@ -167,8 +170,8 @@ api-all = [
"window-all"
]
clipboard-all = [ "clipboard-write-text", "clipboard-read-text" ]
clipboard-read-text = [ ]
clipboard-write-text = [ ]
clipboard-read-text = [ "clipboard" ]
clipboard-write-text = [ "clipboard" ]
dialog-all = [ "dialog-open", "dialog-save", "dialog-message", "dialog-ask" ]
dialog-ask = [ "dialog" ]
dialog-confirm = [ "dialog" ]
@ -193,7 +196,7 @@ fs-remove-dir = [ ]
fs-remove-file = [ ]
fs-rename-file = [ ]
fs-write-file = [ ]
global-shortcut-all = [ ]
global-shortcut-all = [ "global-shortcut" ]
http-all = [ "http-request" ]
http-request = [ "http-api" ]
notification-all = [ "notification", "dialog-ask" ]

View File

@ -2,106 +2,160 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use cfg_aliases::cfg_aliases;
use heck::ToSnakeCase;
use once_cell::sync::OnceCell;
fn main() {
cfg_aliases! {
custom_protocol: { feature = "custom-protocol" },
dev: { not(feature = "custom-protocol") },
updater: { any(feature = "updater", feature = "__updater-docs") },
use std::{path::Path, sync::Mutex};
api_all: { feature = "api-all" },
static CHECKED_FEATURES: OnceCell<Mutex<Vec<String>>> = OnceCell::new();
// fs
fs_all: { any(api_all, feature = "fs-all") },
fs_read_file: { any(fs_all, feature = "fs-read-file") },
fs_write_file: { any(fs_all, feature = "fs-write-file") },
fs_write_binary_file: { any(fs_all, feature = "fs-write-binary-file") },
fs_read_dir: { any(fs_all, feature = "fs-read-dir") },
fs_copy_file: { any(fs_all, feature = "fs-copy-file") },
fs_create_dir: { any(fs_all, feature = "fs-create_dir") },
fs_remove_dir: { any(fs_all, feature = "fs-remove-dir") },
fs_remove_file: { any(fs_all, feature = "fs-remove-file") },
fs_rename_file: { any(fs_all, feature = "fs-rename-file") },
// checks if the given Cargo feature is enabled.
fn has_feature(feature: &str) -> bool {
CHECKED_FEATURES
.get_or_init(Default::default)
.lock()
.unwrap()
.push(feature.to_string());
// window
window_all: { any(api_all, feature = "window-all") },
window_create: { any(window_all, feature = "window-create") },
window_center: { any(window_all, feature = "window-center") },
window_request_user_attention: { any(window_all, feature = "window-request-user-attention") },
window_set_resizable: { any(window_all, feature = "window-set-resizable") },
window_set_title: { any(window_all, feature = "window-set-title") },
window_maximize: { any(window_all, feature = "window-maximize") },
window_unmaximize: { any(window_all, feature = "window-unmaximize") },
window_minimize: { any(window_all, feature = "window-minimize") },
window_unminimize: { any(window_all, feature = "window-unminimize") },
window_show: { any(window_all, feature = "window-show") },
window_hide: { any(window_all, feature = "window-hide") },
window_close: { any(window_all, feature = "window-close") },
window_set_decorations: { any(window_all, feature = "window-set-decorations") },
window_set_always_on_top: { any(window_all, feature = "window-set-always-on-top") },
window_set_size: { any(window_all, feature = "window-set-size") },
window_set_min_size: { any(window_all, feature = "window-set-min-size") },
window_set_max_size: { any(window_all, feature = "window-set-max-size") },
window_set_position: { any(window_all, feature = "window-set-position") },
window_set_fullscreen: { any(window_all, feature = "window-set-fullscreen") },
window_set_focus: { any(window_all, feature = "window-set-focus") },
window_set_icon: { any(window_all, feature = "window-set-icon") },
window_set_skip_taskbar: { any(window_all, feature = "window-set-skip-taskbar") },
window_set_cursor_grab: { any(window_all, feature = "window-set-cursor-grab") },
window_set_cursor_visible: { any(window_all, feature = "window-set-cursor-visible") },
window_set_cursor_icon: { any(window_all, feature = "window-set-cursor-icon") },
window_set_cursor_position: { any(window_all, feature = "window-set-cursor-position") },
window_start_dragging: { any(window_all, feature = "window-start-dragging") },
window_print: { any(window_all, feature = "window-print") },
// when a feature is enabled, Cargo sets the `CARGO_FEATURE_<name` env var to 1
// https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
std::env::var(format!(
"CARGO_FEATURE_{}",
feature.to_snake_case().to_uppercase()
))
.map(|x| x == "1")
.unwrap_or(false)
}
// shell
shell_all: { any(api_all, feature = "shell-all") },
shell_execute: { any(shell_all, feature = "shell-execute") },
shell_sidecar: { any(shell_all, feature = "shell-sidecar") },
shell_open: { any(shell_all, feature = "shell-open") },
// helper for the shell scope functionality
shell_scope: { any(shell_execute, shell_sidecar, feature = "shell-open-api") },
// dialog
dialog_all: { any(api_all, feature = "dialog-all") },
dialog_open: { any(dialog_all, feature = "dialog-open") },
dialog_save: { any(dialog_all, feature = "dialog-save") },
dialog_message: { any(dialog_all, feature = "dialog-message") },
dialog_ask: { any(dialog_all, feature = "dialog-ask") },
dialog_confirm: { any(dialog_all, feature = "dialog-confirm") },
// http
http_all: { any(api_all, feature = "http-all") },
http_request: { any(http_all, feature = "http-request") },
// cli
cli: { feature = "cli" },
// notification
notification_all: { any(api_all, feature = "notification-all") },
// global shortcut
global_shortcut_all: { any(api_all, feature = "global_shortcut-all") },
// os
os_all: { any(api_all, feature = "os-all") },
// path
path_all: { any(api_all, feature = "path-all") },
// protocol
protocol_all: { any(api_all, feature = "protocol-all") },
protocol_asset: { any(protocol_all, feature = "protocol-asset") },
// process
process_all: { any(api_all, feature = "process-all") },
process_relaunch: { any(protocol_all, feature = "process-relaunch") },
process_exit: { any(protocol_all, feature = "process-exit") },
// clipboard
clipboard_all: { any(api_all, feature = "clipboard-all") },
clipboard_write_text: { any(protocol_all, feature = "clipboard-write-text") },
clipboard_read_text: { any(protocol_all, feature = "clipboard-read-text") },
// creates a cfg alias if `has_feature` is true.
// `alias` must be a snake case string.
fn alias(alias: &str, has_feature: bool) {
if has_feature {
println!("cargo:rustc-cfg={}", alias);
}
}
fn main() {
alias("custom_protocol", has_feature("custom-protocol"));
alias("dev", !has_feature("custom-protocol"));
alias(
"updater",
has_feature("updater") || has_feature("__updater-docs"),
);
let api_all = has_feature("api-all");
alias("api_all", api_all);
alias_module(
"fs",
&[
"read-file",
"write-file",
"read-dir",
"copy-file",
"create-dir",
"remove-dir",
"remove-file",
"rename-file",
],
api_all,
);
alias_module(
"window",
&[
"create",
"center",
"request-user-attention",
"set-resizable",
"set-title",
"maximize",
"unmaximize",
"minimize",
"unminimize",
"show",
"hide",
"close",
"set-decorations",
"set-always-on-top",
"set-size",
"set-min-size",
"set-max-size",
"set-position",
"set-fullscreen",
"set-focus",
"set-icon",
"set-skip-taskbar",
"set-cursor-grab",
"set-cursor-visible",
"set-cursor-icon",
"set-cursor-position",
"start-dragging",
"print",
],
api_all,
);
alias_module("shell", &["execute", "sidecar", "open"], api_all);
// helper for the command module macro
let shell_script = has_feature("shell-execute") || has_feature("shell-sidecar");
alias("shell_script", shell_script);
alias("shell_scope", has_feature("shell-open-api") || shell_script);
alias_module(
"dialog",
&["open", "save", "message", "ask", "confirm"],
api_all,
);
alias_module("http", &["request"], api_all);
alias("cli", has_feature("cli"));
alias_module("notification", &[], api_all);
alias_module("global-shortcut", &[], api_all);
alias_module("os", &[], api_all);
alias_module("path", &[], api_all);
alias_module("protocol", &["asset"], api_all);
alias_module("process", &["relaunch", "exit"], api_all);
alias_module("clipboard", &["write-text", "read-text"], api_all);
let checked_features_out_path =
Path::new(&std::env::var("OUT_DIR").unwrap()).join("checked_features");
std::fs::write(
&checked_features_out_path,
&CHECKED_FEATURES.get().unwrap().lock().unwrap().join(","),
)
.expect("failed to write checked_features file");
}
// create aliases for the given module with its apis.
// each api is translated into a feature flag in the format of `<module>-<api>`
// and aliased as `<module_snake_case>_<api_snake_case>`.
//
// The `<module>-all` feature is also aliased to `<module>_all`.
//
// If any of the features is enabled, the `<module_snake_case>_any` alias is created.
//
// Note that both `module` and `apis` strings must be written in kebab case.
fn alias_module(module: &str, apis: &[&str], api_all: bool) {
let all_feature_name = format!("{}-all", module);
let all = has_feature(&all_feature_name) || api_all;
alias(&all_feature_name.to_snake_case(), all);
let mut any = all;
for api in apis {
let has = has_feature(&format!("{}-{}", module, api)) || all;
alias(
&format!("{}_{}", module.to_snake_case(), api.to_snake_case()),
has,
);
any = any || has;
}
alias(&format!("{}_any", module.to_snake_case()), any);
}

File diff suppressed because one or more lines are too long

View File

@ -34,10 +34,12 @@ impl SafePathBuf {
}
}
#[allow(dead_code)]
pub unsafe fn new_unchecked(path: std::path::PathBuf) -> Self {
Self(path)
}
#[allow(dead_code)]
pub fn display(&self) -> Display<'_> {
self.0.display()
}

View File

@ -287,7 +287,9 @@ impl<R: Runtime> AssetResolver<R> {
pub struct AppHandle<R: Runtime> {
runtime_handle: R::Handle,
manager: WindowManager<R>,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: R::GlobalShortcutManager,
#[cfg(feature = "clipboard")]
clipboard_manager: R::ClipboardManager,
#[cfg(feature = "system-tray")]
tray_handle: Option<tray::SystemTrayHandle<R>>,
@ -337,7 +339,9 @@ impl<R: Runtime> Clone for AppHandle<R> {
Self {
runtime_handle: self.runtime_handle.clone(),
manager: self.manager.clone(),
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: self.global_shortcut_manager.clone(),
#[cfg(feature = "clipboard")]
clipboard_manager: self.clipboard_manager.clone(),
#[cfg(feature = "system-tray")]
tray_handle: self.tray_handle.clone(),
@ -442,7 +446,9 @@ impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
pub struct App<R: Runtime> {
runtime: Option<R>,
manager: WindowManager<R>,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: R::GlobalShortcutManager,
#[cfg(feature = "clipboard")]
clipboard_manager: R::ClipboardManager,
#[cfg(feature = "system-tray")]
tray_handle: Option<tray::SystemTrayHandle<R>>,
@ -510,11 +516,15 @@ macro_rules! shared_app_impl {
}
/// Gets a copy of the global shortcut manager instance.
#[cfg(feature = "global-shortcut")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "global-shortcut")))]
pub fn global_shortcut_manager(&self) -> R::GlobalShortcutManager {
self.global_shortcut_manager.clone()
}
/// Gets a copy of the clipboard manager instance.
#[cfg(feature = "clipboard")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "clipboard")))]
pub fn clipboard_manager(&self) -> R::ClipboardManager {
self.clipboard_manager.clone()
}
@ -1233,20 +1243,28 @@ impl<R: Runtime> Builder<R> {
let runtime = R::new()?;
let runtime_handle = runtime.handle();
#[cfg(feature = "global-shortcut")]
let global_shortcut_manager = runtime.global_shortcut_manager();
#[cfg(feature = "clipboard")]
let clipboard_manager = runtime.clipboard_manager();
let mut app = App {
runtime: Some(runtime),
manager: manager.clone(),
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: global_shortcut_manager.clone(),
#[cfg(feature = "clipboard")]
clipboard_manager: clipboard_manager.clone(),
#[cfg(feature = "system-tray")]
tray_handle: None,
handle: AppHandle {
runtime_handle,
manager,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager,
#[cfg(feature = "clipboard")]
clipboard_manager,
#[cfg(feature = "system-tray")]
tray_handle: None,

View File

@ -13,18 +13,27 @@ use serde_json::Value as JsonValue;
use std::sync::Arc;
mod app;
#[cfg(cli)]
mod cli;
#[cfg(clipboard_any)]
mod clipboard;
#[cfg(dialog_any)]
mod dialog;
mod event;
#[allow(unused_imports)]
#[cfg(fs_any)]
mod file_system;
#[cfg(global_shortcut_any)]
mod global_shortcut;
#[cfg(http_any)]
mod http;
mod notification;
#[cfg(os_any)]
mod operating_system;
#[cfg(path_any)]
mod path;
#[cfg(process_any)]
mod process;
#[cfg(shell_any)]
mod shell;
mod window;
@ -63,18 +72,28 @@ impl<T: Serialize> From<T> for InvokeResponse {
#[serde(tag = "module", content = "message")]
enum Module {
App(app::Cmd),
#[cfg(process_any)]
Process(process::Cmd),
#[cfg(fs_any)]
Fs(file_system::Cmd),
#[cfg(os_any)]
Os(operating_system::Cmd),
#[cfg(path_any)]
Path(path::Cmd),
Window(Box<window::Cmd>),
#[cfg(shell_any)]
Shell(shell::Cmd),
Event(event::Cmd),
#[cfg(dialog_any)]
Dialog(dialog::Cmd),
#[cfg(cli)]
Cli(cli::Cmd),
Notification(notification::Cmd),
#[cfg(http_any)]
Http(http::Cmd),
#[cfg(global_shortcut_any)]
GlobalShortcut(global_shortcut::Cmd),
#[cfg(clipboard_any)]
Clipboard(clipboard::Cmd),
}
@ -98,24 +117,28 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(process_any)]
Self::Process(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(fs_any)]
Self::Fs(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(path_any)]
Self::Path(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(os_any)]
Self::Os(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@ -129,6 +152,7 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(shell_any)]
Self::Shell(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@ -141,12 +165,14 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(dialog_any)]
Self::Dialog(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(cli)]
Self::Cli(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@ -159,6 +185,7 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(http_any)]
Self::Http(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@ -166,12 +193,14 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(global_shortcut_any)]
Self::GlobalShortcut(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(clipboard_any)]
Self::Clipboard(cmd) => resolver.respond_async(async move {
cmd
.run(context)
@ -196,11 +225,28 @@ pub(crate) fn handle<R: Runtime>(
} = message;
if let JsonValue::Object(ref mut obj) = payload {
obj.insert("module".to_string(), JsonValue::String(module));
obj.insert("module".to_string(), JsonValue::String(module.clone()));
}
match serde_json::from_value::<Module>(payload) {
Ok(module) => module.run(window, resolver, config, package_info.clone()),
Err(e) => resolver.reject(e.to_string()),
Err(e) => {
let message = e.to_string();
if message.starts_with("unknown variant") {
let mut s = message.split('`');
s.next();
if let Some(unknown_variant_name) = s.next() {
if unknown_variant_name == module {
return resolver.reject(format!(
"The `{}` module is not enabled. You must enable one of its APIs in the allowlist.",
module
));
} else if module == "Window" {
return resolver.reject(window::into_allowlist_error(unknown_variant_name).to_string());
}
}
}
resolver.reject(message);
}
}
}

View File

@ -5,9 +5,10 @@
use super::InvokeContext;
use crate::Runtime;
use serde::Deserialize;
use tauri_macros::CommandModule;
use tauri_macros::{command_enum, CommandModule};
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
#[allow(clippy::enum_variant_names)]

View File

@ -2,13 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::{InvokeContext, InvokeResponse};
use crate::Runtime;
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
/// The API descriptor.
#[derive(Deserialize, CommandModule)]
#[command_enum]
#[derive(CommandModule, Deserialize)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
/// The get CLI matches API.
@ -16,21 +19,26 @@ pub enum Cmd {
}
impl Cmd {
#[module_command_handler(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)")]
#[module_command_handler(cli)]
fn cli_matches<R: Runtime>(context: InvokeContext<R>) -> super::Result<InvokeResponse> {
if let Some(cli) = &context.config.tauri.cli {
crate::api::cli::get_matches(cli, &context.package_info)
.map(Into::into)
.map_err(Into::into)
} else {
Err(crate::Error::ApiNotAllowlisted("CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)".into()).into_anyhow())
Err(crate::error::into_anyhow("CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)"))
}
}
#[cfg(not(cli))]
fn cli_matches<R: Runtime>(_: InvokeContext<R>) -> super::Result<InvokeResponse> {
Err(crate::error::into_anyhow("CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)"))
}
}
#[cfg(test)]
mod tests {
#[tauri_macros::module_command_test(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)")]
#[tauri_macros::module_command_test(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)", runtime)]
#[quickcheck_macros::quickcheck]
fn cli_matches() {
let res = super::Cmd::cli_matches(crate::test::mock_invoke_context());

View File

@ -2,25 +2,29 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::InvokeContext;
#[cfg(any(clipboard_write_text, clipboard_read_text))]
use crate::runtime::ClipboardManager;
use crate::Runtime;
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", content = "data", rename_all = "camelCase")]
pub enum Cmd {
/// Write a text string to the clipboard.
#[cmd(clipboard_write_text, "clipboard > writeText")]
WriteText(String),
/// Read clipboard content as text.
ReadText,
}
impl Cmd {
#[module_command_handler(clipboard_write_text, "clipboard > writeText")]
#[module_command_handler(clipboard_write_text)]
fn write_text<R: Runtime>(context: InvokeContext<R>, text: String) -> super::Result<()> {
context
.window
@ -30,7 +34,7 @@ impl Cmd {
.map_err(crate::error::into_anyhow)
}
#[module_command_handler(clipboard_read_text, "clipboard > readText")]
#[module_command_handler(clipboard_read_text)]
fn read_text<R: Runtime>(context: InvokeContext<R>) -> super::Result<Option<String>> {
context
.window
@ -39,6 +43,11 @@ impl Cmd {
.read_text()
.map_err(crate::error::into_anyhow)
}
#[cfg(not(clipboard_read_text))]
fn read_text<R: Runtime>(_: InvokeContext<R>) -> super::Result<()> {
Err(crate::Error::ApiNotAllowlisted("clipboard > readText".into()).into_anyhow())
}
}
#[cfg(test)]
@ -48,16 +57,20 @@ mod tests {
fn write_text(text: String) {
let ctx = crate::test::mock_invoke_context();
super::Cmd::write_text(ctx.clone(), text.clone()).unwrap();
#[cfg(clipboard_read_text)]
assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text));
}
#[tauri_macros::module_command_test(clipboard_read_text, "clipboard > readText")]
#[tauri_macros::module_command_test(clipboard_read_text, "clipboard > readText", runtime)]
#[quickcheck_macros::quickcheck]
fn read_text() {
let ctx = crate::test::mock_invoke_context();
assert_eq!(super::Cmd::read_text(ctx.clone()).unwrap(), None);
let text = "Tauri!".to_string();
super::Cmd::write_text(ctx.clone(), text.clone()).unwrap();
assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text));
#[cfg(clipboard_write_text)]
{
let text = "Tauri!".to_string();
super::Cmd::write_text(ctx.clone(), text.clone()).unwrap();
assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text));
}
}
}

View File

@ -2,12 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::{InvokeContext, InvokeResponse};
use crate::Runtime;
#[cfg(any(dialog_open, dialog_save))]
use crate::{api::dialog::blocking::FileDialogBuilder, Manager, Scopes};
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
use std::path::PathBuf;
@ -56,25 +58,25 @@ pub struct SaveDialogOptions {
}
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
#[allow(clippy::enum_variant_names)]
pub enum Cmd {
/// The open dialog API.
OpenDialog {
options: OpenDialogOptions,
},
#[cmd(dialog_open, "dialog > open")]
OpenDialog { options: OpenDialogOptions },
/// The save dialog API.
SaveDialog {
options: SaveDialogOptions,
},
MessageDialog {
message: String,
},
#[cmd(dialog_save, "dialog > save")]
SaveDialog { options: SaveDialogOptions },
#[cmd(dialog_message, "dialog > message")]
MessageDialog { message: String },
#[cmd(dialog_ask, "dialog > ask")]
AskDialog {
title: Option<String>,
message: String,
},
#[cmd(dialog_confirm, "dialog > confirm")]
ConfirmDialog {
title: Option<String>,
message: String,
@ -82,7 +84,7 @@ pub enum Cmd {
}
impl Cmd {
#[module_command_handler(dialog_open, "dialog > open")]
#[module_command_handler(dialog_open)]
#[allow(unused_variables)]
fn open_dialog<R: Runtime>(
context: InvokeContext<R>,
@ -130,7 +132,7 @@ impl Cmd {
Ok(res)
}
#[module_command_handler(dialog_save, "dialog > save")]
#[module_command_handler(dialog_save)]
#[allow(unused_variables)]
fn save_dialog<R: Runtime>(
context: InvokeContext<R>,
@ -159,7 +161,7 @@ impl Cmd {
Ok(path)
}
#[module_command_handler(dialog_message, "dialog > message")]
#[module_command_handler(dialog_message)]
fn message_dialog<R: Runtime>(context: InvokeContext<R>, message: String) -> super::Result<()> {
crate::api::dialog::blocking::message(
Some(&context.window),
@ -169,7 +171,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(dialog_ask, "dialog > ask")]
#[module_command_handler(dialog_ask)]
fn ask_dialog<R: Runtime>(
context: InvokeContext<R>,
title: Option<String>,
@ -182,7 +184,7 @@ impl Cmd {
))
}
#[module_command_handler(dialog_confirm, "dialog > confirm")]
#[module_command_handler(dialog_confirm)]
fn confirm_dialog<R: Runtime>(
context: InvokeContext<R>,
title: Option<String>,

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::InvokeContext;
use crate::{
api::ipc::CallbackFn,
@ -12,7 +14,7 @@ use crate::{
Manager, Runtime,
};
use serde::{de::Deserializer, Deserialize};
use tauri_macros::CommandModule;
use tauri_macros::{command_enum, CommandModule};
pub struct EventId(String);
@ -51,6 +53,7 @@ impl<'de> Deserialize<'de> for WindowLabel {
}
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use crate::{
api::{
dir,
@ -19,7 +21,7 @@ use serde::{
de::{Deserializer, Error as DeError},
Deserialize, Serialize,
};
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
use std::fmt::{Debug, Formatter};
use std::{
@ -50,52 +52,62 @@ pub struct FileOperationOptions {
}
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub(crate) enum Cmd {
/// The read binary file API.
#[cmd(fs_read_file, "fs > readFile")]
ReadFile {
path: SafePathBuf,
options: Option<FileOperationOptions>,
},
/// The read binary file API.
#[cmd(fs_read_file, "fs > readFile")]
ReadTextFile {
path: SafePathBuf,
options: Option<FileOperationOptions>,
},
/// The write file API.
#[cmd(fs_write_file, "fs > writeFile")]
WriteFile {
path: SafePathBuf,
contents: Vec<u8>,
options: Option<FileOperationOptions>,
},
/// The read dir API.
#[cmd(fs_read_dir, "fs > readDir")]
ReadDir {
path: SafePathBuf,
options: Option<DirOperationOptions>,
},
/// The copy file API.
#[cmd(fs_copy_file, "fs > copyFile")]
CopyFile {
source: SafePathBuf,
destination: SafePathBuf,
options: Option<FileOperationOptions>,
},
/// The create dir API.
#[cmd(fs_create_dir, "fs > createDir")]
CreateDir {
path: SafePathBuf,
options: Option<DirOperationOptions>,
},
/// The remove dir API.
#[cmd(fs_remove_dir, "fs > removeDir")]
RemoveDir {
path: SafePathBuf,
options: Option<DirOperationOptions>,
},
/// The remove file API.
#[cmd(fs_remove_file, "fs > removeFile")]
RemoveFile {
path: SafePathBuf,
options: Option<FileOperationOptions>,
},
/// The rename file API.
#[cmd(fs_rename_file, "fs > renameFile")]
#[serde(rename_all = "camelCase")]
RenameFile {
old_path: SafePathBuf,
@ -105,7 +117,7 @@ pub(crate) enum Cmd {
}
impl Cmd {
#[module_command_handler(fs_read_file, "fs > readFile")]
#[module_command_handler(fs_read_file)]
fn read_file<R: Runtime>(
context: InvokeContext<R>,
path: SafePathBuf,
@ -123,7 +135,7 @@ impl Cmd {
.map_err(Into::into)
}
#[module_command_handler(fs_read_file, "fs > readFile")]
#[module_command_handler(fs_read_file)]
fn read_text_file<R: Runtime>(
context: InvokeContext<R>,
path: SafePathBuf,
@ -141,7 +153,7 @@ impl Cmd {
.map_err(Into::into)
}
#[module_command_handler(fs_write_file, "fs > writeFile")]
#[module_command_handler(fs_write_file)]
fn write_file<R: Runtime>(
context: InvokeContext<R>,
path: SafePathBuf,
@ -161,7 +173,7 @@ impl Cmd {
.and_then(|mut f| f.write_all(&contents).map_err(|err| err.into()))
}
#[module_command_handler(fs_read_dir, "fs > readDir")]
#[module_command_handler(fs_read_dir)]
fn read_dir<R: Runtime>(
context: InvokeContext<R>,
path: SafePathBuf,
@ -184,7 +196,7 @@ impl Cmd {
.map_err(Into::into)
}
#[module_command_handler(fs_copy_file, "fs > copyFile")]
#[module_command_handler(fs_copy_file)]
fn copy_file<R: Runtime>(
context: InvokeContext<R>,
source: SafePathBuf,
@ -215,7 +227,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(fs_create_dir, "fs > createDir")]
#[module_command_handler(fs_create_dir)]
fn create_dir<R: Runtime>(
context: InvokeContext<R>,
path: SafePathBuf,
@ -244,7 +256,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(fs_remove_dir, "fs > removeDir")]
#[module_command_handler(fs_remove_dir)]
fn remove_dir<R: Runtime>(
context: InvokeContext<R>,
path: SafePathBuf,
@ -273,7 +285,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(fs_remove_file, "fs > removeFile")]
#[module_command_handler(fs_remove_file)]
fn remove_file<R: Runtime>(
context: InvokeContext<R>,
path: SafePathBuf,
@ -291,7 +303,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(fs_rename_file, "fs > renameFile")]
#[module_command_handler(fs_rename_file)]
fn rename_file<R: Runtime>(
context: InvokeContext<R>,
old_path: SafePathBuf,

View File

@ -2,38 +2,45 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::InvokeContext;
use crate::{api::ipc::CallbackFn, Runtime};
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
#[cfg(global_shortcut_all)]
use crate::runtime::GlobalShortcutManager;
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
/// Register a global shortcut.
#[cmd(global_shortcut_all, "globalShortcut > all")]
Register {
shortcut: String,
handler: CallbackFn,
},
/// Register a list of global shortcuts.
#[cmd(global_shortcut_all, "globalShortcut > all")]
RegisterAll {
shortcuts: Vec<String>,
handler: CallbackFn,
},
/// Unregister a global shortcut.
#[cmd(global_shortcut_all, "globalShortcut > all")]
Unregister { shortcut: String },
/// Unregisters all registered shortcuts.
UnregisterAll,
/// Determines whether the given hotkey is registered or not.
#[cmd(global_shortcut_all, "globalShortcut > all")]
IsRegistered { shortcut: String },
}
impl Cmd {
#[module_command_handler(global_shortcut_all, "globalShortcut > all")]
#[module_command_handler(global_shortcut_all)]
fn register<R: Runtime>(
context: InvokeContext<R>,
shortcut: String,
@ -44,7 +51,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(global_shortcut_all, "globalShortcut > all")]
#[module_command_handler(global_shortcut_all)]
fn register_all<R: Runtime>(
context: InvokeContext<R>,
shortcuts: Vec<String>,
@ -57,7 +64,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(global_shortcut_all, "globalShortcut > all")]
#[module_command_handler(global_shortcut_all)]
fn unregister<R: Runtime>(context: InvokeContext<R>, shortcut: String) -> super::Result<()> {
context
.window
@ -68,7 +75,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(global_shortcut_all, "globalShortcut > all")]
#[module_command_handler(global_shortcut_all)]
fn unregister_all<R: Runtime>(context: InvokeContext<R>) -> super::Result<()> {
context
.window
@ -79,7 +86,12 @@ impl Cmd {
Ok(())
}
#[module_command_handler(global_shortcut_all, "globalShortcut > all")]
#[cfg(not(global_shortcut_all))]
fn unregister_all<R: Runtime>(_: InvokeContext<R>) -> super::Result<()> {
Err(crate::Error::ApiNotAllowlisted("globalShortcut > all".into()).into_anyhow())
}
#[module_command_handler(global_shortcut_all)]
fn is_registered<R: Runtime>(context: InvokeContext<R>, shortcut: String) -> super::Result<bool> {
context
.window
@ -139,7 +151,7 @@ mod tests {
assert!(!super::Cmd::is_registered(ctx, shortcut).unwrap());
}
#[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all")]
#[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all", runtime)]
#[quickcheck_macros::quickcheck]
fn unregister_all() {
let shortcuts = vec!["CTRL+X".to_string(), "SUPER+C".to_string(), "D".to_string()];

View File

@ -2,10 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::InvokeContext;
use crate::Runtime;
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
#[cfg(http_request)]
use std::{
@ -20,6 +22,7 @@ type ClientBuilder = ();
#[cfg(not(http_request))]
type HttpRequestBuilder = ();
#[cfg(not(http_request))]
#[allow(dead_code)]
type ResponseData = ();
type ClientId = u32;
@ -34,15 +37,19 @@ fn clients() -> &'static ClientStore {
}
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[cmd(async)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
/// Create a new HTTP client.
#[cmd(http_request, "http > request")]
CreateClient { options: Option<ClientBuilder> },
/// Drop a HTTP client.
#[cmd(http_request, "http > request")]
DropClient { client: ClientId },
/// The HTTP request API.
#[cmd(http_request, "http > request")]
HttpRequest {
client: ClientId,
options: Box<HttpRequestBuilder>,
@ -50,7 +57,7 @@ pub enum Cmd {
}
impl Cmd {
#[module_command_handler(http_request, "http > request")]
#[module_command_handler(http_request)]
async fn create_client<R: Runtime>(
_context: InvokeContext<R>,
options: Option<ClientBuilder>,
@ -62,7 +69,7 @@ impl Cmd {
Ok(id)
}
#[module_command_handler(http_request, "http > request")]
#[module_command_handler(http_request)]
async fn drop_client<R: Runtime>(
_context: InvokeContext<R>,
client: ClientId,
@ -72,7 +79,7 @@ impl Cmd {
Ok(())
}
#[module_command_handler(http_request, "http > request")]
#[module_command_handler(http_request)]
async fn http_request<R: Runtime>(
context: InvokeContext<R>,
client_id: ClientId,
@ -115,7 +122,7 @@ impl Cmd {
mod tests {
use super::{ClientBuilder, ClientId};
#[tauri_macros::module_command_test(http_request, "http > request", async)]
#[tauri_macros::module_command_test(http_request, "http > request")]
#[quickcheck_macros::quickcheck]
fn create_client(options: Option<ClientBuilder>) {
assert!(crate::async_runtime::block_on(super::Cmd::create_client(
@ -125,7 +132,7 @@ mod tests {
.is_ok());
}
#[tauri_macros::module_command_test(http_request, "http > request", async)]
#[tauri_macros::module_command_test(http_request, "http > request")]
#[quickcheck_macros::quickcheck]
fn drop_client(client_id: ClientId) {
crate::async_runtime::block_on(async move {

View File

@ -2,10 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::InvokeContext;
use crate::Runtime;
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
#[cfg(notification_all)]
use crate::{api::notification::Notification, Env, Manager};
@ -28,10 +30,12 @@ pub struct NotificationOptions {
}
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
/// The show notification API.
#[cmd(notification_all, "notification > all")]
Notification { options: NotificationOptions },
/// The request notification permission API.
RequestNotificationPermission,
@ -40,7 +44,7 @@ pub enum Cmd {
}
impl Cmd {
#[module_command_handler(notification_all, "notification > all")]
#[module_command_handler(notification_all)]
fn notification<R: Runtime>(
context: InvokeContext<R>,
options: NotificationOptions,

View File

@ -2,13 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::InvokeContext;
use crate::Runtime;
use serde::Deserialize;
use std::path::PathBuf;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
@ -19,33 +22,52 @@ pub enum Cmd {
Tempdir,
}
#[cfg(os_all)]
impl Cmd {
#[module_command_handler(os_all, "os > all")]
fn platform<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
Ok(os_platform())
}
#[module_command_handler(os_all, "os > all")]
fn version<R: Runtime>(_context: InvokeContext<R>) -> super::Result<String> {
Ok(os_info::get().version().to_string())
}
#[module_command_handler(os_all, "os > all")]
fn os_type<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
Ok(os_type())
}
#[module_command_handler(os_all, "os > all")]
fn arch<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
Ok(std::env::consts::ARCH)
}
#[module_command_handler(os_all, "os > all")]
fn tempdir<R: Runtime>(_context: InvokeContext<R>) -> super::Result<PathBuf> {
Ok(std::env::temp_dir())
}
}
#[cfg(not(os_all))]
impl Cmd {
fn platform<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
}
fn version<R: Runtime>(_context: InvokeContext<R>) -> super::Result<String> {
Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
}
fn os_type<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
}
fn arch<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
}
fn tempdir<R: Runtime>(_context: InvokeContext<R>) -> super::Result<PathBuf> {
Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
}
}
#[cfg(os_all)]
fn os_type() -> &'static str {
#[cfg(target_os = "linux")]
@ -55,6 +77,7 @@ fn os_type() -> &'static str {
#[cfg(target_os = "macos")]
return "Darwin";
}
#[cfg(os_all)]
fn os_platform() -> &'static str {
match std::env::consts::OS {
@ -66,23 +89,23 @@ fn os_platform() -> &'static str {
#[cfg(test)]
mod tests {
#[tauri_macros::module_command_test(os_all, "os > all")]
#[tauri_macros::module_command_test(os_all, "os > all", runtime)]
#[quickcheck_macros::quickcheck]
fn platform() {}
#[tauri_macros::module_command_test(os_all, "os > all")]
#[tauri_macros::module_command_test(os_all, "os > all", runtime)]
#[quickcheck_macros::quickcheck]
fn version() {}
#[tauri_macros::module_command_test(os_all, "os > all")]
#[tauri_macros::module_command_test(os_all, "os > all", runtime)]
#[quickcheck_macros::quickcheck]
fn os_type() {}
#[tauri_macros::module_command_test(os_all, "os > all")]
#[tauri_macros::module_command_test(os_all, "os > all", runtime)]
#[quickcheck_macros::quickcheck]
fn arch() {}
#[tauri_macros::module_command_test(os_all, "os > all")]
#[tauri_macros::module_command_test(os_all, "os > all", runtime)]
#[quickcheck_macros::quickcheck]
fn tempdir() {}
}

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use crate::{api::path::BaseDirectory, Runtime};
#[cfg(path_all)]
use crate::{Env, Manager};
@ -11,42 +13,36 @@ use std::path::{Component, Path, MAIN_SEPARATOR};
use super::InvokeContext;
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
#[cmd(path_all, "path > all")]
ResolvePath {
path: String,
directory: Option<BaseDirectory>,
},
Resolve {
paths: Vec<String>,
},
Normalize {
path: String,
},
Join {
paths: Vec<String>,
},
Dirname {
path: String,
},
Extname {
path: String,
},
Basename {
path: String,
ext: Option<String>,
},
IsAbsolute {
path: String,
},
#[cmd(path_all, "path > all")]
Resolve { paths: Vec<String> },
#[cmd(path_all, "path > all")]
Normalize { path: String },
#[cmd(path_all, "path > all")]
Join { paths: Vec<String> },
#[cmd(path_all, "path > all")]
Dirname { path: String },
#[cmd(path_all, "path > all")]
Extname { path: String },
#[cmd(path_all, "path > all")]
Basename { path: String, ext: Option<String> },
#[cmd(path_all, "path > all")]
IsAbsolute { path: String },
}
impl Cmd {
#[module_command_handler(path_all, "path > all")]
#[module_command_handler(path_all)]
fn resolve_path<R: Runtime>(
context: InvokeContext<R>,
path: String,
@ -62,7 +58,7 @@ impl Cmd {
.map_err(Into::into)
}
#[module_command_handler(path_all, "path > all")]
#[module_command_handler(path_all)]
fn resolve<R: Runtime>(_context: InvokeContext<R>, paths: Vec<String>) -> super::Result<PathBuf> {
// Start with current directory then start adding paths from the vector one by one using `PathBuf.push()` which
// will ensure that if an absolute path is encountered in the iteration, it will be used as the current full path.
@ -77,7 +73,7 @@ impl Cmd {
Ok(normalize_path(&path))
}
#[module_command_handler(path_all, "path > all")]
#[module_command_handler(path_all)]
fn normalize<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<String> {
let mut p = normalize_path_no_absolute(Path::new(&path))
.to_string_lossy()
@ -101,7 +97,7 @@ impl Cmd {
)
}
#[module_command_handler(path_all, "path > all")]
#[module_command_handler(path_all)]
fn join<R: Runtime>(_context: InvokeContext<R>, mut paths: Vec<String>) -> super::Result<String> {
let path = PathBuf::from(
paths
@ -125,7 +121,7 @@ impl Cmd {
Ok(if p.is_empty() { ".".into() } else { p })
}
#[module_command_handler(path_all, "path > all")]
#[module_command_handler(path_all)]
fn dirname<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<PathBuf> {
match Path::new(&path).parent() {
Some(p) => Ok(p.to_path_buf()),
@ -135,7 +131,7 @@ impl Cmd {
}
}
#[module_command_handler(path_all, "path > all")]
#[module_command_handler(path_all)]
fn extname<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<String> {
match Path::new(&path)
.extension()
@ -148,7 +144,7 @@ impl Cmd {
}
}
#[module_command_handler(path_all, "path > all")]
#[module_command_handler(path_all)]
fn basename<R: Runtime>(
_context: InvokeContext<R>,
path: String,
@ -169,7 +165,7 @@ impl Cmd {
}
}
#[module_command_handler(path_all, "path > all")]
#[module_command_handler(path_all)]
fn is_absolute<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<bool> {
Ok(Path::new(&path).is_absolute())
}

View File

@ -2,32 +2,41 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::InvokeContext;
#[cfg(process_relaunch)]
use crate::Manager;
use crate::Runtime;
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
/// Relaunch application
Relaunch,
/// Close application with provided exit_code
#[cmd(process_exit, "process > exit")]
#[serde(rename_all = "camelCase")]
Exit { exit_code: i32 },
}
impl Cmd {
#[module_command_handler(process_relaunch, "process > relaunch")]
#[module_command_handler(process_relaunch)]
fn relaunch<R: Runtime>(context: InvokeContext<R>) -> super::Result<()> {
context.window.app_handle().restart();
Ok(())
}
#[module_command_handler(process_exit, "process > exit")]
#[cfg(not(process_relaunch))]
fn relaunch<R: Runtime>(_: InvokeContext<R>) -> super::Result<()> {
Err(crate::Error::ApiNotAllowlisted("process > relaunch".into()).into_anyhow())
}
#[module_command_handler(process_exit)]
fn exit<R: Runtime>(_context: InvokeContext<R>, exit_code: i32) -> super::Result<()> {
// would be great if we can have a handler inside tauri
// who close all window and emit an event that user can catch
@ -38,7 +47,7 @@ impl Cmd {
#[cfg(test)]
mod tests {
#[tauri_macros::module_command_test(process_relaunch, "process > relaunch")]
#[tauri_macros::module_command_test(process_relaunch, "process > relaunch", runtime)]
#[quickcheck_macros::quickcheck]
fn relaunch() {}

View File

@ -2,12 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::InvokeContext;
use crate::{api::ipc::CallbackFn, Runtime};
#[cfg(shell_scope)]
use crate::{Manager, Scopes};
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
#[cfg(shell_scope)]
use crate::ExecuteArgs;
@ -55,10 +57,12 @@ pub struct CommandOptions {
}
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
/// The execute script API.
#[cmd(shell_script, "shell > execute or shell > sidecar")]
#[serde(rename_all = "camelCase")]
Execute {
program: String,
@ -67,20 +71,16 @@ pub enum Cmd {
#[serde(default)]
options: CommandOptions,
},
StdinWrite {
pid: ChildId,
buffer: Buffer,
},
KillChild {
pid: ChildId,
},
Open {
path: String,
with: Option<String>,
},
#[cmd(shell_script, "shell > execute or shell > sidecar")]
StdinWrite { pid: ChildId, buffer: Buffer },
#[cmd(shell_script, "shell > execute or shell > sidecar")]
KillChild { pid: ChildId },
#[cmd(shell_open, "shell > open")]
Open { path: String, with: Option<String> },
}
impl Cmd {
#[module_command_handler(shell_script)]
#[allow(unused_variables)]
fn execute<R: Runtime>(
context: InvokeContext<R>,
@ -169,7 +169,7 @@ impl Cmd {
}
}
#[cfg(any(shell_execute, shell_sidecar))]
#[module_command_handler(shell_script)]
fn stdin_write<R: Runtime>(
_context: InvokeContext<R>,
pid: ChildId,
@ -184,16 +184,7 @@ impl Cmd {
Ok(())
}
#[cfg(not(any(shell_execute, shell_sidecar)))]
fn stdin_write<R: Runtime>(
_context: InvokeContext<R>,
_pid: ChildId,
_buffer: Buffer,
) -> super::Result<()> {
Err(crate::Error::ApiNotAllowlisted("shell > execute or shell > sidecar".into()).into_anyhow())
}
#[cfg(any(shell_execute, shell_sidecar))]
#[module_command_handler(shell_script)]
fn kill_child<R: Runtime>(_context: InvokeContext<R>, pid: ChildId) -> super::Result<()> {
if let Some(child) = command_childs().lock().unwrap().remove(&pid) {
child.kill()?;
@ -201,15 +192,10 @@ impl Cmd {
Ok(())
}
#[cfg(not(any(shell_execute, shell_sidecar)))]
fn kill_child<R: Runtime>(_context: InvokeContext<R>, _pid: ChildId) -> super::Result<()> {
Err(crate::Error::ApiNotAllowlisted("shell > execute or shell > sidecar".into()).into_anyhow())
}
/// Open a (url) path with a default or specific browser opening program.
///
/// See [`crate::api::shell::open`] for how it handles security-related measures.
#[module_command_handler(shell_open, "shell > open")]
#[module_command_handler(shell_open)]
fn open<R: Runtime>(
context: InvokeContext<R>,
path: String,

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use super::{InvokeContext, InvokeResponse};
#[cfg(window_create)]
use crate::runtime::{webview::WindowBuilder, Dispatch};
@ -14,7 +16,7 @@ use crate::{
CursorIcon, Icon, Manager, Runtime,
};
use serde::Deserialize;
use tauri_macros::{module_command_handler, CommandModule};
use tauri_macros::{command_enum, module_command_handler, CommandModule};
#[derive(Deserialize)]
#[serde(untagged)]
@ -70,38 +72,67 @@ pub enum WindowManagerCmd {
AvailableMonitors,
Theme,
// Setters
#[cfg(window_center)]
Center,
#[cfg(window_request_user_attention)]
RequestUserAttention(Option<UserAttentionType>),
#[cfg(window_set_resizable)]
SetResizable(bool),
#[cfg(window_set_title)]
SetTitle(String),
#[cfg(window_maximize)]
Maximize,
#[cfg(window_unmaximize)]
Unmaximize,
#[cfg(all(window_maximize, window_unmaximize))]
ToggleMaximize,
#[cfg(window_minimize)]
Minimize,
#[cfg(window_unminimize)]
Unminimize,
#[cfg(window_show)]
Show,
#[cfg(window_hide)]
Hide,
#[cfg(window_close)]
Close,
#[cfg(window_set_decorations)]
SetDecorations(bool),
#[cfg(window_set_always_on_top)]
#[serde(rename_all = "camelCase")]
SetAlwaysOnTop(bool),
#[cfg(window_set_size)]
SetSize(Size),
#[cfg(window_set_min_size)]
SetMinSize(Option<Size>),
#[cfg(window_set_max_size)]
SetMaxSize(Option<Size>),
#[cfg(window_set_position)]
SetPosition(Position),
#[cfg(window_set_fullscreen)]
SetFullscreen(bool),
#[cfg(window_set_focus)]
SetFocus,
#[cfg(window_set_icon)]
SetIcon {
icon: IconDto,
},
#[cfg(window_set_skip_taskbar)]
SetSkipTaskbar(bool),
#[cfg(window_set_cursor_grab)]
SetCursorGrab(bool),
#[cfg(window_set_cursor_visible)]
SetCursorVisible(bool),
#[cfg(window_set_cursor_icon)]
SetCursorIcon(CursorIcon),
#[cfg(window_set_cursor_position)]
SetCursorPosition(Position),
#[cfg(window_start_dragging)]
StartDragging,
#[cfg(window_print)]
Print,
// internals
#[cfg(all(window_maximize, window_unmaximize))]
#[serde(rename = "__toggleMaximize")]
InternalToggleMaximize,
#[cfg(any(debug_assertions, feature = "devtools"))]
@ -109,72 +140,56 @@ pub enum WindowManagerCmd {
InternalToggleDevtools,
}
impl WindowManagerCmd {
fn into_allowlist_error(self) -> crate::Error {
match self {
Self::Center => crate::Error::ApiNotAllowlisted("window > center".to_string()),
Self::RequestUserAttention(_) => {
crate::Error::ApiNotAllowlisted("window > requestUserAttention".to_string())
}
Self::SetResizable(_) => crate::Error::ApiNotAllowlisted("window > setResizable".to_string()),
Self::SetTitle(_) => crate::Error::ApiNotAllowlisted("window > setTitle".to_string()),
Self::Maximize => crate::Error::ApiNotAllowlisted("window > maximize".to_string()),
Self::Unmaximize => crate::Error::ApiNotAllowlisted("window > unmaximize".to_string()),
Self::ToggleMaximize => {
crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string())
}
Self::Minimize => crate::Error::ApiNotAllowlisted("window > minimize".to_string()),
Self::Unminimize => crate::Error::ApiNotAllowlisted("window > unminimize".to_string()),
Self::Show => crate::Error::ApiNotAllowlisted("window > show".to_string()),
Self::Hide => crate::Error::ApiNotAllowlisted("window > hide".to_string()),
Self::Close => crate::Error::ApiNotAllowlisted("window > close".to_string()),
Self::SetDecorations(_) => {
crate::Error::ApiNotAllowlisted("window > setDecorations".to_string())
}
Self::SetAlwaysOnTop(_) => {
crate::Error::ApiNotAllowlisted("window > setAlwaysOnTop".to_string())
}
Self::SetSize(_) => crate::Error::ApiNotAllowlisted("window > setSize".to_string()),
Self::SetMinSize(_) => crate::Error::ApiNotAllowlisted("window > setMinSize".to_string()),
Self::SetMaxSize(_) => crate::Error::ApiNotAllowlisted("window > setMaxSize".to_string()),
Self::SetPosition(_) => crate::Error::ApiNotAllowlisted("window > setPosition".to_string()),
Self::SetFullscreen(_) => {
crate::Error::ApiNotAllowlisted("window > setFullscreen".to_string())
}
Self::SetIcon { .. } => crate::Error::ApiNotAllowlisted("window > setIcon".to_string()),
Self::SetSkipTaskbar(_) => {
crate::Error::ApiNotAllowlisted("window > setSkipTaskbar".to_string())
}
Self::SetCursorGrab(_) => {
crate::Error::ApiNotAllowlisted("window > setCursorGrab".to_string())
}
Self::SetCursorVisible(_) => {
crate::Error::ApiNotAllowlisted("window > setCursorVisible".to_string())
}
Self::SetCursorIcon(_) => {
crate::Error::ApiNotAllowlisted("window > setCursorIcon".to_string())
}
Self::SetCursorPosition(_) => {
crate::Error::ApiNotAllowlisted("window > setCursorPosition".to_string())
}
Self::StartDragging => crate::Error::ApiNotAllowlisted("window > startDragging".to_string()),
Self::Print => crate::Error::ApiNotAllowlisted("window > print".to_string()),
Self::InternalToggleMaximize => {
crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string())
}
_ => crate::Error::ApiNotAllowlisted("window > all".to_string()),
pub fn into_allowlist_error(variant: &str) -> crate::Error {
match variant {
"center" => crate::Error::ApiNotAllowlisted("window > center".to_string()),
"requestUserAttention" => {
crate::Error::ApiNotAllowlisted("window > requestUserAttention".to_string())
}
"setResizable" => crate::Error::ApiNotAllowlisted("window > setResizable".to_string()),
"setTitle" => crate::Error::ApiNotAllowlisted("window > setTitle".to_string()),
"maximize" => crate::Error::ApiNotAllowlisted("window > maximize".to_string()),
"unmaximize" => crate::Error::ApiNotAllowlisted("window > unmaximize".to_string()),
"toggleMaximize" => {
crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string())
}
"minimize" => crate::Error::ApiNotAllowlisted("window > minimize".to_string()),
"nnminimize" => crate::Error::ApiNotAllowlisted("window > unminimize".to_string()),
"show" => crate::Error::ApiNotAllowlisted("window > show".to_string()),
"hide" => crate::Error::ApiNotAllowlisted("window > hide".to_string()),
"close" => crate::Error::ApiNotAllowlisted("window > close".to_string()),
"setDecorations" => crate::Error::ApiNotAllowlisted("window > setDecorations".to_string()),
"setAlwaysOnTop" => crate::Error::ApiNotAllowlisted("window > setAlwaysOnTop".to_string()),
"setSize" => crate::Error::ApiNotAllowlisted("window > setSize".to_string()),
"setMinSize" => crate::Error::ApiNotAllowlisted("window > setMinSize".to_string()),
"setMaxSize" => crate::Error::ApiNotAllowlisted("window > setMaxSize".to_string()),
"setPosition" => crate::Error::ApiNotAllowlisted("window > setPosition".to_string()),
"setFullscreen" => crate::Error::ApiNotAllowlisted("window > setFullscreen".to_string()),
"setIcon" => crate::Error::ApiNotAllowlisted("window > setIcon".to_string()),
"setSkipTaskbar" => crate::Error::ApiNotAllowlisted("window > setSkipTaskbar".to_string()),
"setCursorGrab" => crate::Error::ApiNotAllowlisted("window > setCursorGrab".to_string()),
"setCursorVisible" => crate::Error::ApiNotAllowlisted("window > setCursorVisible".to_string()),
"setCursorIcon" => crate::Error::ApiNotAllowlisted("window > setCursorIcon".to_string()),
"setCursorPosition" => {
crate::Error::ApiNotAllowlisted("window > setCursorPosition".to_string())
}
"startDragging" => crate::Error::ApiNotAllowlisted("window > startDragging".to_string()),
"print" => crate::Error::ApiNotAllowlisted("window > print".to_string()),
"internalToggleMaximize" => {
crate::Error::ApiNotAllowlisted("window > maximize and window > unmaximize".to_string())
}
_ => crate::Error::ApiNotAllowlisted("window".to_string()),
}
}
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[cmd(async)]
#[serde(tag = "cmd", content = "data", rename_all = "camelCase")]
pub enum Cmd {
CreateWebview {
options: Box<WindowConfig>,
},
#[cmd(window_create, "window > create")]
CreateWebview { options: Box<WindowConfig> },
Manage {
label: Option<String>,
cmd: WindowManagerCmd,
@ -182,7 +197,7 @@ pub enum Cmd {
}
impl Cmd {
#[module_command_handler(window_create, "window > create")]
#[module_command_handler(window_create)]
async fn create_webview<R: Runtime>(
context: InvokeContext<R>,
options: Box<WindowConfig>,
@ -316,8 +331,6 @@ impl Cmd {
window.open_devtools();
}
}
#[allow(unreachable_patterns)]
_ => return Err(cmd.into_allowlist_error()),
}
#[allow(unreachable_code)]
Ok(().into())

View File

@ -71,11 +71,8 @@ pub enum Error {
/// Client with specified ID not found.
#[error("http client dropped or not initialized")]
HttpClientNotInitialized,
/// API not enabled by Tauri.
#[error("{0}")]
ApiNotEnabled(String),
/// API not whitelisted on tauri.conf.json
#[error("'{0}' not on the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)")]
#[error("'{0}' not in the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)")]
ApiNotAllowlisted(String),
/// Invalid args when running a command.
#[error("invalid args `{1}` for command `{0}`: {2}")]
@ -138,6 +135,7 @@ pub(crate) fn into_anyhow<T: std::fmt::Display>(err: T) -> anyhow::Error {
}
impl Error {
#[allow(dead_code)]
pub(crate) fn into_anyhow(self) -> anyhow::Error {
anyhow::anyhow!(self.to_string())
}

View File

@ -23,6 +23,8 @@
//! - **http-multipart**: Adds support to `multipart/form-data` requests.
//! - **reqwest-client**: Uses `reqwest` as HTTP client on the `http` APIs. Improves performance, but increases the bundle size.
//! - **process-command-api**: Enables the [`api::process::Command`] APIs.
//! - **global-shortcut**: Enables the global shortcut APIs.
//! - **clipboard**: Enables the clipboard APIs.
//! - **process-relaunch-dangerous-allow-symlink-macos**: Allows the [`api::process::current_binary`] function to allow symlinks on macOS (this is dangerous, see the Security section in the documentation website).
//! - **dialog**: Enables the [`api::dialog`] module.
//! - **notification**: Enables the [`api::notification`] module.
@ -232,7 +234,7 @@ pub use {
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
CursorIcon, FileDropEvent,
},
ClipboardManager, GlobalShortcutManager, RunIteration, TrayIcon, UserAttentionType,
RunIteration, TrayIcon, UserAttentionType,
},
self::state::{State, StateManager},
self::utils::{
@ -801,23 +803,98 @@ 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() {
use cargo_toml::Manifest;
use std::{env::var, fs::read_to_string, path::PathBuf};
// this env var is always set by Cargo
let manifest_dir = PathBuf::from(var("CARGO_MANIFEST_DIR").unwrap());
let manifest =
Manifest::from_path(manifest_dir.join("Cargo.toml")).expect("failed to parse Cargo manifest");
let lib_code = read_to_string(manifest_dir.join("src/lib.rs")).expect("failed to read lib.rs");
for (f, _) in manifest.features {
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"
);
// features that look like an allowlist feature, but are not
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);
// we assume that module features are the ones that start with `<module>-`
// though it's not 100% accurate, we have an allowed list to fix it
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)]

View File

@ -1390,6 +1390,7 @@ mod tests {
#[test]
fn string_replace_with_callback() {
let mut tauri_index = 0;
#[allow(clippy::single_element_loop)]
for (src, pattern, replacement, result) in [(
"tauri is awesome, tauri is amazing",
"tauri",

View File

@ -12,8 +12,8 @@ use tauri_runtime::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
CursorIcon, DetachedWindow, MenuEvent, PendingWindow, WindowEvent,
},
ClipboardManager, Dispatch, EventLoopProxy, GlobalShortcutManager, Result, RunEvent, Runtime,
RuntimeHandle, UserAttentionType, UserEvent, WindowIcon,
Dispatch, EventLoopProxy, Result, RunEvent, Runtime, RuntimeHandle, UserAttentionType, UserEvent,
WindowIcon,
};
#[cfg(feature = "system-tray")]
use tauri_runtime::{
@ -92,12 +92,14 @@ pub struct MockDispatcher {
context: RuntimeContext,
}
#[cfg(feature = "global-shortcut")]
#[derive(Debug, Clone)]
pub struct MockGlobalShortcutManager {
context: RuntimeContext,
}
impl GlobalShortcutManager for MockGlobalShortcutManager {
#[cfg(feature = "global-shortcut")]
impl tauri_runtime::GlobalShortcutManager for MockGlobalShortcutManager {
fn is_registered(&self, accelerator: &str) -> Result<bool> {
Ok(
self
@ -130,12 +132,14 @@ impl GlobalShortcutManager for MockGlobalShortcutManager {
}
}
#[cfg(feature = "clipboard")]
#[derive(Debug, Clone)]
pub struct MockClipboardManager {
context: RuntimeContext,
}
impl ClipboardManager for MockClipboardManager {
#[cfg(feature = "clipboard")]
impl tauri_runtime::ClipboardManager for MockClipboardManager {
fn write_text<T: Into<String>>(&mut self, text: T) -> Result<()> {
self.context.clipboard.lock().unwrap().replace(text.into());
Ok(())
@ -543,7 +547,9 @@ impl<T: UserEvent> EventLoopProxy<T> for EventProxy {
#[derive(Debug)]
pub struct MockRuntime {
pub context: RuntimeContext,
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: MockGlobalShortcutManager,
#[cfg(feature = "clipboard")]
clipboard_manager: MockClipboardManager,
#[cfg(feature = "system-tray")]
tray_handler: MockTrayHandler,
@ -556,9 +562,11 @@ impl MockRuntime {
clipboard: Default::default(),
};
Self {
#[cfg(feature = "global-shortcut")]
global_shortcut_manager: MockGlobalShortcutManager {
context: context.clone(),
},
#[cfg(feature = "clipboard")]
clipboard_manager: MockClipboardManager {
context: context.clone(),
},
@ -574,7 +582,9 @@ impl MockRuntime {
impl<T: UserEvent> Runtime<T> for MockRuntime {
type Dispatcher = MockDispatcher;
type Handle = MockRuntimeHandle;
#[cfg(feature = "global-shortcut")]
type GlobalShortcutManager = MockGlobalShortcutManager;
#[cfg(feature = "clipboard")]
type ClipboardManager = MockClipboardManager;
#[cfg(feature = "system-tray")]
type TrayHandler = MockTrayHandler;
@ -599,10 +609,12 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
}
}
#[cfg(feature = "global-shortcut")]
fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager {
self.global_shortcut_manager.clone()
}
#[cfg(feature = "clipboard")]
fn clipboard_manager(&self) -> Self::ClipboardManager {
self.clipboard_manager.clone()
}

View File

@ -605,8 +605,12 @@ impl<R: Runtime> Window<R> {
let invoke = Invoke { message, resolver };
if let Some(module) = &payload.tauri_module {
let module = module.to_string();
crate::endpoints::handle(module, invoke, manager.config(), manager.package_info());
crate::endpoints::handle(
module.to_string(),
invoke,
manager.config(),
manager.package_info(),
);
} else if payload.cmd.starts_with("plugin:") {
manager.extend_api(invoke);
} else {

View File

@ -35,7 +35,10 @@ async function readText(): Promise<string | null> {
return invokeTauriCommand({
__tauriModule: 'Clipboard',
message: {
cmd: 'readText'
cmd: 'readText',
// if data is not set, `serde` will ignore the custom deserializer
// that is set when the API is not allowlisted
data: null
}
})
}