Commit b09e45f77a for qemu.org

commit b09e45f77a01919e7ead3c053c1647f66a1776d9
Author: Marc-André Lureau <marcandre.lureau@redhat.com>
Date:   Tue Jun 23 11:44:37 2026 +0400

    ui/gtk: implement display cleanup

    Add gtk_display_cleanup() to properly tear down GTK display state:
    remove console and mouse notifiers, unregister clipboard peer,
    destroy the main window and virtual consoles.

    Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
    Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
    Message-ID: <20260623-b4-ui-v4-22-4656aec3398d@redhat.com>

diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 3e6ce3cb48..f4c8e6a9f0 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -140,6 +140,7 @@ struct GtkDisplayState {

     GdkCursor *null_cursor;
     Notifier mouse_mode_notifier;
+    VMChangeStateEntry *vmse;
     gboolean free_scale;
     gboolean keep_aspect_ratio;

@@ -225,6 +226,7 @@ int gd_gl_area_make_current(DisplayGLCtx *dgc,

 /* gtk-clipboard.c */
 void gd_clipboard_init(GtkDisplayState *gd);
+void gd_clipboard_cleanup(GtkDisplayState *gd);

 void gd_update_scale(VirtualConsole *vc, int ww, int wh, int fbw, int fbh);

diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
index ea9444be70..476e6b8303 100644
--- a/ui/gtk-clipboard.c
+++ b/ui/gtk-clipboard.c
@@ -235,3 +235,18 @@ void gd_clipboard_init(GtkDisplayState *gd)
     g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY],
                      "owner-change", G_CALLBACK(gd_owner_change), gd);
 }
+
+void gd_clipboard_cleanup(GtkDisplayState *gd)
+{
+    if (!gd->cbpeer.name) {
+        return;
+    }
+    qemu_clipboard_peer_unregister(&gd->cbpeer);
+    g_signal_handlers_disconnect_by_data(
+        gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD], gd);
+    g_signal_handlers_disconnect_by_data(
+        gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY], gd);
+    g_signal_handlers_disconnect_by_data(
+        gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY], gd);
+    gd->cbpeer.name = NULL;
+}
diff --git a/ui/gtk.c b/ui/gtk.c
index 2ee826b56f..dfef1b10fe 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -2505,6 +2505,7 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
 }


+static GtkDisplayState *gtk_display_state;
 static gboolean gtkinit;

 static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
@@ -2523,6 +2524,7 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
     }
     assert(opts->type == DISPLAY_TYPE_GTK);
     s = g_malloc0(sizeof(*s));
+    gtk_display_state = s;
     s->opts = opts;

     theme = gtk_icon_theme_get_default();
@@ -2560,7 +2562,7 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)

     s->mouse_mode_notifier.notify = gd_mouse_mode_change;
     qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
-    qemu_add_vm_change_state_handler(gd_change_runstate, s);
+    s->vmse = qemu_add_vm_change_state_handler(gd_change_runstate, s);

     gtk_window_set_icon_name(GTK_WINDOW(s->window), "qemu");

@@ -2681,10 +2683,26 @@ static void early_gtk_display_init(DisplayOptions *opts)
 #endif
 }

+static void gtk_display_cleanup(void)
+{
+    GtkDisplayState *s = gtk_display_state;
+
+    if (!s) {
+        return;
+    }
+    qemu_del_vm_change_state_handler(s->vmse);
+    qemu_remove_mouse_mode_change_notifier(&s->mouse_mode_notifier);
+    gd_clipboard_cleanup(s);
+    g_clear_pointer(&s->window, gtk_widget_destroy);
+    g_clear_object(&s->null_cursor);
+    g_clear_pointer(&gtk_display_state, g_free);
+}
+
 static QemuDisplay qemu_display_gtk = {
     .type       = DISPLAY_TYPE_GTK,
     .early_init = early_gtk_display_init,
     .init       = gtk_display_init,
+    .cleanup    = gtk_display_cleanup,
     .vc         = "vc",
 };