use std::{
fs::read, path::{Path, PathBuf}, sync::{Arc, Mutex}};
use image::ImageFormat;
use mime_guess::from_path;
use tao::{
dpi::{
LogicalPosition,
LogicalSize,
LogicalUnit,
PhysicalPosition,
PhysicalSize,
PhysicalUnit,
PixelUnit,
Position,
Size
}, event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopBuilder}, monitor::{MonitorHandle, VideoMode}, window::{
self, Fullscreen, Icon, Theme, WindowBuilder, WindowId, WindowSizeConstraints
}
};
use wry::{
http::{
header::CONTENT_TYPE,
HeaderMap,
Request,
Response
},
DragDropEvent,
PageLoadEvent,
ProxyConfig,
Rect,
WebContext, WebViewBuilder
};
enum UserEvent {
CloseWindow(WindowId),
NewTitle(WindowId, String),
NewWindow,
}
fn get_nexium_response_protocol(
request: Request<Vec<u8>>,
index_page: &str,
root_path: &PathBuf
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
let path = request.uri().path();
// Read the file content from file path
let path = if path == "/" {
index_page
} else {
// Removing leading slash
&path[1..]
};
let full_path = std::fs::canonicalize(root_path.join(path))?;
let content = std::fs::read(&full_path)?;
#[cfg(target_os = "windows")]
let headers = "https://wry.localhost".to_string();
#[cfg(not(target_os = "windows"))]
let headers = "wry://localhost".to_string();
// Determine MIME type using `mime_guess`
let mime_type = from_path(&full_path).first_or_octet_stream();
Response::builder()
.header(CONTENT_TYPE, mime_type.as_ref())
.header("Access-Control-Allow-Origin", headers)
.header("Cache-Control", "no-cache, no-store, must-revalidate")
.header("Pragma", "no-cache")
.header("Expires", "0")
.header("Accept-Encoding", "gzip, compress, br, deflate")
.body(content)
.map_err(Into::into)
}
// Enumerate monitors and prompt user to choose one
pub fn prompt_for_monitor(event_loop: &EventLoop<UserEvent>, num:usize) -> MonitorHandle {
let monitor = event_loop
.available_monitors()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {:?}", monitor.name());
monitor
}
pub fn prompt_for_video_mode(monitor: &MonitorHandle,num:usize) -> VideoMode {
let video_mode = monitor
.video_modes()
.nth(num)
.expect("Please enter a valid ID");
println!("Using {}", video_mode);
video_mode
}
struct Nexium{
event_loop:EventLoop<UserEvent>,
webview:wry::WebViewBuilder<'static>,
window: tao::window::WindowBuilder,
}
impl Nexium {
pub fn new(self)->Nexium{
Nexium{}
}
// WebViewBuilder Methods
#[allow(dead_code)]
pub fn use_html(self, html:String){
self.webview.with_html(html);
}
#[allow(dead_code)]
pub fn use_accept_first_mouse(self,accept_first_mouse:bool){
self.webview.with_accept_first_mouse(accept_first_mouse);
}
#[allow(dead_code)]
pub fn use_asynchronous_custom_protocol(self, name:String, func:String){
self.webview.with_asynchronous_custom_protocol(name, move|_,_|{
println!("{}",func)
});
}
#[allow(dead_code)]
pub fn use_autoplay(self,autoplay:bool){
self.webview.with_autoplay(autoplay);
}
#[allow(dead_code)]
pub fn use_back_forward_navigation_gestures(self,gesture:bool){
self.webview.with_back_forward_navigation_gestures(gesture);
}
#[allow(dead_code)]
pub fn use_background_color(self,r:u8,g:u8,b:u8,a:u8){
self.webview.with_background_color((r, g, b, a));
}
#[allow(dead_code)]
pub fn use_bounds(self, x: i32, y: i32, width: i32, height: i32, position_type: &str, size_type: &str) {
let position = match position_type {
"physical" => Position::Physical(PhysicalPosition::new(x, y)),
"logical" => Position::Logical(LogicalPosition::new(x as f64, y as f64)),
_ => {
println!("Invalid position type provided. Use 'physical' or 'logical'.");
return;
}
};
let size = match size_type {
"physical" => Size::Physical(PhysicalSize::new(width as u32, height as u32)),
"logical" => Size::Logical(LogicalSize::new(width as f64, height as f64)),
_ => {
println!("Invalid size type provided. Use 'physical' or 'logical'.");
return;
}
};
self.webview.with_bounds(Rect {
position,
size
});
}
#[allow(dead_code)]
pub fn use_clipboard(self,clipboard:bool){
self.webview.with_clipboard(clipboard);
}
#[allow(dead_code)]
pub fn use_viewer(
self,
name: Option<String>,
index: Option<String>,
root_path: Option<String>
) {
let path = PathBuf::from(root_path.as_deref().unwrap_or(""));
if let Some(index_str) = index {
if index_str.starts_with("http://") || index_str.starts_with("https://") {
self.webview.with_url(index_str);
} else if index_str.starts_with("<") {
self.webview.with_html(index_str);
} else if index_str.ends_with(".html") {
if let Some(root_path_str) = root_path {
let root_path_exists = Path::new(&root_path_str).exists();
if root_path_exists {
self.webview.with_custom_protocol(
name.unwrap_or_default(),
move |request| {
match get_nexium_response_protocol(request, &index_str, &path) {
Ok(response) => response.map(Into::into),
Err(e) => Response::builder()
.header(CONTENT_TYPE, "text/plain")
.status(500)
.body(e.to_string().into_bytes())
.unwrap()
.map(Into::into),
}
},
);
} else {
eprintln!("Error: root_path '{}' does not exist", root_path_str);
}
} else {
eprintln!("Error: root_path is None");
}
}
} else {
eprintln!("Error: index is None");
}
}
#[allow(dead_code)]
pub fn use_devtools(self,devtools: bool){
self.webview.with_devtools(devtools);
}
#[allow(dead_code)]
pub fn use_document_title_changed_handler(self,func:String){
self.webview.with_document_title_changed_handler(move|_|{
println!("{}",func)
});
}
#[allow(dead_code)]
pub fn use_download_completed_handler(self, func: String) {
self.webview.with_download_completed_handler(move |url: String, path: Option<PathBuf>, success: bool| {
println!("{}, {:?}, {}, {}", url, path, success, func);
// You can also use url, path, and success here if needed
});
}
#[allow(dead_code)]
pub fn use_download_started_handler(self, func: String, allow_all:bool) {
self.webview.with_download_started_handler(move |url: String, path: &mut PathBuf| {
println!("{}, {:?}, {}", url, path, func);
// You can also use `url`, `path`, and `func` here as needed
// Example: Allow all downloads
allow_all
});
}
#[allow(dead_code)]
fn use_drag_drop_handler(self, allow_all:bool) {
self.webview.with_drag_drop_handler(move |event: DragDropEvent| {
match event {
DragDropEvent::Enter { paths, position } => {
println!("Drag entered with paths: {:?} at position: {:?}", paths, position);
// Handle the paths and position as needed
}
DragDropEvent::Leave {..} => {
println!("Leave");
// Handle the paths and position as needed
}
DragDropEvent::Over { position } => {
println!("Drag over with position: {:?}",position);
// Handle the paths and position as needed
}
DragDropEvent::Drop { paths, position } => {
println!("Drag dropped with paths: {:?} at position: {:?}", paths, position);
// Handle the paths and position as needed
}
_ => (),
}
// Example: Allow all drag and drop events
allow_all
});
}
#[allow(dead_code)]
pub fn use_focused(self, focused: bool){
self.webview.with_focused(focused);
}
#[allow(dead_code)]
pub fn use_headers(self,headers:HeaderMap){
self.webview.with_headers(headers);
}
#[allow(dead_code)]
pub fn use_hotkeys_zoom(self, zoom: bool){
self.webview.with_hotkeys_zoom(zoom);
}
#[allow(dead_code)]
pub fn use_incognito(self,incognito:bool){
self.webview.with_incognito(incognito);
}
#[allow(dead_code)]
pub fn use_initialization_script(self,js_code:&str){
self.webview.with_initialization_script(js_code);
}
#[allow(dead_code)]
pub fn use_ipc_handler(self){
self.webview.with_ipc_handler(move|_|{});
}
#[allow(dead_code)]
pub fn use_navigation_handler(self, all_navigation:bool){
self.webview.with_navigation_handler(move|url: String| {
println!("Navigating to: {}", url);
all_navigation
});
}
#[allow(dead_code)]
pub fn use_new_window_req_handler(self,all_navigation:bool){
self.webview.with_new_window_req_handler(move|url: String| {
println!("Navigating to: {}", url);
all_navigation
});
}
#[allow(dead_code)]
pub fn use_on_page_load_handler(self) {
self.webview.with_on_page_load_handler(move |event: PageLoadEvent, url: String| {
match event {
PageLoadEvent::Started => println!("Page load started at: {}", url),
PageLoadEvent::Finished => println!("Page load finished at: {}", url),
// If there are other variants, handle them accordingly
}
});
}
#[allow(dead_code)]
pub fn use_proxy_config(self, proxy_config:ProxyConfig){
self.webview.with_proxy_config(proxy_config);
}
#[allow(dead_code)]
pub fn use_transparent(self, transparent:bool){
self.webview.with_transparent(transparent);
}
#[allow(dead_code)]
pub fn use_url(self, url:String){
self.webview.with_url(url);
}
#[allow(dead_code)]
pub fn use_url_and_headers(self,url:String, header:HeaderMap){
self.webview.with_url_and_headers(url, header);
}
#[allow(dead_code)]
pub fn use_user_agent(self, user_agen:String){
self.webview.with_user_agent(&user_agen);
}
#[allow(dead_code)]
pub fn use_visible(self, visible:bool){
self.webview.with_visible(visible);
}
#[allow(dead_code)]
pub fn use_web_context(self, web_context:& mut WebContext){
self.webview.with_web_context(web_context);
}
#[allow(dead_code)]
// WindowBuilder Methods
pub fn use_always_on_bottom(self,always_on_bottom: bool){
self.window.with_always_on_bottom(always_on_bottom);
}
#[allow(dead_code)]
pub fn use_always_on_top(self,always_on_top:bool){
self.window.with_always_on_top(always_on_top);
}
#[allow(dead_code)]
pub fn use_closable(self,closable:bool){
self.window.with_closable(closable);
}
#[allow(dead_code)]
pub fn use_content_protection(self,protected:bool){
self.window.with_content_protection(protected);
}
#[allow(dead_code)]
pub fn use_decorations(self,decorations:bool){
self.window.with_decorations(decorations);
}
#[allow(dead_code)]
pub fn use_fullscreen(self, fullscreen_type: &str) {
let monitors: Vec<_> = self.event_loop.available_monitors().collect();
if monitors.is_empty() {
println!("No monitors available.");
return;
}
let num = monitors.len();
let fullscreen = match fullscreen_type {
"exclusive" => {
let monitor = prompt_for_monitor(&self.event_loop, num);
let video_mode = prompt_for_video_mode(&monitor, num);
Some(Fullscreen::Exclusive(video_mode))
}
"borderless" => {
let monitor = prompt_for_monitor(&self.event_loop, num);
Some(Fullscreen::Borderless(Some(monitor)))
}
"default" => Some(Fullscreen::Borderless(None)),
_ => {
println!("Invalid fullscreen type provided. Use 'exclusive' or 'borderless'.");
return;
},
};
self.window.with_fullscreen(fullscreen);
}
#[allow(dead_code)]
pub fn use_inner_size(self,width:i32,height:i32, size_type:&str){
let size = match size_type {
"physical" => Size::Physical(PhysicalSize::new(width as u32, height as u32)),
"logical" => Size::Logical(LogicalSize::new(width as f64, height as f64)),
_ => {
println!("Invalid size type provided. Use 'physical' or 'logical'.");
return;
}
};
self.window.with_inner_size(size);
}
#[allow(dead_code)]
pub fn use_inner_size_constraints(
self,
min_width: i32,
min_height: i32,
max_width: i32,
max_height: i32,
constraints_type: &str
) {
let (min_width, min_height, max_width, max_height) = match constraints_type {
"logical" => (
PixelUnit::Logical(LogicalUnit(min_width as f64)),
PixelUnit::Logical(LogicalUnit(min_height as f64)),
PixelUnit::Logical(LogicalUnit(max_width as f64)),
PixelUnit::Logical(LogicalUnit(max_height as f64)),
),
"physical" => (
PixelUnit::Physical(PhysicalUnit(min_width)),
PixelUnit::Physical(PhysicalUnit(min_height)),
PixelUnit::Physical(PhysicalUnit(max_width)),
PixelUnit::Physical(PhysicalUnit(max_height)),
),
_ => {
eprintln!("Error: Invalid constraints_type '{}'", constraints_type);
return; // Early return on invalid constraints_type
}
};
self.window.with_inner_size_constraints(WindowSizeConstraints::new(
Some(min_width), Some(min_height), Some(max_width), Some(max_height)
));
}
#[allow(dead_code)]
pub fn use_max_inner_size(self,width:i32,height:i32, size_type:&str){
let size = match size_type {
"physical" => Size::Physical(PhysicalSize::new(width as u32, height as u32)),
"logical" => Size::Logical(LogicalSize::new(width as f64, height as f64)),
_ => {
println!("Invalid size type provided. Use 'physical' or 'logical'.");
return;
}
};
self.window.with_max_inner_size(size);
}
#[allow(dead_code)]
pub fn use_maximizable(self,maximizable: bool){
self.window.with_maximizable(maximizable);
}
#[allow(dead_code)]
pub fn use_maximized(self,maximized: bool){
self.window.with_maximized(maximized);
}
#[allow(dead_code)]
pub fn use_min_inner_size(self,width:i32,height:i32, min_size_type:&str){
let size = match min_size_type {
"physical" => Size::Physical(PhysicalSize::new(width as u32, height as u32)),
"logical" => Size::Logical(LogicalSize::new(width as f64, height as f64)),
_ => {
println!("Invalid size type provided. Use 'physical' or 'logical'.");
return;
}
};
self.window.with_min_inner_size(size);
}
#[allow(dead_code)]
pub fn use_minimizable(self,minimizable: bool){
self.window.with_minimizable(minimizable);
}
#[allow(dead_code)]
pub fn use_position(self, x:i32, y:i32, position_type:&str){
let position = match position_type {
"physical" => Position::Physical(PhysicalPosition::new(x, y)),
"logical" => Position::Logical(LogicalPosition::new(x as f64, y as f64)),
_ => {
println!("Invalid position type provided. Use 'physical' or 'logical'.");
return;
}
};
self.window.with_position(position);
}
#[allow(dead_code)]
pub fn use_resizable(self,resizable: bool){
self.window.with_resizable(resizable);
}
#[allow(dead_code)]
pub fn use_theme(self,theme_type:&str){
let theme = match theme_type {
"Light" => Theme::Light,
"dark" => Theme::Dark,
"system" => Theme::default(),
_ => {
println!("Invalid theme type provided. Use 'Light' or 'dark' or 'system'.");
return;
}
};
self.window.with_theme(Some(theme));
}
#[allow(dead_code)]
pub fn use_title(self,title:&str){
self.window.with_title(title);
}
#[allow(dead_code)]
pub fn use_visible_on_all_workspaces(self,visible: bool){
self.window.with_visible_on_all_workspaces(visible);
}
#[allow(dead_code)]
pub fn use_window_icon(self,icon:&str){
let icon_object = match read(icon) {
Err(_) => None,
Ok(bytes) => {
let imagebuffer =
match image::load_from_memory_with_format(&bytes, ImageFormat::Png) {
Err(_) => None,
Ok(loaded) => {
let imagebuffer = loaded.to_rgba8();
let (icon_width, icon_height) = imagebuffer.dimensions();
let icon_rgba = imagebuffer.into_raw();
match Icon::from_rgba(icon_rgba, icon_width, icon_height) {
Err(_) => None,
Ok(icon) => Some(icon),
}
}
};
imagebuffer
}
};
self.window.with_window_icon(icon_object);
}
#[allow(dead_code)]
pub fn on_applications_startup_event(self, _func:String){}
#[allow(dead_code)]
pub fn on_applications_shutdown_event(self, _func:String){}
#[allow(dead_code)]
pub fn run(mut self, _func: String) {
self.event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
let _proxy = self.event_loop.create_proxy();
let window_builder = WindowBuilder::new();
self.window = window_builder;
let main_window = self.window.build(&self.event_loop).unwrap();
let builder = WebViewBuilder::new(&main_window);
self.webview = builder;
let _ = self.webview.build().unwrap();
self.event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} = event
{
*control_flow = ControlFlow::Exit
}
});
}
}
fn main() {
let app = Nexium::new();
app.run();
}
#Python
from typing import Callable
class PyNexium:
def __init__(self) -> None:
pass
def set_app_config(self):
pass
def with_always_on_bottom(self):
pass
def with_always_on_top(self):
pass
def with_closable(self):
pass
def with_content_protection(self):
pass
def with_decorations(self):
pass
def with_focused(self):
pass
def with_fullscreen(self):
pass
def with_inner_size(self):
pass
def with_inner_size_constraints(self):
pass
def with_max_inner_size(self):
pass
def with_maximizable(self):
pass
def with_maximized(self):
pass
def with_min_inner_size(self):
pass
def with_minimizable(self):
pass
def with_position(self):
pass
def with_resizable(self):
pass
def with_theme(self):
pass
def with_title(self):
pass
def with_transparent(self):
pass
def with_visible(self):
pass
def with_visible_on_all_workspaces(self):
pass
def with_window_icon(self):
pass
def with_accept_first_mouse(self):
pass
def with_asynchronous_custom_protocol(self):
pass
def with_autoplay(self):
pass
def with_back_forward_navigation_gestures(self):
pass
def with_background_color(self):
pass
def with_bounds(self):
pass
def with_clipboard(self):
pass
def with_custom_protocol(self):
pass
def with_devtools(self):
pass
def with_document_title_changed_handler(self):
pass
def with_download_completed_handler(self):
pass
def with_download_started_handler(self):
pass
def with_drag_drop_handler(self):
pass
def with_focused(self):
pass
def with_headers(self):
pass
def with_hotkeys_zoom(self):
pass
def with_html(self):
pass
def with_incognito(self):
pass
def with_initialization_script(self):
pass
def with_ipc_handler(self):
pass
def with_navigation_handler(self):
pass
def with_new_window_req_handler(self):
pass
def with_on_page_load_handler(self):
pass
def with_proxy_config(self):
pass
def with_transparent(self):
pass
def with_url(self):
pass
def with_url_and_headers(self):
pass
def with_user_agent(self):
pass
def with_visible(self):
pass
def with_web_context(self):
pass
def pynexium_command(self):
pass
def pynexium_jscommand(self):
pass
def pynexium_realtime_command(self):
pass
def pynexium__realtime_jscommand(self):
pass
def on_applications_startup_event(self, func:Callable):
pass
def on_applications_shutdown_event(self, func:Callable):
pass
def run(self):
pass
# Example Usage
app = PyNexium()
app.set_app_config() # some App Config
@app.pynexium_command
def some_backend_command(): # some command that can be invoked from frontend
pass
@app.pynexium_realtime_command
def some_backend_command(): # some command that can be invoked from frontend
while True:
print("")
@app.pynexium_command
def some_backend_command():
app.pynexium_jscommand() # # call some javascript function that can be invoked from backendend
def startup_event():
print('Applications started')
def shutdown_event():
print('Applications stopped')
app.on_applications_startup_event(func=startup_event)
app.on_applications_shutdown_event(func=shutdown_event)
if __name__ == "__main__":
app.run()