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>
This commit is contained in:
Marc-André Lureau
2026-06-23 11:44:45 +04:00
parent b1e8bf9238
commit de1ecdc0f8
2 changed files with 75 additions and 1 deletions

View File

@@ -141,6 +141,7 @@ struct GtkDisplayState {
GdkCursor *null_cursor;
Notifier mouse_mode_notifier;
VMChangeStateEntry *vmse;
Notifier console_notifier;
gboolean free_scale;
gboolean keep_aspect_ratio;

View File

@@ -2332,7 +2332,8 @@ static bool gd_scale_valid(double scale)
return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX;
}
static void add_gfx_console(GtkDisplayState *s, QemuConsole *con)
static VirtualConsole *
add_gfx_console(GtkDisplayState *s, QemuConsole *con)
{
VirtualConsole *vc = g_new0(VirtualConsole, 1);
const DisplayChangeListenerOps *ops = &dcl_ops;
@@ -2414,6 +2415,74 @@ static void add_gfx_console(GtkDisplayState *s, QemuConsole *con)
qemu_console_register_listener(con, &vc->gfx.dcl, ops);
gd_connect_vc_gfx_signals(vc);
return vc;
}
static void gd_vc_add_gfx(GtkDisplayState *s, QemuConsole *con)
{
VirtualConsole *vc;
int i;
for (i = 0; i < (int)s->vcs->len; i++) {
VirtualConsole *v = g_ptr_array_index(s->vcs, i);
if (v->type == GD_VC_GFX && v->gfx.dcl.con == con) {
return;
}
}
vc = add_gfx_console(s, con);
gtk_widget_show_all(vc->tab_item);
gtk_widget_realize(vc->gfx.drawing_area);
if (s->free_scale) {
gd_update_windowsize(vc);
}
gd_update_caption(s);
gd_rebuild_vc_menu(s);
}
static void gd_vc_remove_gfx(GtkDisplayState *s, QemuConsole *con)
{
VirtualConsole *vc = NULL;
guint idx;
for (idx = 0; idx < s->vcs->len; idx++) {
VirtualConsole *v = g_ptr_array_index(s->vcs, idx);
if (v->type == GD_VC_GFX && v->gfx.dcl.con == con) {
vc = v;
break;
}
}
if (!vc) {
return;
}
if (s->kbd_owner == vc) {
gd_ungrab_keyboard(s);
}
if (s->ptr_owner == vc) {
gd_ungrab_pointer(s);
}
g_ptr_array_remove_index(s->vcs, idx);
gd_rebuild_vc_menu(s);
gd_update_caption(s);
}
static void gd_console_notify(Notifier *n, void *data)
{
GtkDisplayState *s = container_of(n, GtkDisplayState, console_notifier);
QemuConsoleEvent *event = data;
switch (event->type) {
case QEMU_CONSOLE_ADDED:
gd_vc_add_gfx(s, event->con);
break;
case QEMU_CONSOLE_REMOVED:
gd_vc_remove_gfx(s, event->con);
break;
}
}
static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
@@ -2694,6 +2763,9 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
gd_create_menus(s, opts);
s->console_notifier.notify = gd_console_notify;
qemu_console_add_notifier(&s->console_notifier);
gd_connect_signals(s);
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
@@ -2814,6 +2886,7 @@ static void gtk_display_cleanup(void)
return;
}
qemu_del_vm_change_state_handler(s->vmse);
qemu_console_remove_notifier(&s->console_notifier);
qemu_remove_mouse_mode_change_notifier(&s->mouse_mode_notifier);
gd_clipboard_cleanup(s);
g_signal_handlers_disconnect_by_func(s->notebook,