diff --git a/crates/weft-app-shell/src/embedder.rs b/crates/weft-app-shell/src/embedder.rs index 0cff4d2..6fae473 100644 --- a/crates/weft-app-shell/src/embedder.rs +++ b/crates/weft-app-shell/src/embedder.rs @@ -130,12 +130,17 @@ impl App { 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 Ok(ctx) = softbuffer::Context::new(Arc::clone(window)) else { + tracing::warn!("softbuffer context creation failed; skipping frame"); + return; + }; + let Ok(mut surface) = softbuffer::Surface::new(&ctx, Arc::clone(window)) else { + tracing::warn!("softbuffer surface creation failed; skipping frame"); + return; + }; 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()), + std::num::NonZeroU32::new(size.width).unwrap_or(std::num::NonZeroU32::MIN), + std::num::NonZeroU32::new(size.height).unwrap_or(std::num::NonZeroU32::MIN), ); let Ok(mut buf) = surface.buffer_mut() else { return; @@ -155,11 +160,14 @@ impl ApplicationHandler for App { let title = format!("WEFT App — {}", self.url.host_str().unwrap_or("app")); let attrs = WindowAttributes::default().with_title(title); - let window = Arc::new( - event_loop - .create_window(attrs) - .expect("failed to create app window"), - ); + let window = match event_loop.create_window(attrs) { + Ok(w) => Arc::new(w), + Err(e) => { + tracing::error!(error = %e, "failed to create app window; exiting"); + event_loop.exit(); + return; + } + }; let size = window.inner_size(); self.window = Some(Arc::clone(&window)); @@ -185,7 +193,11 @@ impl ApplicationHandler for App { servo.set_delegate(Rc::new(WeftServoDelegate)); - let rendering_context = build_rendering_ctx(event_loop, &window, size); + let Some(rendering_context) = build_rendering_ctx(event_loop, &window, size) else { + tracing::error!("no rendering context available; exiting without READY signal"); + event_loop.exit(); + return; + }; let ucm = Rc::new(UserContentManager::new(&servo)); if let Some(kit_js) = load_ui_kit_script() { @@ -326,7 +338,7 @@ fn build_rendering_ctx( event_loop: &ActiveEventLoop, window: &Arc, size: winit::dpi::PhysicalSize, -) -> RenderingCtx { +) -> Option { if std::env::var_os("WEFT_EGL_RENDERING").is_some() { let display_handle = event_loop.display_handle(); let window_handle = window.window_handle(); @@ -334,7 +346,7 @@ fn build_rendering_ctx( match servo::WindowRenderingContext::new(dh, wh, size) { Ok(rc) => { tracing::info!("using EGL rendering context"); - return RenderingCtx::Egl(Rc::new(rc)); + return Some(RenderingCtx::Egl(Rc::new(rc))); } Err(e) => { tracing::warn!("EGL rendering context failed ({e}), falling back to software"); @@ -342,10 +354,14 @@ fn build_rendering_ctx( } } } - RenderingCtx::Software(Rc::new( - servo::SoftwareRenderingContext::new(servo::euclid::Size2D::new(size.width, size.height)) - .expect("SoftwareRenderingContext"), - )) + match servo::SoftwareRenderingContext::new(servo::euclid::Size2D::new(size.width, size.height)) + { + Ok(rc) => Some(RenderingCtx::Software(Rc::new(rc))), + Err(e) => { + tracing::error!("SoftwareRenderingContext failed: {e}"); + None + } + } } fn ui_kit_path() -> std::path::PathBuf { diff --git a/crates/weft-app-shell/src/shell_client.rs b/crates/weft-app-shell/src/shell_client.rs index d5839b0..58d8918 100644 --- a/crates/weft-app-shell/src/shell_client.rs +++ b/crates/weft-app-shell/src/shell_client.rs @@ -172,7 +172,10 @@ impl ShellClient { WlSurface::from_id(&conn, id).context("wl_surface from_id")? }; - let manager = data.manager.as_ref().unwrap(); + let manager = data + .manager + .as_ref() + .expect("manager is Some; guaranteed by ensure! above"); let title = format!("{app_id}/{session_id}"); let window = manager.create_window( app_id.to_string(), diff --git a/crates/weft-appd/src/runtime.rs b/crates/weft-appd/src/runtime.rs index 3958e7b..77e4686 100644 --- a/crates/weft-appd/src/runtime.rs +++ b/crates/weft-appd/src/runtime.rs @@ -313,23 +313,32 @@ pub(crate) async fn supervise( _ = &mut abort_rx => None, }; - let mut app_shell: Option = None; - match ready_result { + let app_shell = match ready_result { Some(Ok(Ok(remaining_stdout))) => { - registry - .lock() - .await - .set_state(session_id, AppStateKind::Running); - let _ = registry.lock().await.broadcast().send(Response::AppReady { - session_id, - app_id: app_id.to_owned(), - }); + { + let mut reg = registry.lock().await; + reg.set_state(session_id, AppStateKind::Running); + let _ = reg.broadcast().send(Response::AppReady { + session_id, + app_id: app_id.to_owned(), + }); + } tracing::info!(session_id, %app_id, "app ready"); tokio::spawn(drain_stdout(remaining_stdout, session_id)); - app_shell = spawn_app_shell(session_id, app_id).await; + spawn_app_shell(session_id, app_id).await } Some(Ok(Err(e))) => { - tracing::warn!(session_id, %app_id, error = %e, "stdout read error before READY"); + tracing::warn!(session_id, %app_id, error = %e, "stdout read error before READY; killing process"); + let _ = child.kill().await; + kill_portal(portal).await; + let mut reg = registry.lock().await; + reg.set_state(session_id, AppStateKind::Stopped); + reg.remove_abort_sender(session_id); + let _ = reg.broadcast().send(Response::AppState { + session_id, + state: AppStateKind::Stopped, + }); + return Ok(()); } Some(Err(_elapsed)) => { tracing::warn!(session_id, %app_id, "READY timeout after 30s; killing process"); @@ -356,7 +365,7 @@ pub(crate) async fn supervise( }); return Ok(()); } - } + }; tokio::spawn(drain_stderr(stderr, session_id)); diff --git a/crates/weft-compositor/src/backend/drm.rs b/crates/weft-compositor/src/backend/drm.rs index 25c772e..93bb258 100644 --- a/crates/weft-compositor/src/backend/drm.rs +++ b/crates/weft-compositor/src/backend/drm.rs @@ -137,10 +137,12 @@ pub fn run() -> anyhow::Result<()> { loop_handle .insert_source(listening_socket, |stream, _, state| { - state + if let Err(e) = state .display_handle .insert_client(stream, Arc::new(WeftClientState::default())) - .unwrap(); + { + tracing::warn!(?e, "failed to insert Wayland client"); + } }) .map_err(|e| anyhow::anyhow!("socket source: {e}"))?; @@ -150,7 +152,9 @@ pub fn run() -> anyhow::Result<()> { |_, display, state| { // Safety: Display is owned by this Generic source and outlives the event loop. unsafe { - display.get_mut().dispatch_clients(state).unwrap(); + if let Err(e) = display.get_mut().dispatch_clients(state) { + tracing::warn!(?e, "Wayland dispatch error"); + } } Ok(PostAction::Continue) }, @@ -375,16 +379,21 @@ fn device_added(state: &mut WeftCompositorState, node: DrmNode, path: &Path) -> ) .map_err(|e| anyhow::anyhow!("DRM notifier: {e}"))?; - state.drm.as_mut().unwrap().devices.insert( - node, - WeftDrmDevice { - drm_output_manager, - drm_scanner: DrmScanner::new(), - surfaces: HashMap::new(), - render_node, - registration_token, - }, - ); + state + .drm + .as_mut() + .context("DRM data missing after initialization")? + .devices + .insert( + node, + WeftDrmDevice { + drm_output_manager, + drm_scanner: DrmScanner::new(), + surfaces: HashMap::new(), + render_node, + registration_token, + }, + ); device_changed(state, node); Ok(()) @@ -604,7 +613,7 @@ fn render_output(state: &mut WeftCompositorState, node: DrmNode, crtc: crtc::Han }; let render_node = { - let d = state.drm.as_ref().unwrap(); + let Some(d) = state.drm.as_ref() else { return }; d.devices .get(&node) .and_then(|d| d.render_node) diff --git a/crates/weft-compositor/src/backend/winit.rs b/crates/weft-compositor/src/backend/winit.rs index 0f4a5ba..087b04c 100644 --- a/crates/weft-compositor/src/backend/winit.rs +++ b/crates/weft-compositor/src/backend/winit.rs @@ -63,10 +63,12 @@ pub fn run() -> anyhow::Result<()> { loop_handle .insert_source(listening_socket, |client_stream, _, state| { - state + if let Err(e) = state .display_handle .insert_client(client_stream, Arc::new(WeftClientState::default())) - .unwrap(); + { + tracing::warn!(?e, "failed to insert Wayland client"); + } }) .map_err(|e| anyhow::anyhow!("socket source insertion failed: {e}"))?; @@ -78,7 +80,9 @@ pub fn run() -> anyhow::Result<()> { // Safety: the display is owned by this Generic source and is never dropped // while the event loop runs. unsafe { - display.get_mut().dispatch_clients(state).unwrap(); + if let Err(e) = display.get_mut().dispatch_clients(state) { + tracing::warn!(?e, "Wayland dispatch error"); + } } Ok(PostAction::Continue) }, @@ -129,9 +133,8 @@ pub fn run() -> anyhow::Result<()> { let size = backend.window_size(); let full_damage = Rectangle::from_size(size); - { - let (renderer, mut framebuffer) = backend.bind().unwrap(); - smithay::desktop::space::render_output::< + let render_ok = match backend.bind() { + Ok((renderer, mut framebuffer)) => smithay::desktop::space::render_output::< _, WaylandSurfaceRenderElement, _, @@ -147,9 +150,18 @@ pub fn run() -> anyhow::Result<()> { &mut damage_tracker, [0.1_f32, 0.1, 0.1, 1.0], ) - .unwrap(); + .map_err(|e| tracing::warn!(?e, "render_output failed")) + .is_ok(), + Err(e) => { + tracing::warn!(?e, "backend bind failed"); + false + } + }; + if render_ok { + if let Err(e) = backend.submit(Some(&[full_damage])) { + tracing::warn!(?e, "backend submit failed"); + } } - backend.submit(Some(&[full_damage])).unwrap(); state.space.elements().for_each(|window| { window.send_frame( diff --git a/crates/weft-compositor/src/state.rs b/crates/weft-compositor/src/state.rs index fed2a0f..82960da 100644 --- a/crates/weft-compositor/src/state.rs +++ b/crates/weft-compositor/src/state.rs @@ -294,9 +294,13 @@ impl WlrLayerShellHandler for WeftCompositorState { ) { let desktop_surface = DesktopLayerSurface::new(surface, namespace); if let Some(output) = self.space.outputs().next().cloned() { - layer_map_for_output(&output) + if layer_map_for_output(&output) .map_layer(&desktop_surface) - .expect("layer surface must not already be mapped"); + .is_err() + { + tracing::warn!("received duplicate layer surface mapping; ignoring"); + return; + } layer_map_for_output(&output).arrange(); } } diff --git a/crates/weft-runtime/src/main.rs b/crates/weft-runtime/src/main.rs index 3841805..bb74bfc 100644 --- a/crates/weft-runtime/src/main.rs +++ b/crates/weft-runtime/src/main.rs @@ -212,7 +212,7 @@ fn run_module( move |_: wasmtime::StoreContextMut<'_, State>, (payload,): (String,)| -> wasmtime::Result<(Result<(), String>,)> { - let mut guard = ipc_send.lock().unwrap(); + let mut guard = ipc_send.lock().unwrap_or_else(|p| p.into_inner()); match guard.as_mut() { Some(ipc) => Ok((ipc.send(&payload),)), None => Ok((Err("IPC not connected".to_owned()),)), @@ -227,7 +227,7 @@ fn run_module( move |_: wasmtime::StoreContextMut<'_, State>, ()| -> wasmtime::Result<(Option,)> { - let mut guard = ipc_recv.lock().unwrap(); + let mut guard = ipc_recv.lock().unwrap_or_else(|p| p.into_inner()); Ok((guard.as_mut().and_then(|ipc| ipc.recv()),)) }, ) @@ -299,7 +299,7 @@ fn run_module( if let Some(socket_path) = ipc_socket { ctx_builder.env("WEFT_IPC_SOCKET", socket_path); if let Some(ipc) = IpcState::connect(socket_path) { - *ipc_state.lock().unwrap() = Some(ipc); + *ipc_state.lock().unwrap_or_else(|p| p.into_inner()) = Some(ipc); } else { tracing::warn!("weft:app/ipc: could not connect to IPC socket {socket_path}"); } diff --git a/crates/weft-servo-shell/src/embedder.rs b/crates/weft-servo-shell/src/embedder.rs index f001817..6d7e875 100644 --- a/crates/weft-servo-shell/src/embedder.rs +++ b/crates/weft-servo-shell/src/embedder.rs @@ -96,6 +96,7 @@ struct App { modifiers: ModifiersState, cursor_pos: servo::euclid::default::Point2D, shell_client: Option, + gesture_thread: Option>, } impl App { @@ -113,6 +114,7 @@ impl App { modifiers: ModifiersState::default(), cursor_pos: servo::euclid::default::Point2D::zero(), shell_client: None, + gesture_thread: None, } } @@ -128,12 +130,17 @@ impl App { 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 Ok(ctx) = softbuffer::Context::new(Arc::clone(window)) else { + tracing::warn!("softbuffer context creation failed; skipping frame"); + return; + }; + let Ok(mut surface) = softbuffer::Surface::new(&ctx, Arc::clone(window)) else { + tracing::warn!("softbuffer surface creation failed; skipping frame"); + return; + }; 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()), + std::num::NonZeroU32::new(size.width).unwrap_or(std::num::NonZeroU32::MIN), + std::num::NonZeroU32::new(size.height).unwrap_or(std::num::NonZeroU32::MIN), ); let Ok(mut buf) = surface.buffer_mut() else { return; @@ -152,11 +159,14 @@ impl ApplicationHandler for App { } let attrs = WindowAttributes::default().with_title("WEFT Shell"); - let window = Arc::new( - event_loop - .create_window(attrs) - .expect("failed to create shell window"), - ); + let window = match event_loop.create_window(attrs) { + Ok(w) => Arc::new(w), + Err(e) => { + tracing::error!(error = %e, "failed to create shell window; exiting"); + event_loop.exit(); + return; + } + }; let size = window.inner_size(); self.window = Some(Arc::clone(&window)); @@ -177,7 +187,11 @@ impl ApplicationHandler for App { servo.set_delegate(Rc::new(WeftServoDelegate)); - let rendering_context = build_rendering_ctx(event_loop, &window, size); + let Some(rendering_context) = build_rendering_ctx(event_loop, &window, size) else { + tracing::error!("no rendering context available; shell cannot start"); + event_loop.exit(); + return; + }; let user_content_manager = Rc::new(UserContentManager::new(&servo)); if let Some(kit_js) = load_ui_kit_script() { @@ -226,10 +240,22 @@ impl ApplicationHandler for App { } let gestures = sc.take_pending_gestures(); if !gestures.is_empty() { - let ws_port = self.ws_port; - std::thread::spawn(move || { - forward_gestures_to_appd(ws_port, &gestures); - }); + let prev_done = self + .gesture_thread + .as_ref() + .map(|h| h.is_finished()) + .unwrap_or(true); + if prev_done { + let ws_port = self.ws_port; + self.gesture_thread = Some(std::thread::spawn(move || { + forward_gestures_to_appd(ws_port, &gestures); + })); + } else { + tracing::debug!( + count = gestures.len(), + "gesture forwarding in progress; dropping batch" + ); + } } } if let Some(servo) = &self.servo { @@ -319,7 +345,7 @@ fn build_rendering_ctx( event_loop: &ActiveEventLoop, window: &Arc, size: winit::dpi::PhysicalSize, -) -> RenderingCtx { +) -> Option { if std::env::var_os("WEFT_EGL_RENDERING").is_some() { let display_handle = event_loop.display_handle(); let window_handle = window.window_handle(); @@ -327,7 +353,7 @@ fn build_rendering_ctx( match servo::WindowRenderingContext::new(dh, wh, size) { Ok(rc) => { tracing::info!("using EGL rendering context"); - return RenderingCtx::Egl(Rc::new(rc)); + return Some(RenderingCtx::Egl(Rc::new(rc))); } Err(e) => { tracing::warn!("EGL rendering context failed ({e}), falling back to software"); @@ -335,10 +361,14 @@ fn build_rendering_ctx( } } } - RenderingCtx::Software(Rc::new( - servo::SoftwareRenderingContext::new(servo::euclid::Size2D::new(size.width, size.height)) - .expect("SoftwareRenderingContext"), - )) + match servo::SoftwareRenderingContext::new(servo::euclid::Size2D::new(size.width, size.height)) + { + Ok(rc) => Some(RenderingCtx::Software(Rc::new(rc))), + Err(e) => { + tracing::error!("SoftwareRenderingContext failed: {e}"); + None + } + } } // ── Public entry point ──────────────────────────────────────────────────────── diff --git a/crates/weft-servo-shell/src/shell_client.rs b/crates/weft-servo-shell/src/shell_client.rs index e26d876..b57ab85 100644 --- a/crates/weft-servo-shell/src/shell_client.rs +++ b/crates/weft-servo-shell/src/shell_client.rs @@ -197,7 +197,10 @@ impl ShellClient { WlSurface::from_id(&conn, id).context("wl_surface from_id")? }; - let manager = data.manager.as_ref().unwrap(); + let manager = data + .manager + .as_ref() + .expect("manager is Some; guaranteed by ensure! above"); let window = manager.create_window( "org.weft.system.shell".to_string(), "WEFT Shell".to_string(),