app.rs 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. #[cfg(feature = "system-tray")]
  5. pub(crate) mod tray;
  6. use crate::{
  7. api::ipc::CallbackFn,
  8. command::{CommandArg, CommandItem},
  9. hooks::{
  10. window_invoke_responder, InvokeHandler, InvokeResponder, OnPageLoad, PageLoadPayload, SetupHook,
  11. },
  12. manager::{Asset, CustomProtocol, WindowManager},
  13. plugin::{Plugin, PluginStore},
  14. runtime::{
  15. http::{Request as HttpRequest, Response as HttpResponse},
  16. webview::WebviewAttributes,
  17. window::{PendingWindow, WindowEvent as RuntimeWindowEvent},
  18. ExitRequestedEventAction, RunEvent as RuntimeRunEvent,
  19. },
  20. scope::FsScope,
  21. sealed::{ManagerBase, RuntimeOrDispatch},
  22. utils::config::Config,
  23. utils::{assets::Assets, resources::resource_relpath, Env},
  24. Context, EventLoopMessage, Invoke, InvokeError, InvokeResponse, Manager, Runtime, Scopes,
  25. StateManager, Theme, Window,
  26. };
  27. #[cfg(shell_scope)]
  28. use crate::scope::ShellScope;
  29. use tauri_macros::default_runtime;
  30. use tauri_runtime::window::{
  31. dpi::{PhysicalPosition, PhysicalSize},
  32. FileDropEvent,
  33. };
  34. use tauri_utils::PackageInfo;
  35. use std::{
  36. collections::HashMap,
  37. path::{Path, PathBuf},
  38. sync::{mpsc::Sender, Arc, Weak},
  39. };
  40. use crate::runtime::menu::{Menu, MenuId, MenuIdRef};
  41. use crate::runtime::RuntimeHandle;
  42. #[cfg(feature = "system-tray")]
  43. use crate::runtime::SystemTrayEvent as RuntimeSystemTrayEvent;
  44. #[cfg(updater)]
  45. use crate::updater;
  46. #[cfg(target_os = "macos")]
  47. use crate::ActivationPolicy;
  48. pub(crate) type GlobalMenuEventListener<R> = Box<dyn Fn(WindowMenuEvent<R>) + Send + Sync>;
  49. pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(GlobalWindowEvent<R>) + Send + Sync>;
  50. #[cfg(feature = "system-tray")]
  51. type SystemTrayEventListener<R> = Box<dyn Fn(&AppHandle<R>, tray::SystemTrayEvent) + Send + Sync>;
  52. /// Api exposed on the `ExitRequested` event.
  53. #[derive(Debug)]
  54. pub struct ExitRequestApi(Sender<ExitRequestedEventAction>);
  55. impl ExitRequestApi {
  56. /// Prevents the app from exiting
  57. pub fn prevent_exit(&self) {
  58. self.0.send(ExitRequestedEventAction::Prevent).unwrap();
  59. }
  60. }
  61. /// Api exposed on the `CloseRequested` event.
  62. #[derive(Debug, Clone)]
  63. pub struct CloseRequestApi(Sender<bool>);
  64. impl CloseRequestApi {
  65. /// Prevents the window from being closed.
  66. pub fn prevent_close(&self) {
  67. self.0.send(true).unwrap();
  68. }
  69. }
  70. /// An event from a window.
  71. #[derive(Debug, Clone)]
  72. #[non_exhaustive]
  73. pub enum WindowEvent {
  74. /// The size of the window has changed. Contains the client area's new dimensions.
  75. Resized(PhysicalSize<u32>),
  76. /// The position of the window has changed. Contains the window's new position.
  77. Moved(PhysicalPosition<i32>),
  78. /// The window has been requested to close.
  79. #[non_exhaustive]
  80. CloseRequested {
  81. /// An API modify the behavior of the close requested event.
  82. api: CloseRequestApi,
  83. },
  84. /// The window has been destroyed.
  85. Destroyed,
  86. /// The window gained or lost focus.
  87. ///
  88. /// The parameter is true if the window has gained focus, and false if it has lost focus.
  89. Focused(bool),
  90. /// The window's scale factor has changed.
  91. ///
  92. /// The following user actions can cause DPI changes:
  93. ///
  94. /// - Changing the display's resolution.
  95. /// - Changing the display's scale factor (e.g. in Control Panel on Windows).
  96. /// - Moving the window to a display with a different scale factor.
  97. #[non_exhaustive]
  98. ScaleFactorChanged {
  99. /// The new scale factor.
  100. scale_factor: f64,
  101. /// The window inner size.
  102. new_inner_size: PhysicalSize<u32>,
  103. },
  104. /// An event associated with the file drop action.
  105. FileDrop(FileDropEvent),
  106. /// The system window theme has changed.
  107. ///
  108. /// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme.
  109. ///
  110. /// ## Platform-specific
  111. ///
  112. /// - **macOS / Linux**: Not supported.
  113. /// - **Windows**: Only delivered if the window [`theme`](`crate::window::WindowBuilder#method.theme`) is `None`.
  114. ThemeChanged(Theme),
  115. }
  116. impl From<RuntimeWindowEvent> for WindowEvent {
  117. fn from(event: RuntimeWindowEvent) -> Self {
  118. match event {
  119. RuntimeWindowEvent::Resized(size) => Self::Resized(size),
  120. RuntimeWindowEvent::Moved(position) => Self::Moved(position),
  121. RuntimeWindowEvent::CloseRequested { signal_tx } => Self::CloseRequested {
  122. api: CloseRequestApi(signal_tx),
  123. },
  124. RuntimeWindowEvent::Destroyed => Self::Destroyed,
  125. RuntimeWindowEvent::Focused(flag) => Self::Focused(flag),
  126. RuntimeWindowEvent::ScaleFactorChanged {
  127. scale_factor,
  128. new_inner_size,
  129. } => Self::ScaleFactorChanged {
  130. scale_factor,
  131. new_inner_size,
  132. },
  133. RuntimeWindowEvent::FileDrop(event) => Self::FileDrop(event),
  134. RuntimeWindowEvent::ThemeChanged(theme) => Self::ThemeChanged(theme),
  135. }
  136. }
  137. }
  138. /// An application event, triggered from the event loop.
  139. #[derive(Debug)]
  140. #[non_exhaustive]
  141. pub enum RunEvent {
  142. /// Event loop is exiting.
  143. Exit,
  144. /// The app is about to exit
  145. #[non_exhaustive]
  146. ExitRequested {
  147. /// Event API
  148. api: ExitRequestApi,
  149. },
  150. /// An event associated with a window.
  151. #[non_exhaustive]
  152. WindowEvent {
  153. /// The window label.
  154. label: String,
  155. /// The detailed event.
  156. event: WindowEvent,
  157. },
  158. /// Application ready.
  159. Ready,
  160. /// Sent if the event loop is being resumed.
  161. Resumed,
  162. /// Emitted when all of the event loop’s input events have been processed and redraw processing is about to begin.
  163. ///
  164. /// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the “main body” of your event loop.
  165. MainEventsCleared,
  166. /// Updater event.
  167. #[cfg(updater)]
  168. #[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
  169. Updater(crate::UpdaterEvent),
  170. }
  171. impl From<EventLoopMessage> for RunEvent {
  172. fn from(event: EventLoopMessage) -> Self {
  173. match event {
  174. #[cfg(updater)]
  175. EventLoopMessage::Updater(event) => RunEvent::Updater(event),
  176. }
  177. }
  178. }
  179. /// A menu event that was triggered on a window.
  180. #[default_runtime(crate::Wry, wry)]
  181. #[derive(Debug)]
  182. pub struct WindowMenuEvent<R: Runtime> {
  183. pub(crate) menu_item_id: MenuId,
  184. pub(crate) window: Window<R>,
  185. }
  186. impl<R: Runtime> WindowMenuEvent<R> {
  187. /// The menu item id.
  188. pub fn menu_item_id(&self) -> MenuIdRef<'_> {
  189. &self.menu_item_id
  190. }
  191. /// The window that the menu belongs to.
  192. pub fn window(&self) -> &Window<R> {
  193. &self.window
  194. }
  195. }
  196. /// A window event that was triggered on the specified window.
  197. #[default_runtime(crate::Wry, wry)]
  198. #[derive(Debug)]
  199. pub struct GlobalWindowEvent<R: Runtime> {
  200. pub(crate) event: WindowEvent,
  201. pub(crate) window: Window<R>,
  202. }
  203. impl<R: Runtime> GlobalWindowEvent<R> {
  204. /// The event payload.
  205. pub fn event(&self) -> &WindowEvent {
  206. &self.event
  207. }
  208. /// The window that the menu belongs to.
  209. pub fn window(&self) -> &Window<R> {
  210. &self.window
  211. }
  212. }
  213. #[cfg(updater)]
  214. #[derive(Debug, Clone, Default)]
  215. pub(crate) struct UpdaterSettings {
  216. pub(crate) target: Option<String>,
  217. }
  218. /// The path resolver is a helper for the application-specific [`crate::api::path`] APIs.
  219. #[derive(Debug, Clone)]
  220. pub struct PathResolver {
  221. env: Env,
  222. config: Arc<Config>,
  223. package_info: PackageInfo,
  224. }
  225. impl PathResolver {
  226. /// Returns the path to the resource directory of this app.
  227. pub fn resource_dir(&self) -> Option<PathBuf> {
  228. crate::api::path::resource_dir(&self.package_info, &self.env)
  229. }
  230. /// Resolves the path of the given resource.
  231. /// Note that the path must be the same as provided in `tauri.conf.json`.
  232. ///
  233. /// This function is helpful when your resource path includes a root dir (`/`) or parent component (`..`),
  234. /// because Tauri replaces them with a parent folder, so simply using [`Self::resource_dir`] and joining the path
  235. /// won't work.
  236. ///
  237. /// # Examples
  238. ///
  239. /// `tauri.conf.json`:
  240. /// ```json
  241. /// {
  242. /// "tauri": {
  243. /// "bundle": {
  244. /// "resources": ["../assets/*"]
  245. /// }
  246. /// }
  247. /// }
  248. /// ```
  249. ///
  250. /// ```no_run
  251. /// tauri::Builder::default()
  252. /// .setup(|app| {
  253. /// let resource_path = app.path_resolver()
  254. /// .resolve_resource("../assets/logo.svg")
  255. /// .expect("failed to resolve resource dir");
  256. /// Ok(())
  257. /// });
  258. /// ```
  259. pub fn resolve_resource<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
  260. self
  261. .resource_dir()
  262. .map(|dir| dir.join(resource_relpath(path.as_ref())))
  263. }
  264. /// Returns the path to the suggested directory for your app config files.
  265. pub fn app_dir(&self) -> Option<PathBuf> {
  266. crate::api::path::app_dir(&self.config)
  267. }
  268. /// Returns the path to the suggested log directory.
  269. pub fn log_dir(&self) -> Option<PathBuf> {
  270. crate::api::path::log_dir(&self.config)
  271. }
  272. }
  273. /// The asset resolver is a helper to access the [`tauri_utils::assets::Assets`] interface.
  274. #[derive(Debug, Clone)]
  275. pub struct AssetResolver<R: Runtime> {
  276. manager: WindowManager<R>,
  277. }
  278. impl<R: Runtime> AssetResolver<R> {
  279. /// Gets the app asset associated with the given path.
  280. pub fn get(&self, path: String) -> Option<Asset> {
  281. self.manager.get_asset(path).ok()
  282. }
  283. }
  284. /// A handle to the currently running application.
  285. ///
  286. /// This type implements [`Manager`] which allows for manipulation of global application items.
  287. #[default_runtime(crate::Wry, wry)]
  288. #[derive(Debug)]
  289. pub struct AppHandle<R: Runtime> {
  290. runtime_handle: R::Handle,
  291. pub(crate) manager: WindowManager<R>,
  292. #[cfg(feature = "global-shortcut")]
  293. global_shortcut_manager: R::GlobalShortcutManager,
  294. #[cfg(feature = "clipboard")]
  295. clipboard_manager: R::ClipboardManager,
  296. #[cfg(feature = "system-tray")]
  297. tray_handle: Option<tray::SystemTrayHandle<R>>,
  298. /// The updater configuration.
  299. #[cfg(updater)]
  300. pub(crate) updater_settings: UpdaterSettings,
  301. }
  302. impl<R: Runtime> AppHandle<R> {
  303. // currently only used on the updater
  304. #[allow(dead_code)]
  305. pub(crate) fn create_proxy(&self) -> R::EventLoopProxy {
  306. self.runtime_handle.create_proxy()
  307. }
  308. }
  309. /// APIs specific to the wry runtime.
  310. #[cfg(feature = "wry")]
  311. impl AppHandle<crate::Wry> {
  312. /// Create a new tao window using a callback. The event loop must be running at this point.
  313. pub fn create_tao_window<
  314. F: FnOnce() -> (String, tauri_runtime_wry::WryWindowBuilder) + Send + 'static,
  315. >(
  316. &self,
  317. f: F,
  318. ) -> crate::Result<Weak<tauri_runtime_wry::Window>> {
  319. self.runtime_handle.create_tao_window(f).map_err(Into::into)
  320. }
  321. /// Sends a window message to the event loop.
  322. pub fn send_tao_window_event(
  323. &self,
  324. window_id: tauri_runtime_wry::WindowId,
  325. message: tauri_runtime_wry::WindowMessage,
  326. ) -> crate::Result<()> {
  327. self
  328. .runtime_handle
  329. .send_event(tauri_runtime_wry::Message::Window(
  330. self.runtime_handle.window_id(window_id),
  331. message,
  332. ))
  333. .map_err(Into::into)
  334. }
  335. }
  336. impl<R: Runtime> Clone for AppHandle<R> {
  337. fn clone(&self) -> Self {
  338. Self {
  339. runtime_handle: self.runtime_handle.clone(),
  340. manager: self.manager.clone(),
  341. #[cfg(feature = "global-shortcut")]
  342. global_shortcut_manager: self.global_shortcut_manager.clone(),
  343. #[cfg(feature = "clipboard")]
  344. clipboard_manager: self.clipboard_manager.clone(),
  345. #[cfg(feature = "system-tray")]
  346. tray_handle: self.tray_handle.clone(),
  347. #[cfg(updater)]
  348. updater_settings: self.updater_settings.clone(),
  349. }
  350. }
  351. }
  352. impl<'de, R: Runtime> CommandArg<'de, R> for AppHandle<R> {
  353. /// Grabs the [`Window`] from the [`CommandItem`] and returns the associated [`AppHandle`]. This will never fail.
  354. fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
  355. Ok(command.message.window().app_handle)
  356. }
  357. }
  358. impl<R: Runtime> AppHandle<R> {
  359. /// Runs the given closure on the main thread.
  360. pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
  361. self
  362. .runtime_handle
  363. .run_on_main_thread(f)
  364. .map_err(Into::into)
  365. }
  366. /// Removes the system tray.
  367. #[cfg(all(windows, feature = "system-tray"))]
  368. #[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))]
  369. fn remove_system_tray(&self) -> crate::Result<()> {
  370. self.runtime_handle.remove_system_tray().map_err(Into::into)
  371. }
  372. /// Adds a Tauri application plugin.
  373. /// This function can be used to register a plugin that is loaded dynamically e.g. after login.
  374. /// For plugins that are created when the app is started, prefer [`Builder::plugin`].
  375. ///
  376. /// See [`Builder::plugin`] for more information.
  377. ///
  378. /// # Examples
  379. ///
  380. /// ```
  381. /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};
  382. ///
  383. /// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {
  384. /// PluginBuilder::new("dummy").build()
  385. /// }
  386. ///
  387. /// tauri::Builder::default()
  388. /// .setup(move |app| {
  389. /// let handle = app.handle();
  390. /// std::thread::spawn(move || {
  391. /// handle.plugin(init_plugin());
  392. /// });
  393. ///
  394. /// Ok(())
  395. /// });
  396. /// ```
  397. pub fn plugin<P: Plugin<R> + 'static>(&self, mut plugin: P) -> crate::Result<()> {
  398. plugin
  399. .initialize(
  400. self,
  401. self
  402. .config()
  403. .plugins
  404. .0
  405. .get(plugin.name())
  406. .cloned()
  407. .unwrap_or_default(),
  408. )
  409. .map_err(|e| crate::Error::PluginInitialization(plugin.name().to_string(), e.to_string()))?;
  410. self
  411. .manager()
  412. .inner
  413. .plugins
  414. .lock()
  415. .unwrap()
  416. .register(plugin);
  417. Ok(())
  418. }
  419. /// Removes the plugin with the given name.
  420. ///
  421. /// # Examples
  422. ///
  423. /// ```
  424. /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin, Plugin}, Runtime};
  425. ///
  426. /// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {
  427. /// PluginBuilder::new("dummy").build()
  428. /// }
  429. ///
  430. /// let plugin = init_plugin();
  431. /// // `.name()` requires the `PLugin` trait import
  432. /// let plugin_name = plugin.name();
  433. /// tauri::Builder::default()
  434. /// .plugin(plugin)
  435. /// .setup(move |app| {
  436. /// let handle = app.handle();
  437. /// std::thread::spawn(move || {
  438. /// handle.remove_plugin(plugin_name);
  439. /// });
  440. ///
  441. /// Ok(())
  442. /// });
  443. /// ```
  444. pub fn remove_plugin(&self, plugin: &'static str) -> bool {
  445. self
  446. .manager()
  447. .inner
  448. .plugins
  449. .lock()
  450. .unwrap()
  451. .unregister(plugin)
  452. }
  453. /// Exits the app. This is the same as [`std::process::exit`], but it performs cleanup on this application.
  454. pub fn exit(&self, exit_code: i32) {
  455. self.cleanup_before_exit();
  456. std::process::exit(exit_code);
  457. }
  458. /// Restarts the app. This is the same as [`crate::api::process::restart`], but it performs cleanup on this application.
  459. pub fn restart(&self) {
  460. self.cleanup_before_exit();
  461. crate::api::process::restart(&self.env());
  462. }
  463. /// Runs necessary cleanup tasks before exiting the process
  464. fn cleanup_before_exit(&self) {
  465. #[cfg(any(shell_execute, shell_sidecar))]
  466. {
  467. crate::api::process::kill_children();
  468. }
  469. #[cfg(all(windows, feature = "system-tray"))]
  470. {
  471. let _ = self.remove_system_tray();
  472. }
  473. }
  474. }
  475. impl<R: Runtime> Manager<R> for AppHandle<R> {}
  476. impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
  477. fn manager(&self) -> &WindowManager<R> {
  478. &self.manager
  479. }
  480. fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
  481. RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
  482. }
  483. fn managed_app_handle(&self) -> AppHandle<R> {
  484. self.clone()
  485. }
  486. }
  487. /// The instance of the currently running application.
  488. ///
  489. /// This type implements [`Manager`] which allows for manipulation of global application items.
  490. #[default_runtime(crate::Wry, wry)]
  491. #[derive(Debug)]
  492. pub struct App<R: Runtime> {
  493. runtime: Option<R>,
  494. manager: WindowManager<R>,
  495. #[cfg(feature = "global-shortcut")]
  496. global_shortcut_manager: R::GlobalShortcutManager,
  497. #[cfg(feature = "clipboard")]
  498. clipboard_manager: R::ClipboardManager,
  499. #[cfg(feature = "system-tray")]
  500. tray_handle: Option<tray::SystemTrayHandle<R>>,
  501. handle: AppHandle<R>,
  502. }
  503. impl<R: Runtime> Manager<R> for App<R> {}
  504. impl<R: Runtime> ManagerBase<R> for App<R> {
  505. fn manager(&self) -> &WindowManager<R> {
  506. &self.manager
  507. }
  508. fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
  509. RuntimeOrDispatch::Runtime(self.runtime.as_ref().unwrap())
  510. }
  511. fn managed_app_handle(&self) -> AppHandle<R> {
  512. self.handle()
  513. }
  514. }
  515. /// APIs specific to the wry runtime.
  516. #[cfg(feature = "wry")]
  517. impl App<crate::Wry> {
  518. /// Adds a [`tauri_runtime_wry::Plugin`] using its [`tauri_runtime_wry::PluginBuilder`].
  519. ///
  520. /// # Stability
  521. ///
  522. /// This API is unstable.
  523. pub fn wry_plugin<P: tauri_runtime_wry::PluginBuilder<EventLoopMessage> + 'static>(
  524. &mut self,
  525. plugin: P,
  526. ) {
  527. self.runtime.as_mut().unwrap().plugin(plugin);
  528. }
  529. }
  530. macro_rules! shared_app_impl {
  531. ($app: ty) => {
  532. impl<R: Runtime> $app {
  533. #[cfg(updater)]
  534. #[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
  535. /// Gets the updater builder to manually check if an update is available.
  536. ///
  537. /// # Examples
  538. ///
  539. /// ```no_run
  540. /// tauri::Builder::default()
  541. /// .setup(|app| {
  542. /// let handle = app.handle();
  543. /// tauri::async_runtime::spawn(async move {
  544. #[cfg_attr(
  545. feature = "updater",
  546. doc = r#" let response = handle.updater().check().await;"#
  547. )]
  548. /// });
  549. /// Ok(())
  550. /// });
  551. /// ```
  552. pub fn updater(&self) -> updater::UpdateBuilder<R> {
  553. updater::builder(self.app_handle())
  554. }
  555. #[cfg(feature = "system-tray")]
  556. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  557. /// Gets a handle handle to the system tray.
  558. pub fn tray_handle(&self) -> tray::SystemTrayHandle<R> {
  559. self
  560. .tray_handle
  561. .clone()
  562. .expect("tray not configured; use the `Builder#system_tray` API first.")
  563. }
  564. /// The path resolver for the application.
  565. pub fn path_resolver(&self) -> PathResolver {
  566. PathResolver {
  567. env: self.state::<Env>().inner().clone(),
  568. config: self.manager.config(),
  569. package_info: self.manager.package_info().clone(),
  570. }
  571. }
  572. /// Gets a copy of the global shortcut manager instance.
  573. #[cfg(feature = "global-shortcut")]
  574. #[cfg_attr(doc_cfg, doc(cfg(feature = "global-shortcut")))]
  575. pub fn global_shortcut_manager(&self) -> R::GlobalShortcutManager {
  576. self.global_shortcut_manager.clone()
  577. }
  578. /// Gets a copy of the clipboard manager instance.
  579. #[cfg(feature = "clipboard")]
  580. #[cfg_attr(doc_cfg, doc(cfg(feature = "clipboard")))]
  581. pub fn clipboard_manager(&self) -> R::ClipboardManager {
  582. self.clipboard_manager.clone()
  583. }
  584. /// Gets the app's configuration, defined on the `tauri.conf.json` file.
  585. pub fn config(&self) -> Arc<Config> {
  586. self.manager.config()
  587. }
  588. /// Gets the app's package information.
  589. pub fn package_info(&self) -> &PackageInfo {
  590. self.manager.package_info()
  591. }
  592. /// The application's asset resolver.
  593. pub fn asset_resolver(&self) -> AssetResolver<R> {
  594. AssetResolver {
  595. manager: self.manager.clone(),
  596. }
  597. }
  598. }
  599. };
  600. }
  601. shared_app_impl!(App<R>);
  602. shared_app_impl!(AppHandle<R>);
  603. impl<R: Runtime> App<R> {
  604. /// Gets a handle to the application instance.
  605. pub fn handle(&self) -> AppHandle<R> {
  606. self.handle.clone()
  607. }
  608. /// Sets the activation policy for the application. It is set to `NSApplicationActivationPolicyRegular` by default.
  609. ///
  610. /// # Examples
  611. /// ```rust,no_run
  612. /// let mut app = tauri::Builder::default()
  613. /// // on an actual app, remove the string argument
  614. /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
  615. /// .expect("error while building tauri application");
  616. /// #[cfg(target_os = "macos")]
  617. /// app.set_activation_policy(tauri::ActivationPolicy::Accessory);
  618. /// app.run(|_app_handle, _event| {});
  619. /// ```
  620. #[cfg(target_os = "macos")]
  621. #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
  622. pub fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) {
  623. self
  624. .runtime
  625. .as_mut()
  626. .unwrap()
  627. .set_activation_policy(activation_policy);
  628. }
  629. /// Gets the argument matches of the CLI definition configured in `tauri.conf.json`.
  630. ///
  631. /// # Examples
  632. ///
  633. /// ```rust,no_run
  634. /// tauri::Builder::default()
  635. /// .setup(|app| {
  636. /// let matches = app.get_cli_matches()?;
  637. /// Ok(())
  638. /// });
  639. /// ```
  640. #[cfg(cli)]
  641. pub fn get_cli_matches(&self) -> crate::Result<crate::api::cli::Matches> {
  642. if let Some(cli) = &self.manager.config().tauri.cli {
  643. crate::api::cli::get_matches(cli, self.manager.package_info()).map_err(Into::into)
  644. } else {
  645. Ok(Default::default())
  646. }
  647. }
  648. /// Runs the application.
  649. ///
  650. /// # Examples
  651. /// ```rust,no_run
  652. /// let app = tauri::Builder::default()
  653. /// // on an actual app, remove the string argument
  654. /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
  655. /// .expect("error while building tauri application");
  656. /// app.run(|_app_handle, event| match event {
  657. /// tauri::RunEvent::ExitRequested { api, .. } => {
  658. /// api.prevent_exit();
  659. /// }
  660. /// _ => {}
  661. /// });
  662. /// ```
  663. pub fn run<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, mut callback: F) {
  664. let app_handle = self.handle();
  665. let manager = self.manager.clone();
  666. self.runtime.take().unwrap().run(move |event| match event {
  667. RuntimeRunEvent::Exit => {
  668. on_event_loop_event(
  669. &app_handle,
  670. RuntimeRunEvent::Exit,
  671. &manager,
  672. Some(&mut callback),
  673. );
  674. app_handle.cleanup_before_exit();
  675. }
  676. _ => {
  677. on_event_loop_event(&app_handle, event, &manager, Some(&mut callback));
  678. }
  679. });
  680. }
  681. /// Runs a iteration of the runtime event loop and immediately return.
  682. ///
  683. /// Note that when using this API, app cleanup is not automatically done.
  684. /// The cleanup calls [`crate::api::process::kill_children`] so you may want to call that function before exiting the application.
  685. /// Additionally, the cleanup calls [AppHandle#remove_system_tray](`AppHandle#method.remove_system_tray`) (Windows only).
  686. ///
  687. /// # Examples
  688. /// ```rust,no_run
  689. /// let mut app = tauri::Builder::default()
  690. /// // on an actual app, remove the string argument
  691. /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
  692. /// .expect("error while building tauri application");
  693. /// loop {
  694. /// let iteration = app.run_iteration();
  695. /// if iteration.window_count == 0 {
  696. /// break;
  697. /// }
  698. /// }
  699. /// ```
  700. pub fn run_iteration(&mut self) -> crate::runtime::RunIteration {
  701. let manager = self.manager.clone();
  702. let app_handle = self.handle();
  703. self.runtime.as_mut().unwrap().run_iteration(move |event| {
  704. on_event_loop_event(
  705. &app_handle,
  706. event,
  707. &manager,
  708. Option::<&mut Box<dyn FnMut(&AppHandle<R>, RunEvent)>>::None,
  709. )
  710. })
  711. }
  712. }
  713. #[cfg(updater)]
  714. impl<R: Runtime> App<R> {
  715. /// Runs the updater hook with built-in dialog.
  716. fn run_updater_dialog(&self) {
  717. let handle = self.handle();
  718. crate::async_runtime::spawn(async move { updater::check_update_with_dialog(handle).await });
  719. }
  720. fn run_updater(&self) {
  721. let handle = self.handle();
  722. let handle_ = handle.clone();
  723. let updater_config = self.manager.config().tauri.updater.clone();
  724. // check if updater is active or not
  725. if updater_config.active {
  726. if updater_config.dialog {
  727. #[cfg(not(target_os = "linux"))]
  728. let updater_enabled = true;
  729. #[cfg(target_os = "linux")]
  730. let updater_enabled = cfg!(dev) || self.state::<Env>().appimage.is_some();
  731. if updater_enabled {
  732. // if updater dialog is enabled spawn a new task
  733. self.run_updater_dialog();
  734. // When dialog is enabled, if user want to recheck
  735. // if an update is available after first start
  736. // invoke the Event `tauri://update` from JS or rust side.
  737. handle.listen_global(updater::EVENT_CHECK_UPDATE, move |_msg| {
  738. let handle = handle_.clone();
  739. // re-spawn task inside tokyo to launch the download
  740. // we don't need to emit anything as everything is handled
  741. // by the process (user is asked to restart at the end)
  742. // and it's handled by the updater
  743. crate::async_runtime::spawn(
  744. async move { updater::check_update_with_dialog(handle).await },
  745. );
  746. });
  747. }
  748. } else {
  749. // we only listen for `tauri://update`
  750. // once we receive the call, we check if an update is available or not
  751. // if there is a new update we emit `tauri://update-available` with details
  752. // this is the user responsabilities to display dialog and ask if user want to install
  753. // to install the update you need to invoke the Event `tauri://update-install`
  754. updater::listener(handle);
  755. }
  756. }
  757. }
  758. }
  759. /// Builds a Tauri application.
  760. ///
  761. /// # Examples
  762. /// ```rust,no_run
  763. /// tauri::Builder::default()
  764. /// // on an actual app, remove the string argument
  765. /// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
  766. /// .expect("error while running tauri application");
  767. /// ```
  768. #[allow(clippy::type_complexity)]
  769. pub struct Builder<R: Runtime> {
  770. /// A flag indicating that the runtime must be started on an environment that supports the event loop not on the main thread.
  771. #[cfg(any(windows, target_os = "linux"))]
  772. runtime_any_thread: bool,
  773. /// The JS message handler.
  774. invoke_handler: Box<InvokeHandler<R>>,
  775. /// The JS message responder.
  776. invoke_responder: Arc<InvokeResponder<R>>,
  777. /// The script that initializes the `window.__TAURI_POST_MESSAGE__` function.
  778. invoke_initialization_script: String,
  779. /// The setup hook.
  780. setup: SetupHook<R>,
  781. /// Page load hook.
  782. on_page_load: Box<OnPageLoad<R>>,
  783. /// windows to create when starting up.
  784. pending_windows: Vec<PendingWindow<EventLoopMessage, R>>,
  785. /// All passed plugins
  786. plugins: PluginStore<R>,
  787. /// The webview protocols available to all windows.
  788. uri_scheme_protocols: HashMap<String, Arc<CustomProtocol<R>>>,
  789. /// App state.
  790. state: StateManager,
  791. /// The menu set to all windows.
  792. menu: Option<Menu>,
  793. /// Enable macOS default menu creation.
  794. #[allow(unused)]
  795. enable_macos_default_menu: bool,
  796. /// Menu event handlers that listens to all windows.
  797. menu_event_listeners: Vec<GlobalMenuEventListener<R>>,
  798. /// Window event handlers that listens to all windows.
  799. window_event_listeners: Vec<GlobalWindowEventListener<R>>,
  800. /// The app system tray.
  801. #[cfg(feature = "system-tray")]
  802. system_tray: Option<tray::SystemTray>,
  803. /// System tray event handlers.
  804. #[cfg(feature = "system-tray")]
  805. system_tray_event_listeners: Vec<SystemTrayEventListener<R>>,
  806. /// The updater configuration.
  807. #[cfg(updater)]
  808. updater_settings: UpdaterSettings,
  809. }
  810. impl<R: Runtime> Builder<R> {
  811. /// Creates a new App builder.
  812. pub fn new() -> Self {
  813. Self {
  814. #[cfg(any(windows, target_os = "linux"))]
  815. runtime_any_thread: false,
  816. setup: Box::new(|_| Ok(())),
  817. invoke_handler: Box::new(|_| ()),
  818. invoke_responder: Arc::new(window_invoke_responder),
  819. invoke_initialization_script:
  820. "Object.defineProperty(window, '__TAURI_POST_MESSAGE__', { value: (message) => window.ipc.postMessage(JSON.stringify(message)) })".into(),
  821. on_page_load: Box::new(|_, _| ()),
  822. pending_windows: Default::default(),
  823. plugins: PluginStore::default(),
  824. uri_scheme_protocols: Default::default(),
  825. state: StateManager::new(),
  826. menu: None,
  827. enable_macos_default_menu: true,
  828. menu_event_listeners: Vec::new(),
  829. window_event_listeners: Vec::new(),
  830. #[cfg(feature = "system-tray")]
  831. system_tray: None,
  832. #[cfg(feature = "system-tray")]
  833. system_tray_event_listeners: Vec::new(),
  834. #[cfg(updater)]
  835. updater_settings: Default::default(),
  836. }
  837. }
  838. /// Builds a new Tauri application running on any thread, bypassing the main thread requirement.
  839. ///
  840. /// ## Platform-specific
  841. ///
  842. /// - **macOS:** on macOS the application *must* be executed on the main thread, so this function is not exposed.
  843. #[cfg(any(windows, target_os = "linux"))]
  844. #[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))]
  845. #[must_use]
  846. pub fn any_thread(mut self) -> Self {
  847. self.runtime_any_thread = true;
  848. self
  849. }
  850. /// Defines the JS message handler callback.
  851. ///
  852. /// # Examples
  853. /// ```rust,no_run
  854. /// #[tauri::command]
  855. /// fn command_1() -> String {
  856. /// return "hello world".to_string();
  857. /// }
  858. /// tauri::Builder::default()
  859. /// .invoke_handler(tauri::generate_handler![
  860. /// command_1,
  861. /// // etc...
  862. /// ]);
  863. /// ```
  864. #[must_use]
  865. pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
  866. where
  867. F: Fn(Invoke<R>) + Send + Sync + 'static,
  868. {
  869. self.invoke_handler = Box::new(invoke_handler);
  870. self
  871. }
  872. /// Defines a custom JS message system.
  873. ///
  874. /// The `responder` is a function that will be called when a command has been executed and must send a response to the JS layer.
  875. ///
  876. /// The `initialization_script` is a script that initializes `window.__TAURI_POST_MESSAGE__`.
  877. /// That function must take the `message: object` argument and send it to the backend.
  878. #[must_use]
  879. pub fn invoke_system<F>(mut self, initialization_script: String, responder: F) -> Self
  880. where
  881. F: Fn(Window<R>, InvokeResponse, CallbackFn, CallbackFn) + Send + Sync + 'static,
  882. {
  883. self.invoke_initialization_script = initialization_script;
  884. self.invoke_responder = Arc::new(responder);
  885. self
  886. }
  887. /// Defines the setup hook.
  888. ///
  889. /// # Examples
  890. /// ```rust,no_run
  891. /// use tauri::Manager;
  892. /// tauri::Builder::default()
  893. /// .setup(|app| {
  894. /// let main_window = app.get_window("main").unwrap();
  895. #[cfg_attr(
  896. feature = "dialog",
  897. doc = r#" tauri::api::dialog::blocking::message(Some(&main_window), "Hello", "Welcome back!");"#
  898. )]
  899. /// Ok(())
  900. /// });
  901. /// ```
  902. #[must_use]
  903. pub fn setup<F>(mut self, setup: F) -> Self
  904. where
  905. F: FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error>> + Send + 'static,
  906. {
  907. self.setup = Box::new(setup);
  908. self
  909. }
  910. /// Defines the page load hook.
  911. #[must_use]
  912. pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
  913. where
  914. F: Fn(Window<R>, PageLoadPayload) + Send + Sync + 'static,
  915. {
  916. self.on_page_load = Box::new(on_page_load);
  917. self
  918. }
  919. /// Adds a Tauri application plugin.
  920. ///
  921. /// A plugin is created using the [`crate::plugin::Builder`] struct.Check its documentation for more information.
  922. ///
  923. /// # Examples
  924. ///
  925. /// ```
  926. /// mod plugin {
  927. /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, RunEvent, Runtime};
  928. ///
  929. /// // this command can be called in the frontend using `invoke('plugin:window|do_something')`.
  930. /// #[tauri::command]
  931. /// async fn do_something<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {
  932. /// println!("command called");
  933. /// Ok(())
  934. /// }
  935. /// pub fn init<R: Runtime>() -> TauriPlugin<R> {
  936. /// PluginBuilder::new("window")
  937. /// .setup(|app| {
  938. /// // initialize the plugin here
  939. /// Ok(())
  940. /// })
  941. /// .on_event(|app, event| {
  942. /// match event {
  943. /// RunEvent::Ready => {
  944. /// println!("app is ready");
  945. /// }
  946. /// RunEvent::WindowEvent { label, event, .. } => {
  947. /// println!("window {} received an event: {:?}", label, event);
  948. /// }
  949. /// _ => (),
  950. /// }
  951. /// })
  952. /// .invoke_handler(tauri::generate_handler![do_something])
  953. /// .build()
  954. /// }
  955. /// }
  956. ///
  957. /// tauri::Builder::default()
  958. /// .plugin(plugin::init());
  959. /// ```
  960. #[must_use]
  961. pub fn plugin<P: Plugin<R> + 'static>(mut self, plugin: P) -> Self {
  962. self.plugins.register(plugin);
  963. self
  964. }
  965. /// Add `state` to the state managed by the application.
  966. ///
  967. /// This method can be called any number of times as long as each call
  968. /// refers to a different `T`.
  969. ///
  970. /// Managed state can be retrieved by any command handler via the
  971. /// [`State`](crate::State) guard. In particular, if a value of type `T`
  972. /// is managed by Tauri, adding `State<T>` to the list of arguments in a
  973. /// command handler instructs Tauri to retrieve the managed value.
  974. ///
  975. /// # Panics
  976. ///
  977. /// Panics if state of type `T` is already being managed.
  978. ///
  979. /// # Mutability
  980. ///
  981. /// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability:
  982. ///
  983. /// ```rust,no_run
  984. /// use std::{collections::HashMap, sync::Mutex};
  985. /// use tauri::State;
  986. /// // here we use Mutex to achieve interior mutability
  987. /// struct Storage {
  988. /// store: Mutex<HashMap<u64, String>>,
  989. /// }
  990. /// struct Connection;
  991. /// struct DbConnection {
  992. /// db: Mutex<Option<Connection>>,
  993. /// }
  994. ///
  995. /// #[tauri::command]
  996. /// fn connect(connection: State<DbConnection>) {
  997. /// // initialize the connection, mutating the state with interior mutability
  998. /// *connection.db.lock().unwrap() = Some(Connection {});
  999. /// }
  1000. ///
  1001. /// #[tauri::command]
  1002. /// fn storage_insert(key: u64, value: String, storage: State<Storage>) {
  1003. /// // mutate the storage behind the Mutex
  1004. /// storage.store.lock().unwrap().insert(key, value);
  1005. /// }
  1006. ///
  1007. /// tauri::Builder::default()
  1008. /// .manage(Storage { store: Default::default() })
  1009. /// .manage(DbConnection { db: Default::default() })
  1010. /// .invoke_handler(tauri::generate_handler![connect, storage_insert])
  1011. /// // on an actual app, remove the string argument
  1012. /// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
  1013. /// .expect("error while running tauri application");
  1014. /// ```
  1015. ///
  1016. /// # Examples
  1017. ///
  1018. /// ```rust,no_run
  1019. /// use tauri::State;
  1020. ///
  1021. /// struct MyInt(isize);
  1022. /// struct MyString(String);
  1023. ///
  1024. /// #[tauri::command]
  1025. /// fn int_command(state: State<MyInt>) -> String {
  1026. /// format!("The stateful int is: {}", state.0)
  1027. /// }
  1028. ///
  1029. /// #[tauri::command]
  1030. /// fn string_command<'r>(state: State<'r, MyString>) {
  1031. /// println!("state: {}", state.inner().0);
  1032. /// }
  1033. ///
  1034. /// tauri::Builder::default()
  1035. /// .manage(MyInt(10))
  1036. /// .manage(MyString("Hello, managed state!".to_string()))
  1037. /// .invoke_handler(tauri::generate_handler![int_command, string_command])
  1038. /// // on an actual app, remove the string argument
  1039. /// .run(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
  1040. /// .expect("error while running tauri application");
  1041. /// ```
  1042. #[must_use]
  1043. pub fn manage<T>(self, state: T) -> Self
  1044. where
  1045. T: Send + Sync + 'static,
  1046. {
  1047. let type_name = std::any::type_name::<T>();
  1048. assert!(
  1049. self.state.set(state),
  1050. "state for type '{}' is already being managed",
  1051. type_name
  1052. );
  1053. self
  1054. }
  1055. /// Adds the icon configured on `tauri.conf.json` to the system tray with the specified menu items.
  1056. #[cfg(feature = "system-tray")]
  1057. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  1058. #[must_use]
  1059. pub fn system_tray(mut self, system_tray: tray::SystemTray) -> Self {
  1060. self.system_tray.replace(system_tray);
  1061. self
  1062. }
  1063. /// Sets the menu to use on all windows.
  1064. ///
  1065. /// # Examples
  1066. /// ```rust,no_run
  1067. /// use tauri::{MenuEntry, Submenu, MenuItem, Menu, CustomMenuItem};
  1068. ///
  1069. /// tauri::Builder::default()
  1070. /// .menu(Menu::with_items([
  1071. /// MenuEntry::Submenu(Submenu::new(
  1072. /// "File",
  1073. /// Menu::with_items([
  1074. /// MenuItem::CloseWindow.into(),
  1075. /// #[cfg(target_os = "macos")]
  1076. /// CustomMenuItem::new("hello", "Hello").into(),
  1077. /// ]),
  1078. /// )),
  1079. /// ]));
  1080. /// ```
  1081. #[must_use]
  1082. pub fn menu(mut self, menu: Menu) -> Self {
  1083. self.menu.replace(menu);
  1084. self
  1085. }
  1086. /// Registers a menu event handler for all windows.
  1087. ///
  1088. /// # Examples
  1089. /// ```rust,no_run
  1090. /// use tauri::{Menu, MenuEntry, Submenu, CustomMenuItem, api, Manager};
  1091. /// tauri::Builder::default()
  1092. /// .menu(Menu::with_items([
  1093. /// MenuEntry::Submenu(Submenu::new(
  1094. /// "File",
  1095. /// Menu::with_items([
  1096. /// CustomMenuItem::new("New", "New").into(),
  1097. /// CustomMenuItem::new("Learn More", "Learn More").into(),
  1098. /// ]),
  1099. /// )),
  1100. /// ]))
  1101. /// .on_menu_event(|event| {
  1102. /// match event.menu_item_id() {
  1103. /// "Learn More" => {
  1104. /// // open in browser (requires the `shell-open-api` feature)
  1105. #[cfg_attr(
  1106. feature = "shell-open-api",
  1107. doc = r#" api::shell::open(&event.window().shell_scope(), "https://github.com/tauri-apps/tauri".to_string(), None).unwrap();"#
  1108. )]
  1109. /// }
  1110. /// id => {
  1111. /// // do something with other events
  1112. /// println!("got menu event: {}", id);
  1113. /// }
  1114. /// }
  1115. /// });
  1116. /// ```
  1117. #[must_use]
  1118. pub fn on_menu_event<F: Fn(WindowMenuEvent<R>) + Send + Sync + 'static>(
  1119. mut self,
  1120. handler: F,
  1121. ) -> Self {
  1122. self.menu_event_listeners.push(Box::new(handler));
  1123. self
  1124. }
  1125. /// Registers a window event handler for all windows.
  1126. ///
  1127. /// # Examples
  1128. /// ```rust,no_run
  1129. /// tauri::Builder::default()
  1130. /// .on_window_event(|event| match event.event() {
  1131. /// tauri::WindowEvent::Focused(focused) => {
  1132. /// // hide window whenever it loses focus
  1133. /// if !focused {
  1134. /// event.window().hide().unwrap();
  1135. /// }
  1136. /// }
  1137. /// _ => {}
  1138. /// });
  1139. /// ```
  1140. #[must_use]
  1141. pub fn on_window_event<F: Fn(GlobalWindowEvent<R>) + Send + Sync + 'static>(
  1142. mut self,
  1143. handler: F,
  1144. ) -> Self {
  1145. self.window_event_listeners.push(Box::new(handler));
  1146. self
  1147. }
  1148. /// Registers a system tray event handler.
  1149. ///
  1150. /// # Examples
  1151. /// ```rust,no_run
  1152. /// use tauri::Manager;
  1153. /// tauri::Builder::default()
  1154. /// .on_system_tray_event(|app, event| match event {
  1155. /// // show window with id "main" when the tray is left clicked
  1156. /// tauri::SystemTrayEvent::LeftClick { .. } => {
  1157. /// let window = app.get_window("main").unwrap();
  1158. /// window.show().unwrap();
  1159. /// window.set_focus().unwrap();
  1160. /// }
  1161. /// _ => {}
  1162. /// });
  1163. /// ```
  1164. #[cfg(feature = "system-tray")]
  1165. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  1166. #[must_use]
  1167. pub fn on_system_tray_event<
  1168. F: Fn(&AppHandle<R>, tray::SystemTrayEvent) + Send + Sync + 'static,
  1169. >(
  1170. mut self,
  1171. handler: F,
  1172. ) -> Self {
  1173. self.system_tray_event_listeners.push(Box::new(handler));
  1174. self
  1175. }
  1176. /// Registers a URI scheme protocol available to all webviews.
  1177. /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
  1178. /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
  1179. /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
  1180. ///
  1181. /// # Arguments
  1182. ///
  1183. /// * `uri_scheme` The URI scheme to register, such as `example`.
  1184. /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
  1185. #[must_use]
  1186. pub fn register_uri_scheme_protocol<
  1187. N: Into<String>,
  1188. H: Fn(&AppHandle<R>, &HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>>
  1189. + Send
  1190. + Sync
  1191. + 'static,
  1192. >(
  1193. mut self,
  1194. uri_scheme: N,
  1195. protocol: H,
  1196. ) -> Self {
  1197. self.uri_scheme_protocols.insert(
  1198. uri_scheme.into(),
  1199. Arc::new(CustomProtocol {
  1200. protocol: Box::new(protocol),
  1201. }),
  1202. );
  1203. self
  1204. }
  1205. /// Sets the current platform's target name for the updater.
  1206. ///
  1207. /// See [`UpdateBuilder::target`](crate::updater::UpdateBuilder#method.target) for more information.
  1208. ///
  1209. /// # Examples
  1210. ///
  1211. /// - Use a macOS Universal binary target name:
  1212. ///
  1213. /// ```no_run
  1214. /// let mut builder = tauri::Builder::default();
  1215. /// #[cfg(target_os = "macos")]
  1216. /// {
  1217. /// builder = builder.updater_target("darwin-universal");
  1218. /// }
  1219. /// ```
  1220. ///
  1221. /// - Append debug information to the target:
  1222. ///
  1223. /// ```no_run
  1224. /// let kind = if cfg!(debug_assertions) { "debug" } else { "release" };
  1225. /// tauri::Builder::default()
  1226. /// .updater_target(format!("{}-{}", tauri::updater::target().unwrap(), kind));
  1227. /// ```
  1228. ///
  1229. /// - Use the platform's target triple:
  1230. ///
  1231. /// ```no_run
  1232. /// tauri::Builder::default()
  1233. /// .updater_target(tauri::utils::platform::target_triple().unwrap());
  1234. /// ```
  1235. #[cfg(updater)]
  1236. pub fn updater_target<T: Into<String>>(mut self, target: T) -> Self {
  1237. self.updater_settings.target.replace(target.into());
  1238. self
  1239. }
  1240. /// Builds the application.
  1241. #[allow(clippy::type_complexity)]
  1242. pub fn build<A: Assets>(mut self, context: Context<A>) -> crate::Result<App<R>> {
  1243. #[cfg(target_os = "macos")]
  1244. if self.menu.is_none() && self.enable_macos_default_menu {
  1245. self.menu = Some(Menu::os_default(&context.package_info().name));
  1246. }
  1247. #[cfg(feature = "system-tray")]
  1248. let system_tray_icon = context.system_tray_icon.clone();
  1249. #[cfg(all(feature = "system-tray", target_os = "macos"))]
  1250. let (system_tray_icon_as_template, system_tray_menu_on_left_click) = context
  1251. .config
  1252. .tauri
  1253. .system_tray
  1254. .as_ref()
  1255. .map(|t| (t.icon_as_template, t.menu_on_left_click))
  1256. .unwrap_or_default();
  1257. #[cfg(shell_scope)]
  1258. let shell_scope = context.shell_scope.clone();
  1259. let manager = WindowManager::with_handlers(
  1260. context,
  1261. self.plugins,
  1262. self.invoke_handler,
  1263. self.on_page_load,
  1264. self.uri_scheme_protocols,
  1265. self.state,
  1266. self.window_event_listeners,
  1267. (self.menu, self.menu_event_listeners),
  1268. (self.invoke_responder, self.invoke_initialization_script),
  1269. );
  1270. // set up all the windows defined in the config
  1271. for config in manager.config().tauri.windows.clone() {
  1272. let url = config.url.clone();
  1273. let label = config.label.clone();
  1274. let file_drop_enabled = config.file_drop_enabled;
  1275. let mut webview_attributes = WebviewAttributes::new(url);
  1276. if !file_drop_enabled {
  1277. webview_attributes = webview_attributes.disable_file_drop_handler();
  1278. }
  1279. self.pending_windows.push(PendingWindow::with_config(
  1280. config,
  1281. webview_attributes,
  1282. label,
  1283. )?);
  1284. }
  1285. #[cfg(any(windows, target_os = "linux"))]
  1286. let runtime = if self.runtime_any_thread {
  1287. R::new_any_thread()?
  1288. } else {
  1289. R::new()?
  1290. };
  1291. #[cfg(not(any(windows, target_os = "linux")))]
  1292. let runtime = R::new()?;
  1293. let runtime_handle = runtime.handle();
  1294. #[cfg(feature = "global-shortcut")]
  1295. let global_shortcut_manager = runtime.global_shortcut_manager();
  1296. #[cfg(feature = "clipboard")]
  1297. let clipboard_manager = runtime.clipboard_manager();
  1298. let mut app = App {
  1299. runtime: Some(runtime),
  1300. manager: manager.clone(),
  1301. #[cfg(feature = "global-shortcut")]
  1302. global_shortcut_manager: global_shortcut_manager.clone(),
  1303. #[cfg(feature = "clipboard")]
  1304. clipboard_manager: clipboard_manager.clone(),
  1305. #[cfg(feature = "system-tray")]
  1306. tray_handle: None,
  1307. handle: AppHandle {
  1308. runtime_handle,
  1309. manager,
  1310. #[cfg(feature = "global-shortcut")]
  1311. global_shortcut_manager,
  1312. #[cfg(feature = "clipboard")]
  1313. clipboard_manager,
  1314. #[cfg(feature = "system-tray")]
  1315. tray_handle: None,
  1316. #[cfg(updater)]
  1317. updater_settings: self.updater_settings,
  1318. },
  1319. };
  1320. let env = Env::default();
  1321. app.manage(Scopes {
  1322. fs: FsScope::for_fs_api(
  1323. &app.manager.config(),
  1324. app.package_info(),
  1325. &env,
  1326. &app.config().tauri.allowlist.fs.scope,
  1327. )?,
  1328. #[cfg(protocol_asset)]
  1329. asset_protocol: FsScope::for_fs_api(
  1330. &app.manager.config(),
  1331. app.package_info(),
  1332. &env,
  1333. &app.config().tauri.allowlist.protocol.asset_scope,
  1334. )?,
  1335. #[cfg(http_request)]
  1336. http: crate::scope::HttpScope::for_http_api(&app.config().tauri.allowlist.http.scope),
  1337. #[cfg(shell_scope)]
  1338. shell: ShellScope::new(shell_scope),
  1339. });
  1340. app.manage(env);
  1341. #[cfg(windows)]
  1342. {
  1343. if let crate::utils::config::WebviewInstallMode::FixedRuntime { path } = &app
  1344. .manager
  1345. .config()
  1346. .tauri
  1347. .bundle
  1348. .windows
  1349. .webview_install_mode
  1350. {
  1351. if let Some(resource_dir) = app.path_resolver().resource_dir() {
  1352. std::env::set_var(
  1353. "WEBVIEW2_BROWSER_EXECUTABLE_FOLDER",
  1354. resource_dir.join(path),
  1355. );
  1356. } else {
  1357. #[cfg(debug_assertions)]
  1358. eprintln!(
  1359. "failed to resolve resource directory; fallback to the installed Webview2 runtime."
  1360. );
  1361. }
  1362. }
  1363. }
  1364. #[cfg(feature = "system-tray")]
  1365. if let Some(system_tray) = self.system_tray {
  1366. let mut ids = HashMap::new();
  1367. if let Some(menu) = system_tray.menu() {
  1368. tray::get_menu_ids(&mut ids, menu);
  1369. }
  1370. let tray_icon = if let Some(icon) = system_tray.icon {
  1371. Some(icon)
  1372. } else if let Some(tray_icon) = system_tray_icon {
  1373. Some(tray_icon.try_into()?)
  1374. } else {
  1375. None
  1376. };
  1377. let mut tray = tray::SystemTray::new()
  1378. .with_icon(tray_icon.expect("tray icon not found; please configure it on tauri.conf.json"));
  1379. if let Some(menu) = system_tray.menu {
  1380. tray = tray.with_menu(menu);
  1381. }
  1382. #[cfg(target_os = "macos")]
  1383. let tray = tray
  1384. .with_icon_as_template(system_tray_icon_as_template)
  1385. .with_menu_on_left_click(system_tray_menu_on_left_click);
  1386. let tray_handler = app
  1387. .runtime
  1388. .as_ref()
  1389. .unwrap()
  1390. .system_tray(tray)
  1391. .expect("failed to run tray");
  1392. let tray_handle = tray::SystemTrayHandle {
  1393. ids: Arc::new(std::sync::Mutex::new(ids)),
  1394. inner: tray_handler,
  1395. };
  1396. let ids = tray_handle.ids.clone();
  1397. app.tray_handle.replace(tray_handle.clone());
  1398. app.handle.tray_handle.replace(tray_handle);
  1399. for listener in self.system_tray_event_listeners {
  1400. let app_handle = app.handle();
  1401. let ids = ids.clone();
  1402. let listener = Arc::new(std::sync::Mutex::new(listener));
  1403. app
  1404. .runtime
  1405. .as_mut()
  1406. .unwrap()
  1407. .on_system_tray_event(move |event| {
  1408. let app_handle = app_handle.clone();
  1409. let event = match event {
  1410. RuntimeSystemTrayEvent::MenuItemClick(id) => tray::SystemTrayEvent::MenuItemClick {
  1411. id: ids.lock().unwrap().get(id).unwrap().clone(),
  1412. },
  1413. RuntimeSystemTrayEvent::LeftClick { position, size } => {
  1414. tray::SystemTrayEvent::LeftClick {
  1415. position: *position,
  1416. size: *size,
  1417. }
  1418. }
  1419. RuntimeSystemTrayEvent::RightClick { position, size } => {
  1420. tray::SystemTrayEvent::RightClick {
  1421. position: *position,
  1422. size: *size,
  1423. }
  1424. }
  1425. RuntimeSystemTrayEvent::DoubleClick { position, size } => {
  1426. tray::SystemTrayEvent::DoubleClick {
  1427. position: *position,
  1428. size: *size,
  1429. }
  1430. }
  1431. };
  1432. let listener = listener.clone();
  1433. listener.lock().unwrap()(&app_handle, event);
  1434. });
  1435. }
  1436. }
  1437. app.manager.initialize_plugins(&app.handle())?;
  1438. let window_labels = self
  1439. .pending_windows
  1440. .iter()
  1441. .map(|p| p.label.clone())
  1442. .collect::<Vec<_>>();
  1443. for pending in self.pending_windows {
  1444. let pending =
  1445. app
  1446. .manager
  1447. .prepare_window(app.handle.clone(), pending, &window_labels, None)?;
  1448. let detached = app.runtime.as_ref().unwrap().create_window(pending)?;
  1449. let _window = app.manager.attach_window(app.handle(), detached);
  1450. }
  1451. (self.setup)(&mut app).map_err(|e| crate::Error::Setup(e.into()))?;
  1452. #[cfg(updater)]
  1453. app.run_updater();
  1454. Ok(app)
  1455. }
  1456. /// Runs the configured Tauri application.
  1457. pub fn run<A: Assets>(self, context: Context<A>) -> crate::Result<()> {
  1458. self.build(context)?.run(|_, _| {});
  1459. Ok(())
  1460. }
  1461. }
  1462. fn on_event_loop_event<R: Runtime, F: FnMut(&AppHandle<R>, RunEvent) + 'static>(
  1463. app_handle: &AppHandle<R>,
  1464. event: RuntimeRunEvent<EventLoopMessage>,
  1465. manager: &WindowManager<R>,
  1466. callback: Option<&mut F>,
  1467. ) {
  1468. if let RuntimeRunEvent::WindowEvent {
  1469. label,
  1470. event: RuntimeWindowEvent::Destroyed,
  1471. } = &event
  1472. {
  1473. manager.on_window_close(label);
  1474. }
  1475. let event = match event {
  1476. RuntimeRunEvent::Exit => RunEvent::Exit,
  1477. RuntimeRunEvent::ExitRequested { tx } => RunEvent::ExitRequested {
  1478. api: ExitRequestApi(tx),
  1479. },
  1480. RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
  1481. label,
  1482. event: event.into(),
  1483. },
  1484. RuntimeRunEvent::Ready => {
  1485. // set the app icon in development
  1486. #[cfg(all(dev, target_os = "macos"))]
  1487. unsafe {
  1488. use cocoa::{
  1489. appkit::NSImage,
  1490. base::{id, nil},
  1491. foundation::NSData,
  1492. };
  1493. use objc::*;
  1494. if let Some(icon) = app_handle.manager.inner.app_icon.clone() {
  1495. let ns_app: id = msg_send![class!(NSApplication), sharedApplication];
  1496. let data = NSData::dataWithBytes_length_(
  1497. nil,
  1498. icon.as_ptr() as *const std::os::raw::c_void,
  1499. icon.len() as u64,
  1500. );
  1501. let app_icon = NSImage::initWithData_(NSImage::alloc(nil), data);
  1502. let _: () = msg_send![ns_app, setApplicationIconImage: app_icon];
  1503. }
  1504. }
  1505. RunEvent::Ready
  1506. }
  1507. RuntimeRunEvent::Resumed => RunEvent::Resumed,
  1508. RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared,
  1509. RuntimeRunEvent::UserEvent(t) => t.into(),
  1510. _ => unimplemented!(),
  1511. };
  1512. manager
  1513. .inner
  1514. .plugins
  1515. .lock()
  1516. .expect("poisoned plugin store")
  1517. .on_event(app_handle, &event);
  1518. if let Some(c) = callback {
  1519. c(app_handle, event);
  1520. }
  1521. }
  1522. /// Make `Wry` the default `Runtime` for `Builder`
  1523. #[cfg(feature = "wry")]
  1524. #[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))]
  1525. impl Default for Builder<crate::Wry> {
  1526. fn default() -> Self {
  1527. Self::new()
  1528. }
  1529. }
  1530. #[cfg(test)]
  1531. mod tests {
  1532. #[test]
  1533. fn is_send_sync() {
  1534. crate::test_utils::assert_send::<super::AppHandle>();
  1535. crate::test_utils::assert_sync::<super::AppHandle>();
  1536. #[cfg(feature = "wry")]
  1537. {
  1538. crate::test_utils::assert_send::<super::AssetResolver<crate::Wry>>();
  1539. crate::test_utils::assert_sync::<super::AssetResolver<crate::Wry>>();
  1540. }
  1541. crate::test_utils::assert_send::<super::PathResolver>();
  1542. crate::test_utils::assert_sync::<super::PathResolver>();
  1543. }
  1544. }