desktop_context.rs 13 KB

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