desktop_context.rs 13 KB

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