Skip to content

Commit c8e0e5b

Browse files
authored
feat(tauri-runtime-wry): add plugin API (#4094)
1 parent 87a2c2f commit c8e0e5b

File tree

5 files changed

+146
-54
lines changed

5 files changed

+146
-54
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-runtime-wry": patch
3+
---
4+
5+
Added the `plugin` method to the `Wry` runtime, allowing extensions to the event loop.

.changes/wry-plugin.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 the `App#wry_plugin` API to inject a plugin for the wry integration.

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

Lines changed: 105 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ use wry::{
7070
webview::{FileDropEvent as WryFileDropEvent, WebContext, WebView, WebViewBuilder},
7171
};
7272

73+
pub use wry;
7374
pub use wry::application::window::{Window, WindowBuilder as WryWindowBuilder, WindowId};
7475

7576
#[cfg(windows)]
@@ -98,7 +99,7 @@ use std::{
9899
thread::{current as current_thread, ThreadId},
99100
};
100101

101-
type WebviewId = u64;
102+
pub type WebviewId = u64;
102103

103104
mod webview;
104105
pub use webview::Webview;
@@ -118,25 +119,25 @@ mod clipboard;
118119
#[cfg(feature = "clipboard")]
119120
use clipboard::*;
120121

121-
type WebContextStore = Arc<Mutex<HashMap<Option<PathBuf>, WebContext>>>;
122+
pub type WebContextStore = Arc<Mutex<HashMap<Option<PathBuf>, WebContext>>>;
122123
// window
123124
type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
124125
type WindowEventListenersMap = Arc<Mutex<HashMap<Uuid, WindowEventHandler>>>;
125-
type WindowEventListeners = Arc<Mutex<HashMap<WebviewId, WindowEventListenersMap>>>;
126+
pub type WindowEventListeners = Arc<Mutex<HashMap<WebviewId, WindowEventListenersMap>>>;
126127
// menu
127128
pub type MenuEventHandler = Box<dyn Fn(&MenuEvent) + Send>;
128129
pub type MenuEventListeners = Arc<Mutex<HashMap<WebviewId, WindowMenuEventListeners>>>;
129130
pub type WindowMenuEventListeners = Arc<Mutex<HashMap<Uuid, MenuEventHandler>>>;
130131

131132
#[derive(Debug, Clone, Default)]
132-
struct WebviewIdStore(Arc<Mutex<HashMap<WindowId, WebviewId>>>);
133+
pub struct WebviewIdStore(Arc<Mutex<HashMap<WindowId, WebviewId>>>);
133134

134135
impl WebviewIdStore {
135-
fn insert(&self, w: WindowId, id: WebviewId) {
136+
pub fn insert(&self, w: WindowId, id: WebviewId) {
136137
self.0.lock().unwrap().insert(w, id);
137138
}
138139

139-
fn get(&self, w: &WindowId) -> WebviewId {
140+
pub fn get(&self, w: &WindowId) -> WebviewId {
140141
*self.0.lock().unwrap().get(w).unwrap()
141142
}
142143

@@ -192,7 +193,7 @@ fn send_user_message<T: UserEvent>(context: &Context<T>, message: Message<T>) ->
192193

193194
#[derive(Clone)]
194195
pub struct Context<T: UserEvent> {
195-
webview_id_map: WebviewIdStore,
196+
pub webview_id_map: WebviewIdStore,
196197
main_thread_id: ThreadId,
197198
proxy: WryEventLoopProxy<Message<T>>,
198199
window_event_listeners: WindowEventListeners,
@@ -495,7 +496,7 @@ impl TryFrom<WindowIcon> for WryIcon {
495496
}
496497
}
497498

498-
struct WindowEventWrapper(Option<WindowEvent>);
499+
pub struct WindowEventWrapper(pub Option<WindowEvent>);
499500

500501
impl WindowEventWrapper {
501502
fn parse(webview: &Option<WindowHandle>, event: &WryWindowEvent<'_>) -> Self {
@@ -1555,7 +1556,7 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
15551556

15561557
#[cfg(feature = "system-tray")]
15571558
#[derive(Clone, Default)]
1558-
struct TrayContext {
1559+
pub struct TrayContext {
15591560
tray: Arc<Mutex<Option<Arc<Mutex<WrySystemTray>>>>>,
15601561
listeners: SystemTrayEventListeners,
15611562
items: SystemTrayItems,
@@ -1616,10 +1617,24 @@ impl<T: UserEvent> EventLoopProxy<T> for EventProxy<T> {
16161617
}
16171618
}
16181619

1620+
pub trait Plugin<T: UserEvent> {
1621+
fn on_event(
1622+
&mut self,
1623+
event: &Event<Message<T>>,
1624+
event_loop: &EventLoopWindowTarget<Message<T>>,
1625+
proxy: &WryEventLoopProxy<Message<T>>,
1626+
control_flow: &mut ControlFlow,
1627+
context: EventLoopIterationContext<'_, T>,
1628+
web_context: &WebContextStore,
1629+
) -> bool;
1630+
}
1631+
16191632
/// A Tauri [`Runtime`] wrapper around wry.
16201633
pub struct Wry<T: UserEvent> {
16211634
main_thread_id: ThreadId,
16221635

1636+
plugins: Vec<Box<dyn Plugin<T>>>,
1637+
16231638
#[cfg(feature = "global-shortcut")]
16241639
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
16251640
#[cfg(feature = "global-shortcut")]
@@ -1791,6 +1806,8 @@ impl<T: UserEvent> Wry<T> {
17911806
Ok(Self {
17921807
main_thread_id,
17931808

1809+
plugins: Default::default(),
1810+
17941811
#[cfg(feature = "global-shortcut")]
17951812
global_shortcut_manager,
17961813
#[cfg(feature = "global-shortcut")]
@@ -1815,6 +1832,10 @@ impl<T: UserEvent> Wry<T> {
18151832
tray_context,
18161833
})
18171834
}
1835+
1836+
pub fn plugin<P: Plugin<T> + 'static>(&mut self, plugin: P) {
1837+
self.plugins.push(Box::new(plugin));
1838+
}
18181839
}
18191840

18201841
impl<T: UserEvent> Runtime<T> for Wry<T> {
@@ -1999,6 +2020,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
19992020
let windows = self.windows.clone();
20002021
let webview_id_map = self.webview_id_map.clone();
20012022
let web_context = &self.web_context;
2023+
let plugins = &mut self.plugins;
20022024
let window_event_listeners = self.window_event_listeners.clone();
20032025
let menu_event_listeners = self.menu_event_listeners.clone();
20042026
#[cfg(feature = "system-tray")]
@@ -2013,6 +2035,8 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
20132035
let clipboard_manager = self.clipboard_manager.clone();
20142036
let mut iteration = RunIteration::default();
20152037

2038+
let proxy = self.event_loop.create_proxy();
2039+
20162040
self
20172041
.event_loop
20182042
.run_return(|event, event_loop, control_flow| {
@@ -2021,6 +2045,34 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
20212045
*control_flow = ControlFlow::Exit;
20222046
}
20232047

2048+
for p in plugins.iter_mut() {
2049+
let prevent_default = p.on_event(
2050+
&event,
2051+
event_loop,
2052+
&proxy,
2053+
control_flow,
2054+
EventLoopIterationContext {
2055+
callback: &mut callback,
2056+
webview_id_map: webview_id_map.clone(),
2057+
windows: windows.clone(),
2058+
window_event_listeners: &window_event_listeners,
2059+
#[cfg(feature = "global-shortcut")]
2060+
global_shortcut_manager: global_shortcut_manager.clone(),
2061+
#[cfg(feature = "global-shortcut")]
2062+
global_shortcut_manager_handle: &global_shortcut_manager_handle,
2063+
#[cfg(feature = "clipboard")]
2064+
clipboard_manager: clipboard_manager.clone(),
2065+
menu_event_listeners: &menu_event_listeners,
2066+
#[cfg(feature = "system-tray")]
2067+
tray_context: &tray_context,
2068+
},
2069+
web_context,
2070+
);
2071+
if prevent_default {
2072+
return;
2073+
}
2074+
}
2075+
20242076
iteration = handle_event_loop(
20252077
event,
20262078
event_loop,
@@ -2051,6 +2103,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
20512103
let windows = self.windows.clone();
20522104
let webview_id_map = self.webview_id_map.clone();
20532105
let web_context = self.web_context;
2106+
let mut plugins = self.plugins;
20542107
let window_event_listeners = self.window_event_listeners.clone();
20552108
let menu_event_listeners = self.menu_event_listeners.clone();
20562109

@@ -2065,7 +2118,36 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
20652118
#[cfg(feature = "clipboard")]
20662119
let clipboard_manager = self.clipboard_manager.clone();
20672120

2121+
let proxy = self.event_loop.create_proxy();
2122+
20682123
self.event_loop.run(move |event, event_loop, control_flow| {
2124+
for p in &mut plugins {
2125+
let prevent_default = p.on_event(
2126+
&event,
2127+
event_loop,
2128+
&proxy,
2129+
control_flow,
2130+
EventLoopIterationContext {
2131+
callback: &mut callback,
2132+
webview_id_map: webview_id_map.clone(),
2133+
windows: windows.clone(),
2134+
window_event_listeners: &window_event_listeners,
2135+
#[cfg(feature = "global-shortcut")]
2136+
global_shortcut_manager: global_shortcut_manager.clone(),
2137+
#[cfg(feature = "global-shortcut")]
2138+
global_shortcut_manager_handle: &global_shortcut_manager_handle,
2139+
#[cfg(feature = "clipboard")]
2140+
clipboard_manager: clipboard_manager.clone(),
2141+
menu_event_listeners: &menu_event_listeners,
2142+
#[cfg(feature = "system-tray")]
2143+
tray_context: &tray_context,
2144+
},
2145+
&web_context,
2146+
);
2147+
if prevent_default {
2148+
return;
2149+
}
2150+
}
20692151
handle_event_loop(
20702152
event,
20712153
event_loop,
@@ -2092,19 +2174,19 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
20922174
}
20932175

20942176
pub struct EventLoopIterationContext<'a, T: UserEvent> {
2095-
callback: &'a mut (dyn FnMut(RunEvent<T>) + 'static),
2096-
webview_id_map: WebviewIdStore,
2097-
windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
2098-
window_event_listeners: &'a WindowEventListeners,
2177+
pub callback: &'a mut (dyn FnMut(RunEvent<T>) + 'static),
2178+
pub webview_id_map: WebviewIdStore,
2179+
pub windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
2180+
pub window_event_listeners: &'a WindowEventListeners,
20992181
#[cfg(feature = "global-shortcut")]
2100-
global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
2182+
pub global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
21012183
#[cfg(feature = "global-shortcut")]
2102-
global_shortcut_manager_handle: &'a GlobalShortcutManagerHandle<T>,
2184+
pub global_shortcut_manager_handle: &'a GlobalShortcutManagerHandle<T>,
21032185
#[cfg(feature = "clipboard")]
2104-
clipboard_manager: Arc<Mutex<Clipboard>>,
2105-
menu_event_listeners: &'a MenuEventListeners,
2186+
pub clipboard_manager: Arc<Mutex<Clipboard>>,
2187+
pub menu_event_listeners: &'a MenuEventListeners,
21062188
#[cfg(feature = "system-tray")]
2107-
tray_context: &'a TrayContext,
2189+
pub tray_context: &'a TrayContext,
21082190
}
21092191

21102192
struct UserMessageContext<'a> {
@@ -2653,16 +2735,13 @@ fn handle_event_loop<T: UserEvent>(
26532735

26542736
match event {
26552737
WryWindowEvent::CloseRequested => {
2656-
on_close_requested(
2657-
callback,
2658-
window_id,
2659-
windows.clone(),
2660-
window_event_listeners,
2661-
menu_event_listeners.clone(),
2662-
);
2738+
on_close_requested(callback, window_id, windows.clone(), window_event_listeners);
26632739
}
26642740
WryWindowEvent::Destroyed => {
26652741
if windows.lock().unwrap().remove(&window_id).is_some() {
2742+
menu_event_listeners.lock().unwrap().remove(&window_id);
2743+
window_event_listeners.lock().unwrap().remove(&window_id);
2744+
26662745
let is_empty = windows.lock().unwrap().is_empty();
26672746
if is_empty {
26682747
let (tx, rx) = channel();
@@ -2695,11 +2774,7 @@ fn handle_event_loop<T: UserEvent>(
26952774
}
26962775
Event::UserEvent(message) => match message {
26972776
Message::Window(id, WindowMessage::Close) => {
2698-
on_window_close(
2699-
id,
2700-
windows.lock().expect("poisoned webview collection"),
2701-
menu_event_listeners.clone(),
2702-
);
2777+
on_window_close(id, windows.lock().expect("poisoned webview collection"));
27032778
}
27042779
Message::UserEvent(t) => callback(RunEvent::UserEvent(t)),
27052780
message => {
@@ -2736,7 +2811,6 @@ fn on_close_requested<'a, T: UserEvent>(
27362811
window_id: WebviewId,
27372812
windows: Arc<Mutex<HashMap<WebviewId, WindowWrapper>>>,
27382813
window_event_listeners: &WindowEventListeners,
2739-
menu_event_listeners: MenuEventListeners,
27402814
) {
27412815
let (tx, rx) = channel();
27422816
let windows_guard = windows.lock().expect("poisoned webview collection");
@@ -2765,7 +2839,6 @@ fn on_close_requested<'a, T: UserEvent>(
27652839
on_window_close(
27662840
window_id,
27672841
windows.lock().expect("poisoned webview collection"),
2768-
menu_event_listeners,
27692842
);
27702843
}
27712844
}
@@ -2774,11 +2847,9 @@ fn on_close_requested<'a, T: UserEvent>(
27742847
fn on_window_close(
27752848
window_id: WebviewId,
27762849
mut windows: MutexGuard<'_, HashMap<WebviewId, WindowWrapper>>,
2777-
menu_event_listeners: MenuEventListeners,
27782850
) {
27792851
if let Some(mut window_wrapper) = windows.get_mut(&window_id) {
27802852
window_wrapper.inner = None;
2781-
menu_event_listeners.lock().unwrap().remove(&window_id);
27822853
}
27832854
}
27842855

core/tauri/src/app.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,21 @@ impl<R: Runtime> ManagerBase<R> for App<R> {
470470
}
471471
}
472472

473+
#[cfg(feature = "wry")]
474+
impl App<crate::Wry> {
475+
/// Adds a [`tauri_runtime_wry::Plugin`].
476+
///
477+
/// # Stability
478+
///
479+
/// This API is unstable.
480+
pub fn wry_plugin<P: tauri_runtime_wry::Plugin<EventLoopMessage> + 'static>(
481+
&mut self,
482+
plugin: P,
483+
) {
484+
self.runtime.as_mut().unwrap().plugin(plugin);
485+
}
486+
}
487+
473488
macro_rules! shared_app_impl {
474489
($app: ty) => {
475490
impl<R: Runtime> $app {

core/tests/app-updater/tests/update.rs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,18 @@ fn update_app() {
116116

117117
let cli_bin_path = if let Some(p) = get_cli_bin_path(&cli_dir, false) {
118118
p
119+
} else if let Some(p) = get_cli_bin_path(&cli_dir, true) {
120+
p
119121
} else {
120-
if let Some(p) = get_cli_bin_path(&cli_dir, true) {
121-
p
122-
} else {
123-
let status = Command::new("cargo")
124-
.arg("build")
125-
.current_dir(&cli_dir)
126-
.status()
127-
.expect("failed to run cargo");
128-
if !status.success() {
129-
panic!("failed to build CLI");
130-
}
131-
get_cli_bin_path(&cli_dir, true).expect("cargo did not build the Tauri CLI")
122+
let status = Command::new("cargo")
123+
.arg("build")
124+
.current_dir(&cli_dir)
125+
.status()
126+
.expect("failed to run cargo");
127+
if !status.success() {
128+
panic!("failed to build CLI");
132129
}
130+
get_cli_bin_path(&cli_dir, true).expect("cargo did not build the Tauri CLI")
133131
};
134132

135133
let mut config = Config {
@@ -214,15 +212,13 @@ fn update_app() {
214212
Command::new(root_dir.join("target/debug/app-updater.exe"))
215213
} else if cfg!(target_os = "macos") {
216214
Command::new(bundle_path(&root_dir, "0.1.0").join("Contents/MacOS/app-updater"))
215+
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
216+
let mut c = Command::new("xvfb-run");
217+
c.arg("--auto-servernum")
218+
.arg(bundle_path(&root_dir, "0.1.0"));
219+
c
217220
} else {
218-
if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
219-
let mut c = Command::new("xvfb-run");
220-
c.arg("--auto-servernum")
221-
.arg(bundle_path(&root_dir, "0.1.0"));
222-
c
223-
} else {
224-
Command::new(bundle_path(&root_dir, "0.1.0"))
225-
}
221+
Command::new(bundle_path(&root_dir, "0.1.0"))
226222
};
227223

228224
let status = binary_cmd.status().expect("failed to run app");

0 commit comments

Comments
 (0)