mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
apprt/gtk: fix double-free if quit action is used (#8068)
This fixes a double-free that Valgrind found when the quit action was used (the keybinding to quit or the menu item). This fixes it in both the gtk and gtk-ng apprts. The issue stems from the fact that our quit action worked by traversing the toplevels and destroying all windows. When all windows are destroyed, GTK exits the main loop. When fcitx is used as the input method editor (IME), it appears to hold its own `gtk.Window` widget as a property (probably for the IME popup). Unfortunately this does not react well to being destroyed externally and triggers a double-free when the IME widget also tries to dispose itself. I think this is probably a bug somewhere in the GTK IME widget because it should be resilient to this kind of destruction. But, we can't tolerate a double free in the mean time. We can still quit by destroying only OUR windows (which cascades to destroy everything else).
This commit is contained in:
@ -433,16 +433,27 @@ pub const Application = extern struct {
|
||||
}
|
||||
|
||||
fn quitNow(self: *Self) void {
|
||||
// Get all our windows and destroy them, forcing them to
|
||||
// free their memory.
|
||||
// Get all our windows and destroy them, forcing them to free.
|
||||
const list = gtk.Window.listToplevels();
|
||||
defer list.free();
|
||||
list.foreach(struct {
|
||||
fn callback(data: ?*anyopaque, _: ?*anyopaque) callconv(.c) void {
|
||||
const ptr = data orelse return;
|
||||
const window: *gtk.Window = @ptrCast(@alignCast(ptr));
|
||||
|
||||
// We only want to destroy our windows. These windows own
|
||||
// every other type of window that is possible so this will
|
||||
// trigger a proper shutdown sequence.
|
||||
//
|
||||
// We previously just destroyed ALL windows but this leads to
|
||||
// a double-free with the fcitx ime, because it has a nested
|
||||
// gtk.Window as a property that we don't own and it later
|
||||
// tries to free on its own. I think this is probably a bug in
|
||||
// the fcitx ime widget but still, we don't want a double free!
|
||||
if (gobject.ext.isA(window, Window)) {
|
||||
window.destroy();
|
||||
}
|
||||
}
|
||||
}.callback, null);
|
||||
|
||||
// Trigger our runloop exit.
|
||||
|
@ -1556,8 +1556,23 @@ pub fn quitNow(self: *App) void {
|
||||
fn callback(data: ?*anyopaque, _: ?*anyopaque) callconv(.c) void {
|
||||
const ptr = data orelse return;
|
||||
const window: *gtk.Window = @ptrCast(@alignCast(ptr));
|
||||
|
||||
// We only want to destroy our windows. These windows own
|
||||
// every other type of window that is possible so this will
|
||||
// trigger a proper shutdown sequence.
|
||||
//
|
||||
// We previously just destroyed ALL windows but this leads to
|
||||
// a double-free with the fcitx ime, because it has a nested
|
||||
// gtk.Window as a property that we don't own and it later
|
||||
// tries to free on its own. I think this is probably a bug in
|
||||
// the fcitx ime widget but still, we don't want a double free!
|
||||
//
|
||||
// Since we don't use gobject directly we can't check class,
|
||||
// so we use a heuristic based on CSS class.
|
||||
if (window.as(gtk.Widget).hasCssClass("terminal-window") != 0) {
|
||||
window.destroy();
|
||||
}
|
||||
}
|
||||
}.callback, null);
|
||||
|
||||
self.running = false;
|
||||
|
Reference in New Issue
Block a user