Skip to content

Commit 95d518a

Browse files
authored
feat(core): expose AppHandle, add create_window API (#1855)
1 parent 3d8dcbb commit 95d518a

8 files changed

Lines changed: 185 additions & 47 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Adds `create_window` API to the `AppHandle` struct.

.changes/app-handle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Adds a `handle` function to the `App` struct, which returns a `Send` handle to the app instance.

core/tauri-runtime-wry/src/lib.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use tauri_runtime::{
1313
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
1414
DetachedWindow, PendingWindow, WindowEvent,
1515
},
16-
Dispatch, Error, Icon, Params, Result, Runtime,
16+
Dispatch, Error, Icon, Params, Result, Runtime, RuntimeHandle,
1717
};
1818

1919
#[cfg(feature = "menu")]
@@ -539,6 +539,8 @@ impl Dispatch for WryDispatcher {
539539
.map_err(|_| Error::FailedToSendMessage)
540540
}
541541

542+
// Creates a window by dispatching a message to the event loop.
543+
// Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
542544
fn create_window<P: Params<Runtime = Self::Runtime>>(
543545
&mut self,
544546
pending: PendingWindow<P>,
@@ -763,8 +765,46 @@ pub struct Wry {
763765
task_rx: Receiver<MainThreadTask>,
764766
}
765767

768+
/// A handle to the Wry runtime.
769+
#[derive(Clone)]
770+
pub struct WryHandle {
771+
dispatcher_context: DispatcherContext,
772+
}
773+
774+
impl RuntimeHandle for WryHandle {
775+
type Runtime = Wry;
776+
777+
// Creates a window by dispatching a message to the event loop.
778+
// Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
779+
fn create_window<P: Params<Runtime = Self::Runtime>>(
780+
&self,
781+
pending: PendingWindow<P>,
782+
) -> Result<DetachedWindow<P>> {
783+
let (tx, rx) = channel();
784+
let label = pending.label.clone();
785+
let dispatcher_context = self.dispatcher_context.clone();
786+
self
787+
.dispatcher_context
788+
.proxy
789+
.send_event(Message::CreateWebview(
790+
Arc::new(Mutex::new(Some(Box::new(move |event_loop| {
791+
create_webview(event_loop, dispatcher_context, pending)
792+
})))),
793+
tx,
794+
))
795+
.map_err(|_| Error::FailedToSendMessage)?;
796+
let window_id = rx.recv().unwrap();
797+
let dispatcher = WryDispatcher {
798+
window_id,
799+
context: self.dispatcher_context.clone(),
800+
};
801+
Ok(DetachedWindow { label, dispatcher })
802+
}
803+
}
804+
766805
impl Runtime for Wry {
767806
type Dispatcher = WryDispatcher;
807+
type Handle = WryHandle;
768808

769809
fn new() -> Result<Self> {
770810
let event_loop = EventLoop::<Message>::with_user_event();
@@ -782,6 +822,18 @@ impl Runtime for Wry {
782822
})
783823
}
784824

825+
fn handle(&self) -> Self::Handle {
826+
WryHandle {
827+
dispatcher_context: DispatcherContext {
828+
proxy: self.event_loop.create_proxy(),
829+
task_tx: self.task_tx.clone(),
830+
window_event_listeners: self.window_event_listeners.clone(),
831+
#[cfg(feature = "menu")]
832+
menu_event_listeners: self.menu_event_listeners.clone(),
833+
},
834+
}
835+
}
836+
785837
fn create_window<P: Params<Runtime = Self>>(
786838
&self,
787839
pending: PendingWindow<P>,

core/tauri-runtime/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,29 @@ pub struct SystemTrayEvent {
104104
pub menu_item_id: u32,
105105
}
106106

107+
/// A [`Send`] handle to the runtime.
108+
pub trait RuntimeHandle: Send + Sized + Clone + 'static {
109+
type Runtime: Runtime<Handle = Self>;
110+
/// Create a new webview window.
111+
fn create_window<P: Params<Runtime = Self::Runtime>>(
112+
&self,
113+
pending: PendingWindow<P>,
114+
) -> crate::Result<DetachedWindow<P>>;
115+
}
116+
107117
/// The webview runtime interface.
108118
pub trait Runtime: Sized + 'static {
109119
/// The message dispatcher.
110120
type Dispatcher: Dispatch<Runtime = Self>;
121+
/// The runtime handle type.
122+
type Handle: RuntimeHandle<Runtime = Self>;
111123

112124
/// Creates a new webview runtime.
113125
fn new() -> crate::Result<Self>;
114126

127+
/// Gets a runtime handle.
128+
fn handle(&self) -> Self::Handle;
129+
115130
/// Create a new webview window.
116131
fn create_window<P: Params<Runtime = Self>>(
117132
&self,

core/tauri/src/app.rs

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,32 @@ impl<P: Params> GlobalWindowEvent<P> {
9595

9696
crate::manager::default_args! {
9797
/// A handle to the currently running application.
98+
///
99+
/// This type implements [`Manager`] which allows for manipulation of global application items.
98100
pub struct AppHandle<P: Params> {
101+
runtime_handle: <P::Runtime as Runtime>::Handle,
99102
manager: WindowManager<P>,
100103
}
101104
}
102105

106+
impl<P: Params> Clone for AppHandle<P> {
107+
fn clone(&self) -> Self {
108+
Self {
109+
runtime_handle: self.runtime_handle.clone(),
110+
manager: self.manager.clone(),
111+
}
112+
}
113+
}
114+
103115
impl<P: Params> Manager<P> for AppHandle<P> {}
104116
impl<P: Params> ManagerBase<P> for AppHandle<P> {
105117
fn manager(&self) -> &WindowManager<P> {
106118
&self.manager
107119
}
120+
121+
fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
122+
RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
123+
}
108124
}
109125

110126
crate::manager::default_args! {
@@ -122,29 +138,51 @@ impl<P: Params> ManagerBase<P> for App<P> {
122138
fn manager(&self) -> &WindowManager<P> {
123139
&self.manager
124140
}
141+
142+
fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
143+
RuntimeOrDispatch::Runtime(&self.runtime)
144+
}
145+
}
146+
147+
macro_rules! shared_app_impl {
148+
($app: ty) => {
149+
impl<P: Params> $app {
150+
/// Creates a new webview window.
151+
pub fn create_window<F>(&self, label: P::Label, url: WindowUrl, setup: F) -> crate::Result<()>
152+
where
153+
F: FnOnce(
154+
<<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
155+
WebviewAttributes,
156+
) -> (
157+
<<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
158+
WebviewAttributes,
159+
),
160+
{
161+
let (window_builder, webview_attributes) = setup(
162+
<<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder::new(),
163+
WebviewAttributes::new(url),
164+
);
165+
self.create_new_window(PendingWindow::new(
166+
window_builder,
167+
webview_attributes,
168+
label,
169+
))?;
170+
Ok(())
171+
}
172+
}
173+
};
125174
}
126175

176+
shared_app_impl!(App<P>);
177+
shared_app_impl!(AppHandle<P>);
178+
127179
impl<P: Params> App<P> {
128-
/// Creates a new webview window.
129-
pub fn create_window<F>(&mut self, label: P::Label, url: WindowUrl, setup: F) -> crate::Result<()>
130-
where
131-
F: FnOnce(
132-
<<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
133-
WebviewAttributes,
134-
) -> (
135-
<<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
136-
WebviewAttributes,
137-
),
138-
{
139-
let (window_builder, webview_attributes) = setup(
140-
<<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder::new(),
141-
WebviewAttributes::new(url),
142-
);
143-
self.create_new_window(
144-
RuntimeOrDispatch::Runtime(&self.runtime),
145-
PendingWindow::new(window_builder, webview_attributes, label),
146-
)?;
147-
Ok(())
180+
/// Gets a handle to the application instance.
181+
pub fn handle(&self) -> AppHandle<P> {
182+
AppHandle {
183+
runtime_handle: self.runtime.handle(),
184+
manager: self.manager.clone(),
185+
}
148186
}
149187
}
150188

@@ -577,17 +615,16 @@ where
577615
)
578616
.expect("failed to run tray");
579617
for listener in self.system_tray_event_listeners {
580-
let app_handle = AppHandle {
581-
manager: app.manager.clone(),
582-
};
618+
let app_handle = app.handle();
583619
let ids = ids.clone();
620+
let listener = Arc::new(std::sync::Mutex::new(listener));
584621
app.runtime.on_system_tray_event(move |event| {
585-
listener(
586-
&app_handle,
587-
SystemTrayEvent {
588-
menu_item_id: ids.get(&event.menu_item_id).unwrap().clone(),
589-
},
590-
);
622+
let app_handle = app_handle.clone();
623+
let menu_item_id = ids.get(&event.menu_item_id).unwrap().clone();
624+
let listener = listener.clone();
625+
crate::async_runtime::spawn(async move {
626+
listener.lock().unwrap()(&app_handle, SystemTrayEvent { menu_item_id });
627+
});
591628
});
592629
}
593630
}

core/tauri/src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub use {
6060
config::{Config, WindowUrl},
6161
PackageInfo,
6262
},
63-
self::app::{App, Builder, GlobalWindowEvent},
63+
self::app::{App, AppHandle, Builder, GlobalWindowEvent},
6464
self::hooks::{
6565
Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad,
6666
PageLoadPayload, SetupHook,
@@ -316,13 +316,16 @@ pub trait Manager<P: Params>: sealed::ManagerBase<P> {
316316
/// Prevent implementation details from leaking out of the [`Manager`] trait.
317317
pub(crate) mod sealed {
318318
use crate::manager::WindowManager;
319-
use tauri_runtime::{Params, Runtime};
319+
use tauri_runtime::{Params, Runtime, RuntimeHandle};
320320

321321
/// A running [`Runtime`] or a dispatcher to it.
322322
pub enum RuntimeOrDispatch<'r, P: Params> {
323323
/// Reference to the running [`Runtime`].
324324
Runtime(&'r P::Runtime),
325325

326+
/// Handle to the running [`Runtime`].
327+
RuntimeHandle(<P::Runtime as Runtime>::Handle),
328+
326329
/// A dispatcher to the running [`Runtime`].
327330
Dispatch(<P::Runtime as Runtime>::Dispatcher),
328331
}
@@ -332,17 +335,21 @@ pub(crate) mod sealed {
332335
/// The manager behind the [`Managed`] item.
333336
fn manager(&self) -> &WindowManager<P>;
334337

338+
fn runtime(&self) -> RuntimeOrDispatch<'_, P>;
339+
335340
/// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
336341
fn create_new_window(
337342
&self,
338-
runtime: RuntimeOrDispatch<'_, P>,
339343
pending: crate::PendingWindow<P>,
340344
) -> crate::Result<crate::Window<P>> {
341345
use crate::runtime::Dispatch;
342346
let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
343347
let pending = self.manager().prepare_window(pending, &labels)?;
344-
match runtime {
348+
match self.runtime() {
345349
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending).map_err(Into::into),
350+
RuntimeOrDispatch::RuntimeHandle(handle) => {
351+
handle.create_window(pending).map_err(Into::into)
352+
}
346353
RuntimeOrDispatch::Dispatch(mut dispatcher) => {
347354
dispatcher.create_window(pending).map_err(Into::into)
348355
}

core/tauri/src/window.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ impl<P: Params> ManagerBase<P> for Window<P> {
136136
fn manager(&self) -> &WindowManager<P> {
137137
&self.manager
138138
}
139+
140+
fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
141+
RuntimeOrDispatch::Dispatch(self.dispatcher())
142+
}
139143
}
140144

141145
impl<'de, P: Params> CommandArg<'de, P> for Window<P> {
@@ -171,10 +175,11 @@ impl<P: Params> Window<P> {
171175
<<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder::new(),
172176
WebviewAttributes::new(url),
173177
);
174-
self.create_new_window(
175-
RuntimeOrDispatch::Dispatch(self.dispatcher()),
176-
PendingWindow::new(window_builder, webview_attributes, label),
177-
)
178+
self.create_new_window(PendingWindow::new(
179+
window_builder,
180+
webview_attributes,
181+
label,
182+
))
178183
}
179184

180185
/// The current window's dispatcher.

examples/api/src-tauri/src/main.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mod cmd;
1111
mod menu;
1212

1313
use serde::Serialize;
14-
use tauri::{CustomMenuItem, Manager, SystemTrayMenuItem};
14+
use tauri::{CustomMenuItem, Manager, SystemTrayMenuItem, WindowBuilder, WindowUrl};
1515

1616
#[derive(Serialize)]
1717
struct Reply {
@@ -37,15 +37,27 @@ fn main() {
3737
.on_menu_event(|event| {
3838
println!("{:?}", event.menu_item_id());
3939
})
40-
.system_tray(vec![SystemTrayMenuItem::Custom(CustomMenuItem::new(
41-
"toggle".into(),
42-
"Toggle",
43-
))])
40+
.system_tray(vec![
41+
SystemTrayMenuItem::Custom(CustomMenuItem::new("toggle".into(), "Toggle")),
42+
SystemTrayMenuItem::Custom(CustomMenuItem::new("new".into(), "New window")),
43+
])
4444
.on_system_tray_event(|app, event| {
45-
if event.menu_item_id() == "toggle" {
46-
let window = app.get_window("main").unwrap();
47-
// TODO: window.is_visible API
48-
window.hide().unwrap();
45+
match event.menu_item_id().as_str() {
46+
"toggle" => {
47+
let window = app.get_window("main").unwrap();
48+
// TODO: window.is_visible API
49+
window.hide().unwrap();
50+
}
51+
"new" => app
52+
.create_window(
53+
"new".into(),
54+
WindowUrl::App("index.html".into()),
55+
|window_builder, webview_attributes| {
56+
(window_builder.title("Tauri"), webview_attributes)
57+
},
58+
)
59+
.unwrap(),
60+
_ => {}
4961
}
5062
})
5163
.invoke_handler(tauri::generate_handler![

0 commit comments

Comments
 (0)