window.rs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! A layer between raw [`Runtime`] webview windows and Tauri.
  5. use crate::{
  6. http::{Request as HttpRequest, Response as HttpResponse},
  7. menu::{Menu, MenuEntry, MenuHash, MenuId},
  8. webview::{WebviewAttributes, WebviewIpcHandler},
  9. Dispatch, Runtime, UserEvent, WindowBuilder,
  10. };
  11. use serde::Serialize;
  12. use tauri_utils::config::WindowConfig;
  13. use std::{
  14. collections::{HashMap, HashSet},
  15. hash::{Hash, Hasher},
  16. path::PathBuf,
  17. sync::{mpsc::Sender, Arc, Mutex},
  18. };
  19. type UriSchemeProtocol =
  20. dyn Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + Send + Sync + 'static;
  21. /// UI scaling utilities.
  22. pub mod dpi;
  23. /// An event from a window.
  24. #[derive(Debug, Clone)]
  25. #[non_exhaustive]
  26. pub enum WindowEvent {
  27. /// The size of the window has changed. Contains the client area's new dimensions.
  28. Resized(dpi::PhysicalSize<u32>),
  29. /// The position of the window has changed. Contains the window's new position.
  30. Moved(dpi::PhysicalPosition<i32>),
  31. /// The window has been requested to close.
  32. CloseRequested {
  33. /// The window label.
  34. label: String,
  35. /// A signal sender. If a `true` value is emitted, the window won't be closed.
  36. signal_tx: Sender<bool>,
  37. },
  38. /// The window has been destroyed.
  39. Destroyed,
  40. /// The window gained or lost focus.
  41. ///
  42. /// The parameter is true if the window has gained focus, and false if it has lost focus.
  43. Focused(bool),
  44. /// The window's scale factor has changed.
  45. ///
  46. /// The following user actions can cause DPI changes:
  47. ///
  48. /// - Changing the display's resolution.
  49. /// - Changing the display's scale factor (e.g. in Control Panel on Windows).
  50. /// - Moving the window to a display with a different scale factor.
  51. ScaleFactorChanged {
  52. /// The new scale factor.
  53. scale_factor: f64,
  54. /// The window inner size.
  55. new_inner_size: dpi::PhysicalSize<u32>,
  56. },
  57. /// An event associated with the file drop action.
  58. FileDrop(FileDropEvent),
  59. }
  60. /// The file drop event payload.
  61. #[derive(Debug, Clone)]
  62. #[non_exhaustive]
  63. pub enum FileDropEvent {
  64. /// The file(s) have been dragged onto the window, but have not been dropped yet.
  65. Hovered(Vec<PathBuf>),
  66. /// The file(s) have been dropped onto the window.
  67. Dropped(Vec<PathBuf>),
  68. /// The file drop was aborted.
  69. Cancelled,
  70. }
  71. /// A menu event.
  72. #[derive(Debug, Serialize)]
  73. #[serde(rename_all = "camelCase")]
  74. pub struct MenuEvent {
  75. pub menu_item_id: u16,
  76. }
  77. fn get_menu_ids(map: &mut HashMap<MenuHash, MenuId>, menu: &Menu) {
  78. for item in &menu.items {
  79. match item {
  80. MenuEntry::CustomItem(c) => {
  81. map.insert(c.id, c.id_str.clone());
  82. }
  83. MenuEntry::Submenu(s) => get_menu_ids(map, &s.inner),
  84. _ => {}
  85. }
  86. }
  87. }
  88. /// A webview window that has yet to be built.
  89. pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
  90. /// The label that the window will be named.
  91. pub label: String,
  92. /// The [`WindowBuilder`] that the window will be created with.
  93. pub window_builder: <R::Dispatcher as Dispatch<T>>::WindowBuilder,
  94. /// The [`WebviewAttributes`] that the webview will be created with.
  95. pub webview_attributes: WebviewAttributes,
  96. pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocol>>,
  97. /// How to handle IPC calls on the webview window.
  98. pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
  99. /// The resolved URL to load on the webview.
  100. pub url: String,
  101. /// Maps runtime id to a string menu id.
  102. pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
  103. /// A HashMap mapping JS event names with associated listener ids.
  104. pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
  105. }
  106. pub fn is_label_valid(label: &str) -> bool {
  107. label
  108. .chars()
  109. .all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
  110. }
  111. pub fn assert_label_is_valid(label: &str) {
  112. assert!(
  113. is_label_valid(label),
  114. "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
  115. );
  116. }
  117. impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
  118. /// Create a new [`PendingWindow`] with a label and starting url.
  119. pub fn new(
  120. window_builder: <R::Dispatcher as Dispatch<T>>::WindowBuilder,
  121. webview_attributes: WebviewAttributes,
  122. label: impl Into<String>,
  123. ) -> crate::Result<Self> {
  124. let mut menu_ids = HashMap::new();
  125. if let Some(menu) = window_builder.get_menu() {
  126. get_menu_ids(&mut menu_ids, menu);
  127. }
  128. let label = label.into();
  129. if !is_label_valid(&label) {
  130. Err(crate::Error::InvalidWindowLabel)
  131. } else {
  132. Ok(Self {
  133. window_builder,
  134. webview_attributes,
  135. uri_scheme_protocols: Default::default(),
  136. label,
  137. ipc_handler: None,
  138. url: "tauri://localhost".to_string(),
  139. menu_ids: Arc::new(Mutex::new(menu_ids)),
  140. js_event_listeners: Default::default(),
  141. })
  142. }
  143. }
  144. /// Create a new [`PendingWindow`] from a [`WindowConfig`] with a label and starting url.
  145. pub fn with_config(
  146. window_config: WindowConfig,
  147. webview_attributes: WebviewAttributes,
  148. label: impl Into<String>,
  149. ) -> crate::Result<Self> {
  150. let window_builder =
  151. <<R::Dispatcher as Dispatch<T>>::WindowBuilder>::with_config(window_config);
  152. let mut menu_ids = HashMap::new();
  153. if let Some(menu) = window_builder.get_menu() {
  154. get_menu_ids(&mut menu_ids, menu);
  155. }
  156. let label = label.into();
  157. if !is_label_valid(&label) {
  158. Err(crate::Error::InvalidWindowLabel)
  159. } else {
  160. Ok(Self {
  161. window_builder,
  162. webview_attributes,
  163. uri_scheme_protocols: Default::default(),
  164. label,
  165. ipc_handler: None,
  166. url: "tauri://localhost".to_string(),
  167. menu_ids: Arc::new(Mutex::new(menu_ids)),
  168. js_event_listeners: Default::default(),
  169. })
  170. }
  171. }
  172. #[must_use]
  173. pub fn set_menu(mut self, menu: Menu) -> Self {
  174. let mut menu_ids = HashMap::new();
  175. get_menu_ids(&mut menu_ids, &menu);
  176. *self.menu_ids.lock().unwrap() = menu_ids;
  177. self.window_builder = self.window_builder.menu(menu);
  178. self
  179. }
  180. pub fn register_uri_scheme_protocol<
  181. N: Into<String>,
  182. H: Fn(&HttpRequest) -> Result<HttpResponse, Box<dyn std::error::Error>> + Send + Sync + 'static,
  183. >(
  184. &mut self,
  185. uri_scheme: N,
  186. protocol: H,
  187. ) {
  188. let uri_scheme = uri_scheme.into();
  189. self
  190. .uri_scheme_protocols
  191. .insert(uri_scheme, Box::new(move |data| (protocol)(data)));
  192. }
  193. }
  194. /// Key for a JS event listener.
  195. #[derive(Debug, Clone, PartialEq, Eq, Hash)]
  196. pub struct JsEventListenerKey {
  197. /// The associated window label.
  198. pub window_label: Option<String>,
  199. /// The event name.
  200. pub event: String,
  201. }
  202. /// A webview window that is not yet managed by Tauri.
  203. #[derive(Debug)]
  204. pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
  205. /// Name of the window
  206. pub label: String,
  207. /// The [`Dispatch`](crate::Dispatch) associated with the window.
  208. pub dispatcher: R::Dispatcher,
  209. /// Maps runtime id to a string menu id.
  210. pub menu_ids: Arc<Mutex<HashMap<MenuHash, MenuId>>>,
  211. /// A HashMap mapping JS event names with associated listener ids.
  212. pub js_event_listeners: Arc<Mutex<HashMap<JsEventListenerKey, HashSet<u64>>>>,
  213. }
  214. impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
  215. fn clone(&self) -> Self {
  216. Self {
  217. label: self.label.clone(),
  218. dispatcher: self.dispatcher.clone(),
  219. menu_ids: self.menu_ids.clone(),
  220. js_event_listeners: self.js_event_listeners.clone(),
  221. }
  222. }
  223. }
  224. impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWindow<T, R> {
  225. /// Only use the [`DetachedWindow`]'s label to represent its hash.
  226. fn hash<H: Hasher>(&self, state: &mut H) {
  227. self.label.hash(state)
  228. }
  229. }
  230. impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWindow<T, R> {}
  231. impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
  232. /// Only use the [`DetachedWindow`]'s label to compare equality.
  233. fn eq(&self, other: &Self) -> bool {
  234. self.label.eq(&other.label)
  235. }
  236. }