diff --git a/.changes/app-handle-create-window.md b/.changes/app-handle-create-window.md new file mode 100644 index 000000000..52cb11053 --- /dev/null +++ b/.changes/app-handle-create-window.md @@ -0,0 +1,5 @@ +--- +"tauri": patch +--- + +Adds `create_window` API to the `AppHandle` struct. diff --git a/.changes/app-handle.md b/.changes/app-handle.md new file mode 100644 index 000000000..bcff2bdee --- /dev/null +++ b/.changes/app-handle.md @@ -0,0 +1,5 @@ +--- +"tauri": patch +--- + +Adds a `handle` function to the `App` struct, which returns a `Send` handle to the app instance. diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index 03b982e80..4236ce943 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -13,7 +13,7 @@ use tauri_runtime::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, DetachedWindow, PendingWindow, WindowEvent, }, - Dispatch, Error, Icon, Params, Result, Runtime, + Dispatch, Error, Icon, Params, Result, Runtime, RuntimeHandle, }; #[cfg(feature = "menu")] @@ -539,6 +539,8 @@ impl Dispatch for WryDispatcher { .map_err(|_| Error::FailedToSendMessage) } + // Creates a window by dispatching a message to the event loop. + // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. fn create_window>( &mut self, pending: PendingWindow

, @@ -763,8 +765,46 @@ pub struct Wry { task_rx: Receiver, } +/// A handle to the Wry runtime. +#[derive(Clone)] +pub struct WryHandle { + dispatcher_context: DispatcherContext, +} + +impl RuntimeHandle for WryHandle { + type Runtime = Wry; + + // Creates a window by dispatching a message to the event loop. + // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. + fn create_window>( + &self, + pending: PendingWindow

