diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 4aaae6c64..85dd02bd0 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -35,6 +35,7 @@ dunce = "1.0.2" interprocess = { version = "1.1.1", optional = true} futures-util = "0.3.25" +sledgehammer = "0.2.0" [target.'cfg(target_os = "ios")'.dependencies] objc = "0.2.7" diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 0d198da21..109985ca8 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -29,9 +29,6 @@ pub(super) struct DesktopController { pub(super) event_tx: UnboundedSender, #[cfg(debug_assertions)] pub(super) templates_tx: UnboundedSender>, - - #[cfg(target_os = "ios")] - pub(super) views: Vec<*mut Object>, } impl DesktopController { diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index f6f7df32d..b7a4405fd 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,13 +1,10 @@ +use std::cell::RefCell; use std::rc::Rc; use crate::controller::DesktopController; +use crate::eval::EvalResult; use dioxus_core::ScopeState; -use serde::de::Error; use serde_json::Value; -use std::future::Future; -use std::future::IntoFuture; -use std::pin::Pin; -use wry::application::dpi::LogicalSize; use wry::application::event_loop::ControlFlow; use wry::application::event_loop::EventLoopProxy; #[cfg(target_os = "ios")] @@ -16,8 +13,6 @@ use wry::application::window::Fullscreen as WryFullscreen; use wry::application::window::Window; use wry::webview::WebView; -use UserWindowEvent::*; - pub type ProxyType = EventLoopProxy; /// Get an imperative handle to the current window @@ -48,7 +43,10 @@ pub struct DesktopContext { pub proxy: ProxyType, /// The receiver for eval results since eval is async - pub(super) eval_reciever: Rc>>, + pub(super) eval_reciever: Rc>>, + + #[cfg(target_os = "ios")] + pub(crate) views: Rc>>>, } /// A smart pointer to the current window. @@ -56,7 +54,7 @@ impl std::ops::Deref for DesktopContext { type Target = Window; fn deref(&self) -> &Self::Target { - &self.webview.window() + self.webview.window() } } @@ -69,7 +67,7 @@ impl DesktopContext { Self { webview, proxy, - eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)), + eval_reciever: Rc::new(RefCell::new(eval_reciever)), } } @@ -82,48 +80,108 @@ impl DesktopContext { /// onmousedown: move |_| { desktop.drag_window(); } /// ``` pub fn drag(&self) { - let _ = self.webview.window().drag_window(); + let window = self.webview.window(); + + // if the drag_window has any errors, we don't do anything + window.fullscreen().is_none().then(|| window.drag_window()); } - /// toggle window maximize state + + /// Toggle whether the window is maximized or not pub fn toggle_maximized(&self) { - let _ = self.proxy.send_event(MaximizeToggle); + let window = self.webview.window(); + + window.set_maximized(!window.is_maximized()) } /// close window pub fn close(&self) { - let _ = self.proxy.send_event(CloseWindow); + let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); } /// change window to fullscreen pub fn set_fullscreen(&self, fullscreen: bool) { - let _ = self.proxy.send_event(Fullscreen(fullscreen)); + if let Some(handle) = self.webview.window().current_monitor() { + self.webview + .window() + .set_fullscreen(fullscreen.then_some(WryFullscreen::Borderless(Some(handle)))); + } } /// launch print modal pub fn print(&self) { - let _ = self.proxy.send_event(Print); + if let Err(e) = self.webview.print() { + log::warn!("Open print modal failed: {e}"); + } } /// opens DevTool window pub fn devtool(&self) { - let _ = self.proxy.send_event(DevTool); + #[cfg(debug_assertions)] + self.webview.open_devtools(); + + #[cfg(not(debug_assertions))] + log::warn!("Devtools are disabled in release builds"); } - /// run (evaluate) a script in the WebView context - pub fn eval(&self, script: impl std::string::ToString) { - let _ = self.proxy.send_event(Eval(script.to_string())); + /// Evaluate a javascript expression + pub fn eval(&self, code: &str) -> EvalResult { + // Embed the return of the eval in a function so we can send it back to the main thread + let script = format!( + r#" + window.ipc.postMessage( + JSON.stringify({{ + "method":"eval_result", + "params": ( + function(){{ + {} + }} + )() + }}) + ); + "#, + code + ); + + if let Err(e) = self.webview.evaluate_script(&script) { + // send an error to the eval receiver + log::warn!("Eval script error: {e}"); + } + + EvalResult { + reciever: self.eval_reciever.clone(), + } } - /// Push view + /// Push an objc view to the window #[cfg(target_os = "ios")] pub fn push_view(&self, view: objc_id::ShareId) { - let _ = self.proxy.send_event(PushView(view)); + unsafe { + use objc::runtime::Object; + use objc::*; + assert!(is_main_thread()); + let ui_view = window.ui_view() as *mut Object; + let ui_view_frame: *mut Object = msg_send![ui_view, frame]; + let _: () = msg_send![view, setFrame: ui_view_frame]; + let _: () = msg_send![view, setAutoresizingMask: 31]; + + let ui_view_controller = window.ui_view_controller() as *mut Object; + let _: () = msg_send![ui_view_controller, setView: view]; + self.views.borrow_mut().push(ui_view); + } } - /// Push view + /// Pop an objc view from the window #[cfg(target_os = "ios")] pub fn pop_view(&self) { - let _ = self.proxy.send_event(PopView); + unsafe { + use objc::runtime::Object; + use objc::*; + assert!(is_main_thread()); + if let Some(view) = self.views.borrow_mut().pop() { + let ui_view_controller = window.ui_view_controller() as *mut Object; + let _: () = msg_send![ui_view_controller, setView: view]; + } + } } } @@ -136,157 +194,8 @@ pub enum UserWindowEvent { UserEvent(serde_json::Value), CloseWindow, - DragWindow, - MaximizeToggle, - - Fullscreen(bool), - - Print, - DevTool, - - Eval(String), EvalResult(serde_json::Value), - - #[cfg(target_os = "ios")] - PushView(objc_id::ShareId), - #[cfg(target_os = "ios")] - PopView, -} - -impl DesktopController { - pub(super) fn handle_event( - &mut self, - user_event: UserWindowEvent, - control_flow: &mut ControlFlow, - ) { - // currently dioxus-desktop supports a single window only, - // so we can grab the only webview from the map; - // on wayland it is possible that a user event is emitted - // before the webview is initialized. ignore the event. - let webview = if let Some(webview) = self.webviews.values().next() { - webview - } else { - return; - }; - - let window = webview.window(); - - match user_event { - EditsReady => { - // self.try_load_ready_webviews(); - } - CloseWindow => *control_flow = ControlFlow::Exit, - DragWindow => { - // if the drag_window has any errors, we don't do anything - window.fullscreen().is_none().then(|| window.drag_window()); - } - - MaximizeToggle => window.set_maximized(!window.is_maximized()), - Fullscreen(state) => { - if let Some(handle) = window.current_monitor() { - window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle)))); - } - } - - UserEvent(event) => { - // - } - - Eval(code) => { - let script = format!( - r#"window.ipc.postMessage(JSON.stringify({{"method":"eval_result", params: (function(){{ - {} - }})()}}));"#, - code - ); - if let Err(e) = webview.evaluate_script(&script) { - // we can't panic this error. - log::warn!("Eval script error: {e}"); - } - } - - EvalResult(result) => { - // todo - } - - Poll => { - // todo - } - - Print => { - if let Err(e) = webview.print() { - // we can't panic this error. - log::warn!("Open print modal failed: {e}"); - } - } - DevTool => { - #[cfg(debug_assertions)] - webview.open_devtools(); - #[cfg(not(debug_assertions))] - log::warn!("Devtools are disabled in release builds"); - } - - #[cfg(target_os = "ios")] - PushView(view) => unsafe { - use objc::runtime::Object; - use objc::*; - assert!(is_main_thread()); - let ui_view = window.ui_view() as *mut Object; - let ui_view_frame: *mut Object = msg_send![ui_view, frame]; - let _: () = msg_send![view, setFrame: ui_view_frame]; - let _: () = msg_send![view, setAutoresizingMask: 31]; - - let ui_view_controller = window.ui_view_controller() as *mut Object; - let _: () = msg_send![ui_view_controller, setView: view]; - self.views.push(ui_view); - }, - - #[cfg(target_os = "ios")] - PopView => unsafe { - use objc::runtime::Object; - use objc::*; - assert!(is_main_thread()); - if let Some(view) = self.views.pop() { - let ui_view_controller = window.ui_view_controller() as *mut Object; - let _: () = msg_send![ui_view_controller, setView: view]; - } - }, - } - } -} - -/// Get a closure that executes any JavaScript in the WebView context. -pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult { - let desktop = use_window(cx).clone(); - cx.use_hook(|| { - move |script| { - desktop.eval(script); - let recv = desktop.eval_reciever.clone(); - EvalResult { reciever: recv } - } - }) -} - -/// A future that resolves to the result of a JavaScript evaluation. -pub struct EvalResult { - reciever: Rc>>, -} - -impl IntoFuture for EvalResult { - type Output = Result; - - type IntoFuture = Pin>>>; - - fn into_future(self) -> Self::IntoFuture { - Box::pin(async move { - let mut reciever = self.reciever.lock().await; - match reciever.recv().await { - Some(result) => Ok(result), - None => Err(serde_json::Error::custom("No result returned")), - } - }) as Pin>>> - } } #[cfg(target_os = "ios")] diff --git a/packages/desktop/src/eval.rs b/packages/desktop/src/eval.rs new file mode 100644 index 000000000..358825fa3 --- /dev/null +++ b/packages/desktop/src/eval.rs @@ -0,0 +1,50 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use crate::controller::DesktopController; +use crate::use_window; +use dioxus_core::ScopeState; +use serde::de::Error; +use serde_json::Value; +use std::future::Future; +use std::future::IntoFuture; +use std::pin::Pin; +use wry::application::dpi::LogicalSize; +use wry::application::event_loop::ControlFlow; +use wry::application::event_loop::EventLoopProxy; +#[cfg(target_os = "ios")] +use wry::application::platform::ios::WindowExtIOS; +use wry::application::window::Fullscreen as WryFullscreen; +use wry::application::window::Window; +use wry::webview::WebView; + +/// A future that resolves to the result of a JavaScript evaluation. +pub struct EvalResult { + pub(crate) reciever: Rc>>, +} + +impl IntoFuture for EvalResult { + type Output = Result; + + type IntoFuture = Pin>>>; + + fn into_future(self) -> Self::IntoFuture { + Box::pin(async move { + let mut reciever = self.reciever.borrow_mut(); + match reciever.recv().await { + Some(result) => Ok(result), + None => Err(serde_json::Error::custom("No result returned")), + } + }) as Pin>>> + } +} + +/// Get a closure that executes any JavaScript in the WebView context. +pub fn use_eval(cx: &ScopeState) -> &Rc EvalResult> { + let desktop = use_window(cx); + &*cx.use_hook(|| { + let desktop = desktop.clone(); + + Rc::new(move |script: String| desktop.eval(&script)) as Rc EvalResult> + }) +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index a7b47109b..7dda7e679 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -13,28 +13,27 @@ mod protocol; #[cfg(all(feature = "hot-reload", debug_assertions))] mod hot_reload; +mod eval; +mod waker; +mod webview; + pub use cfg::Config; use desktop_context::UserWindowEvent; -pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult}; +pub use desktop_context::{use_window, DesktopContext}; use dioxus_core::*; use dioxus_html::HtmlEvent; -use events::parse_ipc_message; -use futures_util::task::ArcWake; use futures_util::{pin_mut, FutureExt}; use std::collections::HashMap; use std::rc::Rc; -use std::sync::Arc; +use std::task::Waker; pub use tao::dpi::{LogicalSize, PhysicalSize}; pub use tao::window::WindowBuilder; use tao::{ event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::Window, }; pub use wry; pub use wry::application as tao; -use wry::application::event_loop::EventLoopProxy; -use wry::webview::WebViewBuilder; /// Launch the WebView and run the event loop. /// @@ -82,9 +81,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// Launch the WebView and run the event loop, with configuration and root props. /// -/// THIS WILL BLOCK THE CURRENT THREAD -/// -/// This function will start a multithreaded Tokio runtime as well the WebView event loop. +/// This function will start a multithreaded Tokio runtime as well the WebView event loop. This will block the current thread. /// /// You can configure the WebView window with a configuration closure /// @@ -106,25 +103,27 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// } /// ``` pub fn launch_with_props(root: Component

, props: P, mut cfg: Config) { - let event_loop = EventLoop::with_user_event(); - let mut dom = VirtualDom::new_with_props(root, props); + let event_loop = EventLoop::with_user_event(); + let proxy = event_loop.create_proxy(); - let mut webviews = HashMap::new(); - - // todo: make this configurable + // We start the tokio runtime *on this thread* + // Any future we poll later will use this runtime to spawn tasks and for IO let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); - // We want to poll the virtualdom and the event loop at the same time - // So the waker will be connected to both - let waker = futures_util::task::waker(Arc::new(DomHandle { - proxy: proxy.clone(), - })); + // We enter the runtime but we poll futures manually, circumventing the per-task runtime budget + let _guard = rt.enter(); + + // We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both + let waker = waker::tao_waker(&proxy); + + // We only have one webview right now, but we'll have more later + let mut webviews = HashMap::new(); event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; @@ -132,11 +131,10 @@ pub fn launch_with_props(root: Component

, props: P, mut cf match window_event { Event::NewEvents(StartCause::Init) => { let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel(); - let window = Rc::new(build_webview(&mut cfg, event_loop, proxy.clone())); + let window = Rc::new(webview::build(&mut cfg, event_loop, proxy.clone())); let ctx = DesktopContext::new(window.clone(), proxy.clone(), eval_reciever); dom.base_scope().provide_context(ctx); webviews.insert(window.window().id(), window.clone()); - proxy.send_event(UserWindowEvent::Poll).unwrap(); } @@ -156,200 +154,55 @@ pub fn launch_with_props(root: Component

, props: P, mut cf _ => {} }, - Event::UserEvent(user_event) => { - match user_event { - UserWindowEvent::UserEvent(json_value) => { - if let Ok(value) = serde_json::from_value::(json_value) { - let HtmlEvent { - name, - element, - bubbles, - data, - } = value; - - dom.handle_event(&name, data.into_any(), element, bubbles); - - let edits = dom.render_immediate(); - - let serialized = serde_json::to_string(&edits).unwrap(); - - let (_id, view) = webviews.iter_mut().next().unwrap(); - - view.evaluate_script(&format!( - "window.interpreter.handleEdits({})", - serialized - )) - .unwrap(); - } - } - - UserWindowEvent::Poll => { - let mut cx = std::task::Context::from_waker(&waker); - - // using this will reset the budget for the task that we're blocking the main thread with - let _guard = rt.enter(); - - loop { - { - let fut = dom.wait_for_work(); - pin_mut!(fut); - - match fut.poll_unpin(&mut cx) { - std::task::Poll::Ready(_) => {} - std::task::Poll::Pending => break, - } - } - - let edits = dom.render_immediate(); - - // apply the edits - let serialized = serde_json::to_string(&edits).unwrap(); - - let (_id, view) = webviews.iter_mut().next().unwrap(); - - view.evaluate_script(&format!( - "window.interpreter.handleEdits({})", - serialized - )) - .unwrap(); - } - } - - UserWindowEvent::Initialize => { - let edits = dom.rebuild(); - - let (_id, view) = webviews.iter_mut().next().unwrap(); - - let serialized = serde_json::to_string(&edits).unwrap(); - - view.evaluate_script(&format!( - "window.interpreter.handleEdits({})", - serialized - )) - .unwrap(); - } - - other => { - // desktop.handle_event(user_event, control_flow); - } - } + Event::UserEvent(UserWindowEvent::Initialize) => { + send_edits(dom.rebuild(), &mut webviews); } - _ => {} + Event::UserEvent(UserWindowEvent::CloseWindow) => *control_flow = ControlFlow::Exit, + + Event::UserEvent(UserWindowEvent::EvalResult(_)) => todo!(), + + Event::UserEvent(UserWindowEvent::UserEvent(json_value)) => { + let evt = match serde_json::from_value::(json_value) { + Ok(value) => value, + Err(_) => return, + }; + + dom.handle_event(&evt.name, evt.data.into_any(), evt.element, evt.bubbles); + + send_edits(dom.render_immediate(), &mut webviews); + } + + Event::UserEvent(UserWindowEvent::Poll) => { + poll_vdom(&waker, &mut dom, &mut webviews); + } + + _ => todo!(), } }) } -struct DomHandle { - proxy: EventLoopProxy, -} +type Webviews = HashMap>; -impl ArcWake for DomHandle { - fn wake_by_ref(arc_self: &Arc) { - arc_self.proxy.send_event(UserWindowEvent::Poll).unwrap(); - } -} +fn poll_vdom(waker: &Waker, dom: &mut VirtualDom, webviews: &mut Webviews) { + let mut cx = std::task::Context::from_waker(waker); + loop { + { + let fut = dom.wait_for_work(); + pin_mut!(fut); -fn build_webview( - cfg: &mut Config, - event_loop: &tao::event_loop::EventLoopWindowTarget, - proxy: tao::event_loop::EventLoopProxy, -) -> wry::webview::WebView { - let builder = cfg.window.clone(); - let window = builder.build(event_loop).unwrap(); - let file_handler = cfg.file_drop_handler.take(); - let custom_head = cfg.custom_head.clone(); - let resource_dir = cfg.resource_dir.clone(); - let index_file = cfg.custom_index.clone(); - let root_name = cfg.root_name.clone(); - - // We assume that if the icon is None in cfg, then the user just didnt set it - if cfg.window.window.window_icon.is_none() { - window.set_window_icon(Some( - tao::window::Icon::from_rgba( - include_bytes!("./assets/default_icon.bin").to_vec(), - 460, - 460, - ) - .expect("image parse failed"), - )); - } - - let mut webview = WebViewBuilder::new(window) - .unwrap() - .with_transparent(cfg.window.window.transparent) - .with_url("dioxus://index.html/") - .unwrap() - .with_ipc_handler(move |_window: &Window, payload: String| { - let message = match parse_ipc_message(&payload) { - Some(message) => message, - None => { - log::error!("Failed to parse IPC message: {}", payload); - return; - } - }; - - match message.method() { - "eval_result" => { - let _ = proxy.send_event(UserWindowEvent::EvalResult(message.params())); - } - "user_event" => { - let _ = proxy.send_event(UserWindowEvent::UserEvent(message.params())); - } - "initialize" => { - let _ = proxy.send_event(UserWindowEvent::Initialize); - } - "browser_open" => match message.params().as_object() { - Some(temp) if temp.contains_key("href") => { - let open = webbrowser::open(temp["href"].as_str().unwrap()); - if let Err(e) = open { - log::error!("Open Browser error: {:?}", e); - } - } - _ => (), - }, - _ => (), + match fut.poll_unpin(&mut cx) { + std::task::Poll::Ready(_) => {} + std::task::Poll::Pending => break, } - }) - .with_custom_protocol(String::from("dioxus"), move |r| { - protocol::desktop_handler( - r, - resource_dir.clone(), - custom_head.clone(), - index_file.clone(), - &root_name, - ) - }) - .with_file_drop_handler(move |window, evet| { - file_handler - .as_ref() - .map(|handler| handler(window, evet)) - .unwrap_or_default() - }); + } - for (name, handler) in cfg.protocols.drain(..) { - webview = webview.with_custom_protocol(name, handler) + send_edits(dom.render_immediate(), webviews); } +} - if cfg.disable_context_menu { - // in release mode, we don't want to show the dev tool or reload menus - webview = webview.with_initialization_script( - r#" - if (document.addEventListener) { - document.addEventListener('contextmenu', function(e) { - e.preventDefault(); - }, false); - } else { - document.attachEvent('oncontextmenu', function() { - window.event.returnValue = false; - }); - } - "#, - ) - } else { - // in debug, we are okay with the reload menu showing and dev tool - webview = webview.with_devtools(true); - } - - webview.build().unwrap() +fn send_edits(edits: Mutations, webviews: &mut Webviews) { + let serialized = serde_json::to_string(&edits).unwrap(); + let (_id, view) = webviews.iter_mut().next().unwrap(); + _ = view.evaluate_script(&format!("window.interpreter.handleEdits({})", serialized)); } diff --git a/packages/desktop/src/waker.rs b/packages/desktop/src/waker.rs new file mode 100644 index 000000000..a08c93bd3 --- /dev/null +++ b/packages/desktop/src/waker.rs @@ -0,0 +1,22 @@ +use futures_util::task::ArcWake; +use std::sync::Arc; +use wry::application::event_loop::EventLoopProxy; + +use crate::desktop_context::UserWindowEvent; + +/// Create a waker that will send a poll event to the event loop. +/// +/// This lets the VirtualDom "come up for air" and process events while the main thread is blocked by the WebView. +/// +/// All other IO lives in the Tokio runtime, +pub fn tao_waker(proxy: &EventLoopProxy) -> std::task::Waker { + struct DomHandle(EventLoopProxy); + + impl ArcWake for DomHandle { + fn wake_by_ref(arc_self: &Arc) { + arc_self.0.send_event(UserWindowEvent::Poll).unwrap(); + } + } + + futures_util::task::waker(Arc::new(DomHandle(proxy.clone()))) +} diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs new file mode 100644 index 000000000..5cb80e41b --- /dev/null +++ b/packages/desktop/src/webview.rs @@ -0,0 +1,111 @@ +use crate::events::parse_ipc_message; +use crate::protocol; +use crate::{desktop_context::UserWindowEvent, Config}; +pub use wry; +pub use wry::application as tao; +use wry::application::window::Window; +use wry::webview::WebViewBuilder; + +pub fn build( + cfg: &mut Config, + event_loop: &tao::event_loop::EventLoopWindowTarget, + proxy: tao::event_loop::EventLoopProxy, +) -> wry::webview::WebView { + let builder = cfg.window.clone(); + let window = builder.build(event_loop).unwrap(); + let file_handler = cfg.file_drop_handler.take(); + let custom_head = cfg.custom_head.clone(); + let resource_dir = cfg.resource_dir.clone(); + let index_file = cfg.custom_index.clone(); + let root_name = cfg.root_name.clone(); + + // We assume that if the icon is None in cfg, then the user just didnt set it + if cfg.window.window.window_icon.is_none() { + window.set_window_icon(Some( + tao::window::Icon::from_rgba( + include_bytes!("./assets/default_icon.bin").to_vec(), + 460, + 460, + ) + .expect("image parse failed"), + )); + } + + let mut webview = WebViewBuilder::new(window) + .unwrap() + .with_transparent(cfg.window.window.transparent) + .with_url("dioxus://index.html/") + .unwrap() + .with_ipc_handler(move |_window: &Window, payload: String| { + let message = match parse_ipc_message(&payload) { + Some(message) => message, + None => { + log::error!("Failed to parse IPC message: {}", payload); + return; + } + }; + + match message.method() { + "eval_result" => { + let _ = proxy.send_event(UserWindowEvent::EvalResult(message.params())); + } + "user_event" => { + let _ = proxy.send_event(UserWindowEvent::UserEvent(message.params())); + } + "initialize" => { + let _ = proxy.send_event(UserWindowEvent::Initialize); + } + "browser_open" => match message.params().as_object() { + Some(temp) if temp.contains_key("href") => { + let open = webbrowser::open(temp["href"].as_str().unwrap()); + if let Err(e) = open { + log::error!("Open Browser error: {:?}", e); + } + } + _ => (), + }, + _ => (), + } + }) + .with_custom_protocol(String::from("dioxus"), move |r| { + protocol::desktop_handler( + r, + resource_dir.clone(), + custom_head.clone(), + index_file.clone(), + &root_name, + ) + }) + .with_file_drop_handler(move |window, evet| { + file_handler + .as_ref() + .map(|handler| handler(window, evet)) + .unwrap_or_default() + }); + + for (name, handler) in cfg.protocols.drain(..) { + webview = webview.with_custom_protocol(name, handler) + } + + if cfg.disable_context_menu { + // in release mode, we don't want to show the dev tool or reload menus + webview = webview.with_initialization_script( + r#" + if (document.addEventListener) { + document.addEventListener('contextmenu', function(e) { + e.preventDefault(); + }, false); + } else { + document.attachEvent('oncontextmenu', function() { + window.event.returnValue = false; + }); + } + "#, + ) + } else { + // in debug, we are okay with the reload menu showing and dev tool + webview = webview.with_devtools(true); + } + + webview.build().unwrap() +}