* [PATCH 00/32] ui: better console hotplug support
@ 2026-05-29 11:16 Marc-André Lureau
2026-05-29 11:16 ` [PATCH 01/32] ui/gtk: fix bad widget realize on non-GFX VC Marc-André Lureau
` (31 more replies)
0 siblings, 32 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Hi,
This series improves handling of dynamic consoles in ui/ (commit
9588d67e72 "console: minimal hotplug suport" added basic unplug &
replug). It allows for better hotplug of vfio-pci display devices or
bochs-display (the other device supporting hotplug today), with GTK and
D-Bus backends.
As usual, the first patches are various preliminary cleanups and fixes I
hit while developping the rest, they could be merged earlier. I improved
the display cleanup to replace the scattered atexit() handler, making
leaks more visible to sanitizers.
Then console ADDED/REMOVED notifications are added, so display backends
can react to hotplug. The GTK code is rework a bit to allow easier
hotplug events handling. Finally, D-Bus supports is implemented with a
qtest to exercise it.
thanks
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
Marc-André Lureau (32):
ui/gtk: fix bad widget realize on non-GFX VC
build-sys: build with -fno-omit-frame-pointer with ASAN
irq: add per-IRQ observer to fix qemu_irq_intercept_in leak
scripts/lsan_suppressions: suppress fontconfig leaks
vfio/pci: close display console during unrealize, not finalize
glib-compat: add fallback for g_clear_fd/g_autofd
ui/dbus: remove mouse handler on dispose
ui/qmp: keep a reference of console across yield
ui: stop ui timer when closing
ui/console: init gl_unblock_timer in qemu_console_init
ui/spice: remove dead spice_displays
ui/spice: add cleanup on shutdown
ui: add display cleanup infrastructure
ui/curses: implement display cleanup
ui/sdl2: implement display cleanup
ui/spice-app: implement display cleanup
ui/egl: implement display and EGL cleanup
ui/cocoa: implement display cleanup
ui/dbus: implement display cleanup
ui/gtk: implement display cleanup
ui/console: add console event notifier infrastructure
ui/console: fire console ADDED/REMOVED notifications
ui/console-vc: fire ADDED/REMOVED notifications
ui/gtk: convert VirtualConsole storage from fixed array to GPtrArray
ui/gtk: move global display settings out of per-console init
ui/gtk: fix tab re-insertion order on window close
ui/gtk: centralize console menu and shortcut management
ui/gtk: handle console hotplug/unplug events
ui/console: register console in QOM tree dynamically
ui/console: unregister console from QOM tree on close
ui/dbus: handle console hotplug/unplug events
tests/qtest: add D-Bus display hotplug test
meson.build | 2 +
hw/vfio/pci.h | 1 +
include/glib-compat.h | 29 ++++
include/hw/core/irq.h | 1 +
include/ui/console.h | 20 +++
include/ui/egl-helpers.h | 1 +
include/ui/gtk.h | 6 +-
include/ui/qemu-spice-module.h | 1 +
include/ui/qemu-spice.h | 9 +-
ui/dbus.h | 3 +
hw/core/irq.c | 10 +-
hw/vfio/display.c | 30 ++--
hw/vfio/pci.c | 1 +
system/qtest.c | 3 -
system/runstate.c | 4 +-
tests/qtest/dbus-display-test.c | 101 ++++++++++-
ui/console-vc.c | 13 ++
ui/console.c | 71 +++++++-
ui/curses.c | 17 +-
ui/dbus-console.c | 9 +
ui/dbus.c | 106 ++++++++++--
ui/egl-headless.c | 31 ++++
ui/egl-helpers.c | 19 +++
ui/gtk-clipboard.c | 15 ++
ui/gtk.c | 369 +++++++++++++++++++++++++++++++---------
ui/sdl2.c | 23 ++-
ui/spice-app.c | 10 +-
ui/spice-core.c | 25 ++-
ui/spice-display.c | 52 ++++++
ui/spice-input.c | 52 ++++--
ui/spice-module.c | 5 +
ui/ui-qmp-cmds.c | 3 +
scripts/lsan_suppressions.txt | 15 +-
ui/cocoa.m | 19 ++-
ui/meson.build | 4 +-
35 files changed, 901 insertions(+), 179 deletions(-)
---
base-commit: 2db91528542672cf0db78b3f2cc0e22b36302b38
change-id: 20260529-b4-ui-909bfa695735
Best regards,
--
Marc-André Lureau <marcandre.lureau@redhat.com>
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH 01/32] ui/gtk: fix bad widget realize on non-GFX VC
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 02/32] build-sys: build with -fno-omit-frame-pointer with ASAN Marc-André Lureau
` (30 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
The GTK VirtualConsole is a union, it may be .gfx or .vte depending on
the type.
Fixes: 565f85a9c2 ("ui/gtk: force realization of drawing area")
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/gtk.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/ui/gtk.c b/ui/gtk.c
index 4f706c6bbb2..2ee826b56fb 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -2585,7 +2585,9 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
if (!con) {
break;
}
- gtk_widget_realize(s->vc[idx].gfx.drawing_area);
+ if (s->vc[idx].type == GD_VC_GFX) {
+ gtk_widget_realize(s->vc[idx].gfx.drawing_area);
+ }
}
if (opts->u.gtk.has_show_menubar &&
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 02/32] build-sys: build with -fno-omit-frame-pointer with ASAN
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
2026-05-29 11:16 ` [PATCH 01/32] ui/gtk: fix bad widget realize on non-GFX VC Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak Marc-André Lureau
` (29 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
On fc44, LSan fails to suppress leak:qemu_irq_intercept_in, because
the backtrace isn't deep enough.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
meson.build | 2 ++
1 file changed, 2 insertions(+)
diff --git a/meson.build b/meson.build
index eb074918193..224534ac351 100644
--- a/meson.build
+++ b/meson.build
@@ -545,6 +545,8 @@ if get_option('asan')
if cc.has_argument('-fsanitize=address')
qemu_cflags = ['-fsanitize=address'] + qemu_cflags
qemu_ldflags = ['-fsanitize=address'] + qemu_ldflags
+ # Ensure complete stack traces for LSan suppressions to match correctly.
+ qemu_cflags += ['-fno-omit-frame-pointer']
else
error('Your compiler does not support -fsanitize=address')
endif
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
2026-05-29 11:16 ` [PATCH 01/32] ui/gtk: fix bad widget realize on non-GFX VC Marc-André Lureau
2026-05-29 11:16 ` [PATCH 02/32] build-sys: build with -fno-omit-frame-pointer with ASAN Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 13:27 ` Fabiano Rosas
2026-05-29 13:44 ` Peter Maydell
2026-05-29 11:16 ` [PATCH 04/32] scripts/lsan_suppressions: suppress fontconfig leaks Marc-André Lureau
` (28 subsequent siblings)
31 siblings, 2 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
qemu_irq_intercept_in() saves original IRQ handlers by allocating
new QOM objects, which are never freed. On a PC machine, this leaks
IRQ objects (one per IOAPIC pin) on every qtest run.
Rather than tracking allocations to free later, avoid them: add an
"observer" field to IRQState, called by qemu_set_irq() after the
real handler. Interception sets the observer instead of rewriting
handlers, so there's nothing to save and nothing to leak.
Fix qemu_notirq() to route through qemu_set_irq() so inverted IRQs
trigger observers too. Drop the LSan suppression.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/hw/core/irq.h | 1 +
hw/core/irq.c | 10 +++++-----
system/qtest.c | 3 ---
scripts/lsan_suppressions.txt | 8 --------
4 files changed, 6 insertions(+), 16 deletions(-)
diff --git a/include/hw/core/irq.h b/include/hw/core/irq.h
index 291fdd67df4..93d5710a73e 100644
--- a/include/hw/core/irq.h
+++ b/include/hw/core/irq.h
@@ -14,6 +14,7 @@ struct IRQState {
qemu_irq_handler handler;
void *opaque;
int n;
+ qemu_irq_handler observer;
};
void qemu_set_irq(qemu_irq irq, int level);
diff --git a/hw/core/irq.c b/hw/core/irq.c
index 106805e2417..fa11e9bc0aa 100644
--- a/hw/core/irq.c
+++ b/hw/core/irq.c
@@ -32,6 +32,9 @@ void qemu_set_irq(qemu_irq irq, int level)
return;
irq->handler(irq->opaque, irq->n, level);
+ if (unlikely(irq->observer)) {
+ irq->observer(irq->opaque, irq->n, level);
+ }
}
static void init_irq_fields(IRQState *irq, qemu_irq_handler handler,
@@ -111,7 +114,7 @@ static void qemu_notirq(void *opaque, int line, int level)
{
IRQState *irq = opaque;
- irq->handler(irq->opaque, irq->n, !level);
+ qemu_set_irq(irq, !level);
}
qemu_irq qemu_irq_invert(qemu_irq irq)
@@ -124,11 +127,8 @@ qemu_irq qemu_irq_invert(qemu_irq irq)
void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n)
{
int i;
- qemu_irq *old_irqs = qemu_allocate_irqs(NULL, NULL, n);
for (i = 0; i < n; i++) {
- *old_irqs[i] = *gpio_in[i];
- gpio_in[i]->handler = handler;
- gpio_in[i]->opaque = &old_irqs[i];
+ gpio_in[i]->observer = handler;
}
}
diff --git a/system/qtest.c b/system/qtest.c
index a79d10d1361..b359a3e1c84 100644
--- a/system/qtest.c
+++ b/system/qtest.c
@@ -324,9 +324,6 @@ void qtest_sendf(CharFrontend *chr, const char *fmt, ...)
static void qtest_irq_handler(void *opaque, int n, int level)
{
- qemu_irq old_irq = *(qemu_irq *)opaque;
- qemu_set_irq(old_irq, level);
-
if (irq_levels[n] != level) {
CharFrontend *chr = &qtest->qtest_chr;
irq_levels[n] = level;
diff --git a/scripts/lsan_suppressions.txt b/scripts/lsan_suppressions.txt
index f88bbab18b8..30256bc6d01 100644
--- a/scripts/lsan_suppressions.txt
+++ b/scripts/lsan_suppressions.txt
@@ -16,11 +16,3 @@ leak:/lib64/libxkbcommon.so.0
# https://github.com/GNOME/glib/blob/main/tools/glib.supp
# This avoids false positive leak reports for the qga-ssh-test.
leak:g_set_user_dirs
-
-# qemu_irq_intercept_in is only used by the qtest harness, and
-# its API inherently involves a leak.
-# While we could keep track of the old IRQ data structure
-# in order to free it, it doesn't seem very important to fix
-# since it is only used by the qtest test harness.
-# Just ignore the leak, at least for the moment.
-leak:qemu_irq_intercept_in
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 04/32] scripts/lsan_suppressions: suppress fontconfig leaks
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (2 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 05/32] vfio/pci: close display console during unrealize, not finalize Marc-André Lureau
` (27 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Those are annoying reports for gtk/sdl etc.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
scripts/lsan_suppressions.txt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/scripts/lsan_suppressions.txt b/scripts/lsan_suppressions.txt
index 30256bc6d01..f3b827facff 100644
--- a/scripts/lsan_suppressions.txt
+++ b/scripts/lsan_suppressions.txt
@@ -10,6 +10,10 @@ leak:/lib64/libtcmalloc_minimal.so.4
# libxkbcommon also leaks in qemu-keymap
leak:/lib64/libxkbcommon.so.0
+# libfontconfig leaks are notorious, for ex
+# https://gitlab.freedesktop.org/fontconfig/fontconfig/-/work_items/519
+leak:libfontconfig.so
+
# g_set_user_dirs() deliberately leaks the previous cached g_get_user_*
# values. This is documented in upstream glib's valgrind-format
# suppression file:
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 05/32] vfio/pci: close display console during unrealize, not finalize
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (3 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 04/32] scripts/lsan_suppressions: suppress fontconfig leaks Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 06/32] glib-compat: add fallback for g_clear_fd/g_autofd Marc-André Lureau
` (26 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
The QemuGraphicConsole holds a strong QOM link back to the device via
its "device" property (OBJ_PROP_LINK_STRONG). When graphic_console_close()
is only called from vfio_display_finalize() during object finalize, this
creates a ref-cycle deadlock: the device can't reach refcount 0 because
the console holds a strong ref, but the console's ref is only dropped by
graphic_console_close() which runs inside finalize.
Split the display teardown into two phases:
- vfio_display_exit(): called during unrealize (vfio_exitfn), closes
the graphic console to break the ref cycle, and removes display
region subregions while the parent memory regions are still alive.
- vfio_display_finalize(): remains in finalize (vfio_pci_put_device),
frees display region memory, dmabuf, and edid resources. The region
memory contains QOM child objects (MemoryRegions) that must stay
alive until QOM finalization has processed them.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
hw/vfio/pci.h | 1 +
hw/vfio/display.c | 30 +++++++++++++++++++-----------
hw/vfio/pci.c | 1 +
3 files changed, 21 insertions(+), 11 deletions(-)
diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h
index c3a1f53d350..cf567115870 100644
--- a/hw/vfio/pci.h
+++ b/hw/vfio/pci.h
@@ -270,6 +270,7 @@ bool vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp);
void vfio_display_reset(VFIOPCIDevice *vdev);
bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp);
+void vfio_display_exit(VFIOPCIDevice *vdev);
void vfio_display_finalize(VFIOPCIDevice *vdev);
extern const VMStateDescription vfio_display_vmstate;
diff --git a/hw/vfio/display.c b/hw/vfio/display.c
index 8f91e83da88..34cc25ee0e0 100644
--- a/hw/vfio/display.c
+++ b/hw/vfio/display.c
@@ -505,15 +505,6 @@ static bool vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
return true;
}
-static void vfio_display_region_exit(VFIODisplay *dpy)
-{
- if (!dpy->region.buffer.size) {
- return;
- }
-
- vfio_region_exit(&dpy->region.buffer);
- vfio_region_finalize(&dpy->region.buffer);
-}
/* ---------------------------------------------------------------------- */
@@ -547,17 +538,34 @@ bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
return false;
}
-void vfio_display_finalize(VFIOPCIDevice *vdev)
+void vfio_display_exit(VFIOPCIDevice *vdev)
{
if (!vdev->dpy) {
return;
}
+ if (display_opengl) {
+ qemu_console_set_display_gl_ctx(vdev->dpy->con, NULL);
+ }
qemu_graphic_console_close(vdev->dpy->con);
+ if (vdev->dpy->region.buffer.size) {
+ vfio_region_exit(&vdev->dpy->region.buffer);
+ }
+}
+
+void vfio_display_finalize(VFIOPCIDevice *vdev)
+{
+ if (!vdev->dpy) {
+ return;
+ }
+
vfio_display_dmabuf_exit(vdev->dpy);
- vfio_display_region_exit(vdev->dpy);
+ if (vdev->dpy->region.buffer.size) {
+ vfio_region_finalize(&vdev->dpy->region.buffer);
+ }
vfio_display_edid_exit(vdev->dpy);
g_free(vdev->dpy);
+ vdev->dpy = NULL;
}
static bool migrate_needed(void *opaque)
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
index 9c06b25e637..78beacd24e1 100644
--- a/hw/vfio/pci.c
+++ b/hw/vfio/pci.c
@@ -3624,6 +3624,7 @@ static void vfio_exitfn(PCIDevice *pdev)
VFIOPCIDevice *vdev = VFIO_PCI_DEVICE(pdev);
VFIODevice *vbasedev = &vdev->vbasedev;
+ vfio_display_exit(vdev);
vfio_unregister_req_notifier(vdev);
vfio_unregister_err_notifier(vdev);
pci_device_set_intx_routing_notifier(pdev, NULL);
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 06/32] glib-compat: add fallback for g_clear_fd/g_autofd
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (4 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 05/32] vfio/pci: close display console during unrealize, not finalize Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 07/32] ui/dbus: remove mouse handler on dispose Marc-André Lureau
` (25 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Those helpers were added in glib 2.76.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/glib-compat.h | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/include/glib-compat.h b/include/glib-compat.h
index 2e32b90f051..32ee2afdcdf 100644
--- a/include/glib-compat.h
+++ b/include/glib-compat.h
@@ -30,6 +30,7 @@
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <glib.h>
+#include <glib/gstdio.h>
#if defined(G_OS_UNIX)
#include <glib-unix.h>
#include <sys/types.h>
@@ -129,6 +130,34 @@ qemu_g_test_slow(void)
#define g_test_thorough() qemu_g_test_slow()
#define g_test_quick() (!qemu_g_test_slow())
+static inline gboolean g_clear_fd_qemu(int *fd_ptr, GError **error)
+{
+#if GLIB_CHECK_VERSION(2, 76, 0)
+ return g_clear_fd(fd_ptr, error);
+#else
+ int fd = *fd_ptr;
+
+ *fd_ptr = -1;
+
+ if (fd < 0) {
+ return TRUE;
+ }
+
+ return g_close(fd, error);
+#endif
+}
+#define g_clear_fd(fd, err) g_clear_fd_qemu(fd, err)
+
+#if !GLIB_CHECK_VERSION(2, 76, 0)
+static inline void _g_clear_fd_ignore_error(int *fd_ptr)
+{
+ int errsv = errno;
+ g_clear_fd(fd_ptr, NULL);
+ errno = errsv;
+}
+#define g_autofd __attribute__((cleanup(_g_clear_fd_ignore_error)))
+#endif
+
#pragma GCC diagnostic pop
#ifndef G_NORETURN
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 07/32] ui/dbus: remove mouse handler on dispose
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (5 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 06/32] glib-compat: add fallback for g_clear_fd/g_autofd Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 08/32] ui/qmp: keep a reference of console across yield Marc-André Lureau
` (24 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Fixes: 142ca628a7 ("ui: add a D-Bus display backend")
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/dbus-console.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
index 21eceb24362..0813a08f85e 100644
--- a/ui/dbus-console.c
+++ b/ui/dbus-console.c
@@ -151,6 +151,7 @@ dbus_display_console_dispose(GObject *object)
DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
qemu_console_unregister_listener(&ddc->dcl);
+ qemu_remove_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
g_clear_object(&ddc->iface_touch);
g_clear_object(&ddc->iface_mouse);
g_clear_object(&ddc->iface_kbd);
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 08/32] ui/qmp: keep a reference of console across yield
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (6 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 07/32] ui/dbus: remove mouse handler on dispose Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 09/32] ui: stop ui timer when closing Marc-André Lureau
` (23 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
While the coroutine is waiting, the console could be finalized. Keep a
reference to prevent this.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/ui-qmp-cmds.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/ui/ui-qmp-cmds.c b/ui/ui-qmp-cmds.c
index 1173c82cf7f..753cc2bf522 100644
--- a/ui/ui-qmp-cmds.c
+++ b/ui/ui-qmp-cmds.c
@@ -348,6 +348,7 @@ qmp_screendump(const char *filename, const char *device,
}
}
+ object_ref(con);
qemu_console_co_wait_update(con);
/*
@@ -358,9 +359,11 @@ qmp_screendump(const char *filename, const char *device,
surface = qemu_console_surface(con);
if (!surface) {
error_setg(errp, "no surface");
+ object_unref(con);
return;
}
image = pixman_image_ref(surface->image);
+ object_unref(con);
fd = qemu_create(filename, O_WRONLY | O_TRUNC | O_BINARY, 0666, errp);
if (fd == -1) {
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 09/32] ui: stop ui timer when closing
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (7 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 08/32] ui/qmp: keep a reference of console across yield Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 10/32] ui/console: init gl_unblock_timer in qemu_console_init Marc-André Lureau
` (22 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
hwops is reset, so if the UI timer is pending it will crash.
Fixes: 9588d67e72 ("console: minimal hotplug suport")
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/console.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/ui/console.c b/ui/console.c
index a7c977d0c44..436444723a5 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1136,6 +1136,7 @@ void qemu_graphic_console_close(QemuConsole *con)
trace_console_gfx_close(con->index);
object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
qemu_graphic_console_set_hwops(con, &unused_ops, NULL);
+ timer_del(con->ui_timer);
if (con->gl) {
qemu_console_gl_scanout_disable(con);
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 10/32] ui/console: init gl_unblock_timer in qemu_console_init
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (8 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 09/32] ui: stop ui timer when closing Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 11/32] ui/spice: remove dead spice_displays Marc-André Lureau
` (21 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Move gl_unblock_timer allocation from graphic_console_init() to
qemu_console_init(), similar to what was done in commit cfde05d15b
("ui/console: allocate ui_timer in QemuConsole").
This fixes leaking timers on console recycling.
Fixes: a9b1e471e17 ("ui: add a gl-unblock warning timer")
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/console.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ui/console.c b/ui/console.c
index 436444723a5..58f29e82c85 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -433,6 +433,8 @@ qemu_console_init(Object *obj)
c->window_id = -1;
c->ui_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
dpy_set_ui_info_timer, c);
+ c->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ console_hw_gl_unblock_timer, c);
qemu_console_register(c);
}
@@ -1116,8 +1118,6 @@ QemuConsole *qemu_graphic_console_create(DeviceState *dev, uint32_t head,
surface = qemu_create_placeholder_surface(width, height, noinit);
qemu_console_set_surface(s, surface);
- s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
- console_hw_gl_unblock_timer, s);
return s;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 11/32] ui/spice: remove dead spice_displays
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (9 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 10/32] ui/console: init gl_unblock_timer in qemu_console_init Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 12/32] ui/spice: add cleanup on shutdown Marc-André Lureau
` (20 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
This is left-over from commit 9fa032866da ("spice: fix multihead support")
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/qemu-spice.h | 7 -------
1 file changed, 7 deletions(-)
diff --git a/include/ui/qemu-spice.h b/include/ui/qemu-spice.h
index 111a09ceca3..59a68cd9833 100644
--- a/include/ui/qemu-spice.h
+++ b/include/ui/qemu-spice.h
@@ -33,13 +33,6 @@ bool qemu_spice_have_display_interface(QemuConsole *con);
int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con);
int qemu_spice_migrate_info(const char *hostname, int port, int tls_port,
const char *subject);
-
-#else /* CONFIG_SPICE */
-
-#include "qemu/error-report.h"
-
-#define spice_displays 0
-
#endif /* CONFIG_SPICE */
static inline bool qemu_using_spice(Error **errp)
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 12/32] ui/spice: add cleanup on shutdown
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (10 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 11/32] ui/spice: remove dead spice_displays Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 13/32] ui: add display cleanup infrastructure Marc-André Lureau
` (19 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
SPICE resources were never freed on shutdown. Add per-subsystem
cleanup (display, input, core) and call it from qemu_cleanup().
Move spice-module.c into libui so the qemu_spice ops table links
with the rest of the UI code. Add an LSan suppression for a known
spice-server leak.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/qemu-spice-module.h | 1 +
include/ui/qemu-spice.h | 2 ++
system/runstate.c | 4 ++++
ui/spice-core.c | 25 ++++++++++++++++++--
ui/spice-display.c | 52 ++++++++++++++++++++++++++++++++++++++++++
ui/spice-input.c | 52 ++++++++++++++++++++++++++++--------------
ui/spice-module.c | 5 ++++
scripts/lsan_suppressions.txt | 5 ++++
ui/meson.build | 4 ++--
9 files changed, 129 insertions(+), 21 deletions(-)
diff --git a/include/ui/qemu-spice-module.h b/include/ui/qemu-spice-module.h
index 072efa0c834..bb0f8437c26 100644
--- a/include/ui/qemu-spice-module.h
+++ b/include/ui/qemu-spice-module.h
@@ -26,6 +26,7 @@ typedef struct SpiceInfo SpiceInfo;
struct QemuSpiceOps {
void (*init)(void);
+ void (*cleanup)(void);
void (*display_init)(void);
int (*migrate_info)(const char *h, int p, int t, const char *s);
int (*set_passwd)(const char *passwd,
diff --git a/include/ui/qemu-spice.h b/include/ui/qemu-spice.h
index 59a68cd9833..2cdf10f0313 100644
--- a/include/ui/qemu-spice.h
+++ b/include/ui/qemu-spice.h
@@ -27,7 +27,9 @@
#include "qemu/config-file.h"
void qemu_spice_input_init(void);
+void qemu_spice_input_cleanup(void);
void qemu_spice_display_init(void);
+void qemu_spice_display_cleanup(void);
void qemu_spice_display_init_done(void);
bool qemu_spice_have_display_interface(QemuConsole *con);
int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con);
diff --git a/system/runstate.c b/system/runstate.c
index 0e1cb3b4e67..d35fa270bd6 100644
--- a/system/runstate.c
+++ b/system/runstate.c
@@ -62,6 +62,7 @@
#include "system/system.h"
#include "system/tpm.h"
#include "ui/console.h"
+#include "ui/qemu-spice-module.h"
#include "trace.h"
@@ -1048,6 +1049,9 @@ void qemu_cleanup(int status)
user_creatable_cleanup();
#ifdef CONFIG_VNC
vnc_cleanup();
+#endif
+#ifdef CONFIG_SPICE
+ qemu_spice.cleanup();
#endif
/* TODO: unref root container, check all devices are ok */
}
diff --git a/ui/spice-core.c b/ui/spice-core.c
index ef1c00134fa..1d2315f0b63 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
@@ -651,12 +651,15 @@ static void vm_change_state_handler(void *opaque, bool running,
}
}
+static VMChangeStateEntry *vm_change_entry;
+
void qemu_spice_display_init_done(void)
{
if (runstate_is_running()) {
qemu_spice_display_start();
}
- qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
+ vm_change_entry =
+ qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
}
static void qemu_spice_init(void)
@@ -894,7 +897,8 @@ static int qemu_spice_add_interface(SpiceBaseInstance *sin)
spice_server = spice_server_new();
spice_server_set_sasl_appname(spice_server, "qemu");
spice_server_init(spice_server, &core_interface);
- qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
+ vm_change_entry =
+ qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
}
return spice_server_add_interface(spice_server, sin);
@@ -1005,8 +1009,25 @@ int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd)
return spice_display_is_running;
}
+static void qemu_spice_cleanup(void)
+{
+ if (!spice_server) {
+ return;
+ }
+
+ qemu_spice_display_cleanup();
+ qemu_spice_input_cleanup();
+ migration_remove_notifier(&migration_state);
+ g_clear_pointer(&spice_consoles, g_slist_free);
+ g_clear_pointer(&auth_passwd, g_free);
+ g_clear_pointer(&spice_server, spice_server_destroy);
+ g_clear_pointer(&vm_change_entry, qemu_del_vm_change_state_handler);
+ using_spice = 0;
+}
+
static struct QemuSpiceOps real_spice_ops = {
.init = qemu_spice_init,
+ .cleanup = qemu_spice_cleanup,
.display_init = qemu_spice_display_init,
.migrate_info = qemu_spice_migrate_info,
.set_passwd = qemu_spice_set_passwd,
diff --git a/ui/spice-display.c b/ui/spice-display.c
index e3716127203..75c7df7bb5e 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -34,6 +34,8 @@ bool spice_opengl;
bool spice_remote_client;
int spice_max_refresh_rate;
+static GPtrArray *spice_displays;
+
int qemu_spice_rect_is_empty(const QXLRect* r)
{
return r->top == r->bottom || r->left == r->right;
@@ -1421,6 +1423,54 @@ static void qemu_spice_display_init_one(QemuConsole *con)
qemu_console_set_display_gl_ctx(con, &ssd->dgc);
}
qemu_console_register_listener(con, &ssd->dcl, ops);
+ g_ptr_array_add(spice_displays, ssd);
+}
+
+void qemu_spice_display_cleanup(void)
+{
+ if (!spice_displays) {
+ return;
+ }
+
+ for (guint i = 0; i < spice_displays->len; i++) {
+ SimpleSpiceDisplay *ssd = g_ptr_array_index(spice_displays, i);
+ SimpleSpiceUpdate *update;
+
+ qemu_console_unregister_listener(&ssd->dcl);
+#ifdef HAVE_SPICE_GL
+ if (spice_opengl) {
+ qemu_console_set_display_gl_ctx(ssd->dcl.con, NULL);
+ }
+#endif
+
+ if (ssd->ds) {
+ qemu_spice_destroy_host_primary(ssd);
+ }
+ qemu_spice_del_memslot(ssd, MEMSLOT_GROUP_HOST, 0);
+ spice_server_remove_interface(&ssd->qxl.base);
+
+ while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) {
+ QTAILQ_REMOVE(&ssd->updates, update, next);
+ qemu_spice_destroy_update(ssd, update);
+ }
+ g_clear_pointer(&ssd->ptr_define, g_free);
+ g_clear_pointer(&ssd->ptr_move, g_free);
+ g_clear_pointer(&ssd->cursor, cursor_unref);
+ g_clear_pointer(&ssd->surface, pixman_image_unref);
+ g_clear_pointer(&ssd->mirror, pixman_image_unref);
+ g_clear_pointer(&ssd->buf, g_free);
+#ifdef HAVE_SPICE_GL
+ g_clear_pointer(&ssd->gl_unblock_bh, qemu_bh_delete);
+ g_clear_pointer(&ssd->gl_unblock_timer, timer_free);
+ g_clear_pointer(&ssd->gls, qemu_gl_fini_shader);
+ egl_fb_destroy(&ssd->guest_fb);
+ egl_fb_destroy(&ssd->blit_fb);
+ egl_fb_destroy(&ssd->cursor_fb);
+#endif
+ qemu_mutex_destroy(&ssd->lock);
+ g_free(ssd);
+ }
+ g_clear_pointer(&spice_displays, g_ptr_array_unref);
}
void qemu_spice_display_init(void)
@@ -1431,6 +1481,8 @@ void qemu_spice_display_init(void)
const char *str;
int i;
+ spice_displays = g_ptr_array_new();
+
str = qemu_opt_get(opts, "display");
if (str) {
int head = qemu_opt_get_number(opts, "head", 0);
diff --git a/ui/spice-input.c b/ui/spice-input.c
index f0bb915fd77..c975c1e2516 100644
--- a/ui/spice-input.c
+++ b/ui/spice-input.c
@@ -239,23 +239,41 @@ static void mouse_mode_notifier(Notifier *notifier, void *data)
pointer->absolute = is_absolute;
}
+static QemuSpiceKbd *spice_kbd;
+static QemuSpicePointer *spice_pointer;
+static QEMUPutLEDEntry *spice_led;
+
void qemu_spice_input_init(void)
{
- QemuSpiceKbd *kbd;
- QemuSpicePointer *pointer;
-
- kbd = g_malloc0(sizeof(*kbd));
- kbd->sin.base.sif = &kbd_interface.base;
- qemu_spice.add_interface(&kbd->sin.base);
- qemu_add_led_event_handler(kbd_leds, kbd);
-
- pointer = g_malloc0(sizeof(*pointer));
- pointer->mouse.base.sif = &mouse_interface.base;
- pointer->tablet.base.sif = &tablet_interface.base;
- qemu_spice.add_interface(&pointer->mouse.base);
-
- pointer->absolute = false;
- pointer->mouse_mode.notify = mouse_mode_notifier;
- qemu_add_mouse_mode_change_notifier(&pointer->mouse_mode);
- mouse_mode_notifier(&pointer->mouse_mode, NULL);
+ spice_kbd = g_new0(QemuSpiceKbd, 1);
+ spice_kbd->sin.base.sif = &kbd_interface.base;
+ qemu_spice.add_interface(&spice_kbd->sin.base);
+ spice_led = qemu_add_led_event_handler(kbd_leds, spice_kbd);
+
+ spice_pointer = g_new0(QemuSpicePointer, 1);
+ spice_pointer->mouse.base.sif = &mouse_interface.base;
+ spice_pointer->tablet.base.sif = &tablet_interface.base;
+ qemu_spice.add_interface(&spice_pointer->mouse.base);
+
+ spice_pointer->absolute = false;
+ spice_pointer->mouse_mode.notify = mouse_mode_notifier;
+ qemu_add_mouse_mode_change_notifier(&spice_pointer->mouse_mode);
+ mouse_mode_notifier(&spice_pointer->mouse_mode, NULL);
+}
+
+void qemu_spice_input_cleanup(void)
+{
+ g_clear_pointer(&spice_led, qemu_remove_led_event_handler);
+ if (spice_pointer) {
+ qemu_remove_mouse_mode_change_notifier(&spice_pointer->mouse_mode);
+ if (spice_pointer->absolute) {
+ spice_server_remove_interface(&spice_pointer->tablet.base);
+ }
+ spice_server_remove_interface(&spice_pointer->mouse.base);
+ g_clear_pointer(&spice_pointer, g_free);
+ }
+ if (spice_kbd) {
+ spice_server_remove_interface(&spice_kbd->sin.base);
+ g_clear_pointer(&spice_kbd, g_free);
+ }
}
diff --git a/ui/spice-module.c b/ui/spice-module.c
index 7651c85885f..1961060d128 100644
--- a/ui/spice-module.c
+++ b/ui/spice-module.c
@@ -62,6 +62,10 @@ static int qemu_spice_display_add_client_stub(int csock, int skipauth,
return -1;
}
+static void qemu_spice_cleanup_stub(void)
+{
+}
+
struct QemuSpiceOps qemu_spice = {
.init = qemu_spice_init_stub,
.display_init = qemu_spice_display_init_stub,
@@ -69,6 +73,7 @@ struct QemuSpiceOps qemu_spice = {
.set_passwd = qemu_spice_set_passwd_stub,
.set_pw_expire = qemu_spice_set_pw_expire_stub,
.display_add_client = qemu_spice_display_add_client_stub,
+ .cleanup = qemu_spice_cleanup_stub,
};
#ifdef CONFIG_SPICE
diff --git a/scripts/lsan_suppressions.txt b/scripts/lsan_suppressions.txt
index f3b827facff..2dd6581a650 100644
--- a/scripts/lsan_suppressions.txt
+++ b/scripts/lsan_suppressions.txt
@@ -20,3 +20,8 @@ leak:libfontconfig.so
# https://github.com/GNOME/glib/blob/main/tools/glib.supp
# This avoids false positive leak reports for the qga-ssh-test.
leak:g_set_user_dirs
+
+# spice_server_add_interface allocates internal channel data that
+# spice_server_destroy does not free
+# https://gitlab.freedesktop.org/spice/spice/-/merge_requests/246
+leak:spice_server_add_interface
diff --git a/ui/meson.build b/ui/meson.build
index bb01f0728e2..ca903581abd 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -42,13 +42,14 @@ libui_sources = files(
'kbd-state.c',
'keymaps.c',
'qemu-pixman.c',
+ 'spice-module.c',
'vgafont.c',
)
if pixman.found()
libui_sources += files('cp437.c', 'vt100.c')
endif
libui = static_library('qemuui', libui_sources + genh,
- dependencies: [pixman],
+ dependencies: [pixman, spice_headers],
build_by_default: false)
ui = declare_dependency(objects: libui.extract_all_objects(recursive: false), dependencies: [pixman])
system_ss.add(png)
@@ -65,7 +66,6 @@ system_ss.add(when: pixman, if_true: files('console-vc.c'), if_false: files('con
if dbus_display
system_ss.add(files('dbus-module.c'))
endif
-system_ss.add([spice_headers, files('spice-module.c')])
system_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
if host_os == 'linux'
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 13/32] ui: add display cleanup infrastructure
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (11 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 12/32] ui/spice: add cleanup on shutdown Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 14/32] ui/curses: implement display cleanup Marc-André Lureau
` (18 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Add a cleanup callback to QemuDisplay and a qemu_display_cleanup()
function that iterates all registered display types and calls their
cleanup handler. Wire it into qemu_cleanup() in runstate.c, replacing
the ad-hoc vnc_cleanup() call.
This provides a structured alternative to atexit() handlers, giving
deterministic teardown ordering and making resource leaks visible to
sanitizers.
The cleanup should happen before user_creatable_cleanup(), since some
display have weak user-creatable references to cleanup before.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/console.h | 2 ++
system/runstate.c | 8 +-------
ui/console.c | 20 +++++++++++++++++++-
3 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/include/ui/console.h b/include/ui/console.h
index a31c5a4995c..1c7f7e62c1a 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -430,6 +430,7 @@ struct QemuDisplay {
DisplayType type;
void (*early_init)(DisplayOptions *opts);
void (*init)(DisplayState *ds, DisplayOptions *opts);
+ void (*cleanup)(void);
const char *vc;
};
@@ -438,6 +439,7 @@ bool qemu_display_find_default(DisplayOptions *opts);
void qemu_display_early_init(DisplayOptions *opts);
void qemu_display_init(DisplayState *ds, DisplayOptions *opts);
const char *qemu_display_get_vc(DisplayOptions *opts);
+void qemu_display_cleanup(void);
void qemu_display_help(void);
/* vnc.c */
diff --git a/system/runstate.c b/system/runstate.c
index d35fa270bd6..18e585be47f 100644
--- a/system/runstate.c
+++ b/system/runstate.c
@@ -62,7 +62,6 @@
#include "system/system.h"
#include "system/tpm.h"
#include "ui/console.h"
-#include "ui/qemu-spice-module.h"
#include "trace.h"
@@ -1046,12 +1045,7 @@ void qemu_cleanup(int status)
audio_cleanup();
monitor_cleanup();
qemu_chr_cleanup();
+ qemu_display_cleanup();
user_creatable_cleanup();
-#ifdef CONFIG_VNC
- vnc_cleanup();
-#endif
-#ifdef CONFIG_SPICE
- qemu_spice.cleanup();
-#endif
/* TODO: unref root container, check all devices are ok */
}
diff --git a/ui/console.c b/ui/console.c
index 58f29e82c85..1fe3e3a3a6c 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -42,6 +42,7 @@
#include "qemu/memfd.h"
#include "ui/vt100.h"
#include "vgafont.h"
+#include "ui/qemu-spice.h"
#include "console-priv.h"
@@ -575,7 +576,7 @@ void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl)
{
/* display has opengl support */
assert(con);
- if (con->gl) {
+ if (gl && con->gl) {
error_report("The console already has an OpenGL context.");
exit(1);
}
@@ -1414,6 +1415,23 @@ void qemu_display_init(DisplayState *ds, DisplayOptions *opts)
dpys[opts->type]->init(ds, opts);
}
+void qemu_display_cleanup(void)
+{
+ int i;
+
+ for (i = 0; i < DISPLAY_TYPE__MAX; i++) {
+ if (dpys[i] && dpys[i]->cleanup) {
+ dpys[i]->cleanup();
+ }
+ }
+#ifdef CONFIG_VNC
+ vnc_cleanup();
+#endif
+#ifdef CONFIG_SPICE
+ qemu_spice.cleanup();
+#endif
+}
+
const char *qemu_display_get_vc(DisplayOptions *opts)
{
#ifdef CONFIG_PIXMAN
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 14/32] ui/curses: implement display cleanup
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (12 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 13/32] ui: add display cleanup infrastructure Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 15/32] ui/sdl2: " Marc-André Lureau
` (17 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Replace the atexit() handler with a proper cleanup callback. The new
curses_cleanup() unregisters the display listener, destroy & free the
allocated resources.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/curses.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/ui/curses.c b/ui/curses.c
index 24d3713e57d..4db32b6168d 100644
--- a/ui/curses.c
+++ b/ui/curses.c
@@ -411,11 +411,19 @@ static void curses_refresh(DisplayChangeListener *dcl)
}
}
-static void curses_atexit(void)
+static void curses_cleanup(void)
{
+ if (!dcl) {
+ return;
+ }
+
endwin();
- g_free(vga_to_curses);
- g_free(screen);
+ qemu_console_unregister_listener(dcl);
+ g_clear_pointer(&dcl, g_free);
+ g_clear_pointer(&screenpad, delwin);
+ g_clear_pointer(&vga_to_curses, g_free);
+ g_clear_pointer(&screen, g_free);
+ g_clear_pointer(&kbd_layout, kbd_layout_free);
}
/*
@@ -799,8 +807,6 @@ static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
vga_to_curses = g_new0(cchar_t, 256);
curses_setup();
curses_keyboard_setup();
- atexit(curses_atexit);
-
curses_winch_init();
dcl = g_new0(DisplayChangeListener, 1);
@@ -812,6 +818,7 @@ static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
static QemuDisplay qemu_display_curses = {
.type = DISPLAY_TYPE_CURSES,
.init = curses_display_init,
+ .cleanup = curses_cleanup,
};
static void register_curses(void)
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 15/32] ui/sdl2: implement display cleanup
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (13 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 14/32] ui/curses: implement display cleanup Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 16/32] ui/spice-app: " Marc-André Lureau
` (16 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Replace the atexit() handler with a proper cleanup callback. Extend
sdl_cleanup() to unregister display listeners, free keyboard state,
destroy windows, and clean up all cursor resources.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/sdl2.c | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/ui/sdl2.c b/ui/sdl2.c
index 4fcdbd79d3c..6adcbdb5af9 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -788,9 +788,25 @@ static void sdl_mouse_define(DisplayChangeListener *dcl,
static void sdl_cleanup(void)
{
- if (guest_sprite) {
- SDL_FreeCursor(guest_sprite);
+ int i;
+
+ if (!sdl2_console) {
+ return;
}
+
+ qemu_remove_mouse_mode_change_notifier(&mouse_mode_notifier);
+
+ for (i = 0; i < sdl2_num_outputs; i++) {
+ qemu_console_unregister_listener(&sdl2_console[i].dcl);
+ qkbd_state_free(sdl2_console[i].kbd);
+ sdl2_window_destroy(&sdl2_console[i]);
+ }
+ g_clear_pointer(&sdl2_console, g_free);
+ sdl2_num_outputs = 0;
+
+ g_clear_pointer(&guest_sprite, SDL_FreeCursor);
+ g_clear_pointer(&guest_sprite_surface, SDL_FreeSurface);
+ g_clear_pointer(&sdl_cursor_hidden, SDL_FreeCursor);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
@@ -995,8 +1011,6 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
sdl_grab_start(&sdl2_console[0]);
}
- atexit(sdl_cleanup);
-
/* SDL's event polling (in dpy_refresh) must happen on the main thread. */
qemu_main = NULL;
}
@@ -1005,6 +1019,7 @@ static QemuDisplay qemu_display_sdl2 = {
.type = DISPLAY_TYPE_SDL,
.early_init = sdl2_display_early_init,
.init = sdl2_display_init,
+ .cleanup = sdl_cleanup,
};
static void register_sdl1(void)
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 16/32] ui/spice-app: implement display cleanup
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (14 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 15/32] ui/sdl2: " Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 17/32] ui/egl: implement display and EGL cleanup Marc-André Lureau
` (15 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Replace the atexit() handler with the display cleanup callback,
reusing the existing spice_app_atexit() function.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/spice-app.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/ui/spice-app.c b/ui/spice-app.c
index 0df7325e679..fe3df62bfa5 100644
--- a/ui/spice-app.c
+++ b/ui/spice-app.c
@@ -119,16 +119,17 @@ static const TypeInfo char_vc_type_info = {
.class_size = sizeof(VCChardevClass),
};
-static void spice_app_atexit(void)
+static void spice_app_cleanup(void)
{
if (sock_path) {
unlink(sock_path);
+ g_clear_pointer(&sock_path, g_free);
}
if (tmp_dir) {
rmdir(tmp_dir);
+ tmp_dir = NULL;
}
- g_free(sock_path);
- g_free(app_dir);
+ g_clear_pointer(&app_dir, g_free);
}
static void spice_app_display_early_init(DisplayOptions *opts)
@@ -146,8 +147,6 @@ static void spice_app_display_early_init(DisplayOptions *opts)
exit(1);
}
- atexit(spice_app_atexit);
-
if (qemu_name) {
app_dir = g_build_filename(g_get_user_runtime_dir(),
"qemu", qemu_name, NULL);
@@ -218,6 +217,7 @@ static QemuDisplay qemu_display_spice_app = {
.type = DISPLAY_TYPE_SPICE_APP,
.early_init = spice_app_display_early_init,
.init = spice_app_display_init,
+ .cleanup = spice_app_cleanup,
.vc = "vc",
};
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 17/32] ui/egl: implement display and EGL cleanup
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (15 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 16/32] ui/spice-app: " Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 18/32] ui/cocoa: implement display cleanup Marc-André Lureau
` (14 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Add egl_cleanup() to tear down the EGL render node context, GBM device,
and EGL display. Add egl_headless_cleanup() to unregister listeners,
destroy framebuffers, and free per-console state tracked via a new
GPtrArray.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/egl-helpers.h | 1 +
ui/egl-headless.c | 31 +++++++++++++++++++++++++++++++
ui/egl-helpers.c | 19 +++++++++++++++++++
3 files changed, 51 insertions(+)
diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h
index acf993fcf52..dba788fead7 100644
--- a/include/ui/egl-helpers.h
+++ b/include/ui/egl-helpers.h
@@ -76,6 +76,7 @@ EGLContext qemu_egl_init_ctx(void);
bool qemu_egl_has_dmabuf(void);
bool egl_init(const char *rendernode, DisplayGLMode mode, Error **errp);
+void egl_cleanup(void);
const char *qemu_egl_get_error_string(void);
diff --git a/ui/egl-headless.c b/ui/egl-headless.c
index 878bfebb40c..ba27efcf4c3 100644
--- a/ui/egl-headless.c
+++ b/ui/egl-headless.c
@@ -9,6 +9,7 @@
typedef struct egl_dpy {
DisplayChangeListener dcl;
+ DisplayGLCtx *ctx;
DisplaySurface *ds;
QemuGLShader *gls;
egl_fb guest_fb;
@@ -19,6 +20,8 @@ typedef struct egl_dpy {
uint32_t pos_y;
} egl_dpy;
+static GPtrArray *egl_dpys;
+
/* ------------------------------------------------------------------ */
static void egl_refresh(DisplayChangeListener *dcl)
@@ -220,6 +223,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
egl_dpy *edpy;
int idx;
+ egl_dpys = g_ptr_array_new();
+
for (idx = 0;; idx++) {
DisplayGLCtx *ctx;
@@ -232,15 +237,41 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
edpy->gls = qemu_gl_init_shader();
ctx = g_new0(DisplayGLCtx, 1);
ctx->ops = &eglctx_ops;
+ edpy->ctx = ctx;
qemu_console_set_display_gl_ctx(con, ctx);
qemu_console_register_listener(con, &edpy->dcl, &egl_ops);
+ g_ptr_array_add(egl_dpys, edpy);
+ }
+}
+
+static void egl_headless_cleanup(void)
+{
+ if (!egl_dpys) {
+ return;
}
+
+ for (guint i = 0; i < egl_dpys->len; i++) {
+ egl_dpy *edpy = g_ptr_array_index(egl_dpys, i);
+
+ qemu_console_unregister_listener(&edpy->dcl);
+ qemu_console_set_display_gl_ctx(edpy->dcl.con, NULL);
+ egl_fb_destroy(&edpy->guest_fb);
+ egl_fb_destroy(&edpy->cursor_fb);
+ egl_fb_destroy(&edpy->blit_fb);
+ qemu_gl_fini_shader(edpy->gls);
+ g_free(edpy->ctx);
+ g_free(edpy);
+ }
+ g_clear_pointer(&egl_dpys, g_ptr_array_unref);
+
+ egl_cleanup();
}
static QemuDisplay qemu_display_egl = {
.type = DISPLAY_TYPE_EGL_HEADLESS,
.early_init = early_egl_headless_init,
.init = egl_headless_init,
+ .cleanup = egl_headless_cleanup,
};
static void register_egl(void)
diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
index e3f2872cc14..a7bfb15fd88 100644
--- a/ui/egl-helpers.c
+++ b/ui/egl-helpers.c
@@ -733,3 +733,22 @@ bool egl_init(const char *rendernode, DisplayGLMode mode, Error **errp)
display_opengl = 1;
return true;
}
+
+void egl_cleanup(void)
+{
+ if (qemu_egl_rn_ctx) {
+ eglDestroyContext(qemu_egl_display, qemu_egl_rn_ctx);
+ qemu_egl_rn_ctx = NULL;
+ }
+
+#ifdef CONFIG_GBM
+ g_clear_pointer(&qemu_egl_rn_gbm_dev, gbm_device_destroy);
+ g_clear_fd(&qemu_egl_rn_fd, NULL);
+#endif
+
+ if (qemu_egl_display) {
+ eglReleaseThread();
+ eglTerminate(qemu_egl_display);
+ qemu_egl_display = NULL;
+ }
+}
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 18/32] ui/cocoa: implement display cleanup
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (16 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 17/32] ui/egl: implement display and EGL cleanup Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 19/32] ui/dbus: " Marc-André Lureau
` (13 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Move cbowner release from QemuCocoaAppController -dealloc to
cocoa_display_cleanup(), since cbowner is allocated in
cocoa_display_init() and cleanup is the symmetric teardown path.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/cocoa.m | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/ui/cocoa.m b/ui/cocoa.m
index c5e639ab98d..bd0c5c6ed9e 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -1331,8 +1331,6 @@ - (void) dealloc
COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
[cocoaView release];
- [cbowner release];
- cbowner = nil;
[super dealloc];
}
@@ -2163,9 +2161,26 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
qemu_main = cocoa_main;
}
+static void cocoa_display_cleanup(void)
+{
+ if (!kbd) {
+ return;
+ }
+
+ qemu_console_unregister_listener(&dcl);
+ g_clear_pointer(&kbd, qkbd_state_free);
+ qemu_remove_mouse_mode_change_notifier(&mouse_mode_change_notifier);
+ qemu_clipboard_peer_unregister(&cbpeer);
+ g_clear_pointer(&cbinfo, qemu_clipboard_info_unref);
+ qemu_event_destroy(&cbevent);
+ [cbowner release];
+ cbowner = nil;
+}
+
static QemuDisplay qemu_display_cocoa = {
.type = DISPLAY_TYPE_COCOA,
.init = cocoa_display_init,
+ .cleanup = cocoa_display_cleanup,
};
static void register_cocoa(void)
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 19/32] ui/dbus: implement display cleanup
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (17 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 18/32] ui/cocoa: implement display cleanup Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 12:17 ` Akihiko Odaki
2026-05-29 11:16 ` [PATCH 20/32] ui/gtk: " Marc-André Lureau
` (12 subsequent siblings)
31 siblings, 1 reply; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Add dbus_cleanup() to unparent the D-Bus display object, ensuring
proper teardown before user_creatable_cleanup() runs.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/dbus-console.c | 3 +++
ui/dbus.c | 13 +++++++++++++
2 files changed, 16 insertions(+)
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
index 0813a08f85e..0048951a7ab 100644
--- a/ui/dbus-console.c
+++ b/ui/dbus-console.c
@@ -151,6 +151,9 @@ dbus_display_console_dispose(GObject *object)
DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
qemu_console_unregister_listener(&ddc->dcl);
+ if (ddc->dcl.con) {
+ qemu_console_set_display_gl_ctx(ddc->dcl.con, NULL);
+ }
qemu_remove_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
g_clear_object(&ddc->iface_touch);
g_clear_object(&ddc->iface_mouse);
diff --git a/ui/dbus.c b/ui/dbus.c
index e02a94df2f3..b23cb44c535 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -615,10 +615,23 @@ static const TypeInfo dbus_display_info = {
}
};
+static void
+dbus_cleanup(void)
+{
+ Object *o;
+
+ o = object_resolve_path_component(object_get_objects_root(),
+ "dbus-display");
+ if (o) {
+ object_unparent(o);
+ }
+}
+
static QemuDisplay qemu_display_dbus = {
.type = DISPLAY_TYPE_DBUS,
.early_init = early_dbus_init,
.init = dbus_init,
+ .cleanup = dbus_cleanup,
.vc = "vc",
};
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 20/32] ui/gtk: implement display cleanup
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (18 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 19/32] ui/dbus: " Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 21/32] ui/console: add console event notifier infrastructure Marc-André Lureau
` (11 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
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.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/gtk.h | 1 +
ui/gtk-clipboard.c | 15 +++++++++++++++
ui/gtk.c | 18 ++++++++++++++++++
3 files changed, 34 insertions(+)
diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 3e6ce3cb48c..5156a049509 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -225,6 +225,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 ea9444be70f..476e6b8303c 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 2ee826b56fb..c26dc83ac3e 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();
@@ -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_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(>k_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",
};
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 21/32] ui/console: add console event notifier infrastructure
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (19 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 20/32] ui/gtk: " Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 22/32] ui/console: fire console ADDED/REMOVED notifications Marc-André Lureau
` (10 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Add a NotifierList to DisplayState so display backends can be notified
when consoles are added or removed at runtime.
No events are fired yet, that follows in the next commits.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/console.h | 18 ++++++++++++++++++
ui/console.c | 23 +++++++++++++++++++++++
2 files changed, 41 insertions(+)
diff --git a/include/ui/console.h b/include/ui/console.h
index 1c7f7e62c1a..44ebc578651 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -442,6 +442,24 @@ const char *qemu_display_get_vc(DisplayOptions *opts);
void qemu_display_cleanup(void);
void qemu_display_help(void);
+/*
+ * Console notifications:
+ * Handlers must be idempotent, it may notify multiple times.
+ */
+typedef enum {
+ QEMU_CONSOLE_ADDED,
+ QEMU_CONSOLE_REMOVED,
+} QemuConsoleEventType;
+
+typedef struct QemuConsoleEvent {
+ QemuConsoleEventType type;
+ QemuConsole *con;
+} QemuConsoleEvent;
+
+void qemu_console_add_notifier(Notifier *notifier);
+void qemu_console_remove_notifier(Notifier *notifier);
+void qemu_console_notify(QemuConsoleEventType type, QemuConsole *con);
+
/* vnc.c */
void vnc_display_add_client(const char *id, int csock, bool skipauth);
int vnc_display_password(const char *id, const char *password, Error **errp);
diff --git a/ui/console.c b/ui/console.c
index 1fe3e3a3a6c..76851bc129d 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -70,6 +70,7 @@ struct DisplayState {
bool refreshing;
QLIST_HEAD(, DisplayChangeListener) listeners;
+ NotifierList console_notifiers;
};
static DisplayState *display_state;
@@ -84,6 +85,16 @@ static bool console_compatible_with(QemuConsole *con,
static QemuConsole *qemu_graphic_console_lookup_unused(void);
static void dpy_set_ui_info_timer(void *opaque);
+void qemu_console_notify(QemuConsoleEventType type, QemuConsole *con)
+{
+ DisplayState *ds = get_alloc_displaystate();
+ QemuConsoleEvent event = {
+ .type = type,
+ .con = con,
+ };
+ notifier_list_notify(&ds->console_notifiers, &event);
+}
+
static void gui_update(void *opaque)
{
uint64_t interval = GUI_REFRESH_INTERVAL_IDLE;
@@ -1056,10 +1067,22 @@ static DisplayState *get_alloc_displaystate(void)
{
if (!display_state) {
display_state = g_new0(DisplayState, 1);
+ notifier_list_init(&display_state->console_notifiers);
}
return display_state;
}
+void qemu_console_add_notifier(Notifier *notifier)
+{
+ DisplayState *ds = get_alloc_displaystate();
+ notifier_list_add(&ds->console_notifiers, notifier);
+}
+
+void qemu_console_remove_notifier(Notifier *notifier)
+{
+ notifier_remove(notifier);
+}
+
/*
* Called by main(), after creating QemuConsoles
* and before initializing ui (sdl/vnc/...).
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 22/32] ui/console: fire console ADDED/REMOVED notifications
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (20 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 21/32] ui/console: add console event notifier infrastructure Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 23/32] ui/console-vc: fire " Marc-André Lureau
` (9 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Fire CONSOLE_ADDED at the end of graphic_console_init() and
CONSOLE_REMOVED at the start of graphic_console_close(), so display
backends can react to console hotplug/unplug events.
REMOVED fires before the device link is cleared and before the
placeholder surface swap, so handlers can unregister their DCL while
the console is still in a known state.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/console.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/ui/console.c b/ui/console.c
index 76851bc129d..975eaf15706 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1142,6 +1142,7 @@ QemuConsole *qemu_graphic_console_create(DeviceState *dev, uint32_t head,
surface = qemu_create_placeholder_surface(width, height, noinit);
qemu_console_set_surface(s, surface);
+ qemu_console_notify(QEMU_CONSOLE_ADDED, s);
return s;
}
@@ -1158,6 +1159,7 @@ void qemu_graphic_console_close(QemuConsole *con)
int height = qemu_console_get_height(con, 480);
trace_console_gfx_close(con->index);
+ qemu_console_notify(QEMU_CONSOLE_REMOVED, con);
object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
qemu_graphic_console_set_hwops(con, &unused_ops, NULL);
timer_del(con->ui_timer);
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 23/32] ui/console-vc: fire ADDED/REMOVED notifications
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (21 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 22/32] ui/console: fire console ADDED/REMOVED notifications Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 24/32] ui/gtk: convert VirtualConsole storage from fixed array to GPtrArray Marc-André Lureau
` (8 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Fire CONSOLE_ADDED when the chardev is opened.
Fire CONSOLE_REMOVED in char_vc_finalize() before dropping the console
reference, so the console is still in a valid state when listeners
handle the event. Also fixes a console object leak by adding the
missing object_unref().
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/console-vc.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/ui/console-vc.c b/ui/console-vc.c
index 828e78c41ea..53d9e9d39b3 100644
--- a/ui/console-vc.c
+++ b/ui/console-vc.c
@@ -255,6 +255,7 @@ static bool vc_chr_open(Chardev *chr, ChardevBackend *backend, Error **errp)
}
qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+ qemu_console_notify(QEMU_CONSOLE_ADDED, QEMU_CONSOLE(s));
return true;
}
@@ -327,12 +328,24 @@ static void char_vc_init(Object *obj)
vc->encoding = CHARDEV_VC_ENCODING_UTF8;
}
+static void char_vc_finalize(Object *obj)
+{
+ VCChardev *vc = VC_CHARDEV(obj);
+ QemuConsole *con = QEMU_CONSOLE(vc->console);
+
+ if (con) {
+ qemu_console_notify(QEMU_CONSOLE_REMOVED, con);
+ object_unref(con);
+ }
+}
+
static const TypeInfo char_vc_type_info = {
.name = TYPE_CHARDEV_VC,
.parent = TYPE_CHARDEV,
.instance_size = sizeof(VCChardev),
.instance_init = char_vc_init,
.instance_post_init = object_apply_compat_props,
+ .instance_finalize = char_vc_finalize,
.class_init = char_vc_class_init,
};
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 24/32] ui/gtk: convert VirtualConsole storage from fixed array to GPtrArray
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (22 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 23/32] ui/console-vc: fire " Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 12:21 ` Akihiko Odaki
2026-05-29 11:16 ` [PATCH 25/32] ui/gtk: move global display settings out of per-console init Marc-André Lureau
` (7 subsequent siblings)
31 siblings, 1 reply; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Replace the fixed-size vc[MAX_VCS] with GPtrArray.
This is a preparatory refactoring for console hotplug support, which
needs to add/remove VCs dynamically.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/gtk.h | 3 +-
ui/gtk.c | 102 +++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 81 insertions(+), 24 deletions(-)
diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 5156a049509..4d54de97ea7 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -118,8 +118,7 @@ struct GtkDisplayState {
GtkWidget *grab_item;
GtkWidget *grab_on_hover_item;
- int nb_vcs;
- VirtualConsole vc[MAX_VCS];
+ GPtrArray *vcs;
GtkWidget *show_tabs_item;
GtkWidget *untabify_item;
diff --git a/ui/gtk.c b/ui/gtk.c
index c26dc83ac3e..141cb69d494 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -150,8 +150,8 @@ static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s)
VirtualConsole *vc;
gint i;
- for (i = 0; i < s->nb_vcs; i++) {
- vc = &s->vc[i];
+ for (i = 0; i < s->vcs->len; i++) {
+ vc = g_ptr_array_index(s->vcs, i);
if (gtk_check_menu_item_get_active
(GTK_CHECK_MENU_ITEM(vc->menu_item))) {
return vc;
@@ -165,8 +165,11 @@ static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page)
VirtualConsole *vc;
gint i, p;
- for (i = 0; i < s->nb_vcs; i++) {
- vc = &s->vc[i];
+ if (!s->vcs) {
+ return NULL;
+ }
+ for (i = 0; i < s->vcs->len; i++) {
+ vc = g_ptr_array_index(s->vcs, i);
p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item);
if (p == page) {
return vc;
@@ -247,8 +250,8 @@ static void gd_update_caption(GtkDisplayState *s)
gtk_window_set_title(GTK_WINDOW(s->window), title);
g_free(title);
- for (i = 0; i < s->nb_vcs; i++) {
- VirtualConsole *vc = &s->vc[i];
+ for (i = 0; i < s->vcs->len; i++) {
+ VirtualConsole *vc = g_ptr_array_index(s->vcs, i);
if (!vc->window) {
continue;
@@ -357,7 +360,7 @@ static void gtk_release_modifiers(GtkDisplayState *s)
{
VirtualConsole *vc = gd_vc_find_current(s);
- if (vc->type != GD_VC_GFX ||
+ if (!vc || vc->type != GD_VC_GFX ||
!qemu_console_is_graphic(vc->gfx.dcl.con)) {
return;
}
@@ -702,8 +705,8 @@ static void gd_mouse_mode_change(Notifier *notify, void *data)
gd_ungrab_pointer(s);
}
}
- for (i = 0; i < s->nb_vcs; i++) {
- VirtualConsole *vc = &s->vc[i];
+ for (i = 0; i < s->vcs->len; i++) {
+ VirtualConsole *vc = g_ptr_array_index(s->vcs, i);
gd_update_cursor(vc);
}
}
@@ -2114,9 +2117,10 @@ static void gd_vcs_init(GtkDisplayState *s, GSList *group,
int i;
for (i = 0; i < nb_vcs; i++) {
- VirtualConsole *vc = &s->vc[s->nb_vcs];
- group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
- s->nb_vcs++;
+ VirtualConsole *vc = g_new0(VirtualConsole, 1);
+ g_ptr_array_add(s->vcs, vc);
+ group = gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1,
+ group, view_menu);
}
}
#endif /* CONFIG_VTE */
@@ -2441,13 +2445,14 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
/* gfx */
for (vc = 0;; vc++) {
+ VirtualConsole *v;
con = qemu_console_lookup_by_index(vc);
if (!con) {
break;
}
- group = gd_vc_gfx_init(s, &s->vc[vc], con,
- vc, group, view_menu);
- s->nb_vcs++;
+ v = g_new0(VirtualConsole, 1);
+ g_ptr_array_add(s->vcs, v);
+ group = gd_vc_gfx_init(s, v, con, vc, group, view_menu);
}
#if defined(CONFIG_VTE)
@@ -2505,6 +2510,58 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
}
+static void gd_vc_free(void *p)
+{
+ VirtualConsole *vc = p;
+
+ switch (vc->type) {
+ case GD_VC_GFX:
+ qemu_console_unregister_listener(&vc->gfx.dcl);
+#if defined(CONFIG_OPENGL)
+ if (display_opengl) {
+ qemu_console_set_display_gl_ctx(vc->gfx.dcl.con, NULL);
+ }
+ if (vc->gfx.esurface) {
+ eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
+ }
+ if (vc->gfx.ectx) {
+ eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
+ }
+ if (vc->gfx.gls) {
+ surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
+ qemu_gl_fini_shader(vc->gfx.gls);
+ }
+ egl_fb_destroy(&vc->gfx.guest_fb);
+ egl_fb_destroy(&vc->gfx.win_fb);
+ egl_fb_destroy(&vc->gfx.cursor_fb);
+#endif
+ qkbd_state_free(vc->gfx.kbd);
+ if (vc->gfx.surface) {
+ cairo_surface_destroy(vc->gfx.surface);
+ }
+ if (vc->gfx.convert) {
+ pixman_image_unref(vc->gfx.convert);
+ }
+ break;
+#if defined(CONFIG_VTE)
+ case GD_VC_VTE:
+ fifo8_destroy(&vc->vte.out_fifo);
+ break;
+#endif
+ }
+
+ if (vc->window) {
+ gtk_widget_destroy(vc->window);
+ } else if (vc->tab_item) {
+ gtk_widget_destroy(vc->tab_item);
+ }
+ if (vc->menu_item) {
+ gtk_widget_destroy(vc->menu_item);
+ }
+ g_free(vc->label);
+ g_free(vc);
+}
+
static GtkDisplayState *gtk_display_state;
static gboolean gtkinit;
@@ -2525,6 +2582,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->vcs = g_ptr_array_new_with_free_func(gd_vc_free);
s->opts = opts;
theme = gtk_icon_theme_get_default();
@@ -2582,13 +2640,10 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
gtk_widget_show_all(s->window);
- for (idx = 0;; idx++) {
- QemuConsole *con = qemu_console_lookup_by_index(idx);
- if (!con) {
- break;
- }
- if (s->vc[idx].type == GD_VC_GFX) {
- gtk_widget_realize(s->vc[idx].gfx.drawing_area);
+ for (idx = 0; idx < s->vcs->len; idx++) {
+ VirtualConsole *v = g_ptr_array_index(s->vcs, idx);
+ if (v->type == GD_VC_GFX) {
+ gtk_widget_realize(v->gfx.drawing_area);
}
}
@@ -2693,6 +2748,9 @@ static void gtk_display_cleanup(void)
qemu_remove_mouse_mode_change_notifier(&s->mouse_mode_notifier);
gd_clipboard_cleanup(s);
+ g_signal_handlers_disconnect_by_func(s->notebook,
+ G_CALLBACK(gd_change_page), s);
+ g_clear_pointer(&s->vcs, g_ptr_array_unref);
g_clear_pointer(&s->window, gtk_widget_destroy);
g_clear_object(&s->null_cursor);
g_clear_pointer(>k_display_state, g_free);
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 25/32] ui/gtk: move global display settings out of per-console init
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (23 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 24/32] ui/gtk: convert VirtualConsole storage from fixed array to GPtrArray Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 26/32] ui/gtk: fix tab re-insertion order on window close Marc-André Lureau
` (6 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Move zoom_to_fit, keep_aspect_ratio and touch_slots initialisation
from gd_vc_gfx_init() to gd_create_menu_view(). These are global
display settings that should be set once after all consoles are
created, not repeated on every per-console init (where the last
iteration's values silently win).
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/gtk.c | 46 +++++++++++++++++++++++-----------------------
1 file changed, 23 insertions(+), 23 deletions(-)
diff --git a/ui/gtk.c b/ui/gtk.c
index 141cb69d494..1b9523739f6 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -2269,8 +2269,6 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
GSList *group, GtkWidget *view_menu)
{
const DisplayChangeListenerOps *ops = &dcl_ops;
- bool zoom_to_fit = false;
- int i;
vc->label = qemu_console_get_label(con);
vc->s = s;
@@ -2350,26 +2348,6 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
gd_connect_vc_gfx_signals(vc);
group = gd_vc_menu_init(s, vc, idx, group, view_menu);
- if (qemu_console_ui_info_supported(vc->gfx.dcl.con)) {
- zoom_to_fit = true;
- }
- if (s->opts->u.gtk.has_zoom_to_fit) {
- zoom_to_fit = s->opts->u.gtk.zoom_to_fit;
- }
- if (zoom_to_fit) {
- gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item));
- s->free_scale = true;
- }
-
- s->keep_aspect_ratio = true;
- if (s->opts->u.gtk.has_keep_aspect_ratio)
- s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio;
-
- for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) {
- struct touch_slot *slot = &touch_slots[i];
- slot->tracking_id = -1;
- }
-
return group;
}
@@ -2379,7 +2357,8 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
GtkWidget *view_menu;
GtkWidget *separator;
QemuConsole *con;
- int vc;
+ bool zoom_to_fit = false;
+ int vc, i;
view_menu = gtk_menu_new();
gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
@@ -2453,6 +2432,27 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
v = g_new0(VirtualConsole, 1);
g_ptr_array_add(s->vcs, v);
group = gd_vc_gfx_init(s, v, con, vc, group, view_menu);
+ if (qemu_console_ui_info_supported(con)) {
+ zoom_to_fit = true;
+ }
+ }
+
+ if (s->opts->u.gtk.has_zoom_to_fit) {
+ zoom_to_fit = s->opts->u.gtk.zoom_to_fit;
+ }
+ if (zoom_to_fit) {
+ gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item));
+ s->free_scale = true;
+ }
+
+ s->keep_aspect_ratio = true;
+ if (s->opts->u.gtk.has_keep_aspect_ratio) {
+ s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio;
+ }
+
+ for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) {
+ struct touch_slot *slot = &touch_slots[i];
+ slot->tracking_id = -1;
}
#if defined(CONFIG_VTE)
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 26/32] ui/gtk: fix tab re-insertion order on window close
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (24 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 25/32] ui/gtk: move global display settings out of per-console init Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 27/32] ui/gtk: centralize console menu and shortcut management Marc-André Lureau
` (5 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Add gd_vc_notebook_pos() which computes the correct notebook position
for a console by counting only non-detached (non-windowed) tabs before
it. Use it in gd_tab_window_close() so a re-attached tab is inserted
at its logical position rather than appended at the end.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/gtk.c | 27 ++++++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/ui/gtk.c b/ui/gtk.c
index 1b9523739f6..c4d05757c75 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -1464,16 +1464,37 @@ static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
gd_update_windowsize(vc);
}
+static int gd_vc_notebook_pos(GtkDisplayState *s, VirtualConsole *target)
+{
+ int pos = 0;
+ guint i;
+
+ for (i = 0; i < s->vcs->len; i++) {
+ VirtualConsole *vc = g_ptr_array_index(s->vcs, i);
+ if (vc == target) {
+ return pos;
+ }
+ if (!vc->window) {
+ pos++;
+ }
+ }
+ g_assert_not_reached();
+}
+
static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
void *opaque)
{
VirtualConsole *vc = opaque;
GtkDisplayState *s = vc->s;
+ int page;
gtk_widget_set_sensitive(vc->menu_item, true);
- gd_widget_reparent(vc->window, s->notebook, vc->tab_item);
- gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook),
- vc->tab_item, vc->label);
+ g_object_ref(vc->tab_item);
+ gtk_container_remove(GTK_CONTAINER(vc->window), vc->tab_item);
+ page = gd_vc_notebook_pos(s, vc);
+ gtk_notebook_insert_page(GTK_NOTEBOOK(s->notebook),
+ vc->tab_item, gtk_label_new(vc->label), page);
+ g_object_unref(vc->tab_item);
gtk_widget_destroy(vc->window);
vc->window = NULL;
#if defined(CONFIG_OPENGL)
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 27/32] ui/gtk: centralize console menu and shortcut management
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (25 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 26/32] ui/gtk: fix tab re-insertion order on window close Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 28/32] ui/gtk: handle console hotplug/unplug events Marc-André Lureau
` (4 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Replace the per-console gd_vc_menu_init() with gd_rebuild_vc_menu()
that tears down and rebuilds all console radio menu items and
Ctrl+Alt+N accelerators at once. This is called from initialization
and whenever consoles are detached or reattached.
Shortcuts now skip detached (windowed) consoles, so they always map
to reachable tabs. Rename gd_vc_gfx_init() to add_gfx_console()
and simplify the init function signatures now that menu creation is
decoupled.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/gtk.h | 1 +
ui/gtk.c | 127 ++++++++++++++++++++++++++++++++++++-------------------
2 files changed, 85 insertions(+), 43 deletions(-)
diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 4d54de97ea7..b0be5070795 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -120,6 +120,7 @@ struct GtkDisplayState {
GPtrArray *vcs;
+ GtkWidget *vc_menu_separator;
GtkWidget *show_tabs_item;
GtkWidget *untabify_item;
GtkWidget *show_menubar_item;
diff --git a/ui/gtk.c b/ui/gtk.c
index c4d05757c75..621bd269b3a 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -142,6 +142,7 @@ static void gd_grab_pointer(VirtualConsole *vc, const char *reason);
static void gd_ungrab_pointer(GtkDisplayState *s);
static void gd_grab_keyboard(VirtualConsole *vc, const char *reason);
static void gd_ungrab_keyboard(GtkDisplayState *s);
+static void gd_rebuild_vc_menu(GtkDisplayState *s);
/** Utility Functions **/
@@ -1488,7 +1489,6 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
GtkDisplayState *s = vc->s;
int page;
- gtk_widget_set_sensitive(vc->menu_item, true);
g_object_ref(vc->tab_item);
gtk_container_remove(GTK_CONTAINER(vc->window), vc->tab_item);
page = gd_vc_notebook_pos(s, vc);
@@ -1508,6 +1508,8 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
}
#endif
+ gd_rebuild_vc_menu(s);
+
if (vc == gd_vc_find_by_menu(s)) {
gtk_widget_grab_focus(vc->focus);
}
@@ -1539,7 +1541,6 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
FALSE);
}
if (!vc->window) {
- gtk_widget_set_sensitive(vc->menu_item, false);
vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
#if defined(CONFIG_OPENGL)
if (vc->gfx.esurface) {
@@ -1566,6 +1567,7 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
}
+ gd_rebuild_vc_menu(s);
gd_update_geometry_hints(vc);
gd_update_caption(s);
}
@@ -1906,22 +1908,73 @@ static gboolean gd_configure(GtkWidget *widget,
/** Virtual Console Callbacks **/
-static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
- int idx, GSList *group, GtkWidget *view_menu)
+static void gd_rebuild_vc_menu(GtkDisplayState *s)
{
- vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
- gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx,
- HOTKEY_MODIFIERS, 0,
- g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL));
- gtk_accel_label_set_accel(
- GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
- GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
+ GSList *group = NULL;
+ VirtualConsole *vc;
+ GList *children;
+ gint insert_pos;
+ int shortcut_idx = 0;
+ guint i;
+
+ for (i = 0; i < s->vcs->len; i++) {
+ vc = g_ptr_array_index(s->vcs, i);
+ if (vc->menu_item) {
+ gtk_widget_destroy(vc->menu_item);
+ vc->menu_item = NULL;
+ }
+ }
+
+ for (i = 0; i < 9; i++) {
+ gtk_accel_group_disconnect_key(s->accel_group,
+ GDK_KEY_1 + i, HOTKEY_MODIFIERS);
+ }
+
+ /* find insertion position (just before vc_menu_separator) */
+ children = gtk_container_get_children(GTK_CONTAINER(s->view_menu));
+ insert_pos = g_list_index(children, s->vc_menu_separator);
+ g_list_free(children);
+
+ /* create new menu items for each console */
+ for (i = 0; i < s->vcs->len; i++) {
+ vc = g_ptr_array_index(s->vcs, i);
- g_signal_connect(vc->menu_item, "activate",
- G_CALLBACK(gd_menu_switch_vc), s);
- gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);
+ vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group,
+ vc->label);
+ group = gtk_radio_menu_item_get_group(
+ GTK_RADIO_MENU_ITEM(vc->menu_item));
+
+ if (vc->window) {
+ gtk_widget_set_sensitive(vc->menu_item, false);
+ } else if (shortcut_idx < 9) {
+ guint key = GDK_KEY_1 + shortcut_idx;
+ gtk_accel_group_connect(s->accel_group, key,
+ HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc),
+ vc, NULL));
+ gtk_accel_label_set_accel(
+ GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
+ key, HOTKEY_MODIFIERS);
+ shortcut_idx++;
+ }
- return gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
+ g_signal_connect(vc->menu_item, "activate",
+ G_CALLBACK(gd_menu_switch_vc), s);
+ gtk_menu_shell_insert(GTK_MENU_SHELL(s->view_menu),
+ vc->menu_item, insert_pos + i);
+ gtk_widget_show(vc->menu_item);
+ }
+
+ /* sync active menu item with current notebook page */
+ vc = gd_vc_find_current(s);
+ if (vc && vc->menu_item) {
+ g_signal_handlers_block_by_func(vc->menu_item,
+ gd_menu_switch_vc, s);
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
+ g_signal_handlers_unblock_by_func(vc->menu_item,
+ gd_menu_switch_vc, s);
+ }
}
#if defined(CONFIG_VTE)
@@ -2064,9 +2117,8 @@ static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
return TRUE;
}
-static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
- Chardev *chr, int idx,
- GSList *group, GtkWidget *view_menu)
+static void gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
+ Chardev *chr, int idx)
{
char buffer[32];
GtkWidget *box;
@@ -2082,7 +2134,6 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
snprintf(buffer, sizeof(buffer), "vc%d", idx);
vc->label = g_strdup(vc->vte.chr->label ? : buffer);
- group = gd_vc_menu_init(s, vc, idx, group, view_menu);
vc->vte.terminal = vte_terminal_new();
g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc);
@@ -2128,20 +2179,16 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
gtk_label_new(vc->label));
qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED);
-
- return group;
}
-static void gd_vcs_init(GtkDisplayState *s, GSList *group,
- GtkWidget *view_menu)
+static void gd_vcs_init(GtkDisplayState *s)
{
int i;
for (i = 0; i < nb_vcs; i++) {
VirtualConsole *vc = g_new0(VirtualConsole, 1);
g_ptr_array_add(s->vcs, vc);
- group = gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1,
- group, view_menu);
+ gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1);
}
}
#endif /* CONFIG_VTE */
@@ -2285,12 +2332,12 @@ static bool gd_scale_valid(double scale)
return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX;
}
-static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
- QemuConsole *con, int idx,
- GSList *group, GtkWidget *view_menu)
+static void add_gfx_console(GtkDisplayState *s, QemuConsole *con)
{
+ VirtualConsole *vc = g_new0(VirtualConsole, 1);
const DisplayChangeListenerOps *ops = &dcl_ops;
+ g_ptr_array_add(s->vcs, vc);
vc->label = qemu_console_get_label(con);
vc->s = s;
vc->gfx.preferred_scale = 1.0;
@@ -2367,14 +2414,10 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
qemu_console_register_listener(con, &vc->gfx.dcl, ops);
gd_connect_vc_gfx_signals(vc);
- group = gd_vc_menu_init(s, vc, idx, group, view_menu);
-
- return group;
}
-static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
+static void gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
{
- GSList *group = NULL;
GtkWidget *view_menu;
GtkWidget *separator;
QemuConsole *con;
@@ -2382,6 +2425,7 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
int vc, i;
view_menu = gtk_menu_new();
+ s->view_menu = view_menu;
gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
@@ -2445,14 +2489,11 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
/* gfx */
for (vc = 0;; vc++) {
- VirtualConsole *v;
con = qemu_console_lookup_by_index(vc);
if (!con) {
break;
}
- v = g_new0(VirtualConsole, 1);
- g_ptr_array_add(s->vcs, v);
- group = gd_vc_gfx_init(s, v, con, vc, group, view_menu);
+ add_gfx_console(s, con);
if (qemu_console_ui_info_supported(con)) {
zoom_to_fit = true;
}
@@ -2478,11 +2519,13 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
#if defined(CONFIG_VTE)
/* vte */
- gd_vcs_init(s, group, view_menu);
+ gd_vcs_init(s);
#endif
- separator = gtk_separator_menu_item_new();
- gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
+ s->vc_menu_separator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->vc_menu_separator);
+
+ gd_rebuild_vc_menu(s);
s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item);
@@ -2501,8 +2544,6 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->show_menubar_item))),
GDK_KEY_m, HOTKEY_MODIFIERS);
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_menubar_item);
-
- return view_menu;
}
static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
@@ -2511,7 +2552,7 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
s->accel_group = gtk_accel_group_new();
s->machine_menu = gd_create_menu_machine(s);
- s->view_menu = gd_create_menu_view(s, opts);
+ gd_create_menu_view(s, opts);
s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 28/32] ui/gtk: handle console hotplug/unplug events
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (26 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 27/32] ui/gtk: centralize console menu and shortcut management Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 29/32] ui/console: register console in QOM tree dynamically Marc-André Lureau
` (3 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
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.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
include/ui/gtk.h | 1 +
ui/gtk.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 77 insertions(+), 1 deletion(-)
diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index b0be5070795..6cb611ce474 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -140,6 +140,7 @@ struct GtkDisplayState {
GdkCursor *null_cursor;
Notifier mouse_mode_notifier;
+ Notifier console_notifier;
gboolean free_scale;
gboolean keep_aspect_ratio;
diff --git a/ui/gtk.c b/ui/gtk.c
index 621bd269b3a..484dd9159c2 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -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,76 @@ 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);
+
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), true);
+}
+
+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)
@@ -2688,6 +2759,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);
@@ -2808,6 +2882,7 @@ static void gtk_display_cleanup(void)
return;
}
+ 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,
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 29/32] ui/console: register console in QOM tree dynamically
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (27 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 28/32] ui/gtk: handle console hotplug/unplug events Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 30/32] ui/console: unregister console from QOM tree on close Marc-André Lureau
` (2 subsequent siblings)
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Consoles created after init_displaystate() (e.g. hotplugged
display devices) were never added to the /backend/console[N]
QOM tree. Extract qemu_console_add_to_qom() and call it from
qemu_console_register() when the display is already initialized.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/console.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/ui/console.c b/ui/console.c
index 975eaf15706..e01d893df4b 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -376,6 +376,13 @@ void qemu_text_console_put_string(QemuTextConsole *s, const char *str, int len)
}
}
+static void qemu_console_add_to_qom(QemuConsole *con)
+{
+ g_autofree gchar *name = g_strdup_printf("console[%d]", con->index);
+ object_property_add_child(object_get_container("backend"),
+ name, OBJECT(con));
+}
+
static void
qemu_console_register(QemuConsole *c)
{
@@ -413,6 +420,10 @@ qemu_console_register(QemuConsole *c)
}
}
}
+
+ if (phase_check(PHASE_MACHINE_READY)) {
+ qemu_console_add_to_qom(c);
+ }
}
static void
@@ -1089,17 +1100,13 @@ void qemu_console_remove_notifier(Notifier *notifier)
*/
DisplayState *init_displaystate(void)
{
- gchar *name;
QemuConsole *con;
QTAILQ_FOREACH(con, &consoles, next) {
/* Hook up into the qom tree here (not in object_new()), once
* all QemuConsoles are created and the order / numbering
* doesn't change any more */
- name = g_strdup_printf("console[%d]", con->index);
- object_property_add_child(object_get_container("backend"),
- name, OBJECT(con));
- g_free(name);
+ qemu_console_add_to_qom(con);
}
return display_state;
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 30/32] ui/console: unregister console from QOM tree on close
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (28 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 29/32] ui/console: register console in QOM tree dynamically Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 31/32] ui/dbus: handle console hotplug/unplug events Marc-André Lureau
2026-05-29 11:16 ` [PATCH 32/32] tests/qtest: add D-Bus display hotplug test Marc-André Lureau
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Call object_unparent() in graphic_console_close() to remove the
console from /backend/console[N]. This drops the QOM tree
reference, while the initial object_new() reference keeps the
console alive for potential reuse.
When graphic_console_init() reuses a closed console, re-register
it in the QOM tree.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/console.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/ui/console.c b/ui/console.c
index e01d893df4b..4d10884dc11 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1136,6 +1136,9 @@ QemuConsole *qemu_graphic_console_create(DeviceState *dev, uint32_t head,
trace_console_gfx_reuse(s->index);
width = qemu_console_get_width(s, 0);
height = qemu_console_get_height(s, 0);
+ if (phase_check(PHASE_MACHINE_READY)) {
+ qemu_console_add_to_qom(s);
+ }
} else {
trace_console_gfx_new();
s = (QemuConsole *)object_new(TYPE_QEMU_GRAPHIC_CONSOLE);
@@ -1176,6 +1179,7 @@ void qemu_graphic_console_close(QemuConsole *con)
}
surface = qemu_create_placeholder_surface(width, height, unplugged);
qemu_console_set_surface(con, surface);
+ object_unparent(OBJECT(con));
}
QemuConsole *qemu_console_lookup_default(void)
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 31/32] ui/dbus: handle console hotplug/unplug events
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (29 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 30/32] ui/console: unregister console from QOM tree on close Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 32/32] tests/qtest: add D-Bus display hotplug test Marc-André Lureau
31 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Subscribe to QemuConsoleEvent notifications to dynamically add and
remove D-Bus display consoles. This mirrors the GTK backend's
handling added in the previous commits.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
ui/dbus.h | 3 ++
ui/dbus-console.c | 5 +++
ui/dbus.c | 93 +++++++++++++++++++++++++++++++++++++++++++++----------
3 files changed, 84 insertions(+), 17 deletions(-)
diff --git a/ui/dbus.h b/ui/dbus.h
index e4e78590b49..d2cc176648a 100644
--- a/ui/dbus.h
+++ b/ui/dbus.h
@@ -60,6 +60,7 @@ struct DBusDisplay {
DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT];
Notifier notifier;
+ Notifier console_notifier;
};
#ifdef WIN32
@@ -86,6 +87,8 @@ dbus_display_console_new(DBusDisplay *display, QemuConsole *con);
int
dbus_display_console_get_index(DBusDisplayConsole *ddc);
+QemuConsole *
+dbus_display_console_get_qemu_console(DBusDisplayConsole *ddc);
extern const DisplayChangeListenerOps dbus_console_dcl_ops;
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
index 0048951a7ab..dd1a18c7460 100644
--- a/ui/dbus-console.c
+++ b/ui/dbus-console.c
@@ -531,6 +531,11 @@ int dbus_display_console_get_index(DBusDisplayConsole *ddc)
return qemu_console_get_index(ddc->dcl.con);
}
+QemuConsole *dbus_display_console_get_qemu_console(DBusDisplayConsole *ddc)
+{
+ return ddc->dcl.con;
+}
+
DBusDisplayConsole *
dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
{
diff --git a/ui/dbus.c b/ui/dbus.c
index b23cb44c535..7be0f8e2611 100644
--- a/ui/dbus.c
+++ b/ui/dbus.c
@@ -142,6 +142,9 @@ dbus_display_finalize(Object *o)
{
DBusDisplay *dd = DBUS_DISPLAY(o);
+ if (dd->console_notifier.notify) {
+ qemu_console_remove_notifier(&dd->console_notifier);
+ }
if (dd->notifier.notify) {
dbus_display_notifier_remove(&dd->notifier);
}
@@ -164,14 +167,35 @@ dbus_display_finalize(Object *o)
dbus_display = NULL;
}
+static void
+dbus_update_console_ids(DBusDisplay *dd)
+{
+ g_autoptr(GArray) arr = g_array_new(FALSE, FALSE, sizeof(guint32));
+
+ for (guint i = 0; i < dd->consoles->len; i++) {
+ DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
+ guint32 idx = dbus_display_console_get_index(ddc);
+ g_array_append_val(arr, idx);
+ }
+
+ g_object_set(dd->iface, "console-ids",
+ g_variant_new_fixed_array(G_VARIANT_TYPE("u"),
+ arr->data, arr->len,
+ sizeof(guint32)),
+ NULL);
+}
+
static bool
-dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
+dbus_display_add_console(DBusDisplay *dd, QemuConsole *con, Error **errp)
{
- QemuConsole *con;
DBusDisplayConsole *dbus_console;
- con = qemu_console_lookup_by_index(idx);
- assert(con);
+ for (guint i = 0; i < dd->consoles->len; i++) {
+ DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
+ if (dbus_display_console_get_qemu_console(ddc) == con) {
+ return true;
+ }
+ }
if (qemu_console_is_graphic(con) &&
dd->gl_mode != DISPLAY_GL_MODE_OFF) {
@@ -179,20 +203,58 @@ dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
}
dbus_console = dbus_display_console_new(dd, con);
- g_ptr_array_insert(dd->consoles, idx, dbus_console);
+ g_ptr_array_add(dd->consoles, dbus_console);
g_dbus_object_manager_server_export(dd->server,
G_DBUS_OBJECT_SKELETON(dbus_console));
+ dbus_update_console_ids(dd);
return true;
}
+static void
+dbus_display_remove_console(DBusDisplay *dd, QemuConsole *con)
+{
+ for (guint i = 0; i < dd->consoles->len; i++) {
+ DBusDisplayConsole *ddc = g_ptr_array_index(dd->consoles, i);
+ if (dbus_display_console_get_qemu_console(ddc) == con) {
+ if (display_opengl) {
+ qemu_console_set_display_gl_ctx(con, NULL);
+ }
+ g_dbus_object_manager_server_unexport(
+ dd->server,
+ g_dbus_object_get_object_path(G_DBUS_OBJECT(ddc)));
+ g_ptr_array_remove_index(dd->consoles, i);
+ dbus_update_console_ids(dd);
+ break;
+ }
+ }
+}
+
+static void
+dbus_console_notify(Notifier *n, void *data)
+{
+ DBusDisplay *dd = container_of(n, DBusDisplay, console_notifier);
+ QemuConsoleEvent *event = data;
+
+ switch (event->type) {
+ case QEMU_CONSOLE_ADDED: {
+ Error *err = NULL;
+ if (!dbus_display_add_console(dd, event->con, &err)) {
+ error_report_err(err);
+ }
+ break;
+ }
+ case QEMU_CONSOLE_REMOVED:
+ dbus_display_remove_console(dd, event->con);
+ break;
+ }
+}
+
static void
dbus_display_complete(UserCreatable *uc, Error **errp)
{
DBusDisplay *dd = DBUS_DISPLAY(uc);
g_autoptr(GError) err = NULL;
g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid);
- g_autoptr(GArray) consoles = NULL;
- GVariant *console_ids;
int idx;
if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) {
@@ -233,27 +295,24 @@ dbus_display_complete(UserCreatable *uc, Error **errp)
}
}
- consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
for (idx = 0;; idx++) {
- if (!qemu_console_lookup_by_index(idx)) {
+ QemuConsole *con = qemu_console_lookup_by_index(idx);
+ if (!con) {
break;
}
- if (!dbus_display_add_console(dd, idx, errp)) {
+ if (!dbus_display_add_console(dd, con, errp)) {
return;
}
- g_array_append_val(consoles, idx);
}
- console_ids = g_variant_new_from_data(
- G_VARIANT_TYPE("au"),
- consoles->data, consoles->len * sizeof(guint32), TRUE,
- (GDestroyNotify)g_array_unref, consoles);
- g_steal_pointer(&consoles);
g_object_set(dd->iface,
"name", qemu_name ?: "QEMU " QEMU_VERSION,
"uuid", uuid,
- "console-ids", console_ids,
NULL);
+ dbus_update_console_ids(dd);
+
+ dd->console_notifier.notify = dbus_console_notify;
+ qemu_console_add_notifier(&dd->console_notifier);
if (dd->bus) {
g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH 32/32] tests/qtest: add D-Bus display hotplug test
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
` (30 preceding siblings ...)
2026-05-29 11:16 ` [PATCH 31/32] ui/dbus: handle console hotplug/unplug events Marc-André Lureau
@ 2026-05-29 11:16 ` Marc-André Lureau
2026-05-29 13:34 ` Fabiano Rosas
31 siblings, 1 reply; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-29 11:16 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell, Akihiko Odaki, Marc-André Lureau
Add a qtest that verifies display consoles are dynamically added and
removed over D-Bus when a bochs-display device is hotplugged and
unplugged on a q35 machine.
The test plugs device_add a bochs-display, waits for the DEVICE_ADDED
QMP event, and checks that the D-Bus VM interface reports a second
console. It then device_del it, forces a system reset
(q35 removal is ACPI-based and needs guest cooperation qtest cannot
provide), waits for DEVICE_DELETED, and checks the console count again.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
tests/qtest/dbus-display-test.c | 101 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 99 insertions(+), 2 deletions(-)
diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c
index 5773776cad5..7838ce7323f 100644
--- a/tests/qtest/dbus-display-test.c
+++ b/tests/qtest/dbus-display-test.c
@@ -7,6 +7,8 @@
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "libqtest.h"
+#include "qobject/qdict.h"
+#include "qobject/qstring.h"
#include "ui/dbus-display1.h"
static GDBusConnection*
@@ -38,11 +40,11 @@ test_dbus_p2p_from_fd(int fd)
}
static void
-test_setup(QTestState **qts, GDBusConnection **conn)
+test_setup_args(QTestState **qts, GDBusConnection **conn, const char *args)
{
int pair[2];
- *qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
+ *qts = qtest_init(args);
g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
@@ -52,6 +54,12 @@ test_setup(QTestState **qts, GDBusConnection **conn)
g_dbus_connection_start_message_processing(*conn);
}
+static void
+test_setup(QTestState **qts, GDBusConnection **conn)
+{
+ test_setup_args(qts, conn, "-display dbus,p2p=yes -name dbus-test");
+}
+
static void
test_dbus_display_vm(void)
{
@@ -360,6 +368,92 @@ test_dbus_display_keyboard(void)
qtest_quit(qts);
}
+static gsize
+get_console_ids_count(GDBusConnection *conn)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
+ GVariant *console_ids;
+ gsize n_ids = 0;
+
+ vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
+ qemu_dbus_display1_vm_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ DBUS_DISPLAY1_ROOT "/VM",
+ NULL,
+ &err));
+ g_assert_no_error(err);
+
+ console_ids = qemu_dbus_display1_vm_get_console_ids(
+ QEMU_DBUS_DISPLAY1_VM(vm));
+ if (console_ids) {
+ n_ids = g_variant_n_children(console_ids);
+ }
+ return n_ids;
+}
+
+static void
+wait_device_event(QTestState *qts, const char *event_name, const char *id)
+{
+ QDict *resp, *data;
+ QString *qstr;
+
+ for (;;) {
+ resp = qtest_qmp_eventwait_ref(qts, event_name);
+ data = qdict_get_qdict(resp, "data");
+ if (!data || !qdict_get(data, "device")) {
+ qobject_unref(resp);
+ continue;
+ }
+ qstr = qobject_to(QString, qdict_get(data, "device"));
+ if (!strcmp(qstring_get_str(qstr), id)) {
+ qobject_unref(resp);
+ break;
+ }
+ qobject_unref(resp);
+ }
+}
+
+static void
+test_dbus_display_hotplug(void)
+{
+ g_autoptr(GDBusConnection) conn = NULL;
+ QTestState *qts = NULL;
+ gsize n;
+
+ test_setup_args(&qts, &conn,
+ "-machine q35"
+ " -device pcie-root-port,id=rp0"
+ " -display dbus,p2p=yes"
+ " -name dbus-test");
+
+ n = get_console_ids_count(conn);
+ g_assert_cmpuint(n, ==, 1);
+
+ qtest_qmp_device_add(qts, "bochs-display", "bochs0",
+ "{'bus': 'rp0'}");
+
+ n = get_console_ids_count(conn);
+ g_assert_cmpuint(n, ==, 2);
+
+ /*
+ * On q35, PCI device removal is ACPI-based and requires guest
+ * acknowledgement. Since qtest has no guest OS, issue the delete
+ * request and force removal via system reset.
+ */
+ qtest_qmp_device_del_send(qts, "bochs0");
+ qtest_system_reset_nowait(qts);
+ wait_device_event(qts, "DEVICE_DELETED", "bochs0");
+
+ n = get_console_ids_count(conn);
+ g_assert_cmpuint(n, ==, 1);
+
+ g_clear_object(&conn);
+ qtest_quit(qts);
+}
+
int
main(int argc, char **argv)
{
@@ -369,6 +463,9 @@ main(int argc, char **argv)
qtest_add_data_func("/dbus-display/console", GINT_TO_POINTER(false), test_dbus_display_console);
qtest_add_data_func("/dbus-display/console/map", GINT_TO_POINTER(true), test_dbus_display_console);
qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
+ if (qtest_has_machine("q35") && qtest_has_device("bochs-display")) {
+ qtest_add_func("/dbus-display/hotplug", test_dbus_display_hotplug);
+ }
return g_test_run();
}
--
2.54.0
^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH 19/32] ui/dbus: implement display cleanup
2026-05-29 11:16 ` [PATCH 19/32] ui/dbus: " Marc-André Lureau
@ 2026-05-29 12:17 ` Akihiko Odaki
0 siblings, 0 replies; 39+ messages in thread
From: Akihiko Odaki @ 2026-05-29 12:17 UTC (permalink / raw)
To: Marc-André Lureau, qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell
On 2026/05/29 20:16, Marc-André Lureau wrote:
> Add dbus_cleanup() to unparent the D-Bus display object, ensuring
> proper teardown before user_creatable_cleanup() runs.
"[PATCH 07/32] ui/dbus: remove mouse handler on dispose" removes the
mouse handler, but the LED event handler still remains.
Regards,
Akihiko Odaki
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> ui/dbus-console.c | 3 +++
> ui/dbus.c | 13 +++++++++++++
> 2 files changed, 16 insertions(+)
>
> diff --git a/ui/dbus-console.c b/ui/dbus-console.c
> index 0813a08f85e..0048951a7ab 100644
> --- a/ui/dbus-console.c
> +++ b/ui/dbus-console.c
> @@ -151,6 +151,9 @@ dbus_display_console_dispose(GObject *object)
> DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
>
> qemu_console_unregister_listener(&ddc->dcl);
> + if (ddc->dcl.con) {
> + qemu_console_set_display_gl_ctx(ddc->dcl.con, NULL);
> + }
> qemu_remove_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
> g_clear_object(&ddc->iface_touch);
> g_clear_object(&ddc->iface_mouse);
> diff --git a/ui/dbus.c b/ui/dbus.c
> index e02a94df2f3..b23cb44c535 100644
> --- a/ui/dbus.c
> +++ b/ui/dbus.c
> @@ -615,10 +615,23 @@ static const TypeInfo dbus_display_info = {
> }
> };
>
> +static void
> +dbus_cleanup(void)
> +{
> + Object *o;
> +
> + o = object_resolve_path_component(object_get_objects_root(),
> + "dbus-display");
> + if (o) {
> + object_unparent(o);
> + }
> +}
> +
> static QemuDisplay qemu_display_dbus = {
> .type = DISPLAY_TYPE_DBUS,
> .early_init = early_dbus_init,
> .init = dbus_init,
> + .cleanup = dbus_cleanup,
> .vc = "vc",
> };
>
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 24/32] ui/gtk: convert VirtualConsole storage from fixed array to GPtrArray
2026-05-29 11:16 ` [PATCH 24/32] ui/gtk: convert VirtualConsole storage from fixed array to GPtrArray Marc-André Lureau
@ 2026-05-29 12:21 ` Akihiko Odaki
0 siblings, 0 replies; 39+ messages in thread
From: Akihiko Odaki @ 2026-05-29 12:21 UTC (permalink / raw)
To: Marc-André Lureau, qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Peter Maydell
On 2026/05/29 20:16, Marc-André Lureau wrote:
> Replace the fixed-size vc[MAX_VCS] with GPtrArray.
>
> This is a preparatory refactoring for console hotplug support, which
> needs to add/remove VCs dynamically.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> include/ui/gtk.h | 3 +-
> ui/gtk.c | 102 +++++++++++++++++++++++++++++++++++++++++++------------
> 2 files changed, 81 insertions(+), 24 deletions(-)
>
> diff --git a/include/ui/gtk.h b/include/ui/gtk.h
> index 5156a049509..4d54de97ea7 100644
> --- a/include/ui/gtk.h
> +++ b/include/ui/gtk.h
> @@ -118,8 +118,7 @@ struct GtkDisplayState {
> GtkWidget *grab_item;
> GtkWidget *grab_on_hover_item;
>
> - int nb_vcs;
> - VirtualConsole vc[MAX_VCS];
> + GPtrArray *vcs;
>
> GtkWidget *show_tabs_item;
> GtkWidget *untabify_item;
> diff --git a/ui/gtk.c b/ui/gtk.c
> index c26dc83ac3e..141cb69d494 100644
> --- a/ui/gtk.c
> +++ b/ui/gtk.c
> @@ -150,8 +150,8 @@ static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s)
> VirtualConsole *vc;
> gint i;
>
> - for (i = 0; i < s->nb_vcs; i++) {
> - vc = &s->vc[i];
> + for (i = 0; i < s->vcs->len; i++) {
> + vc = g_ptr_array_index(s->vcs, i);
> if (gtk_check_menu_item_get_active
> (GTK_CHECK_MENU_ITEM(vc->menu_item))) {
> return vc;
> @@ -165,8 +165,11 @@ static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page)
> VirtualConsole *vc;
> gint i, p;
>
> - for (i = 0; i < s->nb_vcs; i++) {
> - vc = &s->vc[i];
> + if (!s->vcs) {
> + return NULL;
> + }
> + for (i = 0; i < s->vcs->len; i++) {
> + vc = g_ptr_array_index(s->vcs, i);
> p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item);
> if (p == page) {
> return vc;
> @@ -247,8 +250,8 @@ static void gd_update_caption(GtkDisplayState *s)
> gtk_window_set_title(GTK_WINDOW(s->window), title);
> g_free(title);
>
> - for (i = 0; i < s->nb_vcs; i++) {
> - VirtualConsole *vc = &s->vc[i];
> + for (i = 0; i < s->vcs->len; i++) {
> + VirtualConsole *vc = g_ptr_array_index(s->vcs, i);
>
> if (!vc->window) {
> continue;
> @@ -357,7 +360,7 @@ static void gtk_release_modifiers(GtkDisplayState *s)
> {
> VirtualConsole *vc = gd_vc_find_current(s);
>
> - if (vc->type != GD_VC_GFX ||
> + if (!vc || vc->type != GD_VC_GFX ||
> !qemu_console_is_graphic(vc->gfx.dcl.con)) {
> return;
> }
> @@ -702,8 +705,8 @@ static void gd_mouse_mode_change(Notifier *notify, void *data)
> gd_ungrab_pointer(s);
> }
> }
> - for (i = 0; i < s->nb_vcs; i++) {
> - VirtualConsole *vc = &s->vc[i];
> + for (i = 0; i < s->vcs->len; i++) {
> + VirtualConsole *vc = g_ptr_array_index(s->vcs, i);
> gd_update_cursor(vc);
> }
> }
> @@ -2114,9 +2117,10 @@ static void gd_vcs_init(GtkDisplayState *s, GSList *group,
> int i;
>
> for (i = 0; i < nb_vcs; i++) {
> - VirtualConsole *vc = &s->vc[s->nb_vcs];
> - group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
> - s->nb_vcs++;
> + VirtualConsole *vc = g_new0(VirtualConsole, 1);
> + g_ptr_array_add(s->vcs, vc);
> + group = gd_vc_vte_init(s, vc, vcs[i], s->vcs->len - 1,
> + group, view_menu);
> }
> }
> #endif /* CONFIG_VTE */
> @@ -2441,13 +2445,14 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, DisplayOptions *opts)
>
> /* gfx */
> for (vc = 0;; vc++) {
> + VirtualConsole *v;
> con = qemu_console_lookup_by_index(vc);
> if (!con) {
> break;
> }
> - group = gd_vc_gfx_init(s, &s->vc[vc], con,
> - vc, group, view_menu);
> - s->nb_vcs++;
> + v = g_new0(VirtualConsole, 1);
> + g_ptr_array_add(s->vcs, v);
> + group = gd_vc_gfx_init(s, v, con, vc, group, view_menu);
> }
>
> #if defined(CONFIG_VTE)
> @@ -2505,6 +2510,58 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
> }
>
>
> +static void gd_vc_free(void *p)
> +{
> + VirtualConsole *vc = p;
> +
> + switch (vc->type) {
> + case GD_VC_GFX:
> + qemu_console_unregister_listener(&vc->gfx.dcl);
> +#if defined(CONFIG_OPENGL)
> + if (display_opengl) {
> + qemu_console_set_display_gl_ctx(vc->gfx.dcl.con, NULL);
> + }
> + if (vc->gfx.esurface) {
> + eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
> + }
> + if (vc->gfx.ectx) {
> + eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
> + }
> + if (vc->gfx.gls) {
> + surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
> + qemu_gl_fini_shader(vc->gfx.gls);
> + }
> + egl_fb_destroy(&vc->gfx.guest_fb);
> + egl_fb_destroy(&vc->gfx.win_fb);
> + egl_fb_destroy(&vc->gfx.cursor_fb);
> +#endif
> + qkbd_state_free(vc->gfx.kbd);
> + if (vc->gfx.surface) {
> + cairo_surface_destroy(vc->gfx.surface);
> + }
> + if (vc->gfx.convert) {
> + pixman_image_unref(vc->gfx.convert);
> + }
> + break;
> +#if defined(CONFIG_VTE)
> + case GD_VC_VTE:
> + fifo8_destroy(&vc->vte.out_fifo);
> + break;
> +#endif
GCC emits a warning if !defined(CONFIG_VTE) because this doesn't handle
GD_VC_VTE while the enum value is still present.
Regards,
Akihiko Odaki
> + }
> +
> + if (vc->window) {
> + gtk_widget_destroy(vc->window);
> + } else if (vc->tab_item) {
> + gtk_widget_destroy(vc->tab_item);
> + }
> + if (vc->menu_item) {
> + gtk_widget_destroy(vc->menu_item);
> + }
> + g_free(vc->label);
> + g_free(vc);
> +}
> +
> static GtkDisplayState *gtk_display_state;
> static gboolean gtkinit;
>
> @@ -2525,6 +2582,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->vcs = g_ptr_array_new_with_free_func(gd_vc_free);
> s->opts = opts;
>
> theme = gtk_icon_theme_get_default();
> @@ -2582,13 +2640,10 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
>
> gtk_widget_show_all(s->window);
>
> - for (idx = 0;; idx++) {
> - QemuConsole *con = qemu_console_lookup_by_index(idx);
> - if (!con) {
> - break;
> - }
> - if (s->vc[idx].type == GD_VC_GFX) {
> - gtk_widget_realize(s->vc[idx].gfx.drawing_area);
> + for (idx = 0; idx < s->vcs->len; idx++) {
> + VirtualConsole *v = g_ptr_array_index(s->vcs, idx);
> + if (v->type == GD_VC_GFX) {
> + gtk_widget_realize(v->gfx.drawing_area);
> }
> }
>
> @@ -2693,6 +2748,9 @@ static void gtk_display_cleanup(void)
>
> qemu_remove_mouse_mode_change_notifier(&s->mouse_mode_notifier);
> gd_clipboard_cleanup(s);
> + g_signal_handlers_disconnect_by_func(s->notebook,
> + G_CALLBACK(gd_change_page), s);
> + g_clear_pointer(&s->vcs, g_ptr_array_unref);
> g_clear_pointer(&s->window, gtk_widget_destroy);
> g_clear_object(&s->null_cursor);
> g_clear_pointer(>k_display_state, g_free);
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak
2026-05-29 11:16 ` [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak Marc-André Lureau
@ 2026-05-29 13:27 ` Fabiano Rosas
2026-05-29 13:44 ` Peter Maydell
1 sibling, 0 replies; 39+ messages in thread
From: Fabiano Rosas @ 2026-05-29 13:27 UTC (permalink / raw)
To: Marc-André Lureau, qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Laurent Vivier,
Alex Williamson, Cédric Le Goater, Peter Maydell,
Akihiko Odaki, Marc-André Lureau
Marc-André Lureau <marcandre.lureau@redhat.com> writes:
> qemu_irq_intercept_in() saves original IRQ handlers by allocating
> new QOM objects, which are never freed. On a PC machine, this leaks
> IRQ objects (one per IOAPIC pin) on every qtest run.
>
> Rather than tracking allocations to free later, avoid them: add an
> "observer" field to IRQState, called by qemu_set_irq() after the
> real handler. Interception sets the observer instead of rewriting
> handlers, so there's nothing to save and nothing to leak.
>
> Fix qemu_notirq() to route through qemu_set_irq() so inverted IRQs
> trigger observers too. Drop the LSan suppression.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> include/hw/core/irq.h | 1 +
> hw/core/irq.c | 10 +++++-----
> system/qtest.c | 3 ---
> scripts/lsan_suppressions.txt | 8 --------
> 4 files changed, 6 insertions(+), 16 deletions(-)
>
> diff --git a/include/hw/core/irq.h b/include/hw/core/irq.h
> index 291fdd67df4..93d5710a73e 100644
> --- a/include/hw/core/irq.h
> +++ b/include/hw/core/irq.h
> @@ -14,6 +14,7 @@ struct IRQState {
> qemu_irq_handler handler;
> void *opaque;
> int n;
> + qemu_irq_handler observer;
> };
>
> void qemu_set_irq(qemu_irq irq, int level);
> diff --git a/hw/core/irq.c b/hw/core/irq.c
> index 106805e2417..fa11e9bc0aa 100644
> --- a/hw/core/irq.c
> +++ b/hw/core/irq.c
> @@ -32,6 +32,9 @@ void qemu_set_irq(qemu_irq irq, int level)
> return;
>
> irq->handler(irq->opaque, irq->n, level);
> + if (unlikely(irq->observer)) {
> + irq->observer(irq->opaque, irq->n, level);
> + }
> }
>
> static void init_irq_fields(IRQState *irq, qemu_irq_handler handler,
> @@ -111,7 +114,7 @@ static void qemu_notirq(void *opaque, int line, int level)
> {
> IRQState *irq = opaque;
>
> - irq->handler(irq->opaque, irq->n, !level);
> + qemu_set_irq(irq, !level);
> }
>
> qemu_irq qemu_irq_invert(qemu_irq irq)
> @@ -124,11 +127,8 @@ qemu_irq qemu_irq_invert(qemu_irq irq)
> void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n)
> {
> int i;
> - qemu_irq *old_irqs = qemu_allocate_irqs(NULL, NULL, n);
> for (i = 0; i < n; i++) {
> - *old_irqs[i] = *gpio_in[i];
> - gpio_in[i]->handler = handler;
> - gpio_in[i]->opaque = &old_irqs[i];
> + gpio_in[i]->observer = handler;
> }
> }
>
> diff --git a/system/qtest.c b/system/qtest.c
> index a79d10d1361..b359a3e1c84 100644
> --- a/system/qtest.c
> +++ b/system/qtest.c
> @@ -324,9 +324,6 @@ void qtest_sendf(CharFrontend *chr, const char *fmt, ...)
>
> static void qtest_irq_handler(void *opaque, int n, int level)
> {
> - qemu_irq old_irq = *(qemu_irq *)opaque;
> - qemu_set_irq(old_irq, level);
> -
> if (irq_levels[n] != level) {
> CharFrontend *chr = &qtest->qtest_chr;
> irq_levels[n] = level;
> diff --git a/scripts/lsan_suppressions.txt b/scripts/lsan_suppressions.txt
> index f88bbab18b8..30256bc6d01 100644
> --- a/scripts/lsan_suppressions.txt
> +++ b/scripts/lsan_suppressions.txt
> @@ -16,11 +16,3 @@ leak:/lib64/libxkbcommon.so.0
> # https://github.com/GNOME/glib/blob/main/tools/glib.supp
> # This avoids false positive leak reports for the qga-ssh-test.
> leak:g_set_user_dirs
> -
> -# qemu_irq_intercept_in is only used by the qtest harness, and
> -# its API inherently involves a leak.
> -# While we could keep track of the old IRQ data structure
> -# in order to free it, it doesn't seem very important to fix
> -# since it is only used by the qtest test harness.
> -# Just ignore the leak, at least for the moment.
> -leak:qemu_irq_intercept_in
Reviewed-by: Fabiano Rosas <farosas@suse.de>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 32/32] tests/qtest: add D-Bus display hotplug test
2026-05-29 11:16 ` [PATCH 32/32] tests/qtest: add D-Bus display hotplug test Marc-André Lureau
@ 2026-05-29 13:34 ` Fabiano Rosas
0 siblings, 0 replies; 39+ messages in thread
From: Fabiano Rosas @ 2026-05-29 13:34 UTC (permalink / raw)
To: Marc-André Lureau, qemu-devel
Cc: Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Laurent Vivier,
Alex Williamson, Cédric Le Goater, Peter Maydell,
Akihiko Odaki, Marc-André Lureau
Marc-André Lureau <marcandre.lureau@redhat.com> writes:
> Add a qtest that verifies display consoles are dynamically added and
> removed over D-Bus when a bochs-display device is hotplugged and
> unplugged on a q35 machine.
>
> The test plugs device_add a bochs-display, waits for the DEVICE_ADDED
> QMP event, and checks that the D-Bus VM interface reports a second
> console. It then device_del it, forces a system reset
> (q35 removal is ACPI-based and needs guest cooperation qtest cannot
> provide), waits for DEVICE_DELETED, and checks the console count again.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> tests/qtest/dbus-display-test.c | 101 +++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 99 insertions(+), 2 deletions(-)
>
> diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c
> index 5773776cad5..7838ce7323f 100644
> --- a/tests/qtest/dbus-display-test.c
> +++ b/tests/qtest/dbus-display-test.c
> @@ -7,6 +7,8 @@
> #include <gio/gio.h>
> #include <gio/gunixfdlist.h>
> #include "libqtest.h"
> +#include "qobject/qdict.h"
> +#include "qobject/qstring.h"
> #include "ui/dbus-display1.h"
>
> static GDBusConnection*
> @@ -38,11 +40,11 @@ test_dbus_p2p_from_fd(int fd)
> }
>
> static void
> -test_setup(QTestState **qts, GDBusConnection **conn)
> +test_setup_args(QTestState **qts, GDBusConnection **conn, const char *args)
> {
> int pair[2];
>
> - *qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
> + *qts = qtest_init(args);
>
> g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
>
> @@ -52,6 +54,12 @@ test_setup(QTestState **qts, GDBusConnection **conn)
> g_dbus_connection_start_message_processing(*conn);
> }
>
> +static void
> +test_setup(QTestState **qts, GDBusConnection **conn)
> +{
> + test_setup_args(qts, conn, "-display dbus,p2p=yes -name dbus-test");
> +}
> +
> static void
> test_dbus_display_vm(void)
> {
> @@ -360,6 +368,92 @@ test_dbus_display_keyboard(void)
> qtest_quit(qts);
> }
>
> +static gsize
> +get_console_ids_count(GDBusConnection *conn)
> +{
> + g_autoptr(GError) err = NULL;
> + g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
> + GVariant *console_ids;
> + gsize n_ids = 0;
> +
> + vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
> + qemu_dbus_display1_vm_proxy_new_sync(
> + conn,
> + G_DBUS_PROXY_FLAGS_NONE,
> + NULL,
> + DBUS_DISPLAY1_ROOT "/VM",
> + NULL,
> + &err));
> + g_assert_no_error(err);
> +
> + console_ids = qemu_dbus_display1_vm_get_console_ids(
> + QEMU_DBUS_DISPLAY1_VM(vm));
> + if (console_ids) {
> + n_ids = g_variant_n_children(console_ids);
> + }
> + return n_ids;
> +}
> +
> +static void
> +wait_device_event(QTestState *qts, const char *event_name, const char *id)
> +{
> + QDict *resp, *data;
> + QString *qstr;
> +
> + for (;;) {
> + resp = qtest_qmp_eventwait_ref(qts, event_name);
> + data = qdict_get_qdict(resp, "data");
> + if (!data || !qdict_get(data, "device")) {
> + qobject_unref(resp);
> + continue;
> + }
> + qstr = qobject_to(QString, qdict_get(data, "device"));
> + if (!strcmp(qstring_get_str(qstr), id)) {
> + qobject_unref(resp);
> + break;
> + }
> + qobject_unref(resp);
> + }
> +}
> +
> +static void
> +test_dbus_display_hotplug(void)
> +{
> + g_autoptr(GDBusConnection) conn = NULL;
> + QTestState *qts = NULL;
> + gsize n;
> +
> + test_setup_args(&qts, &conn,
> + "-machine q35"
> + " -device pcie-root-port,id=rp0"
> + " -display dbus,p2p=yes"
> + " -name dbus-test");
> +
> + n = get_console_ids_count(conn);
> + g_assert_cmpuint(n, ==, 1);
> +
> + qtest_qmp_device_add(qts, "bochs-display", "bochs0",
> + "{'bus': 'rp0'}");
> +
> + n = get_console_ids_count(conn);
> + g_assert_cmpuint(n, ==, 2);
> +
> + /*
> + * On q35, PCI device removal is ACPI-based and requires guest
> + * acknowledgement. Since qtest has no guest OS, issue the delete
> + * request and force removal via system reset.
> + */
> + qtest_qmp_device_del_send(qts, "bochs0");
> + qtest_system_reset_nowait(qts);
> + wait_device_event(qts, "DEVICE_DELETED", "bochs0");
> +
> + n = get_console_ids_count(conn);
> + g_assert_cmpuint(n, ==, 1);
> +
> + g_clear_object(&conn);
> + qtest_quit(qts);
> +}
> +
> int
> main(int argc, char **argv)
> {
> @@ -369,6 +463,9 @@ main(int argc, char **argv)
> qtest_add_data_func("/dbus-display/console", GINT_TO_POINTER(false), test_dbus_display_console);
> qtest_add_data_func("/dbus-display/console/map", GINT_TO_POINTER(true), test_dbus_display_console);
> qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
> + if (qtest_has_machine("q35") && qtest_has_device("bochs-display")) {
> + qtest_add_func("/dbus-display/hotplug", test_dbus_display_hotplug);
> + }
>
> return g_test_run();
> }
Reviewed-by: Fabiano Rosas <farosas@suse.de>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak
2026-05-29 11:16 ` [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak Marc-André Lureau
2026-05-29 13:27 ` Fabiano Rosas
@ 2026-05-29 13:44 ` Peter Maydell
2026-05-31 5:31 ` Marc-André Lureau
1 sibling, 1 reply; 39+ messages in thread
From: Peter Maydell @ 2026-05-29 13:44 UTC (permalink / raw)
To: Marc-André Lureau
Cc: qemu-devel, Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Akihiko Odaki
On Fri, 29 May 2026 at 12:17, Marc-André Lureau
<marcandre.lureau@redhat.com> wrote:
>
> qemu_irq_intercept_in() saves original IRQ handlers by allocating
> new QOM objects, which are never freed. On a PC machine, this leaks
> IRQ objects (one per IOAPIC pin) on every qtest run.
>
> Rather than tracking allocations to free later, avoid them: add an
> "observer" field to IRQState, called by qemu_set_irq() after the
> real handler. Interception sets the observer instead of rewriting
> handlers, so there's nothing to save and nothing to leak.
>
> Fix qemu_notirq() to route through qemu_set_irq() so inverted IRQs
> trigger observers too. Drop the LSan suppression.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> include/hw/core/irq.h | 1 +
> hw/core/irq.c | 10 +++++-----
> system/qtest.c | 3 ---
> scripts/lsan_suppressions.txt | 8 --------
> 4 files changed, 6 insertions(+), 16 deletions(-)
>
> diff --git a/include/hw/core/irq.h b/include/hw/core/irq.h
> index 291fdd67df4..93d5710a73e 100644
> --- a/include/hw/core/irq.h
> +++ b/include/hw/core/irq.h
> @@ -14,6 +14,7 @@ struct IRQState {
> qemu_irq_handler handler;
> void *opaque;
> int n;
> + qemu_irq_handler observer;
> };
>
> void qemu_set_irq(qemu_irq irq, int level);
> diff --git a/hw/core/irq.c b/hw/core/irq.c
> index 106805e2417..fa11e9bc0aa 100644
> --- a/hw/core/irq.c
> +++ b/hw/core/irq.c
> @@ -32,6 +32,9 @@ void qemu_set_irq(qemu_irq irq, int level)
> return;
>
> irq->handler(irq->opaque, irq->n, level);
> + if (unlikely(irq->observer)) {
> + irq->observer(irq->opaque, irq->n, level);
> + }
> }
Hmm. "observer" semantics are probably nicer than "steal
the irq from the thing that would otherwise be connected to it",
but on the other hand this means that this purely test-relevant
thing is now in the code path for the common "signal an IRQ"
that we use everywhere...
-- PMM
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak
2026-05-29 13:44 ` Peter Maydell
@ 2026-05-31 5:31 ` Marc-André Lureau
0 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2026-05-31 5:31 UTC (permalink / raw)
To: Peter Maydell
Cc: qemu-devel, Paolo Bonzini, Daniel P. Berrangé,
Philippe Mathieu-Daudé, Pierrick Bouvier, Fabiano Rosas,
Laurent Vivier, Alex Williamson, Cédric Le Goater,
Akihiko Odaki
Hi
On Fri, May 29, 2026 at 5:44 PM Peter Maydell <peter.maydell@linaro.org> wrote:
>
> On Fri, 29 May 2026 at 12:17, Marc-André Lureau
> <marcandre.lureau@redhat.com> wrote:
> >
> > qemu_irq_intercept_in() saves original IRQ handlers by allocating
> > new QOM objects, which are never freed. On a PC machine, this leaks
> > IRQ objects (one per IOAPIC pin) on every qtest run.
> >
> > Rather than tracking allocations to free later, avoid them: add an
> > "observer" field to IRQState, called by qemu_set_irq() after the
> > real handler. Interception sets the observer instead of rewriting
> > handlers, so there's nothing to save and nothing to leak.
> >
> > Fix qemu_notirq() to route through qemu_set_irq() so inverted IRQs
> > trigger observers too. Drop the LSan suppression.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> > include/hw/core/irq.h | 1 +
> > hw/core/irq.c | 10 +++++-----
> > system/qtest.c | 3 ---
> > scripts/lsan_suppressions.txt | 8 --------
> > 4 files changed, 6 insertions(+), 16 deletions(-)
> >
> > diff --git a/include/hw/core/irq.h b/include/hw/core/irq.h
> > index 291fdd67df4..93d5710a73e 100644
> > --- a/include/hw/core/irq.h
> > +++ b/include/hw/core/irq.h
> > @@ -14,6 +14,7 @@ struct IRQState {
> > qemu_irq_handler handler;
> > void *opaque;
> > int n;
> > + qemu_irq_handler observer;
> > };
> >
> > void qemu_set_irq(qemu_irq irq, int level);
> > diff --git a/hw/core/irq.c b/hw/core/irq.c
> > index 106805e2417..fa11e9bc0aa 100644
> > --- a/hw/core/irq.c
> > +++ b/hw/core/irq.c
> > @@ -32,6 +32,9 @@ void qemu_set_irq(qemu_irq irq, int level)
> > return;
> >
> > irq->handler(irq->opaque, irq->n, level);
> > + if (unlikely(irq->observer)) {
> > + irq->observer(irq->opaque, irq->n, level);
> > + }
> > }
>
> Hmm. "observer" semantics are probably nicer than "steal
> the irq from the thing that would otherwise be connected to it",
> but on the other hand this means that this purely test-relevant
> thing is now in the code path for the common "signal an IRQ"
> that we use everywhere...
Sure, but is this more intrusive or expansive than a trace at this point?
^ permalink raw reply [flat|nested] 39+ messages in thread
end of thread, other threads:[~2026-05-31 5:32 UTC | newest]
Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-29 11:16 [PATCH 00/32] ui: better console hotplug support Marc-André Lureau
2026-05-29 11:16 ` [PATCH 01/32] ui/gtk: fix bad widget realize on non-GFX VC Marc-André Lureau
2026-05-29 11:16 ` [PATCH 02/32] build-sys: build with -fno-omit-frame-pointer with ASAN Marc-André Lureau
2026-05-29 11:16 ` [PATCH 03/32] irq: add per-IRQ observer to fix qemu_irq_intercept_in leak Marc-André Lureau
2026-05-29 13:27 ` Fabiano Rosas
2026-05-29 13:44 ` Peter Maydell
2026-05-31 5:31 ` Marc-André Lureau
2026-05-29 11:16 ` [PATCH 04/32] scripts/lsan_suppressions: suppress fontconfig leaks Marc-André Lureau
2026-05-29 11:16 ` [PATCH 05/32] vfio/pci: close display console during unrealize, not finalize Marc-André Lureau
2026-05-29 11:16 ` [PATCH 06/32] glib-compat: add fallback for g_clear_fd/g_autofd Marc-André Lureau
2026-05-29 11:16 ` [PATCH 07/32] ui/dbus: remove mouse handler on dispose Marc-André Lureau
2026-05-29 11:16 ` [PATCH 08/32] ui/qmp: keep a reference of console across yield Marc-André Lureau
2026-05-29 11:16 ` [PATCH 09/32] ui: stop ui timer when closing Marc-André Lureau
2026-05-29 11:16 ` [PATCH 10/32] ui/console: init gl_unblock_timer in qemu_console_init Marc-André Lureau
2026-05-29 11:16 ` [PATCH 11/32] ui/spice: remove dead spice_displays Marc-André Lureau
2026-05-29 11:16 ` [PATCH 12/32] ui/spice: add cleanup on shutdown Marc-André Lureau
2026-05-29 11:16 ` [PATCH 13/32] ui: add display cleanup infrastructure Marc-André Lureau
2026-05-29 11:16 ` [PATCH 14/32] ui/curses: implement display cleanup Marc-André Lureau
2026-05-29 11:16 ` [PATCH 15/32] ui/sdl2: " Marc-André Lureau
2026-05-29 11:16 ` [PATCH 16/32] ui/spice-app: " Marc-André Lureau
2026-05-29 11:16 ` [PATCH 17/32] ui/egl: implement display and EGL cleanup Marc-André Lureau
2026-05-29 11:16 ` [PATCH 18/32] ui/cocoa: implement display cleanup Marc-André Lureau
2026-05-29 11:16 ` [PATCH 19/32] ui/dbus: " Marc-André Lureau
2026-05-29 12:17 ` Akihiko Odaki
2026-05-29 11:16 ` [PATCH 20/32] ui/gtk: " Marc-André Lureau
2026-05-29 11:16 ` [PATCH 21/32] ui/console: add console event notifier infrastructure Marc-André Lureau
2026-05-29 11:16 ` [PATCH 22/32] ui/console: fire console ADDED/REMOVED notifications Marc-André Lureau
2026-05-29 11:16 ` [PATCH 23/32] ui/console-vc: fire " Marc-André Lureau
2026-05-29 11:16 ` [PATCH 24/32] ui/gtk: convert VirtualConsole storage from fixed array to GPtrArray Marc-André Lureau
2026-05-29 12:21 ` Akihiko Odaki
2026-05-29 11:16 ` [PATCH 25/32] ui/gtk: move global display settings out of per-console init Marc-André Lureau
2026-05-29 11:16 ` [PATCH 26/32] ui/gtk: fix tab re-insertion order on window close Marc-André Lureau
2026-05-29 11:16 ` [PATCH 27/32] ui/gtk: centralize console menu and shortcut management Marc-André Lureau
2026-05-29 11:16 ` [PATCH 28/32] ui/gtk: handle console hotplug/unplug events Marc-André Lureau
2026-05-29 11:16 ` [PATCH 29/32] ui/console: register console in QOM tree dynamically Marc-André Lureau
2026-05-29 11:16 ` [PATCH 30/32] ui/console: unregister console from QOM tree on close Marc-André Lureau
2026-05-29 11:16 ` [PATCH 31/32] ui/dbus: handle console hotplug/unplug events Marc-André Lureau
2026-05-29 11:16 ` [PATCH 32/32] tests/qtest: add D-Bus display hotplug test Marc-André Lureau
2026-05-29 13:34 ` Fabiano Rosas
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.