From 533cdb899b90f0d0e7a59059ac7af9c0a70b8d13 Mon Sep 17 00:00:00 2001 From: cardinalpointstudio Date: Wed, 25 Feb 2026 11:18:52 -0500 Subject: [PATCH] gpui(linux): Fix RefCell borrow panic when callbacks register new callbacks (#49533) ## Summary Fixes RefCell borrow panic on Linux (Wayland and X11) when callbacks try to register new callbacks. **Root cause:** Linux GPUI backends invoked callbacks while still holding a `RefCell` borrow on the `Callbacks` struct. If a callback tried to register a new callback (e.g., `on_window_should_close`), it would panic with "already borrowed: BorrowMutError". **Bug pattern:** ```rust // Callback runs while borrow is held - panics if callback borrows callbacks if let Some(ref mut fun) = self.callbacks.borrow_mut().input { fun(input); } Fix: Apply the take-call-restore pattern (already used in macOS backend): // Take callback out, release borrow, call, restore let callback = self.callbacks.borrow_mut().input.take(); if let Some(mut fun) = callback { let result = fun(input); self.callbacks.borrow_mut().input = Some(fun); } Changes - Wayland (window.rs): Fixed 6 callback invocations - X11 (window.rs): Fixed 4 callback invocations Test plan - cargo check -p gpui compiles successfully - Tested on Linux (Wayland) - no more RefCell panic Release Notes: - Fixed a crash on Linux when window callbacks attempted to register new callbacks Co-authored-by: Claude Opus 4.5 --- crates/agent_ui/src/agent_ui.rs | 1 - crates/gpui_linux/src/linux/wayland/window.rs | 46 +++++++++++-------- crates/gpui_linux/src/linux/x11/window.rs | 31 ++++++++----- 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 7f05437cbae82ef4ae4953c91d33c0b6c7a296bc..736f69855f0e1cac0c7eb82f6596360e32489939 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -25,7 +25,6 @@ mod ui; use std::rc::Rc; use std::sync::Arc; -// Another comment use agent_settings::{AgentProfileId, AgentSettings}; use assistant_slash_command::SlashCommandRegistry; use client::Client; diff --git a/crates/gpui_linux/src/linux/wayland/window.rs b/crates/gpui_linux/src/linux/wayland/window.rs index c1006a816a3844db22ea8932177b0f0b2ff1c99f..4a4c4060bdc31b95bd4b90d930afdc54727a9667 100644 --- a/crates/gpui_linux/src/linux/wayland/window.rs +++ b/crates/gpui_linux/src/linux/wayland/window.rs @@ -640,19 +640,19 @@ impl WaylandWindowStatePtr { match mode { WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => { self.state.borrow_mut().decorations = WindowDecorations::Server; - if let Some(appearance_changed) = - self.callbacks.borrow_mut().appearance_changed.as_mut() - { - appearance_changed(); + let callback = self.callbacks.borrow_mut().appearance_changed.take(); + if let Some(mut fun) = callback { + fun(); + self.callbacks.borrow_mut().appearance_changed = Some(fun); } } WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => { self.state.borrow_mut().decorations = WindowDecorations::Client; // Update background to be transparent - if let Some(appearance_changed) = - self.callbacks.borrow_mut().appearance_changed.as_mut() - { - appearance_changed(); + let callback = self.callbacks.borrow_mut().appearance_changed.take(); + if let Some(mut fun) = callback { + fun(); + self.callbacks.borrow_mut().appearance_changed = Some(fun); } } WEnum::Value(_) => { @@ -924,8 +924,10 @@ impl WaylandWindowStatePtr { (state.bounds.size, state.scale) }; - if let Some(ref mut fun) = self.callbacks.borrow_mut().resize { + let callback = self.callbacks.borrow_mut().resize.take(); + if let Some(mut fun) = callback { fun(size, scale); + self.callbacks.borrow_mut().resize = Some(fun); } { @@ -971,10 +973,13 @@ impl WaylandWindowStatePtr { if self.is_blocked() { return; } - if let Some(ref mut fun) = self.callbacks.borrow_mut().input - && !fun(input.clone()).propagate - { - return; + let callback = self.callbacks.borrow_mut().input.take(); + if let Some(mut fun) = callback { + let result = fun(input.clone()); + self.callbacks.borrow_mut().input = Some(fun); + if !result.propagate { + return; + } } if let PlatformInput::KeyDown(event) = input && event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) @@ -991,23 +996,28 @@ impl WaylandWindowStatePtr { pub fn set_focused(&self, focus: bool) { self.state.borrow_mut().active = focus; - if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change { + let callback = self.callbacks.borrow_mut().active_status_change.take(); + if let Some(mut fun) = callback { fun(focus); + self.callbacks.borrow_mut().active_status_change = Some(fun); } } pub fn set_hovered(&self, focus: bool) { - if let Some(ref mut fun) = self.callbacks.borrow_mut().hover_status_change { + let callback = self.callbacks.borrow_mut().hover_status_change.take(); + if let Some(mut fun) = callback { fun(focus); + self.callbacks.borrow_mut().hover_status_change = Some(fun); } } pub fn set_appearance(&mut self, appearance: WindowAppearance) { self.state.borrow_mut().appearance = appearance; - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(ref mut fun) = callbacks.appearance_changed { - (fun)() + let callback = self.callbacks.borrow_mut().appearance_changed.take(); + if let Some(mut fun) = callback { + fun(); + self.callbacks.borrow_mut().appearance_changed = Some(fun); } } diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index 8060e4c4457c6ef4575d86c4d975e3ead901f693..0ddd6e7adff84908e6a1c06d661347d39bdc5c9e 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -1045,9 +1045,10 @@ impl X11WindowStatePtr { } pub fn refresh(&self, request_frame_options: RequestFrameOptions) { - let mut cb = self.callbacks.borrow_mut(); - if let Some(ref mut fun) = cb.request_frame { + let callback = self.callbacks.borrow_mut().request_frame.take(); + if let Some(mut fun) = callback { fun(request_frame_options); + self.callbacks.borrow_mut().request_frame = Some(fun); } } @@ -1055,10 +1056,13 @@ impl X11WindowStatePtr { if self.is_blocked() { return; } - if let Some(ref mut fun) = self.callbacks.borrow_mut().input - && !fun(input.clone()).propagate - { - return; + let callback = self.callbacks.borrow_mut().input.take(); + if let Some(mut fun) = callback { + let result = fun(input.clone()); + self.callbacks.borrow_mut().input = Some(fun); + if !result.propagate { + return; + } } if let PlatformInput::KeyDown(event) = input { // only allow shift modifier when inserting text @@ -1191,14 +1195,18 @@ impl X11WindowStatePtr { } pub fn set_active(&self, focus: bool) { - if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change { + let callback = self.callbacks.borrow_mut().active_status_change.take(); + if let Some(mut fun) = callback { fun(focus); + self.callbacks.borrow_mut().active_status_change = Some(fun); } } pub fn set_hovered(&self, focus: bool) { - if let Some(ref mut fun) = self.callbacks.borrow_mut().hovered_status_change { + let callback = self.callbacks.borrow_mut().hovered_status_change.take(); + if let Some(mut fun) = callback { fun(focus); + self.callbacks.borrow_mut().hovered_status_change = Some(fun); } } @@ -1209,9 +1217,10 @@ impl X11WindowStatePtr { state.renderer.update_transparency(is_transparent); state.appearance = appearance; drop(state); - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(ref mut fun) = callbacks.appearance_changed { - (fun)() + let callback = self.callbacks.borrow_mut().appearance_changed.take(); + if let Some(mut fun) = callback { + fun(); + self.callbacks.borrow_mut().appearance_changed = Some(fun); } } }