mirror of
https://github.com/tauri-apps/tauri.git
synced 2025-01-01 15:36:14 +03:00
wip: big refactor, removing application cycle + allowing generic parameters to the application (#1400)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
parent
c718bd2382
commit
2158a68d7d
@ -39,7 +39,7 @@ foreach ($command in $args) {
|
||||
}
|
||||
"fmt" {
|
||||
Write-Output "[$command] checking formatting"
|
||||
cargo fmt "--" --check
|
||||
cargo +nightly fmt "--" --check
|
||||
check_error
|
||||
}
|
||||
default {
|
||||
|
@ -30,7 +30,7 @@ for command in "$@"; do
|
||||
;;
|
||||
fmt)
|
||||
echo "[$command] checking formatting"
|
||||
cargo fmt -- --check
|
||||
cargo +nightly fmt -- --check
|
||||
;;
|
||||
*)
|
||||
echo "[cargo-check.sh] Unknown cargo sub-command: $command"
|
||||
|
4
cli/core/Cargo.lock
generated
4
cli/core/Cargo.lock
generated
@ -2420,9 +2420,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
|
||||
checksum = "d107f8c6e916235c4c01cabb3e8acf7bea8ef6a63ca2e7fa0527c049badfc48c"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
@ -1,6 +1,4 @@
|
||||
use anyhow::{Context, Result};
|
||||
use proc_macro2::Ident;
|
||||
use quote::format_ident;
|
||||
use std::{
|
||||
env::var,
|
||||
fs::{create_dir_all, File},
|
||||
@ -18,7 +16,6 @@ use tauri_codegen::{context_codegen, ContextData};
|
||||
#[derive(Debug)]
|
||||
pub struct CodegenContext {
|
||||
config_path: PathBuf,
|
||||
struct_ident: Ident,
|
||||
out_file: PathBuf,
|
||||
}
|
||||
|
||||
@ -26,7 +23,6 @@ impl Default for CodegenContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config_path: PathBuf::from("tauri.conf.json"),
|
||||
struct_ident: format_ident!("TauriBuildCodegenContext"),
|
||||
out_file: PathBuf::from("tauri-build-context.rs"),
|
||||
}
|
||||
}
|
||||
@ -48,20 +44,6 @@ impl CodegenContext {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the name of the generated struct.
|
||||
///
|
||||
/// Don't set this if you are using [`tauri::include_codegen_context!`] as that helper macro
|
||||
/// expects the default value. This option can be useful if you are not using the helper and
|
||||
/// instead using [`std::include!`] on the generated code yourself.
|
||||
///
|
||||
/// Defaults to `TauriBuildCodegenContext`.
|
||||
///
|
||||
/// [`tauri::include_codegen_context!`]: https://docs.rs/tauri/0.12/tauri/macro.include_codegen_context.html
|
||||
pub fn struct_ident(mut self, ident: impl AsRef<str>) -> Self {
|
||||
self.struct_ident = format_ident!("{}", ident.as_ref());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the output file's path.
|
||||
///
|
||||
/// **Note:** This path should be relative to the `OUT_DIR`.
|
||||
@ -100,7 +82,9 @@ impl CodegenContext {
|
||||
let code = context_codegen(ContextData {
|
||||
config,
|
||||
config_parent,
|
||||
struct_ident: self.struct_ident.clone(),
|
||||
// it's very hard to have a build script for unit tests, so assume this is always called from
|
||||
// outside the tauri crate, making the ::tauri root valid.
|
||||
context_path: quote::quote!(::tauri::Context),
|
||||
})?;
|
||||
|
||||
// get the full output file path
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::embedded_assets::{EmbeddedAssets, EmbeddedAssetsError};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use std::path::PathBuf;
|
||||
use tauri_api::config::Config;
|
||||
@ -8,7 +8,7 @@ use tauri_api::config::Config;
|
||||
pub struct ContextData {
|
||||
pub config: Config,
|
||||
pub config_parent: PathBuf,
|
||||
pub struct_ident: Ident,
|
||||
pub context_path: TokenStream,
|
||||
}
|
||||
|
||||
/// Build an `AsTauriContext` implementation for including in application code.
|
||||
@ -16,7 +16,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
||||
let ContextData {
|
||||
mut config,
|
||||
config_parent,
|
||||
struct_ident,
|
||||
context_path,
|
||||
} = data;
|
||||
let dist_dir = config_parent.join(&config.build.dist_dir);
|
||||
config.build.dist_dir = dist_dir.to_string_lossy().to_string();
|
||||
@ -25,44 +25,17 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
|
||||
let assets = EmbeddedAssets::new(&dist_dir)?;
|
||||
|
||||
// handle default window icons for Windows targets
|
||||
#[cfg(not(debug_assertions))]
|
||||
let default_window_icon = if cfg!(windows) {
|
||||
let icon_path = config_parent.join("icons/icon.ico").display().to_string();
|
||||
quote!(Some(include_bytes!(#icon_path)))
|
||||
quote!(Some(include_bytes!(#icon_path).to_vec()))
|
||||
} else {
|
||||
quote!(None)
|
||||
};
|
||||
|
||||
// in development builds, don't use an icon as it slows down cargo check
|
||||
#[cfg(debug_assertions)]
|
||||
let default_window_icon = quote!(None);
|
||||
|
||||
// double braces are purposeful to force the code into a block expression
|
||||
Ok(quote! {{
|
||||
use ::tauri::api::private::{OnceCell, AsTauriContext};
|
||||
|
||||
static CONFIG: OnceCell<::tauri::api::config::Config>= OnceCell::new();
|
||||
|
||||
/// Generated by `tauri-codegen`.
|
||||
struct #struct_ident;
|
||||
|
||||
impl AsTauriContext for #struct_ident {
|
||||
/// Return a static reference to the config we parsed at build time
|
||||
fn config() -> &'static ::tauri::api::config::Config {
|
||||
CONFIG.get_or_init(|| #config)
|
||||
}
|
||||
|
||||
/// Inject assets we generated during build time
|
||||
fn assets() -> &'static ::tauri::api::assets::EmbeddedAssets {
|
||||
#assets
|
||||
}
|
||||
|
||||
/// Default window icon to set automatically if exists
|
||||
fn default_window_icon() -> Option<&'static [u8]> {
|
||||
#default_window_icon
|
||||
}
|
||||
}
|
||||
|
||||
#struct_ident {}
|
||||
}})
|
||||
Ok(quote!(#context_path {
|
||||
config: #config,
|
||||
assets: #assets,
|
||||
default_window_icon: #default_window_icon,
|
||||
}))
|
||||
}
|
||||
|
@ -51,7 +51,6 @@ pub enum EmbeddedAssetsError {
|
||||
pub struct EmbeddedAssets(HashMap<AssetKey, (String, Vec<u8>)>);
|
||||
|
||||
impl EmbeddedAssets {
|
||||
#[cfg(not(debug_assertions))]
|
||||
/// Compress a directory of assets, ready to be generated into a [`tauri_api::assets::Assets`].
|
||||
pub fn new(path: &Path) -> Result<Self, EmbeddedAssetsError> {
|
||||
WalkDir::new(&path)
|
||||
@ -74,14 +73,6 @@ impl EmbeddedAssets {
|
||||
.map(Self)
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
/// A dummy EmbeddedAssets for use during development builds.
|
||||
/// Compressing + including the bytes of assets during development takes a long time.
|
||||
/// On development builds, assets will simply be resolved & fetched from the configured dist folder.
|
||||
pub fn new(_: &Path) -> Result<Self, EmbeddedAssetsError> {
|
||||
Ok(EmbeddedAssets(HashMap::new()))
|
||||
}
|
||||
|
||||
/// Use highest compression level for release, the fastest one for everything else
|
||||
fn compression_level() -> i32 {
|
||||
match var("PROFILE").as_ref().map(String::as_str) {
|
||||
@ -135,10 +126,9 @@ impl ToTokens for EmbeddedAssets {
|
||||
}
|
||||
|
||||
// we expect phf related items to be in path when generating the path code
|
||||
tokens.append_all(quote! {
|
||||
tokens.append_all(quote! {{
|
||||
use ::tauri::api::assets::{EmbeddedAssets, phf, phf::phf_map};
|
||||
static ASSETS: EmbeddedAssets = EmbeddedAssets::from_zstd(phf_map! { #map });
|
||||
&ASSETS
|
||||
});
|
||||
EmbeddedAssets::from_zstd(phf_map! { #map })
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
@ -14,17 +14,16 @@ struct Reply {
|
||||
|
||||
fn main() {
|
||||
tauri::AppBuilder::default()
|
||||
.setup(move |webview_manager| {
|
||||
let dispatcher = webview_manager.current_webview().unwrap();
|
||||
let dispatcher_ = dispatcher.clone();
|
||||
dispatcher.listen("js-event", move |event| {
|
||||
.on_page_load(|window, _| {
|
||||
let window_ = window.clone();
|
||||
window.listen("js-event".into(), move |event| {
|
||||
println!("got js-event with message '{:?}'", event.payload());
|
||||
let reply = Reply {
|
||||
data: "something else".to_string(),
|
||||
};
|
||||
|
||||
dispatcher_
|
||||
.emit("rust-event", Some(reply))
|
||||
window_
|
||||
.emit(&"rust-event".into(), Some(reply))
|
||||
.expect("failed to emit");
|
||||
});
|
||||
})
|
||||
@ -33,5 +32,6 @@ fn main() {
|
||||
cmd::perform_request
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.run();
|
||||
.run()
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
@ -12,5 +12,6 @@ fn main() {
|
||||
tauri::AppBuilder::default()
|
||||
.invoke_handler(tauri::generate_handler![my_custom_command])
|
||||
.build(tauri::generate_context!())
|
||||
.run();
|
||||
.run()
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
2
examples/multiwindow/dist/index.html
vendored
2
examples/multiwindow/dist/index.html
vendored
@ -72,4 +72,4 @@
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
@ -3,27 +3,20 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use tauri::WebviewBuilderExt;
|
||||
use tauri::Attributes;
|
||||
|
||||
fn main() {
|
||||
tauri::AppBuilder::default()
|
||||
.setup(move |webview_manager| {
|
||||
if webview_manager.current_window_label() == "Main" {
|
||||
webview_manager.listen("clicked", move |_| {
|
||||
println!("got 'clicked' event on global channel");
|
||||
});
|
||||
}
|
||||
let current_webview = webview_manager.current_webview().unwrap();
|
||||
let label = webview_manager.current_window_label().to_string();
|
||||
current_webview.listen("clicked", move |_| {
|
||||
println!("got 'clicked' event on window '{}'", label)
|
||||
.on_page_load(|window, _payload| {
|
||||
let label = window.label().to_string();
|
||||
window.listen("clicked".to_string(), move |_payload| {
|
||||
println!("got 'clicked' event on window '{}'", label);
|
||||
});
|
||||
})
|
||||
.create_webview("Rust".to_string(), tauri::WindowUrl::App, |mut builder| {
|
||||
builder = builder.title("Tauri - Rust");
|
||||
Ok(builder)
|
||||
.create_window("Rust".to_string(), tauri::WindowUrl::App, |attributes| {
|
||||
attributes.title("Tauri - Rust")
|
||||
})
|
||||
.unwrap()
|
||||
.build(tauri::generate_context!())
|
||||
.run();
|
||||
.run()
|
||||
.expect("failed to run tauri application");
|
||||
}
|
||||
|
@ -27,14 +27,12 @@
|
||||
"windows": [
|
||||
{
|
||||
"label": "Main",
|
||||
"url": "app",
|
||||
"title": "Tauri - Main",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
},
|
||||
{
|
||||
"label": "Secondary",
|
||||
"url": "app",
|
||||
"title": "Tauri - Secondary",
|
||||
"width": 600,
|
||||
"height": 400
|
||||
|
@ -1,23 +1,10 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
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,
|
||||
};
|
||||
|
||||
pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream {
|
||||
// Check if "with_manager" attr was passed to macro
|
||||
let uses_manager = attrs.iter().any(|a| {
|
||||
if let NestedMeta::Meta(Meta::Path(path)) = a {
|
||||
path
|
||||
.get_ident()
|
||||
.map(|i| *i == "with_manager")
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
@ -37,7 +24,7 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
|
||||
};
|
||||
|
||||
// Split function args into names and types
|
||||
let (mut names, mut types): (Vec<Ident>, Vec<Path>) = function
|
||||
let (names, types): (Vec<Ident>, Vec<Path>) = function
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
@ -59,22 +46,6 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
|
||||
})
|
||||
.unzip();
|
||||
|
||||
// If function doesn't take the webview manager, wrapper just takes webview manager generically and ignores it
|
||||
// Otherwise the wrapper uses the specific type from the original function declaration
|
||||
let mut manager_arg_type = quote!(::tauri::WebviewManager<A>);
|
||||
let manager_arg_maybe = match types.first() {
|
||||
Some(first_type) if uses_manager => {
|
||||
// Give wrapper specific type
|
||||
manager_arg_type = quote!(#first_type);
|
||||
// Remove webview manager arg from list so it isn't expected as arg from JS
|
||||
types.drain(0..1);
|
||||
names.drain(0..1);
|
||||
// Tell wrapper to pass webview manager to original function
|
||||
quote!(_manager,)
|
||||
}
|
||||
// Tell wrapper not to pass webview manager to original function
|
||||
_ => quote!(),
|
||||
};
|
||||
let await_maybe = if function.sig.asyncness.is_some() {
|
||||
quote!(.await)
|
||||
} else {
|
||||
@ -87,18 +58,18 @@ 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(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe {
|
||||
match #fn_name(#(parsed_args.#names),*)#await_maybe {
|
||||
Ok(value) => ::core::result::Result::Ok(value),
|
||||
Err(e) => ::core::result::Result::Err(e),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe) }
|
||||
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#(parsed_args.#names),*)#await_maybe) }
|
||||
};
|
||||
|
||||
quote! {
|
||||
#function
|
||||
pub fn #fn_wrapper<A: ::tauri::ApplicationExt + 'static>(_manager: #manager_arg_type, message: ::tauri::InvokeMessage<A>) {
|
||||
pub fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>) {
|
||||
#[derive(::serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ParsedArgs {
|
||||
@ -134,9 +105,9 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
|
||||
});
|
||||
|
||||
quote! {
|
||||
move |webview_manager, message| {
|
||||
move |message| {
|
||||
match message.command() {
|
||||
#(stringify!(#fn_names) => #fn_wrappers(webview_manager, message),)*
|
||||
#(stringify!(#fn_names) => #fn_wrappers(message),)*
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +1,80 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use std::path::PathBuf;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use std::{env::VarError, path::PathBuf};
|
||||
use syn::{
|
||||
parse::{Parse, ParseBuffer},
|
||||
punctuated::Punctuated,
|
||||
LitStr, PathArguments, PathSegment, Token,
|
||||
};
|
||||
use tauri_codegen::{context_codegen, get_config, ContextData};
|
||||
|
||||
/// Parse the passed [`proc_macro::TokenStream`] and make sure the config file exists.
|
||||
///
|
||||
/// The [`proc_macro::TokenStream`] is expected to be empty for the default config path, or passed
|
||||
/// a [`syn::LitStr`] a custom config path. The custom path can be either relative or absolute, with
|
||||
/// relative paths being searched from the current working directory of the compiling crate.
|
||||
///
|
||||
/// This is a macro so that we can use [`syn::parse_macro_input!`] and easily return a
|
||||
/// [`std::compile_error!`] when the path input is bad.
|
||||
#[macro_use]
|
||||
macro_rules! parse_config_path {
|
||||
($path:ident) => {
|
||||
{
|
||||
use ::std::{env::{var, VarError}, path::PathBuf};
|
||||
use ::syn::{parse_macro_input, LitStr};
|
||||
use ::quote::quote;
|
||||
pub(crate) struct ContextItems {
|
||||
config_file: PathBuf,
|
||||
context_path: syn::Path,
|
||||
}
|
||||
|
||||
let path = if $path.is_empty() {
|
||||
var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join("tauri.conf.json"))
|
||||
impl Parse for ContextItems {
|
||||
fn parse(input: &ParseBuffer) -> syn::parse::Result<Self> {
|
||||
let config_file = if input.is_empty() {
|
||||
std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join("tauri.conf.json"))
|
||||
} else {
|
||||
let raw: LitStr = input.parse()?;
|
||||
let path = PathBuf::from(raw.value());
|
||||
if path.is_relative() {
|
||||
std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join(path))
|
||||
} else {
|
||||
let raw = parse_macro_input!($path as LitStr);
|
||||
let path = PathBuf::from(raw.value());
|
||||
if path.is_relative() {
|
||||
var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join(path))
|
||||
} else {
|
||||
Ok(path)
|
||||
}
|
||||
};
|
||||
|
||||
let path = path
|
||||
.map_err(|error| match error {
|
||||
VarError::NotPresent => "no CARGO_MANIFEST_DIR env var, this should be set by cargo".into(),
|
||||
VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into()
|
||||
})
|
||||
.and_then(|path| {
|
||||
if path.exists() {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(format!(
|
||||
"no file at path {} exists, expected tauri config file",
|
||||
path.display()
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
match path {
|
||||
Ok(path) => path,
|
||||
Err(error_string) => return quote!(compile_error!(#error_string)).into(),
|
||||
Ok(path)
|
||||
}
|
||||
}
|
||||
.map_err(|error| match error {
|
||||
VarError::NotPresent => "no CARGO_MANIFEST_DIR env var, this should be set by cargo".into(),
|
||||
VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into(),
|
||||
})
|
||||
.and_then(|path| {
|
||||
if path.exists() {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(format!(
|
||||
"no file at path {} exists, expected tauri config file",
|
||||
path.display()
|
||||
))
|
||||
}
|
||||
})
|
||||
.map_err(|e| input.error(e))?;
|
||||
|
||||
let context_path = if input.is_empty() {
|
||||
let mut segments = Punctuated::new();
|
||||
segments.push(PathSegment {
|
||||
ident: Ident::new("tauri", Span::call_site()),
|
||||
arguments: PathArguments::None,
|
||||
});
|
||||
segments.push(PathSegment {
|
||||
ident: Ident::new("Context", Span::call_site()),
|
||||
arguments: PathArguments::None,
|
||||
});
|
||||
syn::Path {
|
||||
leading_colon: Some(Token![::](Span::call_site())),
|
||||
segments,
|
||||
}
|
||||
} else {
|
||||
let _: Token![,] = input.parse()?;
|
||||
input.call(syn::Path::parse_mod_style)?
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
config_file,
|
||||
context_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_context(path: PathBuf) -> TokenStream {
|
||||
let context = get_config(&path)
|
||||
pub(crate) fn generate_context(context: ContextItems) -> TokenStream {
|
||||
let context = get_config(&context.config_file)
|
||||
.map_err(|e| e.to_string())
|
||||
.map(|(config, config_parent)| ContextData {
|
||||
config,
|
||||
config_parent,
|
||||
struct_ident: format_ident!("AutoTauriContext"),
|
||||
context_path: context.context_path.to_token_stream(),
|
||||
})
|
||||
.and_then(|data| context_codegen(data).map_err(|e| e.to_string()));
|
||||
|
||||
|
@ -1,6 +1,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;
|
||||
|
||||
@ -8,10 +9,9 @@ mod command;
|
||||
mod context;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn command(attrs: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn command(_: 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()
|
||||
}
|
||||
|
||||
@ -34,8 +34,8 @@ pub fn generate_handler(item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// todo: link the [`AsTauriContext`] docs
|
||||
#[proc_macro]
|
||||
pub fn generate_context(item: TokenStream) -> TokenStream {
|
||||
pub fn generate_context(items: TokenStream) -> TokenStream {
|
||||
// this macro is exported from the context module
|
||||
let path = parse_config_path!(item);
|
||||
let path = parse_macro_input!(items as ContextItems);
|
||||
context::generate_context(path).into()
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ impl<P: AsRef<Path>> From<P> for AssetKey {
|
||||
}
|
||||
|
||||
/// Represents a container of file assets that are retrievable during runtime.
|
||||
pub trait Assets {
|
||||
pub trait Assets: Send + Sync + 'static {
|
||||
/// Get the content of the passed [`AssetKey`].
|
||||
fn get<Key: Into<AssetKey>>(&self, key: Key) -> Option<Cow<'_, [u8]>>;
|
||||
}
|
||||
|
@ -388,17 +388,6 @@ pub struct Config {
|
||||
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
|
||||
pub struct PluginConfig(pub HashMap<String, JsonValue>);
|
||||
|
||||
impl PluginConfig {
|
||||
/// Gets a plugin configuration.
|
||||
pub fn get<S: AsRef<str>>(&self, plugin_name: S) -> String {
|
||||
self
|
||||
.0
|
||||
.get(plugin_name.as_ref())
|
||||
.map(|config| config.to_string())
|
||||
.unwrap_or_else(|| "{}".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement `ToTokens` for all config structs, allowing a literal `Config` to be built.
|
||||
///
|
||||
/// This allows for a build script to output the values in a `Config` to a `TokenStream`, which can
|
||||
|
@ -23,13 +23,12 @@ serde = { version = "1.0", features = [ "derive" ] }
|
||||
base64 = "0.13.0"
|
||||
tokio = { version = "1.4", features = ["rt", "rt-multi-thread", "sync"] }
|
||||
futures = "0.3"
|
||||
async-trait = "0.1"
|
||||
uuid = { version = "0.8.2", features = [ "v4" ] }
|
||||
thiserror = "1.0.24"
|
||||
once_cell = "1.7.2"
|
||||
tauri-api = { version = "0.7.5", path = "../tauri-api" }
|
||||
tauri-macros = { version = "0.1", path = "../tauri-macros" }
|
||||
wry = "0.6"
|
||||
wry = "0.7"
|
||||
rand = "0.8"
|
||||
|
||||
[build-dependencies]
|
||||
|
494
tauri/src/app.rs
494
tauri/src/app.rs
@ -1,494 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use tauri_api::{
|
||||
config::Config,
|
||||
private::AsTauriContext,
|
||||
rpc::{format_callback, format_callback_result},
|
||||
};
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub(crate) mod event;
|
||||
mod utils;
|
||||
pub(crate) mod webview;
|
||||
mod webview_manager;
|
||||
|
||||
pub use crate::api::config::WindowUrl;
|
||||
use crate::flavors::Wry;
|
||||
pub use webview::{
|
||||
wry::WryApplication, ApplicationDispatcherExt, ApplicationExt, CustomProtocol, FileDropEvent,
|
||||
FileDropHandler, Icon, Message, RpcRequest, WebviewBuilderExt, WebviewRpcHandler,
|
||||
};
|
||||
pub use webview_manager::{WebviewDispatcher, WebviewManager};
|
||||
|
||||
type InvokeHandler<A> = dyn Fn(WebviewManager<A>, InvokeMessage<A>) + Send;
|
||||
type ManagerHook<A> = dyn Fn(WebviewManager<A>) + Send;
|
||||
type PageLoadHook<A> = dyn Fn(WebviewManager<A>, PageLoadPayload) + Send;
|
||||
|
||||
/// Payload from an invoke call.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct InvokePayload {
|
||||
#[serde(rename = "__tauriModule")]
|
||||
tauri_module: Option<String>,
|
||||
callback: String,
|
||||
error: String,
|
||||
#[serde(rename = "mainThread", default)]
|
||||
pub(crate) main_thread: bool,
|
||||
#[serde(flatten)]
|
||||
inner: JsonValue,
|
||||
}
|
||||
|
||||
/// An invoke message.
|
||||
pub struct InvokeMessage<A: ApplicationExt> {
|
||||
webview_manager: WebviewManager<A>,
|
||||
command: String,
|
||||
payload: InvokePayload,
|
||||
}
|
||||
|
||||
impl<A: ApplicationExt + 'static> InvokeMessage<A> {
|
||||
pub(crate) fn new(
|
||||
webview_manager: WebviewManager<A>,
|
||||
command: String,
|
||||
payload: InvokePayload,
|
||||
) -> Self {
|
||||
Self {
|
||||
webview_manager,
|
||||
command,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
/// The invoke command.
|
||||
pub fn command(&self) -> &str {
|
||||
&self.command
|
||||
}
|
||||
|
||||
/// The invoke payload.
|
||||
pub fn payload(&self) -> JsonValue {
|
||||
self.payload.inner.clone()
|
||||
}
|
||||
|
||||
/// Reply to the invoke promise with a async task.
|
||||
pub fn respond_async<
|
||||
T: Serialize,
|
||||
E: Serialize,
|
||||
F: std::future::Future<Output = Result<T, E>> + Send + 'static,
|
||||
>(
|
||||
self,
|
||||
task: F,
|
||||
) {
|
||||
if self.payload.main_thread {
|
||||
crate::async_runtime::block_on(async move {
|
||||
return_task(
|
||||
&self.webview_manager,
|
||||
task,
|
||||
self.payload.callback,
|
||||
self.payload.error,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
} else {
|
||||
crate::async_runtime::spawn(async move {
|
||||
return_task(
|
||||
&self.webview_manager,
|
||||
task,
|
||||
self.payload.callback,
|
||||
self.payload.error,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Reply to the invoke promise running the given closure.
|
||||
pub fn respond_closure<T: Serialize, E: Serialize, F: FnOnce() -> Result<T, E>>(self, f: F) {
|
||||
return_closure(
|
||||
&self.webview_manager,
|
||||
f,
|
||||
self.payload.callback,
|
||||
self.payload.error,
|
||||
)
|
||||
}
|
||||
|
||||
/// Resolve the invoke promise with a value.
|
||||
pub fn resolve<S: Serialize>(self, value: S) {
|
||||
return_result(
|
||||
&self.webview_manager,
|
||||
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) {
|
||||
return_result(
|
||||
&self.webview_manager,
|
||||
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.
|
||||
async fn return_task<
|
||||
A: ApplicationExt + 'static,
|
||||
T: Serialize,
|
||||
E: Serialize,
|
||||
F: std::future::Future<Output = Result<T, E>> + Send + 'static,
|
||||
>(
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
task: F,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
) {
|
||||
let result = task.await;
|
||||
return_closure(webview_manager, || result, success_callback, error_callback)
|
||||
}
|
||||
|
||||
fn return_closure<
|
||||
A: ApplicationExt + 'static,
|
||||
T: Serialize,
|
||||
E: Serialize,
|
||||
F: FnOnce() -> Result<T, E>,
|
||||
>(
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
f: F,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
) {
|
||||
return_result(webview_manager, f(), success_callback, error_callback)
|
||||
}
|
||||
|
||||
fn return_result<A: ApplicationExt + 'static, T: Serialize, E: Serialize>(
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
result: Result<T, E>,
|
||||
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()),
|
||||
};
|
||||
if let Ok(dispatcher) = webview_manager.current_webview() {
|
||||
let _ = dispatcher.eval(callback_string.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
/// `App` runtime information.
|
||||
pub struct Context {
|
||||
pub(crate) config: &'static Config,
|
||||
pub(crate) default_window_icon: Option<&'static [u8]>,
|
||||
pub(crate) assets: &'static tauri_api::assets::EmbeddedAssets,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn new<Context: AsTauriContext>(_: Context) -> Self {
|
||||
Self {
|
||||
config: Context::config(),
|
||||
default_window_icon: Context::default_window_icon(),
|
||||
assets: Context::assets(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Webview<A: ApplicationExt> {
|
||||
pub(crate) builder: A::WebviewBuilder,
|
||||
pub(crate) label: String,
|
||||
pub(crate) url: WindowUrl,
|
||||
}
|
||||
|
||||
/// The payload for the "page_load" hook.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PageLoadPayload {
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl PageLoadPayload {
|
||||
/// The page URL.
|
||||
pub fn url(&self) -> &str {
|
||||
&self.url
|
||||
}
|
||||
}
|
||||
|
||||
/// The application runner.
|
||||
pub struct App<A: ApplicationExt> {
|
||||
/// The JS message handler.
|
||||
invoke_handler: Option<Box<InvokeHandler<A>>>,
|
||||
/// The page load hook, invoked when the webview performs a navigation.
|
||||
on_page_load: Option<Box<PageLoadHook<A>>>,
|
||||
/// The setup hook, invoked when the webviews have been created.
|
||||
setup: Option<Box<ManagerHook<A>>>,
|
||||
/// The context the App was created with
|
||||
pub(crate) context: Context,
|
||||
pub(crate) dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
|
||||
pub(crate) webviews: Option<Vec<Webview<A>>>,
|
||||
url: String,
|
||||
window_labels: Arc<Mutex<Vec<String>>>,
|
||||
plugin_initialization_script: String,
|
||||
}
|
||||
|
||||
impl<A: ApplicationExt + 'static> App<A> {
|
||||
/// Runs the app until it finishes.
|
||||
pub fn run(mut self) {
|
||||
{
|
||||
let mut window_labels = self.window_labels.lock().unwrap();
|
||||
for window_config in self.context.config.tauri.windows.clone() {
|
||||
let window_url = window_config.url.clone();
|
||||
let window_label = window_config.label.to_string();
|
||||
window_labels.push(window_label.to_string());
|
||||
let webview = A::WebviewBuilder::from(webview::WindowConfig(window_config));
|
||||
let mut webviews = self.webviews.take().unwrap();
|
||||
webviews.push(Webview {
|
||||
label: window_label,
|
||||
builder: webview,
|
||||
url: window_url,
|
||||
});
|
||||
self.webviews = Some(webviews);
|
||||
}
|
||||
}
|
||||
|
||||
run(self).expect("failed to run application");
|
||||
}
|
||||
|
||||
/// Runs the invoke handler if defined.
|
||||
/// Returns whether the message was consumed or not.
|
||||
/// The message is considered consumed if the handler exists and returns an Ok Result.
|
||||
pub(crate) fn run_invoke_handler(
|
||||
&self,
|
||||
dispatcher: &WebviewManager<A>,
|
||||
message: InvokeMessage<A>,
|
||||
) {
|
||||
if let Some(ref invoke_handler) = self.invoke_handler {
|
||||
invoke_handler(dispatcher.clone(), message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the setup hook if defined.
|
||||
pub(crate) fn run_setup(&self, dispatcher: WebviewManager<A>) {
|
||||
if let Some(ref setup) = self.setup {
|
||||
setup(dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the on page load hook if defined.
|
||||
pub(crate) fn run_on_page_load(&self, dispatcher: &WebviewManager<A>, payload: PageLoadPayload) {
|
||||
if let Some(ref on_page_load) = self.on_page_load {
|
||||
on_page_load(dispatcher.clone(), payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WebviewContext<A> = (
|
||||
<A as ApplicationExt>::WebviewBuilder,
|
||||
Option<WebviewRpcHandler<<A as ApplicationExt>::Dispatcher>>,
|
||||
Option<CustomProtocol>,
|
||||
Option<FileDropHandler>,
|
||||
);
|
||||
|
||||
trait WebviewInitializer<A: ApplicationExt> {
|
||||
fn init_webview(&self, webview: Webview<A>) -> crate::Result<WebviewContext<A>>;
|
||||
|
||||
fn on_webview_created(&self, webview_label: String, dispatcher: A::Dispatcher);
|
||||
}
|
||||
|
||||
impl<A: ApplicationExt + 'static> WebviewInitializer<A> for Arc<Mutex<App<A>>> {
|
||||
fn init_webview(&self, webview: Webview<A>) -> crate::Result<WebviewContext<A>> {
|
||||
let application = self.lock().unwrap();
|
||||
let webview_manager = WebviewManager::new(
|
||||
self.clone(),
|
||||
application.dispatchers.clone(),
|
||||
webview.label.to_string(),
|
||||
);
|
||||
let (webview_builder, rpc_handler, custom_protocol) = utils::build_webview(
|
||||
self.clone(),
|
||||
webview,
|
||||
&webview_manager,
|
||||
&application.url,
|
||||
&application.window_labels.lock().unwrap(),
|
||||
&application.plugin_initialization_script,
|
||||
&application.context,
|
||||
)?;
|
||||
let file_drop_handler: Box<dyn Fn(FileDropEvent) -> bool + Send> = Box::new(move |event| {
|
||||
let webview_manager = webview_manager.clone();
|
||||
crate::async_runtime::block_on(async move {
|
||||
let webview = webview_manager.current_webview().unwrap();
|
||||
let _ = match event {
|
||||
FileDropEvent::Hovered(paths) => webview.emit("tauri://file-drop-hover", Some(paths)),
|
||||
FileDropEvent::Dropped(paths) => webview.emit("tauri://file-drop", Some(paths)),
|
||||
FileDropEvent::Cancelled => webview.emit("tauri://file-drop-cancelled", Some(())),
|
||||
};
|
||||
});
|
||||
true
|
||||
});
|
||||
Ok((
|
||||
webview_builder,
|
||||
rpc_handler,
|
||||
custom_protocol,
|
||||
Some(file_drop_handler),
|
||||
))
|
||||
}
|
||||
|
||||
fn on_webview_created(&self, webview_label: String, dispatcher: A::Dispatcher) {
|
||||
self.lock().unwrap().dispatchers.lock().unwrap().insert(
|
||||
webview_label.to_string(),
|
||||
WebviewDispatcher::new(dispatcher, webview_label),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The App builder.
|
||||
pub struct AppBuilder<A = Wry>
|
||||
where
|
||||
A: ApplicationExt,
|
||||
{
|
||||
/// The JS message handler.
|
||||
invoke_handler: Option<Box<InvokeHandler<A>>>,
|
||||
/// The setup hook.
|
||||
setup: Option<Box<ManagerHook<A>>>,
|
||||
/// Page load hook.
|
||||
on_page_load: Option<Box<PageLoadHook<A>>>,
|
||||
/// The webview dispatchers.
|
||||
dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
|
||||
/// The created webviews.
|
||||
webviews: Vec<Webview<A>>,
|
||||
}
|
||||
|
||||
impl<A: ApplicationExt + 'static> AppBuilder<A> {
|
||||
/// Creates a new App builder.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
invoke_handler: None,
|
||||
setup: None,
|
||||
on_page_load: None,
|
||||
dispatchers: Default::default(),
|
||||
webviews: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the JS message handler callback.
|
||||
pub fn invoke_handler<F: Fn(WebviewManager<A>, InvokeMessage<A>) + Send + 'static>(
|
||||
mut self,
|
||||
invoke_handler: F,
|
||||
) -> Self {
|
||||
self.invoke_handler = Some(Box::new(invoke_handler));
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the setup hook.
|
||||
pub fn setup<F: Fn(WebviewManager<A>) + Send + 'static>(mut self, setup: F) -> Self {
|
||||
self.setup = Some(Box::new(setup));
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the page load hook.
|
||||
pub fn on_page_load<F: Fn(WebviewManager<A>, PageLoadPayload) + Send + 'static>(
|
||||
mut self,
|
||||
on_page_load: F,
|
||||
) -> Self {
|
||||
self.on_page_load = Some(Box::new(on_page_load));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a plugin to the runtime.
|
||||
pub fn plugin(self, plugin: impl crate::plugin::Plugin<A> + Send + 'static) -> Self {
|
||||
crate::plugin::register(A::plugin_store(), plugin);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new webview.
|
||||
pub fn create_webview<F: FnOnce(A::WebviewBuilder) -> crate::Result<A::WebviewBuilder>>(
|
||||
mut self,
|
||||
label: String,
|
||||
url: WindowUrl,
|
||||
f: F,
|
||||
) -> crate::Result<Self> {
|
||||
let builder = f(A::WebviewBuilder::new())?;
|
||||
self.webviews.push(Webview {
|
||||
label,
|
||||
builder,
|
||||
url,
|
||||
});
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Builds the App.
|
||||
pub fn build(self, context: impl AsTauriContext) -> App<A> {
|
||||
let window_labels: Vec<String> = self.webviews.iter().map(|w| w.label.to_string()).collect();
|
||||
let plugin_initialization_script = crate::plugin::initialization_script(A::plugin_store());
|
||||
|
||||
let context = Context::new(context);
|
||||
let url = utils::get_url(&context);
|
||||
|
||||
App {
|
||||
invoke_handler: self.invoke_handler,
|
||||
setup: self.setup,
|
||||
on_page_load: self.on_page_load,
|
||||
context,
|
||||
dispatchers: self.dispatchers,
|
||||
webviews: Some(self.webviews),
|
||||
url,
|
||||
window_labels: Arc::new(Mutex::new(window_labels)),
|
||||
plugin_initialization_script,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make `Wry` the default `ApplicationExt` for `AppBuilder`
|
||||
impl Default for AppBuilder<Wry> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn run<A: ApplicationExt + 'static>(mut application: App<A>) -> crate::Result<()> {
|
||||
let plugin_config = application.context.config.plugins.clone();
|
||||
crate::plugin::initialize(A::plugin_store(), plugin_config)?;
|
||||
|
||||
let webviews = application.webviews.take().unwrap();
|
||||
|
||||
let dispatchers = application.dispatchers.clone();
|
||||
let application = Arc::new(Mutex::new(application));
|
||||
let mut webview_app = A::new()?;
|
||||
let mut main_webview_manager = None;
|
||||
|
||||
for webview in webviews {
|
||||
let webview_label = webview.label.to_string();
|
||||
let webview_manager = WebviewManager::new(
|
||||
application.clone(),
|
||||
dispatchers.clone(),
|
||||
webview_label.to_string(),
|
||||
);
|
||||
if main_webview_manager.is_none() {
|
||||
main_webview_manager = Some(webview_manager.clone());
|
||||
}
|
||||
let (webview_builder, rpc_handler, custom_protocol, file_drop_handler) =
|
||||
application.init_webview(webview)?;
|
||||
|
||||
let dispatcher = webview_app.create_webview(
|
||||
webview_builder,
|
||||
rpc_handler,
|
||||
custom_protocol,
|
||||
file_drop_handler,
|
||||
)?;
|
||||
application.on_webview_created(webview_label, dispatcher);
|
||||
crate::plugin::created(A::plugin_store(), &webview_manager);
|
||||
}
|
||||
|
||||
if let Some(main_webview_manager) = main_webview_manager {
|
||||
application.lock().unwrap().run_setup(main_webview_manager);
|
||||
}
|
||||
|
||||
webview_app.run();
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,244 +0,0 @@
|
||||
use std::{
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::ApplicationDispatcherExt;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Event identifier.
|
||||
pub type EventId = u64;
|
||||
|
||||
/// An event handler.
|
||||
struct EventHandler {
|
||||
/// Event identifier.
|
||||
id: EventId,
|
||||
/// A event handler might be global or tied to a window.
|
||||
window_label: Option<String>,
|
||||
/// The on event callback.
|
||||
on_event: Box<dyn Fn(EventPayload) + Send>,
|
||||
}
|
||||
|
||||
type Listeners = Arc<Mutex<HashMap<String, Vec<EventHandler>>>>;
|
||||
|
||||
static EMIT_FUNCTION_NAME: Lazy<String> = Lazy::new(|| Uuid::new_v4().to_string());
|
||||
static EVENT_LISTENERS_OBJECT_NAME: Lazy<String> = Lazy::new(|| Uuid::new_v4().to_string());
|
||||
static EVENT_QUEUE_OBJECT_NAME: Lazy<String> = Lazy::new(|| Uuid::new_v4().to_string());
|
||||
|
||||
/// Gets the listeners map.
|
||||
fn listeners() -> &'static Listeners {
|
||||
static LISTENERS: Lazy<Listeners> = Lazy::new(Default::default);
|
||||
&LISTENERS
|
||||
}
|
||||
|
||||
/// the emit JS function name
|
||||
pub fn emit_function_name() -> String {
|
||||
EMIT_FUNCTION_NAME.to_string()
|
||||
}
|
||||
|
||||
/// the event listeners JS object name
|
||||
pub fn event_listeners_object_name() -> String {
|
||||
EVENT_LISTENERS_OBJECT_NAME.to_string()
|
||||
}
|
||||
|
||||
/// the event queue JS object name
|
||||
pub fn event_queue_object_name() -> String {
|
||||
EVENT_QUEUE_OBJECT_NAME.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventPayload {
|
||||
id: EventId,
|
||||
payload: Option<String>,
|
||||
}
|
||||
|
||||
impl EventPayload {
|
||||
/// The event identifier.
|
||||
pub fn id(&self) -> EventId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// The event payload.
|
||||
pub fn payload(&self) -> Option<&String> {
|
||||
self.payload.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an event listener for JS events.
|
||||
pub fn listen<F: Fn(EventPayload) + Send + 'static>(
|
||||
event_name: impl AsRef<str>,
|
||||
window_label: Option<String>,
|
||||
handler: F,
|
||||
) -> EventId {
|
||||
let mut l = listeners()
|
||||
.lock()
|
||||
.expect("Failed to lock listeners: listen()");
|
||||
let id = rand::random();
|
||||
let handler = EventHandler {
|
||||
id,
|
||||
window_label,
|
||||
on_event: Box::new(handler),
|
||||
};
|
||||
if let Some(listeners) = l.get_mut(event_name.as_ref()) {
|
||||
listeners.push(handler);
|
||||
} else {
|
||||
l.insert(event_name.as_ref().to_string(), vec![handler]);
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
/// Listen to an JS event and immediately unlisten.
|
||||
pub fn once<F: Fn(EventPayload) + Send + 'static>(
|
||||
event_name: impl AsRef<str>,
|
||||
window_label: Option<String>,
|
||||
handler: F,
|
||||
) {
|
||||
listen(event_name, window_label, move |event| {
|
||||
unlisten(event.id);
|
||||
handler(event);
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes an event listener.
|
||||
pub fn unlisten(event_id: EventId) {
|
||||
crate::async_runtime::spawn(async move {
|
||||
let mut event_listeners = listeners()
|
||||
.lock()
|
||||
.expect("Failed to lock listeners: listen()");
|
||||
for listeners in event_listeners.values_mut() {
|
||||
if let Some(index) = listeners.iter().position(|l| l.id == event_id) {
|
||||
listeners.remove(index);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Emits an event to JS.
|
||||
pub fn emit<D: ApplicationDispatcherExt, S: Serialize>(
|
||||
webview_dispatcher: &crate::WebviewDispatcher<D>,
|
||||
event: impl AsRef<str>,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
let salt = crate::salt::generate();
|
||||
|
||||
let js_payload = if let Some(payload_value) = payload {
|
||||
serde_json::to_value(payload_value)?
|
||||
} else {
|
||||
JsonValue::Null
|
||||
};
|
||||
|
||||
webview_dispatcher.eval(&format!(
|
||||
"window['{}']({{event: '{}', payload: {}}}, '{}')",
|
||||
emit_function_name(),
|
||||
event.as_ref(),
|
||||
js_payload,
|
||||
salt
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Triggers the given event with its payload.
|
||||
pub(crate) fn on_event(event: String, window_label: Option<&str>, data: Option<String>) {
|
||||
let mut l = listeners()
|
||||
.lock()
|
||||
.expect("Failed to lock listeners: on_event()");
|
||||
|
||||
if l.contains_key(&event) {
|
||||
let listeners = l.get_mut(&event).expect("Failed to get mutable handler");
|
||||
for handler in listeners {
|
||||
if let Some(target_window_label) = window_label {
|
||||
// if the emitted event targets a specifid window, only triggers the listeners associated to that window
|
||||
if handler.window_label.as_deref() == Some(target_window_label) {
|
||||
let payload = data.clone();
|
||||
(handler.on_event)(EventPayload {
|
||||
id: handler.id,
|
||||
payload,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// otherwise triggers all listeners
|
||||
let payload = data.clone();
|
||||
(handler.on_event)(EventPayload {
|
||||
id: handler.id,
|
||||
payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
// dummy event handler function
|
||||
fn event_fn(s: EventPayload) {
|
||||
println!("{:?}", s);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig::with_cases(10000))]
|
||||
#[test]
|
||||
// check to see if listen() is properly passing keys into the LISTENERS map
|
||||
fn listeners_check_key(e in "[a-z]+") {
|
||||
// clone e as the key
|
||||
let key = e.clone();
|
||||
// pass e and an dummy func into listen
|
||||
listen(e, None, event_fn);
|
||||
|
||||
// lock mutex
|
||||
let l = listeners().lock().unwrap();
|
||||
|
||||
// check if the generated key is in the map
|
||||
assert_eq!(l.contains_key(&key), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// check to see if listen inputs a handler function properly into the LISTENERS map.
|
||||
fn listeners_check_fn(e in "[a-z]+") {
|
||||
// clone e as the key
|
||||
let key = e.clone();
|
||||
// pass e and an dummy func into listen
|
||||
listen(e, None, event_fn);
|
||||
|
||||
// lock mutex
|
||||
let mut l = listeners().lock().unwrap();
|
||||
|
||||
// check if l contains key
|
||||
if l.contains_key(&key) {
|
||||
// grab key if it exists
|
||||
let handler = l.get_mut(&key);
|
||||
// check to see if we get back a handler or not
|
||||
match handler {
|
||||
// pass on Some(handler)
|
||||
Some(_) => {},
|
||||
// Fail on None
|
||||
None => panic!("handler is None")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// check to see if on_event properly grabs the stored function from listen.
|
||||
fn check_on_event(e in "[a-z]+", d in "[a-z]+") {
|
||||
// clone e as the key
|
||||
let key = e.clone();
|
||||
// call listen with e and the event_fn dummy func
|
||||
listen(e.clone(), None, event_fn);
|
||||
// call on event with e and d.
|
||||
on_event(e, None, Some(d));
|
||||
|
||||
// lock the mutex
|
||||
let l = listeners().lock().unwrap();
|
||||
|
||||
// assert that the key is contained in the listeners map
|
||||
assert!(l.contains_key(&key));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,303 +0,0 @@
|
||||
use crate::{
|
||||
api::{assets::Assets, config::WindowUrl},
|
||||
app::Icon,
|
||||
ApplicationExt, WebviewBuilderExt,
|
||||
};
|
||||
|
||||
use super::{
|
||||
webview::{CustomProtocol, WebviewBuilderExtPrivate, WebviewRpcHandler},
|
||||
App, Context, InvokeMessage, InvokePayload, PageLoadPayload, RpcRequest, Webview, WebviewManager,
|
||||
};
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
// setup content for dev-server
|
||||
#[cfg(dev)]
|
||||
pub(super) fn get_url(context: &Context) -> String {
|
||||
let config = &context.config;
|
||||
if config.build.dev_path.starts_with("http") {
|
||||
config.build.dev_path.clone()
|
||||
} else {
|
||||
let path = "index.html";
|
||||
format!(
|
||||
"data:text/html;base64,{}",
|
||||
base64::encode(
|
||||
context
|
||||
.assets
|
||||
.get(&path)
|
||||
.ok_or_else(|| crate::Error::AssetNotFound(path.to_string()))
|
||||
.map(Cow::into_owned)
|
||||
.expect("Unable to find `index.html` under your devPath folder")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(custom_protocol)]
|
||||
pub(super) fn get_url(context: &Context) -> String {
|
||||
// Custom protocol doesn't require any setup, so just return URL
|
||||
format!("tauri://{}", context.config.tauri.bundle.identifier)
|
||||
}
|
||||
|
||||
pub(super) fn initialization_script(
|
||||
plugin_initialization_script: &str,
|
||||
with_global_tauri: bool,
|
||||
) -> String {
|
||||
format!(
|
||||
r#"
|
||||
{bundle_script}
|
||||
{core_script}
|
||||
{event_initialization_script}
|
||||
if (window.rpc) {{
|
||||
window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
|
||||
}} else {{
|
||||
window.addEventListener('DOMContentLoaded', function () {{
|
||||
window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
|
||||
}})
|
||||
}}
|
||||
{plugin_initialization_script}
|
||||
"#,
|
||||
core_script = include_str!("../../scripts/core.js"),
|
||||
bundle_script = if with_global_tauri {
|
||||
include_str!("../../scripts/bundle.js")
|
||||
} else {
|
||||
""
|
||||
},
|
||||
event_initialization_script = event_initialization_script(),
|
||||
plugin_initialization_script = plugin_initialization_script
|
||||
)
|
||||
}
|
||||
|
||||
fn event_initialization_script() -> String {
|
||||
return format!(
|
||||
"
|
||||
window['{queue}'] = [];
|
||||
window['{fn}'] = function (eventData, salt, ignoreQueue) {{
|
||||
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
|
||||
if (!ignoreQueue && listeners.length === 0) {{
|
||||
window['{queue}'].push({{
|
||||
eventData: eventData,
|
||||
salt: salt
|
||||
}})
|
||||
}}
|
||||
|
||||
if (listeners.length > 0) {{
|
||||
window.__TAURI__.invoke('tauri', {{
|
||||
__tauriModule: 'Internal',
|
||||
message: {{
|
||||
cmd: 'validateSalt',
|
||||
salt: salt
|
||||
}}
|
||||
}}).then(function (flag) {{
|
||||
if (flag) {{
|
||||
for (let i = listeners.length - 1; i >= 0; i--) {{
|
||||
const listener = listeners[i]
|
||||
eventData.id = listener.id
|
||||
listener.handler(eventData)
|
||||
}}
|
||||
}}
|
||||
}})
|
||||
}}
|
||||
}}
|
||||
",
|
||||
fn = crate::event::emit_function_name(),
|
||||
queue = crate::event::event_queue_object_name(),
|
||||
listeners = crate::event::event_listeners_object_name()
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) type BuiltWebview<A> = (
|
||||
<A as ApplicationExt>::WebviewBuilder,
|
||||
Option<WebviewRpcHandler<<A as ApplicationExt>::Dispatcher>>,
|
||||
Option<CustomProtocol>,
|
||||
);
|
||||
|
||||
// build the webview.
|
||||
pub(super) fn build_webview<A: ApplicationExt + 'static>(
|
||||
application: Arc<Mutex<App<A>>>,
|
||||
webview: Webview<A>,
|
||||
webview_manager: &WebviewManager<A>,
|
||||
content_url: &str,
|
||||
window_labels: &[String],
|
||||
plugin_initialization_script: &str,
|
||||
context: &Context,
|
||||
) -> crate::Result<BuiltWebview<A>> {
|
||||
let webview_url = match &webview.url {
|
||||
WindowUrl::App => content_url.to_string(),
|
||||
WindowUrl::Custom(url) => url.to_string(),
|
||||
};
|
||||
|
||||
let is_local = match webview.url {
|
||||
WindowUrl::App => true,
|
||||
WindowUrl::Custom(url) => &url[0..8] == "tauri://",
|
||||
};
|
||||
let (webview_builder, rpc_handler, custom_protocol) = if is_local {
|
||||
let mut webview_builder = webview.builder.url(webview_url)
|
||||
.initialization_script(&initialization_script(plugin_initialization_script, context.config.build.with_global_tauri))
|
||||
.initialization_script(&format!(
|
||||
r#"
|
||||
window.__TAURI__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
|
||||
window.__TAURI__.__currentWindow = {{ label: "{current_window_label}" }}
|
||||
"#,
|
||||
window_labels_array =
|
||||
serde_json::to_string(&window_labels).unwrap(),
|
||||
current_window_label = webview.label,
|
||||
));
|
||||
|
||||
if !webview_builder.has_icon() {
|
||||
if let Some(default_window_icon) = &context.default_window_icon {
|
||||
webview_builder = webview_builder.icon(Icon::Raw(default_window_icon.to_vec()))?;
|
||||
}
|
||||
}
|
||||
|
||||
let webview_manager_ = webview_manager.clone();
|
||||
let rpc_handler: Box<dyn Fn(<A as ApplicationExt>::Dispatcher, RpcRequest) + Send> =
|
||||
Box::new(move |_, request: RpcRequest| {
|
||||
let command = request.command.clone();
|
||||
let arg = request
|
||||
.params
|
||||
.unwrap()
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.first_mut()
|
||||
.unwrap_or(&mut JsonValue::Null)
|
||||
.take();
|
||||
let webview_manager = webview_manager_.clone();
|
||||
match serde_json::from_value::<InvokePayload>(arg) {
|
||||
Ok(message) => {
|
||||
let _ = on_message(application.clone(), webview_manager, command, message);
|
||||
}
|
||||
Err(e) => {
|
||||
if let Ok(dispatcher) = webview_manager.current_webview() {
|
||||
let error: crate::Error = e.into();
|
||||
let _ = dispatcher.eval(&format!(
|
||||
r#"console.error({})"#,
|
||||
JsonValue::String(error.to_string())
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let bundle_identifier = context.config.tauri.bundle.identifier.clone();
|
||||
#[cfg(debug_assertions)]
|
||||
let dist_dir = std::path::PathBuf::from(context.config.build.dist_dir.clone());
|
||||
#[cfg(not(debug_assertions))]
|
||||
let assets = context.assets;
|
||||
let custom_protocol = CustomProtocol {
|
||||
name: "tauri".into(),
|
||||
handler: Box::new(move |path| {
|
||||
let mut path = path
|
||||
.split('?')
|
||||
// ignore query string
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.replace(&format!("tauri://{}", bundle_identifier), "");
|
||||
if path.ends_with('/') {
|
||||
path.pop();
|
||||
}
|
||||
let path = if path.is_empty() {
|
||||
// if the url is `tauri://${appId}`, we should load `index.html`
|
||||
"index.html".to_string()
|
||||
} else {
|
||||
// skip leading `/`
|
||||
path.chars().skip(1).collect::<String>()
|
||||
};
|
||||
|
||||
// In development builds, resolve, read and directly serve assets in the configured dist folder.
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
dist_dir
|
||||
.canonicalize()
|
||||
.or_else(|_| Err(crate::Error::AssetNotFound(path.clone())))
|
||||
.and_then(|pathbuf| {
|
||||
pathbuf
|
||||
.join(path.clone())
|
||||
.canonicalize()
|
||||
.or_else(|_| Err(crate::Error::AssetNotFound(path.clone())))
|
||||
.and_then(|pathbuf| {
|
||||
match std::fs::read(pathbuf) {
|
||||
Ok(asset) => Ok(asset),
|
||||
Err(e) => {
|
||||
eprintln!("Error reading asset from dist: {:?}", e); // TODO log::error!
|
||||
Err(crate::Error::AssetNotFound(path.clone()))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// In release builds, fetch + serve decompressed embedded assets.
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
assets
|
||||
.get(&path)
|
||||
.ok_or(crate::Error::AssetNotFound(path))
|
||||
.map(Cow::into_owned)
|
||||
}
|
||||
}),
|
||||
};
|
||||
(webview_builder, Some(rpc_handler), Some(custom_protocol))
|
||||
} else {
|
||||
(webview.builder.url(webview_url), None, None)
|
||||
};
|
||||
|
||||
Ok((webview_builder, rpc_handler, custom_protocol))
|
||||
}
|
||||
|
||||
fn on_message<A: ApplicationExt + 'static>(
|
||||
application: Arc<Mutex<App<A>>>,
|
||||
webview_manager: WebviewManager<A>,
|
||||
command: String,
|
||||
payload: InvokePayload,
|
||||
) -> crate::Result<()> {
|
||||
let message = InvokeMessage::new(webview_manager.clone(), command.to_string(), payload);
|
||||
if &command == "__initialized" {
|
||||
let payload: PageLoadPayload = serde_json::from_value(message.payload())?;
|
||||
application
|
||||
.lock()
|
||||
.unwrap()
|
||||
.run_on_page_load(&webview_manager, payload.clone());
|
||||
crate::plugin::on_page_load(A::plugin_store(), &webview_manager, payload);
|
||||
} else if let Some(module) = &message.payload.tauri_module {
|
||||
let module = module.to_string();
|
||||
crate::endpoints::handle(
|
||||
&webview_manager,
|
||||
module,
|
||||
message,
|
||||
&application.lock().unwrap().context,
|
||||
);
|
||||
} else if command.starts_with("plugin:") {
|
||||
crate::plugin::extend_api(A::plugin_store(), &webview_manager, command, message);
|
||||
} else {
|
||||
application
|
||||
.lock()
|
||||
.unwrap()
|
||||
.run_invoke_handler(&webview_manager, message);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{generate_context, Context};
|
||||
|
||||
#[test]
|
||||
fn check_get_url() {
|
||||
let context = generate_context!("test/fixture/src-tauri/tauri.conf.json");
|
||||
let context = Context::new(context);
|
||||
let res = super::get_url(&context);
|
||||
#[cfg(custom_protocol)]
|
||||
assert!(res == "tauri://studio.tauri.example");
|
||||
|
||||
#[cfg(dev)]
|
||||
{
|
||||
let config = &context.config;
|
||||
assert_eq!(res, config.build.dev_path);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,303 +0,0 @@
|
||||
pub mod wry;
|
||||
|
||||
use crate::plugin::PluginStore;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
/// A icon definition.
|
||||
pub enum Icon {
|
||||
/// Icon from file path.
|
||||
File(String),
|
||||
/// Icon from raw bytes.
|
||||
Raw(Vec<u8>),
|
||||
}
|
||||
|
||||
/// Messages to dispatch to the application.
|
||||
pub enum Message {
|
||||
// webview messages
|
||||
/// Eval a script on the webview.
|
||||
EvalScript(String),
|
||||
// window messages
|
||||
/// Updates the window resizable flag.
|
||||
SetResizable(bool),
|
||||
/// Updates the window title.
|
||||
SetTitle(String),
|
||||
/// Maximizes the window.
|
||||
Maximize,
|
||||
/// Unmaximizes the window.
|
||||
Unmaximize,
|
||||
/// Minimizes the window.
|
||||
Minimize,
|
||||
/// Unminimizes the window.
|
||||
Unminimize,
|
||||
/// Shows the window.
|
||||
Show,
|
||||
/// Hides the window.
|
||||
Hide,
|
||||
/// Updates the hasDecorations flag.
|
||||
SetDecorations(bool),
|
||||
/// Updates the window alwaysOnTop flag.
|
||||
SetAlwaysOnTop(bool),
|
||||
/// Updates the window width.
|
||||
SetWidth(f64),
|
||||
/// Updates the window height.
|
||||
SetHeight(f64),
|
||||
/// Resizes the window.
|
||||
Resize {
|
||||
/// New width.
|
||||
width: f64,
|
||||
/// New height.
|
||||
height: f64,
|
||||
},
|
||||
/// Updates the window min size.
|
||||
SetMinSize {
|
||||
/// New value for the window min width.
|
||||
min_width: f64,
|
||||
/// New value for the window min height.
|
||||
min_height: f64,
|
||||
},
|
||||
/// Updates the window max size.
|
||||
SetMaxSize {
|
||||
/// New value for the window max width.
|
||||
max_width: f64,
|
||||
/// New value for the window max height.
|
||||
max_height: f64,
|
||||
},
|
||||
/// Updates the X position.
|
||||
SetX(f64),
|
||||
/// Updates the Y position.
|
||||
SetY(f64),
|
||||
/// Updates the window position.
|
||||
SetPosition {
|
||||
/// New value for the window X coordinate.
|
||||
x: f64,
|
||||
/// New value for the window Y coordinate.
|
||||
y: f64,
|
||||
},
|
||||
/// Updates the window fullscreen state.
|
||||
SetFullscreen(bool),
|
||||
/// Updates the window icon.
|
||||
SetIcon(Icon),
|
||||
}
|
||||
|
||||
pub struct WindowConfig(pub crate::api::config::WindowConfig);
|
||||
|
||||
pub trait WebviewBuilderExtPrivate: Sized {
|
||||
/// Sets the webview url.
|
||||
fn url(self, url: String) -> Self;
|
||||
}
|
||||
|
||||
/// The webview builder.
|
||||
pub trait WebviewBuilderExt: Sized {
|
||||
/// The webview object that this builder creates.
|
||||
type Webview;
|
||||
|
||||
/// Initializes a new webview builder.
|
||||
fn new() -> Self;
|
||||
|
||||
/// Sets the init script.
|
||||
fn initialization_script(self, init: &str) -> Self;
|
||||
|
||||
/// The horizontal position of the window's top left corner.
|
||||
fn x(self, x: f64) -> Self;
|
||||
|
||||
/// The vertical position of the window's top left corner.
|
||||
fn y(self, y: f64) -> Self;
|
||||
|
||||
/// Window width.
|
||||
fn width(self, width: f64) -> Self;
|
||||
|
||||
/// Window height.
|
||||
fn height(self, height: f64) -> Self;
|
||||
|
||||
/// Window min width.
|
||||
fn min_width(self, min_width: f64) -> Self;
|
||||
|
||||
/// Window min height.
|
||||
fn min_height(self, min_height: f64) -> Self;
|
||||
|
||||
/// Window max width.
|
||||
fn max_width(self, max_width: f64) -> Self;
|
||||
|
||||
/// Window max height.
|
||||
fn max_height(self, max_height: f64) -> Self;
|
||||
|
||||
/// Whether the window is resizable or not.
|
||||
fn resizable(self, resizable: bool) -> Self;
|
||||
|
||||
/// The title of the window in the title bar.
|
||||
fn title<S: Into<String>>(self, title: S) -> Self;
|
||||
|
||||
/// Whether to start the window in fullscreen or not.
|
||||
fn fullscreen(self, fullscreen: bool) -> Self;
|
||||
|
||||
/// Whether the window should be maximized upon creation.
|
||||
fn maximized(self, maximized: bool) -> Self;
|
||||
|
||||
/// Whether the window should be immediately visible upon creation.
|
||||
fn visible(self, visible: bool) -> Self;
|
||||
|
||||
/// Whether the the window should be transparent. If this is true, writing colors
|
||||
/// with alpha values different than `1.0` will produce a transparent window.
|
||||
fn transparent(self, transparent: bool) -> Self;
|
||||
|
||||
/// Whether the window should have borders and bars.
|
||||
fn decorations(self, decorations: bool) -> Self;
|
||||
|
||||
/// Whether the window should always be on top of other windows.
|
||||
fn always_on_top(self, always_on_top: bool) -> Self;
|
||||
|
||||
/// Sets the window icon.
|
||||
fn icon(self, icon: Icon) -> crate::Result<Self>;
|
||||
|
||||
/// Whether the icon was set or not.
|
||||
fn has_icon(&self) -> bool;
|
||||
|
||||
/// User data path for the webview. Actually only supported on Windows.
|
||||
fn user_data_path(self, user_data_path: Option<PathBuf>) -> Self;
|
||||
|
||||
/// Builds the webview instance.
|
||||
fn finish(self) -> crate::Result<Self::Webview>;
|
||||
}
|
||||
|
||||
/// Rpc request.
|
||||
pub struct RpcRequest {
|
||||
/// RPC command.
|
||||
pub command: String,
|
||||
/// Params.
|
||||
pub params: Option<JsonValue>,
|
||||
}
|
||||
|
||||
/// Rpc handler.
|
||||
pub type WebviewRpcHandler<D> = Box<dyn Fn(D, RpcRequest) + Send>;
|
||||
|
||||
/// Uses a custom handler to resolve file requests
|
||||
pub struct CustomProtocol {
|
||||
/// Name of the protocol
|
||||
pub name: String,
|
||||
/// Handler for protocol
|
||||
pub handler: Box<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send>,
|
||||
}
|
||||
|
||||
/// The file drop event payload.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FileDropEvent {
|
||||
/// The file(s) have been dragged onto the window, but have not been dropped yet.
|
||||
Hovered(Vec<PathBuf>),
|
||||
/// The file(s) have been dropped onto the window.
|
||||
Dropped(Vec<PathBuf>),
|
||||
/// The file drop was aborted.
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// File drop handler callback
|
||||
/// Return `true` in the callback to block the OS' default behavior of handling a file drop..
|
||||
pub type FileDropHandler = Box<dyn Fn(FileDropEvent) -> bool + Send>;
|
||||
|
||||
/// Webview dispatcher. A thread-safe handle to the webview API.
|
||||
pub trait ApplicationDispatcherExt: Clone + Send + Sized {
|
||||
/// The webview builder type.
|
||||
type WebviewBuilder: WebviewBuilderExt + WebviewBuilderExtPrivate + From<WindowConfig> + Send;
|
||||
/// Creates a webview.
|
||||
fn create_webview(
|
||||
&self,
|
||||
webview_builder: Self::WebviewBuilder,
|
||||
rpc_handler: Option<WebviewRpcHandler<Self>>,
|
||||
custom_protocol: Option<CustomProtocol>,
|
||||
file_drop_handler: Option<FileDropHandler>,
|
||||
) -> crate::Result<Self>;
|
||||
|
||||
/// Updates the window resizable flag.
|
||||
fn set_resizable(&self, resizable: bool) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window title.
|
||||
fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()>;
|
||||
|
||||
/// Maximizes the window.
|
||||
fn maximize(&self) -> crate::Result<()>;
|
||||
|
||||
/// Unmaximizes the window.
|
||||
fn unmaximize(&self) -> crate::Result<()>;
|
||||
|
||||
/// Minimizes the window.
|
||||
fn minimize(&self) -> crate::Result<()>;
|
||||
|
||||
/// Unminimizes the window.
|
||||
fn unminimize(&self) -> crate::Result<()>;
|
||||
|
||||
/// Shows the window.
|
||||
fn show(&self) -> crate::Result<()>;
|
||||
|
||||
/// Hides the window.
|
||||
fn hide(&self) -> crate::Result<()>;
|
||||
|
||||
/// Closes the window.
|
||||
fn close(&self) -> crate::Result<()>;
|
||||
|
||||
/// Updates the hasDecorations flag.
|
||||
fn set_decorations(&self, decorations: bool) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window alwaysOnTop flag.
|
||||
fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window width.
|
||||
fn set_width(&self, width: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window height.
|
||||
fn set_height(&self, height: f64) -> crate::Result<()>;
|
||||
|
||||
/// Resizes the window.
|
||||
fn resize(&self, width: f64, height: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window min size.
|
||||
fn set_min_size(&self, min_width: f64, min_height: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window max size.
|
||||
fn set_max_size(&self, max_width: f64, max_height: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the X position.
|
||||
fn set_x(&self, x: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the Y position.
|
||||
fn set_y(&self, y: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window position.
|
||||
fn set_position(&self, x: f64, y: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window fullscreen state.
|
||||
fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window icon.
|
||||
fn set_icon(&self, icon: Icon) -> crate::Result<()>;
|
||||
|
||||
/// Evals a script on the webview.
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
/// The application interface.
|
||||
/// Manages windows and webviews.
|
||||
pub trait ApplicationExt: Sized {
|
||||
/// The webview builder.
|
||||
type WebviewBuilder: WebviewBuilderExt + WebviewBuilderExtPrivate + From<WindowConfig> + Send;
|
||||
/// The message dispatcher.
|
||||
type Dispatcher: ApplicationDispatcherExt<WebviewBuilder = Self::WebviewBuilder>;
|
||||
|
||||
/// Returns the static plugin collection.
|
||||
fn plugin_store() -> &'static PluginStore<Self>;
|
||||
|
||||
/// Creates a new application.
|
||||
fn new() -> crate::Result<Self>;
|
||||
|
||||
/// Creates a new webview.
|
||||
fn create_webview(
|
||||
&mut self,
|
||||
webview_builder: Self::WebviewBuilder,
|
||||
rpc_handler: Option<WebviewRpcHandler<Self::Dispatcher>>,
|
||||
custom_protocol: Option<CustomProtocol>,
|
||||
file_drop_handler: Option<FileDropHandler>,
|
||||
) -> crate::Result<Self::Dispatcher>;
|
||||
|
||||
/// Run the application.
|
||||
fn run(self);
|
||||
}
|
@ -1,347 +0,0 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
future::Future,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{
|
||||
event::{EventId, EventPayload},
|
||||
App, ApplicationDispatcherExt, ApplicationExt, Icon, Webview, WebviewBuilderExt,
|
||||
WebviewInitializer,
|
||||
};
|
||||
use crate::{api::config::WindowUrl, flavors::Wry};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// The webview dispatcher.
|
||||
#[derive(Clone)]
|
||||
pub struct WebviewDispatcher<A: Clone> {
|
||||
dispatcher: A,
|
||||
window_label: String,
|
||||
}
|
||||
|
||||
impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
|
||||
pub(crate) fn new(dispatcher: A, window_label: String) -> Self {
|
||||
Self {
|
||||
dispatcher,
|
||||
window_label,
|
||||
}
|
||||
}
|
||||
|
||||
/// The label of the window tied to this dispatcher.
|
||||
pub fn window_label(&self) -> &str {
|
||||
&self.window_label
|
||||
}
|
||||
|
||||
/// Listen to a webview event.
|
||||
pub fn listen<F: Fn(EventPayload) + Send + 'static>(
|
||||
&self,
|
||||
event: impl AsRef<str>,
|
||||
handler: F,
|
||||
) -> EventId {
|
||||
super::event::listen(event, Some(self.window_label.to_string()), handler)
|
||||
}
|
||||
|
||||
/// Listen to a webview event and unlisten after the first event.
|
||||
pub fn once<F: Fn(EventPayload) + Send + 'static>(&self, event: impl AsRef<str>, handler: F) {
|
||||
super::event::once(event, Some(self.window_label.to_string()), handler)
|
||||
}
|
||||
|
||||
/// Unregister the event listener with the given id.
|
||||
pub fn unlisten(&self, event_id: EventId) {
|
||||
super::event::unlisten(event_id)
|
||||
}
|
||||
|
||||
/// Emits an event to the webview.
|
||||
pub fn emit<S: Serialize>(
|
||||
&self,
|
||||
event: impl AsRef<str>,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
super::event::emit(&self, event, payload)
|
||||
}
|
||||
|
||||
/// Emits an event from the webview.
|
||||
pub(crate) fn on_event(&self, event: String, data: Option<String>) {
|
||||
super::event::on_event(event, Some(&self.window_label), data)
|
||||
}
|
||||
|
||||
/// Evaluates a JS script.
|
||||
pub fn eval(&self, js: &str) -> crate::Result<()> {
|
||||
self.dispatcher.eval_script(js)
|
||||
}
|
||||
|
||||
/// Updates the window resizable flag.
|
||||
pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
|
||||
self.dispatcher.set_resizable(resizable)
|
||||
}
|
||||
|
||||
/// Updates the window title.
|
||||
pub fn set_title(&self, title: &str) -> crate::Result<()> {
|
||||
self.dispatcher.set_title(title.to_string())
|
||||
}
|
||||
|
||||
/// Maximizes the window.
|
||||
pub fn maximize(&self) -> crate::Result<()> {
|
||||
self.dispatcher.maximize()
|
||||
}
|
||||
|
||||
/// Unmaximizes the window.
|
||||
pub fn unmaximize(&self) -> crate::Result<()> {
|
||||
self.dispatcher.unmaximize()
|
||||
}
|
||||
|
||||
/// Minimizes the window.
|
||||
pub fn minimize(&self) -> crate::Result<()> {
|
||||
self.dispatcher.minimize()
|
||||
}
|
||||
|
||||
/// Unminimizes the window.
|
||||
pub fn unminimize(&self) -> crate::Result<()> {
|
||||
self.dispatcher.unminimize()
|
||||
}
|
||||
|
||||
/// Sets the window visibility to true.
|
||||
pub fn show(&self) -> crate::Result<()> {
|
||||
self.dispatcher.show()
|
||||
}
|
||||
|
||||
/// Sets the window visibility to false.
|
||||
pub fn hide(&self) -> crate::Result<()> {
|
||||
self.dispatcher.hide()
|
||||
}
|
||||
|
||||
/// Closes the window.
|
||||
pub fn close(&self) -> crate::Result<()> {
|
||||
self.dispatcher.close()
|
||||
}
|
||||
|
||||
/// Whether the window should have borders and bars.
|
||||
pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
|
||||
self.dispatcher.set_decorations(decorations)
|
||||
}
|
||||
|
||||
/// Whether the window should always be on top of other windows.
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
|
||||
self.dispatcher.set_always_on_top(always_on_top)
|
||||
}
|
||||
|
||||
/// Sets the window width.
|
||||
pub fn set_width(&self, width: impl Into<f64>) -> crate::Result<()> {
|
||||
self.dispatcher.set_width(width.into())
|
||||
}
|
||||
|
||||
/// Sets the window height.
|
||||
pub fn set_height(&self, height: impl Into<f64>) -> crate::Result<()> {
|
||||
self.dispatcher.set_height(height.into())
|
||||
}
|
||||
|
||||
/// Resizes the window.
|
||||
pub fn resize(&self, width: impl Into<f64>, height: impl Into<f64>) -> crate::Result<()> {
|
||||
self.dispatcher.resize(width.into(), height.into())
|
||||
}
|
||||
|
||||
/// Sets the window min size.
|
||||
pub fn set_min_size(
|
||||
&self,
|
||||
min_width: impl Into<f64>,
|
||||
min_height: impl Into<f64>,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
.dispatcher
|
||||
.set_min_size(min_width.into(), min_height.into())
|
||||
}
|
||||
|
||||
/// Sets the window max size.
|
||||
pub fn set_max_size(
|
||||
&self,
|
||||
max_width: impl Into<f64>,
|
||||
max_height: impl Into<f64>,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
.dispatcher
|
||||
.set_max_size(max_width.into(), max_height.into())
|
||||
}
|
||||
|
||||
/// Sets the window x position.
|
||||
pub fn set_x(&self, x: impl Into<f64>) -> crate::Result<()> {
|
||||
self.dispatcher.set_x(x.into())
|
||||
}
|
||||
|
||||
/// Sets the window y position.
|
||||
pub fn set_y(&self, y: impl Into<f64>) -> crate::Result<()> {
|
||||
self.dispatcher.set_y(y.into())
|
||||
}
|
||||
|
||||
/// Sets the window position.
|
||||
pub fn set_position(&self, x: impl Into<f64>, y: impl Into<f64>) -> crate::Result<()> {
|
||||
self.dispatcher.set_position(x.into(), y.into())
|
||||
}
|
||||
|
||||
/// Sets the window fullscreen state.
|
||||
pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
|
||||
self.dispatcher.set_fullscreen(fullscreen)
|
||||
}
|
||||
|
||||
/// Sets the window icon.
|
||||
pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
|
||||
self.dispatcher.set_icon(icon)
|
||||
}
|
||||
}
|
||||
|
||||
/// The webview manager.
|
||||
pub struct WebviewManager<A = Wry>
|
||||
where
|
||||
A: ApplicationExt,
|
||||
{
|
||||
application: Arc<Mutex<App<A>>>,
|
||||
dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
|
||||
current_webview_window_label: String,
|
||||
}
|
||||
|
||||
impl<A: ApplicationExt> Clone for WebviewManager<A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
application: self.application.clone(),
|
||||
dispatchers: self.dispatchers.clone(),
|
||||
current_webview_window_label: self.current_webview_window_label.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ApplicationExt + 'static> WebviewManager<A> {
|
||||
pub(crate) fn new(
|
||||
application: Arc<Mutex<App<A>>>,
|
||||
dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
|
||||
label: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
application,
|
||||
dispatchers,
|
||||
current_webview_window_label: label,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns an asynchronous task
|
||||
pub fn spawn<F>(task: F)
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
crate::async_runtime::spawn(task)
|
||||
}
|
||||
|
||||
/// Returns the label of the window associated with the current context.
|
||||
pub fn current_window_label(&self) -> &str {
|
||||
&self.current_webview_window_label
|
||||
}
|
||||
|
||||
/// Gets the webview associated with the current context.
|
||||
pub fn current_webview(&self) -> crate::Result<WebviewDispatcher<A::Dispatcher>> {
|
||||
self.get_webview(&self.current_webview_window_label)
|
||||
}
|
||||
|
||||
/// Gets the webview associated with the given window label.
|
||||
pub fn get_webview(&self, window_label: &str) -> crate::Result<WebviewDispatcher<A::Dispatcher>> {
|
||||
self
|
||||
.dispatchers
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(window_label)
|
||||
.ok_or(crate::Error::WebviewNotFound)
|
||||
.map(|d| d.clone())
|
||||
}
|
||||
|
||||
/// Creates a new webview.
|
||||
pub async fn create_webview<F: FnOnce(A::WebviewBuilder) -> crate::Result<A::WebviewBuilder>>(
|
||||
&self,
|
||||
label: String,
|
||||
url: WindowUrl,
|
||||
f: F,
|
||||
) -> crate::Result<WebviewDispatcher<A::Dispatcher>> {
|
||||
let builder = f(A::WebviewBuilder::new())?;
|
||||
let webview = Webview {
|
||||
url,
|
||||
label: label.to_string(),
|
||||
builder,
|
||||
};
|
||||
self
|
||||
.application
|
||||
.lock()
|
||||
.unwrap()
|
||||
.window_labels
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(label.to_string());
|
||||
let (webview_builder, rpc_handler, custom_protocol, file_drop_handler) =
|
||||
self.application.init_webview(webview)?;
|
||||
|
||||
let window_dispatcher = self.current_webview()?.dispatcher.create_webview(
|
||||
webview_builder,
|
||||
rpc_handler,
|
||||
custom_protocol,
|
||||
file_drop_handler,
|
||||
)?;
|
||||
let webview_manager = Self::new(
|
||||
self.application.clone(),
|
||||
self.dispatchers.clone(),
|
||||
label.to_string(),
|
||||
);
|
||||
self
|
||||
.application
|
||||
.on_webview_created(label.to_string(), window_dispatcher.clone());
|
||||
crate::plugin::created(A::plugin_store(), &webview_manager);
|
||||
Ok(WebviewDispatcher::new(window_dispatcher, label))
|
||||
}
|
||||
|
||||
/// Listen to a global event.
|
||||
/// An event from any webview will trigger the handler.
|
||||
pub fn listen<F: Fn(EventPayload) + Send + 'static>(
|
||||
&self,
|
||||
event: impl AsRef<str>,
|
||||
handler: F,
|
||||
) -> EventId {
|
||||
super::event::listen(event, None, handler)
|
||||
}
|
||||
|
||||
/// Listen to a global event and unlisten after the first event.
|
||||
pub fn once<F: Fn(EventPayload) + Send + 'static>(&self, event: impl AsRef<str>, handler: F) {
|
||||
super::event::once(event, None, handler)
|
||||
}
|
||||
|
||||
/// Unregister the global event listener with the given id.
|
||||
pub fn unlisten(&self, event_id: EventId) {
|
||||
super::event::unlisten(event_id)
|
||||
}
|
||||
|
||||
/// Emits an event to all webviews.
|
||||
pub fn emit<S: Serialize + Clone>(
|
||||
&self,
|
||||
event: impl AsRef<str>,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
for dispatcher in self.dispatchers.lock().unwrap().values() {
|
||||
super::event::emit(&dispatcher, event.as_ref(), payload.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn emit_except<S: Serialize + Clone>(
|
||||
&self,
|
||||
except_label: String,
|
||||
event: impl AsRef<str>,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
for dispatcher in self.dispatchers.lock().unwrap().values() {
|
||||
if dispatcher.window_label != except_label {
|
||||
super::event::emit(&dispatcher, event.as_ref(), payload.clone())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emits a global event from the webview.
|
||||
pub(crate) fn on_event(&self, event: String, data: Option<String>) {
|
||||
super::event::on_event(event, None, data)
|
||||
}
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
use crate::{api::config::Config, hooks::InvokeMessage, runtime::Params};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
mod cli;
|
||||
mod dialog;
|
||||
mod event;
|
||||
@ -10,11 +14,6 @@ mod notification;
|
||||
mod shell;
|
||||
mod window;
|
||||
|
||||
use crate::{app::Context, ApplicationExt, InvokeMessage};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
/// The response for a JS `invoke` call.
|
||||
pub struct InvokeResponse {
|
||||
json: crate::Result<JsonValue>,
|
||||
@ -44,31 +43,27 @@ enum Module {
|
||||
}
|
||||
|
||||
impl Module {
|
||||
fn run<A: ApplicationExt + 'static>(
|
||||
self,
|
||||
webview_manager: crate::WebviewManager<A>,
|
||||
message: InvokeMessage<A>,
|
||||
context: &Context,
|
||||
) {
|
||||
fn run<M: Params>(self, message: InvokeMessage<M>, config: &Config) {
|
||||
let window = message.window();
|
||||
match self {
|
||||
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 {
|
||||
cmd
|
||||
.run(&webview_manager)
|
||||
.run(window)
|
||||
.await
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
}),
|
||||
Self::Shell(cmd) => message.respond_async(async move {
|
||||
cmd
|
||||
.run(webview_manager)
|
||||
.run(window)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
}),
|
||||
Self::Event(cmd) => message.respond_async(async move {
|
||||
cmd
|
||||
.run(&webview_manager)
|
||||
.run(window)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
}),
|
||||
@ -77,7 +72,7 @@ impl Module {
|
||||
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) = context.config.tauri.cli.clone() {
|
||||
if let Some(cli_config) = config.tauri.cli.clone() {
|
||||
message.respond_async(async move {
|
||||
cmd
|
||||
.run(&cli_config)
|
||||
@ -87,7 +82,7 @@ impl Module {
|
||||
}
|
||||
}
|
||||
Self::Notification(cmd) => {
|
||||
let identifier = context.config.tauri.bundle.identifier.clone();
|
||||
let identifier = config.tauri.bundle.identifier.clone();
|
||||
message.respond_async(async move {
|
||||
cmd
|
||||
.run(identifier)
|
||||
@ -104,7 +99,7 @@ impl Module {
|
||||
}),
|
||||
Self::GlobalShortcut(cmd) => message.respond_async(async move {
|
||||
cmd
|
||||
.run(&webview_manager)
|
||||
.run(window)
|
||||
.and_then(|r| r.json)
|
||||
.map_err(|e| e.to_string())
|
||||
}),
|
||||
@ -112,18 +107,13 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle<A: ApplicationExt + 'static>(
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
module: String,
|
||||
message: InvokeMessage<A>,
|
||||
context: &Context,
|
||||
) {
|
||||
pub(crate) fn handle<M: Params>(module: String, message: InvokeMessage<M>, config: &Config) {
|
||||
let mut payload = message.payload();
|
||||
if let JsonValue::Object(ref mut obj) = payload {
|
||||
obj.insert("module".to_string(), JsonValue::String(module));
|
||||
}
|
||||
match serde_json::from_value::<Module>(payload) {
|
||||
Ok(module) => module.run(webview_manager.clone(), message, context),
|
||||
Ok(module) => module.run(message, config),
|
||||
Err(e) => message.reject(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
use super::InvokeResponse;
|
||||
use crate::{
|
||||
endpoints::InvokeResponse,
|
||||
runtime::{sealed::ManagerPrivate, window::Window, Manager, Params},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
/// The API descriptor.
|
||||
@ -21,22 +24,15 @@ pub enum Cmd {
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
pub fn run<A: crate::ApplicationExt + 'static>(
|
||||
self,
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
) -> crate::Result<InvokeResponse> {
|
||||
pub fn run<M: Params>(self, window: Window<M>) -> crate::Result<InvokeResponse> {
|
||||
match self {
|
||||
Self::Listen { event, handler } => {
|
||||
let event_id = rand::random();
|
||||
webview_manager
|
||||
.current_webview()?
|
||||
.eval(&listen_js(event, event_id, handler))?;
|
||||
window.eval(&listen_js(&window, event, event_id, handler))?;
|
||||
Ok(event_id.into())
|
||||
}
|
||||
Self::Unlisten { event_id } => {
|
||||
webview_manager
|
||||
.current_webview()?
|
||||
.eval(&unlisten_js(event_id))?;
|
||||
window.eval(&unlisten_js(&window, event_id))?;
|
||||
Ok(().into())
|
||||
}
|
||||
Self::Emit {
|
||||
@ -44,17 +40,22 @@ impl Cmd {
|
||||
window_label,
|
||||
payload,
|
||||
} => {
|
||||
if let Some(label) = window_label {
|
||||
let dispatcher = webview_manager.get_webview(&label)?;
|
||||
// dispatch the event to Rust listeners
|
||||
dispatcher.on_event(event.to_string(), payload.clone());
|
||||
// dispatch the event to JS listeners
|
||||
dispatcher.emit(event, payload)?;
|
||||
let e: M::Event = event
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("todo: invalid event str"));
|
||||
|
||||
let window_label: Option<M::Label> = window_label.map(|l| {
|
||||
l.parse()
|
||||
.unwrap_or_else(|_| panic!("todo: invalid window label"))
|
||||
});
|
||||
|
||||
// dispatch the event to Rust listeners
|
||||
window.trigger(e.clone(), payload.clone());
|
||||
|
||||
if let Some(target) = window_label {
|
||||
window.emit_to(&target, e, payload)?;
|
||||
} else {
|
||||
// dispatch the event to Rust listeners
|
||||
webview_manager.on_event(event.to_string(), payload.clone());
|
||||
// dispatch the event to JS listeners
|
||||
webview_manager.emit(event, payload)?;
|
||||
window.emit_all(e, payload)?;
|
||||
}
|
||||
Ok(().into())
|
||||
}
|
||||
@ -62,7 +63,7 @@ impl Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unlisten_js(event_id: u64) -> String {
|
||||
pub fn unlisten_js<M: Params>(window: &Window<M>, event_id: u64) -> String {
|
||||
format!(
|
||||
"
|
||||
for (var event in (window['{listeners}'] || {{}})) {{
|
||||
@ -72,12 +73,17 @@ pub fn unlisten_js(event_id: u64) -> String {
|
||||
}}
|
||||
}}
|
||||
",
|
||||
listeners = crate::app::event::event_listeners_object_name(),
|
||||
listeners = window.manager().event_listeners_object_name(),
|
||||
event_id = event_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn listen_js(event: String, event_id: u64, handler: String) -> String {
|
||||
pub fn listen_js<M: Params>(
|
||||
window: &Window<M>,
|
||||
event: String,
|
||||
event_id: u64,
|
||||
handler: String,
|
||||
) -> String {
|
||||
format!(
|
||||
"if (window['{listeners}'] === void 0) {{
|
||||
window['{listeners}'] = {{}}
|
||||
@ -95,24 +101,11 @@ pub fn listen_js(event: String, event_id: u64, handler: String) -> String {
|
||||
window['{emit}'](e.eventData, e.salt, true)
|
||||
}}
|
||||
",
|
||||
listeners = crate::app::event::event_listeners_object_name(),
|
||||
queue = crate::app::event::event_queue_object_name(),
|
||||
emit = crate::app::event::emit_function_name(),
|
||||
listeners = window.manager().event_listeners_object_name(),
|
||||
queue = window.manager().event_queue_object_name(),
|
||||
emit = window.manager().event_emit_function_name(),
|
||||
event = event,
|
||||
event_id = event_id,
|
||||
handler = handler
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use proptest::prelude::*;
|
||||
|
||||
// check the listen_js for various usecases.
|
||||
proptest! {
|
||||
#[test]
|
||||
fn check_listen_js(event in "", id in proptest::bits::u64::ANY, handler in "") {
|
||||
super::listen_js(event, id, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::InvokeResponse;
|
||||
use crate::{api::path::BaseDirectory, ApplicationDispatcherExt};
|
||||
use crate::api::path::BaseDirectory;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri_api::{dir, file, path::resolve_path};
|
||||
|
@ -1,12 +1,12 @@
|
||||
use super::InvokeResponse;
|
||||
#[cfg(global_shortcut_all)]
|
||||
use crate::api::shortcuts::ShortcutManager;
|
||||
use crate::app::WebviewDispatcher;
|
||||
use crate::runtime::{window::Window, Dispatch, Params};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(global_shortcut_all)]
|
||||
use crate::api::shortcuts::ShortcutManager;
|
||||
|
||||
#[cfg(global_shortcut_all)]
|
||||
type ShortcutManagerHandle = Arc<Mutex<ShortcutManager>>;
|
||||
|
||||
@ -36,8 +36,8 @@ pub enum Cmd {
|
||||
}
|
||||
|
||||
#[cfg(global_shortcut_all)]
|
||||
fn register_shortcut<A: crate::ApplicationDispatcherExt + 'static>(
|
||||
dispatcher: WebviewDispatcher<A>,
|
||||
fn register_shortcut<D: Dispatch>(
|
||||
dispatcher: D,
|
||||
manager: &mut ShortcutManager,
|
||||
shortcut: String,
|
||||
handler: String,
|
||||
@ -47,33 +47,36 @@ fn register_shortcut<A: crate::ApplicationDispatcherExt + 'static>(
|
||||
handler.to_string(),
|
||||
serde_json::Value::String(shortcut.clone()),
|
||||
);
|
||||
let _ = dispatcher.eval(callback_string.as_str());
|
||||
let _ = dispatcher.eval_script(callback_string.as_str());
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(global_shortcut_all))]
|
||||
impl Cmd {
|
||||
pub fn run<A: crate::ApplicationExt + 'static>(
|
||||
self,
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
) -> crate::Result<InvokeResponse> {
|
||||
#[cfg(not(global_shortcut_all))]
|
||||
return Err(crate::Error::ApiNotAllowlisted(
|
||||
pub fn run<M: Params>(self, _window: Window<M>) -> crate::Result<InvokeResponse> {
|
||||
Err(crate::Error::ApiNotAllowlisted(
|
||||
"globalShortcut > all".to_string(),
|
||||
));
|
||||
#[cfg(global_shortcut_all)]
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(global_shortcut_all)]
|
||||
impl Cmd {
|
||||
pub fn run<M: Params>(self, window: Window<M>) -> crate::Result<InvokeResponse> {
|
||||
match self {
|
||||
Self::Register { shortcut, handler } => {
|
||||
let dispatcher = webview_manager.current_webview()?;
|
||||
let dispatcher = window.dispatcher();
|
||||
let mut manager = manager_handle().lock().unwrap();
|
||||
register_shortcut(dispatcher, &mut manager, shortcut, handler)?;
|
||||
Ok(().into())
|
||||
}
|
||||
Self::RegisterAll { shortcuts, handler } => {
|
||||
let dispatcher = webview_manager.current_webview()?;
|
||||
let dispatcher = window.dispatcher();
|
||||
let mut manager = manager_handle().lock().unwrap();
|
||||
for shortcut in shortcuts {
|
||||
register_shortcut(dispatcher.clone(), &mut manager, shortcut, handler.clone())?;
|
||||
let dispatch = dispatcher.clone();
|
||||
register_shortcut(dispatch, &mut manager, shortcut, handler.clone())?;
|
||||
}
|
||||
Ok(().into())
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
use super::InvokeResponse;
|
||||
use crate::{
|
||||
api::{
|
||||
command::{Command, CommandChild, CommandEvent},
|
||||
rpc::format_callback,
|
||||
},
|
||||
app::ApplicationExt,
|
||||
endpoints::InvokeResponse,
|
||||
runtime::{window::Window, Params},
|
||||
};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
@ -57,10 +55,7 @@ pub enum Cmd {
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
pub fn run<A: ApplicationExt + 'static>(
|
||||
self,
|
||||
webview_manager: crate::WebviewManager<A>,
|
||||
) -> crate::Result<InvokeResponse> {
|
||||
pub fn run<M: Params>(self, window: Window<M>) -> crate::Result<InvokeResponse> {
|
||||
match self {
|
||||
Self::Execute {
|
||||
program,
|
||||
@ -87,9 +82,7 @@ impl Cmd {
|
||||
command_childs().lock().unwrap().remove(&pid);
|
||||
}
|
||||
let js = format_callback(on_event_fn.clone(), serde_json::to_value(event).unwrap());
|
||||
if let Ok(dispatcher) = webview_manager.current_webview() {
|
||||
let _ = dispatcher.eval(js.as_str());
|
||||
}
|
||||
let _ = window.eval(js.as_str());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,11 +1,19 @@
|
||||
use super::InvokeResponse;
|
||||
use crate::app::{ApplicationExt, Icon};
|
||||
use crate::{
|
||||
endpoints::InvokeResponse,
|
||||
runtime::{
|
||||
webview::{Icon, WindowConfig},
|
||||
window::{PendingWindow, Window},
|
||||
Manager, Params,
|
||||
},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum IconDto {
|
||||
File(String),
|
||||
File(PathBuf),
|
||||
Raw(Vec<u8>),
|
||||
}
|
||||
|
||||
@ -90,14 +98,10 @@ struct WindowCreatedEvent {
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
pub async fn run<A: ApplicationExt + 'static>(
|
||||
self,
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
) -> crate::Result<InvokeResponse> {
|
||||
pub async fn run<M: Params>(self, mut window: Window<M>) -> crate::Result<InvokeResponse> {
|
||||
if cfg!(not(window_all)) {
|
||||
Err(crate::Error::ApiNotAllowlisted("window > all".to_string()))
|
||||
} else {
|
||||
let current_webview = webview_manager.current_webview()?;
|
||||
match self {
|
||||
Self::CreateWebview { options } => {
|
||||
#[cfg(not(window_create))]
|
||||
@ -106,48 +110,48 @@ impl Cmd {
|
||||
));
|
||||
#[cfg(window_create)]
|
||||
{
|
||||
let label = options.label.to_string();
|
||||
webview_manager
|
||||
.create_webview(label.to_string(), options.url.clone(), |_| {
|
||||
Ok(crate::app::webview::WindowConfig(options).into())
|
||||
})
|
||||
.await?;
|
||||
webview_manager.emit_except(
|
||||
label.to_string(),
|
||||
"tauri://window-created",
|
||||
Some(WindowCreatedEvent { label }),
|
||||
let label: M::Label = options
|
||||
.label
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("todo: label parsing"));
|
||||
|
||||
let url = options.url.clone();
|
||||
let pending = PendingWindow::new(WindowConfig(options), label.clone(), url);
|
||||
window.create_window(pending)?.emit_others_internal(
|
||||
"tauri://window-created".to_string(),
|
||||
Some(WindowCreatedEvent {
|
||||
label: label.to_string(),
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Self::SetResizable { resizable } => current_webview.set_resizable(resizable)?,
|
||||
Self::SetTitle { title } => current_webview.set_title(&title)?,
|
||||
Self::Maximize => current_webview.maximize()?,
|
||||
Self::Unmaximize => current_webview.unmaximize()?,
|
||||
Self::Minimize => current_webview.minimize()?,
|
||||
Self::Unminimize => current_webview.unminimize()?,
|
||||
Self::Show => current_webview.show()?,
|
||||
Self::Hide => current_webview.hide()?,
|
||||
Self::Close => current_webview.close()?,
|
||||
Self::SetDecorations { decorations } => current_webview.set_decorations(decorations)?,
|
||||
Self::SetAlwaysOnTop { always_on_top } => {
|
||||
current_webview.set_always_on_top(always_on_top)?
|
||||
}
|
||||
Self::SetWidth { width } => current_webview.set_width(width)?,
|
||||
Self::SetHeight { height } => current_webview.set_height(height)?,
|
||||
Self::Resize { width, height } => current_webview.resize(width, height)?,
|
||||
Self::SetResizable { resizable } => window.set_resizable(resizable)?,
|
||||
Self::SetTitle { title } => window.set_title(&title)?,
|
||||
Self::Maximize => window.maximize()?,
|
||||
Self::Unmaximize => window.unmaximize()?,
|
||||
Self::Minimize => window.minimize()?,
|
||||
Self::Unminimize => window.unminimize()?,
|
||||
Self::Show => window.show()?,
|
||||
Self::Hide => window.hide()?,
|
||||
Self::Close => window.close()?,
|
||||
Self::SetDecorations { decorations } => window.set_decorations(decorations)?,
|
||||
Self::SetAlwaysOnTop { always_on_top } => window.set_always_on_top(always_on_top)?,
|
||||
Self::SetWidth { width } => window.set_width(width)?,
|
||||
Self::SetHeight { height } => window.set_height(height)?,
|
||||
Self::Resize { width, height } => window.resize(width, height)?,
|
||||
Self::SetMinSize {
|
||||
min_width,
|
||||
min_height,
|
||||
} => current_webview.set_min_size(min_width, min_height)?,
|
||||
} => window.set_min_size(min_width, min_height)?,
|
||||
Self::SetMaxSize {
|
||||
max_width,
|
||||
max_height,
|
||||
} => current_webview.set_max_size(max_width, max_height)?,
|
||||
Self::SetX { x } => current_webview.set_x(x)?,
|
||||
Self::SetY { y } => current_webview.set_y(y)?,
|
||||
Self::SetPosition { x, y } => current_webview.set_position(x, y)?,
|
||||
Self::SetFullscreen { fullscreen } => current_webview.set_fullscreen(fullscreen)?,
|
||||
Self::SetIcon { icon } => current_webview.set_icon(icon.into())?,
|
||||
} => window.set_max_size(max_width, max_height)?,
|
||||
Self::SetX { x } => window.set_x(x)?,
|
||||
Self::SetY { y } => window.set_y(y)?,
|
||||
Self::SetPosition { x, y } => window.set_position(x, y)?,
|
||||
Self::SetFullscreen { fullscreen } => window.set_fullscreen(fullscreen)?,
|
||||
Self::SetIcon { icon } => window.set_icon(icon.into())?,
|
||||
}
|
||||
Ok(().into())
|
||||
}
|
||||
|
@ -46,6 +46,9 @@ pub enum Error {
|
||||
/// Invalid args when running a command.
|
||||
#[error("invalid args for command `{0}`: {1}")]
|
||||
InvalidArgs(&'static str, serde_json::Error),
|
||||
/// Encountered an error in the setup hook,
|
||||
#[error("error encountered during setup hood: {0}")]
|
||||
Setup(#[from] Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
|
219
tauri/src/event.rs
Normal file
219
tauri/src/event.rs
Normal file
@ -0,0 +1,219 @@
|
||||
use crate::runtime::tag::Tag;
|
||||
use std::{
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
hash::Hash,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Represents an event handler.
|
||||
#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct EventHandler(Uuid);
|
||||
|
||||
impl fmt::Display for EventHandler {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that was triggered.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Event {
|
||||
id: EventHandler,
|
||||
data: Option<String>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// The [`EventHandler`] that was triggered.
|
||||
pub fn id(&self) -> EventHandler {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// The event payload.
|
||||
pub fn payload(&self) -> Option<&str> {
|
||||
self.data.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stored in [`Listeners`] to be called upon when the event that stored it is triggered.
|
||||
struct Handler<Window: Tag> {
|
||||
window: Option<Window>,
|
||||
callback: Box<dyn Fn(Event) + Send>,
|
||||
}
|
||||
|
||||
/// A collection of handlers. Multiple handlers can represent the same event.
|
||||
type Handlers<Event, Window> = HashMap<Event, HashMap<EventHandler, Handler<Window>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Listeners<Event: Tag, Window: Tag> {
|
||||
inner: Arc<Mutex<Handlers<Event, Window>>>,
|
||||
function_name: Uuid,
|
||||
listeners_object_name: Uuid,
|
||||
queue_object_name: Uuid,
|
||||
}
|
||||
|
||||
impl<E: Tag, L: Tag> Default for Listeners<E, L> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::default()),
|
||||
function_name: Uuid::new_v4(),
|
||||
listeners_object_name: Uuid::new_v4(),
|
||||
queue_object_name: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Tag, L: Tag> Listeners<E, L> {
|
||||
/// Randomly generated function name to represent the JavaScript event function.
|
||||
pub(crate) fn function_name(&self) -> String {
|
||||
self.function_name.to_string()
|
||||
}
|
||||
|
||||
/// Randomly generated listener object name to represent the JavaScript event listener object.
|
||||
pub(crate) fn listeners_object_name(&self) -> String {
|
||||
self.function_name.to_string()
|
||||
}
|
||||
|
||||
/// Randomly generated queue object name to represent the JavaScript event queue object.
|
||||
pub(crate) fn queue_object_name(&self) -> String {
|
||||
self.queue_object_name.to_string()
|
||||
}
|
||||
|
||||
/// Adds an event listener for JS events.
|
||||
pub(crate) fn listen<F: Fn(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: E,
|
||||
window: Option<L>,
|
||||
handler: F,
|
||||
) -> EventHandler {
|
||||
let id = EventHandler(Uuid::new_v4());
|
||||
let handler = Handler {
|
||||
window,
|
||||
callback: Box::new(handler),
|
||||
};
|
||||
self
|
||||
.inner
|
||||
.lock()
|
||||
.expect("poisoned event mutex")
|
||||
.entry(event)
|
||||
.or_default()
|
||||
.insert(id, handler);
|
||||
id
|
||||
}
|
||||
|
||||
/// Listen to a JS event and immediately unlisten.
|
||||
pub(crate) fn once<F: Fn(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: E,
|
||||
window: Option<L>,
|
||||
handler: F,
|
||||
) {
|
||||
let self_ = self.clone();
|
||||
self.listen(event, window, move |e| {
|
||||
self_.unlisten(e.id);
|
||||
handler(e);
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes an event listener.
|
||||
pub(crate) fn unlisten(&self, handler_id: EventHandler) {
|
||||
self
|
||||
.inner
|
||||
.lock()
|
||||
.expect("poisoned event mutex")
|
||||
.values_mut()
|
||||
.for_each(|handler| {
|
||||
handler.remove(&handler_id);
|
||||
})
|
||||
}
|
||||
|
||||
/// Triggers the given global event with its payload.
|
||||
pub(crate) fn trigger(&self, event: E, window: Option<L>, data: Option<String>) {
|
||||
if let Some(handlers) = self.inner.lock().expect("poisoned event mutex").get(&event) {
|
||||
for (&id, handler) in handlers {
|
||||
if window.is_none() || window == handler.window {
|
||||
let data = data.clone();
|
||||
let payload = Event { id, data };
|
||||
(handler.callback)(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
// dummy event handler function
|
||||
fn event_fn(s: Event) {
|
||||
println!("{:?}", s);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig::with_cases(10000))]
|
||||
|
||||
// check to see if listen() is properly passing keys into the LISTENERS map
|
||||
#[test]
|
||||
fn listeners_check_key(e in "[a-z]+") {
|
||||
let listeners: Listeners<String, String> = Default::default();
|
||||
// clone e as the key
|
||||
let key = e.clone();
|
||||
// pass e and an dummy func into listen
|
||||
listeners.listen(e, None, event_fn);
|
||||
|
||||
// lock mutex
|
||||
let l = listeners.inner.lock().unwrap();
|
||||
|
||||
// check if the generated key is in the map
|
||||
assert_eq!(l.contains_key(&key), true);
|
||||
}
|
||||
|
||||
// check to see if listen inputs a handler function properly into the LISTENERS map.
|
||||
#[test]
|
||||
fn listeners_check_fn(e in "[a-z]+") {
|
||||
let listeners: Listeners<String, String> = Default::default();
|
||||
// clone e as the key
|
||||
let key = e.clone();
|
||||
// pass e and an dummy func into listen
|
||||
listeners.listen(e, None, event_fn);
|
||||
|
||||
// lock mutex
|
||||
let mut l = listeners.inner.lock().unwrap();
|
||||
|
||||
// check if l contains key
|
||||
if l.contains_key(&key) {
|
||||
// grab key if it exists
|
||||
let handler = l.get_mut(&key);
|
||||
// check to see if we get back a handler or not
|
||||
match handler {
|
||||
// pass on Some(handler)
|
||||
Some(_) => {},
|
||||
// Fail on None
|
||||
None => panic!("handler is None")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if on_event properly grabs the stored function from listen.
|
||||
#[test]
|
||||
fn check_on_event(e in "[a-z]+", d in "[a-z]+") {
|
||||
let listeners: Listeners<String, String> = Default::default();
|
||||
// clone e as the key
|
||||
let key = e.clone();
|
||||
// call listen with e and the event_fn dummy func
|
||||
listeners.listen(e.clone(), None, event_fn);
|
||||
// call on event with e and d.
|
||||
listeners.trigger(e, None, Some(d));
|
||||
|
||||
// lock the mutex
|
||||
let l = listeners.inner.lock().unwrap();
|
||||
|
||||
// assert that the key is contained in the listeners map
|
||||
assert!(l.contains_key(&key));
|
||||
}
|
||||
}
|
||||
}
|
164
tauri/src/hooks.rs
Normal file
164
tauri/src/hooks.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use crate::{
|
||||
api::rpc::{format_callback, format_callback_result},
|
||||
runtime::{app::App, window::Window, Params},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::future::Future;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// The payload for the [`OnPageLoad`] hook.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PageLoadPayload {
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl PageLoadPayload {
|
||||
/// The page URL.
|
||||
pub fn url(&self) -> &str {
|
||||
&self.url
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload from an invoke call.
|
||||
#[derive(Debug, 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: serde_json::Value,
|
||||
}
|
||||
|
||||
/// An invoke message.
|
||||
pub struct InvokeMessage<M: Params> {
|
||||
window: Window<M>,
|
||||
command: String,
|
||||
|
||||
/// Allow our crate to access the payload without cloning it.
|
||||
pub(crate) payload: InvokePayload,
|
||||
}
|
||||
|
||||
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 {
|
||||
Self {
|
||||
window,
|
||||
command,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
/// The invoke command.
|
||||
pub fn command(&self) -> &str {
|
||||
&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()),
|
||||
};
|
||||
|
||||
let _ = window.eval(&callback_string);
|
||||
}
|
||||
}
|
@ -6,37 +6,41 @@
|
||||
//! Tauri uses (and contributes to) the MIT licensed project that you can find at [webview](https://github.com/webview/webview).
|
||||
#![warn(missing_docs, rust_2018_idioms)]
|
||||
|
||||
/// The Tauri-specific settings for your app e.g. notification permission status.
|
||||
/// The Tauri error enum.
|
||||
pub use error::Error;
|
||||
pub use tauri_api as api;
|
||||
pub(crate) use tauri_api::private::async_runtime;
|
||||
pub use tauri_macros::*;
|
||||
|
||||
/// The Tauri-specific settings for your runtime e.g. notification permission status.
|
||||
pub mod settings;
|
||||
|
||||
/// The webview application entry.
|
||||
mod app;
|
||||
/// The Tauri API endpoints.
|
||||
mod endpoints;
|
||||
mod error;
|
||||
mod event;
|
||||
mod hooks;
|
||||
/// The plugin manager module contains helpers to manage runtime plugins.
|
||||
pub mod plugin;
|
||||
/// The internal runtime between an [`App`] and the webview.
|
||||
pub mod runtime;
|
||||
/// The salt helpers.
|
||||
mod salt;
|
||||
|
||||
/// The Tauri error enum.
|
||||
pub use error::Error;
|
||||
|
||||
/// Tauri result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// A task to run on the main thread.
|
||||
pub type SyncTask = Box<dyn FnOnce() + Send>;
|
||||
|
||||
pub use app::*;
|
||||
pub use tauri_api as api;
|
||||
pub(crate) use tauri_api::private::async_runtime;
|
||||
pub use tauri_macros::*;
|
||||
|
||||
/// The Tauri webview implementations.
|
||||
pub mod flavors {
|
||||
pub use super::app::WryApplication as Wry;
|
||||
}
|
||||
/// types likely to be used by applications
|
||||
pub use {
|
||||
api::config::WindowUrl,
|
||||
hooks::InvokeMessage,
|
||||
runtime::app::AppBuilder,
|
||||
runtime::webview::Attributes,
|
||||
runtime::{Context, Manager, Params},
|
||||
};
|
||||
|
||||
/// Easy helper function to use the Tauri Context you made during build time.
|
||||
#[macro_export]
|
||||
|
@ -1,17 +1,19 @@
|
||||
use crate::{
|
||||
api::config::PluginConfig, ApplicationExt, InvokeMessage, PageLoadPayload, WebviewManager,
|
||||
api::config::PluginConfig,
|
||||
hooks::{InvokeMessage, PageLoadPayload},
|
||||
runtime::{window::Window, Params},
|
||||
};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The plugin interface.
|
||||
pub trait Plugin<A: ApplicationExt + 'static>: Send {
|
||||
pub trait Plugin<M: Params>: Send {
|
||||
/// The plugin name. Used as key on the plugin config object.
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Initialize the plugin.
|
||||
#[allow(unused_variables)]
|
||||
fn initialize(&mut self, config: String) -> crate::Result<()> {
|
||||
fn initialize(&mut self, config: JsonValue) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -26,94 +28,82 @@ pub trait Plugin<A: ApplicationExt + 'static>: Send {
|
||||
|
||||
/// Callback invoked when the webview is created.
|
||||
#[allow(unused_variables)]
|
||||
fn created(&mut self, webview_manager: WebviewManager<A>) {}
|
||||
fn created(&mut self, window: Window<M>) {}
|
||||
|
||||
/// Callback invoked when the webview performs a navigation.
|
||||
#[allow(unused_variables)]
|
||||
fn on_page_load(&mut self, webview_manager: WebviewManager<A>, payload: PageLoadPayload) {}
|
||||
fn on_page_load(&mut self, window: Window<M>, payload: PageLoadPayload) {}
|
||||
|
||||
/// Add invoke_handler API extension commands.
|
||||
#[allow(unused_variables)]
|
||||
fn extend_api(&mut self, webview_manager: WebviewManager<A>, message: InvokeMessage<A>) {}
|
||||
fn extend_api(&mut self, message: InvokeMessage<M>) {}
|
||||
}
|
||||
|
||||
/// Plugin collection type.
|
||||
pub type PluginStore<A> = Arc<Mutex<Vec<Box<dyn Plugin<A> + Send>>>>;
|
||||
|
||||
/// Registers a plugin.
|
||||
pub fn register<A: ApplicationExt + 'static>(
|
||||
store: &PluginStore<A>,
|
||||
plugin: impl Plugin<A> + Send + 'static,
|
||||
) {
|
||||
let mut plugins = store.lock().unwrap();
|
||||
plugins.push(Box::new(plugin));
|
||||
pub struct PluginStore<M: Params> {
|
||||
store: HashMap<&'static str, Box<dyn Plugin<M>>>,
|
||||
}
|
||||
|
||||
pub(crate) fn initialize<A: ApplicationExt + 'static>(
|
||||
store: &PluginStore<A>,
|
||||
plugins_config: PluginConfig,
|
||||
) -> crate::Result<()> {
|
||||
let mut plugins = store.lock().unwrap();
|
||||
for plugin in plugins.iter_mut() {
|
||||
let plugin_config = plugins_config.get(plugin.name());
|
||||
plugin.initialize(plugin_config)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn initialization_script<A: ApplicationExt + 'static>(store: &PluginStore<A>) -> String {
|
||||
let mut plugins = store.lock().unwrap();
|
||||
let mut initialization_script = String::new();
|
||||
for plugin in plugins.iter_mut() {
|
||||
if let Some(plugin_initialization_script) = plugin.initialization_script() {
|
||||
initialization_script.push_str(&format!(
|
||||
"(function () {{ {} }})();",
|
||||
plugin_initialization_script
|
||||
));
|
||||
}
|
||||
}
|
||||
initialization_script
|
||||
}
|
||||
|
||||
pub(crate) fn created<A: ApplicationExt + 'static>(
|
||||
store: &PluginStore<A>,
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
) {
|
||||
let mut plugins = store.lock().unwrap();
|
||||
for plugin in plugins.iter_mut() {
|
||||
plugin.created(webview_manager.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_page_load<A: ApplicationExt + 'static>(
|
||||
store: &PluginStore<A>,
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
payload: PageLoadPayload,
|
||||
) {
|
||||
let mut plugins = store.lock().unwrap();
|
||||
for plugin in plugins.iter_mut() {
|
||||
plugin.on_page_load(webview_manager.clone(), payload.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extend_api<A: ApplicationExt + 'static>(
|
||||
store: &PluginStore<A>,
|
||||
webview_manager: &crate::WebviewManager<A>,
|
||||
command: String,
|
||||
message: InvokeMessage<A>,
|
||||
) {
|
||||
let mut plugins = store.lock().unwrap();
|
||||
let target_plugin_name = command
|
||||
.replace("plugin:", "")
|
||||
.split('|')
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
for plugin in plugins.iter_mut() {
|
||||
if plugin.name() == target_plugin_name {
|
||||
plugin.extend_api(webview_manager.clone(), message);
|
||||
break;
|
||||
impl<M: Params> Default for PluginStore<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
store: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Params> PluginStore<M> {
|
||||
/// Adds a plugin to the store.
|
||||
///
|
||||
/// Returns `true` if a plugin with the same name is already in the store.
|
||||
pub fn register<P: Plugin<M> + 'static>(&mut self, plugin: P) -> bool {
|
||||
self.store.insert(plugin.name(), Box::new(plugin)).is_some()
|
||||
}
|
||||
|
||||
/// Initializes all plugins in the store.
|
||||
pub(crate) fn initialize(&mut self, config: &PluginConfig) -> crate::Result<()> {
|
||||
self.store.values_mut().try_for_each(|plugin| {
|
||||
plugin.initialize(config.0.get(plugin.name()).cloned().unwrap_or_default())
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates an initialization script from all plugins in the store.
|
||||
pub(crate) fn initialization_script(&self) -> String {
|
||||
self
|
||||
.store
|
||||
.values()
|
||||
.filter_map(|p| p.initialization_script())
|
||||
.fold(String::new(), |acc, script| {
|
||||
format!("{}\n(function () {{ {} }})();", acc, script)
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs the created hook for all plugins in the store.
|
||||
pub(crate) fn created(&mut self, window: Window<M>) {
|
||||
self
|
||||
.store
|
||||
.values_mut()
|
||||
.for_each(|plugin| plugin.created(window.clone()))
|
||||
}
|
||||
|
||||
/// Runs the on_page_load hook for all plugins in the store.
|
||||
pub(crate) fn on_page_load(&mut self, window: Window<M>, payload: PageLoadPayload) {
|
||||
self
|
||||
.store
|
||||
.values_mut()
|
||||
.for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn extend_api(&mut self, command: String, message: InvokeMessage<M>) {
|
||||
let target = command
|
||||
.replace("plugin:", "")
|
||||
.split('|')
|
||||
.next()
|
||||
.expect("target plugin name empty")
|
||||
.to_string();
|
||||
|
||||
if let Some(plugin) = self.store.get_mut(target.as_str()) {
|
||||
plugin.extend_api(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
185
tauri/src/runtime/app.rs
Normal file
185
tauri/src/runtime/app.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use crate::{
|
||||
api::{assets::Assets, config::WindowUrl},
|
||||
hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
|
||||
plugin::{Plugin, PluginStore},
|
||||
runtime::{
|
||||
flavor::wry::Wry,
|
||||
manager::WindowManager,
|
||||
sealed::ManagerPrivate,
|
||||
tag::Tag,
|
||||
webview::{Attributes, WindowConfig},
|
||||
window::{PendingWindow, Window},
|
||||
Context, Dispatch, Manager, Params, Runtime, RuntimeOrDispatch,
|
||||
},
|
||||
};
|
||||
|
||||
/// A handle to the currently running application.
|
||||
pub struct App<M: Params> {
|
||||
runtime: M::Runtime,
|
||||
manager: M,
|
||||
}
|
||||
|
||||
impl<M: Params> Manager<M> for App<M> {}
|
||||
impl<M: Params> ManagerPrivate<M> for App<M> {
|
||||
fn manager(&self) -> &M {
|
||||
&self.manager
|
||||
}
|
||||
|
||||
fn runtime(&mut self) -> RuntimeOrDispatch<'_, M> {
|
||||
RuntimeOrDispatch::Runtime(&mut self.runtime)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub struct Runner<M: Params> {
|
||||
pending_windows: Vec<PendingWindow<M>>,
|
||||
manager: M,
|
||||
setup: SetupHook<M>,
|
||||
}
|
||||
|
||||
impl<M: Params> Runner<M> {
|
||||
/// Consume and run the [`Application`] until it is finished.
|
||||
pub fn run(mut self) -> crate::Result<()> {
|
||||
// set up all the windows defined in the config
|
||||
for config in self.manager.config().tauri.windows.clone() {
|
||||
let url = config.url.clone();
|
||||
let label = config
|
||||
.label
|
||||
.parse()
|
||||
.unwrap_or_else(|_| panic!("bad label: {}", config.label));
|
||||
|
||||
self
|
||||
.pending_windows
|
||||
.push(PendingWindow::new(WindowConfig(config), label, url));
|
||||
}
|
||||
|
||||
self.manager.initialize_plugins()?;
|
||||
let labels = self
|
||||
.pending_windows
|
||||
.iter()
|
||||
.map(|p| p.label.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut app = App {
|
||||
runtime: M::Runtime::new()?,
|
||||
manager: self.manager,
|
||||
};
|
||||
|
||||
let pending_windows = self.pending_windows;
|
||||
for pending in pending_windows {
|
||||
let pending = app.manager.prepare_window(pending, &labels)?;
|
||||
let detached = app.runtime.create_window(pending)?;
|
||||
app.manager.attach_window(detached);
|
||||
}
|
||||
|
||||
(self.setup)(&mut app)?;
|
||||
app.runtime.run();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The App builder.
|
||||
pub struct AppBuilder<E, L, A, R>
|
||||
where
|
||||
E: Tag,
|
||||
L: Tag,
|
||||
A: Assets,
|
||||
R: Runtime,
|
||||
{
|
||||
/// The JS message handler.
|
||||
invoke_handler: Box<InvokeHandler<WindowManager<E, L, A, R>>>,
|
||||
|
||||
/// The setup hook.
|
||||
setup: SetupHook<WindowManager<E, L, A, R>>,
|
||||
|
||||
/// Page load hook.
|
||||
on_page_load: Box<OnPageLoad<WindowManager<E, L, A, R>>>,
|
||||
|
||||
/// windows to create when starting up.
|
||||
pending_windows: Vec<PendingWindow<WindowManager<E, L, A, R>>>,
|
||||
|
||||
/// All passed plugins
|
||||
plugins: PluginStore<WindowManager<E, L, A, R>>,
|
||||
}
|
||||
|
||||
impl<E, L, A, R> AppBuilder<E, L, A, R>
|
||||
where
|
||||
E: Tag,
|
||||
L: Tag,
|
||||
A: Assets,
|
||||
R: Runtime,
|
||||
{
|
||||
/// Creates a new App builder.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
setup: Box::new(|_| Ok(())),
|
||||
invoke_handler: Box::new(|_| ()),
|
||||
on_page_load: Box::new(|_, _| ()),
|
||||
pending_windows: Default::default(),
|
||||
plugins: PluginStore::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the JS message handler callback.
|
||||
pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
|
||||
where
|
||||
F: Fn(InvokeMessage<WindowManager<E, L, A, R>>) + Send + Sync + 'static,
|
||||
{
|
||||
self.invoke_handler = Box::new(invoke_handler);
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the setup hook.
|
||||
pub fn setup<F>(mut self, setup: F) -> Self
|
||||
where
|
||||
F: Fn(&mut App<WindowManager<E, L, A, R>>) -> Result<(), Box<dyn std::error::Error>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
self.setup = Box::new(setup);
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the page load hook.
|
||||
pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
|
||||
where
|
||||
F: Fn(Window<WindowManager<E, L, A, R>>, PageLoadPayload) + Send + Sync + 'static,
|
||||
{
|
||||
self.on_page_load = Box::new(on_page_load);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a plugin to the runtime.
|
||||
pub fn plugin<P: Plugin<WindowManager<E, L, A, R>> + 'static>(mut self, plugin: P) -> Self {
|
||||
self.plugins.register(plugin);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new webview.
|
||||
pub fn create_window<F>(mut self, label: L, url: WindowUrl, setup: F) -> Self
|
||||
where
|
||||
F: FnOnce(<R::Dispatcher as Dispatch>::Attributes) -> <R::Dispatcher as Dispatch>::Attributes,
|
||||
{
|
||||
let attributes = setup(<R::Dispatcher as Dispatch>::Attributes::new());
|
||||
self
|
||||
.pending_windows
|
||||
.push(PendingWindow::new(attributes, label, url));
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the [`App`] and the underlying [`Runtime`].
|
||||
pub fn build(self, context: Context<A>) -> Runner<WindowManager<E, L, A, R>> {
|
||||
Runner {
|
||||
pending_windows: self.pending_windows,
|
||||
setup: self.setup,
|
||||
manager: WindowManager::with_handlers(context, self.invoke_handler, self.on_page_load),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make `Wry` the default `ApplicationExt` for `AppBuilder`
|
||||
impl<A: Assets> Default for AppBuilder<String, String, A, Wry> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
3
tauri/src/runtime/flavor/mod.rs
Normal file
3
tauri/src/runtime/flavor/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
//! Officially supported webview runtimes.
|
||||
|
||||
pub mod wry;
|
@ -1,22 +1,24 @@
|
||||
use super::{
|
||||
ApplicationDispatcherExt, ApplicationExt, CustomProtocol, FileDropEvent, FileDropHandler, Icon,
|
||||
RpcRequest, WebviewBuilderExt, WebviewBuilderExtPrivate, WebviewRpcHandler, WindowConfig,
|
||||
};
|
||||
//! The [`wry`] webview runtime.
|
||||
|
||||
use crate::plugin::PluginStore;
|
||||
use once_cell::sync::Lazy;
|
||||
use crate::runtime::{
|
||||
webview::{
|
||||
Attributes, AttributesPrivate, CustomProtocol, FileDropEvent, FileDropHandler, Icon,
|
||||
RpcRequest, WebviewRpcHandler, WindowConfig,
|
||||
},
|
||||
window::{DetachedWindow, PendingWindow},
|
||||
Dispatch, Params, Runtime,
|
||||
};
|
||||
use std::{convert::TryFrom, path::PathBuf};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::fs::create_dir_all;
|
||||
#[cfg(target_os = "windows")]
|
||||
use tauri_api::path::{resolve_path, BaseDirectory};
|
||||
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
path::PathBuf,
|
||||
};
|
||||
/// Wraps a Tauri icon into a format [`wry`] expects the icon to be in.
|
||||
pub struct WryIcon(wry::Icon);
|
||||
|
||||
impl TryFrom<Icon> for wry::Icon {
|
||||
impl TryFrom<Icon> for WryIcon {
|
||||
type Error = crate::Error;
|
||||
fn try_from(icon: Icon) -> Result<Self, Self::Error> {
|
||||
let icon = match icon {
|
||||
@ -27,11 +29,11 @@ impl TryFrom<Icon> for wry::Icon {
|
||||
wry::Icon::from_bytes(raw).map_err(|e| crate::Error::InvalidIcon(e.to_string()))?
|
||||
}
|
||||
};
|
||||
Ok(icon)
|
||||
Ok(Self(icon))
|
||||
}
|
||||
}
|
||||
|
||||
impl WebviewBuilderExtPrivate for wry::Attributes {
|
||||
impl AttributesPrivate for wry::Attributes {
|
||||
fn url(mut self, url: String) -> Self {
|
||||
self.url.replace(url);
|
||||
self
|
||||
@ -98,9 +100,8 @@ impl From<WindowConfig> for wry::Attributes {
|
||||
}
|
||||
|
||||
/// The webview builder.
|
||||
impl WebviewBuilderExt for wry::Attributes {
|
||||
/// The webview object that this builder creates.
|
||||
type Webview = Self;
|
||||
impl Attributes for wry::Attributes {
|
||||
type Icon = WryIcon;
|
||||
|
||||
fn new() -> Self {
|
||||
Default::default()
|
||||
@ -191,9 +192,9 @@ impl WebviewBuilderExt for wry::Attributes {
|
||||
self
|
||||
}
|
||||
|
||||
fn icon(mut self, icon: Icon) -> crate::Result<Self> {
|
||||
self.icon = Some(icon.try_into()?);
|
||||
Ok(self)
|
||||
fn icon(mut self, icon: Self::Icon) -> Self {
|
||||
self.icon = Some(icon.0);
|
||||
self
|
||||
}
|
||||
|
||||
fn has_icon(&self) -> bool {
|
||||
@ -205,8 +206,8 @@ impl WebviewBuilderExt for wry::Attributes {
|
||||
self
|
||||
}
|
||||
|
||||
fn finish(self) -> crate::Result<Self::Webview> {
|
||||
Ok(self)
|
||||
fn build(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,270 +230,316 @@ impl From<wry::FileDropEvent> for FileDropEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// A dispatcher for a [`wry`] runtime.
|
||||
#[derive(Clone)]
|
||||
pub struct WryDispatcher(wry::WindowProxy, wry::ApplicationProxy);
|
||||
pub struct WryDispatcher {
|
||||
window: wry::WindowProxy,
|
||||
application: wry::ApplicationProxy,
|
||||
}
|
||||
|
||||
impl ApplicationDispatcherExt for WryDispatcher {
|
||||
type WebviewBuilder = wry::Attributes;
|
||||
impl Dispatch for WryDispatcher {
|
||||
type Runtime = Wry;
|
||||
type Icon = WryIcon;
|
||||
type Attributes = wry::Attributes;
|
||||
|
||||
fn create_webview(
|
||||
&self,
|
||||
attributes: Self::WebviewBuilder,
|
||||
rpc_handler: Option<WebviewRpcHandler<Self>>,
|
||||
custom_protocol: Option<CustomProtocol>,
|
||||
file_drop_handler: Option<FileDropHandler>,
|
||||
) -> crate::Result<Self> {
|
||||
let app_dispatcher = self.1.clone();
|
||||
fn create_window<M: Params<Runtime = Self::Runtime>>(
|
||||
&mut self,
|
||||
pending: PendingWindow<M>,
|
||||
) -> crate::Result<DetachedWindow<M>> {
|
||||
let PendingWindow {
|
||||
attributes,
|
||||
rpc_handler,
|
||||
custom_protocol,
|
||||
file_drop_handler,
|
||||
label,
|
||||
..
|
||||
} = pending;
|
||||
|
||||
let wry_rpc_handler = Box::new(
|
||||
move |dispatcher: wry::WindowProxy, request: wry::RpcRequest| {
|
||||
if let Some(handler) = &rpc_handler {
|
||||
handler(
|
||||
WryDispatcher(dispatcher, app_dispatcher.clone()),
|
||||
request.into(),
|
||||
);
|
||||
}
|
||||
None
|
||||
},
|
||||
);
|
||||
let proxy = self.application.clone();
|
||||
|
||||
let file_drop_handler = Box::new(move |event: wry::FileDropEvent| {
|
||||
if let Some(handler) = &file_drop_handler {
|
||||
handler(event.into())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
let rpc_handler =
|
||||
rpc_handler.map(|handler| create_rpc_handler(proxy.clone(), label.clone(), handler));
|
||||
|
||||
let window_dispatcher = self
|
||||
.1
|
||||
let file_drop_handler = file_drop_handler
|
||||
.map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler));
|
||||
|
||||
let window = self
|
||||
.application
|
||||
.add_window_with_configs(
|
||||
attributes,
|
||||
Some(wry_rpc_handler),
|
||||
custom_protocol.map(|p| wry::CustomProtocol {
|
||||
name: p.name.clone(),
|
||||
handler: Box::new(move |a| (*p.handler)(a).map_err(|_| wry::Error::InitScriptError)),
|
||||
}),
|
||||
Some(file_drop_handler),
|
||||
rpc_handler,
|
||||
custom_protocol.map(create_custom_protocol),
|
||||
file_drop_handler,
|
||||
)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)?;
|
||||
Ok(Self(window_dispatcher, self.1.clone()))
|
||||
.map_err(|_| crate::Error::CreateWebview)?;
|
||||
|
||||
let dispatcher = WryDispatcher {
|
||||
window,
|
||||
application: proxy,
|
||||
};
|
||||
|
||||
Ok(DetachedWindow { label, dispatcher })
|
||||
}
|
||||
|
||||
fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_resizable(resizable)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_title(title)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn maximize(&self) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.maximize()
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn unmaximize(&self) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.unmaximize()
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn minimize(&self) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.minimize()
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn unminimize(&self) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.unminimize()
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn show(&self) -> crate::Result<()> {
|
||||
self.0.show().map_err(|_| crate::Error::FailedToSendMessage)
|
||||
self
|
||||
.window
|
||||
.show()
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn hide(&self) -> crate::Result<()> {
|
||||
self.0.hide().map_err(|_| crate::Error::FailedToSendMessage)
|
||||
self
|
||||
.window
|
||||
.hide()
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn close(&self) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.close()
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_decorations(decorations)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_always_on_top(always_on_top)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_width(&self, width: f64) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_width(width)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_height(&self, height: f64) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_height(height)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn resize(&self, width: f64, height: f64) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.resize(width, height)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_min_size(&self, min_width: f64, min_height: f64) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_min_size(min_width, min_height)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_max_size(&self, max_width: f64, max_height: f64) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_max_size(max_width, max_height)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_x(&self, x: f64) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_x(x)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_y(&self, y: f64) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_y(y)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_position(&self, x: f64, y: f64) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_position(x, y)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.set_fullscreen(fullscreen)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn set_icon(&self, icon: Icon) -> crate::Result<()> {
|
||||
fn set_icon(&self, icon: Self::Icon) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.set_icon(icon.try_into()?)
|
||||
.window
|
||||
.set_icon(icon.0)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()> {
|
||||
self
|
||||
.0
|
||||
.window
|
||||
.evaluate_script(script)
|
||||
.map_err(|_| crate::Error::FailedToSendMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around the wry Application interface.
|
||||
pub struct WryApplication {
|
||||
pub struct Wry {
|
||||
inner: wry::Application,
|
||||
}
|
||||
|
||||
impl ApplicationExt for WryApplication {
|
||||
type WebviewBuilder = wry::Attributes;
|
||||
impl Runtime for Wry {
|
||||
type Dispatcher = WryDispatcher;
|
||||
|
||||
fn plugin_store() -> &'static PluginStore<Self> {
|
||||
static PLUGINS: Lazy<PluginStore<WryApplication>> = Lazy::new(Default::default);
|
||||
&PLUGINS
|
||||
}
|
||||
|
||||
fn new() -> crate::Result<Self> {
|
||||
let app = wry::Application::new().map_err(|_| crate::Error::CreateWebview)?;
|
||||
Ok(Self { inner: app })
|
||||
}
|
||||
|
||||
fn create_webview(
|
||||
fn create_window<M: Params<Runtime = Self>>(
|
||||
&mut self,
|
||||
webview_builder: Self::WebviewBuilder,
|
||||
rpc_handler: Option<WebviewRpcHandler<Self::Dispatcher>>,
|
||||
custom_protocol: Option<CustomProtocol>,
|
||||
file_drop_handler: Option<FileDropHandler>,
|
||||
) -> crate::Result<Self::Dispatcher> {
|
||||
let app_dispatcher = self.inner.application_proxy();
|
||||
pending: PendingWindow<M>,
|
||||
) -> crate::Result<DetachedWindow<M>> {
|
||||
let PendingWindow {
|
||||
attributes,
|
||||
rpc_handler,
|
||||
custom_protocol,
|
||||
file_drop_handler,
|
||||
label,
|
||||
..
|
||||
} = pending;
|
||||
|
||||
let app_dispatcher_ = app_dispatcher.clone();
|
||||
let wry_rpc_handler = Box::new(
|
||||
move |dispatcher: wry::WindowProxy, request: wry::RpcRequest| {
|
||||
if let Some(handler) = &rpc_handler {
|
||||
handler(
|
||||
WryDispatcher(dispatcher, app_dispatcher_.clone()),
|
||||
request.into(),
|
||||
);
|
||||
}
|
||||
None
|
||||
},
|
||||
);
|
||||
let proxy = self.inner.application_proxy();
|
||||
|
||||
let file_drop_handler = Box::new(move |event: wry::FileDropEvent| {
|
||||
if let Some(handler) = &file_drop_handler {
|
||||
handler(event.into())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
let rpc_handler =
|
||||
rpc_handler.map(|handler| create_rpc_handler(proxy.clone(), label.clone(), handler));
|
||||
|
||||
let dispatcher = self
|
||||
let file_drop_handler = file_drop_handler
|
||||
.map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler));
|
||||
|
||||
let window = self
|
||||
.inner
|
||||
.add_window_with_configs(
|
||||
webview_builder.finish()?,
|
||||
Some(wry_rpc_handler),
|
||||
custom_protocol.map(|p| wry::CustomProtocol {
|
||||
name: p.name.clone(),
|
||||
handler: Box::new(move |a| (*p.handler)(a).map_err(|_| wry::Error::InitScriptError)),
|
||||
}),
|
||||
Some(file_drop_handler),
|
||||
attributes,
|
||||
rpc_handler,
|
||||
custom_protocol.map(create_custom_protocol),
|
||||
file_drop_handler,
|
||||
)
|
||||
.map_err(|_| crate::Error::CreateWebview)?;
|
||||
Ok(WryDispatcher(dispatcher, app_dispatcher))
|
||||
|
||||
let dispatcher = WryDispatcher {
|
||||
window,
|
||||
application: proxy,
|
||||
};
|
||||
|
||||
Ok(DetachedWindow { label, dispatcher })
|
||||
}
|
||||
|
||||
fn run(self) {
|
||||
wry::Application::run(self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a wry rpc handler from a tauri rpc handler.
|
||||
fn create_rpc_handler<M: Params<Runtime = Wry>>(
|
||||
app_proxy: wry::ApplicationProxy,
|
||||
label: M::Label,
|
||||
handler: WebviewRpcHandler<M>,
|
||||
) -> wry::WindowRpcHandler {
|
||||
Box::new(move |window, request| {
|
||||
handler(
|
||||
DetachedWindow {
|
||||
dispatcher: WryDispatcher {
|
||||
window,
|
||||
application: app_proxy.clone(),
|
||||
},
|
||||
label: label.clone(),
|
||||
},
|
||||
request.into(),
|
||||
);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a wry file drop handler from a tauri file drop handler.
|
||||
fn create_file_drop_handler<M: Params<Runtime = Wry>>(
|
||||
app_proxy: wry::ApplicationProxy,
|
||||
label: M::Label,
|
||||
handler: FileDropHandler<M>,
|
||||
) -> wry::WindowFileDropHandler {
|
||||
Box::new(move |window, event| {
|
||||
handler(
|
||||
event.into(),
|
||||
DetachedWindow {
|
||||
dispatcher: WryDispatcher {
|
||||
window,
|
||||
application: app_proxy.clone(),
|
||||
},
|
||||
label: label.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a wry custom protocol from a tauri custom protocol.
|
||||
fn create_custom_protocol(custom_protocol: CustomProtocol) -> wry::CustomProtocol {
|
||||
wry::CustomProtocol {
|
||||
name: custom_protocol.name.clone(),
|
||||
handler: Box::new(move |data| {
|
||||
(custom_protocol.handler)(data).map_err(|_| wry::Error::InitScriptError)
|
||||
}),
|
||||
}
|
||||
}
|
527
tauri/src/runtime/manager.rs
Normal file
527
tauri/src/runtime/manager.rs
Normal file
@ -0,0 +1,527 @@
|
||||
use crate::{
|
||||
api::{
|
||||
assets::Assets,
|
||||
config::{Config, WindowUrl},
|
||||
},
|
||||
event::{Event, EventHandler, Listeners},
|
||||
hooks::{InvokeHandler, InvokeMessage, InvokePayload, OnPageLoad, PageLoadPayload},
|
||||
plugin::PluginStore,
|
||||
runtime::{
|
||||
sealed::ParamsPrivate,
|
||||
tag::{tags_to_javascript_array, Tag, ToJavascript},
|
||||
webview::{
|
||||
Attributes, AttributesPrivate, CustomProtocol, FileDropEvent, FileDropHandler,
|
||||
WebviewRpcHandler,
|
||||
},
|
||||
window::{DetachedWindow, PendingWindow, Window},
|
||||
Context, Dispatch, Icon, Params, Runtime,
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashSet,
|
||||
convert::TryInto,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub struct InnerWindowManager<M: Params> {
|
||||
windows: Mutex<HashSet<Window<M>>>,
|
||||
plugins: Mutex<PluginStore<M>>,
|
||||
listeners: Listeners<M::Event, M::Label>,
|
||||
|
||||
/// The JS message handler.
|
||||
invoke_handler: Box<InvokeHandler<M>>,
|
||||
|
||||
/// The page load hook, invoked when the webview performs a navigation.
|
||||
on_page_load: Box<OnPageLoad<M>>,
|
||||
|
||||
config: Config,
|
||||
assets: Arc<M::Assets>,
|
||||
default_window_icon: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub struct WindowManager<E, L, A, R>
|
||||
where
|
||||
E: Tag,
|
||||
L: Tag,
|
||||
A: Assets + 'static,
|
||||
R: Runtime,
|
||||
{
|
||||
pub(crate) inner: Arc<InnerWindowManager<Self>>,
|
||||
}
|
||||
|
||||
impl<E, L, A, R> Clone for WindowManager<E, L, A, R>
|
||||
where
|
||||
E: Tag,
|
||||
L: Tag,
|
||||
A: Assets + 'static,
|
||||
R: Runtime,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, L, A, R> WindowManager<E, L, A, R>
|
||||
where
|
||||
E: Tag,
|
||||
L: Tag,
|
||||
A: Assets,
|
||||
R: Runtime,
|
||||
{
|
||||
pub(crate) fn with_handlers(
|
||||
context: Context<A>,
|
||||
invoke_handler: Box<InvokeHandler<Self>>,
|
||||
on_page_load: Box<OnPageLoad<Self>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(InnerWindowManager {
|
||||
windows: Mutex::default(),
|
||||
plugins: Mutex::default(),
|
||||
listeners: Listeners::default(),
|
||||
invoke_handler,
|
||||
on_page_load,
|
||||
config: context.config,
|
||||
assets: Arc::new(context.assets),
|
||||
default_window_icon: context.default_window_icon,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// setup content for dev-server
|
||||
#[cfg(dev)]
|
||||
fn get_url(&self) -> String {
|
||||
if self.inner.config.build.dev_path.starts_with("http") {
|
||||
self.inner.config.build.dev_path.clone()
|
||||
} else {
|
||||
let path = "index.html";
|
||||
format!(
|
||||
"data:text/html;base64,{}",
|
||||
base64::encode(
|
||||
self
|
||||
.inner
|
||||
.assets
|
||||
.get(&path)
|
||||
.ok_or_else(|| crate::Error::AssetNotFound(path.to_string()))
|
||||
.map(Cow::into_owned)
|
||||
.expect("Unable to find `index.html` under your devPath folder")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(custom_protocol)]
|
||||
fn get_url(&self) -> String {
|
||||
format!("tauri://{}", self.inner.config.tauri.bundle.identifier)
|
||||
}
|
||||
|
||||
fn prepare_attributes(
|
||||
&self,
|
||||
attrs: <R::Dispatcher as Dispatch>::Attributes,
|
||||
url: String,
|
||||
label: L,
|
||||
pending_labels: &[L],
|
||||
) -> crate::Result<<R::Dispatcher as Dispatch>::Attributes> {
|
||||
let is_init_global = self.inner.config.build.with_global_tauri;
|
||||
let plugin_init = self
|
||||
.inner
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.initialization_script();
|
||||
|
||||
let mut attributes = attrs
|
||||
.url(url)
|
||||
.initialization_script(&self.initialization_script(&plugin_init, is_init_global))
|
||||
.initialization_script(&format!(
|
||||
r#"
|
||||
window.__TAURI__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
|
||||
window.__TAURI__.__currentWindow = {{ label: {current_window_label} }}
|
||||
"#,
|
||||
window_labels_array = tags_to_javascript_array(pending_labels)?,
|
||||
current_window_label = label.to_javascript()?,
|
||||
));
|
||||
|
||||
if !attributes.has_icon() {
|
||||
if let Some(default_window_icon) = &self.inner.default_window_icon {
|
||||
let icon = Icon::Raw(default_window_icon.clone());
|
||||
let icon = icon.try_into().expect("infallible icon convert failed");
|
||||
attributes = attributes.icon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(attributes)
|
||||
}
|
||||
|
||||
fn prepare_rpc_handler(&self) -> WebviewRpcHandler<Self> {
|
||||
let manager = self.clone();
|
||||
Box::new(move |window, request| {
|
||||
let window = manager.attach_window(window);
|
||||
let command = request.command.clone();
|
||||
|
||||
let arg = request
|
||||
.params
|
||||
.unwrap()
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.first_mut()
|
||||
.unwrap_or(&mut JsonValue::Null)
|
||||
.take();
|
||||
match serde_json::from_value::<InvokePayload>(arg) {
|
||||
Ok(message) => {
|
||||
let _ = window.on_message(command, message);
|
||||
}
|
||||
Err(e) => {
|
||||
let error: crate::Error = e.into();
|
||||
let _ = window.eval(&format!(
|
||||
r#"console.error({})"#,
|
||||
JsonValue::String(error.to_string())
|
||||
));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_custom_protocol(&self) -> CustomProtocol {
|
||||
let assets = self.inner.assets.clone();
|
||||
let bundle_identifier = self.inner.config.tauri.bundle.identifier.clone();
|
||||
CustomProtocol {
|
||||
name: "tauri".into(),
|
||||
handler: Box::new(move |path| {
|
||||
let mut path = path
|
||||
.split('?')
|
||||
// ignore query string
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.replace(&format!("tauri://{}", bundle_identifier), "");
|
||||
if path.ends_with('/') {
|
||||
path.pop();
|
||||
}
|
||||
let path = if path.is_empty() {
|
||||
// if the url is `tauri://${appId}`, we should load `index.html`
|
||||
"index.html".to_string()
|
||||
} else {
|
||||
// skip leading `/`
|
||||
path.chars().skip(1).collect::<String>()
|
||||
};
|
||||
|
||||
let asset_response = assets
|
||||
.get(&path)
|
||||
.ok_or(crate::Error::AssetNotFound(path))
|
||||
.map(Cow::into_owned);
|
||||
match asset_response {
|
||||
Ok(asset) => Ok(asset),
|
||||
Err(e) => {
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("{:?}", e); // TODO log::error!
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_file_drop(&self) -> FileDropHandler<Self> {
|
||||
let manager = self.clone();
|
||||
Box::new(move |event, window| {
|
||||
let manager = manager.clone();
|
||||
crate::async_runtime::block_on(async move {
|
||||
let window = manager.attach_window(window);
|
||||
let _ = match event {
|
||||
FileDropEvent::Hovered(paths) => {
|
||||
window.emit_internal("tauri://file-drop".to_string(), Some(paths))
|
||||
}
|
||||
FileDropEvent::Dropped(paths) => {
|
||||
window.emit_internal("tauri://file-drop-hover".to_string(), Some(paths))
|
||||
}
|
||||
FileDropEvent::Cancelled => {
|
||||
window.emit_internal("tauri://file-drop-cancelled".to_string(), Some(()))
|
||||
}
|
||||
};
|
||||
});
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
fn initialization_script(
|
||||
&self,
|
||||
plugin_initialization_script: &str,
|
||||
with_global_tauri: bool,
|
||||
) -> String {
|
||||
format!(
|
||||
r#"
|
||||
{bundle_script}
|
||||
{core_script}
|
||||
{event_initialization_script}
|
||||
if (window.rpc) {{
|
||||
window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
|
||||
}} else {{
|
||||
window.addEventListener('DOMContentLoaded', function () {{
|
||||
window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
|
||||
}})
|
||||
}}
|
||||
{plugin_initialization_script}
|
||||
"#,
|
||||
core_script = include_str!("../../scripts/core.js"),
|
||||
bundle_script = if with_global_tauri {
|
||||
include_str!("../../scripts/bundle.js")
|
||||
} else {
|
||||
""
|
||||
},
|
||||
event_initialization_script = self.event_initialization_script(),
|
||||
plugin_initialization_script = plugin_initialization_script
|
||||
)
|
||||
}
|
||||
|
||||
fn event_initialization_script(&self) -> String {
|
||||
return format!(
|
||||
"
|
||||
window['{queue}'] = [];
|
||||
window['{function}'] = function (eventData, salt, ignoreQueue) {{
|
||||
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
|
||||
if (!ignoreQueue && listeners.length === 0) {{
|
||||
window['{queue}'].push({{
|
||||
eventData: eventData,
|
||||
salt: salt
|
||||
}})
|
||||
}}
|
||||
|
||||
if (listeners.length > 0) {{
|
||||
window.__TAURI__.invoke('tauri', {{
|
||||
__tauriModule: 'Internal',
|
||||
message: {{
|
||||
cmd: 'validateSalt',
|
||||
salt: salt
|
||||
}}
|
||||
}}).then(function (flag) {{
|
||||
if (flag) {{
|
||||
for (let i = listeners.length - 1; i >= 0; i--) {{
|
||||
const listener = listeners[i]
|
||||
eventData.id = listener.id
|
||||
listener.handler(eventData)
|
||||
}}
|
||||
}}
|
||||
}})
|
||||
}}
|
||||
}}
|
||||
",
|
||||
function = self.inner.listeners.function_name(),
|
||||
queue = self.inner.listeners.queue_object_name(),
|
||||
listeners = self.inner.listeners.listeners_object_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{generate_context, runtime::flavor::wry::Wry};
|
||||
|
||||
use super::WindowManager;
|
||||
|
||||
#[test]
|
||||
fn check_get_url() {
|
||||
let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate::Context);
|
||||
let manager: WindowManager<String, String, _, Wry> =
|
||||
WindowManager::with_handlers(context, Box::new(|_| ()), Box::new(|_, _| ()));
|
||||
|
||||
#[cfg(custom_protocol)]
|
||||
assert_eq!(manager.get_url(), "tauri://studio.tauri.example");
|
||||
|
||||
#[cfg(dev)]
|
||||
{
|
||||
use crate::runtime::sealed::ParamsPrivate;
|
||||
assert_eq!(manager.get_url(), manager.config().build.dev_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, L, A, R> ParamsPrivate<Self> for WindowManager<E, L, A, R>
|
||||
where
|
||||
E: Tag,
|
||||
L: Tag,
|
||||
A: Assets + 'static,
|
||||
R: Runtime,
|
||||
{
|
||||
fn run_invoke_handler(&self, message: InvokeMessage<Self>) {
|
||||
(self.inner.invoke_handler)(message);
|
||||
}
|
||||
|
||||
fn run_on_page_load(&self, window: Window<Self>, payload: PageLoadPayload) {
|
||||
(self.inner.on_page_load)(window.clone(), payload.clone());
|
||||
self
|
||||
.inner
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.on_page_load(window, payload);
|
||||
}
|
||||
|
||||
fn extend_api(&self, command: String, message: InvokeMessage<Self>) {
|
||||
self
|
||||
.inner
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.extend_api(command, message);
|
||||
}
|
||||
|
||||
fn initialize_plugins(&self) -> crate::Result<()> {
|
||||
self
|
||||
.inner
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.initialize(&self.inner.config.plugins)
|
||||
}
|
||||
|
||||
fn prepare_window(
|
||||
&self,
|
||||
mut pending: PendingWindow<Self>,
|
||||
pending_labels: &[L],
|
||||
) -> crate::Result<PendingWindow<Self>> {
|
||||
let (is_local, url) = match &pending.url {
|
||||
WindowUrl::App => (true, self.get_url()),
|
||||
// todo: we should probably warn about how custom urls usually need to be valid urls
|
||||
// e.g. cannot be relative without a base
|
||||
WindowUrl::Custom(url) => (url.len() > 7 && &url[0..8] == "tauri://", url.clone()),
|
||||
};
|
||||
|
||||
let attributes = pending.attributes.clone();
|
||||
if is_local {
|
||||
let label = pending.label.clone();
|
||||
pending.attributes = self.prepare_attributes(attributes, url, label, pending_labels)?;
|
||||
pending.rpc_handler = Some(self.prepare_rpc_handler());
|
||||
pending.custom_protocol = Some(self.prepare_custom_protocol());
|
||||
} else {
|
||||
pending.attributes = attributes.url(url);
|
||||
}
|
||||
|
||||
pending.file_drop_handler = Some(self.prepare_file_drop());
|
||||
|
||||
Ok(pending)
|
||||
}
|
||||
|
||||
fn attach_window(&self, window: DetachedWindow<Self>) -> Window<Self> {
|
||||
let window = Window::new(self.clone(), window);
|
||||
|
||||
// insert the window into our manager
|
||||
{
|
||||
self
|
||||
.inner
|
||||
.windows
|
||||
.lock()
|
||||
.expect("poisoned window manager")
|
||||
.insert(window.clone());
|
||||
}
|
||||
|
||||
// let plugins know that a new window has been added to the manager
|
||||
{
|
||||
self
|
||||
.inner
|
||||
.plugins
|
||||
.lock()
|
||||
.expect("poisoned plugin store")
|
||||
.created(window.clone());
|
||||
}
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
fn emit_filter_internal<S: Serialize + Clone, F: Fn(&Window<Self>) -> bool>(
|
||||
&self,
|
||||
event: String,
|
||||
payload: Option<S>,
|
||||
filter: F,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
.inner
|
||||
.windows
|
||||
.lock()
|
||||
.expect("poisoned window manager")
|
||||
.iter()
|
||||
.filter(|&w| filter(w))
|
||||
.try_for_each(|window| window.emit_internal(event.clone(), payload.clone()))
|
||||
}
|
||||
|
||||
fn emit_filter<S: Serialize + Clone, F: Fn(&Window<Self>) -> bool>(
|
||||
&self,
|
||||
event: E,
|
||||
payload: Option<S>,
|
||||
filter: F,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
.inner
|
||||
.windows
|
||||
.lock()
|
||||
.expect("poisoned window manager")
|
||||
.iter()
|
||||
.filter(|&w| filter(w))
|
||||
.try_for_each(|window| window.emit(&event, payload.clone()))
|
||||
}
|
||||
|
||||
fn labels(&self) -> HashSet<L> {
|
||||
self
|
||||
.inner
|
||||
.windows
|
||||
.lock()
|
||||
.expect("poisoned window manager")
|
||||
.iter()
|
||||
.map(|w| w.label().clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn config(&self) -> &Config {
|
||||
&self.inner.config
|
||||
}
|
||||
|
||||
fn unlisten(&self, handler_id: EventHandler) {
|
||||
self.inner.listeners.unlisten(handler_id)
|
||||
}
|
||||
|
||||
fn trigger(&self, event: E, window: Option<L>, data: Option<String>) {
|
||||
self.inner.listeners.trigger(event, window, data)
|
||||
}
|
||||
|
||||
fn listen<F: Fn(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: E,
|
||||
window: Option<L>,
|
||||
handler: F,
|
||||
) -> EventHandler {
|
||||
self.inner.listeners.listen(event, window, handler)
|
||||
}
|
||||
|
||||
fn once<F: Fn(Event) + Send + 'static>(&self, event: E, window: Option<L>, handler: F) {
|
||||
self.inner.listeners.once(event, window, handler)
|
||||
}
|
||||
|
||||
fn event_listeners_object_name(&self) -> String {
|
||||
self.inner.listeners.listeners_object_name()
|
||||
}
|
||||
|
||||
fn event_queue_object_name(&self) -> String {
|
||||
self.inner.listeners.queue_object_name()
|
||||
}
|
||||
|
||||
fn event_emit_function_name(&self) -> String {
|
||||
self.inner.listeners.function_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, L, A, R> Params for WindowManager<E, L, A, R>
|
||||
where
|
||||
E: Tag,
|
||||
L: Tag,
|
||||
A: Assets,
|
||||
R: Runtime,
|
||||
{
|
||||
type Event = E;
|
||||
type Label = L;
|
||||
type Assets = A;
|
||||
type Runtime = R;
|
||||
}
|
328
tauri/src/runtime/mod.rs
Normal file
328
tauri/src/runtime/mod.rs
Normal file
@ -0,0 +1,328 @@
|
||||
use crate::{
|
||||
api::{assets::Assets, config::Config},
|
||||
event::{Event, EventHandler},
|
||||
runtime::{
|
||||
tag::Tag,
|
||||
webview::{Attributes, AttributesPrivate, Icon, WindowConfig},
|
||||
window::{DetachedWindow, PendingWindow, Window},
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub(crate) mod app;
|
||||
pub mod flavor;
|
||||
pub(crate) mod manager;
|
||||
pub(crate) mod tag;
|
||||
pub(crate) mod webview;
|
||||
pub(crate) mod window;
|
||||
|
||||
/// Important configurable items required by Tauri.
|
||||
pub struct Context<A: Assets> {
|
||||
/// The config the application was prepared with.
|
||||
pub config: Config,
|
||||
|
||||
/// The assets to be served directly by Tauri.
|
||||
pub assets: A,
|
||||
|
||||
/// The default window icon Tauri should use when creating windows.
|
||||
pub default_window_icon: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// The webview runtime interface.
|
||||
pub trait Runtime: Sized + 'static {
|
||||
/// The message dispatcher.
|
||||
type Dispatcher: Dispatch<Runtime = Self>;
|
||||
|
||||
/// Creates a new webview runtime.
|
||||
fn new() -> crate::Result<Self>;
|
||||
|
||||
/// Creates a new webview window.
|
||||
fn create_window<M: Params<Runtime = Self>>(
|
||||
&mut self,
|
||||
pending: PendingWindow<M>,
|
||||
) -> crate::Result<DetachedWindow<M>>;
|
||||
|
||||
/// Run the webview runtime.
|
||||
fn run(self);
|
||||
}
|
||||
|
||||
/// Webview dispatcher. A thread-safe handle to the webview API.
|
||||
pub trait Dispatch: Clone + Send + Sized + 'static {
|
||||
/// The runtime this [`Dispatch`] runs under.
|
||||
type Runtime: Runtime;
|
||||
|
||||
/// Representation of a window icon.
|
||||
type Icon: TryFrom<Icon, Error = crate::Error>;
|
||||
|
||||
/// The webview builder type.
|
||||
type Attributes: Attributes<Icon = Self::Icon>
|
||||
+ AttributesPrivate
|
||||
+ From<WindowConfig>
|
||||
+ Clone
|
||||
+ Send;
|
||||
|
||||
/// Creates a new webview window.
|
||||
fn create_window<M: Params<Runtime = Self::Runtime>>(
|
||||
&mut self,
|
||||
pending: PendingWindow<M>,
|
||||
) -> crate::Result<DetachedWindow<M>>;
|
||||
|
||||
/// Updates the window resizable flag.
|
||||
fn set_resizable(&self, resizable: bool) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window title.
|
||||
fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()>;
|
||||
|
||||
/// Maximizes the window.
|
||||
fn maximize(&self) -> crate::Result<()>;
|
||||
|
||||
/// Unmaximizes the window.
|
||||
fn unmaximize(&self) -> crate::Result<()>;
|
||||
|
||||
/// Minimizes the window.
|
||||
fn minimize(&self) -> crate::Result<()>;
|
||||
|
||||
/// Unminimizes the window.
|
||||
fn unminimize(&self) -> crate::Result<()>;
|
||||
|
||||
/// Shows the window.
|
||||
fn show(&self) -> crate::Result<()>;
|
||||
|
||||
/// Hides the window.
|
||||
fn hide(&self) -> crate::Result<()>;
|
||||
|
||||
/// Closes the window.
|
||||
fn close(&self) -> crate::Result<()>;
|
||||
|
||||
/// Updates the hasDecorations flag.
|
||||
fn set_decorations(&self, decorations: bool) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window alwaysOnTop flag.
|
||||
fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window width.
|
||||
fn set_width(&self, width: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window height.
|
||||
fn set_height(&self, height: f64) -> crate::Result<()>;
|
||||
|
||||
/// Resizes the window.
|
||||
fn resize(&self, width: f64, height: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window min size.
|
||||
fn set_min_size(&self, min_width: f64, min_height: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window max size.
|
||||
fn set_max_size(&self, max_width: f64, max_height: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the X position.
|
||||
fn set_x(&self, x: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the Y position.
|
||||
fn set_y(&self, y: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window position.
|
||||
fn set_position(&self, x: f64, y: f64) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window fullscreen state.
|
||||
fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()>;
|
||||
|
||||
/// Updates the window icon.
|
||||
fn set_icon(&self, icon: Self::Icon) -> crate::Result<()>;
|
||||
|
||||
/// Executes javascript on the window this [`Dispatch`] represents.
|
||||
fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
/// Prevent implementation details from leaking out of the [`Manager`] and [`Managed`] traits.
|
||||
pub(crate) mod sealed {
|
||||
use super::Params;
|
||||
use crate::{
|
||||
api::config::Config,
|
||||
event::{Event, EventHandler},
|
||||
hooks::{InvokeMessage, PageLoadPayload},
|
||||
runtime::{
|
||||
window::{DetachedWindow, PendingWindow, Window},
|
||||
RuntimeOrDispatch,
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// private manager api
|
||||
pub trait ParamsPrivate<M: Params>: Clone + Send + Sized + 'static {
|
||||
/// Pass messages not handled by modules or plugins to the running application
|
||||
fn run_invoke_handler(&self, message: InvokeMessage<M>);
|
||||
|
||||
/// Ran once for every window when the page is loaded.
|
||||
fn run_on_page_load(&self, window: Window<M>, payload: PageLoadPayload);
|
||||
|
||||
/// Pass a message to be handled by a plugin that expects the command.
|
||||
fn extend_api(&self, command: String, message: InvokeMessage<M>);
|
||||
|
||||
/// Initialize all the plugins attached to the [`Manager`].
|
||||
fn initialize_plugins(&self) -> crate::Result<()>;
|
||||
|
||||
/// Prepare a [`PendingWindow`] to be created by the [`Runtime`].
|
||||
///
|
||||
/// The passed labels should represent either all the windows in the manager. If the application
|
||||
/// has not yet been started, the passed labels should represent all windows that will be
|
||||
/// created before starting.
|
||||
fn prepare_window(
|
||||
&self,
|
||||
pending: PendingWindow<M>,
|
||||
labels: &[M::Label],
|
||||
) -> crate::Result<PendingWindow<M>>;
|
||||
|
||||
/// Attach a detached window to the manager.
|
||||
fn attach_window(&self, window: DetachedWindow<M>) -> Window<M>;
|
||||
|
||||
/// Emit an event to javascript windows that pass the predicate.
|
||||
fn emit_filter_internal<S: Serialize + Clone, F: Fn(&Window<Self>) -> bool>(
|
||||
&self,
|
||||
event: String,
|
||||
payload: Option<S>,
|
||||
filter: F,
|
||||
) -> crate::Result<()>;
|
||||
|
||||
/// Emit an event to javascript windows that pass the predicate.
|
||||
fn emit_filter<S: Serialize + Clone, F: Fn(&Window<M>) -> bool>(
|
||||
&self,
|
||||
event: M::Event,
|
||||
payload: Option<S>,
|
||||
predicate: F,
|
||||
) -> crate::Result<()>;
|
||||
|
||||
/// All current window labels existing.
|
||||
fn labels(&self) -> HashSet<M::Label>;
|
||||
|
||||
/// The configuration the [`Manager`] was built with.
|
||||
fn config(&self) -> &Config;
|
||||
|
||||
/// Remove the specified event handler.
|
||||
fn unlisten(&self, handler_id: EventHandler);
|
||||
|
||||
/// Trigger an event.
|
||||
fn trigger(&self, event: M::Event, window: Option<M::Label>, data: Option<String>);
|
||||
|
||||
/// Set up a listener to an event.
|
||||
fn listen<F: Fn(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: M::Event,
|
||||
window: Option<M::Label>,
|
||||
handler: F,
|
||||
) -> EventHandler;
|
||||
|
||||
/// Set up a listener to and event that is automatically removed after called once.
|
||||
fn once<F: Fn(Event) + Send + 'static>(
|
||||
&self,
|
||||
event: M::Event,
|
||||
window: Option<M::Label>,
|
||||
handler: F,
|
||||
);
|
||||
|
||||
fn event_listeners_object_name(&self) -> String;
|
||||
fn event_queue_object_name(&self) -> String;
|
||||
fn event_emit_function_name(&self) -> String;
|
||||
}
|
||||
|
||||
/// Represents a managed handle to the application runner.
|
||||
pub trait ManagerPrivate<M: Params> {
|
||||
/// The manager behind the [`Managed`] item.
|
||||
fn manager(&self) -> &M;
|
||||
|
||||
/// The runtime or runtime dispatcher of the [`Managed`] item.
|
||||
fn runtime(&mut self) -> RuntimeOrDispatch<'_, M>;
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents either a [`Runtime`] or its dispatcher.
|
||||
pub enum RuntimeOrDispatch<'m, M: Params> {
|
||||
/// Mutable reference to the [`Runtime`].
|
||||
Runtime(&'m mut M::Runtime),
|
||||
|
||||
/// Copy of the [`Runtime`]'s dispatcher.
|
||||
Dispatch(<M::Runtime as Runtime>::Dispatcher),
|
||||
}
|
||||
|
||||
/// Represents a managed handle to the application runner
|
||||
pub trait Manager<M: Params>: sealed::ManagerPrivate<M> {
|
||||
/// The [`Config`] the manager was created with.
|
||||
fn config(&self) -> &Config {
|
||||
self.manager().config()
|
||||
}
|
||||
|
||||
/// Emits a event to all windows.
|
||||
fn emit_all<S: Serialize + Clone>(
|
||||
&self,
|
||||
event: M::Event,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
self.manager().emit_filter(event, payload, |_| true)
|
||||
}
|
||||
|
||||
/// Emits an event to a window with the specified label.
|
||||
fn emit_to<S: Serialize + Clone>(
|
||||
&self,
|
||||
label: &M::Label,
|
||||
event: M::Event,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
.manager()
|
||||
.emit_filter(event, payload, |w| w.label() == label)
|
||||
}
|
||||
|
||||
/// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
|
||||
fn create_window(&mut self, pending: PendingWindow<M>) -> crate::Result<Window<M>> {
|
||||
let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
|
||||
let pending = self.manager().prepare_window(pending, &labels)?;
|
||||
match self.runtime() {
|
||||
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending),
|
||||
RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending),
|
||||
}
|
||||
.map(|window| self.manager().attach_window(window))
|
||||
}
|
||||
|
||||
/// Listen to a global event.
|
||||
fn listen_global<F>(&self, event: M::Event, handler: F) -> EventHandler
|
||||
where
|
||||
F: Fn(Event) + Send + 'static,
|
||||
{
|
||||
self.manager().listen(event, None, handler)
|
||||
}
|
||||
|
||||
/// Listen to a global event only once.
|
||||
fn once_global<F>(&self, event: M::Event, handler: F)
|
||||
where
|
||||
F: Fn(Event) + Send + 'static,
|
||||
{
|
||||
self.manager().once(event, None, handler)
|
||||
}
|
||||
|
||||
/// Trigger a global event.
|
||||
fn trigger_global(&self, event: M::Event, data: Option<String>) {
|
||||
self.manager().trigger(event, None, data)
|
||||
}
|
||||
|
||||
/// Remove an event listener.
|
||||
fn unlisten(&self, handler_id: EventHandler) {
|
||||
self.manager().unlisten(handler_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Types that the manager needs to have passed in by the application.
|
||||
pub trait Params: sealed::ParamsPrivate<Self> {
|
||||
/// The event type used to create and listen to events.
|
||||
type Event: Tag;
|
||||
|
||||
/// The type used to determine the name of windows.
|
||||
type Label: Tag;
|
||||
|
||||
/// Assets that Tauri should serve from itself.
|
||||
type Assets: Assets;
|
||||
|
||||
/// The underlying webview runtime used by the Tauri application.
|
||||
type Runtime: Runtime;
|
||||
}
|
48
tauri/src/runtime/tag.rs
Normal file
48
tauri/src/runtime/tag.rs
Normal file
@ -0,0 +1,48 @@
|
||||
//! Working with "string-able" types.
|
||||
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
hash::Hash,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// Represents a "string-able" type.
|
||||
///
|
||||
/// The type is required to be able to be represented as a string [`Display`], along with knowing
|
||||
/// how to be parsed from the string representation [`FromStr`].
|
||||
///
|
||||
/// [`Clone`], [`Hash`], and [`Eq`] are needed so that it can represent un-hashable types.
|
||||
///
|
||||
/// [`Send`] and [`Sync`] and `'static` are current requirements due to how it is sometimes sent
|
||||
/// across thread boundaries, although some of those constraints may relax in the future.
|
||||
///
|
||||
/// The simplest type that fits all these requirements is a [`String`](std::string::String).
|
||||
pub trait Tag: Hash + Eq + FromStr + Display + Debug + Clone + Send + Sync + 'static {}
|
||||
|
||||
/// Automatically implement [`Tag`] for all types that fit the requirements.
|
||||
impl<T> Tag for T where T: Hash + Eq + FromStr + Display + Debug + Clone + Send + Sync + 'static {}
|
||||
|
||||
/// Private helper to turn [`Tag`] related things into JavaScript, safely.
|
||||
///
|
||||
/// The main concern is string escaping, so we rely on [`serde_json`] to handle all serialization
|
||||
/// of the [`Tag`] as a string. We do this instead of requiring [`serde::Serialize`] on [`Tag`]
|
||||
/// because it really represents a string, not any serializable data structure.
|
||||
///
|
||||
/// We don't want downstream users to implement this trait so that [`Tag`]s cannot be turned into
|
||||
/// invalid JavaScript - regardless of their content.
|
||||
pub(crate) trait ToJavascript {
|
||||
fn to_javascript(&self) -> crate::Result<String>;
|
||||
}
|
||||
|
||||
impl<T: Tag> ToJavascript for T {
|
||||
/// Turn any [`Tag`] into the JavaScript representation of a string.
|
||||
fn to_javascript(&self) -> crate::Result<String> {
|
||||
Ok(serde_json::to_string(&self.to_string())?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn any collection of [`Tag`]s into a JavaScript array of strings.
|
||||
pub(crate) fn tags_to_javascript_array(tags: &[impl Tag]) -> crate::Result<String> {
|
||||
let tags: Vec<String> = tags.iter().map(ToString::to_string).collect();
|
||||
Ok(serde_json::to_string(&tags)?)
|
||||
}
|
127
tauri/src/runtime/webview.rs
Normal file
127
tauri/src/runtime/webview.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use crate::runtime::window::DetachedWindow;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{convert::TryFrom, path::PathBuf};
|
||||
|
||||
/// A icon definition.
|
||||
pub enum Icon {
|
||||
/// Icon from file path.
|
||||
File(PathBuf),
|
||||
/// Icon from raw bytes.
|
||||
Raw(Vec<u8>),
|
||||
}
|
||||
|
||||
pub struct WindowConfig(pub crate::api::config::WindowConfig);
|
||||
|
||||
pub trait AttributesPrivate: Sized {
|
||||
/// Sets the webview url.
|
||||
fn url(self, url: String) -> Self;
|
||||
}
|
||||
|
||||
/// The webview builder.
|
||||
pub trait Attributes: Sized {
|
||||
/// Expected icon format.
|
||||
type Icon: TryFrom<Icon, Error = crate::Error>;
|
||||
|
||||
/// Initializes a new webview builder.
|
||||
fn new() -> Self;
|
||||
|
||||
/// Sets the init script.
|
||||
fn initialization_script(self, init: &str) -> Self;
|
||||
|
||||
/// The horizontal position of the window's top left corner.
|
||||
fn x(self, x: f64) -> Self;
|
||||
|
||||
/// The vertical position of the window's top left corner.
|
||||
fn y(self, y: f64) -> Self;
|
||||
|
||||
/// Window width.
|
||||
fn width(self, width: f64) -> Self;
|
||||
|
||||
/// Window height.
|
||||
fn height(self, height: f64) -> Self;
|
||||
|
||||
/// Window min width.
|
||||
fn min_width(self, min_width: f64) -> Self;
|
||||
|
||||
/// Window min height.
|
||||
fn min_height(self, min_height: f64) -> Self;
|
||||
|
||||
/// Window max width.
|
||||
fn max_width(self, max_width: f64) -> Self;
|
||||
|
||||
/// Window max height.
|
||||
fn max_height(self, max_height: f64) -> Self;
|
||||
|
||||
/// Whether the window is resizable or not.
|
||||
fn resizable(self, resizable: bool) -> Self;
|
||||
|
||||
/// The title of the window in the title bar.
|
||||
fn title<S: Into<String>>(self, title: S) -> Self;
|
||||
|
||||
/// Whether to start the window in fullscreen or not.
|
||||
fn fullscreen(self, fullscreen: bool) -> Self;
|
||||
|
||||
/// Whether the window should be maximized upon creation.
|
||||
fn maximized(self, maximized: bool) -> Self;
|
||||
|
||||
/// Whether the window should be immediately visible upon creation.
|
||||
fn visible(self, visible: bool) -> Self;
|
||||
|
||||
/// Whether the the window should be transparent. If this is true, writing colors
|
||||
/// with alpha values different than `1.0` will produce a transparent window.
|
||||
fn transparent(self, transparent: bool) -> Self;
|
||||
|
||||
/// Whether the window should have borders and bars.
|
||||
fn decorations(self, decorations: bool) -> Self;
|
||||
|
||||
/// Whether the window should always be on top of other windows.
|
||||
fn always_on_top(self, always_on_top: bool) -> Self;
|
||||
|
||||
/// Sets the window icon.
|
||||
fn icon(self, icon: Self::Icon) -> Self;
|
||||
|
||||
/// Whether the icon was set or not.
|
||||
fn has_icon(&self) -> bool;
|
||||
|
||||
/// User data path for the webview. Actually only supported on Windows.
|
||||
fn user_data_path(self, user_data_path: Option<PathBuf>) -> Self;
|
||||
|
||||
/// The full attributes.
|
||||
fn build(self) -> Self;
|
||||
}
|
||||
|
||||
// TODO: should probably expand the following documentation
|
||||
|
||||
/// Rpc request.
|
||||
pub struct RpcRequest {
|
||||
/// RPC command.
|
||||
pub command: String,
|
||||
/// Params.
|
||||
pub params: Option<JsonValue>,
|
||||
}
|
||||
|
||||
/// Rpc handler.
|
||||
pub type WebviewRpcHandler<M> = Box<dyn Fn(DetachedWindow<M>, RpcRequest) + Send>;
|
||||
|
||||
/// Uses a custom handler to resolve file requests
|
||||
pub struct CustomProtocol {
|
||||
/// Name of the protocol
|
||||
pub name: String,
|
||||
/// Handler for protocol
|
||||
pub handler: Box<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send>,
|
||||
}
|
||||
|
||||
/// The file drop event payload.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FileDropEvent {
|
||||
/// The file(s) have been dragged onto the window, but have not been dropped yet.
|
||||
Hovered(Vec<PathBuf>),
|
||||
/// The file(s) have been dropped onto the window.
|
||||
Dropped(Vec<PathBuf>),
|
||||
/// The file drop was aborted.
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// File drop handler callback
|
||||
/// Return `true` in the callback to block the OS' default behavior of handling a file drop.
|
||||
pub type FileDropHandler<M> = Box<dyn Fn(FileDropEvent, DetachedWindow<M>) -> bool + Send>;
|
364
tauri/src/runtime/window.rs
Normal file
364
tauri/src/runtime/window.rs
Normal file
@ -0,0 +1,364 @@
|
||||
use crate::{
|
||||
api::config::WindowUrl,
|
||||
event::{Event, EventHandler},
|
||||
hooks::{InvokeMessage, InvokePayload, PageLoadPayload},
|
||||
runtime::{
|
||||
sealed::ManagerPrivate,
|
||||
tag::ToJavascript,
|
||||
webview::{CustomProtocol, FileDropHandler, Icon, WebviewRpcHandler},
|
||||
Dispatch, Manager, Params, Runtime, RuntimeOrDispatch,
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
/// A webview window that has yet to be built.
|
||||
pub struct PendingWindow<M: Params> {
|
||||
/// The label that the window will be named.
|
||||
pub label: M::Label,
|
||||
|
||||
/// The url the window will open with.
|
||||
pub url: WindowUrl,
|
||||
|
||||
/// The [`Attributes`] that the webview window be created with.
|
||||
pub attributes: <<M::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes,
|
||||
|
||||
/// How to handle RPC calls on the webview window.
|
||||
pub rpc_handler: Option<WebviewRpcHandler<M>>,
|
||||
|
||||
/// How to handle custom protocols for the webview window.
|
||||
pub custom_protocol: Option<CustomProtocol>,
|
||||
|
||||
/// How to handle a file dropping onto the webview window.
|
||||
pub file_drop_handler: Option<FileDropHandler<M>>,
|
||||
}
|
||||
|
||||
impl<M: Params> PendingWindow<M> {
|
||||
pub fn new(
|
||||
attributes: impl Into<<<M::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes>,
|
||||
label: M::Label,
|
||||
url: WindowUrl,
|
||||
) -> Self {
|
||||
Self {
|
||||
attributes: attributes.into(),
|
||||
label,
|
||||
url,
|
||||
rpc_handler: None,
|
||||
custom_protocol: None,
|
||||
file_drop_handler: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A webview window that is not yet managed by Tauri.
|
||||
pub struct DetachedWindow<M: Params> {
|
||||
pub label: M::Label,
|
||||
pub dispatcher: <M::Runtime as Runtime>::Dispatcher,
|
||||
}
|
||||
|
||||
impl<M: Params> Clone for DetachedWindow<M> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
label: self.label.clone(),
|
||||
dispatcher: self.dispatcher.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Params> Hash for DetachedWindow<M> {
|
||||
/// Only use the [`DetachedWindow`]'s label to represent its hash.
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.label.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Params> Eq for DetachedWindow<M> {}
|
||||
impl<M: Params> PartialEq for DetachedWindow<M> {
|
||||
/// Only use the [`DetachedWindow`]'s label to compare equality.
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.label.eq(&other.label)
|
||||
}
|
||||
}
|
||||
|
||||
/// A webview window managed by Tarui.
|
||||
///
|
||||
/// TODO: expand these docs since this is a pretty important type
|
||||
pub struct Window<M: Params> {
|
||||
/// The webview window created by the runtime.
|
||||
window: DetachedWindow<M>,
|
||||
|
||||
/// The manager to associate this webview window with.
|
||||
manager: M,
|
||||
}
|
||||
|
||||
impl<M: Params> Clone for Window<M> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
window: self.window.clone(),
|
||||
manager: self.manager.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Params> Hash for Window<M> {
|
||||
/// Only use the [`Window`]'s label to represent its hash.
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.window.label.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Params> Eq for Window<M> {}
|
||||
impl<M: Params> PartialEq for Window<M> {
|
||||
/// Only use the [`Window`]'s label to compare equality.
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.window.label.eq(&other.window.label)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Params> Manager<M> for Window<M> {}
|
||||
impl<M: Params> ManagerPrivate<M> for Window<M> {
|
||||
fn manager(&self) -> &M {
|
||||
&self.manager
|
||||
}
|
||||
|
||||
fn runtime(&mut self) -> RuntimeOrDispatch<'_, M> {
|
||||
RuntimeOrDispatch::Dispatch(self.dispatcher())
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Params> Window<M> {
|
||||
/// Create a new window that is attached to the manager.
|
||||
pub(crate) fn new(manager: M, window: DetachedWindow<M>) -> Self {
|
||||
Self { manager, window }
|
||||
}
|
||||
|
||||
/// The current window's dispatcher.
|
||||
pub(crate) fn dispatcher(&self) -> <M::Runtime as Runtime>::Dispatcher {
|
||||
self.window.dispatcher.clone()
|
||||
}
|
||||
|
||||
/// How to handle this window receiving an [`InvokeMessage`].
|
||||
pub(crate) fn on_message(self, command: String, payload: InvokePayload) -> crate::Result<()> {
|
||||
let manager = self.manager.clone();
|
||||
if &command == "__initialized" {
|
||||
let payload: PageLoadPayload = serde_json::from_value(payload.inner)?;
|
||||
manager.run_on_page_load(self, payload);
|
||||
} else {
|
||||
let message = InvokeMessage::new(self, command.to_string(), payload);
|
||||
if let Some(module) = &message.payload.tauri_module {
|
||||
let module = module.to_string();
|
||||
crate::endpoints::handle(module, message, manager.config());
|
||||
} else if command.starts_with("plugin:") {
|
||||
manager.extend_api(command, message);
|
||||
} else {
|
||||
manager.run_invoke_handler(message);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The label of this window.
|
||||
pub fn label(&self) -> &M::Label {
|
||||
&self.window.label
|
||||
}
|
||||
|
||||
pub(crate) fn emit_internal<E: ToJavascript, S: Serialize>(
|
||||
&self,
|
||||
event: E,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
let js_payload = match payload {
|
||||
Some(payload_value) => serde_json::to_value(payload_value)?,
|
||||
None => JsonValue::Null,
|
||||
};
|
||||
|
||||
self.eval(&format!(
|
||||
"window['{}']({{event: {}, payload: {}}}, '{}')",
|
||||
self.manager.event_emit_function_name(),
|
||||
event.to_javascript()?,
|
||||
js_payload,
|
||||
crate::salt::generate()
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emits an event to the current window.
|
||||
pub fn emit<S: Serialize>(&self, event: &M::Event, payload: Option<S>) -> crate::Result<()> {
|
||||
self.emit_internal(event.clone(), payload)
|
||||
}
|
||||
|
||||
pub(crate) fn emit_others_internal<S: Serialize + Clone>(
|
||||
&self,
|
||||
event: String,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
.manager
|
||||
.emit_filter_internal(event, payload, |w| w != self)
|
||||
}
|
||||
|
||||
/// Emits an event on all windows except this one.
|
||||
pub fn emit_others<S: Serialize + Clone>(
|
||||
&self,
|
||||
event: M::Event,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
self.manager.emit_filter(event, payload, |w| w != self)
|
||||
}
|
||||
|
||||
/// Listen to an event on this window.
|
||||
pub fn listen<F>(&self, event: M::Event, handler: F) -> EventHandler
|
||||
where
|
||||
F: Fn(Event) + Send + 'static,
|
||||
{
|
||||
let label = self.window.label.clone();
|
||||
self.manager.listen(event, Some(label), handler)
|
||||
}
|
||||
|
||||
/// Listen to a an event on this window a single time.
|
||||
pub fn once<F>(&self, event: M::Event, handler: F)
|
||||
where
|
||||
F: Fn(Event) + Send + 'static,
|
||||
{
|
||||
let label = self.window.label.clone();
|
||||
self.manager.once(event, Some(label), handler)
|
||||
}
|
||||
|
||||
/// Triggers an event on this window.
|
||||
pub(crate) fn trigger(&self, event: M::Event, data: Option<String>) {
|
||||
let label = self.window.label.clone();
|
||||
self.manager.trigger(event, Some(label), data)
|
||||
}
|
||||
|
||||
/// Evaluates JavaScript on this window.
|
||||
pub fn eval(&self, js: &str) -> crate::Result<()> {
|
||||
self.window.dispatcher.eval_script(js)
|
||||
}
|
||||
|
||||
/// Determines if this window should be resizable.
|
||||
pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_resizable(resizable)
|
||||
}
|
||||
|
||||
/// Set this window's title.
|
||||
pub fn set_title(&self, title: &str) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_title(title.to_string())
|
||||
}
|
||||
|
||||
/// Maximizes this window.
|
||||
pub fn maximize(&self) -> crate::Result<()> {
|
||||
self.window.dispatcher.maximize()
|
||||
}
|
||||
|
||||
/// Un-maximizes this window.
|
||||
pub fn unmaximize(&self) -> crate::Result<()> {
|
||||
self.window.dispatcher.unmaximize()
|
||||
}
|
||||
|
||||
/// Minimizes this window.
|
||||
pub fn minimize(&self) -> crate::Result<()> {
|
||||
self.window.dispatcher.minimize()
|
||||
}
|
||||
|
||||
/// Un-minimizes this window.
|
||||
pub fn unminimize(&self) -> crate::Result<()> {
|
||||
self.window.dispatcher.unminimize()
|
||||
}
|
||||
|
||||
/// Show this window.
|
||||
pub fn show(&self) -> crate::Result<()> {
|
||||
self.window.dispatcher.show()
|
||||
}
|
||||
|
||||
/// Hide this window.
|
||||
pub fn hide(&self) -> crate::Result<()> {
|
||||
self.window.dispatcher.hide()
|
||||
}
|
||||
|
||||
/// Closes this window.
|
||||
pub fn close(&self) -> crate::Result<()> {
|
||||
self.window.dispatcher.close()
|
||||
}
|
||||
|
||||
/// Determines if this window should be [decorated].
|
||||
///
|
||||
/// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration
|
||||
pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_decorations(decorations)
|
||||
}
|
||||
|
||||
/// Determines if this window should always be on top of other windows.
|
||||
pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_always_on_top(always_on_top)
|
||||
}
|
||||
|
||||
/// Sets this window's width.
|
||||
pub fn set_width(&self, width: impl Into<f64>) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_width(width.into())
|
||||
}
|
||||
|
||||
/// Sets this window's height.
|
||||
pub fn set_height(&self, height: impl Into<f64>) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_height(height.into())
|
||||
}
|
||||
|
||||
/// Resizes this window.
|
||||
pub fn resize(&self, width: impl Into<f64>, height: impl Into<f64>) -> crate::Result<()> {
|
||||
self.window.dispatcher.resize(width.into(), height.into())
|
||||
}
|
||||
|
||||
/// Sets this window's minimum size.
|
||||
pub fn set_min_size(
|
||||
&self,
|
||||
min_width: impl Into<f64>,
|
||||
min_height: impl Into<f64>,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
.window
|
||||
.dispatcher
|
||||
.set_min_size(min_width.into(), min_height.into())
|
||||
}
|
||||
|
||||
/// Sets this window's maximum size.
|
||||
pub fn set_max_size(
|
||||
&self,
|
||||
max_width: impl Into<f64>,
|
||||
max_height: impl Into<f64>,
|
||||
) -> crate::Result<()> {
|
||||
self
|
||||
.window
|
||||
.dispatcher
|
||||
.set_max_size(max_width.into(), max_height.into())
|
||||
}
|
||||
|
||||
/// Sets this window's x position.
|
||||
pub fn set_x(&self, x: impl Into<f64>) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_x(x.into())
|
||||
}
|
||||
|
||||
/// Sets this window's y position.
|
||||
pub fn set_y(&self, y: impl Into<f64>) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_y(y.into())
|
||||
}
|
||||
|
||||
/// Sets this window's position.
|
||||
pub fn set_position(&self, x: impl Into<f64>, y: impl Into<f64>) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_position(x.into(), y.into())
|
||||
}
|
||||
|
||||
/// Determines if this window should be fullscreen.
|
||||
pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_fullscreen(fullscreen)
|
||||
}
|
||||
|
||||
/// Sets this window' icon.
|
||||
pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
|
||||
self.window.dispatcher.set_icon(icon.try_into()?)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user