瀏覽代碼

refactor: move runtime to `tauri-runtime` crate (#1751)

Lucas Fernandes Nogueira 4 年之前
父節點
當前提交
665ec1d4a1

+ 11 - 2
.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"
     }
   }
-}
+}

+ 6 - 0
.changes/runtime-crate.md

@@ -0,0 +1,6 @@
+---
+"tauri-runtime": patch
+"tauri": patch
+---
+
+`tauri-runtime` crate initial release.

+ 31 - 0
.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)
+}

+ 1 - 0
Cargo.toml

@@ -2,6 +2,7 @@
 members = [
   # core
   "core/tauri",
+  "core/tauri-runtime",
   "core/tauri-macros",
   "core/tauri-utils",
   "core/tauri-build",

+ 19 - 0
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"

+ 0 - 0
core/tauri/src/runtime/flavors/mod.rs → core/tauri-runtime/src/flavors/mod.rs


+ 11 - 15
core/tauri/src/runtime/flavors/wry.rs → 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<M: Params<Runtime = Wry>>(
 ) -> crate::Result<WebView> {
   let PendingWindow {
     webview_attributes,
-    window_attributes,
+    window_builder,
     rpc_handler,
     file_drop_handler,
     label,
@@ -1051,7 +1047,7 @@ fn create_webview<M: Params<Runtime = Wry>>(
     ..
   } = 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)

+ 69 - 8
core/tauri/src/runtime/mod.rs → 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<dyn std::error::Error + Send>),
+  /// 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<dyn std::error::Error + Send>),
+  /// Failed to load window icon.
+  #[error("invalid icon: {0}")]
+  InvalidIcon(Box<dyn std::error::Error + Send>),
+}
+
+/// Result type.
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[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<u8>),
+}
+
 /// A system tray event.
 pub struct SystemTrayEvent {
-  pub(crate) menu_item_id: u32,
+  pub menu_item_id: u32,
 }
 
 /// The webview runtime interface.

+ 11 - 7
core/tauri/src/runtime/menu.rs → 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<T> MenuId for T where T: Hash + Eq + Debug + Clone + Send + Sync + 'static {}
+impl<T> MenuId for T where T: Serialize + Hash + Eq + Debug + Clone + Send + Sync + 'static {}
 
 /// A window menu.
 #[derive(Debug, Clone)]
 pub struct Menu<I: MenuId> {
-  pub(crate) title: String,
-  pub(crate) items: Vec<MenuItem<I>>,
+  pub title: String,
+  pub items: Vec<MenuItem<I>>,
 }
 
 impl<I: MenuId> Menu<I> {
@@ -29,11 +31,12 @@ impl<I: MenuId> Menu<I> {
     }
   }
 }
+
 /// A custom menu item.
 #[derive(Debug, Clone)]
 pub struct CustomMenuItem<I: MenuId> {
-  pub(crate) id: I,
-  pub(crate) name: String,
+  pub id: I,
+  pub name: String,
 }
 
 impl<I: MenuId> CustomMenuItem<I> {
@@ -43,7 +46,8 @@ impl<I: MenuId> CustomMenuItem<I> {
     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

+ 0 - 0
core/tauri/src/runtime/monitor.rs → core/tauri-runtime/src/monitor.rs


+ 3 - 3
core/tauri/src/runtime/tag.rs → 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<String>;
 }
 
@@ -125,7 +125,7 @@ impl<D: Display> 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<String> {
+pub 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)?)
 }

+ 22 - 21
core/tauri/src/runtime/webview.rs → 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<Vec<u8>> + Send + Sync + 'static;
+type UriSchemeProtocol =
+  dyn Fn(&str) -> Result<Vec<u8>, Box<dyn std::error::Error>> + Send + Sync + 'static;
 
 /// The attributes used to create an webview.
 pub struct WebviewAttributes {
-  pub(crate) url: WindowUrl,
-  pub(crate) initialization_scripts: Vec<String>,
-  pub(crate) data_directory: Option<PathBuf>,
-  pub(crate) uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
+  pub url: WindowUrl,
+  pub initialization_scripts: Vec<String>,
+  pub data_directory: Option<PathBuf>,
+  pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
 }
 
 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<String>,
