app.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. pub use crate::assets::{AssetFuture, AssetHandler, AssetRequest, AssetResponse};
  2. pub use crate::cfg::{Config, WindowCloseBehaviour};
  3. pub use crate::desktop_context::DesktopContext;
  4. pub use crate::desktop_context::{window, DesktopService, WryEventHandler, WryEventHandlerId};
  5. use crate::edits::{EditQueue, WebviewQueue};
  6. use crate::element::DesktopElement;
  7. use crate::eval::init_eval;
  8. use crate::events::{IpcMessage, IpcMethod};
  9. use crate::file_upload;
  10. use crate::hooks::*;
  11. use crate::query::QueryResult;
  12. use crate::shortcut::GlobalHotKeyEvent;
  13. use crate::shortcut::ShortcutRegistry;
  14. pub use crate::shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
  15. use crate::{
  16. desktop_context::{EventData, UserWindowEvent, WindowEventHandlers},
  17. webview::WebviewHandler,
  18. };
  19. use dioxus_core::*;
  20. use dioxus_html::{event_bubbles, MountedData};
  21. use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
  22. use dioxus_interpreter_js::binary_protocol::Channel;
  23. use futures_util::{pin_mut, FutureExt};
  24. use global_hotkey::{
  25. hotkey::{Code, HotKey, Modifiers},
  26. GlobalHotKeyManager,
  27. };
  28. use rustc_hash::FxHashMap;
  29. use std::rc::Rc;
  30. use std::sync::atomic::AtomicU16;
  31. use std::task::Waker;
  32. use std::{borrow::Borrow, cell::Cell};
  33. use std::{collections::HashMap, sync::Arc};
  34. pub use tao::dpi::{LogicalSize, PhysicalSize};
  35. use tao::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
  36. pub use tao::window::WindowBuilder;
  37. use tao::window::WindowId;
  38. use tao::{
  39. event::{Event, StartCause, WindowEvent},
  40. event_loop::ControlFlow,
  41. };
  42. use tao::{event_loop::EventLoopBuilder, window::Window};
  43. use tokio::runtime::Builder;
  44. pub use wry;
  45. use wry::WebContext;
  46. use wry::WebView;
  47. pub struct App<P> {
  48. // move the props into a cell so we can pop it out later to create the first window
  49. // iOS panics if we create a window before the event loop is started
  50. pub(crate) props: Rc<Cell<Option<P>>>,
  51. pub(crate) cfg: Rc<Cell<Option<Config>>>,
  52. pub(crate) root: Component<P>,
  53. pub(crate) webviews: HashMap<WindowId, WebviewHandler>,
  54. pub(crate) event_handlers: WindowEventHandlers,
  55. pub(crate) queue: WebviewQueue,
  56. pub(crate) shortcut_manager: ShortcutRegistry,
  57. pub(crate) global_hotkey_channel: crossbeam_channel::Receiver<GlobalHotKeyEvent>,
  58. pub(crate) proxy: EventLoopProxy<UserWindowEvent>,
  59. pub(crate) window_behavior: WindowCloseBehaviour,
  60. pub(crate) control_flow: ControlFlow,
  61. pub(crate) is_visible_before_start: bool,
  62. }
  63. impl<P: 'static> App<P> {
  64. pub fn new(cfg: Config, props: P, root: Component<P>) -> (EventLoop<UserWindowEvent>, Self) {
  65. let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
  66. let mut app = Self {
  67. root,
  68. window_behavior: cfg.last_window_close_behaviour,
  69. is_visible_before_start: true,
  70. webviews: HashMap::new(),
  71. event_handlers: WindowEventHandlers::default(),
  72. queue: WebviewQueue::default(),
  73. shortcut_manager: ShortcutRegistry::new(),
  74. global_hotkey_channel: GlobalHotKeyEvent::receiver().clone(),
  75. proxy: event_loop.create_proxy(),
  76. props: Rc::new(Cell::new(Some(props))),
  77. cfg: Rc::new(Cell::new(Some(cfg))),
  78. control_flow: ControlFlow::Wait,
  79. };
  80. #[cfg(all(feature = "hot-reload", debug_assertions))]
  81. app.connect_hotreload();
  82. (event_loop, app)
  83. }
  84. pub fn tick(
  85. &mut self,
  86. window_event: &Event<'_, UserWindowEvent>,
  87. event_loop: &EventLoopWindowTarget<UserWindowEvent>,
  88. ) {
  89. self.control_flow = ControlFlow::Wait;
  90. self.event_handlers.apply_event(window_event, event_loop);
  91. _ = self
  92. .global_hotkey_channel
  93. .try_recv()
  94. .map(|event| self.shortcut_manager.call_handlers(event));
  95. }
  96. #[cfg(all(feature = "hot-reload", debug_assertions))]
  97. pub fn connect_hotreload(&mut self) {
  98. let proxy = self.proxy.clone();
  99. dioxus_hot_reload::connect({
  100. move |template| {
  101. let _ = proxy.send_event(UserWindowEvent(
  102. EventData::HotReloadEvent(template),
  103. unsafe { WindowId::dummy() },
  104. ));
  105. }
  106. });
  107. }
  108. //
  109. pub fn handle_new_window(&mut self) {
  110. for handler in self.queue.borrow_mut().drain(..) {
  111. let id = handler.desktop_context.window.id();
  112. self.webviews.insert(id, handler);
  113. _ = self.proxy.send_event(UserWindowEvent(EventData::Poll, id));
  114. }
  115. }
  116. pub fn handle_close_requested(&mut self, id: WindowId) {
  117. use WindowCloseBehaviour::*;
  118. match self.window_behavior {
  119. LastWindowExitsApp => {
  120. self.webviews.remove(&id);
  121. if self.webviews.is_empty() {
  122. self.control_flow = ControlFlow::Exit
  123. }
  124. }
  125. LastWindowHides => {
  126. let Some(webview) = self.webviews.get(&id) else {
  127. return;
  128. };
  129. hide_app_window(&webview.desktop_context.webview);
  130. }
  131. CloseWindow => {
  132. self.webviews.remove(&id);
  133. }
  134. }
  135. }
  136. pub fn window_destroyed(&mut self, id: WindowId) {
  137. self.webviews.remove(&id);
  138. if matches!(
  139. self.window_behavior,
  140. WindowCloseBehaviour::LastWindowExitsApp
  141. ) && self.webviews.is_empty()
  142. {
  143. self.control_flow = ControlFlow::Exit
  144. }
  145. }
  146. pub fn handle_start_cause_init(&mut self, target: &EventLoopWindowTarget<UserWindowEvent>) {
  147. let props = self.props.take().unwrap();
  148. let cfg = self.cfg.take().unwrap();
  149. let dom = VirtualDom::new_with_props(self.root, props);
  150. self.is_visible_before_start = cfg.window.window.visible;
  151. let handler = crate::webview::create_new_window(
  152. cfg,
  153. target,
  154. &self.proxy,
  155. dom,
  156. &self.queue,
  157. &self.event_handlers,
  158. self.shortcut_manager.clone(),
  159. );
  160. let id = handler.desktop_context.window.id();
  161. self.webviews.insert(id, handler);
  162. _ = self.proxy.send_event(UserWindowEvent(EventData::Poll, id));
  163. }
  164. pub fn handle_browser_open(&mut self, msg: IpcMessage) {
  165. if let Some(temp) = msg.params().as_object() {
  166. if temp.contains_key("href") {
  167. let open = webbrowser::open(temp["href"].as_str().unwrap());
  168. if let Err(e) = open {
  169. tracing::error!("Open Browser error: {:?}", e);
  170. }
  171. }
  172. }
  173. }
  174. pub fn handle_initialize_msg(&mut self, id: WindowId) {
  175. let view = self.webviews.get_mut(&id).unwrap();
  176. view.desktop_context.send_edits(view.dom.rebuild());
  177. view.desktop_context
  178. .window
  179. .set_visible(self.is_visible_before_start);
  180. }
  181. pub fn handle_close_msg(&mut self, id: WindowId) {
  182. self.webviews.remove(&id);
  183. if self.webviews.is_empty() {
  184. self.control_flow = ControlFlow::Exit
  185. }
  186. }
  187. pub fn handle_poll_msg(&mut self, id: WindowId) {
  188. self.poll_vdom(id);
  189. }
  190. pub fn handle_query_msg(&mut self, msg: IpcMessage, id: WindowId) {
  191. let Ok(result) = serde_json::from_value::<QueryResult>(msg.params()) else {
  192. return;
  193. };
  194. let Some(view) = self.webviews.get(&id) else {
  195. return;
  196. };
  197. view.dom
  198. .base_scope()
  199. .consume_context::<DesktopContext>()
  200. .unwrap()
  201. .query
  202. .send(result);
  203. }
  204. pub fn handle_user_event_msg(&mut self, msg: IpcMessage, id: WindowId) {
  205. let params = msg.params();
  206. let evt = match serde_json::from_value::<HtmlEvent>(params) {
  207. Ok(value) => value,
  208. Err(err) => {
  209. tracing::error!("Error parsing user_event: {:?}", err);
  210. return;
  211. }
  212. };
  213. let HtmlEvent {
  214. element,
  215. name,
  216. bubbles,
  217. data,
  218. } = evt;
  219. let view = self.webviews.get_mut(&id).unwrap();
  220. // check for a mounted event placeholder and replace it with a desktop specific element
  221. let as_any = if let dioxus_html::EventData::Mounted = &data {
  222. let query = view
  223. .dom
  224. .base_scope()
  225. .consume_context::<DesktopContext>()
  226. .unwrap()
  227. .query
  228. .clone();
  229. let element = DesktopElement::new(element, view.desktop_context.clone(), query);
  230. Rc::new(MountedData::new(element))
  231. } else {
  232. data.into_any()
  233. };
  234. view.dom.handle_event(&name, as_any, element, bubbles);
  235. view.desktop_context.send_edits(view.dom.render_immediate());
  236. }
  237. #[cfg(all(feature = "hot-reload", debug_assertions))]
  238. pub fn handle_hot_reload_msg(&mut self, msg: dioxus_hot_reload::HotReloadMsg) {
  239. match msg {
  240. dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
  241. for webview in self.webviews.values_mut() {
  242. webview.dom.replace_template(template);
  243. }
  244. let ids = self.webviews.keys().copied().collect::<Vec<_>>();
  245. for id in ids {
  246. self.poll_vdom(id);
  247. }
  248. }
  249. dioxus_hot_reload::HotReloadMsg::Shutdown => {
  250. self.control_flow = ControlFlow::Exit;
  251. }
  252. }
  253. }
  254. pub fn handle_file_dialog_msg(&mut self, msg: IpcMessage, window: WindowId) {
  255. if let Ok(file_diolog) =
  256. serde_json::from_value::<file_upload::FileDialogRequest>(msg.params())
  257. {
  258. let id = ElementId(file_diolog.target);
  259. let event_name = &file_diolog.event;
  260. let event_bubbles = file_diolog.bubbles;
  261. let files = file_upload::get_file_event(&file_diolog);
  262. let data = Rc::new(FormData {
  263. value: Default::default(),
  264. values: Default::default(),
  265. files: Some(Arc::new(NativeFileEngine::new(files))),
  266. });
  267. let view = self.webviews.get_mut(&window).unwrap();
  268. if event_name == "change&input" {
  269. view.dom
  270. .handle_event("input", data.clone(), id, event_bubbles);
  271. view.dom.handle_event("change", data, id, event_bubbles);
  272. } else {
  273. view.dom.handle_event(event_name, data, id, event_bubbles);
  274. }
  275. view.desktop_context.send_edits(view.dom.render_immediate());
  276. }
  277. }
  278. /// Poll the virtualdom until it's pending
  279. ///
  280. /// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again
  281. ///
  282. /// All IO is done on the tokio runtime we started earlier
  283. pub fn poll_vdom(&mut self, id: WindowId) {
  284. let view = self.webviews.get_mut(&id).unwrap();
  285. let mut cx = std::task::Context::from_waker(&view.waker);
  286. loop {
  287. {
  288. let fut = view.dom.wait_for_work();
  289. pin_mut!(fut);
  290. match fut.poll_unpin(&mut cx) {
  291. std::task::Poll::Ready(_) => {}
  292. std::task::Poll::Pending => break,
  293. }
  294. }
  295. view.desktop_context.send_edits(view.dom.render_immediate());
  296. }
  297. }
  298. }
  299. /// Different hide implementations per platform
  300. #[allow(unused)]
  301. pub fn hide_app_window(webview: &WebView) {
  302. #[cfg(target_os = "windows")]
  303. {
  304. use wry::application::platform::windows::WindowExtWindows;
  305. window.set_visible(false);
  306. window.set_skip_taskbar(true);
  307. }
  308. #[cfg(target_os = "linux")]
  309. {
  310. use wry::application::platform::unix::WindowExtUnix;
  311. window.set_visible(false);
  312. }
  313. #[cfg(target_os = "macos")]
  314. {
  315. // window.set_visible(false); has the wrong behaviour on macOS
  316. // It will hide the window but not show it again when the user switches
  317. // back to the app. `NSApplication::hide:` has the correct behaviour
  318. use objc::runtime::Object;
  319. use objc::{msg_send, sel, sel_impl};
  320. objc::rc::autoreleasepool(|| unsafe {
  321. let app: *mut Object = msg_send![objc::class!(NSApplication), sharedApplication];
  322. let nil = std::ptr::null_mut::<Object>();
  323. let _: () = msg_send![app, hide: nil];
  324. });
  325. }
  326. }