//! Drteeth is a library that directly translates web application //! architecture into a desktop app. A Drteeth app is litarally a web //! application server and client bundled into a single binary //! file. As such, all web application tools and skills translate //! directly to use in Drteeth. //! //! Drteeth will help you quickly build an app that uses web //! techologies for a slick user interface and runs on the desktop in //! its own window, while the application logic is written in Rust and //! has a normal application's access to operating system services. //! //! Drteeth works in concert with tokio, async-std, or smol, and has //! `tokio`, `asyncstd`, and `smol` features to select which of the //! three you wish to use. The `examples` feature is required to //! successfully compile any of the examples. use std::{future::Future, net::SocketAddr}; use wry::{ application::{ event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, }, webview::WebViewBuilder, }; pub use wry::application::window::{self, WindowBuilder}; /// Types which implement this trait can be passed to the launch /// functions as the initial address to load in the webview. /// /// It is strongly suggested to set up your server to only listen on a /// loopback address. If you choose to have the server listen on a /// routable address, take care to employ an authetication and /// authorization mechanism in the server's request handlers. Exposing /// the server on a routable address without authorization checks /// would create an enormous security hole. pub trait EntryPoint { fn url(&self) -> String; } impl EntryPoint for SocketAddr { fn url(&self) -> String { format!("http://{}:{}/", self.ip(), self.port()) } } impl EntryPoint for &SocketAddr { fn url(&self) -> String { format!("http://{}:{}/", self.ip(), self.port()) } } impl EntryPoint for String { fn url(&self) -> String { self.clone() } } impl EntryPoint for &str { fn url(&self) -> String { String::from(*self) } } trait DoneServing { fn done_serving(&self) -> bool; } #[cfg(feature = "tokio")] impl DoneServing for (tokio::runtime::Runtime, tokio::task::JoinHandle) { fn done_serving(&self) -> bool { self.1.is_finished() } } #[cfg(feature = "asyncstd")] impl DoneServing for ( std::sync::Arc>, async_std::task::JoinHandle, ) { fn done_serving(&self) -> bool { let guard = self.0.lock().unwrap(); *guard } } #[cfg(feature = "smol")] impl DoneServing for smol::Task { fn done_serving(&self) -> bool { self.is_finished() } } #[cfg(all(feature = "tokio", not(feature = "asyncstd"), not(feature = "smol")))] fn run_server(serve: F) -> impl DoneServing where F: Future + Send + 'static, { let runtime = tokio::runtime::Runtime::new().unwrap(); let serving = runtime.spawn(serve); (runtime, serving) } #[cfg(all(not(feature = "tokio"), feature = "asyncstd", not(feature = "smol")))] fn run_server(serve: F) -> impl DoneServing where F: Future + Send + 'static, { use std::sync::{Arc, Mutex}; let finished = Arc::new(Mutex::new(false)); ( finished.clone(), async_std::task::spawn(async move { serve.await; let mut guard = finished.lock().unwrap(); *guard = true; }), ) } #[cfg(all(not(feature = "tokio"), not(feature = "asyncstd"), feature = "smol"))] fn run_server(serve: F) -> impl DoneServing where F: Future + Send + 'static, { if let Err(_) = std::env::var("SMOL_THREADS") { std::env::set_var( "SMOL_THREADS", std::thread::available_parallelism() .unwrap_or(std::num::NonZeroUsize::new(1).unwrap()) .to_string(), ); } smol::spawn(serve) } #[cfg(any( all(feature = "tokio", feature = "asyncstd"), all(feature = "tokio", feature = "smol"), all(feature = "asyncstd", feature = "smol"), not(any(feature = "tokio", feature = "asyncstd", feature = "smol")) ))] fn run_server(serve: F) -> impl DoneServing { compile_error!("Exactly one of the tokio, asyncstd, or smol features must be enabled.") } /// Start the webview window, the windowing system event loop, and the /// async event loop running. This function accepts a Wry /// [`WindowBuilder`] (re-exported here for convenience) allowing for /// detailed configuration of the properties of the window. Use this /// version if you need to set window traits other than the title, /// such as default size, position, or icon. /// [`WindowBuilder`]: pub fn launch_with_windowbuilder( window_builder: WindowBuilder, entrypoint: impl EntryPoint, serve: F, ) -> wry::Result<()> where F: Future + Send + 'static, { let serving = run_server(serve); let entrypoint = entrypoint.url(); let event_loop = EventLoop::new(); let window = window_builder.build(&event_loop)?; let _webview = WebViewBuilder::new(window)? .with_url(&entrypoint)? .build()?; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; if serving.done_serving() { *control_flow = ControlFlow::Exit; } else { match event { Event::NewEvents(StartCause::Init) => {} Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit, _ => {} } } }); } /// Start the webview window, the windowing system event loop, and the /// async event loop running. pub fn launch(title: impl AsRef, entrypoint: impl EntryPoint, serve: F) -> wry::Result<()> where F: Future + Send + 'static, { launch_with_windowbuilder( WindowBuilder::new().with_title(title.as_ref()), entrypoint, serve, ) }