Merge pull request #243 from asaaki/update/wry-0-13

Update to wry 0.13; support IPC, devtool, and feature flags
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2022-02-15 11:23:16 -05:00
commit 345dfd4214
11 files changed
+357 -354

No files matched your search

+13 -6
View File
@@ -36,6 +36,13 @@ web = ["dioxus-web"]
desktop = ["dioxus-desktop"] desktop = ["dioxus-desktop"]
router = ["dioxus-router"] router = ["dioxus-router"]
devtool = ["dioxus-desktop/devtool"]
fullscreen = ["dioxus-desktop/fullscreen"]
transparent = ["dioxus-desktop/transparent"]
tray = ["dioxus-desktop/tray"]
ayatana = ["dioxus-desktop/ayatana"]
# "dioxus-router/web" # "dioxus-router/web"
# "dioxus-router/desktop" # "dioxus-router/desktop"
# desktop = ["dioxus-desktop", "dioxus-router/desktop"] # desktop = ["dioxus-desktop", "dioxus-router/desktop"]
@@ -57,15 +64,15 @@ members = [
] ]
[dev-dependencies] [dev-dependencies]
futures-util = "0.3.17" futures-util = "0.3.21"
log = "0.4.14" log = "0.4.14"
num-format = "0.4.0" num-format = "0.4.0"
separator = "0.4.1" separator = "0.4.1"
serde = { version = "1.0.131", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
im-rc = "15.0.0" im-rc = "15.0.0"
anyhow = "1.0.51" anyhow = "1.0.53"
serde_json = "1.0.73" serde_json = "1.0.79"
rand = { version = "0.8.4", features = ["small_rng"] } rand = { version = "0.8.4", features = ["small_rng"] }
tokio = { version = "1.14.0", features = ["full"] } tokio = { version = "1.16.1", features = ["full"] }
reqwest = { version = "0.11.8", features = ["json"] } reqwest = { version = "0.11.9", features = ["json"] }
dioxus = { path = ".", features = ["desktop", "ssr", "router"] } dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
+15 -8
View File
@@ -13,15 +13,15 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies] [dependencies]
dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] } dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] }
argh = "0.1.4" argh = "0.1.7"
serde = "1.0.120" serde = "1.0.136"
serde_json = "1.0.61" serde_json = "1.0.79"
thiserror = "1.0.23" thiserror = "1.0.30"
log = "0.4.13" log = "0.4.14"
html-escape = "0.2.9" html-escape = "0.2.9"
wry = "0.12.2" wry = { version = "0.13.1" }
futures-channel = "0.3" futures-channel = "0.3.21"
tokio = { version = "1.12.0", features = [ tokio = { version = "1.16.1", features = [
"sync", "sync",
"rt-multi-thread", "rt-multi-thread",
"rt", "rt",
@@ -38,6 +38,13 @@ dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" }
default = ["tokio_runtime"] default = ["tokio_runtime"]
tokio_runtime = ["tokio"] tokio_runtime = ["tokio"]
devtool = ["wry/devtool"]
fullscreen = ["wry/fullscreen"]
transparent = ["wry/transparent"]
tray = ["wry/tray"]
ayatana = ["wry/ayatana"]
[dev-dependencies] [dev-dependencies]
dioxus-hooks = { path = "../hooks" } dioxus-hooks = { path = "../hooks" }
+4 -4
View File
@@ -12,14 +12,14 @@ use wry::{
pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView); pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView);
pub struct DesktopConfig { pub struct DesktopConfig {
pub window: WindowBuilder, pub(crate) window: WindowBuilder,
pub file_drop_handler: Option<Box<dyn Fn(&Window, FileDropEvent) -> bool>>, pub(crate) file_drop_handler: Option<Box<dyn Fn(&Window, FileDropEvent) -> bool>>,
pub protocols: Vec<WryProtocol>, pub(crate) protocols: Vec<WryProtocol>,
pub(crate) pre_rendered: Option<String>, pub(crate) pre_rendered: Option<String>,
pub(crate) event_handler: Option<Box<DynEventHandlerFn>>, pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
} }
pub type WryProtocol = ( pub(crate) type WryProtocol = (
String, String,
Box<dyn Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static>, Box<dyn Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static>,
); );
+106
View File
@@ -0,0 +1,106 @@
use crate::desktop_context::DesktopContext;
use crate::user_window_events::UserWindowEvent;
use dioxus_core::*;
use std::{
collections::{HashMap, VecDeque},
sync::atomic::AtomicBool,
sync::{Arc, RwLock},
};
use wry::{
self,
application::{event_loop::ControlFlow, event_loop::EventLoopProxy, window::WindowId},
webview::WebView,
};
pub(super) struct DesktopController {
pub(super) webviews: HashMap<WindowId, WebView>,
pub(super) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
pub(super) pending_edits: Arc<RwLock<VecDeque<String>>>,
pub(super) quit_app_on_close: bool,
pub(super) is_ready: Arc<AtomicBool>,
}
impl DesktopController {
// Launch the virtualdom on its own thread managed by tokio
// returns the desktop state
pub(super) fn new_on_tokio<P: Send + 'static>(
root: Component<P>,
props: P,
proxy: EventLoopProxy<UserWindowEvent>,
) -> Self {
let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
let pending_edits = edit_queue.clone();
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let return_sender = sender.clone();
let desktop_context_proxy = proxy.clone();
std::thread::spawn(move || {
// We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut dom =
VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
let window_context = DesktopContext::new(desktop_context_proxy);
dom.base_scope().provide_context(window_context);
let edits = dom.rebuild();
edit_queue
.write()
.unwrap()
.push_front(serde_json::to_string(&edits.edits).unwrap());
loop {
dom.wait_for_work().await;
let mut muts = dom.work_with_deadline(|| false);
while let Some(edit) = muts.pop() {
edit_queue
.write()
.unwrap()
.push_front(serde_json::to_string(&edit.edits).unwrap());
}
let _ = proxy.send_event(UserWindowEvent::Update);
}
})
});
Self {
pending_edits,
sender: return_sender,
webviews: HashMap::new(),
is_ready: Arc::new(AtomicBool::new(false)),
quit_app_on_close: true,
}
}
pub(super) fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) {
self.webviews.remove(&window_id);
if self.webviews.is_empty() && self.quit_app_on_close {
*control_flow = ControlFlow::Exit;
}
}
pub(super) fn try_load_ready_webviews(&mut self) {
if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) {
let mut queue = self.pending_edits.write().unwrap();
let (_id, view) = self.webviews.iter_mut().next().unwrap();
while let Some(edit) = queue.pop_back() {
view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
.unwrap();
}
} else {
println!("waiting for ready");
}
}
}
+25 -22
View File
@@ -3,7 +3,8 @@ use std::rc::Rc;
use dioxus_core::ScopeState; use dioxus_core::ScopeState;
use wry::application::event_loop::EventLoopProxy; use wry::application::event_loop::EventLoopProxy;
use crate::UserWindowEvent; use crate::user_window_events::UserWindowEvent;
use UserWindowEvent::*;
type ProxyType = EventLoopProxy<UserWindowEvent>; type ProxyType = EventLoopProxy<UserWindowEvent>;
@@ -35,75 +36,77 @@ impl DesktopContext {
/// onmousedown: move |_| { desktop.drag_window(); } /// onmousedown: move |_| { desktop.drag_window(); }
/// ``` /// ```
pub fn drag(&self) { pub fn drag(&self) {
let _ = self.proxy.send_event(UserWindowEvent::DragWindow); let _ = self.proxy.send_event(DragWindow);
} }
/// set window minimize state /// set window minimize state
pub fn set_minimized(&self, minimized: bool) { pub fn set_minimized(&self, minimized: bool) {
let _ = self.proxy.send_event(UserWindowEvent::Minimize(minimized)); let _ = self.proxy.send_event(Minimize(minimized));
} }
/// set window maximize state /// set window maximize state
pub fn set_maximized(&self, maximized: bool) { pub fn set_maximized(&self, maximized: bool) {
let _ = self.proxy.send_event(UserWindowEvent::Maximize(maximized)); let _ = self.proxy.send_event(Maximize(maximized));
}
/// toggle window maximize state
pub fn toggle_maximized(&self) {
let _ = self.proxy.send_event(MaximizeToggle);
} }
/// set window visible or not /// set window visible or not
pub fn set_visible(&self, visible: bool) { pub fn set_visible(&self, visible: bool) {
let _ = self.proxy.send_event(UserWindowEvent::Visible(visible)); let _ = self.proxy.send_event(Visible(visible));
} }
/// close window /// close window
pub fn close(&self) { pub fn close(&self) {
let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); let _ = self.proxy.send_event(CloseWindow);
} }
/// set window to focus /// set window to focus
pub fn focus(&self) { pub fn focus(&self) {
let _ = self.proxy.send_event(UserWindowEvent::FocusWindow); let _ = self.proxy.send_event(FocusWindow);
} }
/// change window to fullscreen /// change window to fullscreen
pub fn set_fullscreen(&self, fullscreen: bool) { pub fn set_fullscreen(&self, fullscreen: bool) {
let _ = self let _ = self.proxy.send_event(Fullscreen(fullscreen));
.proxy
.send_event(UserWindowEvent::Fullscreen(fullscreen));
} }
/// set resizable state /// set resizable state
pub fn set_resizable(&self, resizable: bool) { pub fn set_resizable(&self, resizable: bool) {
let _ = self.proxy.send_event(UserWindowEvent::Resizable(resizable)); let _ = self.proxy.send_event(Resizable(resizable));
} }
/// set the window always on top /// set the window always on top
pub fn set_always_on_top(&self, top: bool) { pub fn set_always_on_top(&self, top: bool) {
let _ = self.proxy.send_event(UserWindowEvent::AlwaysOnTop(top)); let _ = self.proxy.send_event(AlwaysOnTop(top));
} }
// set cursor visible or not // set cursor visible or not
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
let _ = self let _ = self.proxy.send_event(CursorVisible(visible));
.proxy
.send_event(UserWindowEvent::CursorVisible(visible));
} }
// set cursor grab // set cursor grab
pub fn set_cursor_grab(&self, grab: bool) { pub fn set_cursor_grab(&self, grab: bool) {
let _ = self.proxy.send_event(UserWindowEvent::CursorGrab(grab)); let _ = self.proxy.send_event(CursorGrab(grab));
} }
/// set window title /// set window title
pub fn set_title(&self, title: &str) { pub fn set_title(&self, title: &str) {
let _ = self let _ = self.proxy.send_event(SetTitle(String::from(title)));
.proxy
.send_event(UserWindowEvent::SetTitle(String::from(title)));
} }
/// change window to borderless /// change window to borderless
pub fn set_decorations(&self, decoration: bool) { pub fn set_decorations(&self, decoration: bool) {
let _ = self let _ = self.proxy.send_event(SetDecorations(decoration));
.proxy }
.send_event(UserWindowEvent::SetDecorations(decoration));
/// opens DevTool window
pub fn devtool(&self) {
let _ = self.proxy.send_event(DevTool);
} }
} }
+31 -9
View File
@@ -1,5 +1,4 @@
//! Convert a serialized event to an event Trigger //! Convert a serialized event to an event trigger
//!
use std::any::Any; use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
@@ -7,27 +6,50 @@ use std::sync::Arc;
use dioxus_core::{ElementId, EventPriority, UserEvent}; use dioxus_core::{ElementId, EventPriority, UserEvent};
use dioxus_html::on::*; use dioxus_html::on::*;
#[derive(serde::Serialize, serde::Deserialize)]
pub(crate) struct IpcMessage {
method: String,
params: serde_json::Value,
}
impl IpcMessage {
pub(crate) fn method(&self) -> &str {
self.method.as_str()
}
pub(crate) fn params(self) -> serde_json::Value {
self.params
}
}
pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
let mm = serde_json::from_str(payload);
match mm {
Ok(message) => Some(message),
Err(e) => {
log::error!("could not parse IPC message, error: {e}");
None
}
}
}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
struct ImEvent { struct ImEvent {
event: String, event: String,
mounted_dom_id: u64, mounted_dom_id: u64,
// scope: u64,
contents: serde_json::Value, contents: serde_json::Value,
} }
pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent { pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
let ims: Vec<ImEvent> = serde_json::from_value(val).unwrap();
let ImEvent { let ImEvent {
event, event,
mounted_dom_id, mounted_dom_id,
contents, contents,
} = ims.into_iter().next().unwrap(); } = serde_json::from_value(val).unwrap();
// let scope_id = ScopeId(scope as usize);
let mounted_dom_id = Some(ElementId(mounted_dom_id as usize)); let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
let name = event_name_from_typ(&event); let name = event_name_from_type(&event);
let event = make_synthetic_event(&event, contents); let event = make_synthetic_event(&event, contents);
UserEvent { UserEvent {
@@ -105,7 +127,7 @@ fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc<dyn Any + Sen
} }
} }
fn event_name_from_typ(typ: &str) -> &'static str { fn event_name_from_type(typ: &str) -> &'static str {
match typ { match typ {
"copy" => "copy", "copy" => "copy",
"cut" => "cut", "cut" => "cut",
+35 -300
View File
@@ -3,31 +3,28 @@
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub mod cfg; pub mod cfg;
mod controller;
pub mod desktop_context; pub mod desktop_context;
pub mod escape; pub mod escape;
pub mod events; pub mod events;
mod protocol;
mod user_window_events;
use cfg::DesktopConfig; use cfg::DesktopConfig;
use controller::DesktopController;
pub use desktop_context::use_window; pub use desktop_context::use_window;
use desktop_context::DesktopContext;
use dioxus_core::*; use dioxus_core::*;
use std::{ use events::parse_ipc_message;
collections::{HashMap, VecDeque},
sync::atomic::AtomicBool,
sync::{Arc, RwLock},
};
use tao::{ use tao::{
event::{Event, StartCause, WindowEvent}, event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
window::{Window, WindowId}, window::Window,
}; };
pub use wry; pub use wry;
pub use wry::application as tao; pub use wry::application as tao;
use wry::{ use wry::webview::WebViewBuilder;
application::{event_loop::EventLoopProxy, window::Fullscreen},
webview::RpcRequest, use crate::events::trigger_from_serialized;
webview::{WebView, WebViewBuilder},
};
/// Launch the WebView and run the event loop. /// Launch the WebView and run the event loop.
/// ///
@@ -132,23 +129,24 @@ pub fn launch_with_props<P: 'static + Send>(
.with_transparent(cfg.window.window.transparent) .with_transparent(cfg.window.window.transparent)
.with_url("dioxus://index.html/") .with_url("dioxus://index.html/")
.unwrap() .unwrap()
.with_rpc_handler(move |_window: &Window, req: RpcRequest| { .with_ipc_handler(move |_window: &Window, payload: String| {
match req.method.as_str() { parse_ipc_message(&payload)
"user_event" => { .map(|message| match message.method() {
let event = events::trigger_from_serialized(req.params.unwrap()); "user_event" => {
log::trace!("User event: {:?}", event); let event = trigger_from_serialized(message.params());
sender.unbounded_send(SchedulerMsg::Event(event)).unwrap(); log::trace!("User event: {:?}", event);
} sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
"initialize" => { }
is_ready.store(true, std::sync::atomic::Ordering::Relaxed); "initialize" => {
let _ = proxy.send_event(UserWindowEvent::Update); is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
} let _ = proxy
"browser_open" => { .send_event(user_window_events::UserWindowEvent::Update);
println!("browser_open"); }
let data = req.params.unwrap(); "browser_open" => {
log::trace!("Open browser: {:?}", data); println!("browser_open");
if let Some(arr) = data.as_array() { let data = message.params();
if let Some(temp) = arr[0].as_object() { log::trace!("Open browser: {:?}", data);
if let Some(temp) = data.as_object() {
if temp.contains_key("href") { if temp.contains_key("href") {
let url = temp.get("href").unwrap().as_str().unwrap(); let url = temp.get("href").unwrap().as_str().unwrap();
if let Err(e) = webbrowser::open(url) { if let Err(e) = webbrowser::open(url) {
@@ -157,55 +155,13 @@ pub fn launch_with_props<P: 'static + Send>(
} }
} }
} }
} _ => (),
_ => {} })
} .unwrap_or_else(|| {
None log::warn!("invalid IPC message received");
}) });
.with_custom_protocol(String::from("dioxus"), move |request| {
// Any content that that uses the `dioxus://` scheme will be shuttled through this handler as a "special case"
// For now, we only serve two pieces of content which get included as bytes into the final binary.
let path = request.uri().replace("dioxus://", "");
// all assets shouldbe called from index.html
let trimmed = path.trim_start_matches("index.html/");
if trimmed.is_empty() {
wry::http::ResponseBuilder::new()
.mimetype("text/html")
.body(include_bytes!("./index.html").to_vec())
} else if trimmed == "index.js" {
wry::http::ResponseBuilder::new()
.mimetype("text/javascript")
.body(dioxus_interpreter_js::INTERPRTER_JS.as_bytes().to_vec())
} else {
// Read the file content from file path
use std::fs::read;
let path_buf = std::path::Path::new(trimmed).canonicalize()?;
let cur_path = std::path::Path::new(".").canonicalize()?;
if !path_buf.starts_with(cur_path) {
return wry::http::ResponseBuilder::new()
.status(wry::http::status::StatusCode::FORBIDDEN)
.body(String::from("Forbidden").into_bytes());
}
if !path_buf.exists() {
return wry::http::ResponseBuilder::new()
.status(wry::http::status::StatusCode::NOT_FOUND)
.body(String::from("Not Found").into_bytes());
}
let mime = mime_guess::from_path(&path_buf).first_or_octet_stream();
// do not let path searching to go two layers beyond the caller level
let data = read(path_buf)?;
let meta = format!("{}", mime);
wry::http::ResponseBuilder::new().mimetype(&meta).body(data)
}
}) })
.with_custom_protocol(String::from("dioxus"), protocol::desktop_handler)
.with_file_drop_handler(move |window, evet| { .with_file_drop_handler(move |window, evet| {
file_handler file_handler
.as_ref() .as_ref()
@@ -235,114 +191,8 @@ pub fn launch_with_props<P: 'static + Send>(
_ => {} _ => {}
}, },
Event::UserEvent(_evt) => { Event::UserEvent(user_event) => {
// user_window_events::handler(user_event, &mut desktop, control_flow)
match _evt {
UserWindowEvent::Update => desktop.try_load_ready_webviews(),
UserWindowEvent::DragWindow => {
// this loop just run once, because dioxus-desktop is unsupport multi-window.
for webview in desktop.webviews.values() {
let window = webview.window();
// start to drag the window.
// if the drag_window have any err. we don't do anything.
if window.fullscreen().is_some() {
return;
}
let _ = window.drag_window();
}
}
UserWindowEvent::CloseWindow => {
// close window
*control_flow = ControlFlow::Exit;
}
UserWindowEvent::Visible(state) => {
for webview in desktop.webviews.values() {
let window = webview.window();
window.set_visible(state);
}
}
UserWindowEvent::Minimize(state) => {
// this loop just run once, because dioxus-desktop is unsupport multi-window.
for webview in desktop.webviews.values() {
let window = webview.window();
// change window minimized state.
window.set_minimized(state);
}
}
UserWindowEvent::Maximize(state) => {
// this loop just run once, because dioxus-desktop is unsupport multi-window.
for webview in desktop.webviews.values() {
let window = webview.window();
// change window maximized state.
window.set_maximized(state);
}
}
UserWindowEvent::Fullscreen(state) => {
for webview in desktop.webviews.values() {
let window = webview.window();
let current_monitor = window.current_monitor();
if current_monitor.is_none() {
return;
}
let fullscreen = if state {
Some(Fullscreen::Borderless(current_monitor))
} else {
None
};
window.set_fullscreen(fullscreen);
}
}
UserWindowEvent::FocusWindow => {
for webview in desktop.webviews.values() {
let window = webview.window();
window.set_focus();
}
}
UserWindowEvent::Resizable(state) => {
for webview in desktop.webviews.values() {
let window = webview.window();
window.set_resizable(state);
}
}
UserWindowEvent::AlwaysOnTop(state) => {
for webview in desktop.webviews.values() {
let window = webview.window();
window.set_always_on_top(state);
}
}
UserWindowEvent::CursorVisible(state) => {
for webview in desktop.webviews.values() {
let window = webview.window();
window.set_cursor_visible(state);
}
}
UserWindowEvent::CursorGrab(state) => {
for webview in desktop.webviews.values() {
let window = webview.window();
let _ = window.set_cursor_grab(state);
}
}
UserWindowEvent::SetTitle(content) => {
for webview in desktop.webviews.values() {
let window = webview.window();
window.set_title(&content);
}
}
UserWindowEvent::SetDecorations(state) => {
for webview in desktop.webviews.values() {
let window = webview.window();
window.set_decorations(state);
}
}
}
} }
Event::MainEventsCleared => {} Event::MainEventsCleared => {}
Event::Resumed => {} Event::Resumed => {}
@@ -353,118 +203,3 @@ pub fn launch_with_props<P: 'static + Send>(
} }
}) })
} }
pub enum UserWindowEvent {
Update,
DragWindow,
CloseWindow,
FocusWindow,
Visible(bool),
Minimize(bool),
Maximize(bool),
Resizable(bool),
AlwaysOnTop(bool),
Fullscreen(bool),
CursorVisible(bool),
CursorGrab(bool),
SetTitle(String),
SetDecorations(bool),
}
pub struct DesktopController {
pub proxy: EventLoopProxy<UserWindowEvent>,
pub webviews: HashMap<WindowId, WebView>,
pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
pub pending_edits: Arc<RwLock<VecDeque<String>>>,
pub quit_app_on_close: bool,
pub is_ready: Arc<AtomicBool>,
}
impl DesktopController {
// Launch the virtualdom on its own thread managed by tokio
// returns the desktop state
pub fn new_on_tokio<P: Send + 'static>(
root: Component<P>,
props: P,
evt: EventLoopProxy<UserWindowEvent>,
) -> Self {
let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
let pending_edits = edit_queue.clone();
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let return_sender = sender.clone();
let proxy = evt.clone();
let desktop_context_proxy = proxy.clone();
std::thread::spawn(move || {
// We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut dom =
VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
let window_context = DesktopContext::new(desktop_context_proxy);
dom.base_scope().provide_context(window_context);
let edits = dom.rebuild();
edit_queue
.write()
.unwrap()
.push_front(serde_json::to_string(&edits.edits).unwrap());
loop {
dom.wait_for_work().await;
let mut muts = dom.work_with_deadline(|| false);
while let Some(edit) = muts.pop() {
edit_queue
.write()
.unwrap()
.push_front(serde_json::to_string(&edit.edits).unwrap());
}
let _ = evt.send_event(UserWindowEvent::Update);
}
})
});
Self {
pending_edits,
sender: return_sender,
proxy,
webviews: HashMap::new(),
is_ready: Arc::new(AtomicBool::new(false)),
quit_app_on_close: true,
}
}
pub fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) {
self.webviews.remove(&window_id);
if self.webviews.is_empty() && self.quit_app_on_close {
*control_flow = ControlFlow::Exit;
}
}
pub fn try_load_ready_webviews(&mut self) {
if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) {
let mut queue = self.pending_edits.write().unwrap();
let (_id, view) = self.webviews.iter_mut().next().unwrap();
while let Some(edit) = queue.pop_back() {
view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
.unwrap();
}
} else {
println!("waiting for ready");
}
}
}
+47
View File
@@ -0,0 +1,47 @@
use std::path::Path;
use wry::{
http::{status::StatusCode, Request, Response, ResponseBuilder},
Result,
};
pub(super) fn desktop_handler(request: &Request) -> Result<Response> {
// Any content that uses the `dioxus://` scheme will be shuttled through this handler as a "special case".
// For now, we only serve two pieces of content which get included as bytes into the final binary.
let path = request.uri().replace("dioxus://", "");
// all assets should be called from index.html
let trimmed = path.trim_start_matches("index.html/");
if trimmed.is_empty() {
ResponseBuilder::new()
.mimetype("text/html")
.body(include_bytes!("./index.html").to_vec())
} else if trimmed == "index.js" {
ResponseBuilder::new()
.mimetype("text/javascript")
.body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec())
} else {
let path_buf = Path::new(trimmed).canonicalize()?;
let cur_path = Path::new(".").canonicalize()?;
if !path_buf.starts_with(cur_path) {
return ResponseBuilder::new()
.status(StatusCode::FORBIDDEN)
.body(String::from("Forbidden").into_bytes());
}
if !path_buf.exists() {
return ResponseBuilder::new()
.status(StatusCode::NOT_FOUND)
.body(String::from("Not Found").into_bytes());
}
let mime = mime_guess::from_path(&path_buf).first_or_octet_stream();
// do not let path searching to go two layers beyond the caller level
let data = std::fs::read(path_buf)?;
let meta = format!("{}", mime);
ResponseBuilder::new().mimetype(&meta).body(data)
}
}
@@ -0,0 +1,72 @@
use wry::application::event_loop::ControlFlow;
use wry::application::window::Fullscreen as WryFullscreen;
use crate::controller::DesktopController;
pub(crate) enum UserWindowEvent {
Update,
CloseWindow,
DragWindow,
FocusWindow,
Visible(bool),
Minimize(bool),
Maximize(bool),
MaximizeToggle,
Resizable(bool),
AlwaysOnTop(bool),
Fullscreen(bool),
CursorVisible(bool),
CursorGrab(bool),
SetTitle(String),
SetDecorations(bool),
DevTool,
}
use UserWindowEvent::*;
pub(super) fn handler(
user_event: UserWindowEvent,
desktop: &mut DesktopController,
control_flow: &mut ControlFlow,
) {
// currently dioxus-desktop supports a single window only,
// so we can grab the only webview from the map;
let webview = desktop.webviews.values().next().unwrap();
let window = webview.window();
match user_event {
Update => desktop.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());
}
Visible(state) => window.set_visible(state),
Minimize(state) => window.set_minimized(state),
Maximize(state) => window.set_maximized(state),
MaximizeToggle => window.set_maximized(!window.is_maximized()),
Fullscreen(state) => {
if let Some(handle) = window.current_monitor() {
window.set_fullscreen(state.then(|| WryFullscreen::Borderless(Some(handle))));
}
}
FocusWindow => window.set_focus(),
Resizable(state) => window.set_resizable(state),
AlwaysOnTop(state) => window.set_always_on_top(state),
CursorVisible(state) => window.set_cursor_visible(state),
CursorGrab(state) => {
let _ = window.set_cursor_grab(state);
}
SetTitle(content) => window.set_title(&content),
SetDecorations(state) => window.set_decorations(state),
DevTool => webview.devtool(),
}
}
+8 -4
View File
@@ -2,7 +2,7 @@ export function main() {
let root = window.document.getElementById("main"); let root = window.document.getElementById("main");
if (root != null) { if (root != null) {
window.interpreter = new Interpreter(root); window.interpreter = new Interpreter(root);
window.rpc.call("initialize"); window.ipc.postMessage(serializeIpcMessage("initialize"))
} }
} }
export class Interpreter { export class Interpreter {
@@ -207,7 +207,7 @@ export class Interpreter {
event.preventDefault(); event.preventDefault();
const href = target.getAttribute("href"); const href = target.getAttribute("href");
if (href !== "" && href !== null && href !== undefined) { if (href !== "" && href !== null && href !== undefined) {
window.rpc.call("browser_open", { href }); window.ipc.postMessage(serializeIpcMessage("browser_open", { href }))
} }
} }
} }
@@ -261,11 +261,12 @@ export class Interpreter {
if (realId == null) { if (realId == null) {
return; return;
} }
window.rpc.call("user_event", { window.ipc.postMessage(serializeIpcMessage(
"user_event", {
event: edit.event_name, event: edit.event_name,
mounted_dom_id: parseInt(realId), mounted_dom_id: parseInt(realId),
contents: contents, contents: contents,
}); }));
} }
}; };
this.NewEventListener(edit.event_name, edit.root, handler); this.NewEventListener(edit.event_name, edit.root, handler);
@@ -544,6 +545,9 @@ export function serialize_event(event) {
} }
} }
} }
function serializeIpcMessage(method, params = {}) {
return JSON.stringify({ method, params });
}
const bool_attrs = { const bool_attrs = {
allowfullscreen: true, allowfullscreen: true,
allowpaymentrequest: true, allowpaymentrequest: true,
+1 -1
View File
@@ -1,4 +1,4 @@
pub static INTERPRTER_JS: &str = include_str!("./interpreter.js"); pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
#[cfg(feature = "web")] #[cfg(feature = "web")]
mod bindings; mod bindings;