* feat(core): add state management, closes #1655
* fix(tests): ignore doc example
* use a trait to manage #[command] parameters
* add docs [skip ci]
* finish command before moving into respond_async
* Revert "finish command before moving into respond_async"
This reverts commit 4651bed5bf
.
* refactor: split InvokeMessage into InvokeResolver, add InvokeResponse
* feat: add managed state to the plugin interface
* feat: add commands example
* add change file [skip ci]
* cleanup clones
Co-authored-by: chip reed <chip@chip.sh>
5
.changes/app-state.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Adds `manage` API to the `Builder` struct, which manages app state.
|
5
.changes/command-state.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-macros": patch
|
||||
---
|
||||
|
||||
Adds support to command state, triggered when a command argument is `arg: State<'_, StateType>`.
|
7
.changes/plugin-refactor.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Refactored the `Plugin` trait `initialize` and `extend_api` signatures.
|
||||
`initialize` now takes the `App` as first argument, and `extend_api` takes a `InvokeResolver` as last argument.
|
||||
This adds support to managed state on plugins.
|
5
.changes/remove-with-window.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `FromCommand` trait.
|
@ -11,6 +11,7 @@ members = [
|
||||
"examples/api/src-tauri",
|
||||
"examples/helloworld/src-tauri",
|
||||
"examples/multiwindow/src-tauri",
|
||||
"examples/commands/src-tauri",
|
||||
# used to build updater artifacts
|
||||
"examples/updater/src-tauri",
|
||||
]
|
||||
|
@ -3,28 +3,35 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use quote::{format_ident, quote, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Path,
|
||||
ReturnType, Token, Type,
|
||||
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Pat, Path, ReturnType, Token, Type,
|
||||
Visibility,
|
||||
};
|
||||
|
||||
pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream {
|
||||
// Check if "with_window" attr was passed to macro
|
||||
let with_window = attrs.iter().any(|a| {
|
||||
if let NestedMeta::Meta(Meta::Path(path)) = a {
|
||||
path
|
||||
.get_ident()
|
||||
.map(|i| *i == "with_window")
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
fn fn_wrapper(function: &ItemFn) -> (&Visibility, Ident) {
|
||||
(
|
||||
&function.vis,
|
||||
format_ident!("{}_wrapper", function.sig.ident),
|
||||
)
|
||||
}
|
||||
|
||||
fn err(function: ItemFn, error_message: &str) -> TokenStream {
|
||||
let (vis, wrap) = fn_wrapper(&function);
|
||||
quote! {
|
||||
#function
|
||||
|
||||
#vis fn #wrap<P: ::tauri::Params>(_message: ::tauri::InvokeMessage<P>) {
|
||||
compile_error!(#error_message);
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_command(function: ItemFn) -> TokenStream {
|
||||
let fn_name = function.sig.ident.clone();
|
||||
let fn_name_str = fn_name.to_string();
|
||||
let fn_wrapper = format_ident!("{}_wrapper", fn_name);
|
||||
let (vis, fn_wrapper) = fn_wrapper(&function);
|
||||
let returns_result = match function.sig.output {
|
||||
ReturnType::Type(_, ref ty) => match &**ty {
|
||||
Type::Path(type_path) => {
|
||||
@ -40,40 +47,45 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
|
||||
ReturnType::Default => false,
|
||||
};
|
||||
|
||||
// Split function args into names and types
|
||||
let (mut names, mut types): (Vec<Ident>, Vec<Path>) = function
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|param| {
|
||||
let mut arg_name = None;
|
||||
let mut arg_type = None;
|
||||
if let FnArg::Typed(arg) = param {
|
||||
if let Pat::Ident(ident) = arg.pat.as_ref() {
|
||||
arg_name = Some(ident.ident.clone());
|
||||
}
|
||||
if let Type::Path(path) = arg.ty.as_ref() {
|
||||
arg_type = Some(path.path.clone());
|
||||
}
|
||||
}
|
||||
(
|
||||
arg_name.clone().unwrap(),
|
||||
arg_type.unwrap_or_else(|| panic!("Invalid type for arg \"{}\"", arg_name.unwrap())),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let mut invoke_arg_names: Vec<Ident> = Default::default();
|
||||
let mut invoke_arg_types: Vec<Path> = Default::default();
|
||||
let mut invoke_args: TokenStream = Default::default();
|
||||
|
||||
let window_arg_maybe = match types.first() {
|
||||
Some(_) if with_window => {
|
||||
// Remove window arg from list so it isn't expected as arg from JS
|
||||
types.drain(0..1);
|
||||
names.drain(0..1);
|
||||
// Tell wrapper to pass `window` to original function
|
||||
quote!(_window,)
|
||||
for param in &function.sig.inputs {
|
||||
let mut arg_name = None;
|
||||
let mut arg_type = None;
|
||||
if let FnArg::Typed(arg) = param {
|
||||
if let Pat::Ident(ident) = arg.pat.as_ref() {
|
||||
arg_name = Some(ident.ident.clone());
|
||||
}
|
||||
if let Type::Path(path) = arg.ty.as_ref() {
|
||||
arg_type = Some(path.path.clone());
|
||||
}
|
||||
}
|
||||
// Tell wrapper not to pass `window` to original function
|
||||
_ => quote!(),
|
||||
};
|
||||
|
||||
let arg_name_ = arg_name.clone().unwrap();
|
||||
let arg_name_s = arg_name_.to_string();
|
||||
|
||||
let arg_type = match arg_type {
|
||||
Some(arg_type) => arg_type,
|
||||
None => {
|
||||
return err(
|
||||
function.clone(),
|
||||
&format!("invalid type for arg: {}", arg_name_),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
invoke_args.append_all(quote! {
|
||||
let #arg_name_ = match <#arg_type>::from_command(#fn_name_str, #arg_name_s, &message) {
|
||||
Ok(value) => value,
|
||||
Err(e) => return tauri::InvokeResponse::error(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string())
|
||||
};
|
||||
});
|
||||
invoke_arg_names.push(arg_name_.clone());
|
||||
invoke_arg_types.push(arg_type);
|
||||
}
|
||||
|
||||
let await_maybe = if function.sig.asyncness.is_some() {
|
||||
quote!(.await)
|
||||
} else {
|
||||
@ -86,30 +98,23 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
|
||||
// note that all types must implement `serde::Serialize`.
|
||||
let return_value = if returns_result {
|
||||
quote! {
|
||||
match #fn_name(#window_arg_maybe #(parsed_args.#names),*)#await_maybe {
|
||||
Ok(value) => ::core::result::Result::Ok(value),
|
||||
Err(e) => ::core::result::Result::Err(e),
|
||||
match #fn_name(#(#invoke_arg_names),*)#await_maybe {
|
||||
Ok(value) => ::core::result::Result::<_, ()>::Ok(value).into(),
|
||||
Err(e) => ::core::result::Result::<(), _>::Err(e).into(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#window_arg_maybe #(parsed_args.#names),*)#await_maybe) }
|
||||
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe).into() }
|
||||
};
|
||||
|
||||
quote! {
|
||||
#function
|
||||
pub fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>) {
|
||||
#[derive(::serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ParsedArgs {
|
||||
#(#names: #types),*
|
||||
}
|
||||
let _window = message.window();
|
||||
match ::serde_json::from_value::<ParsedArgs>(message.payload()) {
|
||||
Ok(parsed_args) => message.respond_async(async move {
|
||||
#return_value
|
||||
}),
|
||||
Err(e) => message.reject(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string()),
|
||||
}
|
||||
#vis fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>, resolver: ::tauri::InvokeResolver<P>) {
|
||||
use ::tauri::command::FromCommand;
|
||||
resolver.respond_async(async move {
|
||||
#invoke_args
|
||||
#return_value
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,12 +139,12 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
|
||||
});
|
||||
|
||||
quote! {
|
||||
move |message| {
|
||||
move |message, resolver| {
|
||||
let cmd = message.command().to_string();
|
||||
match cmd.as_str() {
|
||||
#(stringify!(#fn_names) => #fn_wrappers(message),)*
|
||||
#(stringify!(#fn_names) => #fn_wrappers(message, resolver),)*
|
||||
_ => {
|
||||
message.reject(format!("command {} not found", cmd))
|
||||
resolver.reject(format!("command {} not found", cmd))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
extern crate proc_macro;
|
||||
use crate::context::ContextItems;
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{parse_macro_input, AttributeArgs, ItemFn};
|
||||
use syn::{parse_macro_input, ItemFn};
|
||||
|
||||
mod command;
|
||||
|
||||
@ -13,10 +13,9 @@ mod command;
|
||||
mod context;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn command(attrs: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn command(_attrs: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let function = parse_macro_input!(item as ItemFn);
|
||||
let attrs = parse_macro_input!(attrs as AttributeArgs);
|
||||
let gen = command::generate_command(attrs, function);
|
||||
let gen = command::generate_command(function);
|
||||
gen.into()
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ shared_child = "0.3"
|
||||
os_pipe = "0.9"
|
||||
minisign-verify = "0.1.8"
|
||||
image = "0.23"
|
||||
state = "0.4"
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases = "0.1.1"
|
||||
|
300
core/tauri/src/command.rs
Normal file
@ -0,0 +1,300 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! Useful items for custom commands.
|
||||
|
||||
use crate::{InvokeMessage, Params};
|
||||
use serde::de::Visitor;
|
||||
use serde::Deserializer;
|
||||
|
||||
/// A [`Deserializer`] wrapper around [`Value::get`].
|
||||
///
|
||||
/// If the key doesn't exist, an error will be returned if the deserialized type is not expecting
|
||||
/// an optional item. If the key does exist, the value will be called with [`Value`]'s
|
||||
/// [`Deserializer`] implementation.
|
||||
struct KeyedValue<'de> {
|
||||
command: &'de str,
|
||||
key: &'de str,
|
||||
value: &'de serde_json::Value,
|
||||
}
|
||||
|
||||
macro_rules! kv_value {
|
||||
($s:ident) => {{
|
||||
use serde::de::Error;
|
||||
|
||||
match $s.value.get($s.key) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
return Err(serde_json::Error::custom(format!(
|
||||
"command {} missing required key `{}`",
|
||||
$s.command, $s.key
|
||||
)))
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
impl<'de> Deserializer<'de> for KeyedValue<'de> {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_any(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_bool(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_i8(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_i16(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_i32(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_i64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_u8(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_u16(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_u32(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_u64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_f32(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_f64(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_char(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_str(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_string(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_bytes(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_byte_buf(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
match self.value.get(self.key) {
|
||||
Some(value) => value.deserialize_option(visitor),
|
||||
None => visitor.visit_none(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_unit(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_unit_struct<V>(
|
||||
self,
|
||||
name: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_unit_struct(name, visitor)
|
||||
}
|
||||
|
||||
fn deserialize_newtype_struct<V>(
|
||||
self,
|
||||
name: &'static str,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_newtype_struct(name, visitor)
|
||||
}
|
||||
|
||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_seq(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_tuple(len, visitor)
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(
|
||||
self,
|
||||
name: &'static str,
|
||||
len: usize,
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_tuple_struct(name, len, visitor)
|
||||
}
|
||||
|
||||
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_map(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self,
|
||||
name: &'static str,
|
||||
fields: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_struct(name, fields, visitor)
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
name: &'static str,
|
||||
variants: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_enum(name, variants, visitor)
|
||||
}
|
||||
|
||||
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_identifier(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
kv_value!(self).deserialize_ignored_any(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented by command arguments to derive a value from a [`InvokeMessage`].
|
||||
/// [`tauri::Window`], [`tauri::State`] and types that implements [`Deserialize`] automatically implements this trait.
|
||||
pub trait FromCommand<'de, P: Params>: Sized {
|
||||
/// Derives an instance of `Self` from the [`InvokeMessage`].
|
||||
/// If the derivation fails, the corresponding message will be rejected using [`InvokeMessage#reject`].
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `command`: the command value passed to invoke, e.g. `initialize` on `invoke('initialize', {})`.
|
||||
/// - `key`: The name of the variable in the command handler, e.g. `value` on `#[command] fn handler(value: u64)`
|
||||
/// - `message`: The [`InvokeMessage`] instance.
|
||||
fn from_command(
|
||||
command: &'de str,
|
||||
key: &'de str,
|
||||
message: &'de InvokeMessage<P>,
|
||||
) -> ::core::result::Result<Self, serde_json::Error>;
|
||||
}
|
||||
|
||||
impl<'de, D: serde::Deserialize<'de>, P: Params> FromCommand<'de, P> for D {
|
||||
fn from_command(
|
||||
command: &'de str,
|
||||
key: &'de str,
|
||||
message: &'de InvokeMessage<P>,
|
||||
) -> ::core::result::Result<Self, serde_json::Error> {
|
||||
D::deserialize(KeyedValue {
|
||||
command,
|
||||
key,
|
||||
value: &message.payload,
|
||||
})
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@
|
||||
|
||||
use crate::{
|
||||
api::{config::Config, PackageInfo},
|
||||
hooks::InvokeMessage,
|
||||
Params,
|
||||
hooks::{InvokeMessage, InvokeResolver},
|
||||
Params, Window,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
@ -54,77 +54,106 @@ enum Module {
|
||||
}
|
||||
|
||||
impl Module {
|
||||
fn run<M: Params>(self, message: InvokeMessage<M>, config: &Config, package_info: PackageInfo) {
|
||||
let window = message.window();
|
||||
fn run<M: Params>(
|
||||
self,
|
||||
window: Window<M>,
|
||||
resolver: InvokeResolver<M>,
|
||||
config: &Config,
|
||||
package_info: PackageInfo,
|
||||
) {
|
||||
match self {
|
||||
Self::App(cmd) => message.respond_async(async move {
|
||||
Self::App(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run(package_info)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::Process(cmd) => message
|
||||
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
|
||||
Self::Fs(cmd) => message
|
||||
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
|
||||
Self::Window(cmd) => message.respond_async(async move {
|
||||
Self::Process(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run()
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::Fs(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run()
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::Window(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run(window)
|
||||
.await
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::Shell(cmd) => message.respond_async(async move {
|
||||
Self::Shell(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run(window)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::Event(cmd) => message.respond_async(async move {
|
||||
Self::Event(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run(window)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::Internal(cmd) => message.respond_async(async move {
|
||||
Self::Internal(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run(window)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::Dialog(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run()
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::Dialog(cmd) => message
|
||||
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
|
||||
Self::Cli(cmd) => {
|
||||
if let Some(cli_config) = config.tauri.cli.clone() {
|
||||
message.respond_async(async move {
|
||||
resolver.respond_async(async move {
|
||||
cmd
|
||||
.run(&cli_config)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
Self::Notification(cmd) => {
|
||||
let identifier = config.tauri.bundle.identifier.clone();
|
||||
message.respond_async(async move {
|
||||
resolver.respond_async(async move {
|
||||
cmd
|
||||
.run(identifier)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
})
|
||||
}
|
||||
Self::Http(cmd) => message.respond_async(async move {
|
||||
Self::Http(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run()
|
||||
.await
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
Self::GlobalShortcut(cmd) => message.respond_async(async move {
|
||||
Self::GlobalShortcut(cmd) => resolver.respond_async(async move {
|
||||
cmd
|
||||
.run(window)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
.into()
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -133,15 +162,17 @@ impl Module {
|
||||
pub(crate) fn handle<M: Params>(
|
||||
module: String,
|
||||
message: InvokeMessage<M>,
|
||||
resolver: InvokeResolver<M>,
|
||||
config: &Config,
|
||||
package_info: &PackageInfo,
|
||||
) {
|
||||
let mut payload = message.payload();
|
||||
let mut payload = message.payload;
|
||||
if let JsonValue::Object(ref mut obj) = payload {
|
||||
obj.insert("module".to_string(), JsonValue::String(module));
|
||||
}
|
||||
let window = message.window;
|
||||
match serde_json::from_value::<Module>(payload) {
|
||||
Ok(module) => module.run(message, config, package_info.clone()),
|
||||
Err(e) => message.reject(e.to_string()),
|
||||
Ok(module) => module.run(window, resolver, config, package_info.clone()),
|
||||
Err(e) => resolver.reject(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
@ -5,16 +5,17 @@
|
||||
use crate::{
|
||||
api::rpc::{format_callback, format_callback_result},
|
||||
runtime::app::App,
|
||||
Params, Window,
|
||||
Params, StateManager, Window,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::future::Future;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{future::Future, sync::Arc};
|
||||
|
||||
/// A closure that is run when the Tauri application is setting up.
|
||||
pub type SetupHook<M> = Box<dyn Fn(&mut App<M>) -> Result<(), Box<dyn std::error::Error>> + Send>;
|
||||
|
||||
/// A closure that is run everytime Tauri receives a message it doesn't explicitly handle.
|
||||
pub type InvokeHandler<M> = dyn Fn(InvokeMessage<M>) + Send + Sync + 'static;
|
||||
pub type InvokeHandler<M> = dyn Fn(InvokeMessage<M>, InvokeResolver<M>) + Send + Sync + 'static;
|
||||
|
||||
/// A closure that is run once every time a window is created and loaded.
|
||||
pub type OnPageLoad<M> = dyn Fn(Window<M>, PageLoadPayload) + Send + Sync + 'static;
|
||||
@ -32,33 +33,174 @@ impl PageLoadPayload {
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload from an invoke call.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct InvokePayload {
|
||||
#[serde(rename = "__tauriModule")]
|
||||
pub(crate) tauri_module: Option<String>,
|
||||
/// Response from a [`InvokeMessage`] passed to the [`InvokeResolver`].
|
||||
#[derive(Debug)]
|
||||
pub enum InvokeResponse {
|
||||
/// Resolve the promise.
|
||||
Ok(JsonValue),
|
||||
/// Reject the promise.
|
||||
Err(JsonValue),
|
||||
}
|
||||
|
||||
impl<T: Serialize, E: Serialize> From<Result<T, E>> for InvokeResponse {
|
||||
fn from(result: Result<T, E>) -> Self {
|
||||
match result {
|
||||
Result::Ok(t) => match serde_json::to_value(t) {
|
||||
Ok(v) => Self::Ok(v),
|
||||
Err(e) => Self::Err(JsonValue::String(e.to_string())),
|
||||
},
|
||||
Result::Err(e) => Self::error(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InvokeResponse {
|
||||
#[doc(hidden)]
|
||||
pub fn error<T: Serialize>(value: T) -> Self {
|
||||
match serde_json::to_value(value) {
|
||||
Ok(v) => Self::Err(v),
|
||||
Err(e) => Self::Err(JsonValue::String(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolver of a invoke message.
|
||||
pub struct InvokeResolver<M: Params> {
|
||||
window: Window<M>,
|
||||
pub(crate) main_thread: bool,
|
||||
pub(crate) callback: String,
|
||||
pub(crate) error: String,
|
||||
#[serde(rename = "mainThread", default)]
|
||||
pub(crate) main_thread: bool,
|
||||
#[serde(flatten)]
|
||||
pub(crate) inner: serde_json::Value,
|
||||
}
|
||||
|
||||
/*impl<P: Params> Clone for InvokeResolver<P> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
window: self.window.clone(),
|
||||
main_thread: self.main_thread,
|
||||
callback: self.callback.clone(),
|
||||
error: self.error.clone(),
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
impl<M: Params> InvokeResolver<M> {
|
||||
pub(crate) fn new(window: Window<M>, main_thread: bool, callback: String, error: String) -> Self {
|
||||
Self {
|
||||
window,
|
||||
main_thread,
|
||||
callback,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reply to the invoke promise with an async task.
|
||||
pub fn respond_async<F: Future<Output = InvokeResponse> + Send + 'static>(self, task: F) {
|
||||
if self.main_thread {
|
||||
crate::async_runtime::block_on(async move {
|
||||
Self::return_task(self.window, task, self.callback, self.error).await;
|
||||
});
|
||||
} else {
|
||||
crate::async_runtime::spawn(async move {
|
||||
Self::return_task(self.window, task, self.callback, self.error).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Reply to the invoke promise running the given closure.
|
||||
pub fn respond_closure<F: FnOnce() -> InvokeResponse>(self, f: F) {
|
||||
Self::return_closure(self.window, f, self.callback, self.error)
|
||||
}
|
||||
|
||||
/// Resolve the invoke promise with a value.
|
||||
pub fn resolve<S: Serialize>(self, value: S) {
|
||||
Self::return_result(
|
||||
self.window,
|
||||
Result::<S, ()>::Ok(value).into(),
|
||||
self.callback,
|
||||
self.error,
|
||||
)
|
||||
}
|
||||
|
||||
/// Reject the invoke promise with a value.
|
||||
pub fn reject<S: Serialize>(self, value: S) {
|
||||
Self::return_result(
|
||||
self.window,
|
||||
Result::<(), S>::Err(value).into(),
|
||||
self.callback,
|
||||
self.error,
|
||||
)
|
||||
}
|
||||
|
||||
/// Asynchronously executes the given task
|
||||
/// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.
|
||||
///
|
||||
/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
|
||||
/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
|
||||
pub async fn return_task<F: std::future::Future<Output = InvokeResponse> + Send + 'static>(
|
||||
window: Window<M>,
|
||||
task: F,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
) {
|
||||
let result = task.await;
|
||||
Self::return_closure(window, || result, success_callback, error_callback)
|
||||
}
|
||||
|
||||
pub(crate) fn return_closure<F: FnOnce() -> InvokeResponse>(
|
||||
window: Window<M>,
|
||||
f: F,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
) {
|
||||
Self::return_result(window, f(), success_callback, error_callback)
|
||||
}
|
||||
|
||||
pub(crate) fn return_result(
|
||||
window: Window<M>,
|
||||
response: InvokeResponse,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
) {
|
||||
let callback_string = match format_callback_result(
|
||||
match response {
|
||||
InvokeResponse::Ok(t) => std::result::Result::Ok(t),
|
||||
InvokeResponse::Err(e) => std::result::Result::Err(e),
|
||||
},
|
||||
success_callback,
|
||||
error_callback.clone(),
|
||||
) {
|
||||
Ok(callback_string) => callback_string,
|
||||
Err(e) => format_callback(error_callback, &e.to_string())
|
||||
.expect("unable to serialize shortcut string to json"),
|
||||
};
|
||||
|
||||
let _ = window.eval(&callback_string);
|
||||
}
|
||||
}
|
||||
|
||||
/// An invoke message.
|
||||
pub struct InvokeMessage<M: Params> {
|
||||
window: Window<M>,
|
||||
/// The window that received the invoke message.
|
||||
pub(crate) window: Window<M>,
|
||||
/// Application managed state.
|
||||
pub(crate) state: Arc<StateManager>,
|
||||
/// The RPC command.
|
||||
pub(crate) command: String,
|
||||
|
||||
/// Allow our crate to access the payload without cloning it.
|
||||
pub(crate) payload: InvokePayload,
|
||||
/// The JSON argument passed on the invoke message.
|
||||
pub(crate) payload: JsonValue,
|
||||
}
|
||||
|
||||
impl<M: Params> InvokeMessage<M> {
|
||||
/// Create an new [`InvokeMessage`] from a payload send to a window.
|
||||
pub(crate) fn new(window: Window<M>, command: String, payload: InvokePayload) -> Self {
|
||||
pub(crate) fn new(
|
||||
window: Window<M>,
|
||||
state: Arc<StateManager>,
|
||||
command: String,
|
||||
payload: JsonValue,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
state,
|
||||
command,
|
||||
payload,
|
||||
}
|
||||
@ -69,102 +211,8 @@ impl<M: Params> InvokeMessage<M> {
|
||||
&self.command
|
||||
}
|
||||
|
||||
/// The invoke payload.
|
||||
pub fn payload(&self) -> serde_json::Value {
|
||||
self.payload.inner.clone()
|
||||
}
|
||||
|
||||
/// The window that received the invoke.
|
||||
pub fn window(&self) -> Window<M> {
|
||||
self.window.clone()
|
||||
}
|
||||
|
||||
/// Reply to the invoke promise with an async task.
|
||||
pub fn respond_async<
|
||||
T: Serialize,
|
||||
Err: Serialize,
|
||||
F: Future<Output = Result<T, Err>> + Send + 'static,
|
||||
>(
|
||||
self,
|
||||
task: F,
|
||||
) {
|
||||
if self.payload.main_thread {
|
||||
crate::async_runtime::block_on(async move {
|
||||
Self::return_task(self.window, task, self.payload.callback, self.payload.error).await;
|
||||
});
|
||||
} else {
|
||||
crate::async_runtime::spawn(async move {
|
||||
Self::return_task(self.window, task, self.payload.callback, self.payload.error).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Reply to the invoke promise running the given closure.
|
||||
pub fn respond_closure<T: Serialize, Err: Serialize, F: FnOnce() -> Result<T, Err>>(self, f: F) {
|
||||
Self::return_closure(self.window, f, self.payload.callback, self.payload.error)
|
||||
}
|
||||
|
||||
/// Resolve the invoke promise with a value.
|
||||
pub fn resolve<S: Serialize>(self, value: S) {
|
||||
Self::return_result(
|
||||
self.window,
|
||||
Result::<S, ()>::Ok(value),
|
||||
self.payload.callback,
|
||||
self.payload.error,
|
||||
)
|
||||
}
|
||||
|
||||
/// Reject the invoke promise with a value.
|
||||
pub fn reject<S: Serialize>(self, value: S) {
|
||||
Self::return_result(
|
||||
self.window,
|
||||
Result::<(), S>::Err(value),
|
||||
self.payload.callback,
|
||||
self.payload.error,
|
||||
)
|
||||
}
|
||||
|
||||
/// Asynchronously executes the given task
|
||||
/// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.
|
||||
///
|
||||
/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
|
||||
/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
|
||||
pub async fn return_task<
|
||||
T: Serialize,
|
||||
Err: Serialize,
|
||||
F: std::future::Future<Output = Result<T, Err>> + Send + 'static,
|
||||
>(
|
||||
window: Window<M>,
|
||||
task: F,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
) {
|
||||
let result = task.await;
|
||||
Self::return_closure(window, || result, success_callback, error_callback)
|
||||
}
|
||||
|
||||
pub(crate) fn return_closure<T: Serialize, Err: Serialize, F: FnOnce() -> Result<T, Err>>(
|
||||
window: Window<M>,
|
||||
f: F,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
) {
|
||||
Self::return_result(window, f(), success_callback, error_callback)
|
||||
}
|
||||
|
||||
pub(crate) fn return_result<T: Serialize, Err: Serialize>(
|
||||
window: Window<M>,
|
||||
result: Result<T, Err>,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
) {
|
||||
let callback_string =
|
||||
match format_callback_result(result, success_callback, error_callback.clone()) {
|
||||
Ok(callback_string) => callback_string,
|
||||
Err(e) => format_callback(error_callback, &e.to_string())
|
||||
.expect("unable to serialize shortcut string to json"),
|
||||
};
|
||||
|
||||
let _ = window.eval(&callback_string);
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ pub use tauri_macros::{command, generate_handler};
|
||||
pub mod api;
|
||||
/// Async runtime.
|
||||
pub mod async_runtime;
|
||||
pub mod command;
|
||||
/// The Tauri API endpoints.
|
||||
mod endpoints;
|
||||
mod error;
|
||||
@ -27,6 +28,7 @@ pub mod plugin;
|
||||
pub mod runtime;
|
||||
/// The Tauri-specific settings for your runtime e.g. notification permission status.
|
||||
pub mod settings;
|
||||
mod state;
|
||||
#[cfg(feature = "updater")]
|
||||
pub mod updater;
|
||||
|
||||
@ -36,27 +38,32 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
/// A task to run on the main thread.
|
||||
pub type SyncTask = Box<dyn FnOnce() + Send>;
|
||||
|
||||
use crate::api::assets::Assets;
|
||||
use crate::api::config::Config;
|
||||
use crate::event::{Event, EventHandler};
|
||||
use crate::runtime::tag::{Tag, TagRef};
|
||||
use crate::runtime::window::PendingWindow;
|
||||
use crate::runtime::{Dispatch, Runtime};
|
||||
use crate::{
|
||||
api::{assets::Assets, config::Config},
|
||||
event::{Event, EventHandler},
|
||||
runtime::{
|
||||
tag::{Tag, TagRef},
|
||||
window::PendingWindow,
|
||||
Dispatch, Runtime,
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::{borrow::Borrow, collections::HashMap, path::PathBuf};
|
||||
|
||||
// Export types likely to be used by the application.
|
||||
pub use {
|
||||
api::config::WindowUrl,
|
||||
hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
|
||||
runtime::app::{App, Builder},
|
||||
runtime::flavors::wry::Wry,
|
||||
runtime::webview::{WebviewAttributes, WindowBuilder},
|
||||
runtime::window::export::Window,
|
||||
self::api::config::WindowUrl,
|
||||
self::hooks::{
|
||||
InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad, PageLoadPayload,
|
||||
SetupHook,
|
||||
},
|
||||
self::runtime::app::{App, Builder},
|
||||
self::runtime::flavors::wry::Wry,
|
||||
self::runtime::webview::{WebviewAttributes, WindowBuilder},
|
||||
self::runtime::window::export::Window,
|
||||
self::state::{State, StateManager},
|
||||
};
|
||||
|
||||
use std::borrow::Borrow;
|
||||
/// Reads the config file at compile time and generates a [`Context`] based on its content.
|
||||
///
|
||||
/// The default config file path is a `tauri.conf.json` file inside the Cargo manifest directory of
|
||||
@ -223,6 +230,23 @@ pub trait Manager<P: Params>: sealed::ManagerBase<P> {
|
||||
fn windows(&self) -> HashMap<P::Label, Window<P>> {
|
||||
self.manager().windows()
|
||||
}
|
||||
|
||||
/// Add `state` to the state managed by the application.
|
||||
/// See [`tauri::Builder#manage`] for instructions.
|
||||
fn manage<T>(&self, state: T)
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.manager().state().set(state);
|
||||
}
|
||||
|
||||
/// Gets the managed state for the type `T`.
|
||||
fn state<T>(&self) -> State<'_, T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
self.manager().inner.state.get()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prevent implementation details from leaking out of the [`Manager`] and [`Params`] traits.
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
use crate::{
|
||||
api::config::PluginConfig,
|
||||
hooks::{InvokeMessage, PageLoadPayload},
|
||||
Params, Window,
|
||||
hooks::{InvokeMessage, InvokeResolver, PageLoadPayload},
|
||||
App, Params, Window,
|
||||
};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashMap;
|
||||
@ -22,7 +22,7 @@ pub trait Plugin<M: Params>: Send {
|
||||
|
||||
/// Initialize the plugin.
|
||||
#[allow(unused_variables)]
|
||||
fn initialize(&mut self, config: JsonValue) -> Result<()> {
|
||||
fn initialize(&mut self, app: &App<M>, config: JsonValue) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ pub trait Plugin<M: Params>: Send {
|
||||
|
||||
/// Add invoke_handler API extension commands.
|
||||
#[allow(unused_variables)]
|
||||
fn extend_api(&mut self, message: InvokeMessage<M>) {}
|
||||
fn extend_api(&mut self, message: InvokeMessage<M>, resolver: InvokeResolver<M>) {}
|
||||
}
|
||||
|
||||
/// Plugin collection type.
|
||||
@ -70,10 +70,13 @@ impl<M: Params> PluginStore<M> {
|
||||
}
|
||||
|
||||
/// Initializes all plugins in the store.
|
||||
pub(crate) fn initialize(&mut self, config: &PluginConfig) -> crate::Result<()> {
|
||||
pub(crate) fn initialize(&mut self, app: &App<M>, config: &PluginConfig) -> crate::Result<()> {
|
||||
self.store.values_mut().try_for_each(|plugin| {
|
||||
plugin
|
||||
.initialize(config.0.get(plugin.name()).cloned().unwrap_or_default())
|
||||
.initialize(
|
||||
&app,
|
||||
config.0.get(plugin.name()).cloned().unwrap_or_default(),
|
||||
)
|
||||
.map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))
|
||||
})
|
||||
}
|
||||
@ -105,7 +108,7 @@ impl<M: Params> PluginStore<M> {
|
||||
.for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn extend_api(&mut self, mut message: InvokeMessage<M>) {
|
||||
pub(crate) fn extend_api(&mut self, mut message: InvokeMessage<M>, resolver: InvokeResolver<M>) {
|
||||
let command = message.command.replace("plugin:", "");
|
||||
let mut tokens = command.split('|');
|
||||
// safe to unwrap: split always has a least one item
|
||||
@ -116,9 +119,9 @@ impl<M: Params> PluginStore<M> {
|
||||
.next()
|
||||
.map(|c| c.to_string())
|
||||
.unwrap_or_else(String::new);
|
||||
plugin.extend_api(message);
|
||||
plugin.extend_api(message, resolver);
|
||||
} else {
|
||||
message.reject(format!("plugin {} not found", target));
|
||||
resolver.reject(format!("plugin {} not found", target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
use crate::{
|
||||
api::{assets::Assets, config::WindowUrl},
|
||||
hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
|
||||
hooks::{InvokeHandler, InvokeMessage, InvokeResolver, OnPageLoad, PageLoadPayload, SetupHook},
|
||||
plugin::{Plugin, PluginStore},
|
||||
runtime::{
|
||||
flavors::wry::Wry,
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
Dispatch, Runtime,
|
||||
},
|
||||
sealed::{ManagerBase, RuntimeOrDispatch},
|
||||
Context, Manager, Params, Window,
|
||||
Context, Manager, Params, StateManager, Window,
|
||||
};
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
@ -126,6 +126,9 @@ where
|
||||
|
||||
/// The webview protocols available to all windows.
|
||||
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
|
||||
|
||||
/// App state.
|
||||
state: StateManager,
|
||||
}
|
||||
|
||||
impl<E, L, A, R> Builder<E, L, A, R>
|
||||
@ -139,18 +142,20 @@ where
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
setup: Box::new(|_| Ok(())),
|
||||
invoke_handler: Box::new(|_| ()),
|
||||
invoke_handler: Box::new(|_, _| ()),
|
||||
on_page_load: Box::new(|_, _| ()),
|
||||
pending_windows: Default::default(),
|
||||
plugins: PluginStore::default(),
|
||||
uri_scheme_protocols: Default::default(),
|
||||
state: StateManager::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the JS message handler callback.
|
||||
pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
|
||||
where
|
||||
F: Fn(InvokeMessage<Args<E, L, A, R>>) + Send + Sync + 'static,
|
||||
F:
|
||||
Fn(InvokeMessage<Args<E, L, A, R>>, InvokeResolver<Args<E, L, A, R>>) + Send + Sync + 'static,
|
||||
{
|
||||
self.invoke_handler = Box::new(invoke_handler);
|
||||
self
|
||||
@ -180,6 +185,58 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Add `state` to the state managed by the application.
|
||||
///
|
||||
/// This method can be called any number of times as long as each call
|
||||
/// refers to a different `T`.
|
||||
///
|
||||
/// Managed state can be retrieved by any request handler via the
|
||||
/// [`State`](crate::State) request guard. In particular, if a value of type `T`
|
||||
/// is managed by Tauri, adding `State<T>` to the list of arguments in a
|
||||
/// request handler instructs Tauri to retrieve the managed value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if state of type `T` is already being managed.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use tauri::State;
|
||||
///
|
||||
/// struct MyInt(isize);
|
||||
/// struct MyString(String);
|
||||
///
|
||||
/// #[tauri::command]
|
||||
/// fn int_command(state: State<'_, MyInt>) -> String {
|
||||
/// format!("The stateful int is: {}", state.0)
|
||||
/// }
|
||||
///
|
||||
/// #[tauri::command]
|
||||
/// fn string_command<'r>(state: State<'r, MyString>) {
|
||||
/// println!("state: {}", state.inner().0);
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// tauri::Builder::default()
|
||||
/// .manage(MyInt(10))
|
||||
/// .manage(MyString("Hello, managed state!".to_string()))
|
||||
/// .run(tauri::generate_context!())
|
||||
/// .expect("error while running tauri application");
|
||||
/// }
|
||||
/// ```
|
||||
pub fn manage<T>(self, state: T) -> Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
let type_name = std::any::type_name::<T>();
|
||||
if !self.state.set(state) {
|
||||
panic!("state for type '{}' is already being managed", type_name);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new webview.
|
||||
pub fn create_window<F>(mut self, label: L, url: WindowUrl, setup: F) -> Self
|
||||
where
|
||||
@ -237,6 +294,7 @@ where
|
||||
self.invoke_handler,
|
||||
self.on_page_load,
|
||||
self.uri_scheme_protocols,
|
||||
self.state,
|
||||
);
|
||||
|
||||
// set up all the windows defined in the config
|
||||
@ -254,13 +312,13 @@ where
|
||||
));
|
||||
}
|
||||
|
||||
manager.initialize_plugins()?;
|
||||
|
||||
let mut app = App {
|
||||
runtime: R::new()?,
|
||||
manager,
|
||||
};
|
||||
|
||||
app.manager.initialize_plugins(&app)?;
|
||||
|
||||
let pending_labels = self
|
||||
.pending_windows
|
||||
.iter()
|
||||
@ -284,6 +342,7 @@ where
|
||||
app.run_updater(main_window);
|
||||
|
||||
(self.setup)(&mut app).map_err(|e| crate::Error::Setup(e.to_string()))?;
|
||||
|
||||
app.runtime.run();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::runtime::tag::TagRef;
|
||||
use crate::{
|
||||
api::{
|
||||
assets::Assets,
|
||||
@ -11,16 +10,19 @@ use crate::{
|
||||
PackageInfo,
|
||||
},
|
||||
event::{Event, EventHandler, Listeners},
|
||||
hooks::{InvokeHandler, InvokeMessage, InvokePayload, OnPageLoad, PageLoadPayload},
|
||||
hooks::{InvokeHandler, InvokeMessage, InvokeResolver, OnPageLoad, PageLoadPayload},
|
||||
plugin::PluginStore,
|
||||
runtime::{
|
||||
tag::{tags_to_javascript_array, Tag, ToJsString},
|
||||
webview::{CustomProtocol, FileDropEvent, FileDropHandler, WebviewRpcHandler, WindowBuilder},
|
||||
tag::{tags_to_javascript_array, Tag, TagRef, ToJsString},
|
||||
webview::{
|
||||
CustomProtocol, FileDropEvent, FileDropHandler, InvokePayload, WebviewRpcHandler,
|
||||
WindowBuilder,
|
||||
},
|
||||
window::{DetachedWindow, PendingWindow},
|
||||
Icon, Runtime,
|
||||
},
|
||||
sealed::ParamsBase,
|
||||
Context, Params, Window,
|
||||
App, Context, Params, StateManager, Window,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
@ -52,6 +54,7 @@ pub struct InnerWindowManager<P: Params> {
|
||||
windows: Mutex<HashMap<P::Label, Window<P>>>,
|
||||
plugins: Mutex<PluginStore<P>>,
|
||||
listeners: Listeners<P::Event, P::Label>,
|
||||
pub(crate) state: Arc<StateManager>,
|
||||
|
||||
/// The JS message handler.
|
||||
invoke_handler: Box<InvokeHandler<P>>,
|
||||
@ -67,7 +70,7 @@ pub struct InnerWindowManager<P: Params> {
|
||||
salts: Mutex<HashSet<Uuid>>,
|
||||
package_info: PackageInfo,
|
||||
/// The webview protocols protocols available to all windows.
|
||||
uri_scheme_protocols: HashMap<String, std::sync::Arc<CustomProtocol>>,
|
||||
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
|
||||
}
|
||||
|
||||
/// A [Zero Sized Type] marker representing a full [`Params`].
|
||||
@ -119,13 +122,15 @@ impl<P: Params> WindowManager<P> {
|
||||
plugins: PluginStore<P>,
|
||||
invoke_handler: Box<InvokeHandler<P>>,
|
||||
on_page_load: Box<OnPageLoad<P>>,
|
||||
uri_scheme_protocols: HashMap<String, std::sync::Arc<CustomProtocol>>,
|
||||
uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
|
||||
state: StateManager,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(InnerWindowManager {
|
||||
windows: Mutex::default(),
|
||||
plugins: Mutex::new(plugins),
|
||||
listeners: Listeners::default(),
|
||||
state: Arc::new(state),
|
||||
invoke_handler,
|
||||
on_page_load,
|
||||
config: context.config,
|
||||
@ -144,6 +149,11 @@ impl<P: Params> WindowManager<P> {
|
||||
self.inner.windows.lock().expect("poisoned window manager")
|
||||
}
|
||||
|
||||
/// State managed by the application.
|
||||
pub(crate) fn state(&self) -> Arc<StateManager> {
|
||||
self.inner.state.clone()
|
||||
}
|
||||
|
||||
// setup content for dev-server
|
||||
#[cfg(dev)]
|
||||
fn get_url(&self) -> String {
|
||||
@ -383,7 +393,7 @@ impl<P: Params> WindowManager<P> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Args, WindowManager};
|
||||
use crate::{generate_context, plugin::PluginStore, runtime::flavors::wry::Wry};
|
||||
use crate::{generate_context, plugin::PluginStore, runtime::flavors::wry::Wry, StateManager};
|
||||
|
||||
#[test]
|
||||
fn check_get_url() {
|
||||
@ -391,9 +401,10 @@ mod test {
|
||||
let manager: WindowManager<Args<String, String, _, Wry>> = WindowManager::with_handlers(
|
||||
context,
|
||||
PluginStore::default(),
|
||||
Box::new(|_| ()),
|
||||
Box::new(|_, _| ()),
|
||||
Box::new(|_, _| ()),
|
||||
Default::default(),
|
||||
StateManager::new(),
|
||||
);
|
||||
|
||||
#[cfg(custom_protocol)]
|
||||
@ -405,9 +416,10 @@ mod test {
|
||||
}
|
||||
|
||||
impl<P: Params> WindowManager<P> {
|
||||
pub fn run_invoke_handler(&self, message: InvokeMessage<P>) {
|
||||
(self.inner.invoke_handler)(message);
|
||||
pub fn run_invoke_handler(&self, message: InvokeMessage<P>, resolver: InvokeResolver<P>) {
|
||||
(self.inner.invoke_handler)(message, resolver);
|
||||
}
|
||||
|
||||
pub fn run_on_page_load(&self, window: Window<P>, payload: PageLoadPayload) {
|
||||
(self.inner.on_page_load)(window.clone(), payload.clone());
|
||||
self
|
||||
@ -417,21 +429,23 @@ impl<P: Params> WindowManager<P> {
|
||||
.expect("poisoned plugin store")
|
||||
.on_page_load(window, payload);
|
||||
}
|
||||
pub fn extend_api(&self, message: InvokeMessage<P>) {
|
||||
|
||||
pub fn extend_api(&self, message: InvokeMessage<P>, resolver: InvokeResolver<P>) {
|
||||
self
|
||||
.inner
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.extend_api(message);
|
||||
.extend_api(message, resolver);
|
||||
}
|
||||
pub fn initialize_plugins(&self) -> crate::Result<()> {
|
||||
|
||||
pub fn initialize_plugins(&self, app: &App<P>) -> crate::Result<()> {
|
||||
self
|
||||
.inner
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.initialize(&self.inner.config.plugins)
|
||||
.initialize(&app, &self.inner.config.plugins)
|
||||
}
|
||||
|
||||
pub fn prepare_window(
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
api::config::{WindowConfig, WindowUrl},
|
||||
runtime::window::DetachedWindow,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
@ -166,3 +167,15 @@ pub(crate) type WebviewRpcHandler<M> = Box<dyn Fn(DetachedWindow<M>, RpcRequest)
|
||||
/// File drop handler callback
|
||||
/// Return `true` in the callback to block the OS' default behavior of handling a file drop.
|
||||
pub(crate) type FileDropHandler<M> = Box<dyn Fn(FileDropEvent, DetachedWindow<M>) -> bool + Send>;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct InvokePayload {
|
||||
#[serde(rename = "__tauriModule")]
|
||||
pub(crate) tauri_module: Option<String>,
|
||||
pub(crate) callback: String,
|
||||
pub(crate) error: String,
|
||||
#[serde(rename = "mainThread", default)]
|
||||
pub(crate) main_thread: bool,
|
||||
#[serde(flatten)]
|
||||
pub(crate) inner: JsonValue,
|
||||
}
|
||||
|
@ -7,10 +7,10 @@
|
||||
use crate::{
|
||||
api::config::WindowConfig,
|
||||
event::{Event, EventHandler},
|
||||
hooks::{InvokeMessage, InvokePayload, PageLoadPayload},
|
||||
hooks::{InvokeMessage, InvokeResolver, PageLoadPayload},
|
||||
runtime::{
|
||||
tag::ToJsString,
|
||||
webview::{FileDropHandler, WebviewAttributes, WebviewRpcHandler},
|
||||
webview::{FileDropHandler, InvokePayload, WebviewAttributes, WebviewRpcHandler},
|
||||
Dispatch, Runtime,
|
||||
},
|
||||
sealed::{ManagerBase, RuntimeOrDispatch},
|
||||
@ -114,6 +114,7 @@ impl<M: Params> PartialEq for DetachedWindow<M> {
|
||||
/// We want to export the runtime related window at the crate root, but not look like a re-export.
|
||||
pub(crate) mod export {
|
||||
use super::*;
|
||||
use crate::command::FromCommand;
|
||||
use crate::runtime::{manager::WindowManager, tag::TagRef};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
@ -166,6 +167,16 @@ pub(crate) mod export {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, P: Params> FromCommand<'de, P> for Window<P> {
|
||||
fn from_command(
|
||||
_: &'de str,
|
||||
_: &'de str,
|
||||
message: &'de InvokeMessage<P>,
|
||||
) -> Result<Self, serde_json::Error> {
|
||||
Ok(message.window())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Params> Window<P> {
|
||||
/// Create a new window that is attached to the manager.
|
||||
pub(crate) fn new(manager: WindowManager<P>, window: DetachedWindow<P>) -> Self {
|
||||
@ -186,14 +197,27 @@ pub(crate) mod export {
|
||||
manager.run_on_page_load(self, payload);
|
||||
}
|
||||
_ => {
|
||||
let message = InvokeMessage::new(self, command.to_string(), payload);
|
||||
if let Some(module) = &message.payload.tauri_module {
|
||||
let message = InvokeMessage::new(
|
||||
self.clone(),
|
||||
manager.state(),
|
||||
command.to_string(),
|
||||
payload.inner,
|
||||
);
|
||||
let resolver =
|
||||
InvokeResolver::new(self, payload.main_thread, payload.callback, payload.error);
|
||||
if let Some(module) = &payload.tauri_module {
|
||||
let module = module.to_string();
|
||||
crate::endpoints::handle(module, message, manager.config(), manager.package_info());
|
||||
crate::endpoints::handle(
|
||||
module,
|
||||
message,
|
||||
resolver,
|
||||
manager.config(),
|
||||
manager.package_info(),
|
||||
);
|
||||
} else if command.starts_with("plugin:") {
|
||||
manager.extend_api(message);
|
||||
manager.extend_api(message, resolver);
|
||||
} else {
|
||||
manager.run_invoke_handler(message);
|
||||
manager.run_invoke_handler(message, resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
63
core/tauri/src/state.rs
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::command::FromCommand;
|
||||
use crate::{InvokeMessage, Params};
|
||||
use state::Container;
|
||||
|
||||
/// A guard for a state value.
|
||||
pub struct State<'r, T: Send + Sync + 'static>(&'r T);
|
||||
|
||||
impl<'r, T: Send + Sync + 'static> State<'r, T> {
|
||||
/// Retrieve a borrow to the underlying value with a lifetime of `'r`.
|
||||
/// Using this method is typically unnecessary as `State` implements
|
||||
/// [`Deref`] with a [`Deref::Target`] of `T`.
|
||||
#[inline(always)]
|
||||
pub fn inner(&self) -> &'r T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + 'static> std::ops::Deref for State<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + 'static> Clone for State<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
State(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 'de: 'r, T: Send + Sync + 'static, P: Params> FromCommand<'de, P> for State<'r, T> {
|
||||
fn from_command(
|
||||
_: &'de str,
|
||||
_: &'de str,
|
||||
message: &'de InvokeMessage<P>,
|
||||
) -> Result<Self, serde_json::Error> {
|
||||
Ok(message.state.get())
|
||||
}
|
||||
}
|
||||
|
||||
/// The Tauri state manager.
|
||||
pub struct StateManager(pub(crate) Container);
|
||||
|
||||
impl StateManager {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self(Container::new())
|
||||
}
|
||||
|
||||
pub(crate) fn set<T: Send + Sync + 'static>(&self, state: T) -> bool {
|
||||
self.0.set(state)
|
||||
}
|
||||
|
||||
/// Gets the state associated with the specified type.
|
||||
pub fn get<T: Send + Sync + 'static>(&self) -> State<'_, T> {
|
||||
State(self.0.get())
|
||||
}
|
||||
}
|
@ -11,12 +11,8 @@ pub struct RequestBody {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[command(with_window)]
|
||||
pub fn log_operation<M: tauri::Params>(
|
||||
_window: tauri::Window<M>,
|
||||
event: String,
|
||||
payload: Option<String>,
|
||||
) {
|
||||
#[command]
|
||||
pub fn log_operation(event: String, payload: Option<String>) {
|
||||
println!("{} {:?}", event, payload);
|
||||
}
|
||||
|
||||
|
7
examples/commands/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "commands",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"tauri": "node ../../tooling/cli.js/bin/tauri"
|
||||
}
|
||||
}
|
55
examples/commands/public/index.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tauri</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Tauri Commands</h1>
|
||||
<div id="response">Response:</div>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
const responseDiv = document.querySelector('#response')
|
||||
function runCommand(commandName, args) {
|
||||
window.__TAURI__.invoke(commandName, args).then(response => {
|
||||
responseDiv.innerHTML = `Response: Ok(${response})`
|
||||
}).catch(error => {
|
||||
responseDiv.innerHTML = `Response: Err(${error})`
|
||||
})
|
||||
}
|
||||
|
||||
const container = document.querySelector('#container')
|
||||
const commands = [
|
||||
{ name: 'simple_command', required: true },
|
||||
{ name: 'stateful_command', required: false },
|
||||
{ name: 'async_simple_command', required: true },
|
||||
{ name: 'async_stateful_command', required: false },
|
||||
{ name: 'simple_command_with_result', required: true },
|
||||
{ name: 'stateful_command_with_result', required: false },
|
||||
{ name: 'async_simple_command_with_result', required: true },
|
||||
{ name: 'async_stateful_command_with_result', required: false },
|
||||
]
|
||||
|
||||
for (command of commands) {
|
||||
const { name, required } = command
|
||||
const button = document.createElement('button')
|
||||
button.innerHTML = `Run ${name}`;
|
||||
button.addEventListener("click", function () {
|
||||
runCommand(name, { argument: 'value' })
|
||||
if (!required) {
|
||||
setTimeout(() => {
|
||||
runCommand(name, {})
|
||||
}, 1000)
|
||||
}
|
||||
});
|
||||
container.appendChild(button);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
10
examples/commands/src-tauri/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
WixTools
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
config.json
|
||||
bundle.json
|
1
examples/commands/src-tauri/.license_template
Normal file
@ -0,0 +1 @@
|
||||
../../../.license_template
|
17
examples/commands/src-tauri/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "commands"
|
||||
version = "0.1.0"
|
||||
description = "A simple Tauri Application showcasing the commands API"
|
||||
edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { path = "../../../core/tauri-build", features = [ "codegen" ]}
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
tauri = { path = "../../../core/tauri", features =["api-all"]}
|
||||
|
||||
[features]
|
||||
default = [ "custom-protocol" ]
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
7
examples/commands/src-tauri/build.rs
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() {
|
||||
tauri_build::build();
|
||||
}
|
BIN
examples/commands/src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
examples/commands/src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
examples/commands/src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
examples/commands/src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
examples/commands/src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
examples/commands/src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
examples/commands/src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
examples/commands/src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
examples/commands/src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
examples/commands/src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
examples/commands/src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
examples/commands/src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
examples/commands/src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
examples/commands/src-tauri/icons/icon.icns
Normal file
BIN
examples/commands/src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
examples/commands/src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 88 KiB |
92
examples/commands/src-tauri/src/main.rs
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MyState {
|
||||
value: u64,
|
||||
label: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn simple_command(argument: String) {
|
||||
println!("{}", argument);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn stateful_command(argument: Option<String>, state: tauri::State<'_, MyState>) {
|
||||
println!("{:?} {:?}", argument, state.inner());
|
||||
}
|
||||
|
||||
// Async commands
|
||||
|
||||
#[tauri::command]
|
||||
async fn async_simple_command(argument: String) {
|
||||
println!("{}", argument);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn async_stateful_command(argument: Option<String>, state: tauri::State<'_, MyState>) {
|
||||
println!("{:?} {:?}", argument, state.inner());
|
||||
}
|
||||
|
||||
// ------------------------ Commands returning Result ------------------------
|
||||
|
||||
type Result<T> = std::result::Result<T, ()>;
|
||||
|
||||
#[tauri::command]
|
||||
fn simple_command_with_result(argument: String) -> Result<String> {
|
||||
println!("{}", argument);
|
||||
Ok(argument)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn stateful_command_with_result(
|
||||
argument: Option<String>,
|
||||
state: tauri::State<'_, MyState>,
|
||||
) -> Result<String> {
|
||||
println!("{:?} {:?}", argument, state.inner());
|
||||
Ok(argument.unwrap_or_else(|| "".to_string()))
|
||||
}
|
||||
|
||||
// Async commands
|
||||
|
||||
#[tauri::command]
|
||||
async fn async_simple_command_with_result(argument: String) -> Result<String> {
|
||||
println!("{}", argument);
|
||||
Ok(argument)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn async_stateful_command_with_result(
|
||||
argument: Option<String>,
|
||||
state: tauri::State<'_, MyState>,
|
||||
) -> Result<String> {
|
||||
println!("{:?} {:?}", argument, state.inner());
|
||||
Ok(argument.unwrap_or_else(|| "".to_string()))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.manage(MyState {
|
||||
value: 0,
|
||||
label: "Tauri!".into(),
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
simple_command,
|
||||
stateful_command,
|
||||
async_simple_command,
|
||||
async_stateful_command,
|
||||
simple_command_with_result,
|
||||
stateful_command_with_result,
|
||||
async_simple_command_with_result,
|
||||
async_stateful_command_with_result
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
56
examples/commands/src-tauri/tauri.conf.json
Normal file
@ -0,0 +1,56 @@
|
||||
{
|
||||
"build": {
|
||||
"distDir": "../public",
|
||||
"devPath": "../public",
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": ""
|
||||
},
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "com.tauri.dev",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"copyright": "",
|
||||
"category": "DeveloperTool",
|
||||
"shortDescription": "",
|
||||
"longDescription": "",
|
||||
"deb": {
|
||||
"depends": [],
|
||||
"useBootstrapper": false
|
||||
},
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "",
|
||||
"useBootstrapper": false,
|
||||
"exceptionDomain": ""
|
||||
}
|
||||
},
|
||||
"allowlist": {
|
||||
"all": true
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "Welcome to Tauri!",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
}
|