@samitouri / QOSamiQemu / commits / de1ecdc0f8

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,