desktop_context.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. use std::cell::RefCell;
  2. use std::rc::Rc;
  3. use std::rc::Weak;
  4. use crate::create_new_window;
  5. use crate::events::IpcMessage;
  6. use crate::protocol::AssetFuture;
  7. use crate::protocol::AssetHandlerRegistry;
  8. use crate::query::QueryEngine;
  9. use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError};
  10. use crate::AssetHandler;
  11. use crate::Config;
  12. use crate::WebviewHandler;
  13. use dioxus_core::ScopeState;
  14. use dioxus_core::VirtualDom;
  15. #[cfg(all(feature = "hot-reload", debug_assertions))]
  16. use dioxus_hot_reload::HotReloadMsg;
  17. use slab::Slab;
  18. use wry::application::event::Event;
  19. use wry::application::event_loop::EventLoopProxy;
  20. use wry::application::event_loop::EventLoopWindowTarget;
  21. #[cfg(target_os = "ios")]
  22. use wry::application::platform::ios::WindowExtIOS;
  23. use wry::application::window::Fullscreen as WryFullscreen;
  24. use wry::application::window::Window;
  25. use wry::application::window::WindowId;
  26. use wry::webview::WebView;
  27. pub type ProxyType = EventLoopProxy<UserWindowEvent>;
  28. /// Get an imperative handle to the current window
  29. pub fn use_window(cx: &ScopeState) -> &DesktopContext {
  30. cx.use_hook(|| cx.consume_context::<DesktopContext>())
  31. .as_ref()
  32. .unwrap()
  33. }
  34. pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
  35. /// An imperative interface to the current window.
  36. ///
  37. /// To get a handle to the current window, use the [`use_window`] hook.
  38. ///
  39. ///
  40. /// # Example
  41. ///
  42. /// you can use `cx.consume_context::<DesktopContext>` to get this context
  43. ///
  44. /// ```rust, ignore
  45. /// let desktop = cx.consume_context::<DesktopContext>().unwrap();
  46. /// ```
  47. pub struct DesktopService {
  48. /// The wry/tao proxy to the current window
  49. pub webview: Rc<WebView>,
  50. /// The proxy to the event loop
  51. pub proxy: ProxyType,
  52. /// The receiver for queries about the current window
  53. pub(super) query: QueryEngine,
  54. pub(super) pending_windows: WebviewQueue,
  55. pub(crate) event_loop: EventLoopWindowTarget<UserWindowEvent>,
  56. pub(crate) event_handlers: WindowEventHandlers,
  57. pub(crate) shortcut_manager: ShortcutRegistry,
  58. pub(crate) asset_handlers: AssetHandlerRegistry,
  59. #[cfg(target_os = "ios")]
  60. pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
  61. }
  62. /// A handle to the [`DesktopService`] that can be passed around.
  63. pub type DesktopContext = Rc<DesktopService>;
  64. /// A smart pointer to the current window.
  65. impl std::ops::Deref for DesktopService {
  66. type Target = Window;
  67. fn deref(&self) -> &Self::Target {
  68. self.webview.window()
  69. }
  70. }
  71. impl DesktopService {
  72. pub(crate) fn new(
  73. webview: WebView,
  74. proxy: ProxyType,
  75. event_loop: EventLoopWindowTarget<UserWindowEvent>,
  76. webviews: WebviewQueue,
  77. event_handlers: WindowEventHandlers,
  78. shortcut_manager: ShortcutRegistry,
  79. asset_handlers: AssetHandlerRegistry,
  80. ) -> Self {
  81. Self {
  82. webview: Rc::new(webview),
  83. proxy,
  84. event_loop,
  85. query: Default::default(),
  86. pending_windows: webviews,
  87. event_handlers,
  88. shortcut_manager,
  89. asset_handlers,
  90. #[cfg(target_os = "ios")]
  91. views: Default::default(),
  92. }
  93. }
  94. /// Create a new window using the props and window builder
  95. ///
  96. /// Returns the webview handle for the new window.
  97. ///
  98. /// You can use this to control other windows from the current window.
  99. ///
  100. /// Be careful to not create a cycle of windows, or you might leak memory.
  101. pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> Weak<DesktopService> {
  102. let window = create_new_window(
  103. cfg,
  104. &self.event_loop,
  105. &self.proxy,
  106. dom,
  107. &self.pending_windows,
  108. &self.event_handlers,
  109. self.shortcut_manager.clone(),
  110. );
  111. let desktop_context = window
  112. .dom
  113. .base_scope()
  114. .consume_context::<Rc<DesktopService>>()
  115. .unwrap();
  116. let id = window.desktop_context.webview.window().id();
  117. self.proxy
  118. .send_event(UserWindowEvent(EventData::NewWindow, id))
  119. .unwrap();
  120. self.proxy
  121. .send_event(UserWindowEvent(EventData::Poll, id))
  122. .unwrap();
  123. self.pending_windows.borrow_mut().push(window);
  124. Rc::downgrade(&desktop_context)
  125. }
  126. /// trigger the drag-window event
  127. ///
  128. /// Moves the window with the left mouse button until the button is released.
  129. ///
  130. /// you need use it in `onmousedown` event:
  131. /// ```rust, ignore
  132. /// onmousedown: move |_| { desktop.drag_window(); }
  133. /// ```
  134. pub fn drag(&self) {
  135. let window = self.webview.window();
  136. // if the drag_window has any errors, we don't do anything
  137. if window.fullscreen().is_none() {
  138. window.drag_window().unwrap();
  139. }
  140. }
  141. /// Toggle whether the window is maximized or not
  142. pub fn toggle_maximized(&self) {
  143. let window = self.webview.window();
  144. window.set_maximized(!window.is_maximized())
  145. }
  146. /// close window
  147. pub fn close(&self) {
  148. let _ = self
  149. .proxy
  150. .send_event(UserWindowEvent(EventData::CloseWindow, self.id()));
  151. }
  152. /// close window
  153. pub fn close_window(&self, id: WindowId) {
  154. let _ = self
  155. .proxy
  156. .send_event(UserWindowEvent(EventData::CloseWindow, id));
  157. }
  158. /// change window to fullscreen
  159. pub fn set_fullscreen(&self, fullscreen: bool) {
  160. if let Some(handle) = self.webview.window().current_monitor() {
  161. self.webview
  162. .window()
  163. .set_fullscreen(fullscreen.then_some(WryFullscreen::Borderless(Some(handle))));
  164. }
  165. }
  166. /// launch print modal
  167. pub fn print(&self) {
  168. if let Err(e) = self.webview.print() {
  169. tracing::warn!("Open print modal failed: {e}");
  170. }
  171. }
  172. /// Set the zoom level of the webview
  173. pub fn set_zoom_level(&self, level: f64) {
  174. self.webview.zoom(level);
  175. }
  176. /// opens DevTool window
  177. pub fn devtool(&self) {
  178. #[cfg(debug_assertions)]
  179. self.webview.open_devtools();
  180. #[cfg(not(debug_assertions))]
  181. tracing::warn!("Devtools are disabled in release builds");
  182. }
  183. /// Create a wry event handler that listens for wry events.
  184. /// This event handler is scoped to the currently active window and will only recieve events that are either global or related to the current window.
  185. ///
  186. /// The id this function returns can be used to remove the event handler with [`DesktopContext::remove_wry_event_handler`]
  187. pub fn create_wry_event_handler(
  188. &self,
  189. handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
  190. ) -> WryEventHandlerId {
  191. self.event_handlers.add(self.id(), handler)
  192. }
  193. /// Remove a wry event handler created with [`DesktopContext::create_wry_event_handler`]
  194. pub fn remove_wry_event_handler(&self, id: WryEventHandlerId) {
  195. self.event_handlers.remove(id)
  196. }
  197. /// Create a global shortcut
  198. ///
  199. /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information.
  200. pub fn create_shortcut(
  201. &self,
  202. hotkey: HotKey,
  203. callback: impl FnMut() + 'static,
  204. ) -> Result<ShortcutId, ShortcutRegistryError> {
  205. self.shortcut_manager
  206. .add_shortcut(hotkey, Box::new(callback))
  207. }
  208. /// Remove a global shortcut
  209. pub fn remove_shortcut(&self, id: ShortcutId) {
  210. self.shortcut_manager.remove_shortcut(id)
  211. }
  212. /// Remove all global shortcuts
  213. pub fn remove_all_shortcuts(&self) {
  214. self.shortcut_manager.remove_all()
  215. }
  216. /// Provide a callback to handle asset loading yourself.
  217. ///
  218. /// See [`use_asset_handle`](crate::use_asset_handle) for a convenient hook.
  219. pub async fn register_asset_handler<F: AssetFuture>(&self, f: impl AssetHandler<F>) -> usize {
  220. self.asset_handlers.register_handler(f).await
  221. }
  222. /// Removes an asset handler by its identifier.
  223. ///
  224. /// Returns `None` if the handler did not exist.
  225. pub async fn remove_asset_handler(&self, id: usize) -> Option<()> {
  226. self.asset_handlers.remove_handler(id).await
  227. }
  228. /// Push an objc view to the window
  229. #[cfg(target_os = "ios")]
  230. pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
  231. let window = self.webview.window();
  232. unsafe {
  233. use objc::runtime::Object;
  234. use objc::*;
  235. assert!(is_main_thread());
  236. let ui_view = window.ui_view() as *mut Object;
  237. let ui_view_frame: *mut Object = msg_send![ui_view, frame];
  238. let _: () = msg_send![view, setFrame: ui_view_frame];
  239. let _: () = msg_send![view, setAutoresizingMask: 31];
  240. let ui_view_controller = window.ui_view_controller() as *mut Object;
  241. let _: () = msg_send![ui_view_controller, setView: view];
  242. self.views.borrow_mut().push(ui_view);
  243. }
  244. }
  245. /// Pop an objc view from the window
  246. #[cfg(target_os = "ios")]
  247. pub fn pop_view(&self) {
  248. let window = self.webview.window();
  249. unsafe {
  250. use objc::runtime::Object;
  251. use objc::*;
  252. assert!(is_main_thread());
  253. if let Some(view) = self.views.borrow_mut().pop() {
  254. let ui_view_controller = window.ui_view_controller() as *mut Object;
  255. let _: () = msg_send![ui_view_controller, setView: view];
  256. }
  257. }
  258. }
  259. }
  260. #[derive(Debug, Clone)]
  261. pub struct UserWindowEvent(pub EventData, pub WindowId);
  262. #[derive(Debug, Clone)]
  263. pub enum EventData {
  264. Poll,
  265. Ipc(IpcMessage),
  266. #[cfg(all(feature = "hot-reload", debug_assertions))]
  267. HotReloadEvent(HotReloadMsg),
  268. NewWindow,
  269. CloseWindow,
  270. }
  271. #[cfg(target_os = "ios")]
  272. fn is_main_thread() -> bool {
  273. use objc::runtime::{Class, BOOL, NO};
  274. use objc::*;
  275. let cls = Class::get("NSThread").unwrap();
  276. let result: BOOL = unsafe { msg_send![cls, isMainThread] };
  277. result != NO
  278. }
  279. /// The unique identifier of a window event handler. This can be used to later remove the handler.
  280. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  281. pub struct WryEventHandlerId(usize);
  282. #[derive(Clone, Default)]
  283. pub(crate) struct WindowEventHandlers {
  284. handlers: Rc<RefCell<Slab<WryWindowEventHandlerInner>>>,
  285. }
  286. impl WindowEventHandlers {
  287. pub(crate) fn add(
  288. &self,
  289. window_id: WindowId,
  290. handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
  291. ) -> WryEventHandlerId {
  292. WryEventHandlerId(
  293. self.handlers
  294. .borrow_mut()
  295. .insert(WryWindowEventHandlerInner {
  296. window_id,
  297. handler: Box::new(handler),
  298. }),
  299. )
  300. }
  301. pub(crate) fn remove(&self, id: WryEventHandlerId) {
  302. self.handlers.borrow_mut().try_remove(id.0);
  303. }
  304. pub(crate) fn apply_event(
  305. &self,
  306. event: &Event<UserWindowEvent>,
  307. target: &EventLoopWindowTarget<UserWindowEvent>,
  308. ) {
  309. for (_, handler) in self.handlers.borrow_mut().iter_mut() {
  310. handler.apply_event(event, target);
  311. }
  312. }
  313. }
  314. struct WryWindowEventHandlerInner {
  315. window_id: WindowId,
  316. handler: WryEventHandlerCallback,
  317. }
  318. type WryEventHandlerCallback =
  319. Box<dyn FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static>;
  320. impl WryWindowEventHandlerInner {
  321. fn apply_event(
  322. &mut self,
  323. event: &Event<UserWindowEvent>,
  324. target: &EventLoopWindowTarget<UserWindowEvent>,
  325. ) {
  326. // if this event does not apply to the window this listener cares about, return
  327. if let Event::WindowEvent { window_id, .. } = event {
  328. if *window_id != self.window_id {
  329. return;
  330. }
  331. }
  332. (self.handler)(event, target)
  333. }
  334. }
  335. /// Get a closure that executes any JavaScript in the WebView context.
  336. pub fn use_wry_event_handler(
  337. cx: &ScopeState,
  338. handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
  339. ) -> &WryEventHandler {
  340. let desktop = use_window(cx);
  341. cx.use_hook(move || {
  342. let desktop = desktop.clone();
  343. let id = desktop.create_wry_event_handler(handler);
  344. WryEventHandler {
  345. handlers: desktop.event_handlers.clone(),
  346. id,
  347. }
  348. })
  349. }
  350. /// A wry event handler that is scoped to the current component and window. The event handler will only receive events for the window it was created for and global events.
  351. ///
  352. /// This will automatically be removed when the component is unmounted.
  353. pub struct WryEventHandler {
  354. handlers: WindowEventHandlers,
  355. /// The unique identifier of the event handler.
  356. pub id: WryEventHandlerId,
  357. }
  358. impl WryEventHandler {
  359. /// Remove the event handler.
  360. pub fn remove(&self) {
  361. self.handlers.remove(self.id);
  362. }
  363. }
  364. impl Drop for WryEventHandler {
  365. fn drop(&mut self) {
  366. self.handlers.remove(self.id);
  367. }
  368. }