mirror of
https://github.com/marcoallegretti/WEFT_OS.git
synced 2026-03-27 09:23:09 +00:00
228 lines
7.4 KiB
Rust
228 lines
7.4 KiB
Rust
|
|
#![cfg(feature = "servo-embed")]
|
||
|
|
|
||
|
|
use std::path::Path;
|
||
|
|
use std::rc::Rc;
|
||
|
|
use std::sync::{Arc, Mutex};
|
||
|
|
|
||
|
|
use servo::{
|
||
|
|
EventLoopWaker, ServoBuilder, ServoDelegate, ServoUrl, WebViewBuilder, WebViewDelegate,
|
||
|
|
};
|
||
|
|
use winit::{
|
||
|
|
application::ApplicationHandler,
|
||
|
|
event::WindowEvent,
|
||
|
|
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
|
||
|
|
window::{Window, WindowAttributes, WindowId},
|
||
|
|
};
|
||
|
|
|
||
|
|
// ── Event loop waker ──────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
#[derive(Clone)]
|
||
|
|
struct WeftEventLoopWaker {
|
||
|
|
proxy: Arc<Mutex<EventLoopProxy<ServoWake>>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
struct ServoWake;
|
||
|
|
|
||
|
|
impl EventLoopWaker for WeftEventLoopWaker {
|
||
|
|
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
|
||
|
|
Box::new(self.clone())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn wake(&self) {
|
||
|
|
let _ = self
|
||
|
|
.proxy
|
||
|
|
.lock()
|
||
|
|
.unwrap_or_else(|p| p.into_inner())
|
||
|
|
.send_event(ServoWake);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Servo delegate ────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
struct WeftServoDelegate;
|
||
|
|
|
||
|
|
impl ServoDelegate for WeftServoDelegate {
|
||
|
|
fn notify_error(&self, error: servo::ServoError) {
|
||
|
|
tracing::error!(?error, "Servo error");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── WebView delegate ──────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
struct WeftWebViewDelegate {
|
||
|
|
redraw_requested: Arc<std::sync::atomic::AtomicBool>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl WebViewDelegate for WeftWebViewDelegate {
|
||
|
|
fn notify_new_frame_ready(&self, _webview: servo::WebView) {
|
||
|
|
self.redraw_requested
|
||
|
|
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Application state ─────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
struct App {
|
||
|
|
url: ServoUrl,
|
||
|
|
window: Option<Arc<Window>>,
|
||
|
|
servo: Option<servo::Servo>,
|
||
|
|
webview: Option<servo::WebView>,
|
||
|
|
redraw_requested: Arc<std::sync::atomic::AtomicBool>,
|
||
|
|
waker: WeftEventLoopWaker,
|
||
|
|
shutting_down: bool,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl App {
|
||
|
|
fn new(url: ServoUrl, waker: WeftEventLoopWaker) -> Self {
|
||
|
|
Self {
|
||
|
|
url,
|
||
|
|
window: None,
|
||
|
|
servo: None,
|
||
|
|
webview: None,
|
||
|
|
redraw_requested: Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||
|
|
waker,
|
||
|
|
shutting_down: false,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn blit_to_window(window: &Arc<Window>, rendering_context: &servo::SoftwareRenderingContext) {
|
||
|
|
let size = window.inner_size();
|
||
|
|
let Some(pixels) = rendering_context.read_pixels() else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
let ctx = softbuffer::Context::new(Arc::clone(window)).expect("softbuffer context");
|
||
|
|
let mut surface =
|
||
|
|
softbuffer::Surface::new(&ctx, Arc::clone(window)).expect("softbuffer surface");
|
||
|
|
let _ = surface.resize(
|
||
|
|
std::num::NonZeroU32::new(size.width).unwrap_or(std::num::NonZeroU32::new(1).unwrap()),
|
||
|
|
std::num::NonZeroU32::new(size.height).unwrap_or(std::num::NonZeroU32::new(1).unwrap()),
|
||
|
|
);
|
||
|
|
let Ok(mut buf) = surface.buffer_mut() else {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
for (dst, src) in buf.iter_mut().zip(pixels.chunks(4)) {
|
||
|
|
*dst = u32::from_be_bytes([0, src[0], src[1], src[2]]);
|
||
|
|
}
|
||
|
|
let _ = buf.present();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl ApplicationHandler<ServoWake> for App {
|
||
|
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||
|
|
if self.window.is_some() {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let attrs = WindowAttributes::default().with_title("WEFT Shell");
|
||
|
|
let window = Arc::new(
|
||
|
|
event_loop
|
||
|
|
.create_window(attrs)
|
||
|
|
.expect("failed to create shell window"),
|
||
|
|
);
|
||
|
|
let size = window.inner_size();
|
||
|
|
self.window = Some(Arc::clone(&window));
|
||
|
|
|
||
|
|
let servo = ServoBuilder::default()
|
||
|
|
.event_loop_waker(Box::new(self.waker.clone()))
|
||
|
|
.build();
|
||
|
|
|
||
|
|
servo.set_delegate(Rc::new(WeftServoDelegate));
|
||
|
|
|
||
|
|
let rendering_context = Rc::new(
|
||
|
|
servo::SoftwareRenderingContext::new(servo::euclid::Size2D::new(
|
||
|
|
size.width,
|
||
|
|
size.height,
|
||
|
|
))
|
||
|
|
.expect("SoftwareRenderingContext"),
|
||
|
|
);
|
||
|
|
|
||
|
|
let webview = WebViewBuilder::new(&servo, Rc::clone(&rendering_context))
|
||
|
|
.delegate(Rc::new(WeftWebViewDelegate {
|
||
|
|
redraw_requested: Arc::clone(&self.redraw_requested),
|
||
|
|
}))
|
||
|
|
.url(self.url.clone())
|
||
|
|
.build();
|
||
|
|
|
||
|
|
self.servo = Some(servo);
|
||
|
|
self.webview = Some(webview);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: ServoWake) {
|
||
|
|
if let Some(servo) = &self.servo {
|
||
|
|
servo.spin_event_loop();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||
|
|
if self.shutting_down {
|
||
|
|
event_loop.exit();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if let Some(servo) = &self.servo {
|
||
|
|
servo.spin_event_loop();
|
||
|
|
}
|
||
|
|
if self
|
||
|
|
.redraw_requested
|
||
|
|
.swap(false, std::sync::atomic::Ordering::Relaxed)
|
||
|
|
{
|
||
|
|
if let Some(w) = &self.window {
|
||
|
|
w.request_redraw();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn window_event(
|
||
|
|
&mut self,
|
||
|
|
event_loop: &ActiveEventLoop,
|
||
|
|
_window_id: WindowId,
|
||
|
|
event: WindowEvent,
|
||
|
|
) {
|
||
|
|
match event {
|
||
|
|
WindowEvent::RedrawRequested => {
|
||
|
|
if let (Some(window), Some(servo)) = (&self.window, &self.servo) {
|
||
|
|
if let Some(wv) = &self.webview {
|
||
|
|
let rc = wv.rendering_context();
|
||
|
|
Self::blit_to_window(window, rc);
|
||
|
|
}
|
||
|
|
servo.spin_event_loop();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
WindowEvent::Resized(new_size) => {
|
||
|
|
if let Some(wv) = &self.webview {
|
||
|
|
wv.resize(servo::euclid::Size2D::new(new_size.width, new_size.height));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
WindowEvent::CloseRequested => {
|
||
|
|
self.shutting_down = true;
|
||
|
|
if let Some(servo) = &self.servo {
|
||
|
|
servo.start_shutting_down();
|
||
|
|
}
|
||
|
|
event_loop.exit();
|
||
|
|
}
|
||
|
|
_ => {}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Public entry point ────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
pub fn run(html_path: &Path, _ws_port: u16) -> anyhow::Result<()> {
|
||
|
|
let url_str = format!("file://{}", html_path.display());
|
||
|
|
let url =
|
||
|
|
ServoUrl::parse(&url_str).map_err(|e| anyhow::anyhow!("invalid URL {url_str}: {e}"))?;
|
||
|
|
|
||
|
|
let event_loop = EventLoop::<ServoWake>::with_user_event()
|
||
|
|
.build()
|
||
|
|
.map_err(|e| anyhow::anyhow!("event loop: {e}"))?;
|
||
|
|
|
||
|
|
let waker = WeftEventLoopWaker {
|
||
|
|
proxy: Arc::new(Mutex::new(event_loop.create_proxy())),
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut app = App::new(url, waker);
|
||
|
|
event_loop
|
||
|
|
.run_app(&mut app)
|
||
|
|
.map_err(|e| anyhow::anyhow!("event loop run: {e}"))
|
||
|
|
}
|