-    H: Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static,
+    H: Fn(&str) -> Result<Vec<u8>, Box<dyn std::error::Error>> + 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<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync>,
+  #[allow(clippy::type_complexity)]
+  pub protocol: Box<dyn Fn(&str) -> Result<Vec<u8>, Box<dyn std::error::Error>> + Send + Sync>,
 }
 
 /// The file drop event payload.
@@ -173,18 +174,18 @@ pub enum FileDropEvent {
 }
 
 /// Rpc handler.
-pub(crate) type WebviewRpcHandler<M> = Box<dyn Fn(DetachedWindow<M>, RpcRequest) + Send>;
+pub type WebviewRpcHandler<M> = Box<dyn Fn(DetachedWindow<M>, 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<M> = Box<dyn Fn(FileDropEvent, DetachedWindow<M>) -> bool + Send>;
+pub type FileDropHandler<M> = Box<dyn Fn(FileDropEvent, DetachedWindow<M>) -> bool + Send>;
 
 #[derive(Deserialize)]
-pub(crate) struct InvokePayload {
+pub struct InvokePayload {
   #[serde(rename = "__tauriModule")]
-  pub(crate) tauri_module: Option<String>,
-  pub(crate) callback: String,
-  pub(crate) error: String,
+  pub tauri_module: Option<String>,
+  pub callback: String,
+  pub error: String,
   #[serde(flatten)]
-  pub(crate) inner: JsonValue,
+  pub inner: JsonValue,
 }

+ 147 - 0
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<u32>),
+  /// The position of the window has changed. Contains the window's new position.
+  Moved(dpi::PhysicalPosition<i32>),
+  /// 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<u32>,
+  },
+}
+
+/// 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<M: Params> {
+  /// The label that the window will be named.
+  pub label: M::Label,
+
+  /// The [`WindowBuilder`] that the window will be created with.
+  pub window_builder: <<M::Runtime as Runtime>::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<WebviewRpcHandler<M>>,
+
+  /// How to handle a file dropping onto the webview window.
+  pub file_drop_handler: Option<FileDropHandler<M>>,
+
+  /// The resolved URL to load on the webview.
+  pub url: String,
+}
+
+impl<M: Params> PendingWindow<M> {
+  /// Create a new [`PendingWindow`] with a label and starting url.
+  pub fn new(
+    window_builder: <<M::Runtime as Runtime>::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:
+        <<<M::Runtime as Runtime>::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<M: Params> {
+  /// Name of the window
+  pub label: M::Label,
+
+  /// The [`Dispatch`](crate::runtime::Dispatch) associated with the window.
+  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)
+  }
+}

+ 0 - 0
core/tauri/src/runtime/window/dpi.rs → core/tauri-runtime/src/window/dpi.rs


+ 1 - 1
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"

+ 10 - 9
core/tauri/src/runtime/app.rs → 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<P: Params> App<P> {
       WebviewAttributes,
     ),
   {
-    let (window_attributes, webview_attributes) = setup(
+    let (window_builder, webview_attributes) = setup(
       <<P::Runtime as Runtime>::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(
       <R::Dispatcher as Dispatch>::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<String>,
-    H: Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static,
+    H: Fn(&str) -> Result<Vec<u8>, Box<dyn std::error::Error>> + Send + Sync + 'static,
   >(
     mut self,
     uri_scheme: N,

+ 2 - 2
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::<P::Event>("tauri://window-created"),
+              &crate::manager::tauri_event::<P::Event>("tauri://window-created"),
               Some(WindowCreatedEvent {
                 label: label.to_string(),
               }),

+ 3 - 0
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<dyn std::error::Error + Send>),

+ 1 - 1
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};

+ 26 - 58
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<dyn FnOnce() + Send>;
 
 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<u8>),
-}
-
 /// User supplied data required inside of a Tauri application.
 pub struct Context<A: Assets> {
   /// The config the application was prepared with.
@@ -142,27 +132,6 @@ pub struct Context<A: Assets> {
   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<P: Params>: sealed::ManagerBase<P> {
   }
 }
 
-/// 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::<Vec<_>>();
       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))
     }

+ 24 - 17
core/tauri/src/runtime/manager.rs → 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<P: Params> WindowManager<P> {
     self.inner.state.clone()
   }
 
