1use 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::PendingWebview,
9 AssetRequest, Config, WindowCloseBehaviour, WryEventHandler,
10};
11use dioxus_core::{Callback, VirtualDom};
12use std::{
13 cell::Cell,
14 future::{Future, IntoFuture},
15 pin::Pin,
16 rc::{Rc, Weak},
17 sync::Arc,
18};
19use tao::{
20 event::Event,
21 event_loop::EventLoopWindowTarget,
22 window::{Fullscreen as WryFullscreen, Window, WindowId},
23};
24use wry::{RequestAsyncResponder, WebView};
25
26#[cfg(target_os = "ios")]
27use tao::platform::ios::WindowExtIOS;
28
29pub fn window() -> DesktopContext {
35 dioxus_core::consume_context()
36}
37
38pub type DesktopContext = Rc<DesktopService>;
40
41pub type WeakDesktopContext = Weak<DesktopService>;
45
46pub struct DesktopService {
59 pub webview: WebView,
61
62 pub window: Arc<Window>,
64
65 pub(crate) shared: Rc<SharedContext>,
66
67 pub(super) query: QueryEngine,
69 pub(crate) asset_handlers: AssetHandlerRegistry,
70 pub(crate) file_hover: NativeFileHover,
71 pub(crate) close_behaviour: Rc<Cell<WindowCloseBehaviour>>,
72
73 #[cfg(target_os = "ios")]
74 pub(crate) views: Rc<std::cell::RefCell<Vec<*mut objc::runtime::Object>>>,
75}
76
77impl std::ops::Deref for DesktopService {
79 type Target = Window;
80
81 fn deref(&self) -> &Self::Target {
82 &self.window
83 }
84}
85
86impl DesktopService {
87 pub(crate) fn new(
88 webview: WebView,
89 window: Arc<Window>,
90 shared: Rc<SharedContext>,
91 asset_handlers: AssetHandlerRegistry,
92 file_hover: NativeFileHover,
93 close_behaviour: WindowCloseBehaviour,
94 ) -> Self {
95 Self {
96 window,
97 webview,
98 shared,
99 asset_handlers,
100 file_hover,
101 close_behaviour: Rc::new(Cell::new(close_behaviour)),
102 query: Default::default(),
103 #[cfg(target_os = "ios")]
104 views: Default::default(),
105 }
106 }
107
108 pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> PendingDesktopContext {
142 let (window, context) = PendingWebview::new(dom, cfg);
143
144 self.shared
145 .proxy
146 .send_event(UserWindowEvent::NewWindow)
147 .unwrap();
148
149 self.shared.pending_webviews.borrow_mut().push(window);
150
151 context
152 }
153
154 pub fn drag(&self) {
163 if self.window.fullscreen().is_none() {
164 _ = self.window.drag_window();
165 }
166 }
167
168 pub fn toggle_maximized(&self) {
170 self.window.set_maximized(!self.window.is_maximized())
171 }
172
173 pub fn set_close_behavior(&self, behaviour: WindowCloseBehaviour) {
178 self.close_behaviour.set(behaviour);
179 }
180
181 pub fn close(&self) {
183 let _ = self
184 .shared
185 .proxy
186 .send_event(UserWindowEvent::CloseWindow(self.id()));
187 }
188
189 pub fn close_window(&self, id: WindowId) {
191 let _ = self
192 .shared
193 .proxy
194 .send_event(UserWindowEvent::CloseWindow(id));
195 }
196
197 pub fn set_fullscreen(&self, fullscreen: bool) {
199 if let Some(handle) = &self.window.current_monitor() {
200 self.window.set_fullscreen(
201 fullscreen.then_some(WryFullscreen::Borderless(Some(handle.clone()))),
202 );
203 }
204 }
205
206 pub fn print(&self) {
208 if let Err(e) = self.webview.print() {
209 tracing::warn!("Open print modal failed: {e}");
210 }
211 }
212
213 pub fn set_zoom_level(&self, level: f64) {
215 if let Err(e) = self.webview.zoom(level) {
216 tracing::warn!("Set webview zoom failed: {e}");
217 }
218 }
219
220 pub fn devtool(&self) {
222 #[cfg(debug_assertions)]
223 self.webview.open_devtools();
224
225 #[cfg(not(debug_assertions))]
226 tracing::warn!("Devtools are disabled in release builds");
227 }
228
229 pub fn create_wry_event_handler(
234 &self,
235 handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
236 ) -> WryEventHandler {
237 self.shared.event_handlers.add(self.window.id(), handler)
238 }
239
240 pub fn remove_wry_event_handler(&self, id: WryEventHandler) {
242 self.shared.event_handlers.remove(id)
243 }
244
245 pub fn create_shortcut(
249 &self,
250 hotkey: HotKey,
251 callback: impl FnMut(HotKeyState) + 'static,
252 ) -> Result<ShortcutHandle, ShortcutRegistryError> {
253 self.shared
254 .shortcut_manager
255 .add_shortcut(hotkey, Box::new(callback))
256 }
257
258 pub fn remove_shortcut(&self, id: ShortcutHandle) {
260 self.shared.shortcut_manager.remove_shortcut(id)
261 }
262
263 pub fn remove_all_shortcuts(&self) {
265 self.shared.shortcut_manager.remove_all()
266 }
267
268 pub fn register_asset_handler(
276 &self,
277 name: String,
278 handler: impl Fn(AssetRequest, RequestAsyncResponder) + 'static,
279 ) {
280 self.asset_handlers
281 .register_handler(name, Callback::new(move |(req, resp)| handler(req, resp)))
282 }
283
284 pub fn remove_asset_handler(&self, name: &str) -> Option<()> {
288 self.asset_handlers.remove_handler(name).map(|_| ())
289 }
290
291 #[cfg(target_os = "ios")]
293 pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
294 let window = &self.window;
295
296 unsafe {
297 use objc::runtime::Object;
298 use objc::*;
299 assert!(is_main_thread());
300 let ui_view = window.ui_view() as *mut Object;
301 let ui_view_frame: *mut Object = msg_send![ui_view, frame];
302 let _: () = msg_send![view, setFrame: ui_view_frame];
303 let _: () = msg_send![view, setAutoresizingMask: 31];
304
305 let ui_view_controller = window.ui_view_controller() as *mut Object;
306 let _: () = msg_send![ui_view_controller, setView: view];
307 self.views.borrow_mut().push(ui_view);
308 }
309 }
310
311 #[cfg(target_os = "ios")]
313 pub fn pop_view(&self) {
314 let window = &self.window;
315
316 unsafe {
317 use objc::runtime::Object;
318 use objc::*;
319 assert!(is_main_thread());
320 if let Some(view) = self.views.borrow_mut().pop() {
321 let ui_view_controller = window.ui_view_controller() as *mut Object;
322 let _: () = msg_send![ui_view_controller, setView: view];
323 }
324 }
325 }
326}
327
328#[cfg(target_os = "ios")]
329fn is_main_thread() -> bool {
330 use objc::runtime::{Class, BOOL, NO};
331 use objc::*;
332
333 let cls = Class::get("NSThread").unwrap();
334 let result: BOOL = unsafe { msg_send![cls, isMainThread] };
335 result != NO
336}
337
338pub struct PendingDesktopContext {
358 pub(crate) receiver: futures_channel::oneshot::Receiver<DesktopContext>,
359}
360
361impl PendingDesktopContext {
362 pub async fn resolve(self) -> DesktopContext {
364 self.try_resolve()
365 .await
366 .expect("Failed to resolve pending desktop context")
367 }
368
369 pub async fn try_resolve(self) -> Result<DesktopContext, futures_channel::oneshot::Canceled> {
371 self.receiver.await
372 }
373}
374
375impl IntoFuture for PendingDesktopContext {
376 type Output = DesktopContext;
377
378 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
379
380 fn into_future(self) -> Self::IntoFuture {
381 Box::pin(self.resolve())
382 }
383}