app.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::{
  5. api::assets::Assets,
  6. api::config::WindowUrl,
  7. hooks::{InvokeHandler, OnPageLoad, PageLoadPayload, SetupHook},
  8. manager::{Args, WindowManager},
  9. plugin::{Plugin, PluginStore},
  10. runtime::{
  11. tag::Tag,
  12. webview::{CustomProtocol, WebviewAttributes, WindowBuilder},
  13. window::{PendingWindow, WindowEvent},
  14. Dispatch, MenuId, Params, Runtime,
  15. },
  16. sealed::{ManagerBase, RuntimeOrDispatch},
  17. Context, Invoke, Manager, StateManager, Window,
  18. };
  19. use std::{collections::HashMap, sync::Arc};
  20. #[cfg(feature = "menu")]
  21. use crate::runtime::menu::Menu;
  22. #[cfg(feature = "system-tray")]
  23. use crate::runtime::{menu::SystemTrayMenuItem, Icon};
  24. use crate::manager::DefaultArgs;
  25. #[cfg(feature = "updater")]
  26. use crate::updater;
  27. #[cfg(feature = "menu")]
  28. pub(crate) type GlobalMenuEventListener<P> = Box<dyn Fn(WindowMenuEvent<P>) + Send + Sync>;
  29. pub(crate) type GlobalWindowEventListener<P> = Box<dyn Fn(GlobalWindowEvent<P>) + Send + Sync>;
  30. #[cfg(feature = "system-tray")]
  31. type SystemTrayEventListener<P> =
  32. Box<dyn Fn(&AppHandle<P>, SystemTrayEvent<<P as Params>::SystemTrayMenuId>) + Send + Sync>;
  33. /// System tray event.
  34. #[cfg(feature = "system-tray")]
  35. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  36. pub struct SystemTrayEvent<I: MenuId> {
  37. menu_item_id: I,
  38. }
  39. #[cfg(feature = "system-tray")]
  40. impl<I: MenuId> SystemTrayEvent<I> {
  41. /// The menu item id.
  42. pub fn menu_item_id(&self) -> &I {
  43. &self.menu_item_id
  44. }
  45. }
  46. /// A menu event that was triggered on a window.
  47. #[cfg(feature = "menu")]
  48. #[cfg_attr(doc_cfg, doc(cfg(feature = "menu")))]
  49. pub struct WindowMenuEvent<P: Params = DefaultArgs> {
  50. pub(crate) menu_item_id: P::MenuId,
  51. pub(crate) window: Window<P>,
  52. }
  53. #[cfg(feature = "menu")]
  54. impl<P: Params> WindowMenuEvent<P> {
  55. /// The menu item id.
  56. pub fn menu_item_id(&self) -> &P::MenuId {
  57. &self.menu_item_id
  58. }
  59. /// The window that the menu belongs to.
  60. pub fn window(&self) -> &Window<P> {
  61. &self.window
  62. }
  63. }
  64. /// A window event that was triggered on the specified window.
  65. pub struct GlobalWindowEvent<P: Params = DefaultArgs> {
  66. pub(crate) event: WindowEvent,
  67. pub(crate) window: Window<P>,
  68. }
  69. impl<P: Params> GlobalWindowEvent<P> {
  70. /// The eventpayload.
  71. pub fn event(&self) -> &WindowEvent {
  72. &self.event
  73. }
  74. /// The window that the menu belongs to.
  75. pub fn window(&self) -> &Window<P> {
  76. &self.window
  77. }
  78. }
  79. /// A handle to the currently running application.
  80. pub struct AppHandle<P: Params = DefaultArgs> {
  81. manager: WindowManager<P>,
  82. }
  83. impl<P: Params> Manager<P> for AppHandle<P> {}
  84. impl<P: Params> ManagerBase<P> for AppHandle<P> {
  85. fn manager(&self) -> &WindowManager<P> {
  86. &self.manager
  87. }
  88. }
  89. /// The instance of the currently running application.
  90. ///
  91. /// This type implements [`Manager`] which allows for manipulation of global application items.
  92. pub struct App<P: Params = DefaultArgs> {
  93. runtime: P::Runtime,
  94. manager: WindowManager<P>,
  95. }
  96. impl<P: Params> Manager<P> for App<P> {}
  97. impl<P: Params> ManagerBase<P> for App<P> {
  98. fn manager(&self) -> &WindowManager<P> {
  99. &self.manager
  100. }
  101. }
  102. impl<P: Params> App<P> {
  103. /// Creates a new webview window.
  104. pub fn create_window<F>(&mut self, label: P::Label, url: WindowUrl, setup: F) -> crate::Result<()>
  105. where
  106. F: FnOnce(
  107. <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
  108. WebviewAttributes,
  109. ) -> (
  110. <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
  111. WebviewAttributes,
  112. ),
  113. {
  114. let (window_builder, webview_attributes) = setup(
  115. <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder::new(),
  116. WebviewAttributes::new(url),
  117. );
  118. self.create_new_window(
  119. RuntimeOrDispatch::Runtime(&self.runtime),
  120. PendingWindow::new(window_builder, webview_attributes, label),
  121. )?;
  122. Ok(())
  123. }
  124. }
  125. #[cfg(feature = "updater")]
  126. impl<P: Params> App<P> {
  127. /// Runs the updater hook with built-in dialog.
  128. fn run_updater_dialog(&self, window: Window<P>) {
  129. let updater_config = self.manager.config().tauri.updater.clone();
  130. let package_info = self.manager.package_info().clone();
  131. crate::async_runtime::spawn(async move {
  132. updater::check_update_with_dialog(updater_config, package_info, window).await
  133. });
  134. }
  135. /// Listen updater events when dialog are disabled.
  136. fn listen_updater_events(&self, window: Window<P>) {
  137. let updater_config = self.manager.config().tauri.updater.clone();
  138. updater::listener(updater_config, self.manager.package_info().clone(), &window);
  139. }
  140. fn run_updater(&self, main_window: Option<Window<P>>) {
  141. if let Some(main_window) = main_window {
  142. let event_window = main_window.clone();
  143. let updater_config = self.manager.config().tauri.updater.clone();
  144. // check if updater is active or not
  145. if updater_config.dialog && updater_config.active {
  146. // if updater dialog is enabled spawn a new task
  147. self.run_updater_dialog(main_window.clone());
  148. let config = self.manager.config().tauri.updater.clone();
  149. let package_info = self.manager.package_info().clone();
  150. // When dialog is enabled, if user want to recheck
  151. // if an update is available after first start
  152. // invoke the Event `tauri://update` from JS or rust side.
  153. main_window.listen(
  154. updater::EVENT_CHECK_UPDATE
  155. .parse::<P::Event>()
  156. .unwrap_or_else(|_| panic!("bad label")),
  157. move |_msg| {
  158. let window = event_window.clone();
  159. let package_info = package_info.clone();
  160. let config = config.clone();
  161. // re-spawn task inside tokyo to launch the download
  162. // we don't need to emit anything as everything is handled
  163. // by the process (user is asked to restart at the end)
  164. // and it's handled by the updater
  165. crate::async_runtime::spawn(async move {
  166. updater::check_update_with_dialog(config, package_info, window).await
  167. });
  168. },
  169. );
  170. } else if updater_config.active {
  171. // we only listen for `tauri://update`
  172. // once we receive the call, we check if an update is available or not
  173. // if there is a new update we emit `tauri://update-available` with details
  174. // this is the user responsabilities to display dialog and ask if user want to install
  175. // to install the update you need to invoke the Event `tauri://update-install`
  176. self.listen_updater_events(main_window);
  177. }
  178. }
  179. }
  180. }
  181. /// Builds a Tauri application.
  182. #[allow(clippy::type_complexity)]
  183. pub struct Builder<E, L, MID, TID, A, R>
  184. where
  185. E: Tag,
  186. L: Tag,
  187. MID: MenuId,
  188. TID: MenuId,
  189. A: Assets,
  190. R: Runtime,
  191. {
  192. /// The JS message handler.
  193. invoke_handler: Box<InvokeHandler<Args<E, L, MID, TID, A, R>>>,
  194. /// The setup hook.
  195. setup: SetupHook<Args<E, L, MID, TID, A, R>>,
  196. /// Page load hook.
  197. on_page_load: Box<OnPageLoad<Args<E, L, MID, TID, A, R>>>,
  198. /// windows to create when starting up.
  199. pending_windows: Vec<PendingWindow<Args<E, L, MID, TID, A, R>>>,
  200. /// All passed plugins
  201. plugins: PluginStore<Args<E, L, MID, TID, A, R>>,
  202. /// The webview protocols available to all windows.
  203. uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
  204. /// App state.
  205. state: StateManager,
  206. /// The menu set to all windows.
  207. #[cfg(feature = "menu")]
  208. menu: Vec<Menu<MID>>,
  209. /// Menu event handlers that listens to all windows.
  210. #[cfg(feature = "menu")]
  211. menu_event_listeners: Vec<GlobalMenuEventListener<Args<E, L, MID, TID, A, R>>>,
  212. /// Window event handlers that listens to all windows.
  213. window_event_listeners: Vec<GlobalWindowEventListener<Args<E, L, MID, TID, A, R>>>,
  214. /// The app system tray menu items.
  215. #[cfg(feature = "system-tray")]
  216. system_tray: Vec<SystemTrayMenuItem<TID>>,
  217. /// System tray event handlers.
  218. #[cfg(feature = "system-tray")]
  219. system_tray_event_listeners: Vec<SystemTrayEventListener<Args<E, L, MID, TID, A, R>>>,
  220. }
  221. impl<E, L, MID, TID, A, R> Builder<E, L, MID, TID, A, R>
  222. where
  223. E: Tag,
  224. L: Tag,
  225. MID: MenuId,
  226. TID: MenuId,
  227. A: Assets,
  228. R: Runtime,
  229. {
  230. /// Creates a new App builder.
  231. pub fn new() -> Self {
  232. Self {
  233. setup: Box::new(|_| Ok(())),
  234. invoke_handler: Box::new(|_| ()),
  235. on_page_load: Box::new(|_, _| ()),
  236. pending_windows: Default::default(),
  237. plugins: PluginStore::default(),
  238. uri_scheme_protocols: Default::default(),
  239. state: StateManager::new(),
  240. #[cfg(feature = "menu")]
  241. menu: Vec::new(),
  242. #[cfg(feature = "menu")]
  243. menu_event_listeners: Vec::new(),
  244. window_event_listeners: Vec::new(),
  245. #[cfg(feature = "system-tray")]
  246. system_tray: Vec::new(),
  247. #[cfg(feature = "system-tray")]
  248. system_tray_event_listeners: Vec::new(),
  249. }
  250. }
  251. /// Defines the JS message handler callback.
  252. pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
  253. where
  254. F: Fn(Invoke<Args<E, L, MID, TID, A, R>>) + Send + Sync + 'static,
  255. {
  256. self.invoke_handler = Box::new(invoke_handler);
  257. self
  258. }
  259. /// Defines the setup hook.
  260. pub fn setup<F>(mut self, setup: F) -> Self
  261. where
  262. F: Fn(&mut App<Args<E, L, MID, TID, A, R>>) -> Result<(), Box<dyn std::error::Error + Send>>
  263. + Send
  264. + 'static,
  265. {
  266. self.setup = Box::new(setup);
  267. self
  268. }
  269. /// Defines the page load hook.
  270. pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
  271. where
  272. F: Fn(Window<Args<E, L, MID, TID, A, R>>, PageLoadPayload) + Send + Sync + 'static,
  273. {
  274. self.on_page_load = Box::new(on_page_load);
  275. self
  276. }
  277. /// Adds a plugin to the runtime.
  278. pub fn plugin<P: Plugin<Args<E, L, MID, TID, A, R>> + 'static>(mut self, plugin: P) -> Self {
  279. self.plugins.register(plugin);
  280. self
  281. }
  282. /// Add `state` to the state managed by the application.
  283. ///
  284. /// This method can be called any number of times as long as each call
  285. /// refers to a different `T`.
  286. ///
  287. /// Managed state can be retrieved by any request handler via the
  288. /// [`State`](crate::State) request guard. In particular, if a value of type `T`
  289. /// is managed by Tauri, adding `State<T>` to the list of arguments in a
  290. /// request handler instructs Tauri to retrieve the managed value.
  291. ///
  292. /// # Panics
  293. ///
  294. /// Panics if state of type `T` is already being managed.
  295. ///
  296. /// # Example
  297. ///
  298. /// ```rust,ignore
  299. /// use tauri::State;
  300. ///
  301. /// struct MyInt(isize);
  302. /// struct MyString(String);
  303. ///
  304. /// #[tauri::command]
  305. /// fn int_command(state: State<'_, MyInt>) -> String {
  306. /// format!("The stateful int is: {}", state.0)
  307. /// }
  308. ///
  309. /// #[tauri::command]
  310. /// fn string_command<'r>(state: State<'r, MyString>) {
  311. /// println!("state: {}", state.inner().0);
  312. /// }
  313. ///
  314. /// fn main() {
  315. /// tauri::Builder::default()
  316. /// .manage(MyInt(10))
  317. /// .manage(MyString("Hello, managed state!".to_string()))
  318. /// .run(tauri::generate_context!())
  319. /// .expect("error while running tauri application");
  320. /// }
  321. /// ```
  322. pub fn manage<T>(self, state: T) -> Self
  323. where
  324. T: Send + Sync + 'static,
  325. {
  326. let type_name = std::any::type_name::<T>();
  327. if !self.state.set(state) {
  328. panic!("state for type '{}' is already being managed", type_name);
  329. }
  330. self
  331. }
  332. /// Creates a new webview window.
  333. pub fn create_window<F>(mut self, label: L, url: WindowUrl, setup: F) -> Self
  334. where
  335. F: FnOnce(
  336. <R::Dispatcher as Dispatch>::WindowBuilder,
  337. WebviewAttributes,
  338. ) -> (
  339. <R::Dispatcher as Dispatch>::WindowBuilder,
  340. WebviewAttributes,
  341. ),
  342. {
  343. let (window_builder, webview_attributes) = setup(
  344. <R::Dispatcher as Dispatch>::WindowBuilder::new(),
  345. WebviewAttributes::new(url),
  346. );
  347. self.pending_windows.push(PendingWindow::new(
  348. window_builder,
  349. webview_attributes,
  350. label,
  351. ));
  352. self
  353. }
  354. /// Adds the icon configured on `tauri.conf.json` to the system tray with the specified menu items.
  355. #[cfg(feature = "system-tray")]
  356. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  357. pub fn system_tray(mut self, items: Vec<SystemTrayMenuItem<TID>>) -> Self {
  358. self.system_tray = items;
  359. self
  360. }
  361. /// Sets the menu to use on all windows.
  362. #[cfg(feature = "menu")]
  363. #[cfg_attr(doc_cfg, doc(cfg(feature = "menu")))]
  364. pub fn menu(mut self, menu: Vec<Menu<MID>>) -> Self {
  365. self.menu = menu;
  366. self
  367. }
  368. /// Registers a menu event handler for all windows.
  369. #[cfg(feature = "menu")]
  370. #[cfg_attr(doc_cfg, doc(cfg(feature = "menu")))]
  371. pub fn on_menu_event<
  372. F: Fn(WindowMenuEvent<Args<E, L, MID, TID, A, R>>) + Send + Sync + 'static,
  373. >(
  374. mut self,
  375. handler: F,
  376. ) -> Self {
  377. self.menu_event_listeners.push(Box::new(handler));
  378. self
  379. }
  380. /// Registers a window event handler for all windows.
  381. pub fn on_window_event<
  382. F: Fn(GlobalWindowEvent<Args<E, L, MID, TID, A, R>>) + Send + Sync + 'static,
  383. >(
  384. mut self,
  385. handler: F,
  386. ) -> Self {
  387. self.window_event_listeners.push(Box::new(handler));
  388. self
  389. }
  390. /// Registers a system tray event handler.
  391. #[cfg(feature = "system-tray")]
  392. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  393. pub fn on_system_tray_event<
  394. F: Fn(&AppHandle<Args<E, L, MID, TID, A, R>>, SystemTrayEvent<TID>) + Send + Sync + 'static,
  395. >(
  396. mut self,
  397. handler: F,
  398. ) -> Self {
  399. self.system_tray_event_listeners.push(Box::new(handler));
  400. self
  401. }
  402. /// Registers a URI scheme protocol available to all webviews.
  403. /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
  404. /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
  405. /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
  406. ///
  407. /// # Arguments
  408. ///
  409. /// * `uri_scheme` The URI scheme to register, such as `example`.
  410. /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
  411. pub fn register_global_uri_scheme_protocol<
  412. N: Into<String>,
  413. H: Fn(&str) -> Result<Vec<u8>, Box<dyn std::error::Error>> + Send + Sync + 'static,
  414. >(
  415. mut self,
  416. uri_scheme: N,
  417. protocol: H,
  418. ) -> Self {
  419. self.uri_scheme_protocols.insert(
  420. uri_scheme.into(),
  421. Arc::new(CustomProtocol {
  422. protocol: Box::new(protocol),
  423. }),
  424. );
  425. self
  426. }
  427. /// Runs the configured Tauri application.
  428. pub fn run(mut self, context: Context<A>) -> crate::Result<()> {
  429. #[cfg(feature = "system-tray")]
  430. let system_tray_icon = {
  431. let icon = context.system_tray_icon.clone();
  432. // check the icon format if the system tray is supposed to be ran
  433. if !self.system_tray.is_empty() {
  434. use std::io::{Error, ErrorKind};
  435. #[cfg(target_os = "linux")]
  436. if let Some(Icon::Raw(_)) = icon {
  437. return Err(crate::Error::InvalidIcon(Box::new(Error::new(
  438. ErrorKind::InvalidInput,
  439. "system tray icons on linux must be a file path",
  440. ))));
  441. }
  442. #[cfg(not(target_os = "linux"))]
  443. if let Some(Icon::File(bytes)) = icon {
  444. return Err(crate::Error::InvalidIcon(Box::new(Error::new(
  445. ErrorKind::InvalidInput,
  446. "system tray icons on non-linux platforms must be the raw bytes",
  447. ))));
  448. }
  449. }
  450. icon
  451. };
  452. let manager = WindowManager::with_handlers(
  453. context,
  454. self.plugins,
  455. self.invoke_handler,
  456. self.on_page_load,
  457. self.uri_scheme_protocols,
  458. self.state,
  459. self.window_event_listeners,
  460. #[cfg(feature = "menu")]
  461. (self.menu, self.menu_event_listeners),
  462. );
  463. // set up all the windows defined in the config
  464. for config in manager.config().tauri.windows.clone() {
  465. let url = config.url.clone();
  466. let label = config
  467. .label
  468. .parse()
  469. .unwrap_or_else(|_| panic!("bad label found in config: {}", config.label));
  470. self.pending_windows.push(PendingWindow::with_config(
  471. config,
  472. WebviewAttributes::new(url),
  473. label,
  474. ));
  475. }
  476. let mut app = App {
  477. runtime: R::new()?,
  478. manager,
  479. };
  480. app.manager.initialize_plugins(&app)?;
  481. let pending_labels = self
  482. .pending_windows
  483. .iter()
  484. .map(|p| p.label.clone())
  485. .collect::<Vec<_>>();
  486. #[cfg(feature = "updater")]
  487. let mut main_window = None;
  488. for pending in self.pending_windows {
  489. let pending = app.manager.prepare_window(pending, &pending_labels)?;
  490. let detached = app.runtime.create_window(pending)?;
  491. let _window = app.manager.attach_window(detached);
  492. #[cfg(feature = "updater")]
  493. if main_window.is_none() {
  494. main_window = Some(_window);
  495. }
  496. }
  497. #[cfg(feature = "updater")]
  498. app.run_updater(main_window);
  499. (self.setup)(&mut app).map_err(|e| crate::Error::Setup(e))?;
  500. #[cfg(feature = "system-tray")]
  501. if !self.system_tray.is_empty() {
  502. let ids = get_menu_ids(&self.system_tray);
  503. app
  504. .runtime
  505. .system_tray(
  506. system_tray_icon.expect("tray icon not found; please configure it on tauri.conf.json"),
  507. self.system_tray,
  508. )
  509. .expect("failed to run tray");
  510. for listener in self.system_tray_event_listeners {
  511. let app_handle = AppHandle {
  512. manager: app.manager.clone(),
  513. };
  514. let ids = ids.clone();
  515. app.runtime.on_system_tray_event(move |event| {
  516. listener(
  517. &app_handle,
  518. SystemTrayEvent {
  519. menu_item_id: ids.get(&event.menu_item_id).unwrap().clone(),
  520. },
  521. );
  522. });
  523. }
  524. }
  525. app.runtime.run();
  526. Ok(())
  527. }
  528. }
  529. #[cfg(feature = "system-tray")]
  530. fn get_menu_ids<I: MenuId>(items: &[SystemTrayMenuItem<I>]) -> HashMap<u32, I> {
  531. let mut map = HashMap::new();
  532. for item in items {
  533. if let SystemTrayMenuItem::Custom(i) = item {
  534. map.insert(i.id_value(), i.id.clone());
  535. }
  536. }
  537. map
  538. }
  539. /// Make `Wry` the default `Runtime` for `Builder`
  540. #[cfg(feature = "wry")]
  541. impl<A: Assets> Default for Builder<String, String, String, String, A, crate::Wry> {
  542. fn default() -> Self {
  543. Self::new()
  544. }
  545. }
  546. #[cfg(not(feature = "wry"))]
  547. impl<A: Assets, R: Runtime> Default for Builder<String, String, String, String, A, R> {
  548. fn default() -> Self {
  549. Self::new()
  550. }
  551. }