desktop_context.rs 13 KB

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