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:
11 files changed
+357
-354
No files matched your search
+13
-6
@@ -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"] }
|
||||||
@@ -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" }
|
||||||
|
|||||||
@@ -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>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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;
|
||||||
|
|||||||
Reference in new issue
Block a user