wezterm/wezterm · error

raw_window_handle only callable on main thread

Error message

raw_window_handle only callable on main thread

What it means

HasWindowHandle::window_handle on WaylandWindow starts with Connection::get(), a thread-local accessor that only holds a value on the GUI main thread where Connection::init ran. Calling any raw-window-handle API from another thread makes get() return None and this expect panics.

Source

Thrown at window/src/os/wayland/window.rs:1655

        let handle = WaylandWindowHandle::new(
            NonNull::new(self.surface().id().as_ptr() as _).expect("non-null"),
        );
        unsafe { Ok(WindowHandle::borrow_raw(RawWindowHandle::Wayland(handle))) }
    }
}

impl HasDisplayHandle for WaylandWindow {
    fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
        let conn = WaylandConnection::get().unwrap().wayland();
        let backend = conn.connection.backend();
        let handle = backend.display_handle()?;
        Ok(unsafe { DisplayHandle::borrow_raw(handle.as_raw()) })
    }
}

impl HasWindowHandle for WaylandWindow {
    fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
        let conn = Connection::get().expect("raw_window_handle only callable on main thread");
        let handle = conn
            .wayland()
            .window_by_id(self.0)
            .expect("window handle invalid!?");

        let inner = handle.borrow();
        let handle = inner.window_handle()?;
        unsafe { Ok(WindowHandle::borrow_raw(handle.as_raw())) }
    }
}

View on GitHub (pinned to 08e5e0afc6)

Solutions

  1. Call window_handle()/display_handle() only on the thread that ran Connection::init (the main/GUI thread)
  2. Capture the raw handle on the main thread and pass RawWindowHandle/RawDisplayHandle values (they are plain data) to worker threads instead of the object
  3. If you control the embedder, dispatch handle queries through the main thread (e.g. promise::spawn::spawn_into_main_thread) and await the result

Example fix

// before: panics when run on a worker thread
let raw = window.window_handle()?.as_raw();

// after: capture on the main thread, then send the raw value
let raw = window.window_handle()?.as_raw(); // main thread
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || { tx.send(render_with(raw)).unwrap(); });
Defensive patterns

Strategy: validation

Validate before calling

// guard before querying, if you embed this crate
fn on_main() -> bool { /* your main-thread token check */ true }
assert!(on_main(), "window_handle must be called on the main thread");

Prevention

When it happens

Trigger: Triggered by window_handle()/display_handle() invoked off the main thread — classic sources: wgpu surface creation from a worker, EGL/GL initialization on a spawned thread, or libraries that query raw handles lazily on arbitrary threads.

Common situations: Integrating wezterm's window crate into another framework; vendored copies used by GUI/graphics stacks that create resources on render threads; calling handle APIs after moving a window id into async tasks.

Related errors


AI-assisted analysis of wezterm/wezterm@08e5e0afc6 (2026-08-20). Data as JSON: /api/errors/aaffcf2ed1af4655. Report an issue: GitHub.