1use crate::{
6 image::Image,
7 ipc::{
8 channel::ChannelDataIpcQueue, CallbackFn, CommandArg, CommandItem, Invoke, InvokeError,
9 InvokeHandler, InvokeResponseBody,
10 },
11 manager::{webview::UriSchemeProtocol, AppManager, Asset},
12 plugin::{Plugin, PluginStore},
13 resources::ResourceTable,
14 runtime::{
15 window::{WebviewEvent as RuntimeWebviewEvent, WindowEvent as RuntimeWindowEvent},
16 ExitRequestedEventAction, RunEvent as RuntimeRunEvent,
17 },
18 sealed::{ManagerBase, RuntimeOrDispatch},
19 utils::{config::Config, Env},
20 webview::PageLoadPayload,
21 Context, DeviceEventFilter, Emitter, EventLoopMessage, EventName, Listener, Manager, Monitor,
22 Runtime, Scopes, StateManager, Theme, Webview, WebviewWindowBuilder, Window,
23};
24
25#[cfg(desktop)]
26use crate::menu::{Menu, MenuEvent};
27#[cfg(all(desktop, feature = "tray-icon"))]
28use crate::tray::{TrayIcon, TrayIconBuilder, TrayIconEvent, TrayIconId};
29use raw_window_handle::HasDisplayHandle;
30use serialize_to_javascript::{default_template, DefaultTemplate, Template};
31use tauri_macros::default_runtime;
32#[cfg(desktop)]
33use tauri_runtime::EventLoopProxy;
34use tauri_runtime::{
35 dpi::{PhysicalPosition, PhysicalSize},
36 window::DragDropEvent,
37 RuntimeInitArgs,
38};
39use tauri_utils::{assets::AssetsIter, PackageInfo};
40
41use std::{
42 borrow::Cow,
43 collections::HashMap,
44 fmt,
45 sync::{atomic, mpsc::Sender, Arc, Mutex, MutexGuard},
46 thread::ThreadId,
47 time::Duration,
48};
49
50use crate::{event::EventId, runtime::RuntimeHandle, Event, EventTarget};
51
52#[cfg(target_os = "macos")]
53use crate::ActivationPolicy;
54
55pub(crate) mod plugin;
56
57#[cfg(desktop)]
58pub(crate) type GlobalMenuEventListener<T> = Box<dyn Fn(&T, crate::menu::MenuEvent) + Send + Sync>;
59#[cfg(all(desktop, feature = "tray-icon"))]
60pub(crate) type GlobalTrayIconEventListener<T> =
61 Box<dyn Fn(&T, crate::tray::TrayIconEvent) + Send + Sync>;
62pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(&Window<R>, &WindowEvent) + Send + Sync>;
63pub(crate) type GlobalWebviewEventListener<R> =
64 Box<dyn Fn(&Webview<R>, &WebviewEvent) + Send + Sync>;
65pub type SetupHook<R> =
67 Box<dyn FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send>;
68pub type OnPageLoad<R> = dyn Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static;
70#[cfg(any(target_os = "macos", target_os = "ios"))]
72pub type OnWebContentProcessTerminate<R> = dyn Fn(&Webview<R>) + Send + Sync + 'static;
73pub type ChannelInterceptor<R> =
74 Box<dyn Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static>;
75
76pub const RESTART_EXIT_CODE: i32 = i32::MAX;
78
79#[derive(Debug, Clone)]
81pub struct ExitRequestApi {
82 tx: Sender<ExitRequestedEventAction>,
83 code: Option<i32>,
84}
85
86impl ExitRequestApi {
87 pub fn prevent_exit(&self) {
91 if self.code != Some(RESTART_EXIT_CODE) {
92 self.tx.send(ExitRequestedEventAction::Prevent).unwrap();
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct CloseRequestApi(Sender<bool>);
100
101impl CloseRequestApi {
102 pub fn prevent_close(&self) {
104 self.0.send(true).unwrap();
105 }
106}
107
108#[derive(Debug, Clone)]
110#[non_exhaustive]
111pub enum WindowEvent {
112 Resized(PhysicalSize<u32>),
114 Moved(PhysicalPosition<i32>),
116 #[non_exhaustive]
118 CloseRequested {
119 api: CloseRequestApi,
121 },
122 Destroyed,
124 Focused(bool),
128 #[non_exhaustive]
136 ScaleFactorChanged {
137 scale_factor: f64,
139 new_inner_size: PhysicalSize<u32>,
141 },
142 DragDrop(DragDropEvent),
144 ThemeChanged(Theme),
152 #[cfg(mobile)]
160 Suspended,
161 #[cfg(mobile)]
169 Resumed,
170}
171
172impl From<RuntimeWindowEvent> for WindowEvent {
173 fn from(event: RuntimeWindowEvent) -> Self {
174 match event {
175 RuntimeWindowEvent::Resized(size) => Self::Resized(size),
176 RuntimeWindowEvent::Moved(position) => Self::Moved(position),
177 RuntimeWindowEvent::CloseRequested { signal_tx } => Self::CloseRequested {
178 api: CloseRequestApi(signal_tx),
179 },
180 RuntimeWindowEvent::Destroyed => Self::Destroyed,
181 RuntimeWindowEvent::Focused(flag) => Self::Focused(flag),
182 RuntimeWindowEvent::ScaleFactorChanged {
183 scale_factor,
184 new_inner_size,
185 } => Self::ScaleFactorChanged {
186 scale_factor,
187 new_inner_size,
188 },
189 RuntimeWindowEvent::DragDrop(event) => Self::DragDrop(event),
190 RuntimeWindowEvent::ThemeChanged(theme) => Self::ThemeChanged(theme),
191 #[cfg(mobile)]
192 RuntimeWindowEvent::Suspended => Self::Suspended,
193 #[cfg(mobile)]
194 RuntimeWindowEvent::Resumed => Self::Resumed,
195 }
196 }
197}
198
199#[derive(Debug, Clone)]
201#[non_exhaustive]
202pub enum WebviewEvent {
203 DragDrop(DragDropEvent),
205}
206
207impl From<RuntimeWebviewEvent> for WebviewEvent {
208 fn from(event: RuntimeWebviewEvent) -> Self {
209 match event {
210 RuntimeWebviewEvent::DragDrop(e) => Self::DragDrop(e),
211 }
212 }
213}
214
215#[derive(Debug)]
219#[non_exhaustive]
220pub enum RunEvent {
221 Exit,
223 #[non_exhaustive]
225 ExitRequested {
226 code: Option<i32>,
230 api: ExitRequestApi,
232 },
233 #[non_exhaustive]
235 WindowEvent {
236 label: String,
238 event: WindowEvent,
240 },
241 #[non_exhaustive]
243 WebviewEvent {
244 label: String,
246 event: WebviewEvent,
248 },
249 Ready,
251 Resumed,
253 MainEventsCleared,
257 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
259 #[cfg_attr(
260 docsrs,
261 doc(cfg(any(target_os = "macos", target_os = "ios", target_os = "android")))
262 )]
263 Opened {
264 urls: Vec<url::Url>,
266 },
267 #[cfg(desktop)]
269 #[cfg_attr(docsrs, doc(cfg(desktop)))]
270 MenuEvent(crate::menu::MenuEvent),
271 #[cfg(all(desktop, feature = "tray-icon"))]
273 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
274 TrayIconEvent(crate::tray::TrayIconEvent),
275 #[non_exhaustive]
277 #[cfg(target_os = "macos")]
278 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
279 Reopen {
280 has_visible_windows: bool,
282 },
283 #[cfg(target_os = "ios")]
289 SceneRequested {
290 scene: objc2::rc::Retained<objc2_ui_kit::UIScene>,
292 options: objc2::rc::Retained<objc2_ui_kit::UISceneConnectionOptions>,
296 },
297}
298
299impl From<EventLoopMessage> for RunEvent {
300 fn from(event: EventLoopMessage) -> Self {
301 match event {
302 #[cfg(desktop)]
303 EventLoopMessage::MenuEvent(e) => Self::MenuEvent(e),
304 #[cfg(all(desktop, feature = "tray-icon"))]
305 EventLoopMessage::TrayIconEvent(e) => Self::TrayIconEvent(e),
306 }
307 }
308}
309
310#[derive(Debug, Clone)]
312pub struct AssetResolver<R: Runtime> {
313 manager: Arc<AppManager<R>>,
314}
315
316impl<R: Runtime> AssetResolver<R> {
317 pub fn get(&self, path: String) -> Option<Asset> {
330 let use_https_scheme = self
331 .manager
332 .webviews()
333 .values()
334 .all(|webview| webview.use_https_scheme());
335 self.get_for_scheme(path, use_https_scheme)
336 }
337
338 pub fn get_for_scheme(&self, path: String, use_https_scheme: bool) -> Option<Asset> {
343 #[cfg(dev)]
344 {
345 if let (Some(_), Some(crate::utils::config::FrontendDist::Directory(dist_path))) = (
348 &self.manager.config().build.dev_url,
349 &self.manager.config().build.frontend_dist,
350 ) {
351 let asset_path = std::path::PathBuf::from(&path)
352 .components()
353 .filter(|c| !matches!(c, std::path::Component::RootDir))
354 .collect::<std::path::PathBuf>();
355
356 let asset_path = self
357 .manager
358 .config_parent()
359 .map(|p| p.join(dist_path).join(&asset_path))
360 .unwrap_or_else(|| dist_path.join(&asset_path));
361 return std::fs::read(asset_path).ok().map(|bytes| {
362 let mime_type = crate::utils::mime_type::MimeType::parse(&bytes, &path);
363 Asset {
364 bytes,
365 mime_type,
366 csp_header: None,
367 }
368 });
369 }
370 }
371
372 self.manager.get_asset(path, use_https_scheme).ok()
373 }
374
375 pub fn iter(&self) -> Box<AssetsIter<'_>> {
377 self.manager.assets.iter()
378 }
379}
380
381#[default_runtime(crate::Wry, wry)]
385#[derive(Debug)]
386pub struct AppHandle<R: Runtime> {
387 pub(crate) runtime_handle: R::Handle,
388 pub(crate) manager: Arc<AppManager<R>>,
389 event_loop: Arc<Mutex<EventLoop>>,
390}
391
392#[derive(Debug)]
394struct EventLoop {
395 main_thread_id: ThreadId,
396}
397
398#[cfg(feature = "wry")]
400impl AppHandle<crate::Wry> {
401 pub fn create_tao_window<
403 F: FnOnce() -> (String, tauri_runtime_wry::TaoWindowBuilder) + Send + 'static,
404 >(
405 &self,
406 f: F,
407 ) -> crate::Result<std::sync::Weak<tauri_runtime_wry::Window>> {
408 self.runtime_handle.create_tao_window(f).map_err(Into::into)
409 }
410
411 pub fn send_tao_window_event(
413 &self,
414 window_id: tauri_runtime_wry::TaoWindowId,
415 message: tauri_runtime_wry::WindowMessage,
416 ) -> crate::Result<()> {
417 self
418 .runtime_handle
419 .send_event(tauri_runtime_wry::Message::Window(
420 self.runtime_handle.window_id(window_id),
421 message,
422 ))
423 .map_err(Into::into)
424 }
425}
426
427#[cfg(target_vendor = "apple")]
428impl<R: Runtime> AppHandle<R> {
429 pub async fn fetch_data_store_identifiers(&self) -> crate::Result<Vec<[u8; 16]>> {
433 let (tx, rx) = tokio::sync::oneshot::channel::<Result<Vec<[u8; 16]>, tauri_runtime::Error>>();
434 let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
435 let runtime_handle = self.runtime_handle.clone();
436
437 self.run_on_main_thread(move || {
438 let cloned_lock = lock.clone();
439 if let Err(err) = runtime_handle.fetch_data_store_identifiers(move |ids| {
440 if let Some(tx) = cloned_lock.lock().unwrap().take() {
441 let _ = tx.send(Ok(ids));
442 }
443 }) {
444 if let Some(tx) = lock.lock().unwrap().take() {
445 let _ = tx.send(Err(err));
446 }
447 }
448 })?;
449
450 rx.await?.map_err(Into::into)
451 }
452 pub async fn remove_data_store(&self, uuid: [u8; 16]) -> crate::Result<()> {
456 let (tx, rx) = tokio::sync::oneshot::channel::<Result<(), tauri_runtime::Error>>();
457 let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
458 let runtime_handle = self.runtime_handle.clone();
459
460 self.run_on_main_thread(move || {
461 let cloned_lock = lock.clone();
462 if let Err(err) = runtime_handle.remove_data_store(uuid, move |result| {
463 if let Some(tx) = cloned_lock.lock().unwrap().take() {
464 let _ = tx.send(result);
465 }
466 }) {
467 if let Some(tx) = lock.lock().unwrap().take() {
468 let _ = tx.send(Err(err));
469 }
470 }
471 })?;
472 rx.await?.map_err(Into::into)
473 }
474}
475
476impl<R: Runtime> Clone for AppHandle<R> {
477 fn clone(&self) -> Self {
478 Self {
479 runtime_handle: self.runtime_handle.clone(),
480 manager: self.manager.clone(),
481 event_loop: self.event_loop.clone(),
482 }
483 }
484}
485
486impl<'de, R: Runtime> CommandArg<'de, R> for AppHandle<R> {
487 fn from_command(command: CommandItem<'de, R>) -> std::result::Result<Self, InvokeError> {
489 Ok(command.message.webview().app_handle)
490 }
491}
492
493impl<R: Runtime> AppHandle<R> {
494 pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
496 self
497 .runtime_handle
498 .run_on_main_thread(f)
499 .map_err(Into::into)
500 }
501
502 pub fn plugin<P: Plugin<R> + 'static>(&self, plugin: P) -> crate::Result<()> {
528 self.plugin_boxed(Box::new(plugin))
529 }
530
531 #[cfg_attr(feature = "tracing", tracing::instrument(name = "app::plugin::register", skip(plugin), fields(name = plugin.name())))]
536 pub fn plugin_boxed(&self, mut plugin: Box<dyn Plugin<R>>) -> crate::Result<()> {
537 let mut store = self.manager().plugins.lock().unwrap();
538 store.initialize(&mut plugin, self, &self.config().plugins)?;
539 store.register(plugin);
540
541 Ok(())
542 }
543
544 pub fn remove_plugin(&self, plugin: &str) -> bool {
570 self.manager().plugins.lock().unwrap().unregister(plugin)
571 }
572
573 pub fn exit(&self, exit_code: i32) {
575 if let Err(e) = self.runtime_handle.request_exit(exit_code) {
576 log::error!("failed to exit: {}", e);
577 self.cleanup_before_exit();
578 std::process::exit(exit_code);
579 }
580 }
581
582 pub fn restart(&self) -> ! {
589 if self.event_loop.lock().unwrap().main_thread_id == std::thread::current().id() {
590 log::debug!("restart triggered on the main thread");
591 self.cleanup_before_exit();
592 crate::process::restart(&self.env());
593 } else {
594 log::debug!("restart triggered from a separate thread");
595 self
597 .manager
598 .restart_on_exit
599 .store(true, atomic::Ordering::Relaxed);
600 match self.runtime_handle.request_exit(RESTART_EXIT_CODE) {
602 Ok(()) => loop {
603 std::thread::sleep(Duration::MAX);
604 },
605 Err(e) => {
606 log::error!("failed to request exit: {e}");
607 self.cleanup_before_exit();
608 crate::process::restart(&self.env());
609 }
610 }
611 }
612 }
613
614 pub fn request_restart(&self) {
616 self
617 .manager
618 .restart_on_exit
619 .store(true, atomic::Ordering::Relaxed);
620 if self.runtime_handle.request_exit(RESTART_EXIT_CODE).is_err() {
622 self.cleanup_before_exit();
623 crate::process::restart(&self.env());
624 }
625 }
626
627 #[cfg(target_os = "macos")]
639 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
640 pub fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> crate::Result<()> {
641 self
642 .runtime_handle
643 .set_activation_policy(activation_policy)
644 .map_err(Into::into)
645 }
646
647 #[cfg(target_os = "macos")]
659 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
660 pub fn set_dock_visibility(&self, visible: bool) -> crate::Result<()> {
661 self
662 .runtime_handle
663 .set_dock_visibility(visible)
664 .map_err(Into::into)
665 }
666
667 pub fn set_device_event_filter(&self, filter: DeviceEventFilter) {
675 self.runtime_handle.set_device_event_filter(filter);
676 }
677
678 #[cfg(target_os = "ios")]
680 pub fn supports_multiple_windows(&self) -> bool {
681 let (tx, rx) = std::sync::mpsc::channel();
682 self.run_on_main_thread(move || unsafe {
683 let mtm = objc2::MainThreadMarker::new().unwrap();
684 let ui_application = objc2_ui_kit::UIApplication::sharedApplication(mtm);
685 tx.send(ui_application.supportsMultipleScenes()).unwrap();
686 });
687 rx.recv().unwrap()
688 }
689}
690
691impl<R: Runtime> Manager<R> for AppHandle<R> {
692 fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
693 self.manager.resources_table()
694 }
695}
696
697impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
698 fn manager(&self) -> &AppManager<R> {
699 &self.manager
700 }
701
702 fn manager_owned(&self) -> Arc<AppManager<R>> {
703 self.manager.clone()
704 }
705
706 fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
707 RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
708 }
709
710 fn managed_app_handle(&self) -> &AppHandle<R> {
711 self
712 }
713
714 #[cfg(target_os = "android")]
715 fn activity_name(&self) -> Option<crate::Result<String>> {
716 None
717 }
718
719 #[cfg(target_os = "ios")]
720 fn scene_identifier(&self) -> Option<crate::Result<String>> {
721 None
722 }
723}
724
725#[default_runtime(crate::Wry, wry)]
729pub struct App<R: Runtime> {
730 runtime: Option<R>,
731 setup: Option<SetupHook<R>>,
732 manager: Arc<AppManager<R>>,
733 handle: AppHandle<R>,
734 ran_setup: bool,
735}
736
737impl<R: Runtime> fmt::Debug for App<R> {
738 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739 f.debug_struct("App")
740 .field("runtime", &self.runtime)
741 .field("manager", &self.manager)
742 .field("handle", &self.handle)
743 .finish()
744 }
745}
746
747impl<R: Runtime> Manager<R> for App<R> {
748 fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
749 self.manager.resources_table()
750 }
751}
752
753impl<R: Runtime> ManagerBase<R> for App<R> {
754 fn manager(&self) -> &AppManager<R> {
755 &self.manager
756 }
757
758 fn manager_owned(&self) -> Arc<AppManager<R>> {
759 self.manager.clone()
760 }
761
762 fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
763 if let Some(runtime) = self.runtime.as_ref() {
764 RuntimeOrDispatch::Runtime(runtime)
765 } else {
766 self.handle.runtime()
767 }
768 }
769
770 fn managed_app_handle(&self) -> &AppHandle<R> {
771 self.handle()
772 }
773
774 #[cfg(target_os = "android")]
775 fn activity_name(&self) -> Option<crate::Result<String>> {
776 None
777 }
778
779 #[cfg(target_os = "ios")]
780 fn scene_identifier(&self) -> Option<crate::Result<String>> {
781 None
782 }
783}
784
785#[cfg(feature = "wry")]
787impl App<crate::Wry> {
788 pub fn wry_plugin<P: tauri_runtime_wry::PluginBuilder<EventLoopMessage> + Send + 'static>(
794 &mut self,
795 plugin: P,
796 ) where
797 <P as tauri_runtime_wry::PluginBuilder<EventLoopMessage>>::Plugin: Send,
798 {
799 self.handle.runtime_handle.plugin(plugin);
800 }
801}
802
803macro_rules! shared_app_impl {
804 ($app: ty) => {
805 impl<R: Runtime> $app {
806 #[cfg(desktop)]
808 pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(
809 &self,
810 handler: F,
811 ) {
812 self.manager.menu.on_menu_event(handler)
813 }
814
815 #[cfg(all(desktop, feature = "tray-icon"))]
817 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
818 pub fn on_tray_icon_event<F: Fn(&AppHandle<R>, TrayIconEvent) + Send + Sync + 'static>(
819 &self,
820 handler: F,
821 ) {
822 self.manager.tray.on_tray_icon_event(handler)
823 }
824
825 #[cfg(all(desktop, feature = "tray-icon"))]
827 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
828 pub fn tray_by_id<'a, I>(&self, id: &'a I) -> Option<TrayIcon<R>>
829 where
830 I: ?Sized,
831 TrayIconId: PartialEq<&'a I>,
832 {
833 self.manager.tray.tray_by_id(self.app_handle(), id)
834 }
835
836 #[cfg(all(desktop, feature = "tray-icon"))]
841 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
842 pub fn remove_tray_by_id<'a, I>(&self, id: &'a I) -> Option<TrayIcon<R>>
843 where
844 I: ?Sized,
845 TrayIconId: PartialEq<&'a I>,
846 {
847 self.manager.tray.remove_tray_by_id(self.app_handle(), id)
848 }
849
850 pub fn config(&self) -> &Config {
852 self.manager.config()
853 }
854
855 pub fn package_info(&self) -> &PackageInfo {
857 self.manager.package_info()
858 }
859
860 pub fn asset_resolver(&self) -> AssetResolver<R> {
862 AssetResolver {
863 manager: self.manager.clone(),
864 }
865 }
866
867 pub fn primary_monitor(&self) -> crate::Result<Option<Monitor>> {
871 Ok(match self.runtime() {
872 RuntimeOrDispatch::Runtime(h) => h.primary_monitor().map(Into::into),
873 RuntimeOrDispatch::RuntimeHandle(h) => h.primary_monitor().map(Into::into),
874 _ => unreachable!(),
875 })
876 }
877
878 pub fn monitor_from_point(&self, x: f64, y: f64) -> crate::Result<Option<Monitor>> {
880 Ok(match self.runtime() {
881 RuntimeOrDispatch::Runtime(h) => h.monitor_from_point(x, y).map(Into::into),
882 RuntimeOrDispatch::RuntimeHandle(h) => h.monitor_from_point(x, y).map(Into::into),
883 _ => unreachable!(),
884 })
885 }
886
887 pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {
889 Ok(match self.runtime() {
890 RuntimeOrDispatch::Runtime(h) => {
891 h.available_monitors().into_iter().map(Into::into).collect()
892 }
893 RuntimeOrDispatch::RuntimeHandle(h) => {
894 h.available_monitors().into_iter().map(Into::into).collect()
895 }
896 _ => unreachable!(),
897 })
898 }
899
900 pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {
909 Ok(match self.runtime() {
910 RuntimeOrDispatch::Runtime(h) => h.cursor_position()?,
911 RuntimeOrDispatch::RuntimeHandle(h) => h.cursor_position()?,
912 _ => unreachable!(),
913 })
914 }
915
916 pub fn set_theme(&self, theme: Option<Theme>) {
922 #[cfg(windows)]
923 for window in self.manager.windows().values() {
924 if let (Some(menu), Ok(hwnd)) = (window.menu(), window.hwnd()) {
925 let raw_hwnd = hwnd.0 as isize;
926 let _ = self.run_on_main_thread(move || {
927 let _ = unsafe {
928 menu.inner().set_theme_for_hwnd(
929 raw_hwnd,
930 theme
931 .map(crate::menu::map_to_menu_theme)
932 .unwrap_or(muda::MenuTheme::Auto),
933 )
934 };
935 });
936 };
937 }
938 match self.runtime() {
939 RuntimeOrDispatch::Runtime(h) => h.set_theme(theme),
940 RuntimeOrDispatch::RuntimeHandle(h) => h.set_theme(theme),
941 _ => unreachable!(),
942 }
943 }
944
945 pub fn default_window_icon(&self) -> Option<&Image<'_>> {
947 self.manager.window.default_icon.as_ref()
948 }
949
950 #[cfg(desktop)]
952 pub fn menu(&self) -> Option<Menu<R>> {
953 self.manager.menu.menu_lock().clone()
954 }
955
956 #[cfg(desktop)]
961 pub fn set_menu(&self, menu: Menu<R>) -> crate::Result<Option<Menu<R>>> {
962 let prev_menu = self.remove_menu()?;
963
964 self.manager.menu.insert_menu_into_stash(&menu);
965
966 self.manager.menu.menu_lock().replace(menu.clone());
967
968 #[cfg(not(target_os = "macos"))]
970 {
971 for window in self.manager.windows().values() {
972 let has_app_wide_menu = window.has_app_wide_menu() || window.menu().is_none();
973 if has_app_wide_menu {
974 window.set_menu(menu.clone())?;
975 window.menu_lock().replace(crate::window::WindowMenu {
976 is_app_wide: true,
977 menu: menu.clone(),
978 });
979 }
980 }
981 }
982
983 #[cfg(target_os = "macos")]
985 {
986 let menu_ = menu.clone();
987 self.run_on_main_thread(move || {
988 let _ = init_app_menu(&menu_);
989 })?;
990 }
991
992 Ok(prev_menu)
993 }
994
995 #[cfg(desktop)]
1000 pub fn remove_menu(&self) -> crate::Result<Option<Menu<R>>> {
1001 let menu = self.manager.menu.menu_lock().as_ref().cloned();
1002 #[allow(unused_variables)]
1003 if let Some(menu) = menu {
1004 #[cfg(not(target_os = "macos"))]
1006 {
1007 for window in self.manager.windows().values() {
1008 let has_app_wide_menu = window.has_app_wide_menu();
1009 if has_app_wide_menu {
1010 window.remove_menu()?;
1011 *window.menu_lock() = None;
1012 }
1013 }
1014 }
1015
1016 #[cfg(target_os = "macos")]
1018 {
1019 self.run_on_main_thread(move || {
1020 menu.inner().remove_for_nsapp();
1021 })?;
1022 }
1023 }
1024
1025 let prev_menu = self.manager.menu.menu_lock().take();
1026
1027 self
1028 .manager
1029 .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id()));
1030
1031 Ok(prev_menu)
1032 }
1033
1034 #[cfg(desktop)]
1039 pub fn hide_menu(&self) -> crate::Result<()> {
1040 #[cfg(not(target_os = "macos"))]
1041 {
1042 let is_app_menu_set = self.manager.menu.menu_lock().is_some();
1043 if is_app_menu_set {
1044 for window in self.manager.windows().values() {
1045 if window.has_app_wide_menu() {
1046 window.hide_menu()?;
1047 }
1048 }
1049 }
1050 }
1051
1052 Ok(())
1053 }
1054
1055 #[cfg(desktop)]
1060 pub fn show_menu(&self) -> crate::Result<()> {
1061 #[cfg(not(target_os = "macos"))]
1062 {
1063 let is_app_menu_set = self.manager.menu.menu_lock().is_some();
1064 if is_app_menu_set {
1065 for window in self.manager.windows().values() {
1066 if window.has_app_wide_menu() {
1067 window.show_menu()?;
1068 }
1069 }
1070 }
1071 }
1072
1073 Ok(())
1074 }
1075
1076 #[cfg(target_os = "macos")]
1078 pub fn show(&self) -> crate::Result<()> {
1079 match self.runtime() {
1080 RuntimeOrDispatch::Runtime(r) => r.show(),
1081 RuntimeOrDispatch::RuntimeHandle(h) => h.show()?,
1082 _ => unreachable!(),
1083 }
1084 Ok(())
1085 }
1086
1087 #[cfg(target_os = "macos")]
1089 pub fn hide(&self) -> crate::Result<()> {
1090 match self.runtime() {
1091 RuntimeOrDispatch::Runtime(r) => r.hide(),
1092 RuntimeOrDispatch::RuntimeHandle(h) => h.hide()?,
1093 _ => unreachable!(),
1094 }
1095 Ok(())
1096 }
1097
1098 pub fn cleanup_before_exit(&self) {
1101 #[cfg(all(desktop, feature = "tray-icon"))]
1102 self.manager.tray.icons.lock().unwrap().clear();
1103 self.manager.resources_table().clear();
1104 for (_, window) in self.manager.windows() {
1105 window.resources_table().clear();
1106 #[cfg(windows)]
1107 let _ = window.hide();
1108 }
1109 for (_, webview) in self.manager.webviews() {
1110 webview.resources_table().clear();
1111 }
1112 }
1113
1114 pub fn invoke_key(&self) -> &str {
1120 self.manager.invoke_key()
1121 }
1122
1123 #[cfg(desktop)]
1125 pub fn supports_multiple_windows(&self) -> bool {
1126 true
1127 }
1128
1129 #[cfg(target_os = "android")]
1131 pub fn supports_multiple_windows(&self) -> bool {
1132 let runtime_handle = match self.runtime() {
1133 RuntimeOrDispatch::Runtime(runtime) => runtime.handle(),
1134 RuntimeOrDispatch::RuntimeHandle(handle) => handle,
1135 _ => unreachable!(),
1136 };
1137
1138 let (tx, rx) = std::sync::mpsc::channel();
1139
1140 runtime_handle.run_on_android_context(move |env, _activity, _webview| {
1141 let supports = (|| {
1142 let version_class = env.find_class("android/os/Build$VERSION")?;
1143 let sdk = env
1144 .get_static_field(version_class, "SDK_INT", "I")?
1145 .i()
1146 .unwrap_or_default();
1147 crate::Result::Ok(sdk >= 32)
1148 })()
1149 .unwrap_or(false);
1150
1151 let _ = tx.send(supports);
1152 });
1153
1154 rx.recv().unwrap_or(false)
1155 }
1156 }
1157
1158 impl<R: Runtime> Listener<R> for $app {
1159 fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
1176 where
1177 F: Fn(Event) + Send + 'static,
1178 {
1179 let event = EventName::new(event.into()).unwrap();
1180 self.manager.listen(event, EventTarget::App, handler)
1181 }
1182
1183 fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
1187 where
1188 F: FnOnce(Event) + Send + 'static,
1189 {
1190 let event = EventName::new(event.into()).unwrap();
1191 self.manager.once(event, EventTarget::App, handler)
1192 }
1193
1194 fn unlisten(&self, id: EventId) {
1214 self.manager.unlisten(id)
1215 }
1216 }
1217
1218 impl<R: Runtime> Emitter<R> for $app {}
1219 };
1220}
1221
1222shared_app_impl!(App<R>);
1223shared_app_impl!(AppHandle<R>);
1224
1225impl<R: Runtime> App<R> {
1226 #[cfg_attr(
1227 feature = "tracing",
1228 tracing::instrument(name = "app::core_plugins::register")
1229 )]
1230 fn register_core_plugins(&self) -> crate::Result<()> {
1231 self.handle.plugin(crate::path::plugin::init())?;
1232 self.handle.plugin(crate::event::plugin::init(self))?;
1233 self.handle.plugin(crate::window::plugin::init())?;
1234 self.handle.plugin(crate::webview::plugin::init())?;
1235 self.handle.plugin(crate::app::plugin::init())?;
1236 self.handle.plugin(crate::resources::plugin::init())?;
1237 self.handle.plugin(crate::image::plugin::init())?;
1238 #[cfg(desktop)]
1239 self.handle.plugin(crate::menu::plugin::init())?;
1240 #[cfg(all(desktop, feature = "tray-icon"))]
1241 self.handle.plugin(crate::tray::plugin::init())?;
1242 Ok(())
1243 }
1244
1245 pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
1247 self.app_handle().run_on_main_thread(f)
1248 }
1249
1250 pub fn handle(&self) -> &AppHandle<R> {
1252 &self.handle
1253 }
1254
1255 #[cfg(target_os = "ios")]
1257 pub fn supports_multiple_windows(&self) -> bool {
1258 unsafe {
1259 let mtm = objc2::MainThreadMarker::new().unwrap();
1260 let ui_application = objc2_ui_kit::UIApplication::sharedApplication(mtm);
1261 ui_application.supportsMultipleScenes()
1262 }
1263 }
1264
1265 #[cfg(target_os = "macos")]
1277 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
1278 pub fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
1279 if let Some(runtime) = self.runtime.as_mut() {
1280 runtime.set_activation_policy(activation_policy);
1281 } else {
1282 let _ = self.app_handle().set_activation_policy(activation_policy);
1283 }
1284 }
1285
1286 #[cfg(target_os = "macos")]
1298 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
1299 pub fn set_dock_visibility(&mut self, visible: bool) {
1300 if let Some(runtime) = self.runtime.as_mut() {
1301 runtime.set_dock_visibility(visible);
1302 } else {
1303 let _ = self.app_handle().set_dock_visibility(visible);
1304 }
1305 }
1306
1307 pub fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {
1329 self
1330 .runtime
1331 .as_mut()
1332 .unwrap()
1333 .set_device_event_filter(filter);
1334 }
1335
1336 pub fn run<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, callback: F) {
1359 self.handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
1360
1361 self
1362 .runtime
1363 .take()
1364 .unwrap()
1365 .run(self.make_run_event_loop_callback(callback));
1366 }
1367
1368 pub fn run_return<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, callback: F) -> i32 {
1398 self.handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
1399
1400 self
1401 .runtime
1402 .take()
1403 .unwrap()
1404 .run_return(self.make_run_event_loop_callback(callback))
1405 }
1406
1407 fn make_run_event_loop_callback<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(
1408 mut self,
1409 mut callback: F,
1410 ) -> impl FnMut(RuntimeRunEvent<EventLoopMessage>) {
1411 let app_handle = self.handle().clone();
1412 let manager = self.manager.clone();
1413
1414 move |event| match event {
1415 RuntimeRunEvent::Ready => {
1416 if let Err(e) = setup(&mut self) {
1417 panic!("Failed to setup app: {e}");
1418 }
1419 let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Ready, &manager);
1420 callback(&app_handle, event);
1421 }
1422 RuntimeRunEvent::Exit => {
1423 let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Exit, &manager);
1424 callback(&app_handle, event);
1425 app_handle.cleanup_before_exit();
1426 if self.manager.restart_on_exit.load(atomic::Ordering::Relaxed) {
1427 crate::process::restart(&self.env());
1428 }
1429 }
1430 _ => {
1431 let event = on_event_loop_event(&app_handle, event, &manager);
1432 callback(&app_handle, event);
1433 }
1434 }
1435 }
1436
1437 #[cfg(desktop)]
1460 #[deprecated(
1461 note = "When called in a loop (as suggested by the name), this function will busy-loop. To re-gain control of control flow after the app has exited, use `App::run_return` instead."
1462 )]
1463 pub fn run_iteration<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(&mut self, mut callback: F) {
1464 let manager = self.manager.clone();
1465 let app_handle = self.handle().clone();
1466
1467 if !self.ran_setup {
1468 if let Err(e) = setup(self) {
1469 panic!("Failed to setup app: {e}");
1470 }
1471 }
1472
1473 app_handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
1474
1475 self.runtime.as_mut().unwrap().run_iteration(move |event| {
1476 let event = on_event_loop_event(&app_handle, event, &manager);
1477 callback(&app_handle, event);
1478 })
1479 }
1480}
1481
1482#[allow(clippy::type_complexity)]
1492pub struct Builder<R: Runtime> {
1493 #[cfg(any(windows, target_os = "linux"))]
1495 runtime_any_thread: bool,
1496
1497 invoke_handler: Box<InvokeHandler<R>>,
1499
1500 pub(crate) invoke_initialization_script: String,
1502
1503 channel_interceptor: Option<ChannelInterceptor<R>>,
1504
1505 setup: SetupHook<R>,
1507
1508 on_page_load: Option<Arc<OnPageLoad<R>>>,
1510
1511 #[cfg(any(target_os = "macos", target_os = "ios"))]
1513 on_web_content_process_terminate: Option<Arc<OnWebContentProcessTerminate<R>>>,
1514
1515 plugins: PluginStore<R>,
1517
1518 uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
1520
1521 state: StateManager,
1523
1524 #[cfg(desktop)]
1526 menu: Option<Box<dyn FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send>>,
1527
1528 #[cfg(desktop)]
1530 menu_event_listeners: Vec<GlobalMenuEventListener<AppHandle<R>>>,
1531
1532 #[cfg(all(desktop, feature = "tray-icon"))]
1534 tray_icon_event_listeners: Vec<GlobalTrayIconEventListener<AppHandle<R>>>,
1535
1536 #[allow(unused)]
1538 enable_macos_default_menu: bool,
1539
1540 window_event_listeners: Vec<GlobalWindowEventListener<R>>,
1542
1543 webview_event_listeners: Vec<GlobalWebviewEventListener<R>>,
1545
1546 device_event_filter: DeviceEventFilter,
1548
1549 pub(crate) invoke_key: String,
1550}
1551
1552#[derive(Template)]
1553#[default_template("../scripts/ipc-protocol.js")]
1554pub(crate) struct InvokeInitializationScript<'a> {
1555 #[raw]
1557 pub(crate) process_ipc_message_fn: &'a str,
1558 pub(crate) os_name: &'a str,
1559 pub(crate) fetch_channel_data_command: &'a str,
1560 pub(crate) invoke_key: &'a str,
1561}
1562
1563#[cfg(feature = "wry")]
1565#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
1566impl Default for Builder<crate::Wry> {
1567 fn default() -> Self {
1568 Self::new()
1569 }
1570}
1571
1572#[cfg(not(feature = "wry"))]
1573#[cfg_attr(docsrs, doc(cfg(not(feature = "wry"))))]
1574impl<R: Runtime> Default for Builder<R> {
1575 fn default() -> Self {
1576 Self::new()
1577 }
1578}
1579
1580impl<R: Runtime> Builder<R> {
1581 pub fn new() -> Self {
1583 let invoke_key = crate::generate_invoke_key().unwrap();
1584
1585 Self {
1586 #[cfg(any(windows, target_os = "linux"))]
1587 runtime_any_thread: false,
1588 setup: Box::new(|_| Ok(())),
1589 invoke_handler: Box::new(|_| false),
1590 invoke_initialization_script: InvokeInitializationScript {
1591 process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN,
1592 os_name: std::env::consts::OS,
1593 fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
1594 invoke_key: &invoke_key.clone(),
1595 }
1596 .render_default(&Default::default())
1597 .unwrap()
1598 .into_string(),
1599 channel_interceptor: None,
1600 on_page_load: None,
1601 #[cfg(any(target_os = "macos", target_os = "ios"))]
1602 on_web_content_process_terminate: None,
1603 plugins: PluginStore::default(),
1604 uri_scheme_protocols: Default::default(),
1605 state: StateManager::new(),
1606 #[cfg(desktop)]
1607 menu: None,
1608 #[cfg(desktop)]
1609 menu_event_listeners: Vec::new(),
1610 #[cfg(all(desktop, feature = "tray-icon"))]
1611 tray_icon_event_listeners: Vec::new(),
1612 enable_macos_default_menu: true,
1613 window_event_listeners: Vec::new(),
1614 webview_event_listeners: Vec::new(),
1615 device_event_filter: Default::default(),
1616 invoke_key,
1617 }
1618 }
1619}
1620
1621impl<R: Runtime> Builder<R> {
1622 #[cfg(any(windows, target_os = "linux"))]
1628 #[cfg_attr(docsrs, doc(cfg(any(windows, target_os = "linux"))))]
1629 #[must_use]
1630 pub fn any_thread(mut self) -> Self {
1631 self.runtime_any_thread = true;
1632 self
1633 }
1634
1635 #[must_use]
1650 pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
1651 where
1652 F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
1653 {
1654 self.invoke_handler = Box::new(invoke_handler);
1655 self
1656 }
1657
1658 #[must_use]
1677 pub fn invoke_system(mut self, initialization_script: impl AsRef<str>) -> Self {
1678 self.invoke_initialization_script = initialization_script
1679 .as_ref()
1680 .replace("__INVOKE_KEY__", &format!("\"{}\"", self.invoke_key));
1681 self
1682 }
1683
1684 pub fn channel_interceptor<
1691 F: Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static,
1692 >(
1693 mut self,
1694 interceptor: F,
1695 ) -> Self {
1696 self.channel_interceptor.replace(Box::new(interceptor));
1697 self
1698 }
1699
1700 pub fn append_invoke_initialization_script(
1738 mut self,
1739 initialization_script: impl AsRef<str>,
1740 ) -> Self {
1741 self
1742 .invoke_initialization_script
1743 .push_str(initialization_script.as_ref());
1744 self
1745 }
1746
1747 #[cfg_attr(
1751 feature = "unstable",
1752 doc = r####"
1753```
1754use tauri::Manager;
1755tauri::Builder::default()
1756 .setup(|app| {
1757 let main_window = app.get_webview_window("main").unwrap();
1758 main_window.set_title("Tauri!")?;
1759 Ok(())
1760 });
1761```
1762 "####
1763 )]
1764 #[must_use]
1765 pub fn setup<F>(mut self, setup: F) -> Self
1766 where
1767 F: FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send + 'static,
1768 {
1769 self.setup = Box::new(setup);
1770 self
1771 }
1772
1773 #[must_use]
1775 pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
1776 where
1777 F: Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static,
1778 {
1779 self.on_page_load.replace(Arc::new(on_page_load));
1780 self
1781 }
1782
1783 #[cfg(any(target_os = "macos", target_os = "ios"))]
1789 #[must_use]
1790 pub fn on_web_content_process_terminate<F>(mut self, on_web_content_process_terminate: F) -> Self
1791 where
1792 F: Fn(&Webview<R>) + Send + Sync + 'static,
1793 {
1794 self
1795 .on_web_content_process_terminate
1796 .replace(Arc::new(on_web_content_process_terminate));
1797 self
1798 }
1799
1800 #[must_use]
1842 pub fn plugin<P: Plugin<R> + 'static>(self, plugin: P) -> Self {
1843 self.plugin_boxed(Box::new(plugin))
1844 }
1845
1846 #[must_use]
1851 pub fn plugin_boxed(mut self, plugin: Box<dyn Plugin<R>>) -> Self {
1852 self.plugins.register(plugin);
1853 self
1854 }
1855
1856 #[must_use]
1935 pub fn manage<T>(self, state: T) -> Self
1936 where
1937 T: Send + Sync + 'static,
1938 {
1939 let type_name = std::any::type_name::<T>();
1940 assert!(
1941 self.state.set(state),
1942 "state for type '{type_name}' is already being managed",
1943 );
1944 self
1945 }
1946
1947 #[must_use]
1968 #[cfg(desktop)]
1969 pub fn menu<F: FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send + 'static>(
1970 mut self,
1971 f: F,
1972 ) -> Self {
1973 self.menu.replace(Box::new(f));
1974 self
1975 }
1976
1977 #[must_use]
1991 #[cfg(desktop)]
1992 pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(
1993 mut self,
1994 f: F,
1995 ) -> Self {
1996 self.menu_event_listeners.push(Box::new(f));
1997 self
1998 }
1999
2000 #[must_use]
2013 #[cfg(all(desktop, feature = "tray-icon"))]
2014 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
2015 pub fn on_tray_icon_event<F: Fn(&AppHandle<R>, TrayIconEvent) + Send + Sync + 'static>(
2016 mut self,
2017 f: F,
2018 ) -> Self {
2019 self.tray_icon_event_listeners.push(Box::new(f));
2020 self
2021 }
2022
2023 #[must_use]
2031 pub fn enable_macos_default_menu(mut self, enable: bool) -> Self {
2032 self.enable_macos_default_menu = enable;
2033 self
2034 }
2035
2036 #[must_use]
2052 pub fn on_window_event<F: Fn(&Window<R>, &WindowEvent) + Send + Sync + 'static>(
2053 mut self,
2054 handler: F,
2055 ) -> Self {
2056 self.window_event_listeners.push(Box::new(handler));
2057 self
2058 }
2059
2060 #[must_use]
2073 pub fn on_webview_event<F: Fn(&Webview<R>, &WebviewEvent) + Send + Sync + 'static>(
2074 mut self,
2075 handler: F,
2076 ) -> Self {
2077 self.webview_event_listeners.push(Box::new(handler));
2078 self
2079 }
2080
2081 #[must_use]
2122 pub fn register_uri_scheme_protocol<
2123 N: Into<String>,
2124 T: Into<Cow<'static, [u8]>>,
2125 H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>) -> http::Response<T>
2126 + Send
2127 + Sync
2128 + 'static,
2129 >(
2130 mut self,
2131 uri_scheme: N,
2132 protocol_handler: H,
2133 ) -> Self {
2134 self.uri_scheme_protocols.insert(
2135 uri_scheme.into(),
2136 Arc::new(UriSchemeProtocol {
2137 handler: Box::new(move |ctx, request, responder| {
2138 responder.respond(protocol_handler(ctx, request))
2139 }),
2140 }),
2141 );
2142 self
2143 }
2144
2145 #[must_use]
2190 pub fn register_asynchronous_uri_scheme_protocol<
2191 N: Into<String>,
2192 H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync + 'static,
2193 >(
2194 mut self,
2195 uri_scheme: N,
2196 protocol_handler: H,
2197 ) -> Self {
2198 self.uri_scheme_protocols.insert(
2199 uri_scheme.into(),
2200 Arc::new(UriSchemeProtocol {
2201 handler: Box::new(protocol_handler),
2202 }),
2203 );
2204 self
2205 }
2206
2207 pub fn device_event_filter(mut self, filter: DeviceEventFilter) -> Self {
2225 self.device_event_filter = filter;
2226 self
2227 }
2228
2229 #[allow(clippy::type_complexity, unused_mut)]
2231 #[cfg_attr(
2232 feature = "tracing",
2233 tracing::instrument(name = "app::build", skip_all)
2234 )]
2235 pub fn build(mut self, context: Context<R>) -> crate::Result<App<R>> {
2236 #[cfg(target_os = "macos")]
2237 if self.menu.is_none() && self.enable_macos_default_menu {
2238 self.menu = Some(Box::new(|app_handle| {
2239 crate::menu::Menu::default(app_handle)
2240 }));
2241 }
2242
2243 let manager = Arc::new(AppManager::with_handlers(
2244 context,
2245 self.plugins,
2246 self.invoke_handler,
2247 self.on_page_load,
2248 #[cfg(any(target_os = "macos", target_os = "ios"))]
2249 self.on_web_content_process_terminate,
2250 self.uri_scheme_protocols,
2251 self.state,
2252 #[cfg(desktop)]
2253 self.menu_event_listeners,
2254 #[cfg(all(desktop, feature = "tray-icon"))]
2255 self.tray_icon_event_listeners,
2256 self.window_event_listeners,
2257 self.webview_event_listeners,
2258 #[cfg(desktop)]
2259 HashMap::new(),
2260 self.invoke_initialization_script,
2261 self.channel_interceptor,
2262 self.invoke_key,
2263 ));
2264
2265 #[cfg(any(
2266 target_os = "linux",
2267 target_os = "dragonfly",
2268 target_os = "freebsd",
2269 target_os = "netbsd",
2270 target_os = "openbsd"
2271 ))]
2272 let app_id = if manager.config.app.enable_gtk_app_id {
2273 Some(manager.config.identifier.clone())
2274 } else {
2275 None
2276 };
2277
2278 let runtime_args = RuntimeInitArgs {
2279 #[cfg(any(
2280 target_os = "linux",
2281 target_os = "dragonfly",
2282 target_os = "freebsd",
2283 target_os = "netbsd",
2284 target_os = "openbsd"
2285 ))]
2286 app_id,
2287
2288 #[cfg(windows)]
2289 msg_hook: {
2290 let menus = manager.menu.menus.clone();
2291 Some(Box::new(move |msg| {
2292 use windows::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, HACCEL, MSG};
2293 unsafe {
2294 let msg = msg as *const MSG;
2295 for menu in menus.lock().unwrap().values() {
2296 let translated =
2297 TranslateAcceleratorW((*msg).hwnd, HACCEL(menu.inner().haccel() as _), msg);
2298 if translated == 1 {
2299 return true;
2300 }
2301 }
2302
2303 false
2304 }
2305 }))
2306 },
2307 };
2308
2309 #[cfg(windows)]
2311 {
2312 if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } =
2313 &manager.config.bundle.windows.webview_install_mode
2314 {
2315 if let Some(exe_dir) = crate::utils::platform::current_exe()
2316 .ok()
2317 .and_then(|p| p.parent().map(|p| p.to_path_buf()))
2318 {
2319 std::env::set_var("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", exe_dir.join(path));
2320 } else {
2321 #[cfg(debug_assertions)]
2322 eprintln!(
2323 "failed to resolve resource directory; fallback to the installed Webview2 runtime."
2324 );
2325 }
2326 }
2327 }
2328
2329 #[cfg(any(windows, target_os = "linux"))]
2330 let mut runtime = if self.runtime_any_thread {
2331 R::new_any_thread(runtime_args)?
2332 } else {
2333 R::new(runtime_args)?
2334 };
2335 #[cfg(not(any(windows, target_os = "linux")))]
2336 let mut runtime = R::new(runtime_args)?;
2337
2338 #[cfg(desktop)]
2339 {
2340 let proxy = runtime.create_proxy();
2342 muda::MenuEvent::set_event_handler(Some(move |e: muda::MenuEvent| {
2343 let _ = proxy.send_event(EventLoopMessage::MenuEvent(e.into()));
2344 }));
2345
2346 #[cfg(feature = "tray-icon")]
2348 {
2349 let proxy = runtime.create_proxy();
2350 tray_icon::TrayIconEvent::set_event_handler(Some(move |e: tray_icon::TrayIconEvent| {
2351 let _ = proxy.send_event(EventLoopMessage::TrayIconEvent(e.into()));
2352 }));
2353 }
2354 }
2355
2356 runtime.set_device_event_filter(self.device_event_filter);
2357
2358 let runtime_handle = runtime.handle();
2359
2360 #[allow(unused_mut)]
2361 let mut app = App {
2362 runtime: Some(runtime),
2363 setup: Some(self.setup),
2364 manager: manager.clone(),
2365 handle: AppHandle {
2366 runtime_handle,
2367 manager,
2368 event_loop: Arc::new(Mutex::new(EventLoop {
2369 main_thread_id: std::thread::current().id(),
2370 })),
2371 },
2372 ran_setup: false,
2373 };
2374
2375 #[cfg(desktop)]
2376 if let Some(menu) = self.menu {
2377 let menu = menu(&app.handle)?;
2378 app
2379 .manager
2380 .menu
2381 .menus_stash_lock()
2382 .insert(menu.id().clone(), menu.clone());
2383
2384 #[cfg(target_os = "macos")]
2385 init_app_menu(&menu)?;
2386
2387 app.manager.menu.menu_lock().replace(menu);
2388 }
2389
2390 app.register_core_plugins()?;
2391
2392 let env = Env::default();
2393 app.manage(env);
2394
2395 app.manage(Scopes {
2396 #[cfg(feature = "protocol-asset")]
2397 asset_protocol: crate::scope::fs::Scope::new(
2398 &app,
2399 &app.config().app.security.asset_protocol.scope,
2400 )?,
2401 });
2402
2403 app.manage(ChannelDataIpcQueue::default());
2404 app.handle.plugin(crate::ipc::channel::plugin())?;
2405
2406 let handle = app.handle();
2407
2408 #[cfg(all(desktop, feature = "tray-icon"))]
2410 {
2411 let config = app.config();
2412 if let Some(tray_config) = &config.app.tray_icon {
2413 #[allow(deprecated)]
2414 let mut tray =
2415 TrayIconBuilder::with_id(tray_config.id.clone().unwrap_or_else(|| "main".into()))
2416 .icon_as_template(tray_config.icon_as_template)
2417 .menu_on_left_click(tray_config.menu_on_left_click)
2418 .show_menu_on_left_click(tray_config.show_menu_on_left_click);
2419 if let Some(icon) = &app.manager.tray.icon {
2420 tray = tray.icon(icon.clone());
2421 }
2422 if let Some(title) = &tray_config.title {
2423 tray = tray.title(title);
2424 }
2425 if let Some(tooltip) = &tray_config.tooltip {
2426 tray = tray.tooltip(tooltip);
2427 }
2428 tray.build(handle)?;
2429 }
2430 }
2431
2432 app.manager.initialize_plugins(handle)?;
2433
2434 Ok(app)
2435 }
2436
2437 pub fn run(self, context: Context<R>) -> crate::Result<()> {
2442 self.build(context)?.run(|_, _| {});
2443 Ok(())
2444 }
2445}
2446
2447pub(crate) type UriSchemeResponderFn = Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>;
2448
2449pub struct UriSchemeResponder(pub(crate) UriSchemeResponderFn);
2451
2452impl UriSchemeResponder {
2453 pub fn respond<T: Into<Cow<'static, [u8]>>>(self, response: http::Response<T>) {
2455 let (parts, body) = response.into_parts();
2456 (self.0)(http::Response::from_parts(parts, body.into()))
2457 }
2458}
2459
2460pub struct UriSchemeContext<'a, R: Runtime> {
2462 pub(crate) app_handle: &'a AppHandle<R>,
2463 pub(crate) webview_label: &'a str,
2464}
2465
2466impl<'a, R: Runtime> UriSchemeContext<'a, R> {
2467 pub fn app_handle(&self) -> &'a AppHandle<R> {
2469 self.app_handle
2470 }
2471
2472 pub fn webview_label(&self) -> &'a str {
2474 self.webview_label
2475 }
2476}
2477
2478#[cfg(target_os = "macos")]
2479fn init_app_menu<R: Runtime>(menu: &Menu<R>) -> crate::Result<()> {
2480 menu.inner().init_for_nsapp();
2481
2482 if let Some(window_menu) = menu.get(crate::menu::WINDOW_SUBMENU_ID) {
2483 if let Some(m) = window_menu.as_submenu() {
2484 m.set_as_windows_menu_for_nsapp()?;
2485 }
2486 }
2487 if let Some(help_menu) = menu.get(crate::menu::HELP_SUBMENU_ID) {
2488 if let Some(m) = help_menu.as_submenu() {
2489 m.set_as_help_menu_for_nsapp()?;
2490 }
2491 }
2492
2493 Ok(())
2494}
2495
2496impl<R: Runtime> HasDisplayHandle for AppHandle<R> {
2497 fn display_handle(
2498 &self,
2499 ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
2500 self.runtime_handle.display_handle()
2501 }
2502}
2503
2504impl<R: Runtime> HasDisplayHandle for App<R> {
2505 fn display_handle(
2506 &self,
2507 ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
2508 self.handle.display_handle()
2509 }
2510}
2511
2512#[cfg_attr(feature = "tracing", tracing::instrument(name = "app::setup"))]
2513fn setup<R: Runtime>(app: &mut App<R>) -> crate::Result<()> {
2514 app.ran_setup = true;
2515
2516 for window_config in app.config().app.windows.iter().filter(|w| w.create) {
2517 WebviewWindowBuilder::from_config(app.handle(), window_config)?.build()?;
2518 }
2519
2520 app.manager.assets.setup(app);
2521
2522 if let Some(setup) = app.setup.take() {
2523 (setup)(app).map_err(|e| crate::Error::Setup(e.into()))?;
2524 }
2525
2526 Ok(())
2527}
2528
2529fn on_event_loop_event<R: Runtime>(
2530 app_handle: &AppHandle<R>,
2531 event: RuntimeRunEvent<EventLoopMessage>,
2532 manager: &AppManager<R>,
2533) -> RunEvent {
2534 if let RuntimeRunEvent::WindowEvent {
2535 label,
2536 event: RuntimeWindowEvent::Destroyed,
2537 } = &event
2538 {
2539 manager.on_window_close(label);
2540 }
2541
2542 let event = match event {
2543 RuntimeRunEvent::Exit => RunEvent::Exit,
2544 RuntimeRunEvent::ExitRequested { code, tx } => RunEvent::ExitRequested {
2545 code,
2546 api: ExitRequestApi { tx, code },
2547 },
2548 RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
2549 label,
2550 event: event.into(),
2551 },
2552 RuntimeRunEvent::WebviewEvent { label, event } => RunEvent::WebviewEvent {
2553 label,
2554 event: event.into(),
2555 },
2556 RuntimeRunEvent::Ready => {
2557 #[cfg(all(dev, target_os = "macos"))]
2559 {
2560 use objc2::{AllocAnyThread, MainThreadMarker};
2561 use objc2_app_kit::{NSApplication, NSImage};
2562 use objc2_foundation::NSData;
2563
2564 if let Some(icon) = app_handle.manager.app_icon.clone() {
2565 let mtm = unsafe { MainThreadMarker::new_unchecked() };
2567 let app = NSApplication::sharedApplication(mtm);
2568 let data = NSData::with_bytes(&icon);
2569 let app_icon = NSImage::initWithData(NSImage::alloc(), &data).expect("creating icon");
2570 unsafe { app.setApplicationIconImage(Some(&app_icon)) };
2571 }
2572 }
2573 RunEvent::Ready
2574 }
2575 RuntimeRunEvent::Resumed => RunEvent::Resumed,
2576 RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared,
2577 RuntimeRunEvent::UserEvent(t) => {
2578 match t {
2579 #[cfg(desktop)]
2580 EventLoopMessage::MenuEvent(ref e) => {
2581 for listener in &*app_handle
2582 .manager
2583 .menu
2584 .global_event_listeners
2585 .lock()
2586 .unwrap()
2587 {
2588 listener(app_handle, e.clone());
2589 }
2590 for (label, listener) in &*app_handle.manager.menu.event_listeners.lock().unwrap() {
2591 if let Some(w) = app_handle.manager().get_window(label) {
2592 listener(&w, e.clone());
2593 }
2594 }
2595 }
2596 #[cfg(all(desktop, feature = "tray-icon"))]
2597 EventLoopMessage::TrayIconEvent(ref e) => {
2598 for listener in &*app_handle
2599 .manager
2600 .tray
2601 .global_event_listeners
2602 .lock()
2603 .unwrap()
2604 {
2605 listener(app_handle, e.clone());
2606 }
2607
2608 for (id, listener) in &*app_handle.manager.tray.event_listeners.lock().unwrap() {
2609 if e.id() == id {
2610 if let Some(tray) = app_handle.tray_by_id(id) {
2611 listener(&tray, e.clone());
2612 }
2613 }
2614 }
2615 }
2616 }
2617
2618 #[allow(unreachable_code)]
2619 t.into()
2620 }
2621 #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
2622 RuntimeRunEvent::Opened { urls } => RunEvent::Opened { urls },
2623 #[cfg(target_os = "macos")]
2624 RuntimeRunEvent::Reopen {
2625 has_visible_windows,
2626 } => RunEvent::Reopen {
2627 has_visible_windows,
2628 },
2629 #[cfg(target_os = "ios")]
2630 RuntimeRunEvent::SceneRequested { scene, options } => {
2631 RunEvent::SceneRequested { scene, options }
2632 }
2633 _ => unimplemented!(),
2634 };
2635
2636 manager
2637 .plugins
2638 .lock()
2639 .expect("poisoned plugin store")
2640 .on_event(app_handle, &event);
2641
2642 event
2643}
2644
2645#[cfg(test)]
2646mod tests {
2647 #[test]
2648 fn is_send_sync() {
2649 crate::test_utils::assert_send::<super::AppHandle>();
2650 crate::test_utils::assert_sync::<super::AppHandle>();
2651
2652 #[cfg(feature = "wry")]
2653 {
2654 crate::test_utils::assert_send::<super::AssetResolver<crate::Wry>>();
2655 crate::test_utils::assert_sync::<super::AssetResolver<crate::Wry>>();
2656 }
2657 }
2658}