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;
70pub type ChannelInterceptor<R> =
71 Box<dyn Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static>;
72
73pub const RESTART_EXIT_CODE: i32 = i32::MAX;
75
76#[derive(Debug, Clone)]
78pub struct ExitRequestApi {
79 tx: Sender<ExitRequestedEventAction>,
80 code: Option<i32>,
81}
82
83impl ExitRequestApi {
84 pub fn prevent_exit(&self) {
88 if self.code != Some(RESTART_EXIT_CODE) {
89 self.tx.send(ExitRequestedEventAction::Prevent).unwrap();
90 }
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct CloseRequestApi(Sender<bool>);
97
98impl CloseRequestApi {
99 pub fn prevent_close(&self) {
101 self.0.send(true).unwrap();
102 }
103}
104
105#[derive(Debug, Clone)]
107#[non_exhaustive]
108pub enum WindowEvent {
109 Resized(PhysicalSize<u32>),
111 Moved(PhysicalPosition<i32>),
113 #[non_exhaustive]
115 CloseRequested {
116 api: CloseRequestApi,
118 },
119 Destroyed,
121 Focused(bool),
125 #[non_exhaustive]
133 ScaleFactorChanged {
134 scale_factor: f64,
136 new_inner_size: PhysicalSize<u32>,
138 },
139 DragDrop(DragDropEvent),
141 ThemeChanged(Theme),
149}
150
151impl From<RuntimeWindowEvent> for WindowEvent {
152 fn from(event: RuntimeWindowEvent) -> Self {
153 match event {
154 RuntimeWindowEvent::Resized(size) => Self::Resized(size),
155 RuntimeWindowEvent::Moved(position) => Self::Moved(position),
156 RuntimeWindowEvent::CloseRequested { signal_tx } => Self::CloseRequested {
157 api: CloseRequestApi(signal_tx),
158 },
159 RuntimeWindowEvent::Destroyed => Self::Destroyed,
160 RuntimeWindowEvent::Focused(flag) => Self::Focused(flag),
161 RuntimeWindowEvent::ScaleFactorChanged {
162 scale_factor,
163 new_inner_size,
164 } => Self::ScaleFactorChanged {
165 scale_factor,
166 new_inner_size,
167 },
168 RuntimeWindowEvent::DragDrop(event) => Self::DragDrop(event),
169 RuntimeWindowEvent::ThemeChanged(theme) => Self::ThemeChanged(theme),
170 }
171 }
172}
173
174#[derive(Debug, Clone)]
176#[non_exhaustive]
177pub enum WebviewEvent {
178 DragDrop(DragDropEvent),
180}
181
182impl From<RuntimeWebviewEvent> for WebviewEvent {
183 fn from(event: RuntimeWebviewEvent) -> Self {
184 match event {
185 RuntimeWebviewEvent::DragDrop(e) => Self::DragDrop(e),
186 }
187 }
188}
189
190#[derive(Debug)]
194#[non_exhaustive]
195pub enum RunEvent {
196 Exit,
198 #[non_exhaustive]
200 ExitRequested {
201 code: Option<i32>,
205 api: ExitRequestApi,
207 },
208 #[non_exhaustive]
210 WindowEvent {
211 label: String,
213 event: WindowEvent,
215 },
216 #[non_exhaustive]
218 WebviewEvent {
219 label: String,
221 event: WebviewEvent,
223 },
224 Ready,
226 Resumed,
228 MainEventsCleared,
232 #[cfg(any(target_os = "macos", target_os = "ios"))]
234 #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", feature = "ios"))))]
235 Opened {
236 urls: Vec<url::Url>,
238 },
239 #[cfg(desktop)]
241 #[cfg_attr(docsrs, doc(cfg(desktop)))]
242 MenuEvent(crate::menu::MenuEvent),
243 #[cfg(all(desktop, feature = "tray-icon"))]
245 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
246 TrayIconEvent(crate::tray::TrayIconEvent),
247 #[non_exhaustive]
249 #[cfg(target_os = "macos")]
250 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
251 Reopen {
252 has_visible_windows: bool,
254 },
255}
256
257impl From<EventLoopMessage> for RunEvent {
258 fn from(event: EventLoopMessage) -> Self {
259 match event {
260 #[cfg(desktop)]
261 EventLoopMessage::MenuEvent(e) => Self::MenuEvent(e),
262 #[cfg(all(desktop, feature = "tray-icon"))]
263 EventLoopMessage::TrayIconEvent(e) => Self::TrayIconEvent(e),
264 }
265 }
266}
267
268#[derive(Debug, Clone)]
270pub struct AssetResolver<R: Runtime> {
271 manager: Arc<AppManager<R>>,
272}
273
274impl<R: Runtime> AssetResolver<R> {
275 pub fn get(&self, path: String) -> Option<Asset> {
289 let use_https_scheme = self
290 .manager
291 .webviews()
292 .values()
293 .all(|webview| webview.use_https_scheme());
294 self.get_for_scheme(path, use_https_scheme)
295 }
296
297 pub fn get_for_scheme(&self, path: String, use_https_scheme: bool) -> Option<Asset> {
302 #[cfg(dev)]
303 {
304 if let (Some(_), Some(crate::utils::config::FrontendDist::Directory(dist_path))) = (
308 &self.manager.config().build.dev_url,
309 &self.manager.config().build.frontend_dist,
310 ) {
311 let asset_path = std::path::PathBuf::from(&path)
312 .components()
313 .filter(|c| !matches!(c, std::path::Component::RootDir))
314 .collect::<std::path::PathBuf>();
315
316 let asset_path = self
317 .manager
318 .config_parent()
319 .map(|p| p.join(dist_path).join(&asset_path))
320 .unwrap_or_else(|| dist_path.join(&asset_path));
321 return std::fs::read(asset_path).ok().map(|bytes| {
322 let mime_type = crate::utils::mime_type::MimeType::parse(&bytes, &path);
323 Asset {
324 bytes,
325 mime_type,
326 csp_header: None,
327 }
328 });
329 }
330 }
331
332 self.manager.get_asset(path, use_https_scheme).ok()
333 }
334
335 pub fn iter(&self) -> Box<AssetsIter<'_>> {
337 self.manager.assets.iter()
338 }
339}
340
341#[default_runtime(crate::Wry, wry)]
345#[derive(Debug)]
346pub struct AppHandle<R: Runtime> {
347 pub(crate) runtime_handle: R::Handle,
348 pub(crate) manager: Arc<AppManager<R>>,
349 event_loop: Arc<Mutex<EventLoop>>,
350}
351
352#[derive(Debug)]
354struct EventLoop {
355 main_thread_id: ThreadId,
356}
357
358#[cfg(feature = "wry")]
360impl AppHandle<crate::Wry> {
361 pub fn create_tao_window<
363 F: FnOnce() -> (String, tauri_runtime_wry::TaoWindowBuilder) + Send + 'static,
364 >(
365 &self,
366 f: F,
367 ) -> crate::Result<std::sync::Weak<tauri_runtime_wry::Window>> {
368 self.runtime_handle.create_tao_window(f).map_err(Into::into)
369 }
370
371 pub fn send_tao_window_event(
373 &self,
374 window_id: tauri_runtime_wry::TaoWindowId,
375 message: tauri_runtime_wry::WindowMessage,
376 ) -> crate::Result<()> {
377 self
378 .runtime_handle
379 .send_event(tauri_runtime_wry::Message::Window(
380 self.runtime_handle.window_id(window_id),
381 message,
382 ))
383 .map_err(Into::into)
384 }
385}
386
387#[cfg(target_vendor = "apple")]
388impl<R: Runtime> AppHandle<R> {
389 pub async fn fetch_data_store_identifiers(&self) -> crate::Result<Vec<[u8; 16]>> {
393 let (tx, rx) = tokio::sync::oneshot::channel::<Result<Vec<[u8; 16]>, tauri_runtime::Error>>();
394 let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
395 let runtime_handle = self.runtime_handle.clone();
396
397 self.run_on_main_thread(move || {
398 let cloned_lock = lock.clone();
399 if let Err(err) = runtime_handle.fetch_data_store_identifiers(move |ids| {
400 if let Some(tx) = cloned_lock.lock().unwrap().take() {
401 let _ = tx.send(Ok(ids));
402 }
403 }) {
404 if let Some(tx) = lock.lock().unwrap().take() {
405 let _ = tx.send(Err(err));
406 }
407 }
408 })?;
409
410 rx.await?.map_err(Into::into)
411 }
412 pub async fn remove_data_store(&self, uuid: [u8; 16]) -> crate::Result<()> {
416 let (tx, rx) = tokio::sync::oneshot::channel::<Result<(), tauri_runtime::Error>>();
417 let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
418 let runtime_handle = self.runtime_handle.clone();
419
420 self.run_on_main_thread(move || {
421 let cloned_lock = lock.clone();
422 if let Err(err) = runtime_handle.remove_data_store(uuid, move |result| {
423 if let Some(tx) = cloned_lock.lock().unwrap().take() {
424 let _ = tx.send(result);
425 }
426 }) {
427 if let Some(tx) = lock.lock().unwrap().take() {
428 let _ = tx.send(Err(err));
429 }
430 }
431 })?;
432 rx.await?.map_err(Into::into)
433 }
434}
435
436impl<R: Runtime> Clone for AppHandle<R> {
437 fn clone(&self) -> Self {
438 Self {
439 runtime_handle: self.runtime_handle.clone(),
440 manager: self.manager.clone(),
441 event_loop: self.event_loop.clone(),
442 }
443 }
444}
445
446impl<'de, R: Runtime> CommandArg<'de, R> for AppHandle<R> {
447 fn from_command(command: CommandItem<'de, R>) -> std::result::Result<Self, InvokeError> {
449 Ok(command.message.webview().app_handle)
450 }
451}
452
453impl<R: Runtime> AppHandle<R> {
454 pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
456 self
457 .runtime_handle
458 .run_on_main_thread(f)
459 .map_err(Into::into)
460 }
461
462 pub fn plugin<P: Plugin<R> + 'static>(&self, plugin: P) -> crate::Result<()> {
488 self.plugin_boxed(Box::new(plugin))
489 }
490
491 #[cfg_attr(feature = "tracing", tracing::instrument(name = "app::plugin::register", skip(plugin), fields(name = plugin.name())))]
496 pub fn plugin_boxed(&self, mut plugin: Box<dyn Plugin<R>>) -> crate::Result<()> {
497 let mut store = self.manager().plugins.lock().unwrap();
498 store.initialize(&mut plugin, self, &self.config().plugins)?;
499 store.register(plugin);
500
501 Ok(())
502 }
503
504 pub fn remove_plugin(&self, plugin: &str) -> bool {
530 self.manager().plugins.lock().unwrap().unregister(plugin)
531 }
532
533 pub fn exit(&self, exit_code: i32) {
535 if let Err(e) = self.runtime_handle.request_exit(exit_code) {
536 log::error!("failed to exit: {}", e);
537 self.cleanup_before_exit();
538 std::process::exit(exit_code);
539 }
540 }
541
542 pub fn restart(&self) -> ! {
549 if self.event_loop.lock().unwrap().main_thread_id == std::thread::current().id() {
550 log::debug!("restart triggered on the main thread");
551 self.cleanup_before_exit();
552 crate::process::restart(&self.env());
553 } else {
554 log::debug!("restart triggered from a separate thread");
555 self
557 .manager
558 .restart_on_exit
559 .store(true, atomic::Ordering::Relaxed);
560 match self.runtime_handle.request_exit(RESTART_EXIT_CODE) {
562 Ok(()) => loop {
563 std::thread::sleep(Duration::MAX);
564 },
565 Err(e) => {
566 log::error!("failed to request exit: {e}");
567 self.cleanup_before_exit();
568 crate::process::restart(&self.env());
569 }
570 }
571 }
572 }
573
574 pub fn request_restart(&self) {
576 self
577 .manager
578 .restart_on_exit
579 .store(true, atomic::Ordering::Relaxed);
580 if self.runtime_handle.request_exit(RESTART_EXIT_CODE).is_err() {
582 self.cleanup_before_exit();
583 crate::process::restart(&self.env());
584 }
585 }
586
587 #[cfg(target_os = "macos")]
599 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
600 pub fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> crate::Result<()> {
601 self
602 .runtime_handle
603 .set_activation_policy(activation_policy)
604 .map_err(Into::into)
605 }
606
607 #[cfg(target_os = "macos")]
619 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
620 pub fn set_dock_visibility(&self, visible: bool) -> crate::Result<()> {
621 self
622 .runtime_handle
623 .set_dock_visibility(visible)
624 .map_err(Into::into)
625 }
626
627 pub fn set_device_event_filter(&self, filter: DeviceEventFilter) {
635 self.runtime_handle.set_device_event_filter(filter);
636 }
637}
638
639impl<R: Runtime> Manager<R> for AppHandle<R> {
640 fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
641 self.manager.resources_table()
642 }
643}
644
645impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
646 fn manager(&self) -> &AppManager<R> {
647 &self.manager
648 }
649
650 fn manager_owned(&self) -> Arc<AppManager<R>> {
651 self.manager.clone()
652 }
653
654 fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
655 RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
656 }
657
658 fn managed_app_handle(&self) -> &AppHandle<R> {
659 self
660 }
661}
662
663#[default_runtime(crate::Wry, wry)]
667pub struct App<R: Runtime> {
668 runtime: Option<R>,
669 setup: Option<SetupHook<R>>,
670 manager: Arc<AppManager<R>>,
671 handle: AppHandle<R>,
672 ran_setup: bool,
673}
674
675impl<R: Runtime> fmt::Debug for App<R> {
676 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677 f.debug_struct("App")
678 .field("runtime", &self.runtime)
679 .field("manager", &self.manager)
680 .field("handle", &self.handle)
681 .finish()
682 }
683}
684
685impl<R: Runtime> Manager<R> for App<R> {
686 fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
687 self.manager.resources_table()
688 }
689}
690
691impl<R: Runtime> ManagerBase<R> for App<R> {
692 fn manager(&self) -> &AppManager<R> {
693 &self.manager
694 }
695
696 fn manager_owned(&self) -> Arc<AppManager<R>> {
697 self.manager.clone()
698 }
699
700 fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
701 if let Some(runtime) = self.runtime.as_ref() {
702 RuntimeOrDispatch::Runtime(runtime)
703 } else {
704 self.handle.runtime()
705 }
706 }
707
708 fn managed_app_handle(&self) -> &AppHandle<R> {
709 self.handle()
710 }
711}
712
713#[cfg(feature = "wry")]
715impl App<crate::Wry> {
716 pub fn wry_plugin<P: tauri_runtime_wry::PluginBuilder<EventLoopMessage> + Send + 'static>(
722 &mut self,
723 plugin: P,
724 ) where
725 <P as tauri_runtime_wry::PluginBuilder<EventLoopMessage>>::Plugin: Send,
726 {
727 self.handle.runtime_handle.plugin(plugin);
728 }
729}
730
731macro_rules! shared_app_impl {
732 ($app: ty) => {
733 impl<R: Runtime> $app {
734 #[cfg(desktop)]
736 pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(
737 &self,
738 handler: F,
739 ) {
740 self.manager.menu.on_menu_event(handler)
741 }
742
743 #[cfg(all(desktop, feature = "tray-icon"))]
745 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
746 pub fn on_tray_icon_event<F: Fn(&AppHandle<R>, TrayIconEvent) + Send + Sync + 'static>(
747 &self,
748 handler: F,
749 ) {
750 self.manager.tray.on_tray_icon_event(handler)
751 }
752
753 #[cfg(all(desktop, feature = "tray-icon"))]
755 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
756 pub fn tray_by_id<'a, I>(&self, id: &'a I) -> Option<TrayIcon<R>>
757 where
758 I: ?Sized,
759 TrayIconId: PartialEq<&'a I>,
760 {
761 self.manager.tray.tray_by_id(self.app_handle(), id)
762 }
763
764 #[cfg(all(desktop, feature = "tray-icon"))]
769 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
770 pub fn remove_tray_by_id<'a, I>(&self, id: &'a I) -> Option<TrayIcon<R>>
771 where
772 I: ?Sized,
773 TrayIconId: PartialEq<&'a I>,
774 {
775 self.manager.tray.remove_tray_by_id(self.app_handle(), id)
776 }
777
778 pub fn config(&self) -> &Config {
780 self.manager.config()
781 }
782
783 pub fn package_info(&self) -> &PackageInfo {
785 self.manager.package_info()
786 }
787
788 pub fn asset_resolver(&self) -> AssetResolver<R> {
790 AssetResolver {
791 manager: self.manager.clone(),
792 }
793 }
794
795 pub fn primary_monitor(&self) -> crate::Result<Option<Monitor>> {
799 Ok(match self.runtime() {
800 RuntimeOrDispatch::Runtime(h) => h.primary_monitor().map(Into::into),
801 RuntimeOrDispatch::RuntimeHandle(h) => h.primary_monitor().map(Into::into),
802 _ => unreachable!(),
803 })
804 }
805
806 pub fn monitor_from_point(&self, x: f64, y: f64) -> crate::Result<Option<Monitor>> {
808 Ok(match self.runtime() {
809 RuntimeOrDispatch::Runtime(h) => h.monitor_from_point(x, y).map(Into::into),
810 RuntimeOrDispatch::RuntimeHandle(h) => h.monitor_from_point(x, y).map(Into::into),
811 _ => unreachable!(),
812 })
813 }
814
815 pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {
817 Ok(match self.runtime() {
818 RuntimeOrDispatch::Runtime(h) => {
819 h.available_monitors().into_iter().map(Into::into).collect()
820 }
821 RuntimeOrDispatch::RuntimeHandle(h) => {
822 h.available_monitors().into_iter().map(Into::into).collect()
823 }
824 _ => unreachable!(),
825 })
826 }
827
828 pub fn cursor_position(&self) -> crate::Result<PhysicalPosition<f64>> {
837 Ok(match self.runtime() {
838 RuntimeOrDispatch::Runtime(h) => h.cursor_position()?,
839 RuntimeOrDispatch::RuntimeHandle(h) => h.cursor_position()?,
840 _ => unreachable!(),
841 })
842 }
843
844 pub fn set_theme(&self, theme: Option<Theme>) {
850 #[cfg(windows)]
851 for window in self.manager.windows().values() {
852 if let (Some(menu), Ok(hwnd)) = (window.menu(), window.hwnd()) {
853 let raw_hwnd = hwnd.0 as isize;
854 let _ = self.run_on_main_thread(move || {
855 let _ = unsafe {
856 menu.inner().set_theme_for_hwnd(
857 raw_hwnd,
858 theme
859 .map(crate::menu::map_to_menu_theme)
860 .unwrap_or(muda::MenuTheme::Auto),
861 )
862 };
863 });
864 };
865 }
866 match self.runtime() {
867 RuntimeOrDispatch::Runtime(h) => h.set_theme(theme),
868 RuntimeOrDispatch::RuntimeHandle(h) => h.set_theme(theme),
869 _ => unreachable!(),
870 }
871 }
872
873 pub fn default_window_icon(&self) -> Option<&Image<'_>> {
875 self.manager.window.default_icon.as_ref()
876 }
877
878 #[cfg(desktop)]
880 pub fn menu(&self) -> Option<Menu<R>> {
881 self.manager.menu.menu_lock().clone()
882 }
883
884 #[cfg(desktop)]
889 pub fn set_menu(&self, menu: Menu<R>) -> crate::Result<Option<Menu<R>>> {
890 let prev_menu = self.remove_menu()?;
891
892 self.manager.menu.insert_menu_into_stash(&menu);
893
894 self.manager.menu.menu_lock().replace(menu.clone());
895
896 #[cfg(not(target_os = "macos"))]
898 {
899 for window in self.manager.windows().values() {
900 let has_app_wide_menu = window.has_app_wide_menu() || window.menu().is_none();
901 if has_app_wide_menu {
902 window.set_menu(menu.clone())?;
903 window.menu_lock().replace(crate::window::WindowMenu {
904 is_app_wide: true,
905 menu: menu.clone(),
906 });
907 }
908 }
909 }
910
911 #[cfg(target_os = "macos")]
913 {
914 let menu_ = menu.clone();
915 self.run_on_main_thread(move || {
916 let _ = init_app_menu(&menu_);
917 })?;
918 }
919
920 Ok(prev_menu)
921 }
922
923 #[cfg(desktop)]
928 pub fn remove_menu(&self) -> crate::Result<Option<Menu<R>>> {
929 let menu = self.manager.menu.menu_lock().as_ref().cloned();
930 #[allow(unused_variables)]
931 if let Some(menu) = menu {
932 #[cfg(not(target_os = "macos"))]
934 {
935 for window in self.manager.windows().values() {
936 let has_app_wide_menu = window.has_app_wide_menu();
937 if has_app_wide_menu {
938 window.remove_menu()?;
939 *window.menu_lock() = None;
940 }
941 }
942 }
943
944 #[cfg(target_os = "macos")]
946 {
947 self.run_on_main_thread(move || {
948 menu.inner().remove_for_nsapp();
949 })?;
950 }
951 }
952
953 let prev_menu = self.manager.menu.menu_lock().take();
954
955 self
956 .manager
957 .remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id()));
958
959 Ok(prev_menu)
960 }
961
962 #[cfg(desktop)]
967 pub fn hide_menu(&self) -> crate::Result<()> {
968 #[cfg(not(target_os = "macos"))]
969 {
970 let is_app_menu_set = self.manager.menu.menu_lock().is_some();
971 if is_app_menu_set {
972 for window in self.manager.windows().values() {
973 if window.has_app_wide_menu() {
974 window.hide_menu()?;
975 }
976 }
977 }
978 }
979
980 Ok(())
981 }
982
983 #[cfg(desktop)]
988 pub fn show_menu(&self) -> crate::Result<()> {
989 #[cfg(not(target_os = "macos"))]
990 {
991 let is_app_menu_set = self.manager.menu.menu_lock().is_some();
992 if is_app_menu_set {
993 for window in self.manager.windows().values() {
994 if window.has_app_wide_menu() {
995 window.show_menu()?;
996 }
997 }
998 }
999 }
1000
1001 Ok(())
1002 }
1003
1004 #[cfg(target_os = "macos")]
1006 pub fn show(&self) -> crate::Result<()> {
1007 match self.runtime() {
1008 RuntimeOrDispatch::Runtime(r) => r.show(),
1009 RuntimeOrDispatch::RuntimeHandle(h) => h.show()?,
1010 _ => unreachable!(),
1011 }
1012 Ok(())
1013 }
1014
1015 #[cfg(target_os = "macos")]
1017 pub fn hide(&self) -> crate::Result<()> {
1018 match self.runtime() {
1019 RuntimeOrDispatch::Runtime(r) => r.hide(),
1020 RuntimeOrDispatch::RuntimeHandle(h) => h.hide()?,
1021 _ => unreachable!(),
1022 }
1023 Ok(())
1024 }
1025
1026 pub fn cleanup_before_exit(&self) {
1029 #[cfg(all(desktop, feature = "tray-icon"))]
1030 self.manager.tray.icons.lock().unwrap().clear();
1031 self.manager.resources_table().clear();
1032 for (_, window) in self.manager.windows() {
1033 window.resources_table().clear();
1034 #[cfg(windows)]
1035 let _ = window.hide();
1036 }
1037 for (_, webview) in self.manager.webviews() {
1038 webview.resources_table().clear();
1039 }
1040 }
1041
1042 pub fn invoke_key(&self) -> &str {
1048 self.manager.invoke_key()
1049 }
1050 }
1051
1052 impl<R: Runtime> Listener<R> for $app {
1053 fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventId
1070 where
1071 F: Fn(Event) + Send + 'static,
1072 {
1073 let event = EventName::new(event.into()).unwrap();
1074 self.manager.listen(event, EventTarget::App, handler)
1075 }
1076
1077 fn once<F>(&self, event: impl Into<String>, handler: F) -> EventId
1081 where
1082 F: FnOnce(Event) + Send + 'static,
1083 {
1084 let event = EventName::new(event.into()).unwrap();
1085 self.manager.once(event, EventTarget::App, handler)
1086 }
1087
1088 fn unlisten(&self, id: EventId) {
1108 self.manager.unlisten(id)
1109 }
1110 }
1111
1112 impl<R: Runtime> Emitter<R> for $app {}
1113 };
1114}
1115
1116shared_app_impl!(App<R>);
1117shared_app_impl!(AppHandle<R>);
1118
1119impl<R: Runtime> App<R> {
1120 #[cfg_attr(
1121 feature = "tracing",
1122 tracing::instrument(name = "app::core_plugins::register")
1123 )]
1124 fn register_core_plugins(&self) -> crate::Result<()> {
1125 self.handle.plugin(crate::path::plugin::init())?;
1126 self.handle.plugin(crate::event::plugin::init(self))?;
1127 self.handle.plugin(crate::window::plugin::init())?;
1128 self.handle.plugin(crate::webview::plugin::init())?;
1129 self.handle.plugin(crate::app::plugin::init())?;
1130 self.handle.plugin(crate::resources::plugin::init())?;
1131 self.handle.plugin(crate::image::plugin::init())?;
1132 #[cfg(desktop)]
1133 self.handle.plugin(crate::menu::plugin::init())?;
1134 #[cfg(all(desktop, feature = "tray-icon"))]
1135 self.handle.plugin(crate::tray::plugin::init())?;
1136 Ok(())
1137 }
1138
1139 pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
1141 self.app_handle().run_on_main_thread(f)
1142 }
1143
1144 pub fn handle(&self) -> &AppHandle<R> {
1146 &self.handle
1147 }
1148
1149 #[cfg(target_os = "macos")]
1161 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
1162 pub fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
1163 if let Some(runtime) = self.runtime.as_mut() {
1164 runtime.set_activation_policy(activation_policy);
1165 } else {
1166 let _ = self.app_handle().set_activation_policy(activation_policy);
1167 }
1168 }
1169
1170 #[cfg(target_os = "macos")]
1182 #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
1183 pub fn set_dock_visibility(&mut self, visible: bool) {
1184 if let Some(runtime) = self.runtime.as_mut() {
1185 runtime.set_dock_visibility(visible);
1186 } else {
1187 let _ = self.app_handle().set_dock_visibility(visible);
1188 }
1189 }
1190
1191 pub fn set_device_event_filter(&mut self, filter: DeviceEventFilter) {
1213 self
1214 .runtime
1215 .as_mut()
1216 .unwrap()
1217 .set_device_event_filter(filter);
1218 }
1219
1220 pub fn run<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, callback: F) {
1243 self.handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
1244
1245 self
1246 .runtime
1247 .take()
1248 .unwrap()
1249 .run(self.make_run_event_loop_callback(callback));
1250 }
1251
1252 pub fn run_return<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, callback: F) -> i32 {
1282 self.handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
1283
1284 self
1285 .runtime
1286 .take()
1287 .unwrap()
1288 .run_return(self.make_run_event_loop_callback(callback))
1289 }
1290
1291 fn make_run_event_loop_callback<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(
1292 mut self,
1293 mut callback: F,
1294 ) -> impl FnMut(RuntimeRunEvent<EventLoopMessage>) {
1295 let app_handle = self.handle().clone();
1296 let manager = self.manager.clone();
1297
1298 move |event| match event {
1299 RuntimeRunEvent::Ready => {
1300 if let Err(e) = setup(&mut self) {
1301 panic!("Failed to setup app: {e}");
1302 }
1303 let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Ready, &manager);
1304 callback(&app_handle, event);
1305 }
1306 RuntimeRunEvent::Exit => {
1307 let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Exit, &manager);
1308 callback(&app_handle, event);
1309 app_handle.cleanup_before_exit();
1310 if self.manager.restart_on_exit.load(atomic::Ordering::Relaxed) {
1311 crate::process::restart(&self.env());
1312 }
1313 }
1314 _ => {
1315 let event = on_event_loop_event(&app_handle, event, &manager);
1316 callback(&app_handle, event);
1317 }
1318 }
1319 }
1320
1321 #[cfg(desktop)]
1344 #[deprecated(
1345 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."
1346 )]
1347 pub fn run_iteration<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(&mut self, mut callback: F) {
1348 let manager = self.manager.clone();
1349 let app_handle = self.handle().clone();
1350
1351 if !self.ran_setup {
1352 if let Err(e) = setup(self) {
1353 panic!("Failed to setup app: {e}");
1354 }
1355 }
1356
1357 app_handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
1358
1359 self.runtime.as_mut().unwrap().run_iteration(move |event| {
1360 let event = on_event_loop_event(&app_handle, event, &manager);
1361 callback(&app_handle, event);
1362 })
1363 }
1364}
1365
1366#[allow(clippy::type_complexity)]
1376pub struct Builder<R: Runtime> {
1377 #[cfg(any(windows, target_os = "linux"))]
1379 runtime_any_thread: bool,
1380
1381 invoke_handler: Box<InvokeHandler<R>>,
1383
1384 pub(crate) invoke_initialization_script: String,
1386
1387 channel_interceptor: Option<ChannelInterceptor<R>>,
1388
1389 setup: SetupHook<R>,
1391
1392 on_page_load: Option<Arc<OnPageLoad<R>>>,
1394
1395 plugins: PluginStore<R>,
1397
1398 uri_scheme_protocols: HashMap<String, Arc<UriSchemeProtocol<R>>>,
1400
1401 state: StateManager,
1403
1404 #[cfg(desktop)]
1406 menu: Option<Box<dyn FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send>>,
1407
1408 #[cfg(desktop)]
1410 menu_event_listeners: Vec<GlobalMenuEventListener<AppHandle<R>>>,
1411
1412 #[cfg(all(desktop, feature = "tray-icon"))]
1414 tray_icon_event_listeners: Vec<GlobalTrayIconEventListener<AppHandle<R>>>,
1415
1416 #[allow(unused)]
1418 enable_macos_default_menu: bool,
1419
1420 window_event_listeners: Vec<GlobalWindowEventListener<R>>,
1422
1423 webview_event_listeners: Vec<GlobalWebviewEventListener<R>>,
1425
1426 device_event_filter: DeviceEventFilter,
1428
1429 pub(crate) invoke_key: String,
1430}
1431
1432#[derive(Template)]
1433#[default_template("../scripts/ipc-protocol.js")]
1434pub(crate) struct InvokeInitializationScript<'a> {
1435 #[raw]
1437 pub(crate) process_ipc_message_fn: &'a str,
1438 pub(crate) os_name: &'a str,
1439 pub(crate) fetch_channel_data_command: &'a str,
1440 pub(crate) invoke_key: &'a str,
1441}
1442
1443#[cfg(feature = "wry")]
1445#[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
1446impl Default for Builder<crate::Wry> {
1447 fn default() -> Self {
1448 Self::new()
1449 }
1450}
1451
1452#[cfg(not(feature = "wry"))]
1453#[cfg_attr(docsrs, doc(cfg(not(feature = "wry"))))]
1454impl<R: Runtime> Default for Builder<R> {
1455 fn default() -> Self {
1456 Self::new()
1457 }
1458}
1459
1460impl<R: Runtime> Builder<R> {
1461 pub fn new() -> Self {
1463 let invoke_key = crate::generate_invoke_key().unwrap();
1464
1465 Self {
1466 #[cfg(any(windows, target_os = "linux"))]
1467 runtime_any_thread: false,
1468 setup: Box::new(|_| Ok(())),
1469 invoke_handler: Box::new(|_| false),
1470 invoke_initialization_script: InvokeInitializationScript {
1471 process_ipc_message_fn: crate::manager::webview::PROCESS_IPC_MESSAGE_FN,
1472 os_name: std::env::consts::OS,
1473 fetch_channel_data_command: crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND,
1474 invoke_key: &invoke_key.clone(),
1475 }
1476 .render_default(&Default::default())
1477 .unwrap()
1478 .into_string(),
1479 channel_interceptor: None,
1480 on_page_load: None,
1481 plugins: PluginStore::default(),
1482 uri_scheme_protocols: Default::default(),
1483 state: StateManager::new(),
1484 #[cfg(desktop)]
1485 menu: None,
1486 #[cfg(desktop)]
1487 menu_event_listeners: Vec::new(),
1488 #[cfg(all(desktop, feature = "tray-icon"))]
1489 tray_icon_event_listeners: Vec::new(),
1490 enable_macos_default_menu: true,
1491 window_event_listeners: Vec::new(),
1492 webview_event_listeners: Vec::new(),
1493 device_event_filter: Default::default(),
1494 invoke_key,
1495 }
1496 }
1497}
1498
1499impl<R: Runtime> Builder<R> {
1500 #[cfg(any(windows, target_os = "linux"))]
1506 #[cfg_attr(docsrs, doc(cfg(any(windows, target_os = "linux"))))]
1507 #[must_use]
1508 pub fn any_thread(mut self) -> Self {
1509 self.runtime_any_thread = true;
1510 self
1511 }
1512
1513 #[must_use]
1528 pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
1529 where
1530 F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
1531 {
1532 self.invoke_handler = Box::new(invoke_handler);
1533 self
1534 }
1535
1536 #[must_use]
1555 pub fn invoke_system(mut self, initialization_script: impl AsRef<str>) -> Self {
1556 self.invoke_initialization_script = initialization_script
1557 .as_ref()
1558 .replace("__INVOKE_KEY__", &format!("\"{}\"", self.invoke_key));
1559 self
1560 }
1561
1562 pub fn channel_interceptor<
1569 F: Fn(&Webview<R>, CallbackFn, usize, &InvokeResponseBody) -> bool + Send + Sync + 'static,
1570 >(
1571 mut self,
1572 interceptor: F,
1573 ) -> Self {
1574 self.channel_interceptor.replace(Box::new(interceptor));
1575 self
1576 }
1577
1578 pub fn append_invoke_initialization_script(
1616 mut self,
1617 initialization_script: impl AsRef<str>,
1618 ) -> Self {
1619 self
1620 .invoke_initialization_script
1621 .push_str(initialization_script.as_ref());
1622 self
1623 }
1624
1625 #[cfg_attr(
1629 feature = "unstable",
1630 doc = r####"
1631```
1632use tauri::Manager;
1633tauri::Builder::default()
1634 .setup(|app| {
1635 let main_window = app.get_webview_window("main").unwrap();
1636 main_window.set_title("Tauri!")?;
1637 Ok(())
1638 });
1639```
1640 "####
1641 )]
1642 #[must_use]
1643 pub fn setup<F>(mut self, setup: F) -> Self
1644 where
1645 F: FnOnce(&mut App<R>) -> std::result::Result<(), Box<dyn std::error::Error>> + Send + 'static,
1646 {
1647 self.setup = Box::new(setup);
1648 self
1649 }
1650
1651 #[must_use]
1653 pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
1654 where
1655 F: Fn(&Webview<R>, &PageLoadPayload<'_>) + Send + Sync + 'static,
1656 {
1657 self.on_page_load.replace(Arc::new(on_page_load));
1658 self
1659 }
1660
1661 #[must_use]
1703 pub fn plugin<P: Plugin<R> + 'static>(self, plugin: P) -> Self {
1704 self.plugin_boxed(Box::new(plugin))
1705 }
1706
1707 #[must_use]
1712 pub fn plugin_boxed(mut self, plugin: Box<dyn Plugin<R>>) -> Self {
1713 self.plugins.register(plugin);
1714 self
1715 }
1716
1717 #[must_use]
1796 pub fn manage<T>(self, state: T) -> Self
1797 where
1798 T: Send + Sync + 'static,
1799 {
1800 let type_name = std::any::type_name::<T>();
1801 assert!(
1802 self.state.set(state),
1803 "state for type '{type_name}' is already being managed",
1804 );
1805 self
1806 }
1807
1808 #[must_use]
1829 #[cfg(desktop)]
1830 pub fn menu<F: FnOnce(&AppHandle<R>) -> crate::Result<Menu<R>> + Send + 'static>(
1831 mut self,
1832 f: F,
1833 ) -> Self {
1834 self.menu.replace(Box::new(f));
1835 self
1836 }
1837
1838 #[must_use]
1852 #[cfg(desktop)]
1853 pub fn on_menu_event<F: Fn(&AppHandle<R>, MenuEvent) + Send + Sync + 'static>(
1854 mut self,
1855 f: F,
1856 ) -> Self {
1857 self.menu_event_listeners.push(Box::new(f));
1858 self
1859 }
1860
1861 #[must_use]
1874 #[cfg(all(desktop, feature = "tray-icon"))]
1875 #[cfg_attr(docsrs, doc(cfg(all(desktop, feature = "tray-icon"))))]
1876 pub fn on_tray_icon_event<F: Fn(&AppHandle<R>, TrayIconEvent) + Send + Sync + 'static>(
1877 mut self,
1878 f: F,
1879 ) -> Self {
1880 self.tray_icon_event_listeners.push(Box::new(f));
1881 self
1882 }
1883
1884 #[must_use]
1892 pub fn enable_macos_default_menu(mut self, enable: bool) -> Self {
1893 self.enable_macos_default_menu = enable;
1894 self
1895 }
1896
1897 #[must_use]
1913 pub fn on_window_event<F: Fn(&Window<R>, &WindowEvent) + Send + Sync + 'static>(
1914 mut self,
1915 handler: F,
1916 ) -> Self {
1917 self.window_event_listeners.push(Box::new(handler));
1918 self
1919 }
1920
1921 #[must_use]
1934 pub fn on_webview_event<F: Fn(&Webview<R>, &WebviewEvent) + Send + Sync + 'static>(
1935 mut self,
1936 handler: F,
1937 ) -> Self {
1938 self.webview_event_listeners.push(Box::new(handler));
1939 self
1940 }
1941
1942 #[must_use]
1983 pub fn register_uri_scheme_protocol<
1984 N: Into<String>,
1985 T: Into<Cow<'static, [u8]>>,
1986 H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>) -> http::Response<T>
1987 + Send
1988 + Sync
1989 + 'static,
1990 >(
1991 mut self,
1992 uri_scheme: N,
1993 protocol: H,
1994 ) -> Self {
1995 self.uri_scheme_protocols.insert(
1996 uri_scheme.into(),
1997 Arc::new(UriSchemeProtocol {
1998 protocol: Box::new(move |ctx, request, responder| {
1999 responder.respond(protocol(ctx, request))
2000 }),
2001 }),
2002 );
2003 self
2004 }
2005
2006 #[must_use]
2051 pub fn register_asynchronous_uri_scheme_protocol<
2052 N: Into<String>,
2053 H: Fn(UriSchemeContext<'_, R>, http::Request<Vec<u8>>, UriSchemeResponder) + Send + Sync + 'static,
2054 >(
2055 mut self,
2056 uri_scheme: N,
2057 protocol: H,
2058 ) -> Self {
2059 self.uri_scheme_protocols.insert(
2060 uri_scheme.into(),
2061 Arc::new(UriSchemeProtocol {
2062 protocol: Box::new(protocol),
2063 }),
2064 );
2065 self
2066 }
2067
2068 pub fn device_event_filter(mut self, filter: DeviceEventFilter) -> Self {
2086 self.device_event_filter = filter;
2087 self
2088 }
2089
2090 #[allow(clippy::type_complexity, unused_mut)]
2092 #[cfg_attr(
2093 feature = "tracing",
2094 tracing::instrument(name = "app::build", skip_all)
2095 )]
2096 pub fn build(mut self, context: Context<R>) -> crate::Result<App<R>> {
2097 #[cfg(target_os = "macos")]
2098 if self.menu.is_none() && self.enable_macos_default_menu {
2099 self.menu = Some(Box::new(|app_handle| {
2100 crate::menu::Menu::default(app_handle)
2101 }));
2102 }
2103
2104 let manager = Arc::new(AppManager::with_handlers(
2105 context,
2106 self.plugins,
2107 self.invoke_handler,
2108 self.on_page_load,
2109 self.uri_scheme_protocols,
2110 self.state,
2111 #[cfg(desktop)]
2112 self.menu_event_listeners,
2113 #[cfg(all(desktop, feature = "tray-icon"))]
2114 self.tray_icon_event_listeners,
2115 self.window_event_listeners,
2116 self.webview_event_listeners,
2117 #[cfg(desktop)]
2118 HashMap::new(),
2119 self.invoke_initialization_script,
2120 self.channel_interceptor,
2121 self.invoke_key,
2122 ));
2123
2124 #[cfg(any(
2125 target_os = "linux",
2126 target_os = "dragonfly",
2127 target_os = "freebsd",
2128 target_os = "netbsd",
2129 target_os = "openbsd"
2130 ))]
2131 let app_id = if manager.config.app.enable_gtk_app_id {
2132 Some(manager.config.identifier.clone())
2133 } else {
2134 None
2135 };
2136
2137 let runtime_args = RuntimeInitArgs {
2138 #[cfg(any(
2139 target_os = "linux",
2140 target_os = "dragonfly",
2141 target_os = "freebsd",
2142 target_os = "netbsd",
2143 target_os = "openbsd"
2144 ))]
2145 app_id,
2146
2147 #[cfg(windows)]
2148 msg_hook: {
2149 let menus = manager.menu.menus.clone();
2150 Some(Box::new(move |msg| {
2151 use windows::Win32::UI::WindowsAndMessaging::{TranslateAcceleratorW, HACCEL, MSG};
2152 unsafe {
2153 let msg = msg as *const MSG;
2154 for menu in menus.lock().unwrap().values() {
2155 let translated =
2156 TranslateAcceleratorW((*msg).hwnd, HACCEL(menu.inner().haccel() as _), msg);
2157 if translated == 1 {
2158 return true;
2159 }
2160 }
2161
2162 false
2163 }
2164 }))
2165 },
2166 };
2167
2168 #[cfg(windows)]
2170 {
2171 if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } =
2172 &manager.config.bundle.windows.webview_install_mode
2173 {
2174 if let Some(exe_dir) = crate::utils::platform::current_exe()
2175 .ok()
2176 .and_then(|p| p.parent().map(|p| p.to_path_buf()))
2177 {
2178 std::env::set_var("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", exe_dir.join(path));
2179 } else {
2180 #[cfg(debug_assertions)]
2181 eprintln!(
2182 "failed to resolve resource directory; fallback to the installed Webview2 runtime."
2183 );
2184 }
2185 }
2186 }
2187
2188 #[cfg(any(windows, target_os = "linux"))]
2189 let mut runtime = if self.runtime_any_thread {
2190 R::new_any_thread(runtime_args)?
2191 } else {
2192 R::new(runtime_args)?
2193 };
2194 #[cfg(not(any(windows, target_os = "linux")))]
2195 let mut runtime = R::new(runtime_args)?;
2196
2197 #[cfg(desktop)]
2198 {
2199 let proxy = runtime.create_proxy();
2201 muda::MenuEvent::set_event_handler(Some(move |e: muda::MenuEvent| {
2202 let _ = proxy.send_event(EventLoopMessage::MenuEvent(e.into()));
2203 }));
2204
2205 #[cfg(feature = "tray-icon")]
2207 {
2208 let proxy = runtime.create_proxy();
2209 tray_icon::TrayIconEvent::set_event_handler(Some(move |e: tray_icon::TrayIconEvent| {
2210 let _ = proxy.send_event(EventLoopMessage::TrayIconEvent(e.into()));
2211 }));
2212 }
2213 }
2214
2215 runtime.set_device_event_filter(self.device_event_filter);
2216
2217 let runtime_handle = runtime.handle();
2218
2219 #[allow(unused_mut)]
2220 let mut app = App {
2221 runtime: Some(runtime),
2222 setup: Some(self.setup),
2223 manager: manager.clone(),
2224 handle: AppHandle {
2225 runtime_handle,
2226 manager,
2227 event_loop: Arc::new(Mutex::new(EventLoop {
2228 main_thread_id: std::thread::current().id(),
2229 })),
2230 },
2231 ran_setup: false,
2232 };
2233
2234 #[cfg(desktop)]
2235 if let Some(menu) = self.menu {
2236 let menu = menu(&app.handle)?;
2237 app
2238 .manager
2239 .menu
2240 .menus_stash_lock()
2241 .insert(menu.id().clone(), menu.clone());
2242
2243 #[cfg(target_os = "macos")]
2244 init_app_menu(&menu)?;
2245
2246 app.manager.menu.menu_lock().replace(menu);
2247 }
2248
2249 app.register_core_plugins()?;
2250
2251 let env = Env::default();
2252 app.manage(env);
2253
2254 app.manage(Scopes {
2255 #[cfg(feature = "protocol-asset")]
2256 asset_protocol: crate::scope::fs::Scope::new(
2257 &app,
2258 &app.config().app.security.asset_protocol.scope,
2259 )?,
2260 });
2261
2262 app.manage(ChannelDataIpcQueue::default());
2263 app.handle.plugin(crate::ipc::channel::plugin())?;
2264
2265 let handle = app.handle();
2266
2267 #[cfg(all(desktop, feature = "tray-icon"))]
2269 {
2270 let config = app.config();
2271 if let Some(tray_config) = &config.app.tray_icon {
2272 #[allow(deprecated)]
2273 let mut tray =
2274 TrayIconBuilder::with_id(tray_config.id.clone().unwrap_or_else(|| "main".into()))
2275 .icon_as_template(tray_config.icon_as_template)
2276 .menu_on_left_click(tray_config.menu_on_left_click)
2277 .show_menu_on_left_click(tray_config.show_menu_on_left_click);
2278 if let Some(icon) = &app.manager.tray.icon {
2279 tray = tray.icon(icon.clone());
2280 }
2281 if let Some(title) = &tray_config.title {
2282 tray = tray.title(title);
2283 }
2284 if let Some(tooltip) = &tray_config.tooltip {
2285 tray = tray.tooltip(tooltip);
2286 }
2287 tray.build(handle)?;
2288 }
2289 }
2290
2291 app.manager.initialize_plugins(handle)?;
2292
2293 Ok(app)
2294 }
2295
2296 pub fn run(self, context: Context<R>) -> crate::Result<()> {
2301 self.build(context)?.run(|_, _| {});
2302 Ok(())
2303 }
2304}
2305
2306pub(crate) type UriSchemeResponderFn = Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>;
2307
2308pub struct UriSchemeResponder(pub(crate) UriSchemeResponderFn);
2310
2311impl UriSchemeResponder {
2312 pub fn respond<T: Into<Cow<'static, [u8]>>>(self, response: http::Response<T>) {
2314 let (parts, body) = response.into_parts();
2315 (self.0)(http::Response::from_parts(parts, body.into()))
2316 }
2317}
2318
2319pub struct UriSchemeContext<'a, R: Runtime> {
2321 pub(crate) app_handle: &'a AppHandle<R>,
2322 pub(crate) webview_label: &'a str,
2323}
2324
2325impl<'a, R: Runtime> UriSchemeContext<'a, R> {
2326 pub fn app_handle(&self) -> &'a AppHandle<R> {
2328 self.app_handle
2329 }
2330
2331 pub fn webview_label(&self) -> &'a str {
2333 self.webview_label
2334 }
2335}
2336
2337#[cfg(target_os = "macos")]
2338fn init_app_menu<R: Runtime>(menu: &Menu<R>) -> crate::Result<()> {
2339 menu.inner().init_for_nsapp();
2340
2341 if let Some(window_menu) = menu.get(crate::menu::WINDOW_SUBMENU_ID) {
2342 if let Some(m) = window_menu.as_submenu() {
2343 m.set_as_windows_menu_for_nsapp()?;
2344 }
2345 }
2346 if let Some(help_menu) = menu.get(crate::menu::HELP_SUBMENU_ID) {
2347 if let Some(m) = help_menu.as_submenu() {
2348 m.set_as_help_menu_for_nsapp()?;
2349 }
2350 }
2351
2352 Ok(())
2353}
2354
2355impl<R: Runtime> HasDisplayHandle for AppHandle<R> {
2356 fn display_handle(
2357 &self,
2358 ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
2359 self.runtime_handle.display_handle()
2360 }
2361}
2362
2363impl<R: Runtime> HasDisplayHandle for App<R> {
2364 fn display_handle(
2365 &self,
2366 ) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
2367 self.handle.display_handle()
2368 }
2369}
2370
2371#[cfg_attr(feature = "tracing", tracing::instrument(name = "app::setup"))]
2372fn setup<R: Runtime>(app: &mut App<R>) -> crate::Result<()> {
2373 app.ran_setup = true;
2374
2375 for window_config in app.config().app.windows.iter().filter(|w| w.create) {
2376 WebviewWindowBuilder::from_config(app.handle(), window_config)?.build()?;
2377 }
2378
2379 app.manager.assets.setup(app);
2380
2381 if let Some(setup) = app.setup.take() {
2382 (setup)(app).map_err(|e| crate::Error::Setup(e.into()))?;
2383 }
2384
2385 Ok(())
2386}
2387
2388fn on_event_loop_event<R: Runtime>(
2389 app_handle: &AppHandle<R>,
2390 event: RuntimeRunEvent<EventLoopMessage>,
2391 manager: &AppManager<R>,
2392) -> RunEvent {
2393 if let RuntimeRunEvent::WindowEvent {
2394 label,
2395 event: RuntimeWindowEvent::Destroyed,
2396 } = &event
2397 {
2398 manager.on_window_close(label);
2399 }
2400
2401 let event = match event {
2402 RuntimeRunEvent::Exit => RunEvent::Exit,
2403 RuntimeRunEvent::ExitRequested { code, tx } => RunEvent::ExitRequested {
2404 code,
2405 api: ExitRequestApi { tx, code },
2406 },
2407 RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
2408 label,
2409 event: event.into(),
2410 },
2411 RuntimeRunEvent::WebviewEvent { label, event } => RunEvent::WebviewEvent {
2412 label,
2413 event: event.into(),
2414 },
2415 RuntimeRunEvent::Ready => {
2416 #[cfg(all(dev, target_os = "macos"))]
2418 {
2419 use objc2::AllocAnyThread;
2420 use objc2_app_kit::{NSApplication, NSImage};
2421 use objc2_foundation::{MainThreadMarker, NSData};
2422
2423 if let Some(icon) = app_handle.manager.app_icon.clone() {
2424 let mtm = unsafe { MainThreadMarker::new_unchecked() };
2426 let app = NSApplication::sharedApplication(mtm);
2427 let data = NSData::with_bytes(&icon);
2428 let app_icon = NSImage::initWithData(NSImage::alloc(), &data).expect("creating icon");
2429 unsafe { app.setApplicationIconImage(Some(&app_icon)) };
2430 }
2431 }
2432 RunEvent::Ready
2433 }
2434 RuntimeRunEvent::Resumed => RunEvent::Resumed,
2435 RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared,
2436 RuntimeRunEvent::UserEvent(t) => {
2437 match t {
2438 #[cfg(desktop)]
2439 EventLoopMessage::MenuEvent(ref e) => {
2440 for listener in &*app_handle
2441 .manager
2442 .menu
2443 .global_event_listeners
2444 .lock()
2445 .unwrap()
2446 {
2447 listener(app_handle, e.clone());
2448 }
2449 for (label, listener) in &*app_handle.manager.menu.event_listeners.lock().unwrap() {
2450 if let Some(w) = app_handle.manager().get_window(label) {
2451 listener(&w, e.clone());
2452 }
2453 }
2454 }
2455 #[cfg(all(desktop, feature = "tray-icon"))]
2456 EventLoopMessage::TrayIconEvent(ref e) => {
2457 for listener in &*app_handle
2458 .manager
2459 .tray
2460 .global_event_listeners
2461 .lock()
2462 .unwrap()
2463 {
2464 listener(app_handle, e.clone());
2465 }
2466
2467 for (id, listener) in &*app_handle.manager.tray.event_listeners.lock().unwrap() {
2468 if e.id() == id {
2469 if let Some(tray) = app_handle.tray_by_id(id) {
2470 listener(&tray, e.clone());
2471 }
2472 }
2473 }
2474 }
2475 }
2476
2477 #[allow(unreachable_code)]
2478 t.into()
2479 }
2480 #[cfg(any(target_os = "macos", target_os = "ios"))]
2481 RuntimeRunEvent::Opened { urls } => RunEvent::Opened { urls },
2482 #[cfg(target_os = "macos")]
2483 RuntimeRunEvent::Reopen {
2484 has_visible_windows,
2485 } => RunEvent::Reopen {
2486 has_visible_windows,
2487 },
2488 _ => unimplemented!(),
2489 };
2490
2491 manager
2492 .plugins
2493 .lock()
2494 .expect("poisoned plugin store")
2495 .on_event(app_handle, &event);
2496
2497 event
2498}
2499
2500#[cfg(test)]
2501mod tests {
2502 #[test]
2503 fn is_send_sync() {
2504 crate::test_utils::assert_send::<super::AppHandle>();
2505 crate::test_utils::assert_sync::<super::AppHandle>();
2506
2507 #[cfg(feature = "wry")]
2508 {
2509 crate::test_utils::assert_send::<super::AssetResolver<crate::Wry>>();
2510 crate::test_utils::assert_sync::<super::AssetResolver<crate::Wry>>();
2511 }
2512 }
2513}