diff --git a/.changes/config.json b/.changes/config.json index da12d7f3c4c..aa5da41d39c 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -214,6 +214,14 @@ "path": "./core/tauri-utils", "manager": "rust" }, + "tauri-runtime": { + "path": "./core/tauri-runtime", + "manager": "rust", + "dependencies": [ + "tauri-utils" + ], + "postversion": "node ../../.scripts/sync-prerelease.js ${ release.type }" + }, "tauri-codegen": { "path": "./core/tauri-codegen", "manager": "rust", @@ -241,7 +249,8 @@ "manager": "rust", "dependencies": [ "tauri-macros", - "tauri-utils" + "tauri-utils", + "tauri-runtime" ], "postversion": "node ../../.scripts/sync-cli-metadata.js ${ pkg.pkg } ${ release.type }" }, @@ -276,4 +285,4 @@ "manager": "javascript" } } -} \ No newline at end of file +} diff --git a/.changes/runtime-crate.md b/.changes/runtime-crate.md new file mode 100644 index 00000000000..035090724c9 --- /dev/null +++ b/.changes/runtime-crate.md @@ -0,0 +1,6 @@ +--- +"tauri-runtime": patch +"tauri": patch +--- + +`tauri-runtime` crate initial release. diff --git a/.scripts/sync-prerelease.js b/.scripts/sync-prerelease.js new file mode 100644 index 00000000000..faf9bfe432c --- /dev/null +++ b/.scripts/sync-prerelease.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/* +This script is solely intended to be run as part of the `covector version` step to +keep the `tauri-release` crate version without the `beta` or `beta-rc` suffix. +*/ + +const { readFileSync, writeFileSync } = require("fs") + +const runtimeManifestPath = '../../core/tauri-runtime/Cargo.toml' +const dependencyManifestPaths = ['../../core/tauri/Cargo.toml'] +const changelogPath = '../../core/tauri-runtime/CHANGELOG.md' + +const bump = process.argv[2] + +let runtimeManifest = readFileSync(runtimeManifestPath, "utf-8") +runtimeManifest = runtimeManifest.replace(/version = "(\d+\.\d+\.\d+)-[^0-9\.]+\.0"/, 'version = "$1"') +writeFileSync(runtimeManifestPath, runtimeManifest) + +let changelog = readFileSync(changelogPath, "utf-8") +changelog = changelog.replace(/(\d+\.\d+\.\d+)-[^0-9\.]+\.0/, '$1') +writeFileSync(changelogPath, changelog) + +for (const dependencyManifestPath of dependencyManifestPaths) { + let dependencyManifest = readFileSync(dependencyManifestPath, "utf-8") + dependencyManifest = dependencyManifest.replace(/tauri-runtime = { version = "(\d+\.\d+\.\d+)-[^0-9\.]+\.0"/, 'tauri-runtime = { version = "$1"') + writeFileSync(dependencyManifestPath, dependencyManifest) +} diff --git a/Cargo.toml b/Cargo.toml index 2496b025946..017b83bcdaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ # core "core/tauri", + "core/tauri-runtime", "core/tauri-macros", "core/tauri-utils", "core/tauri-build", diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml new file mode 100644 index 00000000000..d6c0550fb4a --- /dev/null +++ b/core/tauri-runtime/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tauri-runtime" +version = "0.0.0" +authors = [ "Tauri Programme within The Commons Conservancy" ] +categories = [ "gui", "web-programming" ] +license = "Apache-2.0 OR MIT" +homepage = "https://tauri.studio" +repository = "https://github.com/tauri-apps/tauri" +description = "Runtime for Tauri applications" +edition = "2018" + +[dependencies] +serde = { version = "1.0", features = [ "derive" ] } +serde_json = "1.0" +thiserror = "1.0" +uuid = { version = "0.8.2", features = [ "v4" ] } +tauri-utils = { version = "1.0.0-beta-rc.1", path = "../tauri-utils" } +wry = { git = "https://github.com/tauri-apps/wry", rev = "6bc97aff525644b83a3a00537316c46d7afb985b" } +image = "0.23" diff --git a/core/tauri/src/runtime/flavors/mod.rs b/core/tauri-runtime/src/flavors/mod.rs similarity index 100% rename from core/tauri/src/runtime/flavors/mod.rs rename to core/tauri-runtime/src/flavors/mod.rs diff --git a/core/tauri/src/runtime/flavors/wry.rs b/core/tauri-runtime/src/flavors/wry.rs similarity index 98% rename from core/tauri/src/runtime/flavors/wry.rs rename to core/tauri-runtime/src/flavors/wry.rs index 2252a5a5680..f10e7f463ed 100644 --- a/core/tauri/src/runtime/flavors/wry.rs +++ b/core/tauri-runtime/src/flavors/wry.rs @@ -5,23 +5,19 @@ //! The [`wry`] Tauri [`Runtime`]. use crate::{ - api::config::WindowConfig, - runtime::{ - menu::{CustomMenuItem, Menu, MenuId, MenuItem, SystemTrayMenuItem}, - webview::{ - FileDropEvent, FileDropHandler, RpcRequest, WebviewRpcHandler, WindowBuilder, - WindowBuilderBase, - }, - window::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - DetachedWindow, MenuEvent, PendingWindow, WindowEvent, - }, - Dispatch, Monitor, Params, Runtime, SystemTrayEvent, + menu::{CustomMenuItem, Menu, MenuId, MenuItem, SystemTrayMenuItem}, + webview::{ + FileDropEvent, FileDropHandler, RpcRequest, WebviewRpcHandler, WindowBuilder, WindowBuilderBase, + }, + window::{ + dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, + DetachedWindow, MenuEvent, PendingWindow, WindowEvent, }, - Icon, + Dispatch, Icon, Monitor, Params, Runtime, SystemTrayEvent, }; use image::{GenericImageView, Pixel}; +use tauri_utils::config::WindowConfig; use uuid::Uuid; use wry::{ application::{ @@ -1043,7 +1039,7 @@ fn create_webview>( ) -> crate::Result { let PendingWindow { webview_attributes, - window_attributes, + window_builder, rpc_handler, file_drop_handler, label, @@ -1051,7 +1047,7 @@ fn create_webview>( .. } = pending; - let window = window_attributes.build(event_loop).unwrap(); + let window = window_builder.build(event_loop).unwrap(); let mut webview_builder = WebViewBuilder::new(window) .map_err(|e| crate::Error::CreateWebview(Box::new(e)))? .with_url(&url) diff --git a/core/tauri/src/runtime/mod.rs b/core/tauri-runtime/src/lib.rs similarity index 76% rename from core/tauri/src/runtime/mod.rs rename to core/tauri-runtime/src/lib.rs index 8a06c31b563..0f0e19bddfe 100644 --- a/core/tauri/src/runtime/mod.rs +++ b/core/tauri-runtime/src/lib.rs @@ -4,15 +4,12 @@ //! Internal runtime between Tauri and the underlying webview runtime. -use crate::{ - runtime::window::{DetachedWindow, PendingWindow}, - Icon, Params, WindowBuilder, -}; +use std::path::PathBuf; + +use tauri_utils::assets::Assets; use uuid::Uuid; -pub(crate) mod app; pub mod flavors; -pub(crate) mod manager; /// Create window and system tray menus. pub mod menu; /// Types useful for interacting with a user's monitors. @@ -23,14 +20,78 @@ pub mod window; use menu::{MenuId, SystemTrayMenuItem}; use monitor::Monitor; +use tag::Tag; +use webview::WindowBuilder; use window::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - MenuEvent, WindowEvent, + DetachedWindow, MenuEvent, PendingWindow, WindowEvent, }; +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + /// Failed to create webview. + #[error("failed to create webview: {0}")] + CreateWebview(Box), + /// Failed to create window. + #[error("failed to create window")] + CreateWindow, + /// Failed to send message to webview. + #[error("failed to send message to the webview")] + FailedToSendMessage, + /// Failed to serialize/deserialize. + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + /// Encountered an error creating the app system tray, + #[error("error encountered during tray setup: {0}")] + SystemTray(Box), + /// Failed to load window icon. + #[error("invalid icon: {0}")] + InvalidIcon(Box), +} + +/// Result type. +pub type Result = std::result::Result; + +#[doc(hidden)] +pub mod private { + pub trait ParamsBase {} +} + +/// Types associated with the running Tauri application. +pub trait Params: private::ParamsBase + 'static { + /// 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; + + /// The type used to determine window menu ids. + type MenuId: MenuId; + + /// The type used to determine system tray menu ids. + type SystemTrayMenuId: MenuId; + + /// Assets that Tauri should serve from itself. + type Assets: Assets; + + /// The underlying webview runtime used by the Tauri application. + type Runtime: Runtime; +} + +/// A icon definition. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum Icon { + /// Icon from file path. + File(PathBuf), + /// Icon from raw bytes. + Raw(Vec), +} + /// A system tray event. pub struct SystemTrayEvent { - pub(crate) menu_item_id: u32, + pub menu_item_id: u32, } /// The webview runtime interface. diff --git a/core/tauri/src/runtime/menu.rs b/core/tauri-runtime/src/menu.rs similarity index 93% rename from core/tauri/src/runtime/menu.rs rename to core/tauri-runtime/src/menu.rs index b4eace56eac..249cb5bfbcb 100644 --- a/core/tauri/src/runtime/menu.rs +++ b/core/tauri-runtime/src/menu.rs @@ -8,16 +8,18 @@ use std::{ hash::{Hash, Hasher}, }; +use serde::Serialize; + /// A type that can be derived into a menu id. -pub trait MenuId: Hash + Eq + Debug + Clone + Send + Sync + 'static {} +pub trait MenuId: Serialize + Hash + Eq + Debug + Clone + Send + Sync + 'static {} -impl MenuId for T where T: Hash + Eq + Debug + Clone + Send + Sync + 'static {} +impl MenuId for T where T: Serialize + Hash + Eq + Debug + Clone + Send + Sync + 'static {} /// A window menu. #[derive(Debug, Clone)] pub struct Menu { - pub(crate) title: String, - pub(crate) items: Vec>, + pub title: String, + pub items: Vec>, } impl Menu { @@ -29,11 +31,12 @@ impl Menu { } } } + /// A custom menu item. #[derive(Debug, Clone)] pub struct CustomMenuItem { - pub(crate) id: I, - pub(crate) name: String, + pub id: I, + pub name: String, } impl CustomMenuItem { @@ -43,7 +46,8 @@ impl CustomMenuItem { Self { id, name: title } } - pub(crate) fn id_value(&self) -> u32 { + #[doc(hidden)] + pub fn id_value(&self) -> u32 { let mut s = DefaultHasher::new(); self.id.hash(&mut s); s.finish() as u32 diff --git a/core/tauri/src/runtime/monitor.rs b/core/tauri-runtime/src/monitor.rs similarity index 100% rename from core/tauri/src/runtime/monitor.rs rename to core/tauri-runtime/src/monitor.rs diff --git a/core/tauri/src/runtime/tag.rs b/core/tauri-runtime/src/tag.rs similarity index 96% rename from core/tauri/src/runtime/tag.rs rename to core/tauri-runtime/src/tag.rs index 0359828bd0c..b55a2118bc3 100644 --- a/core/tauri/src/runtime/tag.rs +++ b/core/tauri-runtime/src/tag.rs @@ -71,7 +71,7 @@ use std::{ /// let event: Event = "tauri://file-drop".parse().unwrap(); /// /// // show that this event type can be represented as a Tag, a requirement for using it in Tauri. -/// fn is_file_drop(tag: impl tauri::runtime::tag::Tag) { +/// fn is_file_drop(tag: impl tauri_runtime::tag::Tag) { /// assert_eq!("tauri://file-drop", tag.to_string()); /// } /// @@ -113,7 +113,7 @@ where /// /// 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 ToJsString { +pub trait ToJsString { fn to_js_string(&self) -> crate::Result; } @@ -125,7 +125,7 @@ impl ToJsString for D { } /// Turn any collection of [`Tag`]s into a JavaScript array of strings. -pub(crate) fn tags_to_javascript_array(tags: &[impl Tag]) -> crate::Result { +pub fn tags_to_javascript_array(tags: &[impl Tag]) -> crate::Result { let tags: Vec = tags.iter().map(ToString::to_string).collect(); Ok(serde_json::to_string(&tags)?) } diff --git a/core/tauri/src/runtime/webview.rs b/core/tauri-runtime/src/webview.rs similarity index 85% rename from core/tauri/src/runtime/webview.rs rename to core/tauri-runtime/src/webview.rs index 7d9c12760d6..ba85507b727 100644 --- a/core/tauri/src/runtime/webview.rs +++ b/core/tauri-runtime/src/webview.rs @@ -4,26 +4,27 @@ //! Items specific to the [`Runtime`](crate::runtime::Runtime)'s webview. -use crate::runtime::Icon; use crate::{ - api::config::{WindowConfig, WindowUrl}, - runtime::{ - menu::{Menu, MenuId}, - window::DetachedWindow, - }, + menu::{Menu, MenuId}, + window::DetachedWindow, + Icon, }; + use serde::Deserialize; use serde_json::Value as JsonValue; +use tauri_utils::config::{WindowConfig, WindowUrl}; + use std::{collections::HashMap, path::PathBuf}; -type UriSchemeProtocol = dyn Fn(&str) -> crate::Result> + Send + Sync + 'static; +type UriSchemeProtocol = + dyn Fn(&str) -> Result, Box> + Send + Sync + 'static; /// The attributes used to create an webview. pub struct WebviewAttributes { - pub(crate) url: WindowUrl, - pub(crate) initialization_scripts: Vec, - pub(crate) data_directory: Option, - pub(crate) uri_scheme_protocols: HashMap>, + pub url: WindowUrl, + pub initialization_scripts: Vec, + pub data_directory: Option, + pub uri_scheme_protocols: HashMap>, } impl WebviewAttributes { @@ -65,7 +66,7 @@ impl WebviewAttributes { /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`. pub fn register_uri_scheme_protocol< N: Into, - H: Fn(&str) -> crate::Result> + Send + Sync + 'static, + H: Fn(&str) -> Result, Box> + Send + Sync + 'static, >( mut self, uri_scheme: N, @@ -146,7 +147,6 @@ pub trait WindowBuilder: WindowBuilderBase { } /// Rpc request. -#[non_exhaustive] pub struct RpcRequest { /// RPC command. pub command: String, @@ -157,7 +157,8 @@ pub struct RpcRequest { /// Uses a custom URI scheme handler to resolve file requests pub struct CustomProtocol { /// Handler for protocol - pub protocol: Box crate::Result> + Send + Sync>, + #[allow(clippy::type_complexity)] + pub protocol: Box Result, Box> + Send + Sync>, } /// The file drop event payload. @@ -173,18 +174,18 @@ pub enum FileDropEvent { } /// Rpc handler. -pub(crate) type WebviewRpcHandler = Box, RpcRequest) + Send>; +pub type WebviewRpcHandler = Box, RpcRequest) + Send>; /// File drop handler callback /// Return `true` in the callback to block the OS' default behavior of handling a file drop. -pub(crate) type FileDropHandler = Box) -> bool + Send>; +pub type FileDropHandler = Box) -> bool + Send>; #[derive(Deserialize)] -pub(crate) struct InvokePayload { +pub struct InvokePayload { #[serde(rename = "__tauriModule")] - pub(crate) tauri_module: Option, - pub(crate) callback: String, - pub(crate) error: String, + pub tauri_module: Option, + pub callback: String, + pub error: String, #[serde(flatten)] - pub(crate) inner: JsonValue, + pub inner: JsonValue, } diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs new file mode 100644 index 00000000000..0feb787373a --- /dev/null +++ b/core/tauri-runtime/src/window.rs @@ -0,0 +1,147 @@ +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! A layer between raw [`Runtime`] webview windows and Tauri. + +use crate::{ + webview::{FileDropHandler, WebviewAttributes, WebviewRpcHandler}, + Dispatch, Params, Runtime, WindowBuilder, +}; +use serde::Serialize; +use tauri_utils::config::WindowConfig; + +use std::hash::{Hash, Hasher}; + +/// UI scaling utilities. +pub mod dpi; + +/// An event from a window. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum WindowEvent { + /// The size of the window has changed. Contains the client area's new dimensions. + Resized(dpi::PhysicalSize), + /// The position of the window has changed. Contains the window's new position. + Moved(dpi::PhysicalPosition), + /// The window has been requested to close. + CloseRequested, + /// The window has been destroyed. + Destroyed, + /// The window gained or lost focus. + /// + /// The parameter is true if the window has gained focus, and false if it has lost focus. + Focused(bool), + ///The window's scale factor has changed. + /// + /// The following user actions can cause DPI changes: + /// + /// - Changing the display's resolution. + /// - Changing the display's scale factor (e.g. in Control Panel on Windows). + /// - Moving the window to a display with a different scale factor. + #[non_exhaustive] + ScaleFactorChanged { + /// The new scale factor. + scale_factor: f64, + /// The window inner size. + new_inner_size: dpi::PhysicalSize, + }, +} + +/// A menu event. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MenuEvent { + pub menu_item_id: u32, +} + +/// A webview window that has yet to be built. +pub struct PendingWindow { + /// The label that the window will be named. + pub label: M::Label, + + /// The [`WindowBuilder`] that the window will be created with. + pub window_builder: <::Dispatcher as Dispatch>::WindowBuilder, + + /// The [`WebviewAttributes`] that the webview will be created with. + pub webview_attributes: WebviewAttributes, + + /// How to handle RPC calls on the webview window. + pub rpc_handler: Option>, + + /// How to handle a file dropping onto the webview window. + pub file_drop_handler: Option>, + + /// The resolved URL to load on the webview. + pub url: String, +} + +impl PendingWindow { + /// Create a new [`PendingWindow`] with a label and starting url. + pub fn new( + window_builder: <::Dispatcher as Dispatch>::WindowBuilder, + webview_attributes: WebviewAttributes, + label: M::Label, + ) -> Self { + Self { + window_builder, + webview_attributes, + label, + rpc_handler: None, + file_drop_handler: None, + url: "tauri://localhost".to_string(), + } + } + + /// Create a new [`PendingWindow`] from a [`WindowConfig`] with a label and starting url. + pub fn with_config( + window_config: WindowConfig, + webview_attributes: WebviewAttributes, + label: M::Label, + ) -> Self { + Self { + window_builder: + <<::Dispatcher as Dispatch>::WindowBuilder>::with_config( + window_config, + ), + webview_attributes, + label, + rpc_handler: None, + file_drop_handler: None, + url: "tauri://localhost".to_string(), + } + } +} + +/// A webview window that is not yet managed by Tauri. +pub struct DetachedWindow { + /// Name of the window + pub label: M::Label, + + /// The [`Dispatch`](crate::runtime::Dispatch) associated with the window. + pub dispatcher: ::Dispatcher, +} + +impl Clone for DetachedWindow { + fn clone(&self) -> Self { + Self { + label: self.label.clone(), + dispatcher: self.dispatcher.clone(), + } + } +} + +impl Hash for DetachedWindow { + /// Only use the [`DetachedWindow`]'s label to represent its hash. + fn hash(&self, state: &mut H) { + self.label.hash(state) + } +} + +impl Eq for DetachedWindow {} +impl PartialEq for DetachedWindow { + /// Only use the [`DetachedWindow`]'s label to compare equality. + fn eq(&self, other: &Self) -> bool { + self.label.eq(&other.label) + } +} diff --git a/core/tauri/src/runtime/window/dpi.rs b/core/tauri-runtime/src/window/dpi.rs similarity index 100% rename from core/tauri/src/runtime/window/dpi.rs rename to core/tauri-runtime/src/window/dpi.rs diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 6036330542b..22ce3b1b6bb 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -22,9 +22,9 @@ futures = "0.3" uuid = { version = "0.8.2", features = [ "v4" ] } thiserror = "1.0.24" once_cell = "1.7.2" +tauri-runtime = { version = "0.0.0", path = "../tauri-runtime" } tauri-macros = { version = "1.0.0-beta-rc.1", path = "../tauri-macros" } tauri-utils = { version = "1.0.0-beta-rc.1", path = "../tauri-utils" } -wry = { git = "https://github.com/tauri-apps/wry", rev = "6bc97aff525644b83a3a00537316c46d7afb985b" } rand = "0.8" reqwest = { version = "0.11", features = [ "json", "multipart" ] } tempfile = "3" diff --git a/core/tauri/src/runtime/app.rs b/core/tauri/src/app.rs similarity index 97% rename from core/tauri/src/runtime/app.rs rename to core/tauri/src/app.rs index 10961b01c1c..e9e3a612629 100644 --- a/core/tauri/src/runtime/app.rs +++ b/core/tauri/src/app.rs @@ -3,20 +3,21 @@ // SPDX-License-Identifier: MIT use crate::{ - api::{assets::Assets, config::WindowUrl}, + api::assets::Assets, + api::config::WindowUrl, hooks::{InvokeHandler, OnPageLoad, PageLoadPayload, SetupHook}, + manager::{Args, WindowManager}, plugin::{Plugin, PluginStore}, runtime::{ flavors::wry::Wry, - manager::{Args, WindowManager}, menu::{Menu, MenuId, SystemTrayMenuItem}, tag::Tag, webview::{CustomProtocol, WebviewAttributes, WindowBuilder}, window::PendingWindow, - Dispatch, Runtime, + Dispatch, Params, Runtime, }, sealed::{ManagerBase, RuntimeOrDispatch}, - Context, Invoke, Manager, Params, StateManager, Window, + Context, Invoke, Manager, StateManager, Window, }; use std::{collections::HashMap, sync::Arc}; @@ -97,13 +98,13 @@ impl App

{ WebviewAttributes, ), { - let (window_attributes, webview_attributes) = setup( + let (window_builder, webview_attributes) = setup( <::Dispatcher as Dispatch>::WindowBuilder::new(), WebviewAttributes::new(url), ); self.create_new_window( RuntimeOrDispatch::Runtime(&self.runtime), - PendingWindow::new(window_attributes, webview_attributes, label), + PendingWindow::new(window_builder, webview_attributes, label), )?; Ok(()) } @@ -337,12 +338,12 @@ where WebviewAttributes, ), { - let (window_attributes, webview_attributes) = setup( + let (window_builder, webview_attributes) = setup( ::WindowBuilder::new(), WebviewAttributes::new(url), ); self.pending_windows.push(PendingWindow::new( - window_attributes, + window_builder, webview_attributes, label, )); @@ -394,7 +395,7 @@ where /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`. pub fn register_global_uri_scheme_protocol< N: Into, - H: Fn(&str) -> crate::Result> + Send + Sync + 'static, + H: Fn(&str) -> Result, Box> + Send + Sync + 'static, >( mut self, uri_scheme: N, diff --git a/core/tauri/src/endpoints/window.rs b/core/tauri/src/endpoints/window.rs index 01586735879..feee5ab7d72 100644 --- a/core/tauri/src/endpoints/window.rs +++ b/core/tauri/src/endpoints/window.rs @@ -12,7 +12,7 @@ use crate::{ }; use serde::Deserialize; -use crate::Icon; +use crate::runtime::Icon; use std::path::PathBuf; #[derive(Deserialize)] @@ -115,7 +115,7 @@ impl Cmd { ) })? .emit_others( - &crate::runtime::manager::tauri_event::("tauri://window-created"), + &crate::manager::tauri_event::("tauri://window-created"), Some(WindowCreatedEvent { label: label.to_string(), }), diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index 1facc4fde61..bd2f242c152 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -8,6 +8,9 @@ use std::path::PathBuf; #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { + /// Runtime error. + #[error("runtime error: {0}")] + Runtime(#[from] tauri_runtime::Error), /// Failed to create webview. #[error("failed to create webview: {0}")] CreateWebview(Box), diff --git a/core/tauri/src/hooks.rs b/core/tauri/src/hooks.rs index 215af567d1c..9e16e53e3f3 100644 --- a/core/tauri/src/hooks.rs +++ b/core/tauri/src/hooks.rs @@ -4,7 +4,7 @@ use crate::{ api::rpc::{format_callback, format_callback_result}, - runtime::app::App, + app::App, Params, StateManager, Window, }; use serde::{Deserialize, Serialize}; diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 1a167ebb29b..834d21be2f4 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -16,6 +16,7 @@ pub use tauri_macros::{command, generate_handler}; /// Core API. pub mod api; +pub(crate) mod app; /// Async runtime. pub mod async_runtime; pub mod command; @@ -24,8 +25,11 @@ mod endpoints; mod error; mod event; mod hooks; +mod manager; pub mod plugin; -pub mod runtime; +/// Tauri window. +pub mod window; +use tauri_runtime as runtime; /// The Tauri-specific settings for your runtime e.g. notification permission status. pub mod settings; mod state; @@ -40,38 +44,34 @@ pub type SyncTask = Box; use crate::{ event::{Event, EventHandler}, - runtime::{ - tag::{Tag, TagRef}, - window::PendingWindow, - Runtime, - }, + runtime::window::PendingWindow, }; use serde::Serialize; use std::{borrow::Borrow, collections::HashMap, path::PathBuf, sync::Arc}; // Export types likely to be used by the application. pub use { - self::api::{ - assets::Assets, - config::{Config, WindowUrl}, - }, + self::api::assets::Assets, + self::api::config::{Config, WindowUrl}, + self::app::{App, Builder, SystemTrayEvent, WindowMenuEvent}, self::hooks::{ Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad, PageLoadPayload, SetupHook, }, - self::runtime::app::{App, Builder, SystemTrayEvent, WindowMenuEvent}, - self::runtime::flavors::wry::Wry, - self::runtime::menu::{CustomMenuItem, Menu, MenuId, MenuItem, SystemTrayMenuItem}, - self::runtime::monitor::Monitor, - self::runtime::webview::{WebviewAttributes, WindowBuilder}, - self::runtime::window::{ - export::{ + self::runtime::{ + flavors::wry::Wry, + menu::{CustomMenuItem, Menu, MenuId, MenuItem, SystemTrayMenuItem}, + monitor::Monitor, + tag::{Tag, TagRef}, + webview::{WebviewAttributes, WindowBuilder}, + window::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size}, - Window, + WindowEvent, }, - WindowEvent, + Params, }, self::state::{State, StateManager}, + self::window::{MenuEvent, Window}, tauri_utils::platform, }; @@ -109,16 +109,6 @@ macro_rules! tauri_build_context { }; } -/// A icon definition. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum Icon { - /// Icon from file path. - File(PathBuf), - /// Icon from raw bytes. - Raw(Vec), -} - /// User supplied data required inside of a Tauri application. pub struct Context { /// The config the application was prepared with. @@ -142,27 +132,6 @@ pub struct Context { pub package_info: crate::api::PackageInfo, } -/// Types associated with the running Tauri application. -pub trait Params: sealed::ParamsBase { - /// 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; - - /// The type used to determine window menu ids. - type MenuId: MenuId; - - /// The type used to determine system tray menu ids. - type SystemTrayMenuId: MenuId; - - /// Assets that Tauri should serve from itself. - type Assets: Assets; - - /// The underlying webview runtime used by the Tauri application. - type Runtime: Runtime; -} - /// Manages a running application. /// /// TODO: expand these docs @@ -262,13 +231,10 @@ pub trait Manager: sealed::ManagerBase

{ } } -/// Prevent implementation details from leaking out of the [`Manager`] and [`Params`] traits. +/// Prevent implementation details from leaking out of the [`Manager`] trait. pub(crate) mod sealed { - use super::Params; - use crate::runtime::{manager::WindowManager, Runtime}; - - /// No downstream implementations of [`Params`]. - pub trait ParamsBase: 'static {} + use crate::manager::WindowManager; + use tauri_runtime::{Params, Runtime}; /// A running [`Runtime`] or a dispatcher to it. pub enum RuntimeOrDispatch<'r, P: Params> { @@ -294,8 +260,10 @@ pub(crate) mod sealed { let labels = self.manager().labels().into_iter().collect::>(); let pending = self.manager().prepare_window(pending, &labels)?; match runtime { - RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending), - RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending), + RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending).map_err(Into::into), + RuntimeOrDispatch::Dispatch(mut dispatcher) => { + dispatcher.create_window(pending).map_err(Into::into) + } } .map(|window| self.manager().attach_window(window)) } diff --git a/core/tauri/src/runtime/manager.rs b/core/tauri/src/manager.rs similarity index 95% rename from core/tauri/src/runtime/manager.rs rename to core/tauri/src/manager.rs index d75e886db83..6e8add38d31 100644 --- a/core/tauri/src/runtime/manager.rs +++ b/core/tauri/src/manager.rs @@ -9,22 +9,22 @@ use crate::{ path::{resolve_path, BaseDirectory}, PackageInfo, }, + app::{GlobalMenuEventListener, WindowMenuEvent}, event::{Event, EventHandler, Listeners}, hooks::{InvokeHandler, OnPageLoad, PageLoadPayload}, plugin::PluginStore, runtime::{ - app::{GlobalMenuEventListener, WindowMenuEvent}, menu::{Menu, MenuId, MenuItem}, + private::ParamsBase, tag::{tags_to_javascript_array, Tag, TagRef, ToJsString}, webview::{ CustomProtocol, FileDropEvent, FileDropHandler, InvokePayload, WebviewRpcHandler, WindowBuilder, }, - window::{dpi::PhysicalSize, DetachedWindow, MenuEvent, PendingWindow, WindowEvent}, - Icon, Runtime, + window::{dpi::PhysicalSize, DetachedWindow, PendingWindow, WindowEvent}, + Icon, Params, Runtime, }, - sealed::ParamsBase, - App, Context, Invoke, Params, StateManager, Window, + App, Context, Invoke, MenuEvent, StateManager, Window, }; use serde::Serialize; use serde_json::Value as JsonValue; @@ -203,6 +203,11 @@ impl WindowManager

{ self.inner.state.clone() } + /// Get the menu ids mapper. + pub(crate) fn menu_ids(&self) -> HashMap { + self.inner.menu_ids.clone() + } + // setup content for dev-server #[cfg(dev)] fn get_url(&self) -> String { @@ -243,15 +248,15 @@ impl WindowManager

{ current_window_label = label.to_js_string()?, )); - if !pending.window_attributes.has_icon() { + if !pending.window_builder.has_icon() { if let Some(default_window_icon) = &self.inner.default_window_icon { let icon = Icon::Raw(default_window_icon.clone()); - pending.window_attributes = pending.window_attributes.icon(icon)?; + pending.window_builder = pending.window_builder.icon(icon)?; } } - if !pending.window_attributes.has_menu() { - pending.window_attributes = pending.window_attributes.menu(self.inner.menu.clone()); + if !pending.window_builder.has_menu() { + pending.window_builder = pending.window_builder.menu(self.inner.menu.clone()); } for (uri_scheme, protocol) in &self.inner.uri_scheme_protocols { @@ -344,7 +349,7 @@ impl WindowManager

{ Err(e) => { #[cfg(debug_assertions)] eprintln!("{:?}", e); // TODO log::error! - Err(e) + Err(Box::new(e)) } } }), @@ -369,6 +374,7 @@ impl WindowManager

{ &tauri_event::("tauri://file-drop-cancelled"), Some(()), ), + _ => unimplemented!(), }; }); true @@ -394,9 +400,9 @@ impl WindowManager

{ }} {plugin_initialization_script} "#, - core_script = include_str!("../../scripts/core.js"), + core_script = include_str!("../scripts/core.js"), bundle_script = if with_global_tauri { - include_str!("../../scripts/bundle.js") + include_str!("../scripts/bundle.js") } else { "" }, @@ -548,13 +554,12 @@ impl WindowManager

{ }); let window_ = window.clone(); let menu_event_listeners = self.inner.menu_event_listeners.clone(); - let menu_ids = self.inner.menu_ids.clone(); window.on_menu_event(move |event| { - let _ = on_menu_event(&window_, event); + let _ = on_menu_event(&window_, &event); for handler in menu_event_listeners.iter() { handler(WindowMenuEvent { window: window_.clone(), - menu_item_id: menu_ids.get(&event.menu_item_id).unwrap().clone(), + menu_item_id: event.menu_item_id.clone(), }); } }); @@ -727,6 +732,7 @@ fn on_window_event(window: &Window

, event: &WindowEvent) -> crate: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size, + .. } => window.emit( &WINDOW_SCALE_FACTOR_CHANGED_EVENT .parse() @@ -736,6 +742,7 @@ fn on_window_event(window: &Window

, event: &WindowEvent) -> crate: size: new_inner_size.clone(), }), )?, + _ => unimplemented!(), } Ok(()) } @@ -747,11 +754,11 @@ struct ScaleFactorChanged { size: PhysicalSize, } -fn on_menu_event(window: &Window

, event: &MenuEvent) -> crate::Result<()> { +fn on_menu_event(window: &Window

, event: &MenuEvent) -> crate::Result<()> { window.emit( &MENU_EVENT .parse() .unwrap_or_else(|_| panic!("unhandled event")), - Some(event), + Some(event.menu_item_id.clone()), ) } diff --git a/core/tauri/src/runtime/window.rs b/core/tauri/src/runtime/window.rs deleted file mode 100644 index c14d008a6ee..00000000000 --- a/core/tauri/src/runtime/window.rs +++ /dev/null @@ -1,552 +0,0 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -//! A layer between raw [`Runtime`] webview windows and Tauri. - -use crate::{ - api::config::{WindowConfig, WindowUrl}, - event::{Event, EventHandler}, - hooks::{InvokeMessage, InvokeResolver, PageLoadPayload}, - runtime::{ - tag::ToJsString, - webview::{FileDropHandler, InvokePayload, WebviewAttributes, WebviewRpcHandler}, - Dispatch, Monitor, Runtime, - }, - sealed::{ManagerBase, RuntimeOrDispatch}, - Icon, Manager, Params, WindowBuilder, -}; -use serde::Serialize; -use serde_json::Value as JsonValue; -use std::hash::{Hash, Hasher}; - -/// UI scaling utilities. -pub mod dpi; - -/// An event from a window. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum WindowEvent { - /// The size of the window has changed. Contains the client area's new dimensions. - Resized(dpi::PhysicalSize), - /// The position of the window has changed. Contains the window's new position. - Moved(dpi::PhysicalPosition), - /// The window has been requested to close. - CloseRequested, - /// The window has been destroyed. - Destroyed, - /// The window gained or lost focus. - /// - /// The parameter is true if the window has gained focus, and false if it has lost focus. - Focused(bool), - ///The window's scale factor has changed. - /// - /// The following user actions can cause DPI changes: - /// - /// - Changing the display's resolution. - /// - Changing the display's scale factor (e.g. in Control Panel on Windows). - /// - Moving the window to a display with a different scale factor. - #[non_exhaustive] - ScaleFactorChanged { - /// The new scale factor. - scale_factor: f64, - /// The window inner size. - new_inner_size: dpi::PhysicalSize, - }, -} - -/// A menu event. -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MenuEvent { - pub(crate) menu_item_id: u32, -} - -/// A webview window that has yet to be built. -pub struct PendingWindow { - /// The label that the window will be named. - pub label: M::Label, - - /// The [`WindowBuilder`] that the window will be created with. - pub window_attributes: <::Dispatcher as Dispatch>::WindowBuilder, - - /// The [`WebviewAttributes`] that the webview will be created with. - pub webview_attributes: WebviewAttributes, - - /// How to handle RPC calls on the webview window. - pub rpc_handler: Option>, - - /// How to handle a file dropping onto the webview window. - pub file_drop_handler: Option>, - - /// The resolved URL to load on the webview. - pub url: String, -} - -impl PendingWindow { - /// Create a new [`PendingWindow`] with a label and starting url. - pub fn new( - window_attributes: <::Dispatcher as Dispatch>::WindowBuilder, - webview_attributes: WebviewAttributes, - label: M::Label, - ) -> Self { - Self { - window_attributes, - webview_attributes, - label, - rpc_handler: None, - file_drop_handler: None, - url: "tauri://localhost".to_string(), - } - } - - /// Create a new [`PendingWindow`] from a [`WindowConfig`] with a label and starting url. - pub fn with_config( - window_config: WindowConfig, - webview_attributes: WebviewAttributes, - label: M::Label, - ) -> Self { - Self { - window_attributes: - <<::Dispatcher as Dispatch>::WindowBuilder>::with_config( - window_config, - ), - webview_attributes, - label, - rpc_handler: None, - file_drop_handler: None, - url: "tauri://localhost".to_string(), - } - } -} - -/// A webview window that is not yet managed by Tauri. -pub struct DetachedWindow { - /// Name of the window - pub label: M::Label, - - /// The [`Dispatch`](crate::runtime::Dispatch) associated with the window. - pub dispatcher: ::Dispatcher, -} - -impl Clone for DetachedWindow { - fn clone(&self) -> Self { - Self { - label: self.label.clone(), - dispatcher: self.dispatcher.clone(), - } - } -} - -impl Hash for DetachedWindow { - /// Only use the [`DetachedWindow`]'s label to represent its hash. - fn hash(&self, state: &mut H) { - self.label.hash(state) - } -} - -impl Eq for DetachedWindow {} -impl PartialEq for DetachedWindow { - /// Only use the [`DetachedWindow`]'s label to compare equality. - fn eq(&self, other: &Self) -> bool { - self.label.eq(&other.label) - } -} - -/// We want to export the runtime related window at the crate root, but not look like a re-export. -pub(crate) mod export { - pub(crate) use super::dpi; - use super::*; - use crate::command::{CommandArg, CommandItem}; - use crate::runtime::{manager::WindowManager, tag::TagRef}; - use crate::{Invoke, InvokeError}; - use dpi::{PhysicalPosition, PhysicalSize, Position, Size}; - use std::borrow::Borrow; - - /// A webview window managed by Tauri. - /// - /// This type also implements [`Manager`] which allows you to manage other windows attached to - /// the same application. - /// - /// TODO: expand these docs since this is a pretty important type - pub struct Window { - /// The webview window created by the runtime. - window: DetachedWindow

, - - /// The manager to associate this webview window with. - manager: WindowManager

, - } - - impl Clone for Window { - fn clone(&self) -> Self { - Self { - window: self.window.clone(), - manager: self.manager.clone(), - } - } - } - - impl Hash for Window

{ - /// Only use the [`Window`]'s label to represent its hash. - fn hash(&self, state: &mut H) { - self.window.label.hash(state) - } - } - - impl Eq for Window

{} - impl PartialEq for Window

{ - /// Only use the [`Window`]'s label to compare equality. - fn eq(&self, other: &Self) -> bool { - self.window.label.eq(&other.window.label) - } - } - - impl Manager

for Window

{} - impl ManagerBase

for Window

{ - fn manager(&self) -> &WindowManager

{ - &self.manager - } - } - - impl<'de, P: Params> CommandArg<'de, P> for Window

{ - /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail. - fn from_command(command: CommandItem<'de, P>) -> Result { - Ok(command.message.window()) - } - } - - impl Window

{ - /// Create a new window that is attached to the manager. - pub(crate) fn new(manager: WindowManager

, window: DetachedWindow

) -> Self { - Self { window, manager } - } - - /// Creates a new webview window. - pub fn create_window( - &mut self, - label: P::Label, - url: WindowUrl, - setup: F, - ) -> crate::Result> - where - F: FnOnce( - <::Dispatcher as Dispatch>::WindowBuilder, - WebviewAttributes, - ) -> ( - <::Dispatcher as Dispatch>::WindowBuilder, - WebviewAttributes, - ), - { - let (window_attributes, webview_attributes) = setup( - <::Dispatcher as Dispatch>::WindowBuilder::new(), - WebviewAttributes::new(url), - ); - self.create_new_window( - RuntimeOrDispatch::Dispatch(self.dispatcher()), - PendingWindow::new(window_attributes, webview_attributes, label), - ) - } - - /// The current window's dispatcher. - pub(crate) fn dispatcher(&self) -> ::Dispatcher { - self.window.dispatcher.clone() - } - - pub(crate) fn run_on_main_thread( - &self, - f: F, - ) -> crate::Result<()> { - self.window.dispatcher.run_on_main_thread(f) - } - - /// 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(); - match command.as_str() { - "__initialized" => { - let payload: PageLoadPayload = serde_json::from_value(payload.inner)?; - manager.run_on_page_load(self, payload); - } - _ => { - let message = InvokeMessage::new( - self.clone(), - manager.state(), - command.to_string(), - payload.inner, - ); - let resolver = InvokeResolver::new(self, payload.callback, payload.error); - let invoke = Invoke { message, resolver }; - if let Some(module) = &payload.tauri_module { - let module = module.to_string(); - crate::endpoints::handle(module, invoke, manager.config(), manager.package_info()); - } else if command.starts_with("plugin:") { - manager.extend_api(invoke); - } else { - manager.run_invoke_handler(invoke); - } - } - } - - Ok(()) - } - - /// The label of this window. - pub fn label(&self) -> &P::Label { - &self.window.label - } - - pub(crate) fn emit_internal( - &self, - event: &E, - payload: Option, - ) -> crate::Result<()> - where - P::Event: Borrow, - E: TagRef, - S: Serialize, - { - 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_js_string()?, - js_payload, - self.manager.generate_salt(), - ))?; - - Ok(()) - } - - /// Emits an event to the current window. - pub fn emit(&self, event: &E, payload: Option) -> crate::Result<()> - where - P::Event: Borrow, - E: TagRef, - S: Serialize, - { - self.emit_internal(event, payload) - } - - /// Emits an event on all windows except this one. - pub fn emit_others(&self, event: &E, payload: Option) -> crate::Result<()> - where - P::Event: Borrow, - E: TagRef, - S: Serialize + Clone, - { - self.manager.emit_filter(event, payload, |w| w != self) - } - - /// Listen to an event on this window. - pub fn listen, F>(&self, event: E, handler: F) -> EventHandler - where - F: Fn(Event) + Send + 'static, - { - let label = self.window.label.clone(); - self.manager.listen(event.into(), Some(label), handler) - } - - /// Listen to a an event on this window a single time. - pub fn once, F>(&self, event: E, handler: F) -> EventHandler - where - F: Fn(Event) + Send + 'static, - { - let label = self.window.label.clone(); - self.manager.once(event.into(), Some(label), handler) - } - - /// Triggers an event on this window. - pub fn trigger(&self, event: &E, data: Option) - where - P::Event: Borrow, - E: TagRef, - { - 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) - } - - /// Registers a window event listener. - pub fn on_window_event(&self, f: F) { - self.window.dispatcher.on_window_event(f); - } - - /// Registers a menu event listener. - pub fn on_menu_event(&self, f: F) { - self.window.dispatcher.on_menu_event(f); - } - - // Getters - - /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. - pub fn scale_factor(&self) -> crate::Result { - self.window.dispatcher.scale_factor() - } - - /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop. - pub fn inner_position(&self) -> crate::Result> { - self.window.dispatcher.inner_position() - } - - /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. - pub fn outer_position(&self) -> crate::Result> { - self.window.dispatcher.outer_position() - } - - /// Returns the physical size of the window's client area. - /// - /// The client area is the content of the window, excluding the title bar and borders. - pub fn inner_size(&self) -> crate::Result> { - self.window.dispatcher.inner_size() - } - - /// Returns the physical size of the entire window. - /// - /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead. - pub fn outer_size(&self) -> crate::Result> { - self.window.dispatcher.outer_size() - } - - /// Gets the window's current fullscreen state. - pub fn is_fullscreen(&self) -> crate::Result { - self.window.dispatcher.is_fullscreen() - } - - /// Gets the window's current maximized state. - pub fn is_maximized(&self) -> crate::Result { - self.window.dispatcher.is_maximized() - } - - /// Returns the monitor on which the window currently resides. - /// - /// Returns None if current monitor can't be detected. - pub fn current_monitor(&self) -> crate::Result> { - self.window.dispatcher.current_monitor() - } - - /// Returns the primary monitor of the system. - /// - /// Returns None if it can't identify any monitor as a primary one. - pub fn primary_monitor(&self) -> crate::Result> { - self.window.dispatcher.primary_monitor() - } - - /// Returns the list of all the monitors available on the system. - pub fn available_monitors(&self) -> crate::Result> { - self.window.dispatcher.available_monitors() - } - - // Setters - - /// Opens the dialog to prints the contents of the webview. - /// Currently only supported on macOS on `wry`. - /// `window.print()` works on all platforms. - pub fn print(&self) -> crate::Result<()> { - self.window.dispatcher.print() - } - - /// 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) - } - - /// Resizes this window. - pub fn set_size>(&self, size: S) -> crate::Result<()> { - self.window.dispatcher.set_size(size.into()) - } - - /// Sets this window's minimum size. - pub fn set_min_size>(&self, size: Option) -> crate::Result<()> { - self.window.dispatcher.set_min_size(size.map(|s| s.into())) - } - - /// Sets this window's maximum size. - pub fn set_max_size>(&self, size: Option) -> crate::Result<()> { - self.window.dispatcher.set_max_size(size.map(|s| s.into())) - } - - /// Sets this window's position. - pub fn set_position>(&self, position: Pos) -> crate::Result<()> { - self.window.dispatcher.set_position(position.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) - } - - /// Starts dragging the window. - pub fn start_dragging(&self) -> crate::Result<()> { - self.window.dispatcher.start_dragging() - } - - pub(crate) fn verify_salt(&self, salt: String) -> bool { - self.manager.verify_salt(salt) - } - } -} diff --git a/core/tauri/src/updater/mod.rs b/core/tauri/src/updater/mod.rs index d1fe1874c86..9de90a9c61c 100644 --- a/core/tauri/src/updater/mod.rs +++ b/core/tauri/src/updater/mod.rs @@ -339,7 +339,7 @@ mod error; pub use self::error::Error; -use crate::runtime::manager::tauri_event; +use crate::manager::tauri_event; use crate::{ api::{ config::UpdaterConfig, diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs new file mode 100644 index 00000000000..ceeaaa81bf4 --- /dev/null +++ b/core/tauri/src/window.rs @@ -0,0 +1,478 @@ +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use crate::{ + api::config::WindowUrl, + command::{CommandArg, CommandItem}, + event::{Event, EventHandler}, + manager::WindowManager, + runtime::{ + menu::MenuId, + monitor::Monitor, + tag::{TagRef, ToJsString}, + webview::{InvokePayload, WebviewAttributes, WindowBuilder}, + window::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + DetachedWindow, PendingWindow, WindowEvent, + }, + Dispatch, Icon, Params, Runtime, + }, + sealed::ManagerBase, + sealed::RuntimeOrDispatch, + Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager, PageLoadPayload, +}; + +use serde::Serialize; +use serde_json::Value as JsonValue; + +use std::{ + borrow::Borrow, + hash::{Hash, Hasher}, +}; + +/// The window menu event. +#[derive(Debug, Clone)] +pub struct MenuEvent { + pub(crate) menu_item_id: I, +} + +impl MenuEvent { + /// The menu item id. + pub fn menu_item_id(&self) -> &I { + &self.menu_item_id + } +} + +/// A webview window managed by Tauri. +/// +/// This type also implements [`Manager`] which allows you to manage other windows attached to +/// the same application. +/// +/// TODO: expand these docs since this is a pretty important type +pub struct Window { + /// The webview window created by the runtime. + window: DetachedWindow

, + + /// The manager to associate this webview window with. + manager: WindowManager

, +} + +impl Clone for Window { + fn clone(&self) -> Self { + Self { + window: self.window.clone(), + manager: self.manager.clone(), + } + } +} + +impl Hash for Window

{ + /// Only use the [`Window`]'s label to represent its hash. + fn hash(&self, state: &mut H) { + self.window.label.hash(state) + } +} + +impl Eq for Window

{} +impl PartialEq for Window

{ + /// Only use the [`Window`]'s label to compare equality. + fn eq(&self, other: &Self) -> bool { + self.window.label.eq(&other.window.label) + } +} + +impl Manager

for Window

{} +impl ManagerBase

for Window

{ + fn manager(&self) -> &WindowManager

{ + &self.manager + } +} + +impl<'de, P: Params> CommandArg<'de, P> for Window

{ + /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail. + fn from_command(command: CommandItem<'de, P>) -> Result { + Ok(command.message.window()) + } +} + +impl Window

{ + /// Create a new window that is attached to the manager. + pub(crate) fn new(manager: WindowManager

, window: DetachedWindow

) -> Self { + Self { window, manager } + } + + /// Creates a new webview window. + pub fn create_window( + &mut self, + label: P::Label, + url: WindowUrl, + setup: F, + ) -> crate::Result> + where + F: FnOnce( + <::Dispatcher as Dispatch>::WindowBuilder, + WebviewAttributes, + ) -> ( + <::Dispatcher as Dispatch>::WindowBuilder, + WebviewAttributes, + ), + { + let (window_builder, webview_attributes) = setup( + <::Dispatcher as Dispatch>::WindowBuilder::new(), + WebviewAttributes::new(url), + ); + self.create_new_window( + RuntimeOrDispatch::Dispatch(self.dispatcher()), + PendingWindow::new(window_builder, webview_attributes, label), + ) + } + + /// The current window's dispatcher. + pub(crate) fn dispatcher(&self) -> ::Dispatcher { + self.window.dispatcher.clone() + } + + pub(crate) fn run_on_main_thread(&self, f: F) -> crate::Result<()> { + self + .window + .dispatcher + .run_on_main_thread(f) + .map_err(Into::into) + } + + /// 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(); + match command.as_str() { + "__initialized" => { + let payload: PageLoadPayload = serde_json::from_value(payload.inner)?; + manager.run_on_page_load(self, payload); + } + _ => { + let message = InvokeMessage::new( + self.clone(), + manager.state(), + command.to_string(), + payload.inner, + ); + let resolver = InvokeResolver::new(self, payload.callback, payload.error); + let invoke = Invoke { message, resolver }; + if let Some(module) = &payload.tauri_module { + let module = module.to_string(); + crate::endpoints::handle(module, invoke, manager.config(), manager.package_info()); + } else if command.starts_with("plugin:") { + manager.extend_api(invoke); + } else { + manager.run_invoke_handler(invoke); + } + } + } + + Ok(()) + } + + /// The label of this window. + pub fn label(&self) -> &P::Label { + &self.window.label + } + + pub(crate) fn emit_internal( + &self, + event: &E, + payload: Option, + ) -> crate::Result<()> + where + P::Event: Borrow, + E: TagRef, + S: Serialize, + { + 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_js_string()?, + js_payload, + self.manager.generate_salt(), + ))?; + + Ok(()) + } + + /// Emits an event to the current window. + pub fn emit(&self, event: &E, payload: Option) -> crate::Result<()> + where + P::Event: Borrow, + E: TagRef, + S: Serialize, + { + self.emit_internal(event, payload) + } + + /// Emits an event on all windows except this one. + pub fn emit_others(&self, event: &E, payload: Option) -> crate::Result<()> + where + P::Event: Borrow, + E: TagRef, + S: Serialize + Clone, + { + self.manager.emit_filter(event, payload, |w| w != self) + } + + /// Listen to an event on this window. + pub fn listen, F>(&self, event: E, handler: F) -> EventHandler + where + F: Fn(Event) + Send + 'static, + { + let label = self.window.label.clone(); + self.manager.listen(event.into(), Some(label), handler) + } + + /// Listen to a an event on this window a single time. + pub fn once, F>(&self, event: E, handler: F) -> EventHandler + where + F: Fn(Event) + Send + 'static, + { + let label = self.window.label.clone(); + self.manager.once(event.into(), Some(label), handler) + } + + /// Triggers an event on this window. + pub fn trigger(&self, event: &E, data: Option) + where + P::Event: Borrow, + E: TagRef, + { + 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).map_err(Into::into) + } + + /// Registers a window event listener. + pub fn on_window_event(&self, f: F) { + self.window.dispatcher.on_window_event(f); + } + + /// Registers a menu event listener. + pub fn on_menu_event) + Send + 'static>(&self, f: F) { + let menu_ids = self.manager.menu_ids(); + self.window.dispatcher.on_menu_event(move |event| { + f(MenuEvent { + menu_item_id: menu_ids.get(&event.menu_item_id).unwrap().clone(), + }) + }); + } + + // Getters + + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. + pub fn scale_factor(&self) -> crate::Result { + self.window.dispatcher.scale_factor().map_err(Into::into) + } + + /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop. + pub fn inner_position(&self) -> crate::Result> { + self.window.dispatcher.inner_position().map_err(Into::into) + } + + /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. + pub fn outer_position(&self) -> crate::Result> { + self.window.dispatcher.outer_position().map_err(Into::into) + } + + /// Returns the physical size of the window's client area. + /// + /// The client area is the content of the window, excluding the title bar and borders. + pub fn inner_size(&self) -> crate::Result> { + self.window.dispatcher.inner_size().map_err(Into::into) + } + + /// Returns the physical size of the entire window. + /// + /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead. + pub fn outer_size(&self) -> crate::Result> { + self.window.dispatcher.outer_size().map_err(Into::into) + } + + /// Gets the window's current fullscreen state. + pub fn is_fullscreen(&self) -> crate::Result { + self.window.dispatcher.is_fullscreen().map_err(Into::into) + } + + /// Gets the window's current maximized state. + pub fn is_maximized(&self) -> crate::Result { + self.window.dispatcher.is_maximized().map_err(Into::into) + } + + /// Returns the monitor on which the window currently resides. + /// + /// Returns None if current monitor can't be detected. + pub fn current_monitor(&self) -> crate::Result> { + self.window.dispatcher.current_monitor().map_err(Into::into) + } + + /// Returns the primary monitor of the system. + /// + /// Returns None if it can't identify any monitor as a primary one. + pub fn primary_monitor(&self) -> crate::Result> { + self.window.dispatcher.primary_monitor().map_err(Into::into) + } + + /// Returns the list of all the monitors available on the system. + pub fn available_monitors(&self) -> crate::Result> { + self + .window + .dispatcher + .available_monitors() + .map_err(Into::into) + } + + // Setters + + /// Opens the dialog to prints the contents of the webview. + /// Currently only supported on macOS on `wry`. + /// `window.print()` works on all platforms. + pub fn print(&self) -> crate::Result<()> { + self.window.dispatcher.print().map_err(Into::into) + } + + /// Determines if this window should be resizable. + pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_resizable(resizable) + .map_err(Into::into) + } + + /// Set this window's title. + pub fn set_title(&self, title: &str) -> crate::Result<()> { + self + .window + .dispatcher + .set_title(title.to_string()) + .map_err(Into::into) + } + + /// Maximizes this window. + pub fn maximize(&self) -> crate::Result<()> { + self.window.dispatcher.maximize().map_err(Into::into) + } + + /// Un-maximizes this window. + pub fn unmaximize(&self) -> crate::Result<()> { + self.window.dispatcher.unmaximize().map_err(Into::into) + } + + /// Minimizes this window. + pub fn minimize(&self) -> crate::Result<()> { + self.window.dispatcher.minimize().map_err(Into::into) + } + + /// Un-minimizes this window. + pub fn unminimize(&self) -> crate::Result<()> { + self.window.dispatcher.unminimize().map_err(Into::into) + } + + /// Show this window. + pub fn show(&self) -> crate::Result<()> { + self.window.dispatcher.show().map_err(Into::into) + } + + /// Hide this window. + pub fn hide(&self) -> crate::Result<()> { + self.window.dispatcher.hide().map_err(Into::into) + } + + /// Closes this window. + pub fn close(&self) -> crate::Result<()> { + self.window.dispatcher.close().map_err(Into::into) + } + + /// 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) + .map_err(Into::into) + } + + /// 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) + .map_err(Into::into) + } + + /// Resizes this window. + pub fn set_size>(&self, size: S) -> crate::Result<()> { + self + .window + .dispatcher + .set_size(size.into()) + .map_err(Into::into) + } + + /// Sets this window's minimum size. + pub fn set_min_size>(&self, size: Option) -> crate::Result<()> { + self + .window + .dispatcher + .set_min_size(size.map(|s| s.into())) + .map_err(Into::into) + } + + /// Sets this window's maximum size. + pub fn set_max_size>(&self, size: Option) -> crate::Result<()> { + self + .window + .dispatcher + .set_max_size(size.map(|s| s.into())) + .map_err(Into::into) + } + + /// Sets this window's position. + pub fn set_position>(&self, position: Pos) -> crate::Result<()> { + self + .window + .dispatcher + .set_position(position.into()) + .map_err(Into::into) + } + + /// Determines if this window should be fullscreen. + pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> { + self + .window + .dispatcher + .set_fullscreen(fullscreen) + .map_err(Into::into) + } + + /// Sets this window' icon. + pub fn set_icon(&self, icon: Icon) -> crate::Result<()> { + self.window.dispatcher.set_icon(icon).map_err(Into::into) + } + + /// Starts dragging the window. + pub fn start_dragging(&self) -> crate::Result<()> { + self.window.dispatcher.start_dragging().map_err(Into::into) + } + + pub(crate) fn verify_salt(&self, salt: String) -> bool { + self.manager.verify_salt(salt) + } +} diff --git a/examples/multiwindow/src-tauri/src/main.rs b/examples/multiwindow/src-tauri/src/main.rs index 60f8190a881..86e60ba26e7 100644 --- a/examples/multiwindow/src-tauri/src/main.rs +++ b/examples/multiwindow/src-tauri/src/main.rs @@ -20,8 +20,8 @@ fn main() { .create_window( "Rust".to_string(), tauri::WindowUrl::App("index.html".into()), - |window_attributes, webview_attributes| { - (window_attributes.title("Tauri - Rust"), webview_attributes) + |window_builder, webview_attributes| { + (window_builder.title("Tauri - Rust"), webview_attributes) }, ) .run(tauri::generate_context!())