Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/smithay/wayland-rs/llms.txt

Use this file to discover all available pages before exploring further.

The wayland-egl crate provides the thin bridge between a Wayland wl_surface and the EGL windowing system. OpenGL and Vulkan drivers do not understand Wayland surfaces directly — they need a wl_egl_window handle, a C struct allocated by libwayland-egl that the driver can use to present rendered frames. wayland-egl exposes this as [WlEglSurface]: a safe Rust type that creates and destroys the wl_egl_window, provides its raw pointer for passing to EGL initialization routines, and supports runtime resize operations.
wayland-egl links against libwayland-egl.so at compile time through the client_system feature of wayland-backend. You must have the EGL development headers and the Wayland EGL library installed on the build host.

Adding the dependency

Because wayland-egl delegates object-ID tracking to wayland-backend with the client_system feature, you only need one line in Cargo.toml:
[dependencies]
wayland-egl = "0.32"
wayland-backend with client_system and wayland-sys with the egl feature are pulled in automatically as transitive dependencies.
The dlopen feature on wayland-backend (and therefore wayland-sys) makes the system libraries load at runtime rather than being linked at compile time. You can combine this with wayland-egl if you want the EGL surface path to fail gracefully when libwayland-egl.so is absent — call wayland_egl::is_available() to check.

Checking library availability

When the dlopen feature is active, the EGL library is loaded lazily. Call is_available() before creating any surface:
if !wayland_egl::is_available() {
    eprintln!("libwayland-egl not found — cannot create EGL surface");
    return;
}
When dlopen is not active, is_available() always returns true because the library is linked directly.

Creating a WlEglSurface

[WlEglSurface::new()] accepts an [ObjectId] obtained from a WlSurface, plus the initial pixel dimensions. It returns an error if the ObjectId does not correspond to a live wl_surface or if the dimensions are non-positive.
use wayland_egl::WlEglSurface;
use wayland_client::{Connection, protocol::wl_surface::WlSurface};

fn create_egl_surface(surface: &WlSurface, width: i32, height: i32) -> WlEglSurface {
    // ObjectId::clone() is cheap — it is reference-counted internally.
    WlEglSurface::new(surface.id(), width, height)
        .expect("Failed to create WlEglSurface")
}
You must always destroy the WlEglSurface before the underlying wl_surface protocol object is destroyed. Dropping WlEglSurface calls wl_egl_window_destroy automatically via its Drop implementation.

Getting the raw pointer for EGL initialization

Most OpenGL/EGL binding crates (glutin, khronos-egl, raw EGL) need two pointers at context creation time:
  • EGLDisplay — obtained from the raw wl_display pointer via Backend::display_ptr()
  • EGLSurface / native window — obtained from WlEglSurface::ptr()
use wayland_egl::WlEglSurface;
use wayland_backend::client::Backend;
use std::os::raw::c_void;

fn egl_pointers(backend: &Backend, egl_surface: &WlEglSurface) {
    // wl_display* — pass to eglGetDisplay() as EGLNativeDisplayType.
    let display_ptr: *mut c_void = backend.display_ptr().cast();

    // wl_egl_window* — pass to eglCreateWindowSurface() as EGLNativeWindowType.
    let window_ptr: *const c_void = egl_surface.ptr();

    println!("EGL display ptr: {display_ptr:?}");
    println!("EGL window ptr:  {window_ptr:?}");
}

Resizing the surface

When the compositor sends a wl_surface.configure event with new dimensions, call [WlEglSurface::resize()]. The last two arguments, dx and dy, shift the top-left corner of the surface and can be set to 0 in the common case.
// Resize to 1280×720 without moving the origin.
egl_surface.resize(1280, 720, 0, 0);
Query the current attached size at any time with [WlEglSurface::get_size()]:
let (w, h) = egl_surface.get_size();
println!("Current EGL surface size: {w}×{h}");

raw-window-handle integration

wayland-backend implements [HasDisplayHandle][rwh_06::HasDisplayHandle] for client::Backend when both the rwh_06 and client_system features are enabled on wayland-backend (not on wayland-egl). This lets you pass the display handle to crates such as wgpu or winit through the standard raw-window-handle API. To obtain a RawWindowHandle for the surface side you construct a WaylandWindowHandle from the wl_egl_window pointer. Add wayland-backend with rwh_06 explicitly:
[dependencies]
wayland-egl = "0.32"
wayland-backend = { version = "0.3", features = ["client_system", "rwh_06"] }
raw-window-handle = "0.6"
fn window_handle(egl_surface: &WlEglSurface) -> raw_window_handle::RawWindowHandle {
    use raw_window_handle::{RawWindowHandle, WaylandWindowHandle};
    use std::ptr::NonNull;

    // WlEglSurface::ptr() returns *const c_void pointing to the wl_egl_window.
    let ptr = NonNull::new(egl_surface.ptr() as *mut _).unwrap();
    RawWindowHandle::Wayland(WaylandWindowHandle::new(ptr))
}

Combining with raw EGL calls

use wayland_egl::WlEglSurface;
use wayland_backend::client::Backend;

fn setup_egl(backend: &Backend, egl_surface: &WlEglSurface) {
    // Obtain raw C pointers.
    let display_ptr = backend.display_ptr(); // *mut wl_display
    let window_ptr  = egl_surface.ptr();     // *const c_void (wl_egl_window*)

    // --- Pass to khronos-egl (or any other EGL binding) ---
    // let egl = egl::Instance::new(egl::Static);
    // let egl_display = egl.get_display(display_ptr as _).unwrap();
    // let egl_win_surface = egl.create_window_surface(
    //     egl_display, config, window_ptr as _, None
    // ).unwrap();
    let _ = (display_ptr, window_ptr); // suppress unused warnings
}

Thread safety

WlEglSurface implements Send — it can be moved to another thread after creation, which is useful when rendering on a dedicated thread. It is not Sync because the underlying wl_egl_window* pointer performs no internal synchronization. Do not share a reference to WlEglSurface across threads concurrently.

Error handling

[WlEglSurface::new()] returns a Result<WlEglSurface, wayland_egl::Error>:
VariantCause
Error::InvalidIdThe ObjectId is not a live wl_surface
Error::InvalidSizewidth or height is ≤ 0
use wayland_egl::{WlEglSurface, Error};

match WlEglSurface::new(surface.id(), 0, 0) {
    Ok(_)  => unreachable!(),
    Err(Error::InvalidSize) => eprintln!("Width/height must be positive"),
    Err(Error::InvalidId)   => eprintln!("Surface is already destroyed"),
}

API summary

MethodDescription
is_available()Check whether libwayland-egl.so could be loaded
WlEglSurface::new(id, width, height)Create an EGL window from a wl_surface ObjectId
WlEglSurface::ptr()Raw *const c_void for eglCreateWindowSurface
WlEglSurface::resize(w, h, dx, dy)Update the surface dimensions
WlEglSurface::get_size()Query the current (width, height)
Backend::display_ptr()Raw *mut wl_display for eglGetDisplay (system backend only)

Build docs developers (and LLMs) love