mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
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:
commit
345dfd4214
11 changed files with 357 additions and 354 deletions
19
Cargo.toml
19
Cargo.toml
|
@ -36,6 +36,13 @@ web = ["dioxus-web"]
|
|||
desktop = ["dioxus-desktop"]
|
||||
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/desktop"
|
||||
# desktop = ["dioxus-desktop", "dioxus-router/desktop"]
|
||||
|
@ -57,15 +64,15 @@ members = [
|
|||
]
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = "0.3.17"
|
||||
futures-util = "0.3.21"
|
||||
log = "0.4.14"
|
||||
num-format = "0.4.0"
|
||||
separator = "0.4.1"
|
||||
serde = { version = "1.0.131", features = ["derive"] }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
im-rc = "15.0.0"
|
||||
anyhow = "1.0.51"
|
||||
serde_json = "1.0.73"
|
||||
anyhow = "1.0.53"
|
||||
serde_json = "1.0.79"
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
tokio = { version = "1.14.0", features = ["full"] }
|
||||
reqwest = { version = "0.11.8", features = ["json"] }
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
reqwest = { version = "0.11.9", features = ["json"] }
|
||||
dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
|
||||
|
|
|
@ -13,15 +13,15 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] }
|
||||
argh = "0.1.4"
|
||||
serde = "1.0.120"
|
||||
serde_json = "1.0.61"
|
||||
thiserror = "1.0.23"
|
||||
log = "0.4.13"
|
||||
argh = "0.1.7"
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
thiserror = "1.0.30"
|
||||
log = "0.4.14"
|
||||
html-escape = "0.2.9"
|
||||
wry = "0.12.2"
|
||||
futures-channel = "0.3"
|
||||
tokio = { version = "1.12.0", features = [
|
||||
wry = { version = "0.13.1" }
|
||||
futures-channel = "0.3.21"
|
||||
tokio = { version = "1.16.1", features = [
|
||||
"sync",
|
||||
"rt-multi-thread",
|
||||
"rt",
|
||||
|
@ -38,6 +38,13 @@ dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" }
|
|||
default = ["tokio_runtime"]
|
||||
tokio_runtime = ["tokio"]
|
||||
|
||||
devtool = ["wry/devtool"]
|
||||
fullscreen = ["wry/fullscreen"]
|
||||
transparent = ["wry/transparent"]
|
||||
|
||||
tray = ["wry/tray"]
|
||||
ayatana = ["wry/ayatana"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus-hooks = { path = "../hooks" }
|
||||
|
|
|
@ -12,14 +12,14 @@ use wry::{
|
|||
pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView);
|
||||
|
||||
pub struct DesktopConfig {
|
||||
pub window: WindowBuilder,
|
||||
pub file_drop_handler: Option<Box<dyn Fn(&Window, FileDropEvent) -> bool>>,
|
||||
pub protocols: Vec<WryProtocol>,
|
||||
pub(crate) window: WindowBuilder,
|
||||
pub(crate) file_drop_handler: Option<Box<dyn Fn(&Window, FileDropEvent) -> bool>>,
|
||||
pub(crate) protocols: Vec<WryProtocol>,
|
||||
pub(crate) pre_rendered: Option<String>,
|
||||
pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
|
||||
}
|
||||
|
||||
pub type WryProtocol = (
|
||||
pub(crate) type WryProtocol = (
|
||||
String,
|
||||
Box<dyn Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static>,
|
||||
);
|
||||
|
|
106
packages/desktop/src/controller.rs
Normal file
106
packages/desktop/src/controller.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@ use std::rc::Rc;
|
|||
use dioxus_core::ScopeState;
|
||||
use wry::application::event_loop::EventLoopProxy;
|
||||
|
||||
use crate::UserWindowEvent;
|
||||
use crate::user_window_events::UserWindowEvent;
|
||||
use UserWindowEvent::*;
|
||||
|
||||
type ProxyType = EventLoopProxy<UserWindowEvent>;
|
||||
|
||||
|
@ -35,75 +36,77 @@ impl DesktopContext {
|
|||
/// onmousedown: move |_| { desktop.drag_window(); }
|
||||
/// ```
|
||||
pub fn drag(&self) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::DragWindow);
|
||||
let _ = self.proxy.send_event(DragWindow);
|
||||
}
|
||||
|
||||
/// set window minimize state
|
||||
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
|
||||
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
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::Visible(visible));
|
||||
let _ = self.proxy.send_event(Visible(visible));
|
||||
}
|
||||
|
||||
/// close window
|
||||
pub fn close(&self) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::CloseWindow);
|
||||
let _ = self.proxy.send_event(CloseWindow);
|
||||
}
|
||||
|
||||
/// set window to focus
|
||||
pub fn focus(&self) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::FocusWindow);
|
||||
let _ = self.proxy.send_event(FocusWindow);
|
||||
}
|
||||
|
||||
/// change window to fullscreen
|
||||
pub fn set_fullscreen(&self, fullscreen: bool) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent::Fullscreen(fullscreen));
|
||||
let _ = self.proxy.send_event(Fullscreen(fullscreen));
|
||||
}
|
||||
|
||||
/// set resizable state
|
||||
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
|
||||
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
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent::CursorVisible(visible));
|
||||
let _ = self.proxy.send_event(CursorVisible(visible));
|
||||
}
|
||||
|
||||
// set cursor grab
|
||||
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
|
||||
pub fn set_title(&self, title: &str) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent::SetTitle(String::from(title)));
|
||||
let _ = self.proxy.send_event(SetTitle(String::from(title)));
|
||||
}
|
||||
|
||||
/// change window to borderless
|
||||
pub fn set_decorations(&self, decoration: bool) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent::SetDecorations(decoration));
|
||||
let _ = self.proxy.send_event(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::sync::Arc;
|
||||
|
@ -7,27 +6,50 @@ use std::sync::Arc;
|
|||
use dioxus_core::{ElementId, EventPriority, UserEvent};
|
||||
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)]
|
||||
struct ImEvent {
|
||||
event: String,
|
||||
mounted_dom_id: u64,
|
||||
// scope: u64,
|
||||
contents: serde_json::Value,
|
||||
}
|
||||
|
||||
pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
||||
let ims: Vec<ImEvent> = serde_json::from_value(val).unwrap();
|
||||
|
||||
let ImEvent {
|
||||
event,
|
||||
mounted_dom_id,
|
||||
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 name = event_name_from_typ(&event);
|
||||
let name = event_name_from_type(&event);
|
||||
let event = make_synthetic_event(&event, contents);
|
||||
|
||||
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 {
|
||||
"copy" => "copy",
|
||||
"cut" => "cut",
|
||||
|
|
|
@ -3,31 +3,28 @@
|
|||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
pub mod cfg;
|
||||
mod controller;
|
||||
pub mod desktop_context;
|
||||
pub mod escape;
|
||||
pub mod events;
|
||||
mod protocol;
|
||||
mod user_window_events;
|
||||
|
||||
use cfg::DesktopConfig;
|
||||
use controller::DesktopController;
|
||||
pub use desktop_context::use_window;
|
||||
use desktop_context::DesktopContext;
|
||||
use dioxus_core::*;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
sync::atomic::AtomicBool,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use events::parse_ipc_message;
|
||||
use tao::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Window, WindowId},
|
||||
window::Window,
|
||||
};
|
||||
pub use wry;
|
||||
pub use wry::application as tao;
|
||||
use wry::{
|
||||
application::{event_loop::EventLoopProxy, window::Fullscreen},
|
||||
webview::RpcRequest,
|
||||
webview::{WebView, WebViewBuilder},
|
||||
};
|
||||
use wry::webview::WebViewBuilder;
|
||||
|
||||
use crate::events::trigger_from_serialized;
|
||||
|
||||
/// 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_url("dioxus://index.html/")
|
||||
.unwrap()
|
||||
.with_rpc_handler(move |_window: &Window, req: RpcRequest| {
|
||||
match req.method.as_str() {
|
||||
"user_event" => {
|
||||
let event = events::trigger_from_serialized(req.params.unwrap());
|
||||
log::trace!("User event: {:?}", event);
|
||||
sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
|
||||
}
|
||||
"initialize" => {
|
||||
is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
let _ = proxy.send_event(UserWindowEvent::Update);
|
||||
}
|
||||
"browser_open" => {
|
||||
println!("browser_open");
|
||||
let data = req.params.unwrap();
|
||||
log::trace!("Open browser: {:?}", data);
|
||||
if let Some(arr) = data.as_array() {
|
||||
if let Some(temp) = arr[0].as_object() {
|
||||
.with_ipc_handler(move |_window: &Window, payload: String| {
|
||||
parse_ipc_message(&payload)
|
||||
.map(|message| match message.method() {
|
||||
"user_event" => {
|
||||
let event = trigger_from_serialized(message.params());
|
||||
log::trace!("User event: {:?}", event);
|
||||
sender.unbounded_send(SchedulerMsg::Event(event)).unwrap();
|
||||
}
|
||||
"initialize" => {
|
||||
is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
let _ = proxy
|
||||
.send_event(user_window_events::UserWindowEvent::Update);
|
||||
}
|
||||
"browser_open" => {
|
||||
println!("browser_open");
|
||||
let data = message.params();
|
||||
log::trace!("Open browser: {:?}", data);
|
||||
if let Some(temp) = data.as_object() {
|
||||
if temp.contains_key("href") {
|
||||
let url = temp.get("href").unwrap().as_str().unwrap();
|
||||
if let Err(e) = webbrowser::open(url) {
|
||||
|
@ -157,55 +155,13 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
})
|
||||
.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)
|
||||
}
|
||||
_ => (),
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
log::warn!("invalid IPC message received");
|
||||
});
|
||||
})
|
||||
.with_custom_protocol(String::from("dioxus"), protocol::desktop_handler)
|
||||
.with_file_drop_handler(move |window, evet| {
|
||||
file_handler
|
||||
.as_ref()
|
||||
|
@ -235,114 +191,8 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
_ => {}
|
||||
},
|
||||
|
||||
Event::UserEvent(_evt) => {
|
||||
//
|
||||
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::UserEvent(user_event) => {
|
||||
user_window_events::handler(user_event, &mut desktop, control_flow)
|
||||
}
|
||||
Event::MainEventsCleared => {}
|
||||
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
packages/desktop/src/protocol.rs
Normal file
47
packages/desktop/src/protocol.rs
Normal 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)
|
||||
}
|
||||
}
|
72
packages/desktop/src/user_window_events.rs
Normal file
72
packages/desktop/src/user_window_events.rs
Normal file
|
@ -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");
|
||||
if (root != null) {
|
||||
window.interpreter = new Interpreter(root);
|
||||
window.rpc.call("initialize");
|
||||
window.ipc.postMessage(serializeIpcMessage("initialize"))
|
||||
}
|
||||
}
|
||||
export class Interpreter {
|
||||
|
@ -207,7 +207,7 @@ export class Interpreter {
|
|||
event.preventDefault();
|
||||
const href = target.getAttribute("href");
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
window.rpc.call("user_event", {
|
||||
window.ipc.postMessage(serializeIpcMessage(
|
||||
"user_event", {
|
||||
event: edit.event_name,
|
||||
mounted_dom_id: parseInt(realId),
|
||||
contents: contents,
|
||||
});
|
||||
}));
|
||||
}
|
||||
};
|
||||
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 = {
|
||||
allowfullscreen: 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")]
|
||||
mod bindings;
|
||||
|
|
Loading…
Reference in a new issue