mirror of
https://github.com/tauri-apps/tauri.git
synced 2025-01-03 08:36:07 +03:00
Merge pull request #3959 from tauri-apps/perf/improve-binary-size
This commit is contained in:
commit
4a405065c7
5
.changes/binary-size-perf.md
Normal file
5
.changes/binary-size-perf.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Reduce the amount of generated code for the API endpoints.
|
7
.changes/clipboard-feature.md
Normal file
7
.changes/clipboard-feature.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri": patch
|
||||
"tauri-runtime": minor
|
||||
"tauri-runtime-wry": minor
|
||||
---
|
||||
|
||||
**Breaking change::* Added the `clipboard` Cargo feature.
|
7
.changes/global-shortcut-feature.md
Normal file
7
.changes/global-shortcut-feature.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri": patch
|
||||
"tauri-runtime": minor
|
||||
"tauri-runtime-wry": minor
|
||||
---
|
||||
|
||||
**Breaking change::* Added the `global-shortcut` Cargo feature.
|
@ -24,6 +24,7 @@ exclude = [
|
||||
|
||||
# default to small, optimized workspace release binaries
|
||||
[profile.release]
|
||||
strip = true
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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)]
|
||||
|
@ -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" ]
|
||||
|
62
core/tauri-runtime-wry/src/clipboard.rs
Normal file
62
core/tauri-runtime-wry/src/clipboard.rs
Normal 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(),
|
||||
}
|
||||
}
|
164
core/tauri-runtime-wry/src/global_shortcut.rs
Normal file
164
core/tauri-runtime-wry/src/global_shortcut.rs
Normal 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(),
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -46,3 +46,5 @@ gtk = { version = "0.15", features = [ "v3_20" ] }
|
||||
devtools = [ ]
|
||||
system-tray = [ ]
|
||||
macos-private-api = [ ]
|
||||
global-shortcut = [ ]
|
||||
clipboard = [ ]
|
||||
|
@ -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.
|
||||
|
@ -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" ]
|
||||
|
@ -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
@ -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()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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());
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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()];
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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() {}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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() {}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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())
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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",
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user