linebender/druid · error

Failed to update cairo drawable

Error message

Failed to update cairo drawable: {}

What it means

This error is raised in `update_cairo_surface` when the cairo surface refuses to switch its backing XCB drawable to the newly created pixmap for the window. It wraps the underlying cairo error (via anyhow) after `set_drawable` fails, typically because the pixmap is invalid, the connection is broken, or cairo is in an error state. It means druid-shell cannot keep the render target in sync with the X11 buffer pool, so the frame cannot be drawn.

Solutions

  1. Check that the X11 connection is alive and the window id is still valid (handle DestroyNotify/Disconnected events).
  2. Ensure resize logic does not call render with a 0-width or 0-height pixmap; guard sizes before create_pixmap.
  3. Update/verify cairo and druid-shell versions; cairo-level set_drawable failures can stem from cairo/xcb binding bugs.
  4. Reproduce under Xvfb/xtrace to inspect whether the pixmap creation itself failed before set_drawable.

Example fix

// before
borrow_mut!(self.cairo_surface)?
    .set_drawable(&drawable, buffers.width as i32, buffers.height as i32)
    .map_err(|e| anyhow!("Failed to update cairo drawable: {}", e))?;
// after
if buffers.width == 0 || buffers.height == 0 {
    return Ok(()); // skip render for degenerate sizes
}
borrow_mut!(self.cairo_surface)?
    .set_drawable(&drawable, buffers.width as i32, buffers.height as i32)
    .map_err(|e| anyhow!("Failed to update cairo drawable: {}", e))?;
Defensive patterns

Strategy: try-catch

Validate before calling

// before triggering a render on X11
assert!(window.is_visible(), "window destroyed; cannot update cairo surface");
let (w, h) = window.get_size();
assert!(w > 0 && h > 0, "degenerate window size");

Type guard

fn is_surface_usable(bufs: &SurfaceBuffers) -> bool {
    bufs.width > 0 && bufs.height > 0 && !bufs.pixmaps.is_empty()
}

Try / catch

match window.update_cairo_surface() {
    Ok(()) => {},
    Err(e) if e.to_string().contains("Failed to update cairo drawable") => {
        log::error!("cairo drawable update failed: {} — checking X connection", e);
        if !connection.is_alive() { reconnect_or_exit(); }
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: `Window::render` calls `update_cairo_surface` after creating/resizing pixmaps via `buffers.create_pixmap`, and `cairo_surface.set_drawable(&XCBDrawable(pixmap), width, height)` returns Err — e.g. after a window resize produces an invalid pixmap, or the X server connection died.

Common situations: Window resize races on X11, X server disconnects or crashes mid-frame, running in environments where XCB/GLX state is stale (e.g. SSH-forwarded displays, nested X servers like Xvfb with limited extensions).

Related errors


AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10). Data as JSON: /api/errors/0198da23b1d37c89. Report an issue: GitHub.

Appendix: source

Thrown at druid-shell/src/backend/x11/window.rs:766

            self.with_handler(|h| h.scale(scale));
        }
        Ok(())
    }

    // Ensure that our cairo context is targeting the right drawable, allocating one if necessary.
    fn update_cairo_surface(&self) -> Result<(), Error> {
        let mut buffers = borrow_mut!(self.buffers)?;
        let pixmap = if let Some(p) = buffers.idle_pixmaps.last() {
            *p
        } else {
            info!("ran out of idle pixmaps, creating a new one");
            buffers.create_pixmap(self.app.connection(), self.id)?
        };

        let drawable = XCBDrawable(pixmap);
        borrow_mut!(self.cairo_surface)?
            .set_drawable(&drawable, buffers.width as i32, buffers.height as i32)
            .map_err(|e| anyhow!("Failed to update cairo drawable: {}", e))?;
        Ok(())
    }

    fn render(&self) -> Result<(), Error> {
        self.with_handler(|h| h.prepare_paint());

        if self.destroyed() {
            return Ok(());
        }

        self.update_cairo_surface()?;
        let invalid = std::mem::replace(&mut *borrow_mut!(self.invalid)?, Region::EMPTY);
        {
            let surface = borrow!(self.cairo_surface)?;
            let cairo_ctx = cairo::Context::new(&*surface).unwrap();
            let scale = self.scale.get();
            for rect in invalid.rects() {
                let rect = rect.to_px(scale).round();

View on GitHub (pinned to 0f8b1195e4)