* [GIT PULL 00/14] UI/display queue
@ 2026-09-13 10:41 Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 01/14] ui/dbus: fix cursor race, copy cursor data Marc-André Lureau
` (14 more replies)
0 siblings, 15 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel
The following changes since commit d43c2d5f89db70359a7b3a7e2ad7098fcc0165ef:
Merge tag 'for_upstream' of https://git.kernel.org/pub/scm/virt/kvm/mst/qemu into staging (2026-09-11 12:04:42 -1000)
are available in the Git repository at:
https://gitlab.com/marcandre.lureau/qemu.git tags/ui-pr-v1
for you to fetch changes up to 78809254f367cec04d68afd4c90115d9cc15a2e4:
ui/gtk: Clean up GL resources on tab detach, re-attach, and VC free (2026-09-13 14:41:12 +0400)
----------------------------------------------------------------
UI/display queue
- cursor data races fixes
- qxl migration harderning
- add org.qemu.Display1.UIInfo
- gtk fixes
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
----------------------------------------------------------------
Akihiko Odaki (2):
ui/gtk: Remove glFlush() after eglSwapBuffers()
ui/gtk: Work around the gtk-menu-bar-accel leak
Andrey Drobyshev (3):
hw/display/qxl: factor out qxl_guest_phys2virt()
hw/display/qxl: validate replayed commands in qxl_post_load
hw/display/qxl: trace skipped stale loadvm commands
Chengyang Zhu (1):
ui/dbus: add org.qemu.Display1.UIInfo interface
Denis V. Lunev (2):
hw/display/qxl: hold ssd.lock while replacing ssd.cursor
ui/cursor: make the cursor refcount atomic
Dongwon Kim (2):
ui/gtk: Handle empty notebook state in menu handlers
ui/gtk: Clean up GL resources on tab detach, re-attach, and VC free
Marc-André Lureau (4):
ui/dbus: fix cursor race, copy cursor data
docs/sphinx/dbus: register build dependency
vhost-user-gpu: validate command buffer size in submit_3d
virtio-gpu-virgl: guard new_blob with VIRGL_VERSION_MAJORS>=1
contrib/vhost-user-gpu/virgl.c | 12 ++--
docs/sphinx/dbusdoc.py | 1 +
hw/display/qxl.c | 134 ++++++++++++++++++++++++++++++-----------
hw/display/trace-events | 1 +
hw/display/virtio-gpu-virgl.c | 2 +
include/ui/console.h | 9 +++
ui/cursor.c | 17 ++++--
ui/dbus-console.c | 106 ++++++++++++++++++++++++++++++++
ui/dbus-display1.xml | 55 +++++++++++++++++
ui/dbus-listener.c | 8 +--
ui/gtk-egl.c | 3 -
ui/gtk.c | 125 ++++++++++++++++++++++++++++----------
12 files changed, 387 insertions(+), 86 deletions(-)
^ permalink raw reply [flat|nested] 17+ messages in thread
* [GIT PULL 01/14] ui/dbus: fix cursor race, copy cursor data
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 02/14] hw/display/qxl: hold ssd.lock while replacing ssd.cursor Marc-André Lureau
` (13 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Eliminates the undefined behavior where virtio-gpu's memcpy() overwrites
the buffer that an in-flight gvariant still references. Fixes the data
race where the GDBus worker thread calls cursor_unref() concurrently
with main thread.
Fixes: 142ca628a733 ("ui: add a D-Bus display backend")
Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260904123020.3108205-1-marcandre.lureau@redhat.com>
---
ui/dbus-listener.c | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
index c1e86648384c..624d0fb71d6b 100644
--- a/ui/dbus-listener.c
+++ b/ui/dbus-listener.c
@@ -926,13 +926,11 @@ static void dbus_cursor_define(DisplayChangeListener *dcl,
ddl_discard_cursor_messages(ddl);
- v_data = g_variant_new_from_data(
- G_VARIANT_TYPE("ay"),
+ v_data = g_variant_new_fixed_array(
+ G_VARIANT_TYPE_BYTE,
c->data,
c->width * c->height * 4,
- TRUE,
- (GDestroyNotify)cursor_unref,
- cursor_ref(c));
+ 1);
qemu_dbus_display1_listener_call_cursor_define(
ddl->proxy,
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 02/14] hw/display/qxl: hold ssd.lock while replacing ssd.cursor
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 01/14] ui/dbus: fix cursor race, copy cursor data Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 03/14] ui/cursor: make the cursor refcount atomic Marc-André Lureau
` (12 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel
From: "Denis V. Lunev" <den@openvz.org>
qxl_spice_reset_cursor() unrefs qxl->ssd.cursor and installs the hidden
cursor without holding qxl->ssd.lock. Every other writer of that field
takes it: qxl_render_cursor(), display_mouse_define() and
qemu_spice_cursor_refresh_bh().
The unlocked path runs on a vCPU thread, reached from ioport_write() on
QXL_IO_DESTROY_PRIMARY and QXL_IO_DESTROY_PRIMARY_ASYNC, and holds only
the BQL, which the SPICE display worker never takes. Unlike
qxl_hard_reset(), it leaves that worker running.
spice_qxl_reset_cursor() does round trip through the dispatcher, but the
worker is free again as soon as it returns, so it can enter
qxl_render_cursor() and unref the same QEMUCursor a few instructions
later. Both threads then drop one reference for what is a single
reference, freeing a cursor that another user still holds. The store to
ssd.cursor races the same way, and a guest that keeps this up also ends
up waiting forever in qxl_fence_wait().
A guest reaches this by switching QXL mode while it also updates the
pointer shape.
Fixes: 958c2bceba06 ("qxl: fix cursor reset")
Cc: qemu-stable@nongnu.org
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260903192647.2677279-2-den@openvz.org>
---
hw/display/qxl.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 384b8767b8e6..c4f547e88bd5 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -294,10 +294,12 @@ void qxl_spice_reset_cursor(PCIQXLDevice *qxl)
qemu_mutex_lock(&qxl->track_lock);
qxl->guest_cursor = 0;
qemu_mutex_unlock(&qxl->track_lock);
+ qemu_mutex_lock(&qxl->ssd.lock);
if (qxl->ssd.cursor) {
cursor_unref(qxl->ssd.cursor);
}
qxl->ssd.cursor = cursor_builtin_hidden();
+ qemu_mutex_unlock(&qxl->ssd.lock);
}
static uint32_t qxl_crc32(const uint8_t *p, unsigned len)
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 03/14] ui/cursor: make the cursor refcount atomic
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 01/14] ui/dbus: fix cursor race, copy cursor data Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 02/14] hw/display/qxl: hold ssd.lock while replacing ssd.cursor Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 04/14] docs/sphinx/dbus: register build dependency Marc-André Lureau
` (11 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
From: "Denis V. Lunev" <den@openvz.org>
A QEMUCursor outlives the call that publishes it and is shared between
threads, but its refcount was a plain int with no single lock covering
every user. qemu_console_set_cursor() takes and drops references from
the main loop under the BQL alone, hw/display/qxl-render.c does so from
the SPICE display worker thread, and ui/spice-display.c does so under
SimpleSpiceDisplay::lock. ui/cocoa.m and ui/dbus-listener.c add two more
threads.
The pair that collides is qemu_spice_cursor_refresh_bh(), which drops
ssd->lock before calling qemu_console_set_cursor(), and the worker
refcounting the same cursor under that lock. A lost increment frees the
cursor while the console still points at it, so the console's next unref
decrements memory the allocator has already handed out again. Locking
ssd.cursor is not enough on its own: with that done, this is the race
that remains.
Assert on the value the decrement observed while here. Dropping a
reference that was never taken used to be silent, because the decrement
lands in the allocator metadata of the freed chunk: nothing is logged,
the object is not freed twice, and the process runs on until some later
allocation walks the damaged free list and faults, arbitrarily far from
the code that caused it.
Fixes: 0b2824e5e48a ("spice: use bottom half instead of refresh timer for cursor updates")
Cc: qemu-stable@nongnu.org
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260903192647.2677279-3-den@openvz.org>
---
include/ui/console.h | 9 +++++++++
ui/cursor.c | 17 +++++++++++------
2 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/include/ui/console.h b/include/ui/console.h
index 29bf72288833..3634956949ac 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -126,6 +126,15 @@ typedef struct QEMUCursor {
} QEMUCursor;
QEMUCursor *cursor_alloc(uint16_t width, uint16_t height);
+
+/*
+ * A cursor may be shared between the main loop, a vCPU thread and a
+ * display backend's own thread, so the refcount is atomic and these two
+ * may be called from any of them. The object itself is not otherwise
+ * thread-safe: take a reference before publishing the pointer anywhere
+ * another thread can reach it, and never dereference a cursor you do
+ * not hold a reference to.
+ */
QEMUCursor *cursor_ref(QEMUCursor *c);
void cursor_unref(QEMUCursor *c);
QEMUCursor *cursor_builtin_hidden(void);
diff --git a/ui/cursor.c b/ui/cursor.c
index 6e23244fbe6b..69d27d49a135 100644
--- a/ui/cursor.c
+++ b/ui/cursor.c
@@ -1,4 +1,5 @@
#include "qemu/osdep.h"
+#include "qemu/atomic.h"
#include "ui/console.h"
#include "cursor_hidden.xpm"
@@ -103,24 +104,28 @@ QEMUCursor *cursor_alloc(uint16_t width, uint16_t height)
c = g_malloc0(sizeof(QEMUCursor) + datasize);
c->width = width;
c->height = height;
- c->refcount = 1;
+ qatomic_set(&c->refcount, 1);
return c;
}
QEMUCursor *cursor_ref(QEMUCursor *c)
{
- c->refcount++;
+ qatomic_inc(&c->refcount);
return c;
}
void cursor_unref(QEMUCursor *c)
{
+ int refcount;
+
if (c == NULL)
return;
- c->refcount--;
- if (c->refcount)
- return;
- g_free(c);
+
+ refcount = qatomic_fetch_dec(&c->refcount);
+ assert(refcount > 0);
+ if (refcount == 1) {
+ g_free(c);
+ }
}
int cursor_get_mono_bpl(QEMUCursor *c)
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 04/14] docs/sphinx/dbus: register build dependency
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (2 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 03/14] ui/cursor: make the cursor refcount atomic Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-14 5:45 ` Mauro Carvalho Chehab
2026-09-13 10:41 ` [GIT PULL 05/14] ui/dbus: add org.qemu.Display1.UIInfo interface Marc-André Lureau
` (10 subsequent siblings)
14 siblings, 1 reply; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, John Snow, Peter Maydell,
Mauro Carvalho Chehab, Pierrick Bouvier
Touching dbus xml file wasn't enough to trigger a new build because
the file wasn't registered to the dependency system.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
docs/sphinx/dbusdoc.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/sphinx/dbusdoc.py b/docs/sphinx/dbusdoc.py
index be284ed08fd7..2b086e2f583c 100644
--- a/docs/sphinx/dbusdoc.py
+++ b/docs/sphinx/dbusdoc.py
@@ -146,6 +146,7 @@ def run(self):
env = self.state.document.settings.env
dbusfile = env.config.qapidoc_srctree + "/" + self.arguments[0]
+ env.note_dependency(os.path.abspath(dbusfile))
with open(dbusfile, "rb") as f:
xml_data = f.read()
xml = parse_dbus_xml(xml_data)
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 05/14] ui/dbus: add org.qemu.Display1.UIInfo interface
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (3 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 04/14] docs/sphinx/dbus: register build dependency Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 06/14] hw/display/qxl: factor out qxl_guest_phys2virt() Marc-André Lureau
` (9 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
From: Chengyang Zhu <colazcyg@gmail.com>
Currently, the SetUIInfo method cannot set a refresh rate.
Simply adding a refresh_rate argument would break the method signature.
This patch adds the UIInfo interface containing
* property `Supported` indicating whether console UI info is supported.
* the method `Apply` taking a dictionary as input.
* the method `Get` returning the current UI info as a dictionary.
Message-ID: <20260830054641.45437-2-colazcyg@gmail.com>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Chengyang Zhu <colazcyg@gmail.com>
---
ui/dbus-console.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++
ui/dbus-display1.xml | 55 ++++++++++++++++++++++++++
2 files changed, 161 insertions(+)
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
index e1ac06814ba8..947c3b3a545e 100644
--- a/ui/dbus-console.c
+++ b/ui/dbus-console.c
@@ -55,6 +55,8 @@ struct _DBusDisplayConsole {
guint last_x;
guint last_y;
Notifier mouse_mode_notifier;
+
+ QemuDBusDisplay1UIInfo *iface_ui_info;
};
G_DEFINE_TYPE(DBusDisplayConsole,
@@ -155,6 +157,7 @@ dbus_display_console_dispose(GObject *object)
qemu_input_led_notifier_remove(&ddc->led_notifier);
qemu_console_unregister_listener(&ddc->dcl);
qemu_remove_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
+ g_clear_object(&ddc->iface_ui_info);
g_clear_object(&ddc->iface_touch);
g_clear_object(&ddc->iface_mouse);
g_clear_object(&ddc->iface_kbd);
@@ -528,6 +531,98 @@ dbus_mouse_mode_change(Notifier *notify, void *data)
dbus_mouse_update_is_absolute(ddc);
}
+static gboolean
+dbus_ui_info_get(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation)
+{
+ QemuUIInfo ui_info;
+ GVariantDict dict;
+
+ if (!qemu_console_ui_info_supported(ddc->dcl.con)) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_UNSUPPORTED,
+ "UIInfo is not supported");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ ui_info = *qemu_console_get_ui_info(ddc->dcl.con);
+ g_variant_dict_init(&dict, NULL);
+
+ g_variant_dict_insert(&dict, "width_mm", "q", ui_info.width_mm);
+ g_variant_dict_insert(&dict, "height_mm", "q", ui_info.height_mm);
+ g_variant_dict_insert(&dict, "xoff", "i", ui_info.xoff);
+ g_variant_dict_insert(&dict, "yoff", "i", ui_info.yoff);
+ g_variant_dict_insert(&dict, "width", "u", ui_info.width);
+ g_variant_dict_insert(&dict, "height", "u", ui_info.height);
+ g_variant_dict_insert(&dict, "refresh_rate", "u", ui_info.refresh_rate);
+
+ qemu_dbus_display1_uiinfo_complete_get(ddc->iface_ui_info, invocation,
+ g_variant_dict_end(&dict));
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static bool
+dbus_ui_info_apply_lookup(GDBusMethodInvocation *invocation,
+ GVariantDict *dict,
+ const gchar *key,
+ const gchar *fmt_str,
+ void *res)
+{
+ if (g_variant_dict_contains(dict, key) &&
+ !g_variant_dict_lookup(dict, key, fmt_str, res)) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "%s must have D-Bus signature %s",
+ key, fmt_str);
+ return false;
+ }
+ return true;
+}
+
+static gboolean
+dbus_ui_info_apply(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ GVariant *arg_ui_info)
+{
+ QemuUIInfo ui_info;
+ g_auto(GVariantDict) dict = G_VARIANT_DICT_INIT(arg_ui_info);
+
+ if (!qemu_console_ui_info_supported(ddc->dcl.con)) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_UNSUPPORTED,
+ "UIInfo is not supported");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ ui_info = *qemu_console_get_ui_info(ddc->dcl.con);
+
+ if (!dbus_ui_info_apply_lookup(invocation, &dict, "width_mm", "q",
+ &ui_info.width_mm) ||
+ !dbus_ui_info_apply_lookup(invocation, &dict, "height_mm", "q",
+ &ui_info.height_mm) ||
+ !dbus_ui_info_apply_lookup(invocation, &dict, "xoff", "i",
+ &ui_info.xoff) ||
+ !dbus_ui_info_apply_lookup(invocation, &dict, "yoff", "i",
+ &ui_info.yoff) ||
+ !dbus_ui_info_apply_lookup(invocation, &dict, "width", "u",
+ &ui_info.width) ||
+ !dbus_ui_info_apply_lookup(invocation, &dict, "height", "u",
+ &ui_info.height) ||
+ !dbus_ui_info_apply_lookup(invocation, &dict, "refresh_rate", "u",
+ &ui_info.refresh_rate)) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ qemu_console_set_ui_info(ddc->dcl.con, &ui_info, false);
+ qemu_dbus_display1_uiinfo_complete_apply(ddc->iface_ui_info, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
int dbus_display_console_get_index(DBusDisplayConsole *ddc)
{
return qemu_console_get_index(ddc->dcl.con);
@@ -550,6 +645,7 @@ dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
"org.qemu.Display1.Keyboard",
"org.qemu.Display1.Mouse",
"org.qemu.Display1.MultiTouch",
+ "org.qemu.Display1.UIInfo",
NULL
};
@@ -626,5 +722,15 @@ dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
dbus_mouse_update_is_absolute(ddc);
+ ddc->iface_ui_info = qemu_dbus_display1_uiinfo_skeleton_new();
+ qemu_dbus_display1_uiinfo_set_supported(ddc->iface_ui_info,
+ qemu_console_ui_info_supported(ddc->dcl.con));
+ g_object_connect(ddc->iface_ui_info,
+ "swapped-signal::handle-get", dbus_ui_info_get, ddc,
+ "swapped-signal::handle-apply", dbus_ui_info_apply, ddc,
+ NULL);
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+ G_DBUS_INTERFACE_SKELETON(ddc->iface_ui_info));
+
return ddc;
}
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
index d96bae2ed642..a23ca0b8b125 100644
--- a/ui/dbus-display1.xml
+++ b/ui/dbus-display1.xml
@@ -90,6 +90,10 @@
@height: console height, in pixels.
Modify the dimensions and display settings.
+
+ .. seealso::
+
+ :dbus:iface:`org.qemu.Display1.UIInfo` which supports a superset of these properties.
-->
<method name="SetUIInfo">
<arg name="width_mm" type="q" direction="in"/>
@@ -1159,4 +1163,55 @@
-->
<property name="Encoding" type="s" access="read"/>
</interface>
+
+ <!--
+ org.qemu.Display1.UIInfo:
+
+ This interface is implemented on
+ ``/org/qemu/Display1/Console_$id`` (see
+ :dbus:iface:`~org.qemu.Display1.Console`).
+ -->
+ <interface name="org.qemu.Display1.UIInfo">
+ <!--
+ Supported:
+
+ Whether console UI info is supported.
+ -->
+ <property name="Supported" type="b" access="read"/>
+
+ <!--
+ Get:
+ @ui_info: a dictionary of UI info properties.
+
+ Return the currently requested properties for the console.
+ The list of returned properties may vary and be extended in the future.
+
+ - ``width_mm`` (q): the physical display width in millimeters.
+ - ``height_mm`` (q): the physical display height in millimeters.
+ - ``xoff`` (i): the horizontal offset in pixels.
+ - ``yoff`` (i): the vertical offset in pixels.
+ - ``width`` (u): the console width in pixels.
+ - ``height`` (u): the console height in pixels.
+ - ``refresh_rate`` (u): the display refresh rate in millihertz.
+ -->
+ <method name="Get">
+ <arg name="ui_info" type="a{sv}" direction="out"/>
+ </method>
+
+ <!--
+ Apply:
+ @ui_info: a dictionary of new UI info properties.
+
+ Apply the properties to the console display.
+ The dictionary schema of ``Apply`` is a subset of that of ``Get``.
+
+ The properties will be merged with existing values.
+ Extra keys will be ignored.
+ The invocation will fail if any key has the wrong type,
+ leaving the UI info unchanged.
+ -->
+ <method name="Apply">
+ <arg name="ui_info" type="a{sv}" direction="in"/>
+ </method>
+ </interface>
</node>
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 06/14] hw/display/qxl: factor out qxl_guest_phys2virt()
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (4 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 05/14] ui/dbus: add org.qemu.Display1.UIInfo interface Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 07/14] hw/display/qxl: validate replayed commands in qxl_post_load Marc-André Lureau
` (8 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel
From: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Split the GROUP_GUEST half of qxl_phys2virt() into the helper
qxl_guest_phys2virt(). Also add a bool 'report_bug' param to the
qxl_get_check_slot_offset() called from it: when it's false, a failing
check just returns false without calling qxl_set_guest_bug(). All
existing callers pass true, so there's no functional change. This
is in preparation for a quiet caller that validates guest addresses
which might be legitimately stale, when flagging a guest bug would be
wrong.
Message-ID: <20260825172051.435372-2-andrey.drobyshev@virtuozzo.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
hw/display/qxl.c | 83 ++++++++++++++++++++++++++++++++++----------------------
1 file changed, 51 insertions(+), 32 deletions(-)
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index c4f547e88bd5..b6bc182fc2c3 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -1409,7 +1409,7 @@ static void qxl_reset_surfaces(PCIQXLDevice *d)
/* can be also called from spice server thread context */
static bool qxl_get_check_slot_offset(PCIQXLDevice *qxl, QXLPHYSICAL pqxl,
uint32_t *s, uint64_t *o,
- size_t size_requested)
+ size_t size_requested, bool report_bug)
{
uint64_t phys = le64_to_cpu(pqxl);
uint32_t slot = (phys >> (64 - 8)) & 0xff;
@@ -1417,42 +1417,55 @@ static bool qxl_get_check_slot_offset(PCIQXLDevice *qxl, QXLPHYSICAL pqxl,
uint64_t size_available;
if (slot >= NUM_MEMSLOTS) {
- qxl_set_guest_bug(qxl, "slot too large %d >= %d", slot,
- NUM_MEMSLOTS);
+ if (report_bug) {
+ qxl_set_guest_bug(qxl, "slot too large %d >= %d", slot,
+ NUM_MEMSLOTS);
+ }
return false;
}
if (!qxl->guest_slots[slot].active) {
- qxl_set_guest_bug(qxl, "inactive slot %d\n", slot);
+ if (report_bug) {
+ qxl_set_guest_bug(qxl, "inactive slot %d\n", slot);
+ }
return false;
}
if (offset < qxl->guest_slots[slot].delta) {
- qxl_set_guest_bug(qxl,
- "slot %d offset %"PRIu64" < delta %"PRIu64"\n",
- slot, offset, qxl->guest_slots[slot].delta);
+ if (report_bug) {
+ qxl_set_guest_bug(qxl,
+ "slot %d offset %"PRIu64" < delta %"PRIu64"\n",
+ slot, offset, qxl->guest_slots[slot].delta);
+ }
return false;
}
offset -= qxl->guest_slots[slot].delta;
if (offset > qxl->guest_slots[slot].size) {
- qxl_set_guest_bug(qxl,
- "slot %d offset %"PRIu64" > size %"PRIu64"\n",
- slot, offset, qxl->guest_slots[slot].size);
+ if (report_bug) {
+ qxl_set_guest_bug(qxl,
+ "slot %d offset %"PRIu64" > size %"PRIu64"\n",
+ slot, offset, qxl->guest_slots[slot].size);
+ }
return false;
}
size_available = memory_region_size(qxl->guest_slots[slot].mr);
if (qxl->guest_slots[slot].offset + offset >= size_available) {
- qxl_set_guest_bug(qxl,
- "slot %d offset %"PRIu64" > region size %"PRIu64"\n",
- slot, qxl->guest_slots[slot].offset + offset,
- size_available);
+ if (report_bug) {
+ qxl_set_guest_bug(qxl,
+ "slot %d offset %"PRIu64" > region size %"PRIu64
+ "\n", slot,
+ qxl->guest_slots[slot].offset + offset,
+ size_available);
+ }
return false;
}
size_available -= qxl->guest_slots[slot].offset + offset;
if (size_requested > size_available) {
- qxl_set_guest_bug(qxl,
- "slot %d offset %"PRIu64" size %zu: "
- "overrun by %"PRIu64" bytes\n",
- slot, offset, size_requested,
- size_requested - size_available);
+ if (report_bug) {
+ qxl_set_guest_bug(qxl,
+ "slot %d offset %"PRIu64" size %zu: "
+ "overrun by %"PRIu64" bytes\n",
+ slot, offset, size_requested,
+ size_requested - size_available);
+ }
return false;
}
@@ -1462,25 +1475,31 @@ static bool qxl_get_check_slot_offset(PCIQXLDevice *qxl, QXLPHYSICAL pqxl,
}
/* can be also called from spice server thread context */
-void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL pqxl, int group_id,
- size_t size)
+static void *qxl_guest_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL pqxl,
+ size_t size, bool report_bug)
{
uint64_t offset;
uint32_t slot;
- void *ptr;
+ uint8_t *ptr;
+ if (!qxl_get_check_slot_offset(qxl, pqxl, &slot, &offset, size,
+ report_bug)) {
+ return NULL;
+ }
+ ptr = memory_region_get_ram_ptr(qxl->guest_slots[slot].mr);
+ ptr += qxl->guest_slots[slot].offset;
+ ptr += offset;
+ return ptr;
+}
+
+void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL pqxl, int group_id,
+ size_t size)
+{
switch (group_id) {
case MEMSLOT_GROUP_HOST:
- offset = le64_to_cpu(pqxl) & 0xffffffffffff;
- return (void *)(intptr_t)offset;
+ return (void *)(intptr_t)(le64_to_cpu(pqxl) & 0xffffffffffff);
case MEMSLOT_GROUP_GUEST:
- if (!qxl_get_check_slot_offset(qxl, pqxl, &slot, &offset, size)) {
- return NULL;
- }
- ptr = memory_region_get_ram_ptr(qxl->guest_slots[slot].mr);
- ptr += qxl->guest_slots[slot].offset;
- ptr += offset;
- return ptr;
+ return qxl_guest_phys2virt(qxl, pqxl, size, true);
}
return NULL;
}
@@ -2003,7 +2022,7 @@ static void qxl_dirty_one_surface(PCIQXLDevice *qxl, QXLPHYSICAL pqxl,
bool rc;
size = (uint64_t)height * abs(stride);
- rc = qxl_get_check_slot_offset(qxl, pqxl, &slot, &offset, size);
+ rc = qxl_get_check_slot_offset(qxl, pqxl, &slot, &offset, size, true);
assert(rc == true);
trace_qxl_surfaces_dirty(qxl->id, offset, size);
qxl_set_dirty(qxl->guest_slots[slot].mr,
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 07/14] hw/display/qxl: validate replayed commands in qxl_post_load
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (5 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 06/14] hw/display/qxl: factor out qxl_guest_phys2virt() Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 08/14] hw/display/qxl: trace skipped stale loadvm commands Marc-André Lureau
` (7 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel
From: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
On incoming migration qxl_post_load() replays the tracked cursor and
surface commands by handing their guest addresses straight to spice,
without revalidating them. Those addresses were checked when the guest
submitted them, but the guest may have freed or reused that memory
before migration, so qxl's tracked pointer can be stale. spice-server
then re-parses the command from that memory and, for a stale cursor,
reads a garbage shape pointer -- aborting the target in memslot_get_virt()
(again, spice-server function) and failing the migration.
Validate each replayed command with qxl_guest_phys2virt(report_bug=false)
before adding it to the replay list, and for a cursor also validate the
nested shape pointer. Commands that no longer resolve are skipped rather
than replayed. report_bug is false so a stale pointer is not mistaken for
a live guest error, which would needlessly disable a healthy guest's
display.
Message-ID: <20260825172051.435372-3-andrey.drobyshev@virtuozzo.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
hw/display/qxl.c | 38 +++++++++++++++++++++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index b6bc182fc2c3..fb77f217b1c6 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -2390,6 +2390,37 @@ static void qxl_create_memslots(PCIQXLDevice *d)
}
}
+/*
+ * Validate a command tracked for loadvm replay before handing its guest
+ * address to spice-server.
+ */
+static bool qxl_loadvm_cmd_valid(PCIQXLDevice *d, QXLPHYSICAL data,
+ uint32_t type)
+{
+ switch (type) {
+ case QXL_CMD_SURFACE:
+ return qxl_guest_phys2virt(d, data,
+ sizeof(QXLSurfaceCmd), false) != NULL;
+
+ case QXL_CMD_CURSOR: {
+ QXLCursorCmd *cmd = qxl_guest_phys2virt(d, data, sizeof(QXLCursorCmd),
+ false);
+
+ if (!cmd) {
+ return false;
+ }
+ if (le32_to_cpu(cmd->type) == QXL_CURSOR_SET) {
+ return qxl_guest_phys2virt(d, le64_to_cpu(cmd->u.set.shape),
+ sizeof(QXLCursor), false) != NULL;
+ }
+ return true;
+ }
+
+ default:
+ g_assert_not_reached();
+ }
+}
+
static int qxl_post_load(void *opaque, int version)
{
PCIQXLDevice* d = opaque;
@@ -2428,12 +2459,17 @@ static int qxl_post_load(void *opaque, int version)
if (d->guest_surfaces.cmds[in] == 0) {
continue;
}
+ if (!qxl_loadvm_cmd_valid(d, d->guest_surfaces.cmds[in],
+ QXL_CMD_SURFACE)) {
+ continue;
+ }
cmds[out].cmd.data = d->guest_surfaces.cmds[in];
cmds[out].cmd.type = QXL_CMD_SURFACE;
cmds[out].group_id = MEMSLOT_GROUP_GUEST;
out++;
}
- if (d->guest_cursor) {
+ if (d->guest_cursor &&
+ qxl_loadvm_cmd_valid(d, d->guest_cursor, QXL_CMD_CURSOR)) {
cmds[out].cmd.data = d->guest_cursor;
cmds[out].cmd.type = QXL_CMD_CURSOR;
cmds[out].group_id = MEMSLOT_GROUP_GUEST;
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 08/14] hw/display/qxl: trace skipped stale loadvm commands
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (6 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 07/14] hw/display/qxl: validate replayed commands in qxl_post_load Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 09/14] ui/gtk: Remove glFlush() after eglSwapBuffers() Marc-André Lureau
` (6 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel
From: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Emit a trace event when qxl_post_load() drops a cursor or surface
command whose guest address no longer resolves, so a migration that
lands on a stale tracked pointer is visible instead of silent.
Message-ID: <20260825172051.435372-4-andrey.drobyshev@virtuozzo.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
hw/display/qxl.c | 17 +++++++++++------
hw/display/trace-events | 1 +
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index fb77f217b1c6..d5f9771f5af2 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -2461,6 +2461,8 @@ static int qxl_post_load(void *opaque, int version)
}
if (!qxl_loadvm_cmd_valid(d, d->guest_surfaces.cmds[in],
QXL_CMD_SURFACE)) {
+ trace_qxl_post_load_stale_cmd(d->id, "surface",
+ d->guest_surfaces.cmds[in]);
continue;
}
cmds[out].cmd.data = d->guest_surfaces.cmds[in];
@@ -2468,12 +2470,15 @@ static int qxl_post_load(void *opaque, int version)
cmds[out].group_id = MEMSLOT_GROUP_GUEST;
out++;
}
- if (d->guest_cursor &&
- qxl_loadvm_cmd_valid(d, d->guest_cursor, QXL_CMD_CURSOR)) {
- cmds[out].cmd.data = d->guest_cursor;
- cmds[out].cmd.type = QXL_CMD_CURSOR;
- cmds[out].group_id = MEMSLOT_GROUP_GUEST;
- out++;
+ if (d->guest_cursor) {
+ if (qxl_loadvm_cmd_valid(d, d->guest_cursor, QXL_CMD_CURSOR)) {
+ cmds[out].cmd.data = d->guest_cursor;
+ cmds[out].cmd.type = QXL_CMD_CURSOR;
+ cmds[out].group_id = MEMSLOT_GROUP_GUEST;
+ out++;
+ } else {
+ trace_qxl_post_load_stale_cmd(d->id, "cursor", d->guest_cursor);
+ }
}
qxl_spice_loadvm_commands(d, cmds, out);
g_free(cmds);
diff --git a/hw/display/trace-events b/hw/display/trace-events
index 4bfc457fbac1..c5e7e42af23b 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -82,6 +82,7 @@ qxl_io_unexpected_vga_mode(int qid, uint64_t addr, uint64_t val, const char *des
qxl_io_write(int qid, const char *mode, uint64_t addr, const char *aname, uint64_t val, unsigned size, int async) "%d %s addr=%"PRIu64 " (%s) val=%"PRIu64" size=%u async=%d"
qxl_memslot_add_guest(int qid, uint32_t slot_id, uint64_t guest_start, uint64_t guest_end) "%d %u: guest phys 0x%"PRIx64 " - 0x%" PRIx64
qxl_post_load(int qid, const char *mode) "%d %s"
+qxl_post_load_stale_cmd(int qid, const char *kind, uint64_t data) "%d skip stale %s cmd 0x%"PRIx64
qxl_pre_load(int qid) "%d"
qxl_pre_save(int qid) "%d"
qxl_reset_surfaces(int qid) "%d"
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 09/14] ui/gtk: Remove glFlush() after eglSwapBuffers()
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (7 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 08/14] hw/display/qxl: trace skipped stale loadvm commands Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 10/14] ui/gtk: Work around the gtk-menu-bar-accel leak Marc-André Lureau
` (5 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
From: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
It is redundant since eglSwapBuffers() implicitly performs glFlush().
Message-ID: <20260913-flush-v1-1-229efa3f1e53@rsg.ci.i.u-tokyo.ac.jp>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
ui/gtk-egl.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
index d595916476d9..0eec06826bdd 100644
--- a/ui/gtk-egl.c
+++ b/ui/gtk-egl.c
@@ -104,7 +104,6 @@ void gd_egl_draw(VirtualConsole *vc)
surface_width(vc->gfx.ds),
surface_height(vc->gfx.ds));
- glFlush();
#ifdef CONFIG_GBM
if (dmabuf) {
gd_gl_wait_sync(vc, sync);
@@ -122,8 +121,6 @@ void gd_egl_draw(VirtualConsole *vc)
gd_update_scale(vc, ww, wh,
surface_width(vc->gfx.ds),
surface_height(vc->gfx.ds));
-
- glFlush();
}
}
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 10/14] ui/gtk: Work around the gtk-menu-bar-accel leak
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (8 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 09/14] ui/gtk: Remove glFlush() after eglSwapBuffers() Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 11/14] vhost-user-gpu: validate command buffer size in submit_3d Marc-André Lureau
` (4 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
From: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
The implementation of the gtk-menu-bar-accel property had a bug that
leaks memory when the set value is an empty string, which was fixed
with:
https://gitlab.gnome.org/GNOME/gtk/-/commit/44bf10c4a2a0463891884a105fa27cf36b73f119
To work around the issue for old GTK versions, replace the empty string
with NULL, which has the same meaning for the property.
Message-ID: <20260913-gtk-v1-1-3e4ac542e054@rsg.ci.i.u-tokyo.ac.jp>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
---
ui/gtk.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ui/gtk.c b/ui/gtk.c
index c615d35451b6..ae28f1fbb89b 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -2645,7 +2645,7 @@ static void gd_create_menus(GtkDisplayState *s, DisplayOptions *opts)
/* Disable the default "F10" menu shortcut. */
settings = gtk_widget_get_settings(s->window);
- g_object_set(G_OBJECT(settings), "gtk-menu-bar-accel", "", NULL);
+ g_object_set(G_OBJECT(settings), "gtk-menu-bar-accel", NULL, NULL);
}
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 11/14] vhost-user-gpu: validate command buffer size in submit_3d
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (9 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 10/14] ui/gtk: Work around the gtk-menu-bar-accel leak Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 12/14] virtio-gpu-virgl: guard new_blob with VIRGL_VERSION_MAJORS>=1 Marc-André Lureau
` (3 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau, Michael S. Tsirkin, Stefano Garzarella
virgl_cmd_submit_3d() passes the guest-controlled cs.size directly to
g_malloc() without any bounds check. A malicious guest can set this
field to an arbitrarily large value (up to 4GB), causing an OOM abort
that crashes the vhost-user-gpu daemon.
Validate cs.size against the actual descriptor payload size before
allocating, rejecting values that exceed what the virtqueue entry
can carry.
Fixes: d52c454aadc ("contrib: add vhost-user-gpu")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3776
Reported-by: admin@fluentlogic.org
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Message-ID: <20260713125431.107278-1-marcandre.lureau@redhat.com>
Message-ID: <67f10fb88d3c75da3ba7fa5a37f7d6bcfcf3ce9e.1789071042.git.mst@redhat.com>
---
contrib/vhost-user-gpu/virgl.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/contrib/vhost-user-gpu/virgl.c b/contrib/vhost-user-gpu/virgl.c
index 5a5f9f14c80c..0ef4b9d8c903 100644
--- a/contrib/vhost-user-gpu/virgl.c
+++ b/contrib/vhost-user-gpu/virgl.c
@@ -209,20 +209,24 @@ virgl_cmd_submit_3d(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_cmd_submit cs;
+ size_t iov_len;
void *buf;
size_t s;
VUGPU_FILL_CMD(cs);
- if (cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) {
- g_critical("%s: command buffer too large (%u)",
- __func__, cs.size);
+ iov_len = iov_size(cmd->elem.out_sg, cmd->elem.out_num);
+ if (cs.size == 0 || iov_len < sizeof(cs) ||
+ cs.size > iov_len - sizeof(cs) ||
+ cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) {
+ g_critical("%s: size out of range (%u/%zu)",
+ __func__, cs.size, iov_len);
cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
return;
}
buf = g_try_malloc(cs.size);
- if (!buf && cs.size) {
+ if (!buf) {
cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY;
return;
}
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 12/14] virtio-gpu-virgl: guard new_blob with VIRGL_VERSION_MAJORS>=1
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (10 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 11/14] vhost-user-gpu: validate command buffer size in submit_3d Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 13/14] ui/gtk: Handle empty notebook state in menu handlers Marc-André Lureau
` (2 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel
Cc: Alex Bennée, Akihiko Odaki, Dmitry Osipenko,
Michael S. Tsirkin
virtio_gpu_virgl_resource_new_blob() is only called from
virgl_cmd_resource_create_blob(), which is guarded by
VIRGL_VERSION_MAJOR >= 1.
Fixes: d814b44636d0 ("hw/display/virtio-gpu: introduce virtio_gpu_{simple,virgl}_resource_new()")
Reviewed-by: Brian Cain <brian.cain@oss.qualcomm.com>
Reviewed-by: Michael Tokarev <mjt@tls.msk.ru>
Fixes: d814b44636d0 ("hw/display/virtio-gpu: introduce virtio_gpu_{simple,virgl}_resource_new()")
Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260911140645.56094-1-marcandre.lureau@redhat.com>
---
hw/display/virtio-gpu-virgl.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
index 1c9380e2a6d2..f45571060fea 100644
--- a/hw/display/virtio-gpu-virgl.c
+++ b/hw/display/virtio-gpu-virgl.c
@@ -324,6 +324,7 @@ virtio_gpu_virgl_resource_new(uint32_t resource_id, uint32_t width,
return res;
}
+#if VIRGL_VERSION_MAJOR >= 1
static struct virtio_gpu_virgl_resource *
virtio_gpu_virgl_resource_new_blob(uint32_t resource_id, uint64_t blob_size)
{
@@ -335,6 +336,7 @@ virtio_gpu_virgl_resource_new_blob(uint32_t resource_id, uint64_t blob_size)
return res;
}
+#endif
static void virgl_cmd_create_resource_2d(VirtIOGPU *g,
struct virtio_gpu_ctrl_command *cmd)
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 13/14] ui/gtk: Handle empty notebook state in menu handlers
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (11 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 12/14] virtio-gpu-virgl: guard new_blob with VIRGL_VERSION_MAJORS>=1 Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 14/14] ui/gtk: Clean up GL resources on tab detach, re-attach, and VC free Marc-André Lureau
2026-09-14 3:09 ` [GIT PULL 00/14] UI/display queue Richard Henderson
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
From: Dongwon Kim <dongwon.kim@intel.com>
When all virtual console tabs are detached (untabified) from the main
window, the notebook contains no active pages, causing
gtk_notebook_get_current_page() to return -1.
Because gtk_notebook_page_num() also returns -1 for any detached VC,
gd_vc_find_by_page(s, -1) mistakenly matches the first detached
console. As a result, gd_vc_find_current() incorrectly returns
a detached VC instead of NULL. Menu actions executed on the empty main
window then unintentionally operate on that detached VC.
Fix this by having gd_vc_find_current() explicitly check for page < 0
and return NULL when the notebook has no active page. In addition, add
NULL checks for the current VC across relevant UI menu callbacks so
actions are properly bypassed or reset when no console tab is focused
in the main window.
Cc: Daniel P. Berrangé <berrange@redhat.com>
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Dongwon Kim <dongwon.kim@intel.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260729214456.3350-1-dongwon.kim@intel.com>
---
ui/gtk.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 50 insertions(+), 6 deletions(-)
diff --git a/ui/gtk.c b/ui/gtk.c
index ae28f1fbb89b..a194488a0cf3 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -184,6 +184,11 @@ static VirtualConsole *gd_vc_find_current(GtkDisplayState *s)
gint page;
page = gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook));
+
+ if (page < 0) {
+ return NULL;
+ }
+
return gd_vc_find_by_page(s, page);
}
@@ -1469,7 +1474,10 @@ static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
} else {
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
}
- gd_update_windowsize(vc);
+
+ if (vc) {
+ gd_update_windowsize(vc);
+ }
}
static int gd_vc_notebook_pos(GtkDisplayState *s, VirtualConsole *target)
@@ -1542,6 +1550,10 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
GtkDisplayState *s = opaque;
VirtualConsole *vc = gd_vc_find_current(s);
+ if (!vc) {
+ return;
+ }
+
if (vc->type == GD_VC_GFX &&
qemu_console_is_graphic(vc->gfx.dcl.con)) {
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
@@ -1595,7 +1607,10 @@ static void gd_menu_show_menubar(GtkMenuItem *item, void *opaque)
} else {
gtk_widget_hide(s->menu_bar);
}
- gd_update_windowsize(vc);
+
+ if (vc) {
+ gd_update_windowsize(vc);
+ }
}
static void gd_accel_show_menubar(void *opaque)
@@ -1612,7 +1627,7 @@ static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
if (!s->full_screen) {
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
gtk_widget_hide(s->menu_bar);
- if (vc->type == GD_VC_GFX) {
+ if (vc && vc->type == GD_VC_GFX) {
gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
}
gtk_window_fullscreen(GTK_WINDOW(s->window));
@@ -1625,14 +1640,16 @@ static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
gtk_widget_show(s->menu_bar);
}
s->full_screen = FALSE;
- if (vc->type == GD_VC_GFX) {
+ if (vc && vc->type == GD_VC_GFX) {
vc->gfx.scale_x = vc->gfx.preferred_scale;
vc->gfx.scale_y = vc->gfx.preferred_scale;
gd_update_windowsize(vc);
}
}
- gd_update_cursor(vc);
+ if (vc) {
+ gd_update_cursor(vc);
+ }
}
static void gd_accel_full_screen(void *opaque)
@@ -1646,6 +1663,10 @@ static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
GtkDisplayState *s = opaque;
VirtualConsole *vc = gd_vc_find_current(s);
+ if (!vc) {
+ return;
+ }
+
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
FALSE);
@@ -1666,6 +1687,10 @@ static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
GtkDisplayState *s = opaque;
VirtualConsole *vc = gd_vc_find_current(s);
+ if (!vc) {
+ return;
+ }
+
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
FALSE);
@@ -1683,6 +1708,10 @@ static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
GtkDisplayState *s = opaque;
VirtualConsole *vc = gd_vc_find_current(s);
+ if (!vc) {
+ return;
+ }
+
vc->gfx.scale_x = vc->gfx.preferred_scale;
vc->gfx.scale_y = vc->gfx.preferred_scale;
@@ -1694,6 +1723,10 @@ static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
GtkDisplayState *s = opaque;
VirtualConsole *vc = gd_vc_find_current(s);
+ if (!vc) {
+ return;
+ }
+
if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
s->free_scale = TRUE;
} else {
@@ -1807,6 +1840,11 @@ static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
VirtualConsole *vc = gd_vc_find_current(s);
if (gd_is_grab_active(s)) {
+ if (!vc) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+ FALSE);
+ return;
+ }
gd_grab_keyboard(vc, "user-request-main-window");
gd_grab_pointer(vc, "user-request-main-window");
} else {
@@ -1814,7 +1852,9 @@ static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
gd_ungrab_pointer(s);
}
- gd_update_cursor(vc);
+ if (vc) {
+ gd_update_cursor(vc);
+ }
}
static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
@@ -1990,6 +2030,10 @@ static void gd_menu_copy(GtkMenuItem *item, void *opaque)
GtkDisplayState *s = opaque;
VirtualConsole *vc = gd_vc_find_current(s);
+ if (!vc) {
+ return;
+ }
+
#if VTE_CHECK_VERSION(0, 50, 0)
vte_terminal_copy_clipboard_format(VTE_TERMINAL(vc->vte.terminal),
VTE_FORMAT_TEXT);
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [GIT PULL 14/14] ui/gtk: Clean up GL resources on tab detach, re-attach, and VC free
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (12 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 13/14] ui/gtk: Handle empty notebook state in menu handlers Marc-André Lureau
@ 2026-09-13 10:41 ` Marc-André Lureau
2026-09-14 3:09 ` [GIT PULL 00/14] UI/display queue Richard Henderson
14 siblings, 0 replies; 17+ messages in thread
From: Marc-André Lureau @ 2026-09-13 10:41 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
From: Dongwon Kim <dongwon.kim@intel.com>
When a VC is detached into an independent window or re-attached back
to the main window via gd_tab_window_close(), its underlying EGL surface
and context are destroyed and recreated.
However, the associated FB objects (guest_fb, win_fb, cursor_fb),
display surface textures, and shader instances were not being cleaned up
during these transitions, leading to potential resource leaks.
Introduce a helper function, gd_gl_release_resources(), to make the
appropriate GL context current, delete the textures and framebuffers,
release the shader instance, and reset state pointers.
Use this helper in gd_tab_window_close(), gd_menu_untabify(), and refactor
gd_vc_free() to use it as well.
Cc: Daniel P. Berrangé <berrange@redhat.com>
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Dongwon Kim <dongwon.kim@intel.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260729134759.2877-1-dongwon.kim@intel.com>
---
ui/gtk.c | 67 ++++++++++++++++++++++++++++++++++++++++------------------------
1 file changed, 42 insertions(+), 25 deletions(-)
diff --git a/ui/gtk.c b/ui/gtk.c
index a194488a0cf3..ed7ffc06b154 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -1497,6 +1497,28 @@ static int gd_vc_notebook_pos(GtkDisplayState *s, VirtualConsole *target)
g_assert_not_reached();
}
+#if defined(CONFIG_OPENGL)
+static void gd_gl_release_resources(VirtualConsole *vc)
+{
+ if (vc->gfx.ectx) {
+ eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, vc->gfx.ectx);
+ } else if (gtk_use_gl_area) {
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+ }
+
+ if (vc->gfx.gls) {
+ surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
+ qemu_gl_fini_shader(vc->gfx.gls);
+ vc->gfx.gls = NULL;
+ }
+
+ egl_fb_destroy(&vc->gfx.guest_fb);
+ egl_fb_destroy(&vc->gfx.win_fb);
+ egl_fb_destroy(&vc->gfx.cursor_fb);
+}
+#endif
+
static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
void *opaque)
{
@@ -1513,13 +1535,17 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
gtk_widget_destroy(vc->window);
vc->window = NULL;
#if defined(CONFIG_OPENGL)
- if (vc->gfx.esurface) {
- eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
- vc->gfx.esurface = NULL;
- }
- if (vc->gfx.ectx) {
- eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
- vc->gfx.ectx = NULL;
+ if (vc->type == GD_VC_GFX) {
+ gd_gl_release_resources(vc);
+
+ if (vc->gfx.esurface) {
+ eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
+ vc->gfx.esurface = NULL;
+ }
+ if (vc->gfx.ectx) {
+ eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
+ vc->gfx.ectx = NULL;
+ }
}
#endif
@@ -1556,12 +1582,9 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
if (vc->type == GD_VC_GFX &&
qemu_console_is_graphic(vc->gfx.dcl.con)) {
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
- FALSE);
- }
- if (!vc->window) {
- vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
#if defined(CONFIG_OPENGL)
+ gd_gl_release_resources(vc);
+
if (vc->gfx.esurface) {
eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
vc->gfx.esurface = NULL;
@@ -1571,6 +1594,11 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
vc->gfx.ectx = NULL;
}
#endif
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+ FALSE);
+ }
+ if (!vc->window) {
+ vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gd_widget_reparent(s->notebook, vc->window, vc->tab_item);
g_signal_connect(vc->window, "delete-event",
@@ -2707,19 +2735,8 @@ static void gd_vc_free(void *p)
if (display_opengl) {
qemu_console_set_display_gl_ctx(vc->gfx.dcl.con, NULL);
}
- if (vc->gfx.ectx) {
- eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
- vc->gfx.esurface, vc->gfx.ectx);
- } else if (gtk_use_gl_area) {
- gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
- }
- 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);
+ gd_gl_release_resources(vc);
+
if (vc->gfx.esurface) {
eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
}
--
2.55.0.543.g5ebe2ebe4ea8
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [GIT PULL 00/14] UI/display queue
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
` (13 preceding siblings ...)
2026-09-13 10:41 ` [GIT PULL 14/14] ui/gtk: Clean up GL resources on tab detach, re-attach, and VC free Marc-André Lureau
@ 2026-09-14 3:09 ` Richard Henderson
14 siblings, 0 replies; 17+ messages in thread
From: Richard Henderson @ 2026-09-14 3:09 UTC (permalink / raw)
To: Marc-André Lureau, qemu-devel
On 9/13/26 00:41, Marc-André Lureau wrote:
> The following changes since commit d43c2d5f89db70359a7b3a7e2ad7098fcc0165ef:
>
> Merge tag 'for_upstream' ofhttps://git.kernel.org/pub/scm/virt/kvm/mst/qemu into staging (2026-09-11 12:04:42 -1000)
>
> are available in the Git repository at:
>
> https://gitlab.com/marcandre.lureau/qemu.git tags/ui-pr-v1
>
> for you to fetch changes up to 78809254f367cec04d68afd4c90115d9cc15a2e4:
>
> ui/gtk: Clean up GL resources on tab detach, re-attach, and VC free (2026-09-13 14:41:12 +0400)
>
> ----------------------------------------------------------------
> UI/display queue
>
> - cursor data races fixes
> - qxl migration harderning
> - add org.qemu.Display1.UIInfo
> - gtk fixes
>
> Signed-off-by: Marc-André Lureau<marcandre.lureau@redhat.com>
Applied, thanks.
r~
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [GIT PULL 04/14] docs/sphinx/dbus: register build dependency
2026-09-13 10:41 ` [GIT PULL 04/14] docs/sphinx/dbus: register build dependency Marc-André Lureau
@ 2026-09-14 5:45 ` Mauro Carvalho Chehab
0 siblings, 0 replies; 17+ messages in thread
From: Mauro Carvalho Chehab @ 2026-09-14 5:45 UTC (permalink / raw)
To: Marc-André Lureau
Cc: qemu-devel, John Snow, Peter Maydell, Pierrick Bouvier
On Sun, 13 Sep 2026 14:41:22 +0400
Marc-André Lureau <marcandre.lureau@redhat.com> wrote:
> Touching dbus xml file wasn't enough to trigger a new build because
> the file wasn't registered to the dependency system.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Makes sense to me.
Reviewed-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
> ---
> docs/sphinx/dbusdoc.py | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/docs/sphinx/dbusdoc.py b/docs/sphinx/dbusdoc.py
> index be284ed08fd7..2b086e2f583c 100644
> --- a/docs/sphinx/dbusdoc.py
> +++ b/docs/sphinx/dbusdoc.py
> @@ -146,6 +146,7 @@ def run(self):
>
> env = self.state.document.settings.env
> dbusfile = env.config.qapidoc_srctree + "/" + self.arguments[0]
> + env.note_dependency(os.path.abspath(dbusfile))
> with open(dbusfile, "rb") as f:
> xml_data = f.read()
> xml = parse_dbus_xml(xml_data)
>
Thanks,
Mauro
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-09-14 5:45 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-13 10:41 [GIT PULL 00/14] UI/display queue Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 01/14] ui/dbus: fix cursor race, copy cursor data Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 02/14] hw/display/qxl: hold ssd.lock while replacing ssd.cursor Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 03/14] ui/cursor: make the cursor refcount atomic Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 04/14] docs/sphinx/dbus: register build dependency Marc-André Lureau
2026-09-14 5:45 ` Mauro Carvalho Chehab
2026-09-13 10:41 ` [GIT PULL 05/14] ui/dbus: add org.qemu.Display1.UIInfo interface Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 06/14] hw/display/qxl: factor out qxl_guest_phys2virt() Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 07/14] hw/display/qxl: validate replayed commands in qxl_post_load Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 08/14] hw/display/qxl: trace skipped stale loadvm commands Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 09/14] ui/gtk: Remove glFlush() after eglSwapBuffers() Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 10/14] ui/gtk: Work around the gtk-menu-bar-accel leak Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 11/14] vhost-user-gpu: validate command buffer size in submit_3d Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 12/14] virtio-gpu-virgl: guard new_blob with VIRGL_VERSION_MAJORS>=1 Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 13/14] ui/gtk: Handle empty notebook state in menu handlers Marc-André Lureau
2026-09-13 10:41 ` [GIT PULL 14/14] ui/gtk: Clean up GL resources on tab detach, re-attach, and VC free Marc-André Lureau
2026-09-14 3:09 ` [GIT PULL 00/14] UI/display queue Richard Henderson
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.