lib.rs
raw
//! 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<T> DoneServing for (tokio::runtime::Runtime, tokio::task::JoinHandle<T>) {
fn done_serving(&self) -> bool {
self.1.is_finished()
}
}
#[cfg(feature = "asyncstd")]
impl<T> DoneServing
for (
std::sync::Arc<std::sync::Mutex<bool>>,
async_std::task::JoinHandle<T>,
)
{
fn done_serving(&self) -> bool {
let guard = self.0.lock().unwrap();
*guard
}
}
#[cfg(feature = "smol")]
impl<T> DoneServing for smol::Task<T> {
fn done_serving(&self) -> bool {
self.is_finished()
}
}
#[cfg(all(feature = "tokio", not(feature = "asyncstd"), not(feature = "smol")))]
fn run_server<F>(serve: F) -> impl DoneServing
where
F: Future<Output = ()> + 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<F>(serve: F) -> impl DoneServing
where
F: Future<Output = ()> + 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<F>(serve: F) -> impl DoneServing
where
F: Future<Output = ()> + 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<F>(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`]: <https://docs.rs/wry/latest/wry/application/window/struct.WindowBuilder.html>
pub fn launch_with_windowbuilder<F>(
window_builder: WindowBuilder,
entrypoint: impl EntryPoint,
serve: F,
) -> wry::Result<()>
where
F: Future<Output = ()> + 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<F>(title: impl AsRef<str>, entrypoint: impl EntryPoint, serve: F) -> wry::Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
launch_with_windowbuilder(
WindowBuilder::new().with_title(title.as_ref()),
entrypoint,
serve,
)
}