From 3ee2f283d89e22fbe7d2bc9a863d6d03cd3fe7e6 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 11 Mar 2026 20:46:53 +0100 Subject: [PATCH] feat(servo-shell): dispatch shell client event queue in servo event loop Wire ShellClient into App so its Wayland event queue is dispatched each frame via about_to_wait. This ensures configure, focus_changed, and window_closed events from the compositor are processed. window_closed now triggers a clean Servo shutdown. The EGL rendering path (WindowRenderingContext + surfman eglSwapBuffers) produces frames transparently via Mesa DMA-BUF buffer sharing; no explicit zwp_linux_dmabuf_v1 code is required in the shell. Remaining: ZweftShellWindowV1 is created with surface=null; sharing the winit wl_surface with the shell client connection is not currently feasible without refactoring to a single shared Wayland connection. --- crates/weft-servo-shell/SERVO_PIN.md | 25 +++++++++++++++++-------- crates/weft-servo-shell/src/embedder.rs | 23 +++++++++++++++++++++-- crates/weft-servo-shell/src/main.rs | 8 +++++--- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/crates/weft-servo-shell/SERVO_PIN.md b/crates/weft-servo-shell/SERVO_PIN.md index a92617e..0b527b3 100644 --- a/crates/weft-servo-shell/SERVO_PIN.md +++ b/crates/weft-servo-shell/SERVO_PIN.md @@ -64,19 +64,28 @@ On Fedora/RHEL: `mesa-libGL-devel openssl-devel dbus-devel systemd-devel libxkbc Default: `SoftwareRenderingContext` (CPU rasterisation) blitted to a `softbuffer`-backed winit window. -EGL path (scaffolded): set `WEFT_EGL_RENDERING=1` at runtime. The embedder -attempts `WindowRenderingContext::new` using the winit display and window -handles. If construction fails it falls back to software automatically. -When the EGL path is active Servo presents directly to the EGL surface; -the softbuffer blit is skipped. Full DMA-BUF export to the Wayland -compositor is not yet wired (`RenderingCtx::Egl` blit body is a no-op). +EGL path: set `WEFT_EGL_RENDERING=1` at runtime. The embedder attempts +`WindowRenderingContext::new` using the winit display and window handles. +If construction fails it falls back to software automatically. +When the EGL path is active Servo presents directly to the EGL surface via +surfman's `eglSwapBuffers`; the softbuffer blit is skipped. Mesa handles +DMA-BUF buffer sharing with the compositor transparently. ## Known gaps at this pin - **GAP-1**: ~~Wayland input events not forwarded to Servo~~ **Resolved** — keyboard and mouse events forwarded via `webview.notify_input_event`; key mapping in `keyutils.rs`. -- **GAP-2**: EGL `WindowRenderingContext` path scaffolded (`WEFT_EGL_RENDERING=1`); - DMA-BUF export to the Wayland compositor (linux-dmabuf-unstable-v1) not yet wired. +- **GAP-2**: EGL `WindowRenderingContext` path scaffolded (`WEFT_EGL_RENDERING=1`). + When EGL is active, Servo presents frames via surfman's `eglSwapBuffers`; Mesa handles + DMA-BUF buffer sharing with the compositor transparently — no explicit + `zwp_linux_dmabuf_v1` code is needed in the shell for basic rendering. + The `zweft_shell_manager_v1` event queue is now dispatched each frame so `configure`, + `focus_changed`, and `window_closed` events are processed; `window_closed` triggers a + clean Servo shutdown. + Remaining gap: the `ZweftShellWindowV1` is created with `surface = null`; the winit + `wl_surface` is not yet associated with the shell window slot (requires sharing a single + Wayland connection between winit and the shell client, which is not currently feasible + without significant refactoring). - **GAP-3**: WebGPU adapter on Mesa may fail CTS — validation task, requires Mesa GPU hardware. - **GAP-4**: ~~CSS Grid~~ **Grid resolved** (Taffy-backed, fully wired). ~~CSS `backdrop-filter` unimplemented~~ **`backdrop-filter` resolved** (servo/servo issue diff --git a/crates/weft-servo-shell/src/embedder.rs b/crates/weft-servo-shell/src/embedder.rs index 0a5dc53..d613094 100644 --- a/crates/weft-servo-shell/src/embedder.rs +++ b/crates/weft-servo-shell/src/embedder.rs @@ -99,6 +99,7 @@ struct App { shutting_down: bool, modifiers: ModifiersState, cursor_pos: servo::euclid::default::Point2D, + shell_client: Option, } impl App { @@ -107,6 +108,7 @@ impl App { waker: WeftEventLoopWaker, ws_port: u16, app_rx: mpsc::Receiver, + shell_client: Option, ) -> Self { Self { url, @@ -123,6 +125,7 @@ impl App { shutting_down: false, modifiers: ModifiersState::default(), cursor_pos: servo::euclid::default::Point2D::zero(), + shell_client, } } @@ -246,6 +249,18 @@ impl ApplicationHandler for App { event_loop.exit(); return; } + if let Some(sc) = &mut self.shell_client { + match sc.dispatch_pending() { + Ok(false) => { + self.shutting_down = true; + if let Some(servo) = &self.servo { + servo.start_shutting_down(); + } + } + Err(e) => tracing::warn!("shell client dispatch error: {e}"), + Ok(true) => {} + } + } while let Ok(cmd) = self.app_rx.try_recv() { match cmd { crate::appd_ws::AppdCmd::Launch { session_id, app_id } => { @@ -418,7 +433,11 @@ fn app_store_roots() -> Vec { roots } -pub fn run(html_path: &Path, ws_port: u16) -> anyhow::Result<()> { +pub fn run( + html_path: &Path, + ws_port: u16, + shell_client: Option, +) -> anyhow::Result<()> { let url_str = format!("file://{}", html_path.display()); let raw_url = ServoUrl::parse(&url_str).map_err(|e| anyhow::anyhow!("invalid URL {url_str}: {e}"))?; @@ -436,7 +455,7 @@ pub fn run(html_path: &Path, ws_port: u16) -> anyhow::Result<()> { let waker_for_thread = waker.clone(); crate::appd_ws::spawn_appd_listener(ws_port, app_tx, Box::new(move || waker_for_thread.wake())); - let mut app = App::new(url, waker, ws_port, app_rx); + let mut app = App::new(url, waker, ws_port, app_rx, shell_client); event_loop .run_app(&mut app) .map_err(|e| anyhow::anyhow!("event loop run: {e}")) diff --git a/crates/weft-servo-shell/src/main.rs b/crates/weft-servo-shell/src/main.rs index bab4280..b04fd74 100644 --- a/crates/weft-servo-shell/src/main.rs +++ b/crates/weft-servo-shell/src/main.rs @@ -34,7 +34,7 @@ fn run() -> anyhow::Result<()> { let ws_port = appd_ws_port(); tracing::info!(ws_port, "appd WebSocket port"); - let _shell = match shell_client::ShellClient::connect() { + let shell = match shell_client::ShellClient::connect() { Ok(c) => { tracing::info!("shell window registered with compositor"); Some(c) @@ -45,7 +45,7 @@ fn run() -> anyhow::Result<()> { } }; - embed_servo(&wayland_display, &html_path, ws_port) + embed_servo(&wayland_display, &html_path, ws_port, shell) } fn system_ui_html_path() -> anyhow::Result { @@ -86,12 +86,14 @@ fn embed_servo( _wayland_display: &str, html_path: &std::path::Path, ws_port: u16, + shell_client: Option, ) -> anyhow::Result<()> { #[cfg(feature = "servo-embed")] - return embedder::run(html_path, ws_port); + return embedder::run(html_path, ws_port, shell_client); #[cfg(not(feature = "servo-embed"))] { + let _ = shell_client; tracing::warn!( path = %html_path.display(), ws_port,