, + ) -> Result> { + let (tx, rx) = channel(); + let label = pending.label.clone(); + let dispatcher_context = self.dispatcher_context.clone(); + self + .dispatcher_context + .proxy + .send_event(Message::CreateWebview( + Arc::new(Mutex::new(Some(Box::new(move |event_loop| { + create_webview(event_loop, dispatcher_context, pending) + })))), + tx, + )) + .map_err(|_| Error::FailedToSendMessage)?; + let window_id = rx.recv().unwrap(); + let dispatcher = WryDispatcher { + window_id, + context: self.dispatcher_context.clone(), + }; + Ok(DetachedWindow { label, dispatcher }) + } +} + impl Runtime for Wry { type Dispatcher = WryDispatcher; + type Handle = WryHandle; fn new() -> Result { let event_loop = EventLoop::::with_user_event(); @@ -782,6 +822,18 @@ impl Runtime for Wry { }) } + fn handle(&self) -> Self::Handle { + WryHandle { + dispatcher_context: DispatcherContext { + proxy: self.event_loop.create_proxy(), + task_tx: self.task_tx.clone(), + window_event_listeners: self.window_event_listeners.clone(), + #[cfg(feature = "menu")] + menu_event_listeners: self.menu_event_listeners.clone(), + }, + } + } + fn create_window>( &self, pending: PendingWindow

, diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index 13f6b5a24..338d7b7b7 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -104,14 +104,29 @@ pub struct SystemTrayEvent { pub menu_item_id: u32, } +/// A [`Send`] handle to the runtime. +pub trait RuntimeHandle: Send + Sized + Clone + 'static { + type Runtime: Runtime; + /// Create a new webview window. + fn create_window>( + &self, + pending: PendingWindow

, + ) -> crate::Result>; +} + /// The webview runtime interface. pub trait Runtime: Sized + 'static { /// The message dispatcher. type Dispatcher: Dispatch; + /// The runtime handle type. + type Handle: RuntimeHandle; /// Creates a new webview runtime. fn new() -> crate::Result; + /// Gets a runtime handle. + fn handle(&self) -> Self::Handle; + /// Create a new webview window. fn create_window>( &self, diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 437afb0f2..8583343b1 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -95,16 +95,32 @@ impl GlobalWindowEvent

{ crate::manager::default_args! { /// A handle to the currently running application. + /// + /// This type implements [`Manager`] which allows for manipulation of global application items. pub struct AppHandle { + runtime_handle: ::Handle, manager: WindowManager

, } } +impl Clone for AppHandle

{ + fn clone(&self) -> Self { + Self { + runtime_handle: self.runtime_handle.clone(), + manager: self.manager.clone(), + } + } +} + impl Manager

for AppHandle

{} impl ManagerBase

for AppHandle

{ fn manager(&self) -> &WindowManager

{ &self.manager } + + fn runtime(&self) -> RuntimeOrDispatch<'_, P> { + RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone()) + } } crate::manager::default_args! { @@ -122,29 +138,51 @@ impl ManagerBase

for App

{ fn manager(&self) -> &WindowManager

{ &self.manager } + + fn runtime(&self) -> RuntimeOrDispatch<'_, P> { + RuntimeOrDispatch::Runtime(&self.runtime) + } } +macro_rules! shared_app_impl { + ($app: ty) => { + impl $app { + /// Creates a new webview window. + pub fn create_window(&self, label: P::Label, url: WindowUrl, setup: F) -> crate::Result<()> + where + F: FnOnce( + <::Dispatcher as Dispatch>::WindowBuilder, + WebviewAttributes, + ) -> ( + <::Dispatcher as Dispatch>::WindowBuilder, + WebviewAttributes, + ), + { + let (window_builder, webview_attributes) = setup( + <::Dispatcher as Dispatch>::WindowBuilder::new(), + WebviewAttributes::new(url), + ); + self.create_new_window(PendingWindow::new( + window_builder, + webview_attributes, + label, + ))?; + Ok(()) + } + } + }; +} + +shared_app_impl!(App

); +shared_app_impl!(AppHandle

); + impl App

{ - /// Creates a new webview window. - pub fn create_window(&mut self, label: P::Label, url: WindowUrl, setup: F) -> crate::Result<()> - where - F: FnOnce( - <::Dispatcher as Dispatch>::WindowBuilder, - WebviewAttributes, - ) -> ( - <::Dispatcher as Dispatch>::WindowBuilder, - WebviewAttributes, - ), - { - let (window_builder, webview_attributes) = setup( - <::Dispatcher as Dispatch>::WindowBuilder::new(), - WebviewAttributes::new(url), - ); - self.create_new_window( - RuntimeOrDispatch::Runtime(&self.runtime), - PendingWindow::new(window_builder, webview_attributes, label), - )?; - Ok(()) + /// Gets a handle to the application instance. + pub fn handle(&self) -> AppHandle

{ + AppHandle { + runtime_handle: self.runtime.handle(), + manager: self.manager.clone(), + } } } @@ -577,17 +615,16 @@ where ) .expect("failed to run tray"); for listener in self.system_tray_event_listeners { - let app_handle = AppHandle { - manager: app.manager.clone(), - }; + let app_handle = app.handle(); let ids = ids.clone(); + let listener = Arc::new(std::sync::Mutex::new(listener)); app.runtime.on_system_tray_event(move |event| { - listener( - &app_handle, - SystemTrayEvent { - menu_item_id: ids.get(&event.menu_item_id).unwrap().clone(), - }, - ); + let app_handle = app_handle.clone(); + let menu_item_id = ids.get(&event.menu_item_id).unwrap().clone(); + let listener = listener.clone(); + crate::async_runtime::spawn(async move { + listener.lock().unwrap()(&app_handle, SystemTrayEvent { menu_item_id }); + }); }); } } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index da4e1efa6..cef43ac03 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -60,7 +60,7 @@ pub use { config::{Config, WindowUrl}, PackageInfo, }, - self::app::{App, Builder, GlobalWindowEvent}, + self::app::{App, AppHandle, Builder, GlobalWindowEvent}, self::hooks::{ Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad, PageLoadPayload, SetupHook, @@ -316,13 +316,16 @@ pub trait Manager: sealed::ManagerBase

{ /// Prevent implementation details from leaking out of the [`Manager`] trait. pub(crate) mod sealed { use crate::manager::WindowManager; - use tauri_runtime::{Params, Runtime}; + use tauri_runtime::{Params, Runtime, RuntimeHandle}; /// A running [`Runtime`] or a dispatcher to it. pub enum RuntimeOrDispatch<'r, P: Params> { /// Reference to the running [`Runtime`]. Runtime(&'r P::Runtime), + /// Handle to the running [`Runtime`]. + RuntimeHandle(::Handle), + /// A dispatcher to the running [`Runtime`]. Dispatch(::Dispatcher), } @@ -332,17 +335,21 @@ pub(crate) mod sealed { /// The manager behind the [`Managed`] item. fn manager(&self) -> &WindowManager

; + fn runtime(&self) -> RuntimeOrDispatch<'_, P>; + /// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`]. fn create_new_window( &self, - runtime: RuntimeOrDispatch<'_, P>, pending: crate::PendingWindow

, ) -> crate::Result> { use crate::runtime::Dispatch; let labels = self.manager().labels().into_iter().collect::>(); let pending = self.manager().prepare_window(pending, &labels)?; - match runtime { + match self.runtime() { RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending).map_err(Into::into), + RuntimeOrDispatch::RuntimeHandle(handle) => { + handle.create_window(pending).map_err(Into::into) + } RuntimeOrDispatch::Dispatch(mut dispatcher) => { dispatcher.create_window(pending).map_err(Into::into) } diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 6f58db888..e7264dc2e 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -136,6 +136,10 @@ impl ManagerBase

for Window

{ fn manager(&self) -> &WindowManager

{ &self.manager } + + fn runtime(&self) -> RuntimeOrDispatch<'_, P> { + RuntimeOrDispatch::Dispatch(self.dispatcher()) + } } impl<'de, P: Params> CommandArg<'de, P> for Window

{ @@ -171,10 +175,11 @@ impl Window

{ <::Dispatcher as Dispatch>::WindowBuilder::new(), WebviewAttributes::new(url), ); - self.create_new_window( - RuntimeOrDispatch::Dispatch(self.dispatcher()), - PendingWindow::new(window_builder, webview_attributes, label), - ) + self.create_new_window(PendingWindow::new( + window_builder, + webview_attributes, + label, + )) } /// The current window's dispatcher. diff --git a/examples/api/src-tauri/src/main.rs b/examples/api/src-tauri/src/main.rs index 3be5d8be0..9cf526452 100644 --- a/examples/api/src-tauri/src/main.rs +++ b/examples/api/src-tauri/src/main.rs @@ -11,7 +11,7 @@ mod cmd; mod menu; use serde::Serialize; -use tauri::{CustomMenuItem, Manager, SystemTrayMenuItem}; +use tauri::{CustomMenuItem, Manager, SystemTrayMenuItem, WindowBuilder, WindowUrl}; #[derive(Serialize)] struct Reply { @@ -37,15 +37,27 @@ fn main() { .on_menu_event(|event| { println!("{:?}", event.menu_item_id()); }) - .system_tray(vec![SystemTrayMenuItem::Custom(CustomMenuItem::new( - "toggle".into(), - "Toggle", - ))]) + .system_tray(vec![ + SystemTrayMenuItem::Custom(CustomMenuItem::new("toggle".into(), "Toggle")), + SystemTrayMenuItem::Custom(CustomMenuItem::new("new".into(), "New window")), + ]) .on_system_tray_event(|app, event| { - if event.menu_item_id() == "toggle" { - let window = app.get_window("main").unwrap(); - // TODO: window.is_visible API - window.hide().unwrap(); + match event.menu_item_id().as_str() { + "toggle" => { + let window = app.get_window("main").unwrap(); + // TODO: window.is_visible API + window.hide().unwrap(); + } + "new" => app + .create_window( + "new".into(), + WindowUrl::App("index.html".into()), + |window_builder, webview_attributes| { + (window_builder.title("Tauri"), webview_attributes) + }, + ) + .unwrap(), + _ => {} } }) .invoke_handler(tauri::generate_handler![