desktop_context.rs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. use crate::{
  2. app::SharedContext,
  3. assets::AssetHandlerRegistry,
  4. file_upload::NativeFileHover,
  5. ipc::UserWindowEvent,
  6. query::QueryEngine,
  7. shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
  8. webview::WebviewInstance,
  9. AssetRequest, Config, WryEventHandler,
  10. };
  11. use dioxus_core::{
  12. prelude::{Callback, ScopeId},
  13. VirtualDom,
  14. };
  15. use std::rc::{Rc, Weak};
  16. use tao::{
  17. event::Event,
  18. event_loop::EventLoopWindowTarget,
  19. window::{Fullscreen as WryFullscreen, Window, WindowId},
  20. };
  21. use wry::{RequestAsyncResponder, WebView};
  22. #[cfg(target_os = "ios")]
  23. use tao::platform::ios::WindowExtIOS;
  24. /// Get an imperative handle to the current window without using a hook
  25. ///
  26. /// ## Panics
  27. ///
  28. /// This function will panic if it is called outside of the context of a Dioxus App.
  29. pub fn window() -> DesktopContext {
  30. dioxus_core::prelude::consume_context()
  31. }
  32. /// A handle to the [`DesktopService`] that can be passed around.
  33. pub type DesktopContext = Rc<DesktopService>;
  34. /// A weak handle to the [`DesktopService`] to ensure safe passing.
  35. /// The problem without this is that the tao window is never dropped and therefore cannot be closed.
  36. /// This was due to the Rc that had still references because of multiple copies when creating a webview.
  37. pub type WeakDesktopContext = Weak<DesktopService>;
  38. /// An imperative interface to the current window.
  39. ///
  40. /// To get a handle to the current window, use the [`window`] function.
  41. ///
  42. ///
  43. /// # Example
  44. ///
  45. /// you can use `cx.consume_context::<DesktopContext>` to get this context
  46. ///
  47. /// ```rust, ignore
  48. /// let desktop = cx.consume_context::<DesktopContext>().unwrap();
  49. /// ```
  50. pub struct DesktopService {
  51. /// The wry/tao proxy to the current window
  52. pub webview: WebView,
  53. /// The tao window itself
  54. pub window: Window,
  55. pub(crate) shared: Rc<SharedContext>,
  56. /// The receiver for queries about the current window
  57. pub(super) query: QueryEngine,
  58. pub(crate) asset_handlers: AssetHandlerRegistry,
  59. pub(crate) file_hover: NativeFileHover,
  60. #[cfg(target_os = "ios")]
  61. pub(crate) views: Rc<std::cell::RefCell<Vec<*mut objc::runtime::Object>>>,
  62. }
  63. /// A smart pointer to the current window.
  64. impl std::ops::Deref for DesktopService {
  65. type Target = Window;
  66. fn deref(&self) -> &Self::Target {
  67. &self.window
  68. }
  69. }
  70. impl DesktopService {
  71. pub(crate) fn new(
  72. webview: WebView,
  73. window: Window,
  74. shared: Rc<SharedContext>,
  75. asset_handlers: AssetHandlerRegistry,
  76. file_hover: NativeFileHover,
  77. ) -> Self {
  78. Self {
  79. window,
  80. webview,
  81. shared,
  82. asset_handlers,
  83. file_hover,
  84. query: Default::default(),
  85. #[cfg(target_os = "ios")]
  86. views: Default::default(),
  87. }
  88. }
  89. /// Create a new window using the props and window builder
  90. ///
  91. /// Returns the webview handle for the new window.
  92. ///
  93. /// You can use this to control other windows from the current window.
  94. ///
  95. /// Be careful to not create a cycle of windows, or you might leak memory.
  96. pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> WeakDesktopContext {
  97. let window = WebviewInstance::new(cfg, dom, self.shared.clone());
  98. let cx = window.dom.in_runtime(|| {
  99. ScopeId::ROOT
  100. .consume_context::<Rc<DesktopService>>()
  101. .unwrap()
  102. });
  103. self.shared
  104. .proxy
  105. .send_event(UserWindowEvent::NewWindow)
  106. .unwrap();
  107. self.shared.pending_webviews.borrow_mut().push(window);
  108. Rc::downgrade(&cx)
  109. }
  110. /// trigger the drag-window event
  111. ///
  112. /// Moves the window with the left mouse button until the button is released.
  113. ///
  114. /// you need use it in `onmousedown` event:
  115. /// ```rust, ignore
  116. /// onmousedown: move |_| { desktop.drag_window(); }
  117. /// ```
  118. pub fn drag(&self) {
  119. if self.window.fullscreen().is_none() {
  120. _ = self.window.drag_window();
  121. }
  122. }
  123. /// Toggle whether the window is maximized or not
  124. pub fn toggle_maximized(&self) {
  125. self.window.set_maximized(!self.window.is_maximized())
  126. }
  127. /// Close this window
  128. pub fn close(&self) {
  129. let _ = self
  130. .shared
  131. .proxy
  132. .send_event(UserWindowEvent::CloseWindow(self.id()));
  133. }
  134. /// Close a particular window, given its ID
  135. pub fn close_window(&self, id: WindowId) {
  136. let _ = self
  137. .shared
  138. .proxy
  139. .send_event(UserWindowEvent::CloseWindow(id));
  140. }
  141. /// change window to fullscreen
  142. pub fn set_fullscreen(&self, fullscreen: bool) {
  143. if let Some(handle) = &self.window.current_monitor() {
  144. self.window.set_fullscreen(
  145. fullscreen.then_some(WryFullscreen::Borderless(Some(handle.clone()))),
  146. );
  147. }
  148. }
  149. /// launch print modal
  150. pub fn print(&self) {
  151. if let Err(e) = self.webview.print() {
  152. tracing::warn!("Open print modal failed: {e}");
  153. }
  154. }
  155. /// Set the zoom level of the webview
  156. pub fn set_zoom_level(&self, level: f64) {
  157. if let Err(e) = self.webview.zoom(level) {
  158. tracing::warn!("Set webview zoom failed: {e}");
  159. }
  160. }
  161. /// opens DevTool window
  162. pub fn devtool(&self) {
  163. #[cfg(debug_assertions)]
  164. self.webview.open_devtools();
  165. #[cfg(not(debug_assertions))]
  166. tracing::warn!("Devtools are disabled in release builds");
  167. }
  168. /// Create a wry event handler that listens for wry events.
  169. /// This event handler is scoped to the currently active window and will only receive events that are either global or related to the current window.
  170. ///
  171. /// The id this function returns can be used to remove the event handler with [`Self::remove_wry_event_handler`]
  172. pub fn create_wry_event_handler(
  173. &self,
  174. handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
  175. ) -> WryEventHandler {
  176. self.shared.event_handlers.add(self.window.id(), handler)
  177. }
  178. /// Remove a wry event handler created with [`Self::create_wry_event_handler`]
  179. pub fn remove_wry_event_handler(&self, id: WryEventHandler) {
  180. self.shared.event_handlers.remove(id)
  181. }
  182. /// Create a global shortcut
  183. ///
  184. /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information.
  185. pub fn create_shortcut(
  186. &self,
  187. hotkey: HotKey,
  188. callback: impl FnMut(HotKeyState) + 'static,
  189. ) -> Result<ShortcutHandle, ShortcutRegistryError> {
  190. self.shared
  191. .shortcut_manager
  192. .add_shortcut(hotkey, Box::new(callback))
  193. }
  194. /// Remove a global shortcut
  195. pub fn remove_shortcut(&self, id: ShortcutHandle) {
  196. self.shared.shortcut_manager.remove_shortcut(id)
  197. }
  198. /// Remove all global shortcuts
  199. pub fn remove_all_shortcuts(&self) {
  200. self.shared.shortcut_manager.remove_all()
  201. }
  202. /// Provide a callback to handle asset loading yourself.
  203. /// If the ScopeId isn't provided, defaults to a global handler.
  204. /// Note that the handler is namespaced by name, not ScopeId.
  205. ///
  206. /// When the component is dropped, the handler is removed.
  207. ///
  208. /// See [`crate::use_asset_handler`] for a convenient hook.
  209. pub fn register_asset_handler(
  210. &self,
  211. name: String,
  212. handler: impl Fn(AssetRequest, RequestAsyncResponder) + 'static,
  213. ) {
  214. self.asset_handlers
  215. .register_handler(name, Callback::new(move |(req, resp)| handler(req, resp)))
  216. }
  217. /// Removes an asset handler by its identifier.
  218. ///
  219. /// Returns `None` if the handler did not exist.
  220. pub fn remove_asset_handler(&self, name: &str) -> Option<()> {
  221. self.asset_handlers.remove_handler(name).map(|_| ())
  222. }
  223. /// Push an objc view to the window
  224. #[cfg(target_os = "ios")]
  225. pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
  226. let window = &self.window;
  227. unsafe {
  228. use objc::runtime::Object;
  229. use objc::*;
  230. assert!(is_main_thread());
  231. let ui_view = window.ui_view() as *mut Object;
  232. let ui_view_frame: *mut Object = msg_send![ui_view, frame];
  233. let _: () = msg_send![view, setFrame: ui_view_frame];
  234. let _: () = msg_send![view, setAutoresizingMask: 31];
  235. let ui_view_controller = window.ui_view_controller() as *mut Object;
  236. let _: () = msg_send![ui_view_controller, setView: view];
  237. self.views.borrow_mut().push(ui_view);
  238. }
  239. }
  240. /// Pop an objc view from the window
  241. #[cfg(target_os = "ios")]
  242. pub fn pop_view(&self) {
  243. let window = &self.window;
  244. unsafe {
  245. use objc::runtime::Object;
  246. use objc::*;
  247. assert!(is_main_thread());
  248. if let Some(view) = self.views.borrow_mut().pop() {
  249. let ui_view_controller = window.ui_view_controller() as *mut Object;
  250. let _: () = msg_send![ui_view_controller, setView: view];
  251. }
  252. }
  253. }
  254. }
  255. #[cfg(target_os = "ios")]
  256. fn is_main_thread() -> bool {
  257. use objc::runtime::{Class, BOOL, NO};
  258. use objc::*;
  259. let cls = Class::get("NSThread").unwrap();
  260. let result: BOOL = unsafe { msg_send![cls, isMainThread] };
  261. result != NO
  262. }