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 <noreply@anthropic.com>
This commit is contained in:
cardinalpointstudioandClaude Opus 4.5 authored and GitHub committed 2026-02-25 16:18:52 +00:00
1 parent 6256824c65
commit 8fcdbf41cd
3 files changed
+48 -30

No files matched your search

-1
View File
@@ -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;
+28 -18
View File
@@ -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);
}
}
+20 -11
View File
@@ -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);
}
}
}