Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions packages/desktop/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,169 @@ use dioxus_document::eval;
use std::any::Any;
use tao::event::{Event, StartCause, WindowEvent};

/// Launch the WebView and run the event loop, returning to the caller when the event loop exits.
///
/// This is useful when embedding Dioxus Desktop inside another host process where calling
/// `std::process::exit` would terminate the host.
///
/// On Windows, `tao::event_loop::EventLoop::run` will call `std::process::exit` when the event loop
/// ends. This function uses `run_return` where supported so the event loop can shut down without
/// exiting the process.
pub fn launch_virtual_dom_blocking_return(virtual_dom: VirtualDom, mut desktop_config: Config) {
#[cfg(target_os = "ios")]
{
// `run_return` is not available on iOS in tao; fall back to the normal launcher.
launch_virtual_dom_blocking(virtual_dom, desktop_config);
}

#[cfg(not(target_os = "ios"))]
{
let mut custom_event_handler = desktop_config.custom_event_handler.take();
let (mut event_loop, mut app) = App::new(desktop_config, virtual_dom);

use tao::platform::run_return::EventLoopExtRunReturn;
event_loop.run_return(move |window_event, event_loop, control_flow| {
// Set the control flow and check if any events need to be handled in the app itself
app.tick(&window_event);

if let Some(ref mut f) = custom_event_handler {
f(&window_event, event_loop)
}

match window_event {
Event::NewEvents(StartCause::Init) => app.handle_start_cause_init(),
Event::LoopDestroyed => app.handle_loop_destroyed(),
Event::WindowEvent {
event, window_id, ..
} => match event {
WindowEvent::CloseRequested => app.handle_close_requested(window_id),
WindowEvent::Destroyed { .. } => app.window_destroyed(window_id),
WindowEvent::Resized(new_size) => app.resize_window(window_id, new_size),
_ => {}
},

Event::UserEvent(event) => match event {
UserWindowEvent::Poll(id) => app.poll_vdom(id),
UserWindowEvent::NewWindow => app.handle_new_window(),
UserWindowEvent::CloseWindow(id) => app.handle_close_requested(id),
UserWindowEvent::Shutdown => app.control_flow = tao::event_loop::ControlFlow::Exit,

#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
UserWindowEvent::GlobalHotKeyEvent(evnt) => app.handle_global_hotkey(evnt),

#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
UserWindowEvent::MudaMenuEvent(evnt) => app.handle_menu_event(evnt),

#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
UserWindowEvent::TrayMenuEvent(evnt) => app.handle_tray_menu_event(evnt),

#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
UserWindowEvent::TrayIconEvent(evnt) => app.handle_tray_icon_event(evnt),

#[cfg(all(feature = "devtools", debug_assertions))]
UserWindowEvent::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg),

// Windows-only drag-n-drop fix events. We need to call the interpreter drag-n-drop code.
UserWindowEvent::WindowsDragDrop(id) => {
if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_scope(ScopeId::ROOT, || {
eval("window.interpreter.handleWindowsDragDrop();");
});
}
}
UserWindowEvent::WindowsDragLeave(id) => {
if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_scope(ScopeId::ROOT, || {
eval("window.interpreter.handleWindowsDragLeave();");
});
}
}
UserWindowEvent::WindowsDragOver(id, x_pos, y_pos) => {
if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_scope(ScopeId::ROOT, || {
let e = eval(
r#"
const xPos = await dioxus.recv();
const yPos = await dioxus.recv();
window.interpreter.handleWindowsDragOver(xPos, yPos)
"#,
);

_ = e.send(x_pos);
_ = e.send(y_pos);
});
}
}

UserWindowEvent::Ipc { id, msg } => match msg.method() {
IpcMethod::Initialize => app.handle_initialize_msg(id),
IpcMethod::UserEvent => {}
IpcMethod::Query => app.handle_query_msg(msg, id),
IpcMethod::BrowserOpen => app.handle_browser_open(msg),
IpcMethod::Other(_) => {}
},
},
_ => {}
}

*control_flow = app.control_flow;
});
}
}

/// Launches the WebView and runs the event loop, returning to the caller when the event loop exits.
pub fn launch_virtual_dom_return(virtual_dom: VirtualDom, desktop_config: Config) {
#[cfg(feature = "tokio_runtime")]
{
if let std::result::Result::Ok(handle) = tokio::runtime::Handle::try_current() {
assert_ne!(
handle.runtime_flavor(),
tokio::runtime::RuntimeFlavor::CurrentThread,
"The tokio current-thread runtime does not work with dioxus event handling"
);
launch_virtual_dom_blocking_return(virtual_dom, desktop_config);
return;
}

tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(tokio::task::unconstrained(async move {
launch_virtual_dom_blocking_return(virtual_dom, desktop_config)
}));

return;
}

#[cfg(not(feature = "tokio_runtime"))]
{
launch_virtual_dom_blocking_return(virtual_dom, desktop_config);
}
}

/// Launches the WebView and runs the event loop, returning to the caller when the event loop exits.
///
/// This function matches the signature expected by `dioxus::LaunchBuilder::custom`.
pub fn launch_return(
root: fn() -> Element,
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
platform_config: Vec<Box<dyn Any>>,
) {
let mut virtual_dom = VirtualDom::new(root);

for context in contexts {
virtual_dom.insert_any_root_context(context());
}

let platform_config = *platform_config
.into_iter()
.find_map(|cfg| cfg.downcast::<Config>().ok())
.unwrap_or_default();

launch_virtual_dom_return(virtual_dom, platform_config)
}

/// Launch the WebView and run the event loop, with configuration and root props.
///
/// This will block the main thread, and *must* be spawned on the main thread. This function does not assume any runtime
Expand Down
18 changes: 18 additions & 0 deletions packages/dioxus/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,24 @@ impl LaunchBuilder {
}
}

/// Switch to an embedded-friendly desktop launcher.
///
/// This is intended for applications that embed Dioxus Desktop inside an existing host process
/// (for example, as a plugin/add-in). On Windows, `tao::EventLoop::run` will call
/// `std::process::exit` when the event loop exits, which can terminate the host process.
///
/// When the current platform is Desktop, this switches the launcher to
/// `dioxus_desktop::launch::launch_return` which uses `run_return` so the event loop can exit
/// without calling `std::process::exit`.
#[cfg(feature = "desktop")]
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
pub fn embedded(mut self) -> Self {
if matches!(self.platform, KnownPlatform::Desktop) {
self.platform = KnownPlatform::Other(dioxus_desktop::launch::launch_return);
}
self
}

/// Inject state into the root component's context that is created on the thread that the app is launched on.
///
/// # Example
Expand Down