+  /// Get the menu ids mapper.
+  pub(crate) fn menu_ids(&self) -> HashMap<u32, P::MenuId> {
+    self.inner.menu_ids.clone()
+  }
+
   // setup content for dev-server
   #[cfg(dev)]
   fn get_url(&self) -> String {
@@ -243,15 +248,15 @@ impl<P: Params> WindowManager<P> {
         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<P: Params> WindowManager<P> {
           Err(e) => {
             #[cfg(debug_assertions)]
             eprintln!("{:?}", e); // TODO log::error!
-            Err(e)
+            Err(Box::new(e))
           }
         }
       }),
@@ -369,6 +374,7 @@ impl<P: Params> WindowManager<P> {
             &tauri_event::<P::Event>("tauri://file-drop-cancelled"),
             Some(()),
           ),
+          _ => unimplemented!(),
         };
       });
       true
@@ -394,9 +400,9 @@ impl<P: Params> WindowManager<P> {
       }}
       {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<P: Params> WindowManager<P> {
     });
     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<P: Params>(window: &Window<P>, 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<P: Params>(window: &Window<P>, event: &WindowEvent) -> crate:
         size: new_inner_size.clone(),
       }),
     )?,
+    _ => unimplemented!(),
   }
   Ok(())
 }
@@ -747,11 +754,11 @@ struct ScaleFactorChanged {
   size: PhysicalSize<u32>,
 }
 
