app.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. use crate::{
  2. config::{Config, WindowCloseBehaviour},
  3. edits::EditWebsocket,
  4. event_handlers::WindowEventHandlers,
  5. file_upload::{DesktopFileUploadForm, FileDialogRequest, NativeFileEngine},
  6. ipc::{IpcMessage, UserWindowEvent},
  7. query::QueryResult,
  8. shortcut::ShortcutRegistry,
  9. webview::{PendingWebview, WebviewInstance},
  10. };
  11. use dioxus_core::{ElementId, ScopeId, VirtualDom};
  12. use dioxus_history::History;
  13. use dioxus_html::PlatformEventData;
  14. use std::{
  15. any::Any,
  16. cell::{Cell, RefCell},
  17. collections::HashMap,
  18. rc::Rc,
  19. sync::Arc,
  20. time::Duration,
  21. };
  22. use tao::{
  23. dpi::PhysicalSize,
  24. event::Event,
  25. event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
  26. window::WindowId,
  27. };
  28. /// The single top-level object that manages all the running windows, assets, shortcuts, etc
  29. pub(crate) struct App {
  30. // move the props into a cell so we can pop it out later to create the first window
  31. // iOS panics if we create a window before the event loop is started, so we toss them into a cell
  32. pub(crate) unmounted_dom: Cell<Option<VirtualDom>>,
  33. pub(crate) cfg: Cell<Option<Config>>,
  34. // Stuff we need mutable access to
  35. pub(crate) control_flow: ControlFlow,
  36. pub(crate) is_visible_before_start: bool,
  37. pub(crate) exit_on_last_window_close: bool,
  38. pub(crate) webviews: HashMap<WindowId, WebviewInstance>,
  39. pub(crate) float_all: bool,
  40. pub(crate) show_devtools: bool,
  41. /// This single blob of state is shared between all the windows so they have access to the runtime state
  42. ///
  43. /// This includes stuff like the event handlers, shortcuts, etc as well as ways to modify *other* windows
  44. pub(crate) shared: Rc<SharedContext>,
  45. }
  46. /// A bundle of state shared between all the windows, providing a way for us to communicate with running webview.
  47. pub(crate) struct SharedContext {
  48. pub(crate) event_handlers: WindowEventHandlers,
  49. pub(crate) pending_webviews: RefCell<Vec<PendingWebview>>,
  50. pub(crate) shortcut_manager: ShortcutRegistry,
  51. pub(crate) proxy: EventLoopProxy<UserWindowEvent>,
  52. pub(crate) target: EventLoopWindowTarget<UserWindowEvent>,
  53. pub(crate) websocket: EditWebsocket,
  54. }
  55. impl App {
  56. pub fn new(mut cfg: Config, virtual_dom: VirtualDom) -> (EventLoop<UserWindowEvent>, Self) {
  57. let event_loop = cfg
  58. .event_loop
  59. .take()
  60. .unwrap_or_else(|| EventLoopBuilder::<UserWindowEvent>::with_user_event().build());
  61. let app = Self {
  62. exit_on_last_window_close: cfg.exit_on_last_window_close,
  63. is_visible_before_start: true,
  64. webviews: HashMap::new(),
  65. control_flow: ControlFlow::Wait,
  66. unmounted_dom: Cell::new(Some(virtual_dom)),
  67. float_all: false,
  68. show_devtools: false,
  69. cfg: Cell::new(Some(cfg)),
  70. shared: Rc::new(SharedContext {
  71. event_handlers: WindowEventHandlers::default(),
  72. pending_webviews: Default::default(),
  73. shortcut_manager: ShortcutRegistry::new(),
  74. proxy: event_loop.create_proxy(),
  75. target: event_loop.clone(),
  76. websocket: EditWebsocket::start(),
  77. }),
  78. };
  79. // Set the event converter
  80. dioxus_html::set_event_converter(Box::new(crate::events::SerializedHtmlEventConverter));
  81. // Wire up the global hotkey handler
  82. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  83. app.set_global_hotkey_handler();
  84. // Wire up the menubar receiver - this way any component can key into the menubar actions
  85. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  86. app.set_menubar_receiver();
  87. // Wire up the tray icon receiver - this way any component can key into the menubar actions
  88. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  89. app.set_tray_icon_receiver();
  90. // Allow hotreloading to work - but only in debug mode
  91. #[cfg(all(feature = "devtools", debug_assertions))]
  92. app.connect_hotreload();
  93. #[cfg(debug_assertions)]
  94. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  95. app.connect_preserve_window_state_handler();
  96. (event_loop, app)
  97. }
  98. pub fn tick(&mut self, window_event: &Event<'_, UserWindowEvent>) {
  99. self.control_flow = ControlFlow::Wait;
  100. self.shared
  101. .event_handlers
  102. .apply_event(window_event, &self.shared.target);
  103. }
  104. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  105. pub fn handle_global_hotkey(&self, event: global_hotkey::GlobalHotKeyEvent) {
  106. self.shared.shortcut_manager.call_handlers(event);
  107. }
  108. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  109. pub fn handle_menu_event(&mut self, event: muda::MenuEvent) {
  110. match event.id().0.as_str() {
  111. "dioxus-float-top" => {
  112. for webview in self.webviews.values() {
  113. webview
  114. .desktop_context
  115. .window
  116. .set_always_on_top(self.float_all);
  117. }
  118. self.float_all = !self.float_all;
  119. }
  120. "dioxus-toggle-dev-tools" => {
  121. self.show_devtools = !self.show_devtools;
  122. for webview in self.webviews.values() {
  123. let wv = &webview.desktop_context.webview;
  124. if self.show_devtools {
  125. wv.open_devtools();
  126. } else {
  127. wv.close_devtools();
  128. }
  129. }
  130. }
  131. _ => (),
  132. }
  133. }
  134. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  135. pub fn handle_tray_menu_event(&mut self, event: tray_icon::menu::MenuEvent) {
  136. _ = event;
  137. }
  138. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  139. pub fn handle_tray_icon_event(&mut self, event: tray_icon::TrayIconEvent) {
  140. if let tray_icon::TrayIconEvent::Click {
  141. id: _,
  142. position: _,
  143. rect: _,
  144. button,
  145. button_state: _,
  146. } = event
  147. {
  148. if button == tray_icon::MouseButton::Left {
  149. for webview in self.webviews.values() {
  150. webview.desktop_context.window.set_visible(true);
  151. webview.desktop_context.window.set_focus();
  152. }
  153. }
  154. }
  155. }
  156. #[cfg(all(feature = "devtools", debug_assertions))]
  157. pub fn connect_hotreload(&self) {
  158. let proxy = self.shared.proxy.clone();
  159. dioxus_devtools::connect(move |msg| {
  160. _ = proxy.send_event(UserWindowEvent::HotReloadEvent(msg));
  161. })
  162. }
  163. pub fn handle_new_window(&mut self) {
  164. for pending_webview in self.shared.pending_webviews.borrow_mut().drain(..) {
  165. let window = pending_webview.create_window(&self.shared);
  166. let id = window.desktop_context.window.id();
  167. self.webviews.insert(id, window);
  168. _ = self.shared.proxy.send_event(UserWindowEvent::Poll(id));
  169. }
  170. }
  171. pub fn handle_close_requested(&mut self, id: WindowId) {
  172. let Some(window) = self.webviews.get(&id) else {
  173. // If the window is not found, we can just return
  174. return;
  175. };
  176. match window.desktop_context.close_behaviour.get() {
  177. // If the window is just set to hide when closed, we can just hide it
  178. WindowCloseBehaviour::WindowHides => {
  179. window.desktop_context.window.set_visible(false);
  180. }
  181. // If the window is set to close, we can remove it from the list of webviews
  182. // If the app is set to exit when the last window closes, we should also exit the app
  183. WindowCloseBehaviour::WindowCloses => {
  184. #[cfg(debug_assertions)]
  185. self.persist_window_state();
  186. self.webviews.remove(&id);
  187. if self.exit_on_last_window_close && self.webviews.is_empty() {
  188. self.control_flow = ControlFlow::Exit
  189. }
  190. }
  191. };
  192. }
  193. pub fn window_destroyed(&mut self, id: WindowId) {
  194. self.webviews.remove(&id);
  195. if self.exit_on_last_window_close && self.webviews.is_empty() {
  196. self.control_flow = ControlFlow::Exit
  197. }
  198. }
  199. pub fn resize_window(&self, id: WindowId, size: PhysicalSize<u32>) {
  200. // TODO: the app layer should avoid directly manipulating the webview webview instance internals.
  201. // Window creation and modification is the responsibility of the webview instance so it makes sense to
  202. // encapsulate that there.
  203. if let Some(webview) = self.webviews.get(&id) {
  204. use wry::Rect;
  205. _ = webview.desktop_context.webview.set_bounds(Rect {
  206. position: wry::dpi::Position::Logical(wry::dpi::LogicalPosition::new(0.0, 0.0)),
  207. size: wry::dpi::Size::Physical(wry::dpi::PhysicalSize::new(
  208. size.width,
  209. size.height,
  210. )),
  211. });
  212. }
  213. }
  214. pub fn handle_start_cause_init(&mut self) {
  215. let virtual_dom = self
  216. .unmounted_dom
  217. .take()
  218. .expect("Virtualdom should be set before initialization");
  219. let mut cfg = self
  220. .cfg
  221. .take()
  222. .expect("Config should be set before initialization");
  223. self.is_visible_before_start = cfg.window.window.visible;
  224. cfg.window = cfg.window.with_visible(false);
  225. let explicit_window_size = cfg.window.window.inner_size;
  226. let explicit_window_position = cfg.window.window.position;
  227. let webview = WebviewInstance::new(cfg, virtual_dom, self.shared.clone());
  228. // And then attempt to resume from state
  229. self.resume_from_state(&webview, explicit_window_size, explicit_window_position);
  230. let id = webview.desktop_context.window.id();
  231. self.webviews.insert(id, webview);
  232. }
  233. pub fn handle_browser_open(&mut self, msg: IpcMessage) {
  234. if let Some(temp) = msg.params().as_object() {
  235. if temp.contains_key("href") {
  236. if let Some(href) = temp.get("href").and_then(|v| v.as_str()) {
  237. if let Err(e) = open::that_detached(href) {
  238. tracing::error!("Open Browser error: {:?}", e);
  239. }
  240. }
  241. }
  242. }
  243. }
  244. /// The webview is finally loaded
  245. ///
  246. /// Let's rebuild it and then start polling it
  247. pub fn handle_initialize_msg(&mut self, id: WindowId) {
  248. let view = self.webviews.get_mut(&id).unwrap();
  249. view.edits
  250. .wry_queue
  251. .with_mutation_state_mut(|f| view.dom.rebuild(f));
  252. view.edits.wry_queue.send_edits();
  253. view.desktop_context
  254. .window
  255. .set_visible(self.is_visible_before_start);
  256. _ = self.shared.proxy.send_event(UserWindowEvent::Poll(id));
  257. }
  258. /// Todo: maybe we should poll the virtualdom asking if it has any final actions to apply before closing the webview
  259. ///
  260. /// Technically you can handle this with the use_window_event hook
  261. pub fn handle_close_msg(&mut self, id: WindowId) {
  262. self.webviews.remove(&id);
  263. if self.webviews.is_empty() {
  264. self.control_flow = ControlFlow::Exit
  265. }
  266. }
  267. pub fn handle_query_msg(&mut self, msg: IpcMessage, id: WindowId) {
  268. let Ok(result) = serde_json::from_value::<QueryResult>(msg.params()) else {
  269. return;
  270. };
  271. let Some(view) = self.webviews.get(&id) else {
  272. return;
  273. };
  274. view.desktop_context.query.send(result);
  275. }
  276. #[cfg(all(feature = "devtools", debug_assertions))]
  277. pub fn handle_hot_reload_msg(&mut self, msg: dioxus_devtools::DevserverMsg) {
  278. use std::time::Duration;
  279. use dioxus_devtools::DevserverMsg;
  280. // Amount of time that toats should be displayed.
  281. const TOAST_TIMEOUT: Duration = Duration::from_secs(2);
  282. const TOAST_TIMEOUT_LONG: Duration = Duration::from_secs(3600); // Duration::MAX is too long for JS.
  283. match msg {
  284. DevserverMsg::HotReload(hr_msg) => {
  285. for webview in self.webviews.values_mut() {
  286. {
  287. // This is a place where wry says it's threadsafe but it's actually not.
  288. // If we're patching the app, we want to make sure it's not going to progress in the interim.
  289. let lock = crate::android_sync_lock::android_runtime_lock();
  290. dioxus_devtools::apply_changes(&webview.dom, &hr_msg);
  291. drop(lock);
  292. }
  293. webview.poll_vdom();
  294. }
  295. if !hr_msg.assets.is_empty() {
  296. for webview in self.webviews.values_mut() {
  297. webview.kick_stylsheets();
  298. }
  299. }
  300. if hr_msg.jump_table.is_some()
  301. && hr_msg.for_build_id == Some(dioxus_cli_config::build_id())
  302. {
  303. self.send_toast_to_all(
  304. "Hot-patch success!",
  305. &format!("App successfully patched in {} ms", hr_msg.ms_elapsed),
  306. "success",
  307. TOAST_TIMEOUT,
  308. false,
  309. );
  310. }
  311. }
  312. DevserverMsg::FullReloadCommand => {
  313. self.send_toast_to_all(
  314. "Successfully rebuilt.",
  315. "Your app was rebuilt successfully and without error.",
  316. "success",
  317. TOAST_TIMEOUT,
  318. true,
  319. );
  320. }
  321. DevserverMsg::FullReloadStart => self.send_toast_to_all(
  322. "Your app is being rebuilt.",
  323. "A non-hot-reloadable change occurred and we must rebuild.",
  324. "info",
  325. TOAST_TIMEOUT_LONG,
  326. false,
  327. ),
  328. DevserverMsg::FullReloadFailed => self.send_toast_to_all(
  329. "Oops! The build failed.",
  330. "We tried to rebuild your app, but something went wrong.",
  331. "error",
  332. TOAST_TIMEOUT_LONG,
  333. false,
  334. ),
  335. DevserverMsg::HotPatchStart => self.send_toast_to_all(
  336. "Hot-patching app...",
  337. "Hot-patching modified Rust code.",
  338. "info",
  339. TOAST_TIMEOUT_LONG,
  340. false,
  341. ),
  342. DevserverMsg::Shutdown => {
  343. self.control_flow = ControlFlow::Exit;
  344. }
  345. _ => {}
  346. }
  347. }
  348. #[cfg(all(feature = "devtools", debug_assertions))]
  349. fn send_toast_to_all(
  350. &self,
  351. header_text: &str,
  352. message: &str,
  353. level: &str,
  354. duration: Duration,
  355. after_reload: bool,
  356. ) {
  357. for webview in self.webviews.values() {
  358. webview.show_toast(header_text, message, level, duration, after_reload);
  359. }
  360. }
  361. pub fn handle_file_dialog_msg(&mut self, msg: IpcMessage, window: WindowId) {
  362. let Ok(file_dialog) = serde_json::from_value::<FileDialogRequest>(msg.params()) else {
  363. return;
  364. };
  365. let id = ElementId(file_dialog.target);
  366. let event_name = &file_dialog.event;
  367. let event_bubbles = file_dialog.bubbles;
  368. let files = file_dialog.get_file_event();
  369. let as_any = Box::new(DesktopFileUploadForm {
  370. files: Arc::new(NativeFileEngine::new(files)),
  371. });
  372. let data = Rc::new(PlatformEventData::new(as_any));
  373. let Some(view) = self.webviews.get_mut(&window) else {
  374. return;
  375. };
  376. let event = dioxus_core::Event::new(data as Rc<dyn Any>, event_bubbles);
  377. let runtime = view.dom.runtime();
  378. if event_name == "change&input" {
  379. runtime.handle_event("input", event.clone(), id);
  380. runtime.handle_event("change", event, id);
  381. } else {
  382. runtime.handle_event(event_name, event, id);
  383. }
  384. }
  385. /// Poll the virtualdom until it's pending
  386. ///
  387. /// 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
  388. ///
  389. /// All IO is done on the tokio runtime we started earlier
  390. pub fn poll_vdom(&mut self, id: WindowId) {
  391. let Some(view) = self.webviews.get_mut(&id) else {
  392. return;
  393. };
  394. view.poll_vdom();
  395. }
  396. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  397. fn set_global_hotkey_handler(&self) {
  398. let receiver = self.shared.proxy.clone();
  399. // The event loop becomes the hotkey receiver
  400. // This means we don't need to poll the receiver on every tick - we just get the events as they come in
  401. // This is a bit more efficient than the previous implementation, but if someone else sets a handler, the
  402. // receiver will become inert.
  403. global_hotkey::GlobalHotKeyEvent::set_event_handler(Some(move |t| {
  404. // todo: should we unset the event handler when the app shuts down?
  405. _ = receiver.send_event(UserWindowEvent::GlobalHotKeyEvent(t));
  406. }));
  407. }
  408. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  409. fn set_menubar_receiver(&self) {
  410. let receiver = self.shared.proxy.clone();
  411. // The event loop becomes the menu receiver
  412. // This means we don't need to poll the receiver on every tick - we just get the events as they come in
  413. // This is a bit more efficient than the previous implementation, but if someone else sets a handler, the
  414. // receiver will become inert.
  415. muda::MenuEvent::set_event_handler(Some(move |t| {
  416. // todo: should we unset the event handler when the app shuts down?
  417. _ = receiver.send_event(UserWindowEvent::MudaMenuEvent(t));
  418. }));
  419. }
  420. #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
  421. fn set_tray_icon_receiver(&self) {
  422. let receiver = self.shared.proxy.clone();
  423. // The event loop becomes the menu receiver
  424. // This means we don't need to poll the receiver on every tick - we just get the events as they come in
  425. // This is a bit more efficient than the previous implementation, but if someone else sets a handler, the
  426. // receiver will become inert.
  427. tray_icon::TrayIconEvent::set_event_handler(Some(move |t| {
  428. // todo: should we unset the event handler when the app shuts down?
  429. _ = receiver.send_event(UserWindowEvent::TrayIconEvent(t));
  430. }));
  431. // for whatever reason they had to make it separate
  432. let receiver = self.shared.proxy.clone();
  433. tray_icon::menu::MenuEvent::set_event_handler(Some(move |t| {
  434. // todo: should we unset the event handler when the app shuts down?
  435. _ = receiver.send_event(UserWindowEvent::TrayMenuEvent(t));
  436. }));
  437. }
  438. /// Do our best to preserve state about the window when the event loop is destroyed
  439. ///
  440. /// This will attempt to save the window position, size, and monitor into the environment before
  441. /// closing. This way, when the app is restarted, it can attempt to restore the window to the same
  442. /// position and size it was in before, making a better DX.
  443. pub(crate) fn handle_loop_destroyed(&self) {
  444. #[cfg(debug_assertions)]
  445. self.persist_window_state();
  446. }
  447. #[cfg(debug_assertions)]
  448. fn persist_window_state(&self) {
  449. use dioxus_core::ScopeId;
  450. use dioxus_history::History;
  451. if let Some(webview) = self.webviews.values().next() {
  452. let window = &webview.desktop_context.window;
  453. let Some(monitor) = window.current_monitor() else {
  454. return;
  455. };
  456. let Ok(position) = window.outer_position() else {
  457. return;
  458. };
  459. let (x, y) = if cfg!(target_os = "macos") {
  460. let position = position.to_logical::<i32>(window.scale_factor());
  461. (position.x, position.y)
  462. } else {
  463. (position.x, position.y)
  464. };
  465. let (width, height) = if cfg!(target_os = "macos") {
  466. let size = window.outer_size();
  467. let size = size.to_logical::<u32>(window.scale_factor());
  468. // This is to work around a bug in how tao handles inner_size on macOS
  469. // We *want* to use inner_size, but that's currently broken, so we use outer_size instead and then an adjustment
  470. //
  471. // https://github.com/tauri-apps/tao/issues/889
  472. let adjustment = if window.is_decorated() { 28 } else { 0 };
  473. (size.width, size.height.saturating_sub(adjustment))
  474. } else {
  475. let size = window.inner_size();
  476. (size.width, size.height)
  477. };
  478. let Some(monitor_name) = monitor.name() else {
  479. return;
  480. };
  481. let url = webview.dom.in_runtime(|| {
  482. ScopeId::ROOT
  483. .consume_context::<Rc<dyn History>>()
  484. .unwrap()
  485. .current_route()
  486. });
  487. let state = PreservedWindowState {
  488. x,
  489. y,
  490. width: width.max(200),
  491. height: height.max(200),
  492. monitor: monitor_name.to_string(),
  493. url: Some(url),
  494. };
  495. // Yes... I know... we're loading a file that might not be ours... but it's a debug feature
  496. if let Ok(state) = serde_json::to_string(&state) {
  497. _ = std::fs::write(restore_file(), state);
  498. }
  499. }
  500. }
  501. // Write this to the target dir so we can pick back up
  502. fn resume_from_state(
  503. &mut self,
  504. webview: &WebviewInstance,
  505. explicit_inner_size: Option<tao::dpi::Size>,
  506. explicit_window_position: Option<tao::dpi::Position>,
  507. ) {
  508. // We only want to do this on desktop
  509. if cfg!(target_os = "android") || cfg!(target_os = "ios") {
  510. return;
  511. }
  512. // We only want to do this in debug mode
  513. if !cfg!(debug_assertions) {
  514. return;
  515. }
  516. if let Ok(state) = std::fs::read_to_string(restore_file()) {
  517. if let Ok(state) = serde_json::from_str::<PreservedWindowState>(&state) {
  518. let window = &webview.desktop_context.window;
  519. let position = (state.x, state.y);
  520. let size = (state.width, state.height);
  521. // Only set the outer position if it wasn't explicitly set
  522. if explicit_window_position.is_none() {
  523. if cfg!(target_os = "macos") {
  524. window.set_outer_position(tao::dpi::LogicalPosition::new(
  525. position.0, position.1,
  526. ));
  527. } else {
  528. window.set_outer_position(tao::dpi::PhysicalPosition::new(
  529. position.0, position.1,
  530. ));
  531. }
  532. }
  533. // Only set the inner size if it wasn't explicitly set
  534. if explicit_inner_size.is_none() {
  535. if cfg!(target_os = "macos") {
  536. window.set_inner_size(tao::dpi::LogicalSize::new(size.0, size.1));
  537. } else {
  538. window.set_inner_size(tao::dpi::PhysicalSize::new(size.0, size.1));
  539. }
  540. }
  541. // Set the url if it exists
  542. webview.dom.in_runtime(|| {
  543. if let Some(url) = state.url {
  544. ScopeId::ROOT
  545. .consume_context::<Rc<dyn History>>()
  546. .unwrap()
  547. .replace(url);
  548. }
  549. })
  550. }
  551. }
  552. }
  553. /// Wire up a receiver to sigkill that lets us preserve the window state
  554. /// Whenever sigkill is sent, we shut down the app and save the window state
  555. #[cfg(debug_assertions)]
  556. fn connect_preserve_window_state_handler(&self) {
  557. // TODO: make this work on windows
  558. #[cfg(unix)]
  559. {
  560. // Wire up the trap
  561. let target = self.shared.proxy.clone();
  562. std::thread::spawn(move || {
  563. use signal_hook::consts::{SIGINT, SIGTERM};
  564. let sigkill = signal_hook::iterator::Signals::new([SIGTERM, SIGINT]);
  565. if let Ok(mut sigkill) = sigkill {
  566. for _ in sigkill.forever() {
  567. if target.send_event(UserWindowEvent::Shutdown).is_err() {
  568. std::process::exit(0);
  569. }
  570. // give it a moment for the event to be processed
  571. std::thread::sleep(std::time::Duration::from_millis(100));
  572. }
  573. }
  574. });
  575. }
  576. }
  577. }
  578. #[derive(Debug, serde::Serialize, serde::Deserialize)]
  579. struct PreservedWindowState {
  580. x: i32,
  581. y: i32,
  582. width: u32,
  583. height: u32,
  584. monitor: String,
  585. url: Option<String>,
  586. }
  587. /// Return the location of a tempfile with our window state in it such that we can restore it later
  588. fn restore_file() -> std::path::PathBuf {
  589. let dir = dioxus_cli_config::session_cache_dir().unwrap_or_else(std::env::temp_dir);
  590. dir.join("window-state.json")
  591. }