ui/gtk: handle console hotplug/unplug events
Register a console notifier so the GTK display dynamically creates and destroys VirtualConsole tabs when graphic consoles are added or removed at runtime (e.g. vfio-pci with display=on hotplug). Add skips consoles that already have a VC binding, remove skips unknown 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-30-4656aec3398d@redhat.com>
Marc-André Lureau committed
Jun 23, 2026 at 11:44 UTC
de1ecdc0f86b3a532291402794fe5d72096fa722
2 files changed
+75
-1
include/ui/gtk.h
+1
@@ -141,6 +141,7 @@ struct GtkDisplayState {
141
GdkCursor *null_cursor;
142
Notifier mouse_mode_notifier;
143
VMChangeStateEntry *vmse;
144
+ Notifier console_notifier;
145
gboolean free_scale;
146
gboolean keep_aspect_ratio;
147
ui/gtk.c
+74
-1
@@ -2332,7 +2332,8 @@ static bool gd_scale_valid(double scale)
2332
return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX;
2333
}
2334
2335
-static void add_gfx_console(GtkDisplayState *s, QemuConsole *con)
2335
+static VirtualConsole *
2336
+add_gfx_console(GtkDisplayState *s, QemuConsole *con)
2337
{
2338
VirtualConsole *vc = g_new0(VirtualConsole, 1);
2339
const DisplayChangeListenerOps *ops = &dcl_ops;
@@ -2414,6 +2415,74 @@ static void add_gfx_console(GtkDisplayState *s, QemuConsole *con)
2415
qemu_console_register_listener(con, &vc->gfx.dcl, ops);
2416
2417
gd_connect_vc_gfx_signals(vc);
2418
+ return vc;
2419
+}
2420
+
2421
+static void gd_vc_add_gfx(GtkDisplayState *s, QemuConsole *con)
2422
+{
2423
+ VirtualConsole *vc;
2424
+ int i;
2425
+
2426
+ for (i = 0; i < (int)s->vcs->len; i++) {
2427
+ VirtualConsole *v = g_ptr_array_index(s->vcs, i);
2428
+ if (v->type == GD_VC_GFX && v->gfx.dcl.con == con) {
2429
+ return;
2430
+ }
2431
+ }
2432
+
2433
+ vc = add_gfx_console(s, con);
2434
+ gtk_widget_show_all(vc->tab_item);
2435
+ gtk_widget_realize(vc->gfx.drawing_area);
2436
+
2437
+ if (s->free_scale) {
2438
+ gd_update_windowsize(vc);
2439
+ }
2440
+
2441
+ gd_update_caption(s);
2442
+ gd_rebuild_vc_menu(s);
2443
+}
2444
+
2445
+static void gd_vc_remove_gfx(GtkDisplayState *s, QemuConsole *con)
2446
+{
2447
+ VirtualConsole *vc = NULL;
2448
+ guint idx;
2449
+
2450
+ for (idx = 0; idx < s->vcs->len; idx++) {
2451
+ VirtualConsole *v = g_ptr_array_index(s->vcs, idx);
2452
+ if (v->type == GD_VC_GFX && v->gfx.dcl.con == con) {
2453
+ vc = v;
2454
+ break;
2455
+ }
2456
+ }
2457
+ if (!vc) {
2458
+ return;
2459
+ }
2460
+
2461
+ if (s->kbd_owner == vc) {
2462
+ gd_ungrab_keyboard(s);
2463
+ }
2464
+ if (s->ptr_owner == vc) {
2465
+ gd_ungrab_pointer(s);
2466
+ }
2467
+
2468
+ g_ptr_array_remove_index(s->vcs, idx);
2469
+ gd_rebuild_vc_menu(s);
2470
+ gd_update_caption(s);
2471
+}
2472
+
2473
+static void gd_console_notify(Notifier *n, void *data)
2474
+{
2475
+ GtkDisplayState *s = container_of(n, GtkDisplayState, console_notifier);
2476
+ QemuConsoleEvent *event = data;
2477
+
2478
+ switch (event->type) {
2479
+ case QEMU_CONSOLE_ADDED:
2480
+ gd_vc_add_gfx(s, event->con);
2481
+ break;
2482
+ case QEMU_CONSOLE_REMOVED:
2483
+ gd_vc_remove_gfx(s, event->con);
2484
+ break;
2485
+ }
2486
}
2487
2488
static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
@@ -2694,6 +2763,9 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
2763
2764
gd_create_menus(s, opts);
2765
2766
+ s->console_notifier.notify = gd_console_notify;
2767
+ qemu_console_add_notifier(&s->console_notifier);
2768
+
2769
gd_connect_signals(s);
2770
2771
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
@@ -2814,6 +2886,7 @@ static void gtk_display_cleanup(void)
2886
return;
2887
}
2888
qemu_del_vm_change_state_handler(s->vmse);
2889
+ qemu_console_remove_notifier(&s->console_notifier);
2890
qemu_remove_mouse_mode_change_notifier(&s->mouse_mode_notifier);
2891
gd_clipboard_cleanup(s);
2892
g_signal_handlers_disconnect_by_func(s->notebook,