-fn on_menu_event<P: Params>(window: &Window<P>, event: &MenuEvent) -> crate::Result<()> {
+fn on_menu_event<P: Params>(window: &Window<P>, event: &MenuEvent<P::MenuId>) -> crate::Result<()> {
   window.emit(
     &MENU_EVENT
       .parse()
       .unwrap_or_else(|_| panic!("unhandled event")),
-    Some(event),
+    Some(event.menu_item_id.clone()),
   )
 }

+ 0 - 552
core/tauri/src/runtime/window.rs

@@ -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<u32>),
-  /// The position of the window has changed. Contains the window's new position.
-  Moved(dpi::PhysicalPosition<i32>),
-  /// 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<u32>,
-  },
-}
-
-/// 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<M: Params> {
-  /// The label that the window will be named.
-  pub label: M::Label,
-
-  /// The [`WindowBuilder`] that the window will be created with.
-  pub window_attributes: <<M::Runtime as Runtime>::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<WebviewRpcHandler<M>>,
-
-  /// How to handle a file dropping onto the webview window.
-  pub file_drop_handler: Option<FileDropHandler<M>>,
-
-  /// The resolved URL to load on the webview.
-  pub url: String,
-}
-
-impl<M: Params> PendingWindow<M> {
-  /// Create a new [`PendingWindow`] with a label and starting url.
-  pub fn new(
-    window_attributes: <<M::Runtime as Runtime>::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:
-        <<<M::Runtime as Runtime>::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<M: Params> {
-  /// Name of the window
-  pub label: M::Label,
-
-  /// The [`Dispatch`](crate::runtime::Dispatch) associated with the window.
-  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)
-  }
-}
-
-/// 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<P: Params> {
-    /// The webview window created by the runtime.
-    window: DetachedWindow<P>,
-
-    /// The manager to associate this webview window with.
-    manager: WindowManager<P>,
-  }
-
-  impl<M: Params> Clone for Window<M> {
-    fn clone(&self) -> Self {
-      Self {
-        window: self.window.clone(),
-        manager: self.manager.clone(),
-      }
-    }
-  }
-
-  impl<P: Params> Hash for Window<P> {
-    /// Only use the [`Window`]'s label to represent its hash.
-    fn hash<H: Hasher>(&self, state: &mut H) {
-      self.window.label.hash(state)
-    }
-  }
-
-  impl<P: Params> Eq for Window<P> {}
-  impl<P: Params> PartialEq for Window<P> {
-    /// Only use the [`Window`]'s label to compare equality.
-    fn eq(&self, other: &Self) -> bool {
-      self.window.label.eq(&other.window.label)
-    }
-  }
-
-  impl<P: Params> Manager<P> for Window<P> {}
-  impl<P: Params> ManagerBase<P> for Window<P> {
-    fn manager(&self) -> &WindowManager<P> {
-      &self.manager
-    }
-  }
-
-  impl<'de, P: Params> CommandArg<'de, P> for Window<P> {
-    /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail.
-    fn from_command(command: CommandItem<'de, P>) -> Result<Self, InvokeError> {
-      Ok(command.message.window())
-    }
-  }
-
-  impl<P: Params> Window<P> {
-    /// Create a new window that is attached to the manager.
-    pub(crate) fn new(manager: WindowManager<P>, window: DetachedWindow<P>) -> Self {
-      Self { window, manager }
-    }
-
-    /// Creates a new webview window.
-    pub fn create_window<F>(
-      &mut self,
-      label: P::Label,
-      url: WindowUrl,
-      setup: F,
-    ) -> crate::Result<Window<P>>
-    where
-      F: FnOnce(
-        <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
-        WebviewAttributes,
-      ) -> (
-        <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
-        WebviewAttributes,
-      ),
-    {
-      let (window_attributes, webview_attributes) = setup(
-        <<P::Runtime as Runtime>::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) -> <P::Runtime as Runtime>::Dispatcher {
-      self.window.dispatcher.clone()
-    }
-
-    pub(crate) fn run_on_main_thread<F: FnOnce() + Send + 'static>(
-      &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<E: ?Sized, S>(
-      &self,
-      event: &E,
-      payload: Option<S>,
-    ) -> crate::Result<()>
-    where
-      P::Event: Borrow<E>,
-      E: TagRef<P::Event>,
-      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<E: ?Sized, S>(&self, event: &E, payload: Option<S>) -> crate::Result<()>
-    where
-      P::Event: Borrow<E>,
-      E: TagRef<P::Event>,
-      S: Serialize,
-    {
-      self.emit_internal(event, payload)
-    }
-
-    /// Emits an event on all windows except this one.
-    pub fn emit_others<E: ?Sized, S>(&self, event: &E, payload: Option<S>) -> crate::Result<()>
-    where
-      P::Event: Borrow<E>,
-      E: TagRef<P::Event>,
-      S: Serialize + Clone,
-    {
-      self.manager.emit_filter(event, payload, |w| w != self)
-    }
-
-    /// Listen to an event on this window.
-    pub fn listen<E: Into<P::Event>, 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<E: Into<P::Event>, 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<E: ?Sized>(&self, event: &E, data: Option<String>)
-    where
-      P::Event: Borrow<E>,
-      E: TagRef<P::Event>,
-    {
-      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<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {
-      self.window.dispatcher.on_window_event(f);
-    }
-
-    /// Registers a menu event listener.
-    pub fn on_menu_event<F: Fn(&MenuEvent) + Send + 'static>(&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<f64> {
-      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<PhysicalPosition<i32>> {
-      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<PhysicalPosition<i32>> {
-      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<PhysicalSize<u32>> {
-      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<PhysicalSize<u32>> {
-      self.window.dispatcher.outer_size()
-    }
-
-    /// Gets the window's current fullscreen state.
-    pub fn is_fullscreen(&self) -> crate::Result<bool> {
-      self.window.dispatcher.is_fullscreen()
-    }
-
-    /// Gets the window's current maximized state.
-    pub fn is_maximized(&self) -> crate::Result<bool> {
-      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<Option<Monitor>> {
-      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<Option<Monitor>> {
-      self.window.dispatcher.primary_monitor()
-    }
-
-    /// Returns the list of all the monitors available on the system.
-    pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {
-      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<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
-      self.window.dispatcher.set_size(size.into())
-    }
-
-    /// Sets this window's minimum size.
-    pub fn set_min_size<S: Into<Size>>(&self, size: Option<S>) -> crate::Result<()> {
-      self.window.dispatcher.set_min_size(size.map(|s| s.into()))
-    }
-
-    /// Sets this window's maximum size.
-    pub fn set_max_size<S: Into<Size>>(&self, size: Option<S>) -> crate::Result<()> {
-      self.window.dispatcher.set_max_size(size.map(|s| s.into()))
-    }
-
-    /// Sets this window's position.
-    pub fn set_position<Pos: Into<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)
-    }
-  }
-}

+ 1 - 1
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,

+ 478 - 0
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<I: MenuId> {
+  pub(crate) menu_item_id: I,
+}
+
+impl<I: MenuId> MenuEvent<I> {
+  /// 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<P: Params> {
+  /// The webview window created by the runtime.
+  window: DetachedWindow<P>,
+
+  /// The manager to associate this webview window with.
+  manager: WindowManager<P>,
+}
+
+impl<M: Params> Clone for Window<M> {
+  fn clone(&self) -> Self {
+    Self {
+      window: self.window.clone(),
+      manager: self.manager.clone(),
+    }
+  }
+}
+
+impl<P: Params> Hash for Window<P> {
+  /// Only use the [`Window`]'s label to represent its hash.
+  fn hash<H: Hasher>(&self, state: &mut H) {
+    self.window.label.hash(state)
+  }
+}
+
+impl<P: Params> Eq for Window<P> {}
+impl<P: Params> PartialEq for Window<P> {
+  /// Only use the [`Window`]'s label to compare equality.
+  fn eq(&self, other: &Self) -> bool {
+    self.window.label.eq(&other.window.label)
+  }
+}
+
+impl<P: Params> Manager<P> for Window<P> {}
+impl<P: Params> ManagerBase<P> for Window<P> {
+  fn manager(&self) -> &WindowManager<P> {
+    &self.manager
+  }
+}
+
+impl<'de, P: Params> CommandArg<'de, P> for Window<P> {
+  /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail.
+  fn from_command(command: CommandItem<'de, P>) -> Result<Self, InvokeError> {
+    Ok(command.message.window())
+  }
+}
+
+impl<P: Params> Window<P> {
+  /// Create a new window that is attached to the manager.
+  pub(crate) fn new(manager: WindowManager<P>, window: DetachedWindow<P>) -> Self {
+    Self { window, manager }
+  }
+
+  /// Creates a new webview window.
+  pub fn create_window<F>(
+    &mut self,
+    label: P::Label,
+    url: WindowUrl,
+    setup: F,
+  ) -> crate::Result<Window<P>>
+  where
+    F: FnOnce(
+      <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
+      WebviewAttributes,
+    ) -> (
+      <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
+      WebviewAttributes,
+    ),
+  {
+    let (window_builder, webview_attributes) = setup(
+      <<P::Runtime as Runtime>::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) -> <P::Runtime as Runtime>::Dispatcher {
+    self.window.dispatcher.clone()
+  }
+
+  pub(crate) fn run_on_main_thread<F: FnOnce() + Send + 'static>(&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<E: ?Sized, S>(
+    &self,
+    event: &E,
+    payload: Option<S>,
+  ) -> crate::Result<()>
+  where
+    P::Event: Borrow<E>,
+    E: TagRef<P::Event>,
+    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<E: ?Sized, S>(&self, event: &E, payload: Option<S>) -> crate::Result<()>
+  where
+    P::Event: Borrow<E>,
+    E: TagRef<P::Event>,
+    S: Serialize,
+  {
+    self.emit_internal(event, payload)
+  }
+
+  /// Emits an event on all windows except this one.
+  pub fn emit_others<E: ?Sized, S>(&self, event: &E, payload: Option<S>) -> crate::Result<()>
+  where
+    P::Event: Borrow<E>,
+    E: TagRef<P::Event>,
+    S: Serialize + Clone,
+  {
+    self.manager.emit_filter(event, payload, |w| w != self)
+  }
+
+  /// Listen to an event on this window.
+  pub fn listen<E: Into<P::Event>, 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<E: Into<P::Event>, 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<E: ?Sized>(&self, event: &E, data: Option<String>)
+  where
+    P::Event: Borrow<E>,
+    E: TagRef<P::Event>,
+  {
+    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<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {
+    self.window.dispatcher.on_window_event(f);
+  }
+
+  /// Registers a menu event listener.
+  pub fn on_menu_event<F: Fn(MenuEvent<P::MenuId>) + 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<f64> {
+    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<PhysicalPosition<i32>> {
+    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<PhysicalPosition<i32>> {
+    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<PhysicalSize<u32>> {
+    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<PhysicalSize<u32>> {
+    self.window.dispatcher.outer_size().map_err(Into::into)
+  }
+
+  /// Gets the window's current fullscreen state.
+  pub fn is_fullscreen(&self) -> crate::Result<bool> {
+    self.window.dispatcher.is_fullscreen().map_err(Into::into)
+  }
+
+  /// Gets the window's current maximized state.
+  pub fn is_maximized(&self) -> crate::Result<bool> {
+    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<Option<Monitor>> {
+    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<Option<Monitor>> {
+    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<Vec<Monitor>> {
+    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<S: Into<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<S: Into<Size>>(&self, size: Option<S>) -> 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<S: Into<Size>>(&self, size: Option<S>) -> 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<Pos: Into<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)
+  }
+}

+ 2 - 2
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!())