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
- Call window_handle()/display_handle() only on the thread that ran Connection::init (the main/GUI thread)
- Capture the raw handle on the main thread and pass RawWindowHandle/RawDisplayHandle values (they are plain data) to worker threads instead of the object
- 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
- Treat raw-handle getters as main-thread-only API by convention
- Capture RawWindowHandle/RawDisplayHandle once on the main thread and pass the values around
- Dispatch any framework's handle queries through the main loop
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
- display_handle only callable on main thread
- window_handle only callable on main thread
- window handle invalid!?
- I/O Error while redrawing the borders
- make pixmap from existing bitmap
AI-assisted analysis of wezterm/wezterm@08e5e0afc6 (2026-08-20).
Data as JSON: /api/errors/aaffcf2ed1af4655.
Report an issue: GitHub.