wavecraft_dev_server/
webview.rs1use crate::app::AppState;
7use crate::assets;
8use std::borrow::Cow;
9use std::sync::Arc;
10use tao::{
11 event::{Event, WindowEvent},
12 event_loop::{ControlFlow, EventLoop},
13 window::WindowBuilder,
14};
15use tracing::{debug, error, warn};
16use wavecraft_bridge::IpcHandler;
17use wry::WebViewBuilder;
18
19const IPC_PRIMITIVES_JS: &str = include_str!("js/ipc-primitives.js");
21
22pub fn run_app(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error>> {
26 let event_loop = EventLoop::new();
28
29 let window = WindowBuilder::new()
31 .with_title("VstKit Desktop POC")
32 .with_inner_size(tao::dpi::LogicalSize::new(800, 600))
33 .build(&event_loop)
34 .map_err(|e| format!("Failed to create window: {}", e))?;
35
36 let handler = IpcHandler::new((*state).clone());
38
39 let _state = state;
41
42 let (response_tx, response_rx) = std::sync::mpsc::channel::<String>();
44
45 let webview = WebViewBuilder::new()
47 .with_initialization_script(IPC_PRIMITIVES_JS)
49 .with_custom_protocol("vstkit".to_string(), move |_webview, request| {
51 handle_asset_request(request)
52 })
53 .with_ipc_handler(move |request: wry::http::Request<String>| {
55 let message = request.body();
56 let response = handler.handle_json(message);
57
58 debug!("IPC Request: {}", message);
60 debug!("IPC Response: {}", response);
61
62 let _ = response_tx.send(response);
64 })
65 .build(&window)?;
66
67 webview.load_url("vstkit://localhost/index.html")?;
69
70 event_loop.run(move |event, _, control_flow| {
72 *control_flow = ControlFlow::Poll;
74
75 while let Ok(response) = response_rx.try_recv() {
77 let escaped_response = response
79 .replace('\\', "\\\\")
80 .replace('\'', "\\'")
81 .replace('\n', "\\n")
82 .replace('\r', "\\r");
83
84 let js_code = format!(
86 "globalThis.__WAVECRAFT_IPC__._receive('{}');",
87 escaped_response
88 );
89
90 if let Err(e) = webview.evaluate_script(&js_code) {
91 error!("Failed to send response to UI: {}", e);
92 }
93 }
94
95 if let Event::WindowEvent {
96 event: WindowEvent::CloseRequested,
97 ..
98 } = event
99 {
100 *control_flow = ControlFlow::Exit;
101 }
102 });
103}
104
105fn handle_asset_request(
107 request: wry::http::Request<Vec<u8>>,
108) -> wry::http::Response<Cow<'static, [u8]>> {
109 let path = request.uri().path();
110
111 match assets::get_asset(path) {
112 Some((content, mime_type)) => wry::http::Response::builder()
113 .status(200)
114 .header("Content-Type", mime_type)
115 .header("Access-Control-Allow-Origin", "*")
116 .body(content)
117 .unwrap(),
118 None => {
119 warn!("Asset not found: {}", path);
120 wry::http::Response::builder()
121 .status(404)
122 .body(Cow::Borrowed(b"Not Found" as &[u8]))
123 .unwrap()
124 }
125 }
126}