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;
cardinalpointstudio and Claude Opus 4.5 created
## 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 <noreply@anthropic.com>
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(-)
@@ -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;
@@ -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);
}
}
@@ -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);
}
}
}