feat(core): add mult-window support (#1217)
5
.changes/event.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": minor
|
||||
---
|
||||
|
||||
The `tauri::event` module has been moved to a Webview manager API.
|
||||
5
.changes/multiwindow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": minor
|
||||
---
|
||||
|
||||
Added support to multiple windows.
|
||||
@@ -104,7 +104,7 @@ If you are interested in making a tauri-app, please visit the [documentation web
|
||||
| Multithreading | Yes | No |
|
||||
| Bytecode Delivery | Yes | No |
|
||||
| Can Render PDF | Yes | No |
|
||||
| Multiple Windows | Soon | Yes |
|
||||
| Multiple Windows | Yes | Yes |
|
||||
| Auto Updater | Soon | Yes (2) |
|
||||
| Cross Platform | Yes | Yes |
|
||||
| Custom App Icon | Yes | Yes |
|
||||
@@ -116,7 +116,6 @@ If you are interested in making a tauri-app, please visit the [documentation web
|
||||
| Localhost Server | Yes | Yes |
|
||||
| No localhost option | Yes | No |
|
||||
| Desktop Tray | Soon | Yes |
|
||||
| Splashscreen | Yes | Yes |
|
||||
| Sidecar Binaries | Yes | No |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -21,53 +21,6 @@ fn config_handle() -> &'static ConfigHandle {
|
||||
&CONFING_HANDLE
|
||||
}
|
||||
|
||||
/// The window configuration object.
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||
#[serde(tag = "window", rename_all = "camelCase")]
|
||||
pub struct WindowConfig {
|
||||
/// The window width.
|
||||
#[serde(default = "default_width")]
|
||||
pub width: i32,
|
||||
/// The window height.
|
||||
#[serde(default = "default_height")]
|
||||
pub height: i32,
|
||||
/// Whether the window is resizable or not.
|
||||
#[serde(default = "default_resizable")]
|
||||
pub resizable: bool,
|
||||
/// The window title.
|
||||
#[serde(default = "default_title")]
|
||||
pub title: String,
|
||||
/// Whether the window starts as fullscreen or not.
|
||||
#[serde(default)]
|
||||
pub fullscreen: bool,
|
||||
}
|
||||
|
||||
fn default_width() -> i32 {
|
||||
800
|
||||
}
|
||||
|
||||
fn default_height() -> i32 {
|
||||
600
|
||||
}
|
||||
|
||||
fn default_resizable() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_title() -> String {
|
||||
"Tauri App".to_string()
|
||||
}
|
||||
|
||||
fn default_window() -> WindowConfig {
|
||||
WindowConfig {
|
||||
width: default_width(),
|
||||
height: default_height(),
|
||||
resizable: default_resizable(),
|
||||
title: default_title(),
|
||||
fullscreen: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// The embedded server port.
|
||||
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum Port {
|
||||
@@ -312,9 +265,6 @@ fn default_bundle() -> BundleConfig {
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||
#[serde(tag = "tauri", rename_all = "camelCase")]
|
||||
pub struct TauriConfig {
|
||||
/// The window configuration.
|
||||
#[serde(default = "default_window")]
|
||||
pub window: WindowConfig,
|
||||
/// The embeddedServer configuration.
|
||||
#[serde(default = "default_embedded_server")]
|
||||
pub embedded_server: EmbeddedServerConfig,
|
||||
@@ -370,7 +320,6 @@ pub struct Config {
|
||||
|
||||
fn default_tauri() -> TauriConfig {
|
||||
TauriConfig {
|
||||
window: default_window(),
|
||||
embedded_server: default_embedded_server(),
|
||||
cli: None,
|
||||
bundle: default_bundle(),
|
||||
|
||||
@@ -420,36 +420,64 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"window": {
|
||||
"additionalProperties": false,
|
||||
"defaultProperties": [],
|
||||
"properties": {
|
||||
"fullscreen": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"height": {
|
||||
"type": "number"
|
||||
},
|
||||
"resizable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "number"
|
||||
}
|
||||
"windows": {
|
||||
"additionalItems": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"defaultProperties": [],
|
||||
"properties": {
|
||||
"fullscreen": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"height": {
|
||||
"type": "number"
|
||||
},
|
||||
"resizable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": ["title"],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": ["title"],
|
||||
"type": "object"
|
||||
"items": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"defaultProperties": [],
|
||||
"properties": {
|
||||
"fullscreen": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"height": {
|
||||
"type": "number"
|
||||
},
|
||||
"resizable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": ["title"],
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"allowlist",
|
||||
"bundle",
|
||||
"security",
|
||||
"window"
|
||||
],
|
||||
"required": ["allowlist", "bundle", "security", "windows"],
|
||||
"type": "object"
|
||||
},
|
||||
"verbose": {
|
||||
|
||||
@@ -277,13 +277,15 @@ export interface TauriConfig {
|
||||
all: boolean
|
||||
[index: string]: boolean
|
||||
}
|
||||
window: {
|
||||
title: string
|
||||
width?: number
|
||||
height?: number
|
||||
resizable?: boolean
|
||||
fullscreen?: boolean
|
||||
}
|
||||
windows: [
|
||||
{
|
||||
title: string
|
||||
width?: number
|
||||
height?: number
|
||||
resizable?: boolean
|
||||
fullscreen?: boolean
|
||||
}
|
||||
]
|
||||
security: {
|
||||
csp?: string
|
||||
}
|
||||
|
||||
@@ -472,31 +472,64 @@ export const TauriConfigSchema = {
|
||||
},
|
||||
type: 'object'
|
||||
},
|
||||
window: {
|
||||
additionalProperties: false,
|
||||
defaultProperties: [],
|
||||
properties: {
|
||||
fullscreen: {
|
||||
type: 'boolean'
|
||||
},
|
||||
height: {
|
||||
type: 'number'
|
||||
},
|
||||
resizable: {
|
||||
type: 'boolean'
|
||||
},
|
||||
title: {
|
||||
type: 'string'
|
||||
},
|
||||
width: {
|
||||
type: 'number'
|
||||
}
|
||||
windows: {
|
||||
additionalItems: {
|
||||
anyOf: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
defaultProperties: [],
|
||||
properties: {
|
||||
fullscreen: {
|
||||
type: 'boolean'
|
||||
},
|
||||
height: {
|
||||
type: 'number'
|
||||
},
|
||||
resizable: {
|
||||
type: 'boolean'
|
||||
},
|
||||
title: {
|
||||
type: 'string'
|
||||
},
|
||||
width: {
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
required: ['title'],
|
||||
type: 'object'
|
||||
}
|
||||
]
|
||||
},
|
||||
required: ['title'],
|
||||
type: 'object'
|
||||
items: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
defaultProperties: [],
|
||||
properties: {
|
||||
fullscreen: {
|
||||
type: 'boolean'
|
||||
},
|
||||
height: {
|
||||
type: 'number'
|
||||
},
|
||||
resizable: {
|
||||
type: 'boolean'
|
||||
},
|
||||
title: {
|
||||
type: 'string'
|
||||
},
|
||||
width: {
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
required: ['title'],
|
||||
type: 'object'
|
||||
}
|
||||
],
|
||||
minItems: 1,
|
||||
type: 'array'
|
||||
}
|
||||
},
|
||||
required: ['allowlist', 'bundle', 'security', 'window'],
|
||||
required: ['allowlist', 'bundle', 'security', 'windows'],
|
||||
type: 'object'
|
||||
},
|
||||
verbose: {
|
||||
|
||||
@@ -7,19 +7,21 @@ struct Context;
|
||||
|
||||
fn main() {
|
||||
tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
|
||||
.setup(|dispatcher, _| async move {
|
||||
let mut dispatcher_ = dispatcher.clone();
|
||||
.setup(|webview_manager| async move {
|
||||
let mut webview_manager_ = webview_manager.clone();
|
||||
tauri::event::listen(String::from("hello"), move |_| {
|
||||
tauri::event::emit(
|
||||
&mut dispatcher_,
|
||||
&webview_manager_,
|
||||
String::from("reply"),
|
||||
Some("{ msg: 'TEST' }".to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
dispatcher.eval("window.onTauriInit && window.onTauriInit()");
|
||||
webview_manager
|
||||
.current_webview()
|
||||
.eval("window.onTauriInit && window.onTauriInit()");
|
||||
})
|
||||
.invoke_handler(|dispatcher, arg| async move {
|
||||
.invoke_handler(|webview_manager, arg| async move {
|
||||
use cmd::Cmd::*;
|
||||
match serde_json::from_str(&arg) {
|
||||
Err(e) => Err(e.to_string()),
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
],
|
||||
"identifier": "fixture.app"
|
||||
},
|
||||
"window": {
|
||||
"title": "Fixture"
|
||||
}
|
||||
"windows": [
|
||||
{
|
||||
"title": "Fixture"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,75 @@ use serde_json::Value as JsonValue;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The window webview URL options.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum WindowUrl {
|
||||
/// The app's index URL.
|
||||
App,
|
||||
/// A custom URL.
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl Default for WindowUrl {
|
||||
fn default() -> Self {
|
||||
Self::App
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for WindowUrl {
|
||||
fn deserialize<D>(deserializer: D) -> Result<WindowUrl, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct StringVisitor;
|
||||
impl<'de> Visitor<'de> for StringVisitor {
|
||||
type Value = WindowUrl;
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str("a string representing an url")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
if v.to_lowercase() == "app" {
|
||||
Ok(WindowUrl::App)
|
||||
} else {
|
||||
Ok(WindowUrl::Custom(v.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(StringVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// The window configuration object.
|
||||
#[derive(PartialEq, Deserialize, Debug)]
|
||||
#[serde(tag = "window", rename_all = "camelCase")]
|
||||
#[derive(PartialEq, Deserialize, Debug, Clone)]
|
||||
pub struct WindowConfig {
|
||||
#[serde(default = "default_window_label")]
|
||||
/// The window identifier.
|
||||
pub label: String,
|
||||
/// The window webview URL.
|
||||
#[serde(default)]
|
||||
pub url: WindowUrl,
|
||||
/// The horizontal position of the window's top left corner
|
||||
pub x: Option<f64>,
|
||||
/// The vertical position of the window's top left corner
|
||||
pub y: Option<f64>,
|
||||
/// The window width.
|
||||
#[serde(default = "default_width")]
|
||||
pub width: i32,
|
||||
pub width: f64,
|
||||
/// The window height.
|
||||
#[serde(default = "default_height")]
|
||||
pub height: i32,
|
||||
pub height: f64,
|
||||
/// The min window width.
|
||||
pub min_width: Option<f64>,
|
||||
/// The min window height.
|
||||
pub min_height: Option<f64>,
|
||||
/// The max window width.
|
||||
pub max_width: Option<f64>,
|
||||
/// The max window height.
|
||||
pub max_height: Option<f64>,
|
||||
/// Whether the window is resizable or not.
|
||||
#[serde(default = "default_resizable")]
|
||||
pub resizable: bool,
|
||||
@@ -25,20 +84,47 @@ pub struct WindowConfig {
|
||||
/// Whether the window starts as fullscreen or not.
|
||||
#[serde(default)]
|
||||
pub fullscreen: bool,
|
||||
/// Whether the window is transparent or not.
|
||||
#[serde(default)]
|
||||
pub transparent: bool,
|
||||
/// Whether the window is maximized or not.
|
||||
#[serde(default)]
|
||||
pub maximized: bool,
|
||||
/// Whether the window is visible or not.
|
||||
#[serde(default = "default_visible")]
|
||||
pub visible: bool,
|
||||
/// Whether the window should have borders and bars.
|
||||
#[serde(default = "default_decorations")]
|
||||
pub decorations: bool,
|
||||
/// Whether the window should always be on top of other windows.
|
||||
#[serde(default)]
|
||||
pub always_on_top: bool,
|
||||
}
|
||||
|
||||
fn default_width() -> i32 {
|
||||
800
|
||||
fn default_window_label() -> String {
|
||||
"main".to_string()
|
||||
}
|
||||
|
||||
fn default_height() -> i32 {
|
||||
600
|
||||
fn default_width() -> f64 {
|
||||
800f64
|
||||
}
|
||||
|
||||
fn default_height() -> f64 {
|
||||
600f64
|
||||
}
|
||||
|
||||
fn default_resizable() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_visible() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_decorations() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_title() -> String {
|
||||
"Tauri App".to_string()
|
||||
}
|
||||
@@ -46,11 +132,24 @@ fn default_title() -> String {
|
||||
impl Default for WindowConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
label: default_window_label(),
|
||||
url: WindowUrl::App,
|
||||
x: None,
|
||||
y: None,
|
||||
width: default_width(),
|
||||
height: default_height(),
|
||||
min_width: None,
|
||||
min_height: None,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
resizable: default_resizable(),
|
||||
title: default_title(),
|
||||
fullscreen: false,
|
||||
transparent: false,
|
||||
maximized: false,
|
||||
visible: default_visible(),
|
||||
decorations: default_decorations(),
|
||||
always_on_top: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,13 +434,17 @@ impl Default for BundleConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_window_config() -> Vec<WindowConfig> {
|
||||
vec![Default::default()]
|
||||
}
|
||||
|
||||
/// The Tauri configuration object.
|
||||
#[derive(PartialEq, Deserialize, Debug)]
|
||||
#[serde(tag = "tauri", rename_all = "camelCase")]
|
||||
pub struct TauriConfig {
|
||||
/// The window configuration.
|
||||
#[serde(default)]
|
||||
pub window: WindowConfig,
|
||||
#[serde(default = "default_window_config")]
|
||||
pub windows: Vec<WindowConfig>,
|
||||
/// The embeddedServer configuration.
|
||||
#[serde(default)]
|
||||
pub embedded_server: EmbeddedServerConfig,
|
||||
@@ -356,7 +459,7 @@ pub struct TauriConfig {
|
||||
impl Default for TauriConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
window: WindowConfig::default(),
|
||||
windows: default_window_config(),
|
||||
embedded_server: EmbeddedServerConfig::default(),
|
||||
cli: None,
|
||||
bundle: BundleConfig::default(),
|
||||
@@ -441,7 +544,7 @@ mod test {
|
||||
// get default embedded server
|
||||
let de_server = EmbeddedServerConfig::default();
|
||||
// get default window
|
||||
let d_window = WindowConfig::default();
|
||||
let d_windows = default_window_config();
|
||||
// get default title
|
||||
let d_title = default_title();
|
||||
// get default bundle
|
||||
@@ -449,13 +552,26 @@ mod test {
|
||||
|
||||
// create a tauri config.
|
||||
let tauri = TauriConfig {
|
||||
window: WindowConfig {
|
||||
width: 800,
|
||||
height: 600,
|
||||
windows: vec![WindowConfig {
|
||||
label: "main".to_string(),
|
||||
url: WindowUrl::App,
|
||||
x: None,
|
||||
y: None,
|
||||
width: 800f64,
|
||||
height: 600f64,
|
||||
min_width: None,
|
||||
min_height: None,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
resizable: true,
|
||||
title: String::from("Tauri App"),
|
||||
fullscreen: false,
|
||||
},
|
||||
transparent: false,
|
||||
maximized: false,
|
||||
visible: true,
|
||||
decorations: true,
|
||||
always_on_top: false,
|
||||
}],
|
||||
embedded_server: EmbeddedServerConfig {
|
||||
host: String::from("http://127.0.0.1"),
|
||||
port: Port::Random,
|
||||
@@ -479,7 +595,7 @@ mod test {
|
||||
assert_eq!(de_server, tauri.embedded_server);
|
||||
assert_eq!(d_bundle, tauri.bundle);
|
||||
assert_eq!(d_path, String::from("http://localhost:8080"));
|
||||
assert_eq!(d_title, tauri.window.title);
|
||||
assert_eq!(d_window, tauri.window);
|
||||
assert_eq!(d_title, tauri.windows[0].title);
|
||||
assert_eq!(d_windows, tauri.windows);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ thiserror = "1.0.23"
|
||||
once_cell = "1.5.2"
|
||||
tauri-api = { version = "0.7.5", path = "../tauri-api" }
|
||||
tauri-macros = { version = "0.1", path = "../tauri-macros" }
|
||||
wry = { git = "https://github.com/tauri-apps/wry", rev = "42f4f2133f7921ed5adc47908787094da8abeac5" }
|
||||
wry = { git = "https://github.com/tauri-apps/wry", rev = "f4edf89de5dc40b77a94f6b94fcaf76fdac6bbf4" }
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
runas = "0.2"
|
||||
@@ -74,3 +74,7 @@ notification = [ "tauri-api/notification" ]
|
||||
[[example]]
|
||||
name = "communication"
|
||||
path = "examples/communication/src-tauri/src/main.rs"
|
||||
|
||||
[[example]]
|
||||
name = "multiwindow"
|
||||
path = "examples/multiwindow/src-tauri/src/main.rs"
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
<link rel='icon' type='image/png' href='favicon.png'>
|
||||
<link rel='stylesheet' href='global.css'>
|
||||
<!-- <link rel='stylesheet' href='build/bundle.css'> -->
|
||||
|
||||
<script defer src='build/bundle.js'></script>
|
||||
</head>
|
||||
|
||||
3
tauri/examples/api/src-tauri/Cargo.lock
generated
@@ -3094,11 +3094,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.4.1"
|
||||
source = "git+https://github.com/tauri-apps/wry?rev=42f4f2133f7921ed5adc47908787094da8abeac5#42f4f2133f7921ed5adc47908787094da8abeac5"
|
||||
source = "git+https://github.com/tauri-apps/wry?rev=f4edf89de5dc40b77a94f6b94fcaf76fdac6bbf4#f4edf89de5dc40b77a94f6b94fcaf76fdac6bbf4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cocoa",
|
||||
"core-graphics 0.22.2",
|
||||
"gdk",
|
||||
"gio",
|
||||
"glib",
|
||||
"gtk",
|
||||
|
||||
@@ -17,19 +17,21 @@ struct Context;
|
||||
|
||||
fn main() {
|
||||
tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
|
||||
.setup(|dispatcher, _source| async move {
|
||||
let mut dispatcher = dispatcher.clone();
|
||||
tauri::event::listen(String::from("js-event"), move |msg| {
|
||||
.setup(|webview_manager| async move {
|
||||
let dispatcher = webview_manager.current_webview().unwrap();
|
||||
let dispatcher_ = dispatcher.clone();
|
||||
dispatcher.listen("js-event", move |msg| {
|
||||
println!("got js-event with message '{:?}'", msg);
|
||||
let reply = Reply {
|
||||
data: "something else".to_string(),
|
||||
};
|
||||
|
||||
tauri::event::emit(&mut dispatcher, String::from("rust-event"), Some(reply))
|
||||
dispatcher_
|
||||
.emit("rust-event", Some(reply))
|
||||
.expect("failed to emit");
|
||||
});
|
||||
})
|
||||
.invoke_handler(|mut dispatcher, arg| async move {
|
||||
.invoke_handler(|webview_manager, arg| async move {
|
||||
use cmd::Cmd::*;
|
||||
match serde_json::from_str(&arg) {
|
||||
Err(e) => Err(e.to_string()),
|
||||
@@ -47,7 +49,7 @@ fn main() {
|
||||
// tauri::execute_promise is a helper for APIs that uses the tauri.promisified JS function
|
||||
// so you can easily communicate between JS and Rust with promises
|
||||
tauri::execute_promise(
|
||||
&mut dispatcher,
|
||||
&webview_manager,
|
||||
async move {
|
||||
println!("{} {:?}", endpoint, body);
|
||||
// perform an async operation here
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"build": {
|
||||
"distDir": "../public",
|
||||
"devPath": "../public",
|
||||
"devPath": "http://localhost:5000",
|
||||
"withGlobalTauri": true,
|
||||
"beforeBuildCommand": "yarn build",
|
||||
"beforeDevCommand": "yarn dev"
|
||||
@@ -57,9 +57,9 @@
|
||||
"allowlist": {
|
||||
"all": true
|
||||
},
|
||||
"window": {
|
||||
"windows": [{
|
||||
"title": "Tauri API Validation"
|
||||
},
|
||||
}],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
}
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
import { setTitle, open } from "@tauri-apps/api/window";
|
||||
|
||||
let urlValue = "https://tauri.studio";
|
||||
let windowTitle = 'Awesome Tauri Example!';
|
||||
|
||||
function openUrl() {
|
||||
open(urlValue);
|
||||
}
|
||||
|
||||
function setWindowTitle() {
|
||||
setTitle(urlValue);
|
||||
setTitle(windowTitle);
|
||||
}
|
||||
</script>
|
||||
|
||||
<form style="margin-top: 24px" on:submit|preventDefault={setWindowTitle}>
|
||||
<input id="title" value="Awesome Tauri Example!" />
|
||||
<input id="title" bind:value={windowTitle} />
|
||||
<button class="button" type="submit">Set title</button>
|
||||
</form>
|
||||
<form style="margin-top: 24px" on:submit|preventDefault={openUrl}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
document.getElementById("log").addEventListener("click", function () {
|
||||
console.log('log')
|
||||
window.__TAURI__.tauri.invoke({
|
||||
cmd: "logOperation",
|
||||
event: "tauri-click",
|
||||
|
||||
@@ -18,19 +18,21 @@ struct Context;
|
||||
|
||||
fn main() {
|
||||
tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
|
||||
.setup(|dispatcher, _source| async move {
|
||||
let mut dispatcher = dispatcher.clone();
|
||||
tauri::event::listen(String::from("js-event"), move |msg| {
|
||||
.setup(|webview_manager| async move {
|
||||
let current_webview = webview_manager.current_webview().unwrap().clone();
|
||||
let current_webview_ = current_webview.clone();
|
||||
current_webview.listen(String::from("js-event"), move |msg| {
|
||||
println!("got js-event with message '{:?}'", msg);
|
||||
let reply = Reply {
|
||||
data: "something else".to_string(),
|
||||
};
|
||||
|
||||
tauri::event::emit(&mut dispatcher, String::from("rust-event"), Some(reply))
|
||||
current_webview_
|
||||
.emit(String::from("rust-event"), Some(reply))
|
||||
.expect("failed to emit");
|
||||
});
|
||||
})
|
||||
.invoke_handler(|mut dispatcher, arg| async move {
|
||||
.invoke_handler(|webview_manager, arg| async move {
|
||||
use cmd::Cmd::*;
|
||||
match serde_json::from_str(&arg) {
|
||||
Err(e) => Err(e.to_string()),
|
||||
@@ -48,7 +50,7 @@ fn main() {
|
||||
// tauri::execute_promise is a helper for APIs that uses the tauri.promisified JS function
|
||||
// so you can easily communicate between JS and Rust with promises
|
||||
tauri::execute_promise(
|
||||
&mut dispatcher,
|
||||
&webview_manager,
|
||||
async move {
|
||||
println!("{} {:?}", endpoint, body);
|
||||
// perform an async operation here
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
"devPath": "../dist",
|
||||
"devPath": "http://localhost:4000",
|
||||
"withGlobalTauri": true
|
||||
},
|
||||
"ctx": {},
|
||||
@@ -56,9 +56,10 @@
|
||||
"allowlist": {
|
||||
"all": true
|
||||
},
|
||||
"window": {
|
||||
"title": "Tauri API Validation"
|
||||
},
|
||||
"windows": [{
|
||||
"title": "Tauri API Validation",
|
||||
"resizable": false
|
||||
}],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
}
|
||||
|
||||
287
tauri/examples/multiwindow/dist/__tauri.js
vendored
Normal file
36
tauri/examples/multiwindow/dist/index.html
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<body>
|
||||
<div id="window-label"></div>
|
||||
<div id="container"></div>
|
||||
<div id="response"></div>
|
||||
|
||||
<script>
|
||||
var windowLabel = window.__TAURI__.currentWindow.label
|
||||
var windowLabelContainer = document.getElementById('window-label')
|
||||
windowLabelContainer.innerHTML = 'This is the ' + windowLabel + ' window.'
|
||||
|
||||
var responseContainer = document.getElementById('response')
|
||||
window.__TAURI__.event.listen('window://' + windowLabel, function (event) {
|
||||
responseContainer.innerHTML = 'Got ' + JSON.stringify(event)
|
||||
})
|
||||
|
||||
var container = document.getElementById('container')
|
||||
for (var index in window.__TAURI__.windows) {
|
||||
const label = window.__TAURI__.windows[index].label
|
||||
if (label === windowLabel) {
|
||||
continue;
|
||||
}
|
||||
const button = document.createElement('button')
|
||||
button.innerHTML = 'Send message to ' + label
|
||||
button.addEventListener('click', function () {
|
||||
window.__TAURI__.event.emit('window://' + label, 'message from ' + windowLabel)
|
||||
})
|
||||
container.appendChild(button)
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
11
tauri/examples/multiwindow/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "communication-example",
|
||||
"version": "1.0.0",
|
||||
"description": "A Tauri example showcasing the JS-Rust communication",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "node ../../../cli/tauri.js/bin/tauri build"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
10
tauri/examples/multiwindow/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
WixTools
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
config.json
|
||||
bundle.json
|
||||
42
tauri/examples/multiwindow/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,42 @@
|
||||
workspace = { }
|
||||
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri Multiwindow App"
|
||||
authors = [ "you" ]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "app"
|
||||
edition = "2018"
|
||||
build = "src/build.rs"
|
||||
|
||||
[package.metadata.bundle]
|
||||
identifier = "com.tauri.dev"
|
||||
icon = [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tauri = { path = "../../..", features =["all-api", "cli"]}
|
||||
|
||||
[target."cfg(windows)".build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[features]
|
||||
embedded-server = [ "tauri/embedded-server" ]
|
||||
|
||||
[[bin]]
|
||||
name = "app"
|
||||
path = "src/main.rs"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
incremental = false
|
||||
opt-level = "z"
|
||||
BIN
tauri/examples/multiwindow/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/icon.icns
Normal file
BIN
tauri/examples/multiwindow/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
tauri/examples/multiwindow/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
14
tauri/examples/multiwindow/src-tauri/rustfmt.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
max_width = 100
|
||||
hard_tabs = false
|
||||
tab_spaces = 2
|
||||
newline_style = "Auto"
|
||||
use_small_heuristics = "Default"
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
remove_nested_parens = true
|
||||
edition = "2018"
|
||||
merge_derives = true
|
||||
use_try_shorthand = false
|
||||
use_field_init_shorthand = false
|
||||
force_explicit_abi = true
|
||||
imports_granularity = "Crate"
|
||||
16
tauri/examples/multiwindow/src-tauri/src/build.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
#[cfg(windows)]
|
||||
extern crate winres;
|
||||
|
||||
#[cfg(windows)]
|
||||
fn main() {
|
||||
if std::path::Path::new("icons/icon.ico").exists() {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon_with_id("icons/icon.ico", "32512");
|
||||
res.compile().expect("Unable to find visual studio tools");
|
||||
} else {
|
||||
panic!("No Icon.ico found. Please add one or check the path");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn main() {}
|
||||
25
tauri/examples/multiwindow/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
#[derive(tauri::FromTauriContext)]
|
||||
#[config_path = "examples/multiwindow/src-tauri/tauri.conf.json"]
|
||||
struct Context;
|
||||
|
||||
fn main() {
|
||||
tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
|
||||
.setup(|webview_manager| async move {
|
||||
let current_webview = webview_manager.current_webview().unwrap().clone();
|
||||
let current_webview_ = current_webview.clone();
|
||||
let event_name = format!("window://{}", webview_manager.current_window_label());
|
||||
current_webview.listen(event_name.clone(), move |msg| {
|
||||
current_webview_
|
||||
.emit(&event_name, msg)
|
||||
.expect("failed to emit");
|
||||
});
|
||||
})
|
||||
.build()
|
||||
.unwrap()
|
||||
.run();
|
||||
}
|
||||
44
tauri/examples/multiwindow/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
"devPath": "../dist",
|
||||
"withGlobalTauri": true
|
||||
},
|
||||
"tauri": {
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "com.tauri.dev",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"copyright": "",
|
||||
"category": "DeveloperTool"
|
||||
},
|
||||
"allowlist": {
|
||||
"all": true
|
||||
},
|
||||
"windows": [{
|
||||
"label": "Main",
|
||||
"url": "app",
|
||||
"title": "Tauri - Main",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}, {
|
||||
"label": "Secondary",
|
||||
"url": "app",
|
||||
"title": "Tauri - Secondary",
|
||||
"width": 600,
|
||||
"height": 400
|
||||
}],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
tauri/examples/multiwindow/yarn.lock
Normal file
@@ -0,0 +1,4 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
@@ -3,10 +3,15 @@ use futures::future::BoxFuture;
|
||||
use std::marker::PhantomData;
|
||||
use tauri_api::{config::Config, private::AsTauriContext};
|
||||
|
||||
pub(crate) mod event;
|
||||
mod runner;
|
||||
mod webview_manager;
|
||||
|
||||
type InvokeHandler<W> = dyn Fn(W, String) -> BoxFuture<'static, Result<(), String>> + Send + Sync;
|
||||
type Setup<W> = dyn Fn(W, String) -> BoxFuture<'static, ()> + Send + Sync;
|
||||
pub use webview_manager::{WebviewDispatcher, WebviewManager};
|
||||
|
||||
type InvokeHandler<D> =
|
||||
dyn Fn(WebviewManager<D>, String) -> BoxFuture<'static, Result<(), String>> + Send + Sync;
|
||||
type Setup<D> = dyn Fn(WebviewManager<D>) -> BoxFuture<'static, ()> + Send + Sync;
|
||||
|
||||
/// `App` runtime information.
|
||||
pub struct Context {
|
||||
@@ -31,8 +36,6 @@ pub struct App<A: ApplicationExt> {
|
||||
invoke_handler: Option<Box<InvokeHandler<A::Dispatcher>>>,
|
||||
/// The setup callback, invoked when the webview is ready.
|
||||
setup: Option<Box<Setup<A::Dispatcher>>>,
|
||||
/// The HTML of the splashscreen to render.
|
||||
splashscreen_html: Option<String>,
|
||||
/// The context the App was created with
|
||||
pub(crate) context: Context,
|
||||
}
|
||||
@@ -48,7 +51,7 @@ impl<A: ApplicationExt + 'static> App<A> {
|
||||
/// The message is considered consumed if the handler exists and returns an Ok Result.
|
||||
pub(crate) async fn run_invoke_handler(
|
||||
&self,
|
||||
dispatcher: &mut A::Dispatcher,
|
||||
dispatcher: &WebviewManager<A::Dispatcher>,
|
||||
arg: &str,
|
||||
) -> Result<bool, String> {
|
||||
if let Some(ref invoke_handler) = self.invoke_handler {
|
||||
@@ -60,17 +63,12 @@ impl<A: ApplicationExt + 'static> App<A> {
|
||||
}
|
||||
|
||||
/// Runs the setup callback if defined.
|
||||
pub(crate) async fn run_setup(&self, dispatcher: &mut A::Dispatcher, source: String) {
|
||||
pub(crate) async fn run_setup(&self, dispatcher: &WebviewManager<A::Dispatcher>) {
|
||||
if let Some(ref setup) = self.setup {
|
||||
let fut = setup(dispatcher.clone(), source);
|
||||
let fut = setup(dispatcher.clone());
|
||||
fut.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the splashscreen HTML.
|
||||
pub fn splashscreen_html(&self) -> Option<&String> {
|
||||
self.splashscreen_html.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// The App builder.
|
||||
@@ -80,8 +78,6 @@ pub struct AppBuilder<A: ApplicationExt, C: AsTauriContext> {
|
||||
invoke_handler: Option<Box<InvokeHandler<A::Dispatcher>>>,
|
||||
/// The setup callback, invoked when the webview is ready.
|
||||
setup: Option<Box<Setup<A::Dispatcher>>>,
|
||||
/// The HTML of the splashscreen to render.
|
||||
splashscreen_html: Option<String>,
|
||||
/// The configuration used
|
||||
config: PhantomData<C>,
|
||||
}
|
||||
@@ -92,7 +88,6 @@ impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
|
||||
Self {
|
||||
invoke_handler: None,
|
||||
setup: None,
|
||||
splashscreen_html: None,
|
||||
config: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -100,13 +95,13 @@ impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
|
||||
/// Defines the JS message handler callback.
|
||||
pub fn invoke_handler<
|
||||
T: futures::Future<Output = Result<(), String>> + Send + Sync + 'static,
|
||||
F: Fn(A::Dispatcher, String) -> T + Send + Sync + 'static,
|
||||
F: Fn(WebviewManager<A::Dispatcher>, String) -> T + Send + Sync + 'static,
|
||||
>(
|
||||
mut self,
|
||||
invoke_handler: F,
|
||||
) -> Self {
|
||||
self.invoke_handler = Some(Box::new(move |dispatcher, arg| {
|
||||
Box::pin(invoke_handler(dispatcher, arg))
|
||||
self.invoke_handler = Some(Box::new(move |webview_manager, arg| {
|
||||
Box::pin(invoke_handler(webview_manager, arg))
|
||||
}));
|
||||
self
|
||||
}
|
||||
@@ -114,23 +109,17 @@ impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
|
||||
/// Defines the setup callback.
|
||||
pub fn setup<
|
||||
T: futures::Future<Output = ()> + Send + Sync + 'static,
|
||||
F: Fn(A::Dispatcher, String) -> T + Send + Sync + 'static,
|
||||
F: Fn(WebviewManager<A::Dispatcher>) -> T + Send + Sync + 'static,
|
||||
>(
|
||||
mut self,
|
||||
setup: F,
|
||||
) -> Self {
|
||||
self.setup = Some(Box::new(move |dispatcher, source| {
|
||||
Box::pin(setup(dispatcher, source))
|
||||
self.setup = Some(Box::new(move |webview_manager| {
|
||||
Box::pin(setup(webview_manager))
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the splashscreen HTML to render.
|
||||
pub fn splashscreen_html(mut self, html: &str) -> Self {
|
||||
self.splashscreen_html = Some(html.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a plugin to the runtime.
|
||||
pub fn plugin(
|
||||
self,
|
||||
@@ -145,7 +134,6 @@ impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
|
||||
Ok(App {
|
||||
invoke_handler: self.invoke_handler,
|
||||
setup: self.setup,
|
||||
splashscreen_html: self.splashscreen_html,
|
||||
context: Context::new::<C>()?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ struct EventHandler {
|
||||
on_event: Box<dyn FnMut(Option<String>) + Send>,
|
||||
}
|
||||
|
||||
type Listeners = Arc<Mutex<HashMap<String, EventHandler>>>;
|
||||
type Listeners = Arc<Mutex<HashMap<String, Vec<EventHandler>>>>;
|
||||
|
||||
lazy_static! {
|
||||
static ref EMIT_FUNCTION_NAME: String = uuid::Uuid::new_v4().to_string();
|
||||
@@ -46,22 +46,24 @@ pub fn event_queue_object_name() -> String {
|
||||
}
|
||||
|
||||
/// Adds an event listener for JS events.
|
||||
pub fn listen<F: FnMut(Option<String>) + Send + 'static>(id: impl Into<String>, handler: F) {
|
||||
pub fn listen<F: FnMut(Option<String>) + Send + 'static>(id: impl AsRef<str>, handler: F) {
|
||||
let mut l = listeners()
|
||||
.lock()
|
||||
.expect("Failed to lock listeners: listen()");
|
||||
l.insert(
|
||||
id.into(),
|
||||
EventHandler {
|
||||
on_event: Box::new(handler),
|
||||
},
|
||||
);
|
||||
let handler = EventHandler {
|
||||
on_event: Box::new(handler),
|
||||
};
|
||||
if let Some(listeners) = l.get_mut(id.as_ref()) {
|
||||
listeners.push(handler);
|
||||
} else {
|
||||
l.insert(id.as_ref().to_string(), vec![handler]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Emits an event to JS.
|
||||
pub fn emit<D: ApplicationDispatcherExt, S: Serialize>(
|
||||
dispatcher: &mut D,
|
||||
event: impl AsRef<str> + Send + 'static,
|
||||
webview_dispatcher: &crate::WebviewDispatcher<D>,
|
||||
event: impl AsRef<str>,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
let salt = crate::salt::generate();
|
||||
@@ -72,7 +74,7 @@ pub fn emit<D: ApplicationDispatcherExt, S: Serialize>(
|
||||
JsonValue::Null
|
||||
};
|
||||
|
||||
dispatcher.eval(&format!(
|
||||
webview_dispatcher.eval(&format!(
|
||||
"window['{}']({{type: '{}', payload: {}}}, '{}')",
|
||||
emit_function_name(),
|
||||
event.as_ref(),
|
||||
@@ -90,14 +92,16 @@ pub fn on_event(event: String, data: Option<String>) {
|
||||
.expect("Failed to lock listeners: on_event()");
|
||||
|
||||
if l.contains_key(&event) {
|
||||
let handler = l.get_mut(&event).expect("Failed to get mutable handler");
|
||||
(handler.on_event)(data);
|
||||
let listeners = l.get_mut(&event).expect("Failed to get mutable handler");
|
||||
for handler in listeners {
|
||||
(handler.on_event)(data.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::event::*;
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
// dummy event handler function
|
||||
@@ -1,16 +1,13 @@
|
||||
#[cfg(dev)]
|
||||
use std::io::Read;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
#[cfg(dev)]
|
||||
use crate::api::assets::{AssetFetch, Assets};
|
||||
|
||||
use crate::{ApplicationDispatcherExt, ApplicationExt, WebviewBuilderExt, WindowBuilderExt};
|
||||
use crate::{api::config::WindowUrl, ApplicationExt, WebviewBuilderExt};
|
||||
|
||||
use super::App;
|
||||
use super::{App, WebviewDispatcher, WebviewManager};
|
||||
#[cfg(embedded_server)]
|
||||
use crate::api::tcp::{get_available_port, port_is_available};
|
||||
use crate::app::Context;
|
||||
@@ -45,24 +42,8 @@ pub(crate) fn run<A: ApplicationExt + 'static>(application: App<A>) -> crate::Re
|
||||
spawn_server(server_url, &application.context);
|
||||
}
|
||||
|
||||
let splashscreen_content = if application.splashscreen_html().is_some() {
|
||||
Some(Content::Html(
|
||||
application
|
||||
.splashscreen_html()
|
||||
.expect("failed to get splashscreen_html")
|
||||
.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// build the webview
|
||||
let (webview_application, mut dispatcher) =
|
||||
build_webview(application, main_content, splashscreen_content)?;
|
||||
|
||||
crate::async_runtime::spawn(async move {
|
||||
crate::plugin::created(A::plugin_store(), &mut dispatcher).await
|
||||
});
|
||||
let webview_application = build_webview(application, main_content)?;
|
||||
|
||||
// spin up the updater process
|
||||
#[cfg(feature = "updater")]
|
||||
@@ -250,28 +231,12 @@ pub fn event_initialization_script() -> String {
|
||||
fn build_webview<A: ApplicationExt + 'static>(
|
||||
application: App<A>,
|
||||
content: Content<String>,
|
||||
splashscreen_content: Option<Content<String>>,
|
||||
) -> crate::Result<(A, A::Dispatcher)> {
|
||||
let config = &application.context.config;
|
||||
) -> crate::Result<A> {
|
||||
// TODO let debug = cfg!(debug_assertions);
|
||||
// get properties from config struct
|
||||
// TODO let width = config.tauri.window.width;
|
||||
// TODO let height = config.tauri.window.height;
|
||||
let resizable = config.tauri.window.resizable;
|
||||
// let fullscreen = config.tauri.window.fullscreen;
|
||||
let title = config.tauri.window.title.clone();
|
||||
|
||||
let has_splashscreen = splashscreen_content.is_some();
|
||||
let initialized_splashscreen = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let content_url = match content {
|
||||
Content::Html(s) => s,
|
||||
Content::Url(s) => s,
|
||||
};
|
||||
let url = match splashscreen_content {
|
||||
Some(Content::Html(s)) => s,
|
||||
_ => content_url.to_string(),
|
||||
};
|
||||
|
||||
let initialization_script = format!(
|
||||
r#"
|
||||
@@ -296,90 +261,102 @@ fn build_webview<A: ApplicationExt + 'static>(
|
||||
|
||||
let mut webview_application = A::new()?;
|
||||
|
||||
let main_window =
|
||||
webview_application.create_window(A::WindowBuilder::new().resizable(resizable).title(title))?;
|
||||
let mut window_refs = Vec::new();
|
||||
let mut dispatchers = HashMap::new();
|
||||
|
||||
let dispatcher = webview_application.dispatcher(&main_window);
|
||||
for window_config in application.context.config.tauri.windows.clone() {
|
||||
let window = crate::webview::WindowBuilder::from(&window_config);
|
||||
let window = webview_application.create_window(window.get())?;
|
||||
let dispatcher = webview_application.dispatcher(&window);
|
||||
dispatchers.insert(
|
||||
window_config.label.to_string(),
|
||||
WebviewDispatcher::new(dispatcher),
|
||||
);
|
||||
window_refs.push((window_config, window));
|
||||
}
|
||||
|
||||
let tauri_invoke_handler = crate::Callback::<A::Dispatcher> {
|
||||
name: "__TAURI_INVOKE_HANDLER__".to_string(),
|
||||
function: Box::new(move |dispatcher, _, arg| {
|
||||
let arg = arg.into_iter().next().unwrap_or_else(String::new);
|
||||
let application = application.clone();
|
||||
let mut dispatcher = dispatcher.clone();
|
||||
let content_url = content_url.to_string();
|
||||
let initialized_splashscreen = initialized_splashscreen.clone();
|
||||
for (window_config, window) in window_refs {
|
||||
let webview_manager = WebviewManager::new(dispatchers.clone(), window_config.label.to_string());
|
||||
|
||||
crate::async_runtime::spawn(async move {
|
||||
if arg == r#"{"cmd":"__initialized"}"# {
|
||||
let source = if has_splashscreen && !initialized_splashscreen.load(Ordering::Relaxed) {
|
||||
initialized_splashscreen.swap(true, Ordering::Relaxed);
|
||||
"splashscreen"
|
||||
let application = application.clone();
|
||||
let content_url = content_url.to_string();
|
||||
|
||||
let webview_manager_ = webview_manager.clone();
|
||||
let tauri_invoke_handler = crate::Callback::<A::Dispatcher> {
|
||||
name: "__TAURI_INVOKE_HANDLER__".to_string(),
|
||||
function: Box::new(move |_, _, arg| {
|
||||
let arg = arg.into_iter().next().unwrap_or_else(String::new);
|
||||
let application = application.clone();
|
||||
let webview_manager = webview_manager_.clone();
|
||||
|
||||
crate::async_runtime::spawn(async move {
|
||||
if arg == r#"{"cmd":"__initialized"}"# {
|
||||
application.run_setup(&webview_manager).await;
|
||||
crate::plugin::ready(A::plugin_store(), &webview_manager).await;
|
||||
} else {
|
||||
"window-1"
|
||||
};
|
||||
application
|
||||
.run_setup(&mut dispatcher, source.to_string())
|
||||
.await;
|
||||
if source == "window-1" {
|
||||
crate::plugin::ready(A::plugin_store(), &mut dispatcher).await;
|
||||
}
|
||||
} else if arg == r#"{"cmd":"closeSplashscreen"}"# {
|
||||
dispatcher.eval(&format!(r#"window.location.href = "{}""#, content_url));
|
||||
} else {
|
||||
let mut endpoint_handle =
|
||||
crate::endpoints::handle(&mut dispatcher, &arg, &application.context)
|
||||
.await
|
||||
.map_err(|e| e.to_string());
|
||||
if let Err(ref tauri_handle_error) = endpoint_handle {
|
||||
if tauri_handle_error.contains("unknown variant") {
|
||||
let error = match application.run_invoke_handler(&mut dispatcher, &arg).await {
|
||||
let mut endpoint_handle =
|
||||
crate::endpoints::handle(&webview_manager, &arg, &application.context).await;
|
||||
if let Err(crate::Error::UnknownApi) = endpoint_handle {
|
||||
let response = match application.run_invoke_handler(&webview_manager, &arg).await {
|
||||
Ok(handled) => {
|
||||
if handled {
|
||||
String::from("")
|
||||
Ok(())
|
||||
} else {
|
||||
tauri_handle_error.to_string()
|
||||
endpoint_handle
|
||||
}
|
||||
}
|
||||
Err(e) => e,
|
||||
Err(_) => Err(crate::Error::UnknownApi),
|
||||
};
|
||||
endpoint_handle = Err(error);
|
||||
endpoint_handle = response;
|
||||
}
|
||||
if let Err(crate::Error::UnknownApi) = endpoint_handle {
|
||||
endpoint_handle =
|
||||
crate::plugin::extend_api(A::plugin_store(), &webview_manager, &arg)
|
||||
.await
|
||||
.map(|_| ());
|
||||
}
|
||||
if let Err(handler_error_message) = endpoint_handle {
|
||||
let handler_error_message = handler_error_message.to_string().replace("'", "\\'");
|
||||
if !handler_error_message.is_empty() {
|
||||
if let Ok(dispatcher) = webview_manager.current_webview() {
|
||||
dispatcher.eval(&get_api_error_message(&arg, handler_error_message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Err(ref app_handle_error) = endpoint_handle {
|
||||
if app_handle_error.contains("unknown variant") {
|
||||
let error =
|
||||
match crate::plugin::extend_api(A::plugin_store(), &mut dispatcher, &arg).await {
|
||||
Ok(_) => String::from(""),
|
||||
Err(e) => e.to_string(),
|
||||
};
|
||||
endpoint_handle = Err(error);
|
||||
}
|
||||
}
|
||||
endpoint_handle = endpoint_handle.map_err(|e| e.replace("'", "\\'"));
|
||||
if let Err(handler_error_message) = endpoint_handle {
|
||||
if !handler_error_message.is_empty() {
|
||||
dispatcher.eval(&get_api_error_message(&arg, handler_error_message));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
0
|
||||
}),
|
||||
};
|
||||
});
|
||||
0
|
||||
}),
|
||||
};
|
||||
|
||||
webview_application.create_webview(
|
||||
A::WebviewBuilder::new()
|
||||
.url(url)
|
||||
.initialization_script(&initialization_script),
|
||||
main_window,
|
||||
vec![tauri_invoke_handler],
|
||||
)?;
|
||||
let webview_url = match &window_config.url {
|
||||
WindowUrl::App => content_url.to_string(),
|
||||
WindowUrl::Custom(url) => url.to_string(),
|
||||
};
|
||||
|
||||
// TODO waiting for webview window API
|
||||
// webview.set_fullscreen(fullscreen);
|
||||
webview_application.create_webview(
|
||||
A::WebviewBuilder::new()
|
||||
.url(webview_url)
|
||||
.initialization_script(&initialization_script)
|
||||
.initialization_script(&format!(
|
||||
r#"
|
||||
window.__TAURI__.windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
|
||||
window.__TAURI__.currentWindow = {{ label: "{current_window_label}" }}
|
||||
"#,
|
||||
window_labels_array =
|
||||
serde_json::to_string(&dispatchers.keys().collect::<Vec<&String>>()).unwrap(),
|
||||
current_window_label = window_config.label,
|
||||
)),
|
||||
window,
|
||||
vec![tauri_invoke_handler],
|
||||
)?;
|
||||
|
||||
Ok((webview_application, dispatcher))
|
||||
crate::async_runtime::spawn(async move {
|
||||
crate::plugin::created(A::plugin_store(), &webview_manager).await
|
||||
});
|
||||
}
|
||||
|
||||
Ok(webview_application)
|
||||
}
|
||||
|
||||
// Formats an invoke handler error message to print to console.error
|
||||
|
||||
90
tauri/src/app/webview_manager.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
webview::{Event, Message},
|
||||
ApplicationDispatcherExt,
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// The webview dispatcher.
|
||||
#[derive(Clone)]
|
||||
pub struct WebviewDispatcher<A: Clone>(A);
|
||||
|
||||
impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
|
||||
pub(crate) fn new(dispatcher: A) -> Self {
|
||||
Self(dispatcher)
|
||||
}
|
||||
|
||||
pub(crate) fn send_event(&self, event: Event) {
|
||||
self.0.send_message(Message::Event(event))
|
||||
}
|
||||
|
||||
/// Listen to an event.
|
||||
pub fn listen<F: FnMut(Option<String>) + Send + 'static>(
|
||||
&self,
|
||||
event: impl AsRef<str>,
|
||||
handler: F,
|
||||
) {
|
||||
super::event::listen(event, handler)
|
||||
}
|
||||
|
||||
/// Emits an event.
|
||||
pub fn emit<S: Serialize>(
|
||||
&self,
|
||||
event: impl AsRef<str>,
|
||||
payload: Option<S>,
|
||||
) -> crate::Result<()> {
|
||||
super::event::emit(&self, event, payload)
|
||||
}
|
||||
|
||||
pub(crate) fn on_event(&self, event: String, data: Option<String>) {
|
||||
super::event::on_event(event, data)
|
||||
}
|
||||
|
||||
/// Evaluates a JS script.
|
||||
pub fn eval(&self, js: &str) {
|
||||
self.0.send_message(Message::EvalScript(js.to_string()))
|
||||
}
|
||||
|
||||
/// Updates the window title.
|
||||
pub fn set_title(&self, title: &str) {
|
||||
self
|
||||
.0
|
||||
.send_message(Message::SetWindowTitle(title.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// The webview manager.
|
||||
#[derive(Clone)]
|
||||
pub struct WebviewManager<A: Clone> {
|
||||
dispatchers: HashMap<String, WebviewDispatcher<A>>,
|
||||
current_webview_window_label: String,
|
||||
}
|
||||
|
||||
impl<A: ApplicationDispatcherExt> WebviewManager<A> {
|
||||
pub(crate) fn new(dispatchers: HashMap<String, WebviewDispatcher<A>>, label: String) -> Self {
|
||||
Self {
|
||||
dispatchers,
|
||||
current_webview_window_label: label,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the label of the window associated with the current context.
|
||||
pub fn current_window_label(&self) -> &str {
|
||||
&self.current_webview_window_label
|
||||
}
|
||||
|
||||
/// Gets the webview associated with the current context.
|
||||
pub fn current_webview(&self) -> crate::Result<&WebviewDispatcher<A>> {
|
||||
self.get_webview(&self.current_webview_window_label)
|
||||
}
|
||||
|
||||
/// Gets the webview associated with the given window label.
|
||||
pub fn get_webview(&self, window_label: &str) -> crate::Result<&WebviewDispatcher<A>> {
|
||||
self
|
||||
.dispatchers
|
||||
.get(window_label)
|
||||
.ok_or(crate::Error::WebviewNotFound)
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,11 @@ mod http;
|
||||
#[cfg(notification)]
|
||||
mod notification;
|
||||
|
||||
use crate::{app::Context, ApplicationDispatcherExt, Event};
|
||||
use crate::{app::Context, webview::Event, ApplicationDispatcherExt};
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
arg: &str,
|
||||
context: &Context,
|
||||
) -> crate::Result<()> {
|
||||
@@ -34,9 +34,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(read_text_file)]
|
||||
file_system::read_text_file(dispatcher, path, options, callback, error).await;
|
||||
file_system::read_text_file(webview_manager, path, options, callback, error).await;
|
||||
#[cfg(not(read_text_file))]
|
||||
allowlist_error(dispatcher, error, "readTextFile");
|
||||
allowlist_error(webview_manager, error, "readTextFile");
|
||||
}
|
||||
ReadBinaryFile {
|
||||
path,
|
||||
@@ -45,9 +45,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(read_binary_file)]
|
||||
file_system::read_binary_file(dispatcher, path, options, callback, error).await;
|
||||
file_system::read_binary_file(webview_manager, path, options, callback, error).await;
|
||||
#[cfg(not(read_binary_file))]
|
||||
allowlist_error(dispatcher, error, "readBinaryFile");
|
||||
allowlist_error(webview_manager, error, "readBinaryFile");
|
||||
}
|
||||
WriteFile {
|
||||
path,
|
||||
@@ -57,9 +57,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(write_file)]
|
||||
file_system::write_file(dispatcher, path, contents, options, callback, error).await;
|
||||
file_system::write_file(webview_manager, path, contents, options, callback, error).await;
|
||||
#[cfg(not(write_file))]
|
||||
allowlist_error(dispatcher, error, "writeFile");
|
||||
allowlist_error(webview_manager, error, "writeFile");
|
||||
}
|
||||
WriteBinaryFile {
|
||||
path,
|
||||
@@ -69,10 +69,10 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(write_binary_file)]
|
||||
file_system::write_binary_file(dispatcher, path, contents, options, callback, error)
|
||||
file_system::write_binary_file(webview_manager, path, contents, options, callback, error)
|
||||
.await;
|
||||
#[cfg(not(write_binary_file))]
|
||||
allowlist_error(dispatcher, error, "writeBinaryFile");
|
||||
allowlist_error(webview_manager, error, "writeBinaryFile");
|
||||
}
|
||||
ReadDir {
|
||||
path,
|
||||
@@ -81,9 +81,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(read_dir)]
|
||||
file_system::read_dir(dispatcher, path, options, callback, error).await;
|
||||
file_system::read_dir(webview_manager, path, options, callback, error).await;
|
||||
#[cfg(not(read_dir))]
|
||||
allowlist_error(dispatcher, error, "readDir");
|
||||
allowlist_error(webview_manager, error, "readDir");
|
||||
}
|
||||
CopyFile {
|
||||
source,
|
||||
@@ -93,9 +93,17 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(copy_file)]
|
||||
file_system::copy_file(dispatcher, source, destination, options, callback, error).await;
|
||||
file_system::copy_file(
|
||||
webview_manager,
|
||||
source,
|
||||
destination,
|
||||
options,
|
||||
callback,
|
||||
error,
|
||||
)
|
||||
.await;
|
||||
#[cfg(not(copy_file))]
|
||||
allowlist_error(dispatcher, error, "copyFile");
|
||||
allowlist_error(webview_manager, error, "copyFile");
|
||||
}
|
||||
CreateDir {
|
||||
path,
|
||||
@@ -104,9 +112,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(create_dir)]
|
||||
file_system::create_dir(dispatcher, path, options, callback, error).await;
|
||||
file_system::create_dir(webview_manager, path, options, callback, error).await;
|
||||
#[cfg(not(create_dir))]
|
||||
allowlist_error(dispatcher, error, "createDir");
|
||||
allowlist_error(webview_manager, error, "createDir");
|
||||
}
|
||||
RemoveDir {
|
||||
path,
|
||||
@@ -115,9 +123,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(remove_dir)]
|
||||
file_system::remove_dir(dispatcher, path, options, callback, error).await;
|
||||
file_system::remove_dir(webview_manager, path, options, callback, error).await;
|
||||
#[cfg(not(remove_dir))]
|
||||
allowlist_error(dispatcher, error, "removeDir");
|
||||
allowlist_error(webview_manager, error, "removeDir");
|
||||
}
|
||||
RemoveFile {
|
||||
path,
|
||||
@@ -126,9 +134,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(remove_file)]
|
||||
file_system::remove_file(dispatcher, path, options, callback, error).await;
|
||||
file_system::remove_file(webview_manager, path, options, callback, error).await;
|
||||
#[cfg(not(remove_file))]
|
||||
allowlist_error(dispatcher, error, "removeFile");
|
||||
allowlist_error(webview_manager, error, "removeFile");
|
||||
}
|
||||
RenameFile {
|
||||
old_path,
|
||||
@@ -138,9 +146,17 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(rename_file)]
|
||||
file_system::rename_file(dispatcher, old_path, new_path, options, callback, error).await;
|
||||
file_system::rename_file(
|
||||
webview_manager,
|
||||
old_path,
|
||||
new_path,
|
||||
options,
|
||||
callback,
|
||||
error,
|
||||
)
|
||||
.await;
|
||||
#[cfg(not(rename_file))]
|
||||
allowlist_error(dispatcher, error, "renameFile");
|
||||
allowlist_error(webview_manager, error, "renameFile");
|
||||
}
|
||||
ResolvePath {
|
||||
path,
|
||||
@@ -149,18 +165,14 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(path_api)]
|
||||
path::resolve_path(dispatcher, path, directory, callback, error).await;
|
||||
path::resolve_path(webview_manager, path, directory, callback, error).await;
|
||||
#[cfg(not(path_api))]
|
||||
allowlist_error(dispatcher, error, "pathApi");
|
||||
allowlist_error(webview_manager, error, "pathApi");
|
||||
}
|
||||
SetTitle { title } => {
|
||||
// TODO
|
||||
/*#[cfg(set_title)]
|
||||
webview.dispatch(move |w| {
|
||||
w.set_title(&title);
|
||||
});*/
|
||||
webview_manager.current_webview()?.set_title(&title);
|
||||
#[cfg(not(set_title))]
|
||||
throw_allowlist_error(dispatcher, "title");
|
||||
throw_allowlist_error(webview_manager, "title");
|
||||
}
|
||||
Execute {
|
||||
command,
|
||||
@@ -169,22 +181,22 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(execute)]
|
||||
crate::call(dispatcher, command, args, callback, error).await;
|
||||
crate::call(webview_manager, command, args, callback, error).await;
|
||||
#[cfg(not(execute))]
|
||||
throw_allowlist_error(dispatcher, "execute");
|
||||
throw_allowlist_error(webview_manager, "execute");
|
||||
}
|
||||
Open { uri } => {
|
||||
#[cfg(open)]
|
||||
browser::open(uri);
|
||||
#[cfg(not(open))]
|
||||
throw_allowlist_error(dispatcher, "open");
|
||||
throw_allowlist_error(webview_manager, "open");
|
||||
}
|
||||
ValidateSalt {
|
||||
salt,
|
||||
callback,
|
||||
error,
|
||||
} => {
|
||||
salt::validate(dispatcher, salt, callback, error)?;
|
||||
salt::validate(webview_manager, salt, callback, error)?;
|
||||
}
|
||||
Listen {
|
||||
event,
|
||||
@@ -194,16 +206,17 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
#[cfg(event)]
|
||||
{
|
||||
let js_string = event::listen_fn(event, handler, once)?;
|
||||
dispatcher.eval(&js_string);
|
||||
webview_manager.current_webview()?.eval(&js_string);
|
||||
}
|
||||
#[cfg(not(event))]
|
||||
throw_allowlist_error(dispatcher, "event");
|
||||
throw_allowlist_error(webview_manager, "event");
|
||||
}
|
||||
Emit { event, payload } => {
|
||||
// TODO emit to optional window
|
||||
#[cfg(event)]
|
||||
crate::event::on_event(event, payload);
|
||||
webview_manager.current_webview()?.on_event(event, payload);
|
||||
#[cfg(not(event))]
|
||||
throw_allowlist_error(dispatcher, "event");
|
||||
throw_allowlist_error(webview_manager, "event");
|
||||
}
|
||||
OpenDialog {
|
||||
options,
|
||||
@@ -211,9 +224,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(open_dialog)]
|
||||
dialog::open(dispatcher, options, callback, error)?;
|
||||
dialog::open(webview_manager, options, callback, error)?;
|
||||
#[cfg(not(open_dialog))]
|
||||
allowlist_error(dispatcher, error, "title");
|
||||
allowlist_error(webview_manager, error, "title");
|
||||
}
|
||||
SaveDialog {
|
||||
options,
|
||||
@@ -221,9 +234,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(save_dialog)]
|
||||
dialog::save(dispatcher, options, callback, error)?;
|
||||
dialog::save(webview_manager, options, callback, error)?;
|
||||
#[cfg(not(save_dialog))]
|
||||
throw_allowlist_error(dispatcher, "saveDialog");
|
||||
throw_allowlist_error(webview_manager, "saveDialog");
|
||||
}
|
||||
MessageDialog { message } => {
|
||||
let exe = std::env::current_exe()?;
|
||||
@@ -233,9 +246,11 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
.expect("failed to get exe filename")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
dispatcher.send_event(Event::Run(Box::new(move || {
|
||||
dialog::message(app_name, message);
|
||||
})));
|
||||
webview_manager
|
||||
.current_webview()?
|
||||
.send_event(Event::Run(Box::new(move || {
|
||||
dialog::message(app_name, message);
|
||||
})));
|
||||
}
|
||||
AskDialog {
|
||||
title,
|
||||
@@ -245,7 +260,7 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
} => {
|
||||
let exe = std::env::current_exe()?;
|
||||
dialog::ask(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
title.unwrap_or_else(|| {
|
||||
let exe_dir = exe.parent().expect("failed to get exe directory");
|
||||
exe
|
||||
@@ -265,19 +280,19 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(http_request)]
|
||||
http::make_request(dispatcher, *options, callback, error).await;
|
||||
http::make_request(webview_manager, *options, callback, error).await;
|
||||
#[cfg(not(http_request))]
|
||||
allowlist_error(dispatcher, error, "httpRequest");
|
||||
allowlist_error(webview_manager, error, "httpRequest");
|
||||
}
|
||||
CliMatches { callback, error } => {
|
||||
#[cfg(cli)]
|
||||
{
|
||||
let matches = tauri_api::cli::get_matches(&context.config).map_err(|e| e.into());
|
||||
crate::execute_promise(dispatcher, async move { matches }, callback, error).await;
|
||||
crate::execute_promise(webview_manager, async move { matches }, callback, error).await;
|
||||
}
|
||||
#[cfg(not(cli))]
|
||||
api_error(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
error,
|
||||
"CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)",
|
||||
);
|
||||
@@ -288,21 +303,21 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
error,
|
||||
} => {
|
||||
#[cfg(notification)]
|
||||
notification::send(dispatcher, options, callback, error, &context.config).await;
|
||||
notification::send(webview_manager, options, callback, error, &context.config).await;
|
||||
#[cfg(not(notification))]
|
||||
allowlist_error(dispatcher, error, "notification");
|
||||
allowlist_error(webview_manager, error, "notification");
|
||||
}
|
||||
IsNotificationPermissionGranted { callback, error } => {
|
||||
#[cfg(notification)]
|
||||
notification::is_permission_granted(dispatcher, callback, error).await;
|
||||
notification::is_permission_granted(webview_manager, callback, error).await;
|
||||
#[cfg(not(notification))]
|
||||
allowlist_error(dispatcher, error, "notification");
|
||||
allowlist_error(webview_manager, error, "notification");
|
||||
}
|
||||
RequestNotificationPermission { callback, error } => {
|
||||
#[cfg(notification)]
|
||||
notification::request_permission(dispatcher, callback, error)?;
|
||||
notification::request_permission(webview_manager, callback, error)?;
|
||||
#[cfg(not(notification))]
|
||||
allowlist_error(dispatcher, error, "notification");
|
||||
allowlist_error(webview_manager, error, "notification");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -311,19 +326,25 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn api_error<D: ApplicationDispatcherExt>(dispatcher: &mut D, error_fn: String, message: &str) {
|
||||
fn api_error<D: ApplicationDispatcherExt>(
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
error_fn: String,
|
||||
message: &str,
|
||||
) {
|
||||
let reject_code = tauri_api::rpc::format_callback(error_fn, message);
|
||||
let _ = dispatcher.eval(&reject_code);
|
||||
if let Ok(dispatcher) = webview_manager.current_webview() {
|
||||
dispatcher.eval(&reject_code);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn allowlist_error<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
error_fn: String,
|
||||
allowlist_key: &str,
|
||||
) {
|
||||
api_error(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
error_fn,
|
||||
&format!(
|
||||
"{}' not on the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)",
|
||||
@@ -333,12 +354,17 @@ fn allowlist_error<D: ApplicationDispatcherExt>(
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn throw_allowlist_error<D: ApplicationDispatcherExt>(dispatcher: &mut D, allowlist_key: &str) {
|
||||
fn throw_allowlist_error<D: ApplicationDispatcherExt>(
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
allowlist_key: &str,
|
||||
) {
|
||||
let reject_code = format!(
|
||||
r#"throw new Error("'{}' not on the allowlist")"#,
|
||||
allowlist_key
|
||||
);
|
||||
let _ = dispatcher.eval(&reject_code);
|
||||
if let Ok(dispatcher) = webview_manager.current_webview() {
|
||||
dispatcher.eval(&reject_code);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -21,13 +21,13 @@ fn map_response(response: Response) -> JsonValue {
|
||||
/// Shows an open dialog.
|
||||
#[cfg(open_dialog)]
|
||||
pub fn open<D: ApplicationDispatcherExt + 'static>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
options: OpenDialogOptions,
|
||||
callback: String,
|
||||
error: String,
|
||||
) -> crate::Result<()> {
|
||||
crate::execute_promise_sync(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
move || {
|
||||
let response = if options.multiple {
|
||||
select_multiple(options.filter, options.default_path)
|
||||
@@ -48,13 +48,13 @@ pub fn open<D: ApplicationDispatcherExt + 'static>(
|
||||
/// Shows a save dialog.
|
||||
#[cfg(save_dialog)]
|
||||
pub fn save<D: ApplicationDispatcherExt + 'static>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
options: SaveDialogOptions,
|
||||
callback: String,
|
||||
error: String,
|
||||
) -> crate::Result<()> {
|
||||
crate::execute_promise_sync(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
move || {
|
||||
save_file(options.filter, options.default_path)
|
||||
.map(map_response)
|
||||
@@ -73,14 +73,14 @@ pub fn message(title: String, message: String) {
|
||||
|
||||
/// Shows a dialog with a yes/no question.
|
||||
pub fn ask<D: ApplicationDispatcherExt + 'static>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
title: String,
|
||||
message: String,
|
||||
callback: String,
|
||||
error: String,
|
||||
) -> crate::Result<()> {
|
||||
crate::execute_promise_sync(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
move || match ask_dialog(message, title) {
|
||||
DialogSelection::Yes => crate::Result::Ok(true),
|
||||
_ => crate::Result::Ok(false),
|
||||
|
||||
@@ -17,9 +17,9 @@ pub fn listen_fn(event: String, handler: String, once: bool) -> crate::Result<St
|
||||
window['{emit}'](e.payload, e.salt, true)
|
||||
}}
|
||||
",
|
||||
listeners = crate::event::event_listeners_object_name(),
|
||||
queue = crate::event::event_queue_object_name(),
|
||||
emit = crate::event::emit_function_name(),
|
||||
listeners = crate::app::event::event_listeners_object_name(),
|
||||
queue = crate::app::event::event_queue_object_name(),
|
||||
emit = crate::app::event::emit_function_name(),
|
||||
evt = event,
|
||||
handler = handler,
|
||||
once_flag = if once { "true" } else { "false" }
|
||||
|
||||
@@ -9,14 +9,14 @@ use super::cmd::{DirOperationOptions, FileOperationOptions};
|
||||
/// Reads a directory.
|
||||
#[cfg(read_dir)]
|
||||
pub async fn read_dir<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: PathBuf,
|
||||
options: Option<DirOperationOptions>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
let (recursive, dir) = if let Some(options_value) = options {
|
||||
(options_value.recursive, options_value.dir)
|
||||
@@ -34,7 +34,7 @@ pub async fn read_dir<D: ApplicationDispatcherExt>(
|
||||
/// Copies a file.
|
||||
#[cfg(copy_file)]
|
||||
pub async fn copy_file<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
source: PathBuf,
|
||||
destination: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
@@ -42,7 +42,7 @@ pub async fn copy_file<D: ApplicationDispatcherExt>(
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
let (src, dest) = match options.and_then(|o| o.dir) {
|
||||
Some(dir) => (
|
||||
@@ -63,14 +63,14 @@ pub async fn copy_file<D: ApplicationDispatcherExt>(
|
||||
/// Creates a directory.
|
||||
#[cfg(create_dir)]
|
||||
pub async fn create_dir<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: PathBuf,
|
||||
options: Option<DirOperationOptions>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
let (recursive, dir) = if let Some(options_value) = options {
|
||||
(options_value.recursive, options_value.dir)
|
||||
@@ -95,14 +95,14 @@ pub async fn create_dir<D: ApplicationDispatcherExt>(
|
||||
/// Removes a directory.
|
||||
#[cfg(remove_dir)]
|
||||
pub async fn remove_dir<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: PathBuf,
|
||||
options: Option<DirOperationOptions>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
let (recursive, dir) = if let Some(options_value) = options {
|
||||
(options_value.recursive, options_value.dir)
|
||||
@@ -127,14 +127,14 @@ pub async fn remove_dir<D: ApplicationDispatcherExt>(
|
||||
/// Removes a file
|
||||
#[cfg(remove_file)]
|
||||
pub async fn remove_file<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
let resolved_path = resolve_path(path, options.and_then(|o| o.dir))?;
|
||||
fs::remove_file(resolved_path)?;
|
||||
@@ -149,7 +149,7 @@ pub async fn remove_file<D: ApplicationDispatcherExt>(
|
||||
/// Renames a file.
|
||||
#[cfg(rename_file)]
|
||||
pub async fn rename_file<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
old_path: PathBuf,
|
||||
new_path: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
@@ -157,7 +157,7 @@ pub async fn rename_file<D: ApplicationDispatcherExt>(
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
let (old, new) = match options.and_then(|o| o.dir) {
|
||||
Some(dir) => (
|
||||
@@ -177,7 +177,7 @@ pub async fn rename_file<D: ApplicationDispatcherExt>(
|
||||
/// Writes a text file.
|
||||
#[cfg(write_file)]
|
||||
pub async fn write_file<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: PathBuf,
|
||||
contents: String,
|
||||
options: Option<FileOperationOptions>,
|
||||
@@ -185,7 +185,7 @@ pub async fn write_file<D: ApplicationDispatcherExt>(
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
File::create(resolve_path(path, options.and_then(|o| o.dir))?)
|
||||
.map_err(crate::Error::Io)
|
||||
@@ -201,7 +201,7 @@ pub async fn write_file<D: ApplicationDispatcherExt>(
|
||||
/// Writes a binary file.
|
||||
#[cfg(write_binary_file)]
|
||||
pub async fn write_binary_file<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: PathBuf,
|
||||
contents: String,
|
||||
options: Option<FileOperationOptions>,
|
||||
@@ -209,7 +209,7 @@ pub async fn write_binary_file<D: ApplicationDispatcherExt>(
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
base64::decode(contents)
|
||||
.map_err(crate::Error::Base64Decode)
|
||||
@@ -229,14 +229,14 @@ pub async fn write_binary_file<D: ApplicationDispatcherExt>(
|
||||
/// Reads a text file.
|
||||
#[cfg(read_text_file)]
|
||||
pub async fn read_text_file<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
file::read_string(resolve_path(path, options.and_then(|o| o.dir))?)
|
||||
.map_err(crate::Error::FailedToExecuteApi)
|
||||
@@ -250,14 +250,14 @@ pub async fn read_text_file<D: ApplicationDispatcherExt>(
|
||||
/// Reads a binary file.
|
||||
#[cfg(read_binary_file)]
|
||||
pub async fn read_binary_file<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: PathBuf,
|
||||
options: Option<FileOperationOptions>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
file::read_binary(resolve_path(path, options.and_then(|o| o.dir))?)
|
||||
.map_err(crate::Error::FailedToExecuteApi)
|
||||
@@ -312,7 +312,7 @@ mod test {
|
||||
|
||||
//call write file with the path and contents.
|
||||
write_file(
|
||||
&mut dispatcher,
|
||||
&webview_manager,
|
||||
path.clone(),
|
||||
contents.clone(),
|
||||
String::from(""),
|
||||
|
||||
@@ -3,13 +3,13 @@ use tauri_api::http::{make_request as request, HttpRequestOptions};
|
||||
|
||||
/// Makes an HTTP request and resolves the response to the webview
|
||||
pub async fn make_request<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
options: HttpRequestOptions,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move { request(options).map_err(|e| e.into()) },
|
||||
callback,
|
||||
error,
|
||||
|
||||
@@ -4,7 +4,7 @@ use serde_json::Value as JsonValue;
|
||||
use tauri_api::{config::Config, notification::Notification};
|
||||
|
||||
pub async fn send<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
options: NotificationOptions,
|
||||
callback: String,
|
||||
error: String,
|
||||
@@ -13,7 +13,7 @@ pub async fn send<D: ApplicationDispatcherExt>(
|
||||
let identifier = config.tauri.bundle.identifier.clone();
|
||||
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
let mut notification = Notification::new(identifier).title(options.title);
|
||||
if let Some(body) = options.body {
|
||||
@@ -32,12 +32,12 @@ pub async fn send<D: ApplicationDispatcherExt>(
|
||||
}
|
||||
|
||||
pub async fn is_permission_granted<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move {
|
||||
let settings = crate::settings::read_settings()?;
|
||||
if let Some(allow_notification) = settings.allow_notification {
|
||||
@@ -53,12 +53,12 @@ pub async fn is_permission_granted<D: ApplicationDispatcherExt>(
|
||||
}
|
||||
|
||||
pub fn request_permission<D: ApplicationDispatcherExt + 'static>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) -> crate::Result<()> {
|
||||
crate::execute_promise_sync(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
move || {
|
||||
let mut settings = crate::settings::read_settings()?;
|
||||
let granted = "granted".to_string();
|
||||
|
||||
@@ -3,14 +3,14 @@ use crate::ApplicationDispatcherExt;
|
||||
use tauri_api::{path, path::BaseDirectory};
|
||||
|
||||
pub async fn resolve_path<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
path: String,
|
||||
directory: Option<BaseDirectory>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
crate::execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move { path::resolve_path(path, directory).map_err(|e| e.into()) },
|
||||
callback,
|
||||
error,
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::ApplicationDispatcherExt;
|
||||
|
||||
/// Validates a salt.
|
||||
pub fn validate<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
salt: String,
|
||||
callback: String,
|
||||
error: String,
|
||||
@@ -13,6 +13,8 @@ pub fn validate<D: ApplicationDispatcherExt>(
|
||||
Err("Invalid salt")
|
||||
};
|
||||
let callback_string = crate::api::rpc::format_callback_result(response, callback, error)?;
|
||||
dispatcher.eval(callback_string.as_str());
|
||||
webview_manager
|
||||
.current_webview()?
|
||||
.eval(callback_string.as_str());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ pub enum Error {
|
||||
/// Failed to create window.
|
||||
#[error("failed to create window")]
|
||||
CreateWindow,
|
||||
/// Can't access webview dispatcher because the webview was closed or not found.
|
||||
#[error("webview not found: invalid label or it was closed")]
|
||||
WebviewNotFound,
|
||||
/// Embedded asset not found.
|
||||
#[error("asset not found: {0}")]
|
||||
AssetNotFound(String),
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
//! Tauri uses (and contributes to) the MIT licensed project that you can find at [webview](https://github.com/webview/webview).
|
||||
#![warn(missing_docs, rust_2018_idioms)]
|
||||
|
||||
/// The event system module.
|
||||
pub mod event;
|
||||
/// The embedded server helpers.
|
||||
#[cfg(embedded_server)]
|
||||
pub mod server;
|
||||
@@ -40,7 +38,7 @@ pub use app::*;
|
||||
pub use tauri_api as api;
|
||||
pub use tauri_macros::FromTauriContext;
|
||||
pub use webview::{
|
||||
ApplicationDispatcherExt, ApplicationExt, Callback, Event, WebviewBuilderExt, WindowBuilderExt,
|
||||
ApplicationDispatcherExt, ApplicationExt, Callback, WebviewBuilderExt, WindowBuilderExt,
|
||||
};
|
||||
|
||||
/// The Tauri webview implementations.
|
||||
@@ -60,25 +58,29 @@ pub fn execute_promise_sync<
|
||||
R: Serialize,
|
||||
F: FnOnce() -> Result<R> + Send + 'static,
|
||||
>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
task: F,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
let mut dispatcher_ = dispatcher.clone();
|
||||
dispatcher.send_event(Event::Run(Box::new(move || {
|
||||
let callback_string =
|
||||
match format_callback_result(task().map_err(|err| err.to_string()), &callback, &error) {
|
||||
Ok(js) => js,
|
||||
Err(e) => format_callback_result(
|
||||
std::result::Result::<(), String>::Err(e.to_string()),
|
||||
&callback,
|
||||
&error,
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
dispatcher_.eval(callback_string.as_str());
|
||||
})));
|
||||
let webview_manager_ = webview_manager.clone();
|
||||
if let Ok(dispatcher) = webview_manager.current_webview() {
|
||||
dispatcher.send_event(webview::Event::Run(Box::new(move || {
|
||||
let callback_string =
|
||||
match format_callback_result(task().map_err(|err| err.to_string()), &callback, &error) {
|
||||
Ok(js) => js,
|
||||
Err(e) => format_callback_result(
|
||||
std::result::Result::<(), String>::Err(e.to_string()),
|
||||
&callback,
|
||||
&error,
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
if let Ok(dispatcher) = webview_manager_.current_webview() {
|
||||
dispatcher.eval(callback_string.as_str());
|
||||
}
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronously executes the given task
|
||||
@@ -91,7 +93,7 @@ pub async fn execute_promise<
|
||||
R: Serialize,
|
||||
F: futures::Future<Output = Result<R>> + Send + 'static,
|
||||
>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
task: F,
|
||||
success_callback: String,
|
||||
error_callback: String,
|
||||
@@ -104,19 +106,21 @@ pub async fn execute_promise<
|
||||
Ok(callback_string) => callback_string,
|
||||
Err(e) => format_callback(error_callback, e.to_string()),
|
||||
};
|
||||
dispatcher.eval(callback_string.as_str());
|
||||
if let Ok(dispatcher) = webview_manager.current_webview() {
|
||||
dispatcher.eval(callback_string.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the given command and evaluates its output to the JS promise described by the `callback` and `error` function names.
|
||||
pub async fn call<D: ApplicationDispatcherExt>(
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
callback: String,
|
||||
error: String,
|
||||
) {
|
||||
execute_promise(
|
||||
dispatcher,
|
||||
webview_manager,
|
||||
async move { api::command::get_output(command, args, Stdio::piped()).map_err(|e| e.into()) },
|
||||
callback,
|
||||
error,
|
||||
@@ -124,15 +128,6 @@ pub async fn call<D: ApplicationDispatcherExt>(
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Closes the splashscreen.
|
||||
pub fn close_splashscreen<D: ApplicationDispatcherExt>(dispatcher: &mut D) -> crate::Result<()> {
|
||||
// send a signal to the runner so it knows that it should redirect to the main app content
|
||||
dispatcher
|
||||
.eval(r#"window.__TAURI_INVOKE_HANDLER__(JSON.stringify({ cmd: "closeSplashscreen" }))"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use proptest::prelude::*;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::{api::config::PluginConfig, async_runtime::Mutex, ApplicationDispatcherExt};
|
||||
use crate::{
|
||||
api::config::PluginConfig, async_runtime::Mutex, ApplicationDispatcherExt, WebviewManager,
|
||||
};
|
||||
|
||||
use futures::future::join_all;
|
||||
|
||||
@@ -27,15 +29,19 @@ pub trait Plugin<D: ApplicationDispatcherExt + 'static>: Send + Sync {
|
||||
|
||||
/// Callback invoked when the webview is created.
|
||||
#[allow(unused_variables)]
|
||||
async fn created(&mut self, dispatcher: D) {}
|
||||
async fn created(&mut self, webview_manager: WebviewManager<D>) {}
|
||||
|
||||
/// Callback invoked when the webview is ready.
|
||||
#[allow(unused_variables)]
|
||||
async fn ready(&mut self, dispatcher: D) {}
|
||||
async fn ready(&mut self, webview_manager: WebviewManager<D>) {}
|
||||
|
||||
/// Add invoke_handler API extension commands.
|
||||
#[allow(unused_variables)]
|
||||
async fn extend_api(&mut self, dispatcher: D, payload: &str) -> crate::Result<()> {
|
||||
async fn extend_api(
|
||||
&mut self,
|
||||
webview_manager: WebviewManager<D>,
|
||||
payload: &str,
|
||||
) -> crate::Result<()> {
|
||||
Err(crate::Error::UnknownApi)
|
||||
}
|
||||
}
|
||||
@@ -93,36 +99,36 @@ pub(crate) async fn initialization_script<D: ApplicationDispatcherExt + 'static>
|
||||
|
||||
pub(crate) async fn created<D: ApplicationDispatcherExt + 'static>(
|
||||
store: &PluginStore<D>,
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
) {
|
||||
let mut plugins = store.lock().await;
|
||||
let mut futures = Vec::new();
|
||||
for plugin in plugins.iter_mut() {
|
||||
futures.push(plugin.created(dispatcher.clone()));
|
||||
futures.push(plugin.created(webview_manager.clone()));
|
||||
}
|
||||
join_all(futures).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn ready<D: ApplicationDispatcherExt + 'static>(
|
||||
store: &PluginStore<D>,
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
) {
|
||||
let mut plugins = store.lock().await;
|
||||
let mut futures = Vec::new();
|
||||
for plugin in plugins.iter_mut() {
|
||||
futures.push(plugin.ready(dispatcher.clone()));
|
||||
futures.push(plugin.ready(webview_manager.clone()));
|
||||
}
|
||||
join_all(futures).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn extend_api<D: ApplicationDispatcherExt + 'static>(
|
||||
store: &PluginStore<D>,
|
||||
dispatcher: &mut D,
|
||||
webview_manager: &crate::WebviewManager<D>,
|
||||
arg: &str,
|
||||
) -> crate::Result<bool> {
|
||||
let mut plugins = store.lock().await;
|
||||
for ext in plugins.iter_mut() {
|
||||
match ext.extend_api(dispatcher.clone(), arg).await {
|
||||
match ext.extend_api(webview_manager.clone(), arg).await {
|
||||
Ok(_) => {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub(crate) mod wry;
|
||||
|
||||
pub use crate::plugin::PluginStore;
|
||||
pub use crate::{api::config::WindowConfig, plugin::PluginStore};
|
||||
|
||||
/// An event to be posted to the webview event loop.
|
||||
pub enum Event {
|
||||
@@ -8,6 +8,12 @@ pub enum Event {
|
||||
Run(crate::SyncTask),
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
EvalScript(String),
|
||||
SetWindowTitle(String),
|
||||
Event(Event),
|
||||
}
|
||||
|
||||
/// The window builder.
|
||||
pub trait WindowBuilderExt: Sized {
|
||||
/// The window type.
|
||||
@@ -16,12 +22,39 @@ pub trait WindowBuilderExt: Sized {
|
||||
/// Initializes a new window builder.
|
||||
fn new() -> Self;
|
||||
|
||||
/// The horizontal position of the window's top left corner.
|
||||
fn x(self, x: f64) -> Self;
|
||||
|
||||
/// The vertical position of the window's top left corner.
|
||||
fn y(self, y: f64) -> Self;
|
||||
|
||||
/// Window width.
|
||||
fn width(self, width: f64) -> Self;
|
||||
|
||||
/// Window height.
|
||||
fn height(self, height: f64) -> Self;
|
||||
|
||||
/// Window min width.
|
||||
fn min_width(self, min_width: f64) -> Self;
|
||||
|
||||
/// Window min height.
|
||||
fn min_height(self, min_height: f64) -> Self;
|
||||
|
||||
/// Window max width.
|
||||
fn max_width(self, max_width: f64) -> Self;
|
||||
|
||||
/// Window max height.
|
||||
fn max_height(self, max_height: f64) -> Self;
|
||||
|
||||
/// Whether the window is resizable or not.
|
||||
fn resizable(self, resizable: bool) -> Self;
|
||||
|
||||
/// The title of the window in the title bar.
|
||||
fn title(self, title: String) -> Self;
|
||||
|
||||
/// Whether to start the window in fullscreen or not.
|
||||
fn fullscreen(self, fullscreen: bool) -> Self;
|
||||
|
||||
/// Whether the window should be maximized upon creation.
|
||||
fn maximized(self, maximized: bool) -> Self;
|
||||
|
||||
@@ -42,6 +75,50 @@ pub trait WindowBuilderExt: Sized {
|
||||
fn finish(self) -> crate::Result<Self::Window>;
|
||||
}
|
||||
|
||||
pub struct WindowBuilder<T>(T);
|
||||
|
||||
impl<T> WindowBuilder<T> {
|
||||
pub fn get(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: WindowBuilderExt> From<&WindowConfig> for WindowBuilder<T> {
|
||||
fn from(config: &WindowConfig) -> Self {
|
||||
let mut window = T::new()
|
||||
.title(config.title.to_string())
|
||||
.width(config.width)
|
||||
.height(config.height)
|
||||
.visible(config.visible)
|
||||
.resizable(config.resizable)
|
||||
.decorations(config.decorations)
|
||||
.maximized(config.maximized)
|
||||
.fullscreen(config.fullscreen)
|
||||
.transparent(config.transparent)
|
||||
.always_on_top(config.always_on_top);
|
||||
if let Some(min_width) = config.min_width {
|
||||
window = window.min_width(min_width);
|
||||
}
|
||||
if let Some(min_height) = config.min_height {
|
||||
window = window.min_height(min_height);
|
||||
}
|
||||
if let Some(max_width) = config.max_width {
|
||||
window = window.max_width(max_width);
|
||||
}
|
||||
if let Some(max_height) = config.max_height {
|
||||
window = window.max_height(max_height);
|
||||
}
|
||||
if let Some(x) = config.x {
|
||||
window = window.x(x);
|
||||
}
|
||||
if let Some(y) = config.y {
|
||||
window = window.y(y);
|
||||
}
|
||||
|
||||
Self(window)
|
||||
}
|
||||
}
|
||||
|
||||
/// The webview builder.
|
||||
pub trait WebviewBuilderExt: Sized {
|
||||
/// The webview object that this builder creates.
|
||||
@@ -70,12 +147,8 @@ pub struct Callback<D> {
|
||||
|
||||
/// Webview dispatcher. A thread-safe handle to the webview API.
|
||||
pub trait ApplicationDispatcherExt: Clone + Send + Sync + Sized {
|
||||
/// Eval a JS string on the current webview.
|
||||
fn eval(&mut self, js: &str);
|
||||
/// Eval a JS string on the webview associated with the given window.
|
||||
fn eval_on_window(&mut self, window_id: &str, js: &str);
|
||||
/// Sends a event to the webview.
|
||||
fn send_event(&self, event: Event);
|
||||
/// Sends a message to the window.
|
||||
fn send_message(&self, message: Message);
|
||||
}
|
||||
|
||||
/// The application interface.
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use super::{
|
||||
ApplicationDispatcherExt, ApplicationExt, Callback, Event, WebviewBuilderExt, WindowBuilderExt,
|
||||
ApplicationDispatcherExt, ApplicationExt, Callback, Event, Message, WebviewBuilderExt,
|
||||
WindowBuilderExt,
|
||||
};
|
||||
|
||||
use wry::{ApplicationDispatcher, ApplicationExt as _, WindowExt};
|
||||
use wry::{ApplicationDispatcher, ApplicationExt as _, WebviewMessage, WindowExt, WindowMessage};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::plugin::PluginStore;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
impl WindowBuilderExt for wry::AppWindowAttributes {
|
||||
type Window = Self;
|
||||
@@ -20,6 +18,46 @@ impl WindowBuilderExt for wry::AppWindowAttributes {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn x(mut self, x: f64) -> Self {
|
||||
self.x = Some(x);
|
||||
self
|
||||
}
|
||||
|
||||
fn y(mut self, y: f64) -> Self {
|
||||
self.y = Some(y);
|
||||
self
|
||||
}
|
||||
|
||||
fn width(mut self, width: f64) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
fn height(mut self, height: f64) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
fn min_width(mut self, min_width: f64) -> Self {
|
||||
self.min_width = Some(min_width);
|
||||
self
|
||||
}
|
||||
|
||||
fn min_height(mut self, min_height: f64) -> Self {
|
||||
self.min_height = Some(min_height);
|
||||
self
|
||||
}
|
||||
|
||||
fn max_width(mut self, max_width: f64) -> Self {
|
||||
self.max_width = Some(max_width);
|
||||
self
|
||||
}
|
||||
|
||||
fn max_height(mut self, max_height: f64) -> Self {
|
||||
self.max_height = Some(max_height);
|
||||
self
|
||||
}
|
||||
|
||||
fn resizable(mut self, resizable: bool) -> Self {
|
||||
self.resizable = resizable;
|
||||
self
|
||||
@@ -30,6 +68,11 @@ impl WindowBuilderExt for wry::AppWindowAttributes {
|
||||
self
|
||||
}
|
||||
|
||||
fn fullscreen(mut self, fullscreen: bool) -> Self {
|
||||
self.fullscreen = fullscreen;
|
||||
self
|
||||
}
|
||||
|
||||
fn maximized(mut self, maximized: bool) -> Self {
|
||||
self.maximized = maximized;
|
||||
self
|
||||
@@ -50,7 +93,6 @@ impl WindowBuilderExt for wry::AppWindowAttributes {
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether the window should always be on top of other windows.
|
||||
fn always_on_top(mut self, always_on_top: bool) -> Self {
|
||||
self.always_on_top = always_on_top;
|
||||
self
|
||||
@@ -90,45 +132,29 @@ impl WebviewBuilderExt for wry::WebViewAttributes {
|
||||
pub struct WryDispatcher {
|
||||
inner: Arc<Mutex<wry::AppDispatcher<Event>>>,
|
||||
current_window: wry::WindowId,
|
||||
windows: Arc<Mutex<HashMap<String, wry::WindowId>>>,
|
||||
}
|
||||
|
||||
struct WryMessage(wry::Message<wry::WindowId, Event>);
|
||||
|
||||
impl From<(wry::WindowId, Message)> for WryMessage {
|
||||
fn from((id, message): (wry::WindowId, Message)) -> Self {
|
||||
let message = match message {
|
||||
Message::EvalScript(js) => wry::Message::Webview(id, WebviewMessage::EvalScript(js)),
|
||||
Message::SetWindowTitle(title) => wry::Message::Window(id, WindowMessage::SetTitle(title)),
|
||||
Message::Event(event) => wry::Message::Custom(event),
|
||||
};
|
||||
WryMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationDispatcherExt for WryDispatcher {
|
||||
fn eval(&mut self, js: &str) {
|
||||
#[cfg(target_os = "linux")]
|
||||
let window_id = self.current_window;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let window_id = self.current_window.clone();
|
||||
|
||||
fn send_message(&self, message: Message) {
|
||||
let message: WryMessage = (self.current_window, message).into();
|
||||
self
|
||||
.inner
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dispatch_message(wry::Message::Script(window_id, js.to_string()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn eval_on_window(&mut self, window_id: &str, js: &str) {
|
||||
if let Some(window_id) = self.windows.lock().unwrap().get(window_id) {
|
||||
#[cfg(target_os = "linux")]
|
||||
let window_id = *window_id;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let window_id = window_id.clone();
|
||||
self
|
||||
.inner
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dispatch_message(wry::Message::Script(window_id, js.to_string()))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn send_event(&self, event: Event) {
|
||||
self
|
||||
.inner
|
||||
.lock()
|
||||
.unwrap()
|
||||
.dispatch_message(wry::Message::Custom(event))
|
||||
.dispatch_message(message.0)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -136,7 +162,6 @@ impl ApplicationDispatcherExt for WryDispatcher {
|
||||
/// A wrapper around the wry Application interface.
|
||||
pub struct WryApplication {
|
||||
inner: wry::Application<Event>,
|
||||
windows: Arc<Mutex<HashMap<String, wry::WindowId>>>,
|
||||
dispatcher_handle: Arc<Mutex<wry::AppDispatcher<Event>>>,
|
||||
}
|
||||
|
||||
@@ -154,11 +179,9 @@ impl ApplicationExt for WryApplication {
|
||||
fn new() -> crate::Result<Self> {
|
||||
let app = wry::Application::new().map_err(|_| crate::Error::CreateWebview)?;
|
||||
let dispatcher = app.dispatcher();
|
||||
let windows = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
Ok(Self {
|
||||
inner: app,
|
||||
windows,
|
||||
dispatcher_handle: Arc::new(Mutex::new(dispatcher)),
|
||||
})
|
||||
}
|
||||
@@ -166,7 +189,6 @@ impl ApplicationExt for WryApplication {
|
||||
fn dispatcher(&self, window: &Self::Window) -> Self::Dispatcher {
|
||||
WryDispatcher {
|
||||
inner: self.dispatcher_handle.clone(),
|
||||
windows: self.windows.clone(),
|
||||
current_window: window.id(),
|
||||
}
|
||||
}
|
||||
@@ -188,7 +210,6 @@ impl ApplicationExt for WryApplication {
|
||||
let mut wry_callbacks = Vec::new();
|
||||
for mut callback in callbacks {
|
||||
let dispatcher_handle = self.dispatcher_handle.clone();
|
||||
let windows = self.windows.clone();
|
||||
let window_id = window.id();
|
||||
|
||||
let callback = wry::Callback {
|
||||
@@ -197,7 +218,6 @@ impl ApplicationExt for WryApplication {
|
||||
(callback.function)(
|
||||
&WryDispatcher {
|
||||
inner: dispatcher_handle.clone(),
|
||||
windows: windows.clone(),
|
||||
current_window: window_id,
|
||||
},
|
||||
seq,
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
"allowlist": {
|
||||
"all": true
|
||||
},
|
||||
"window": {
|
||||
"windows": [{
|
||||
"title": "Tauri App"
|
||||
},
|
||||
}],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
}
|
||||
|
||||