app.rs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  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::assets::Assets,
  8. api::config::{Config, WindowUrl},
  9. command::{CommandArg, CommandItem},
  10. hooks::{InvokeHandler, OnPageLoad, PageLoadPayload, SetupHook},
  11. manager::WindowManager,
  12. plugin::{Plugin, PluginStore},
  13. runtime::{
  14. webview::{CustomProtocol, WebviewAttributes, WindowBuilder},
  15. window::{PendingWindow, WindowEvent},
  16. Dispatch, RunEvent, Runtime,
  17. },
  18. sealed::{ManagerBase, RuntimeOrDispatch},
  19. Context, Invoke, InvokeError, Manager, StateManager, Window,
  20. };
  21. use tauri_macros::default_runtime;
  22. use tauri_utils::PackageInfo;
  23. use std::{
  24. collections::HashMap,
  25. path::PathBuf,
  26. sync::{mpsc::Sender, Arc},
  27. };
  28. #[cfg(feature = "menu")]
  29. use crate::runtime::menu::{Menu, MenuId, MenuIdRef};
  30. #[cfg(all(windows, feature = "system-tray"))]
  31. use crate::runtime::RuntimeHandle;
  32. #[cfg(feature = "system-tray")]
  33. use crate::runtime::{Icon, SystemTrayEvent as RuntimeSystemTrayEvent};
  34. #[cfg(feature = "updater")]
  35. use crate::updater;
  36. #[cfg(feature = "menu")]
  37. pub(crate) type GlobalMenuEventListener<R> = Box<dyn Fn(WindowMenuEvent<R>) + Send + Sync>;
  38. pub(crate) type GlobalWindowEventListener<R> = Box<dyn Fn(GlobalWindowEvent<R>) + Send + Sync>;
  39. #[cfg(feature = "system-tray")]
  40. type SystemTrayEventListener<R> = Box<dyn Fn(&AppHandle<R>, tray::SystemTrayEvent) + Send + Sync>;
  41. /// Api exposed on the `CloseRequested` event.
  42. pub struct CloseRequestApi(Sender<bool>);
  43. impl CloseRequestApi {
  44. /// Prevents the window from being closed.
  45. pub fn prevent_close(&self) {
  46. self.0.send(true).unwrap();
  47. }
  48. }
  49. /// An application event, triggered from the event loop.
  50. #[non_exhaustive]
  51. pub enum Event {
  52. /// Event loop is exiting.
  53. Exit,
  54. /// Window close was requested by the user.
  55. #[non_exhaustive]
  56. CloseRequested {
  57. /// The window label.
  58. label: String,
  59. /// Event API.
  60. api: CloseRequestApi,
  61. },
  62. /// Window closed.
  63. WindowClosed(String),
  64. }
  65. /// A menu event that was triggered on a window.
  66. #[cfg(feature = "menu")]
  67. #[cfg_attr(doc_cfg, doc(cfg(feature = "menu")))]
  68. #[default_runtime(crate::Wry, wry)]
  69. pub struct WindowMenuEvent<R: Runtime> {
  70. pub(crate) menu_item_id: MenuId,
  71. pub(crate) window: Window<R>,
  72. }
  73. #[cfg(feature = "menu")]
  74. impl<R: Runtime> WindowMenuEvent<R> {
  75. /// The menu item id.
  76. pub fn menu_item_id(&self) -> MenuIdRef<'_> {
  77. &self.menu_item_id
  78. }
  79. /// The window that the menu belongs to.
  80. pub fn window(&self) -> &Window<R> {
  81. &self.window
  82. }
  83. }
  84. /// A window event that was triggered on the specified window.
  85. #[default_runtime(crate::Wry, wry)]
  86. pub struct GlobalWindowEvent<R: Runtime> {
  87. pub(crate) event: WindowEvent,
  88. pub(crate) window: Window<R>,
  89. }
  90. impl<R: Runtime> GlobalWindowEvent<R> {
  91. /// The event payload.
  92. pub fn event(&self) -> &WindowEvent {
  93. &self.event
  94. }
  95. /// The window that the menu belongs to.
  96. pub fn window(&self) -> &Window<R> {
  97. &self.window
  98. }
  99. }
  100. /// The path resolver is a helper for the application-specific [`crate::api::path`] APIs.
  101. #[derive(Debug, Clone)]
  102. pub struct PathResolver {
  103. config: Arc<Config>,
  104. package_info: PackageInfo,
  105. }
  106. impl PathResolver {
  107. /// Returns the path to the resource directory of this app.
  108. pub fn resource_dir(&self) -> Option<PathBuf> {
  109. crate::api::path::resource_dir(&self.package_info)
  110. }
  111. /// Returns the path to the suggested directory for your app config files.
  112. pub fn app_dir(&self) -> Option<PathBuf> {
  113. crate::api::path::app_dir(&self.config)
  114. }
  115. }
  116. /// A handle to the currently running application.
  117. ///
  118. /// This type implements [`Manager`] which allows for manipulation of global application items.
  119. #[default_runtime(crate::Wry, wry)]
  120. pub struct AppHandle<R: Runtime> {
  121. runtime_handle: R::Handle,
  122. manager: WindowManager<R>,
  123. global_shortcut_manager: R::GlobalShortcutManager,
  124. clipboard_manager: R::ClipboardManager,
  125. #[cfg(feature = "system-tray")]
  126. tray_handle: Option<tray::SystemTrayHandle<R>>,
  127. }
  128. #[cfg(feature = "wry")]
  129. impl AppHandle<crate::Wry> {
  130. /// Create a new tao window using a callback. The event loop must be running at this point.
  131. pub fn create_tao_window<
  132. F: FnOnce() -> (String, tauri_runtime_wry::WryWindowBuilder) + Send + 'static,
  133. >(
  134. &self,
  135. f: F,
  136. ) -> crate::Result<Arc<tauri_runtime_wry::Window>> {
  137. self.runtime_handle.create_tao_window(f).map_err(Into::into)
  138. }
  139. }
  140. impl<R: Runtime> Clone for AppHandle<R> {
  141. fn clone(&self) -> Self {
  142. Self {
  143. runtime_handle: self.runtime_handle.clone(),
  144. manager: self.manager.clone(),
  145. global_shortcut_manager: self.global_shortcut_manager.clone(),
  146. clipboard_manager: self.clipboard_manager.clone(),
  147. #[cfg(feature = "system-tray")]
  148. tray_handle: self.tray_handle.clone(),
  149. }
  150. }
  151. }
  152. impl<'de, R: Runtime> CommandArg<'de, R> for AppHandle<R> {
  153. /// Grabs the [`Window`] from the [`CommandItem`] and returns the associated [`AppHandle`]. This will never fail.
  154. fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
  155. Ok(command.message.window().app_handle)
  156. }
  157. }
  158. impl<R: Runtime> AppHandle<R> {
  159. /// Removes the system tray.
  160. #[cfg(all(windows, feature = "system-tray"))]
  161. #[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))]
  162. fn remove_system_tray(&self) -> crate::Result<()> {
  163. self.runtime_handle.remove_system_tray().map_err(Into::into)
  164. }
  165. }
  166. impl<R: Runtime> Manager<R> for AppHandle<R> {}
  167. impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
  168. fn manager(&self) -> &WindowManager<R> {
  169. &self.manager
  170. }
  171. fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
  172. RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
  173. }
  174. fn app_handle(&self) -> AppHandle<R> {
  175. self.clone()
  176. }
  177. }
  178. /// The instance of the currently running application.
  179. ///
  180. /// This type implements [`Manager`] which allows for manipulation of global application items.
  181. #[default_runtime(crate::Wry, wry)]
  182. pub struct App<R: Runtime> {
  183. runtime: Option<R>,
  184. manager: WindowManager<R>,
  185. global_shortcut_manager: R::GlobalShortcutManager,
  186. clipboard_manager: R::ClipboardManager,
  187. #[cfg(feature = "system-tray")]
  188. tray_handle: Option<tray::SystemTrayHandle<R>>,
  189. handle: AppHandle<R>,
  190. }
  191. impl<R: Runtime> Manager<R> for App<R> {}
  192. impl<R: Runtime> ManagerBase<R> for App<R> {
  193. fn manager(&self) -> &WindowManager<R> {
  194. &self.manager
  195. }
  196. fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
  197. RuntimeOrDispatch::Runtime(self.runtime.as_ref().unwrap())
  198. }
  199. fn app_handle(&self) -> AppHandle<R> {
  200. self.handle()
  201. }
  202. }
  203. macro_rules! shared_app_impl {
  204. ($app: ty) => {
  205. impl<R: Runtime> $app {
  206. /// Creates a new webview window.
  207. pub fn create_window<F>(
  208. &self,
  209. label: impl Into<String>,
  210. url: WindowUrl,
  211. setup: F,
  212. ) -> crate::Result<()>
  213. where
  214. F: FnOnce(
  215. <R::Dispatcher as Dispatch>::WindowBuilder,
  216. WebviewAttributes,
  217. ) -> (
  218. <R::Dispatcher as Dispatch>::WindowBuilder,
  219. WebviewAttributes,
  220. ),
  221. {
  222. let (window_builder, webview_attributes) = setup(
  223. <R::Dispatcher as Dispatch>::WindowBuilder::new(),
  224. WebviewAttributes::new(url),
  225. );
  226. self.create_new_window(PendingWindow::new(
  227. window_builder,
  228. webview_attributes,
  229. label,
  230. ))?;
  231. Ok(())
  232. }
  233. #[cfg(feature = "system-tray")]
  234. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  235. /// Gets a handle handle to the system tray.
  236. pub fn tray_handle(&self) -> tray::SystemTrayHandle<R> {
  237. self
  238. .tray_handle
  239. .clone()
  240. .expect("tray not configured; use the `Builder#system_tray` API first.")
  241. }
  242. /// The path resolver for the application.
  243. pub fn path_resolver(&self) -> PathResolver {
  244. PathResolver {
  245. config: self.manager.config(),
  246. package_info: self.manager.package_info().clone(),
  247. }
  248. }
  249. /// Gets a copy of the global shortcut manager instance.
  250. pub fn global_shortcut_manager(&self) -> R::GlobalShortcutManager {
  251. self.global_shortcut_manager.clone()
  252. }
  253. /// Gets a copy of the clipboard manager instance.
  254. pub fn clipboard_manager(&self) -> R::ClipboardManager {
  255. self.clipboard_manager.clone()
  256. }
  257. /// Gets the app's configuration, defined on the `tauri.conf.json` file.
  258. pub fn config(&self) -> Arc<Config> {
  259. self.manager.config()
  260. }
  261. /// Gets the app's package information.
  262. pub fn package_info(&self) -> &PackageInfo {
  263. self.manager.package_info()
  264. }
  265. }
  266. };
  267. }
  268. shared_app_impl!(App<R>);
  269. shared_app_impl!(AppHandle<R>);
  270. impl<R: Runtime> App<R> {
  271. /// Gets a handle to the application instance.
  272. pub fn handle(&self) -> AppHandle<R> {
  273. self.handle.clone()
  274. }
  275. /// Runs the application.
  276. pub fn run<F: Fn(&AppHandle<R>, Event) + 'static>(mut self, callback: F) {
  277. let app_handle = self.handle();
  278. let manager = self.manager.clone();
  279. self.runtime.take().unwrap().run(move |event| match event {
  280. RunEvent::Exit => {
  281. #[cfg(shell_execute)]
  282. {
  283. crate::api::process::kill_children();
  284. }
  285. #[cfg(all(windows, feature = "system-tray"))]
  286. {
  287. let _ = app_handle.remove_system_tray();
  288. }
  289. callback(&app_handle, Event::Exit);
  290. }
  291. _ => {
  292. on_event_loop_event(&event, &manager);
  293. callback(
  294. &app_handle,
  295. match event {
  296. RunEvent::Exit => Event::Exit,
  297. RunEvent::CloseRequested { label, signal_tx } => Event::CloseRequested {
  298. label: label.parse().unwrap_or_else(|_| unreachable!()),
  299. api: CloseRequestApi(signal_tx),
  300. },
  301. RunEvent::WindowClose(label) => {
  302. Event::WindowClosed(label.parse().unwrap_or_else(|_| unreachable!()))
  303. }
  304. _ => unimplemented!(),
  305. },
  306. );
  307. }
  308. });
  309. }
  310. /// Runs a iteration of the runtime event loop and immediately return.
  311. ///
  312. /// Note that when using this API, app cleanup is not automatically done.
  313. /// The cleanup calls [`crate::api::process::kill_children`] so you may want to call that function before exiting the application.
  314. /// Additionally, the cleanup calls [AppHandle#remove_system_tray](`AppHandle#method.remove_system_tray`) (Windows only).
  315. ///
  316. /// # Example
  317. /// ```rust,ignore
  318. /// fn main() {
  319. /// let mut app = tauri::Builder::default()
  320. /// .build(tauri::generate_context!())
  321. /// .expect("error while building tauri application");
  322. /// loop {
  323. /// let iteration = app.run_iteration();
  324. /// if iteration.webview_count == 0 {
  325. /// break;
  326. /// }
  327. /// }
  328. /// }
  329. #[cfg(any(target_os = "windows", target_os = "macos"))]
  330. pub fn run_iteration(&mut self) -> crate::runtime::RunIteration {
  331. let manager = self.manager.clone();
  332. self
  333. .runtime
  334. .as_mut()
  335. .unwrap()
  336. .run_iteration(move |event| on_event_loop_event(&event, &manager))
  337. }
  338. }
  339. #[cfg(feature = "updater")]
  340. impl<R: Runtime> App<R> {
  341. /// Runs the updater hook with built-in dialog.
  342. fn run_updater_dialog(&self, window: Window<R>) {
  343. let updater_config = self.manager.config().tauri.updater.clone();
  344. let package_info = self.manager.package_info().clone();
  345. #[cfg(not(target_os = "linux"))]
  346. crate::async_runtime::spawn(async move {
  347. updater::check_update_with_dialog(updater_config, package_info, window).await
  348. });
  349. #[cfg(target_os = "linux")]
  350. {
  351. let context = glib::MainContext::default();
  352. context.spawn_with_priority(glib::PRIORITY_HIGH, async move {
  353. updater::check_update_with_dialog(updater_config, package_info, window).await
  354. });
  355. }
  356. }
  357. /// Listen updater events when dialog are disabled.
  358. fn listen_updater_events(&self, window: Window<R>) {
  359. let updater_config = self.manager.config().tauri.updater.clone();
  360. updater::listener(updater_config, self.manager.package_info().clone(), &window);
  361. }
  362. fn run_updater(&self, main_window: Option<Window<R>>) {
  363. if let Some(main_window) = main_window {
  364. let event_window = main_window.clone();
  365. let updater_config = self.manager.config().tauri.updater.clone();
  366. // check if updater is active or not
  367. if updater_config.dialog && updater_config.active {
  368. // if updater dialog is enabled spawn a new task
  369. self.run_updater_dialog(main_window.clone());
  370. let config = self.manager.config().tauri.updater.clone();
  371. let package_info = self.manager.package_info().clone();
  372. // When dialog is enabled, if user want to recheck
  373. // if an update is available after first start
  374. // invoke the Event `tauri://update` from JS or rust side.
  375. main_window.listen(updater::EVENT_CHECK_UPDATE, move |_msg| {
  376. let window = event_window.clone();
  377. let package_info = package_info.clone();
  378. let config = config.clone();
  379. // re-spawn task inside tokyo to launch the download
  380. // we don't need to emit anything as everything is handled
  381. // by the process (user is asked to restart at the end)
  382. // and it's handled by the updater
  383. crate::async_runtime::spawn(async move {
  384. updater::check_update_with_dialog(config, package_info, window).await
  385. });
  386. });
  387. } else if updater_config.active {
  388. // we only listen for `tauri://update`
  389. // once we receive the call, we check if an update is available or not
  390. // if there is a new update we emit `tauri://update-available` with details
  391. // this is the user responsabilities to display dialog and ask if user want to install
  392. // to install the update you need to invoke the Event `tauri://update-install`
  393. self.listen_updater_events(main_window);
  394. }
  395. }
  396. }
  397. }
  398. /// Builds a Tauri application.
  399. #[allow(clippy::type_complexity)]
  400. pub struct Builder<R: Runtime> {
  401. /// The JS message handler.
  402. invoke_handler: Box<InvokeHandler<R>>,
  403. /// The setup hook.
  404. setup: SetupHook<R>,
  405. /// Page load hook.
  406. on_page_load: Box<OnPageLoad<R>>,
  407. /// windows to create when starting up.
  408. pending_windows: Vec<PendingWindow<R>>,
  409. /// All passed plugins
  410. plugins: PluginStore<R>,
  411. /// The webview protocols available to all windows.
  412. uri_scheme_protocols: HashMap<String, Arc<CustomProtocol>>,
  413. /// App state.
  414. state: StateManager,
  415. /// The menu set to all windows.
  416. #[cfg(feature = "menu")]
  417. menu: Option<Menu>,
  418. /// Menu event handlers that listens to all windows.
  419. #[cfg(feature = "menu")]
  420. menu_event_listeners: Vec<GlobalMenuEventListener<R>>,
  421. /// Window event handlers that listens to all windows.
  422. window_event_listeners: Vec<GlobalWindowEventListener<R>>,
  423. /// The app system tray.
  424. #[cfg(feature = "system-tray")]
  425. system_tray: Option<tray::SystemTray>,
  426. /// System tray event handlers.
  427. #[cfg(feature = "system-tray")]
  428. system_tray_event_listeners: Vec<SystemTrayEventListener<R>>,
  429. }
  430. impl<R: Runtime> Builder<R> {
  431. /// Creates a new App builder.
  432. pub fn new() -> Self {
  433. Self {
  434. setup: Box::new(|_| Ok(())),
  435. invoke_handler: Box::new(|_| ()),
  436. on_page_load: Box::new(|_, _| ()),
  437. pending_windows: Default::default(),
  438. plugins: PluginStore::default(),
  439. uri_scheme_protocols: Default::default(),
  440. state: StateManager::new(),
  441. #[cfg(feature = "menu")]
  442. menu: None,
  443. #[cfg(feature = "menu")]
  444. menu_event_listeners: Vec::new(),
  445. window_event_listeners: Vec::new(),
  446. #[cfg(feature = "system-tray")]
  447. system_tray: None,
  448. #[cfg(feature = "system-tray")]
  449. system_tray_event_listeners: Vec::new(),
  450. }
  451. }
  452. /// Defines the JS message handler callback.
  453. pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
  454. where
  455. F: Fn(Invoke<R>) + Send + Sync + 'static,
  456. {
  457. self.invoke_handler = Box::new(invoke_handler);
  458. self
  459. }
  460. /// Defines the setup hook.
  461. pub fn setup<F>(mut self, setup: F) -> Self
  462. where
  463. F: Fn(&mut App<R>) -> Result<(), Box<dyn std::error::Error + Send>> + Send + 'static,
  464. {
  465. self.setup = Box::new(setup);
  466. self
  467. }
  468. /// Defines the page load hook.
  469. pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
  470. where
  471. F: Fn(Window<R>, PageLoadPayload) + Send + Sync + 'static,
  472. {
  473. self.on_page_load = Box::new(on_page_load);
  474. self
  475. }
  476. /// Adds a plugin to the runtime.
  477. pub fn plugin<P: Plugin<R> + 'static>(mut self, plugin: P) -> Self {
  478. self.plugins.register(plugin);
  479. self
  480. }
  481. /// Add `state` to the state managed by the application.
  482. ///
  483. /// This method can be called any number of times as long as each call
  484. /// refers to a different `T`.
  485. ///
  486. /// Managed state can be retrieved by any request handler via the
  487. /// [`State`](crate::State) request guard. In particular, if a value of type `T`
  488. /// is managed by Tauri, adding `State<T>` to the list of arguments in a
  489. /// request handler instructs Tauri to retrieve the managed value.
  490. ///
  491. /// # Panics
  492. ///
  493. /// Panics if state of type `T` is already being managed.
  494. ///
  495. /// # Mutability
  496. ///
  497. /// Since the managed state is global and must be [`Send`] + [`Sync`], mutations can only happen through interior mutability:
  498. ///
  499. /// ```rust,ignore
  500. /// use std::{collections::HashMap, sync::Mutex};
  501. /// use tauri::State;
  502. /// // here we use Mutex to achieve interior mutability
  503. /// struct Storage(Mutex<HashMap<u64, String>>);
  504. /// struct Connection;
  505. /// struct DbConnection(Mutex<Option<Connection>>);
  506. ///
  507. /// #[tauri::command]
  508. /// fn connect(connection: State<DbConnection>) {
  509. /// // initialize the connection, mutating the state with interior mutability
  510. /// *connection.0.lock().unwrap() = Some(Connection {});
  511. /// }
  512. ///
  513. /// #[tauri::command]
  514. /// fn storage_insert(key: u64, value: String, storage: State<Storage>) {
  515. /// // mutate the storage behind the Mutex
  516. /// storage.0.lock().unwrap().insert(key, value);
  517. /// }
  518. ///
  519. /// fn main() {
  520. /// Builder::default()
  521. /// .manage(Storage(Default::default()))
  522. /// .manage(DbConnection(Default::default()))
  523. /// .invoke_handler(tauri::generate_handler![connect, storage_insert])
  524. /// .run(tauri::generate_context!())
  525. /// .expect("error while running tauri application");
  526. /// }
  527. /// ```
  528. ///
  529. /// # Example
  530. ///
  531. /// ```rust,ignore
  532. /// use tauri::State;
  533. ///
  534. /// struct MyInt(isize);
  535. /// struct MyString(String);
  536. ///
  537. /// #[tauri::command]
  538. /// fn int_command(state: State<MyInt>) -> String {
  539. /// format!("The stateful int is: {}", state.0)
  540. /// }
  541. ///
  542. /// #[tauri::command]
  543. /// fn string_command<'r>(state: State<'r, MyString>) {
  544. /// println!("state: {}", state.inner().0);
  545. /// }
  546. ///
  547. /// fn main() {
  548. /// tauri::Builder::default()
  549. /// .manage(MyInt(10))
  550. /// .manage(MyString("Hello, managed state!".to_string()))
  551. /// .invoke_handler(tauri::generate_handler![int_command, string_command])
  552. /// .run(tauri::generate_context!())
  553. /// .expect("error while running tauri application");
  554. /// }
  555. /// ```
  556. pub fn manage<T>(self, state: T) -> Self
  557. where
  558. T: Send + Sync + 'static,
  559. {
  560. let type_name = std::any::type_name::<T>();
  561. if !self.state.set(state) {
  562. panic!("state for type '{}' is already being managed", type_name);
  563. }
  564. self
  565. }
  566. /// Creates a new webview window.
  567. pub fn create_window<F>(mut self, label: impl Into<String>, url: WindowUrl, setup: F) -> Self
  568. where
  569. F: FnOnce(
  570. <R::Dispatcher as Dispatch>::WindowBuilder,
  571. WebviewAttributes,
  572. ) -> (
  573. <R::Dispatcher as Dispatch>::WindowBuilder,
  574. WebviewAttributes,
  575. ),
  576. {
  577. let (window_builder, webview_attributes) = setup(
  578. <R::Dispatcher as Dispatch>::WindowBuilder::new(),
  579. WebviewAttributes::new(url),
  580. );
  581. self.pending_windows.push(PendingWindow::new(
  582. window_builder,
  583. webview_attributes,
  584. label,
  585. ));
  586. self
  587. }
  588. /// Adds the icon configured on `tauri.conf.json` to the system tray with the specified menu items.
  589. #[cfg(feature = "system-tray")]
  590. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  591. pub fn system_tray(mut self, system_tray: tray::SystemTray) -> Self {
  592. self.system_tray.replace(system_tray);
  593. self
  594. }
  595. /// Sets the menu to use on all windows.
  596. #[cfg(feature = "menu")]
  597. #[cfg_attr(doc_cfg, doc(cfg(feature = "menu")))]
  598. pub fn menu(mut self, menu: Menu) -> Self {
  599. self.menu.replace(menu);
  600. self
  601. }
  602. /// Registers a menu event handler for all windows.
  603. #[cfg(feature = "menu")]
  604. #[cfg_attr(doc_cfg, doc(cfg(feature = "menu")))]
  605. pub fn on_menu_event<F: Fn(WindowMenuEvent<R>) + Send + Sync + 'static>(
  606. mut self,
  607. handler: F,
  608. ) -> Self {
  609. self.menu_event_listeners.push(Box::new(handler));
  610. self
  611. }
  612. /// Registers a window event handler for all windows.
  613. pub fn on_window_event<F: Fn(GlobalWindowEvent<R>) + Send + Sync + 'static>(
  614. mut self,
  615. handler: F,
  616. ) -> Self {
  617. self.window_event_listeners.push(Box::new(handler));
  618. self
  619. }
  620. /// Registers a system tray event handler.
  621. #[cfg(feature = "system-tray")]
  622. #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
  623. pub fn on_system_tray_event<
  624. F: Fn(&AppHandle<R>, tray::SystemTrayEvent) + Send + Sync + 'static,
  625. >(
  626. mut self,
  627. handler: F,
  628. ) -> Self {
  629. self.system_tray_event_listeners.push(Box::new(handler));
  630. self
  631. }
  632. /// Registers a URI scheme protocol available to all webviews.
  633. /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS,
  634. /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows
  635. /// and [webkit-web-context-register-uri-scheme](https://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-register-uri-scheme) on Linux.
  636. ///
  637. /// # Arguments
  638. ///
  639. /// * `uri_scheme` The URI scheme to register, such as `example`.
  640. /// * `protocol` the protocol associated with the given URI scheme. It's a function that takes an URL such as `example://localhost/asset.css`.
  641. pub fn register_global_uri_scheme_protocol<
  642. N: Into<String>,
  643. H: Fn(&str) -> Result<Vec<u8>, Box<dyn std::error::Error>> + Send + Sync + 'static,
  644. >(
  645. mut self,
  646. uri_scheme: N,
  647. protocol: H,
  648. ) -> Self {
  649. self.uri_scheme_protocols.insert(
  650. uri_scheme.into(),
  651. Arc::new(CustomProtocol {
  652. protocol: Box::new(protocol),
  653. }),
  654. );
  655. self
  656. }
  657. /// Builds the application.
  658. #[allow(clippy::type_complexity)]
  659. pub fn build<A: Assets>(mut self, context: Context<A>) -> crate::Result<App<R>> {
  660. #[cfg(feature = "system-tray")]
  661. let system_tray_icon = {
  662. let icon = context.system_tray_icon.clone();
  663. // check the icon format if the system tray is configured
  664. if self.system_tray.is_some() {
  665. use std::io::{Error, ErrorKind};
  666. #[cfg(target_os = "linux")]
  667. if let Some(Icon::Raw(_)) = icon {
  668. return Err(crate::Error::InvalidIcon(Box::new(Error::new(
  669. ErrorKind::InvalidInput,
  670. "system tray icons on linux must be a file path",
  671. ))));
  672. }
  673. #[cfg(not(target_os = "linux"))]
  674. if let Some(Icon::File(_)) = icon {
  675. return Err(crate::Error::InvalidIcon(Box::new(Error::new(
  676. ErrorKind::InvalidInput,
  677. "system tray icons on non-linux platforms must be the raw bytes",
  678. ))));
  679. }
  680. }
  681. icon
  682. };
  683. #[cfg(all(feature = "system-tray", target_os = "macos"))]
  684. let system_tray_icon_as_template = context
  685. .config
  686. .tauri
  687. .system_tray
  688. .as_ref()
  689. .map(|t| t.icon_as_template)
  690. .unwrap_or_default();
  691. let manager = WindowManager::with_handlers(
  692. context,
  693. self.plugins,
  694. self.invoke_handler,
  695. self.on_page_load,
  696. self.uri_scheme_protocols,
  697. self.state,
  698. self.window_event_listeners,
  699. #[cfg(feature = "menu")]
  700. (self.menu, self.menu_event_listeners),
  701. );
  702. // set up all the windows defined in the config
  703. for config in manager.config().tauri.windows.clone() {
  704. let url = config.url.clone();
  705. let label = config.label.clone();
  706. let file_drop_enabled = config.file_drop_enabled;
  707. let mut webview_attributes = WebviewAttributes::new(url);
  708. if !file_drop_enabled {
  709. webview_attributes = webview_attributes.disable_file_drop_handler();
  710. }
  711. self.pending_windows.push(PendingWindow::with_config(
  712. config,
  713. webview_attributes,
  714. label,
  715. ));
  716. }
  717. let runtime = R::new()?;
  718. let runtime_handle = runtime.handle();
  719. let global_shortcut_manager = runtime.global_shortcut_manager();
  720. let clipboard_manager = runtime.clipboard_manager();
  721. let mut app = App {
  722. runtime: Some(runtime),
  723. manager: manager.clone(),
  724. global_shortcut_manager: global_shortcut_manager.clone(),
  725. clipboard_manager: clipboard_manager.clone(),
  726. #[cfg(feature = "system-tray")]
  727. tray_handle: None,
  728. handle: AppHandle {
  729. runtime_handle,
  730. manager,
  731. global_shortcut_manager,
  732. clipboard_manager,
  733. #[cfg(feature = "system-tray")]
  734. tray_handle: None,
  735. },
  736. };
  737. app.manager.initialize_plugins(&app)?;
  738. let pending_labels = self
  739. .pending_windows
  740. .iter()
  741. .map(|p| p.label.clone())
  742. .collect::<Vec<_>>();
  743. #[cfg(feature = "updater")]
  744. let mut main_window = None;
  745. for pending in self.pending_windows {
  746. let pending = app
  747. .manager
  748. .prepare_window(app.handle.clone(), pending, &pending_labels)?;
  749. let detached = app.runtime.as_ref().unwrap().create_window(pending)?;
  750. let _window = app.manager.attach_window(app.handle(), detached);
  751. #[cfg(feature = "updater")]
  752. if main_window.is_none() {
  753. main_window = Some(_window);
  754. }
  755. }
  756. #[cfg(feature = "system-tray")]
  757. if let Some(system_tray) = self.system_tray {
  758. let mut ids = HashMap::new();
  759. if let Some(menu) = system_tray.menu() {
  760. tray::get_menu_ids(&mut ids, menu);
  761. }
  762. let mut tray = tray::SystemTray::new();
  763. if let Some(menu) = system_tray.menu {
  764. tray = tray.with_menu(menu);
  765. }
  766. #[cfg(not(target_os = "macos"))]
  767. let tray_handler = app
  768. .runtime
  769. .as_ref()
  770. .unwrap()
  771. .system_tray(
  772. tray.with_icon(
  773. system_tray
  774. .icon
  775. .or(system_tray_icon)
  776. .expect("tray icon not found; please configure it on tauri.conf.json"),
  777. ),
  778. )
  779. .expect("failed to run tray");
  780. #[cfg(target_os = "macos")]
  781. let tray_handler = app
  782. .runtime
  783. .as_ref()
  784. .unwrap()
  785. .system_tray(
  786. tray
  787. .with_icon(
  788. system_tray
  789. .icon
  790. .or(system_tray_icon)
  791. .expect("tray icon not found; please configure it on tauri.conf.json"),
  792. )
  793. .with_icon_as_template(system_tray_icon_as_template),
  794. )
  795. .expect("failed to run tray");
  796. let tray_handle = tray::SystemTrayHandle {
  797. ids: Arc::new(ids.clone()),
  798. inner: tray_handler,
  799. };
  800. app.tray_handle.replace(tray_handle.clone());
  801. app.handle.tray_handle.replace(tray_handle);
  802. for listener in self.system_tray_event_listeners {
  803. let app_handle = app.handle();
  804. let ids = ids.clone();
  805. let listener = Arc::new(std::sync::Mutex::new(listener));
  806. app
  807. .runtime
  808. .as_mut()
  809. .unwrap()
  810. .on_system_tray_event(move |event| {
  811. let app_handle = app_handle.clone();
  812. let event = match event {
  813. RuntimeSystemTrayEvent::MenuItemClick(id) => tray::SystemTrayEvent::MenuItemClick {
  814. id: ids.get(id).unwrap().clone(),
  815. },
  816. RuntimeSystemTrayEvent::LeftClick { position, size } => {
  817. tray::SystemTrayEvent::LeftClick {
  818. position: *position,
  819. size: *size,
  820. }
  821. }
  822. RuntimeSystemTrayEvent::RightClick { position, size } => {
  823. tray::SystemTrayEvent::RightClick {
  824. position: *position,
  825. size: *size,
  826. }
  827. }
  828. RuntimeSystemTrayEvent::DoubleClick { position, size } => {
  829. tray::SystemTrayEvent::DoubleClick {
  830. position: *position,
  831. size: *size,
  832. }
  833. }
  834. };
  835. let listener = listener.clone();
  836. crate::async_runtime::spawn(async move {
  837. listener.lock().unwrap()(&app_handle, event);
  838. });
  839. });
  840. }
  841. }
  842. (self.setup)(&mut app).map_err(|e| crate::Error::Setup(e))?;
  843. #[cfg(feature = "updater")]
  844. app.run_updater(main_window);
  845. Ok(app)
  846. }
  847. /// Runs the configured Tauri application.
  848. pub fn run<A: Assets>(self, context: Context<A>) -> crate::Result<()> {
  849. self.build(context)?.run(|_, _| {});
  850. Ok(())
  851. }
  852. }
  853. fn on_event_loop_event<R: Runtime>(event: &RunEvent, manager: &WindowManager<R>) {
  854. if let RunEvent::WindowClose(label) = event {
  855. manager.on_window_close(label);
  856. }
  857. }
  858. /// Make `Wry` the default `Runtime` for `Builder`
  859. #[cfg(feature = "wry")]
  860. impl Default for Builder<crate::Wry> {
  861. fn default() -> Self {
  862. Self::new()
  863. }
  864. }