Commit e562be56cc for qemu.org

commit e562be56cc589b7fbabde1d4631869b2be8fdd14
Author: Christian Quante <christian@quante.one>
Date:   Thu Oct 22 15:43:33 2026 +0200

    tests/qtest/dbus-display-test: check the Scanout after a text resize

    Set up an 80x25 VGA text mode, change the character height from 16 to
    12 lines as the Windows NT 4.0 setup does, and check that the first
    message the listener gets after the change is a Scanout of the new size
    (720x396), not an Update.

    The registration of the listener moves from test_dbus_display_console()
    to test_dbus_console_register(), so that both tests can use it, and the
    listener now also handles Update.

    The test waits until no message has come for some time, so the cursor
    is put outside the screen: vga_draw_text() redraws the character under
    the cursor at every refresh, also when the cursor is off.

    Signed-off-by: Christian Quante <christian@quante.one>
    Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
    Message-ID: <20261022134335.12241-3-christian@quante.one>

diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c
index 7838ce7323..eb81b9ca05 100644
--- a/tests/qtest/dbus-display-test.c
+++ b/tests/qtest/dbus-display-test.c
@@ -94,8 +94,26 @@ typedef struct TestDBusConsoleRegister {
     GDBusConnection *listener_conn;
     GDBusObjectManagerServer *server;
     bool with_map;
+    /* Scanout and Update calls received, and the first one after @watch */
+    guint messages;
+    bool watch;
+    const char *first;
+    guint first_width;
+    guint first_height;
 } TestDBusConsoleRegister;

+static void test_dbus_console_seen(TestDBusConsoleRegister *test,
+                                   const char *method,
+                                   guint width, guint height)
+{
+    test->messages++;
+    if (test->watch && !test->first) {
+        test->first = method;
+        test->first_width = width;
+        test->first_height = height;
+    }
+}
+
 static gboolean listener_handle_scanout(
     QemuDBusDisplay1Listener *object,
     GDBusMethodInvocation *invocation,
@@ -106,6 +124,7 @@ static gboolean listener_handle_scanout(
     GVariant *arg_data,
     TestDBusConsoleRegister *test)
 {
+    test_dbus_console_seen(test, "Scanout", arg_width, arg_height);
     qemu_dbus_display1_listener_complete_scanout(object, invocation);

     if (!test->with_map) {
@@ -115,6 +134,24 @@ static gboolean listener_handle_scanout(
     return DBUS_METHOD_INVOCATION_HANDLED;
 }

+static gboolean listener_handle_update(
+    QemuDBusDisplay1Listener *object,
+    GDBusMethodInvocation *invocation,
+    gint arg_x,
+    gint arg_y,
+    gint arg_width,
+    gint arg_height,
+    guint arg_stride,
+    guint arg_pixman_format,
+    GVariant *arg_data,
+    TestDBusConsoleRegister *test)
+{
+    test_dbus_console_seen(test, "Update", arg_width, arg_height);
+    qemu_dbus_display1_listener_complete_update(object, invocation);
+
+    return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
 #ifndef WIN32
 static gboolean listener_handle_scanout_map(
     QemuDBusDisplay1ListenerUnixMap *object,
@@ -164,6 +201,7 @@ test_dbus_console_setup_listener(TestDBusConsoleRegister *test, bool with_map)
         qemu_dbus_display1_listener_skeleton_new());
     g_object_connect(iface,
                      "signal::handle-scanout", listener_handle_scanout, test,
+                     "signal::handle-update", listener_handle_update, test,
                      NULL);
     g_dbus_object_skeleton_add_interface(listener,
                                          G_DBUS_INTERFACE_SKELETON(iface));
@@ -227,15 +265,12 @@ test_dbus_p2p_server_setup_thread(gpointer data)
 }

 static void
-test_dbus_display_console(const void* data)
+test_dbus_console_register(QTestState *qts, GDBusConnection *conn,
+                           TestDBusConsoleRegister *test)
 {
     g_autoptr(GError) err = NULL;
-    g_autoptr(GDBusConnection) conn = NULL;
     g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL;
-    g_autoptr(GMainLoop) loop = NULL;
-    QTestState *qts = NULL;
     int pair[2];
-    TestDBusConsoleRegister test = { 0, .with_map = GPOINTER_TO_INT(data) };
 #ifdef WIN32
     WSAPROTOCOL_INFOW info;
     g_autoptr(GVariant) listener = NULL;
@@ -244,8 +279,6 @@ test_dbus_display_console(const void* data)
     int idx;
 #endif

-    test_setup(&qts, &conn);
-
     g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
 #ifndef WIN32
     fd_list = g_unix_fd_list_new();
@@ -262,9 +295,9 @@ test_dbus_display_console(const void* data)
             &err));
     g_assert_no_error(err);

-    test.loop = loop = g_main_loop_new(NULL, FALSE);
-    test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread,
-                               GINT_TO_POINTER(pair[0]));
+    test->loop = g_main_loop_new(NULL, FALSE);
+    test->thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread,
+                                GINT_TO_POINTER(pair[0]));

 #ifdef WIN32
     if (WSADuplicateSocketW(_get_osfhandle(pair[1]),
@@ -295,12 +328,111 @@ test_dbus_display_console(const void* data)
 #endif
         NULL,
         test_dbus_console_registered,
-        &test);
+        test);
+
+    g_main_loop_run(test->loop);
+}
+
+static void
+test_dbus_console_cleanup(TestDBusConsoleRegister *test)
+{
+    g_clear_object(&test->server);
+    g_clear_object(&test->listener_conn);
+    g_clear_pointer(&test->loop, g_main_loop_unref);
+}
+
+static void
+test_dbus_display_console(const void *data)
+{
+    g_autoptr(GDBusConnection) conn = NULL;
+    QTestState *qts = NULL;
+    TestDBusConsoleRegister test = { 0, .with_map = GPOINTER_TO_INT(data) };

-    g_main_loop_run(loop);
+    test_setup(&qts, &conn);
+    test_dbus_console_register(qts, conn, &test);
+
+    test_dbus_console_cleanup(&test);
+    g_clear_object(&conn);
+    qtest_quit(qts);
+}
+
+/* Handle whatever reaches the listener during the next @ms milliseconds. */
+static void
+test_dbus_console_turn(guint ms)
+{
+    gint64 end = g_get_monotonic_time() + ms * G_TIME_SPAN_MILLISECOND;
+
+    while (g_get_monotonic_time() < end) {
+        while (g_main_context_iteration(NULL, FALSE)) {
+        }
+        g_usleep(G_TIME_SPAN_MILLISECOND);
+    }
+}
+
+/* Wait until the picture is still: no Scanout or Update for 200 ms. */
+static void
+test_dbus_console_settle(TestDBusConsoleRegister *test)
+{
+    guint seen;
+    int i;
+
+    for (i = 0; i < 25; i++) {
+        seen = test->messages;
+        test_dbus_console_turn(200);
+        if (test->messages == seen) {
+            return;
+        }
+    }
+    g_assert_not_reached();
+}
+
+static void
+vga_crtc_write(QTestState *qts, uint8_t index, uint8_t value)
+{
+    qtest_outb(qts, 0x3d4, index);
+    qtest_outb(qts, 0x3d5, value);
+}
+
+static void
+test_dbus_display_text_resize(void)
+{
+    g_autoptr(GDBusConnection) conn = NULL;
+    QTestState *qts = NULL;
+    TestDBusConsoleRegister test = { 0 };
+    gint64 end;
+
+    test_setup(&qts, &conn);
+    test_dbus_console_register(qts, conn, &test);
+
+    /* VGA text mode, 80x25 cells of 9x16 pixels: 720x400 */
+    qtest_outb(qts, 0x3c2, 0x67);       /* CRTC at 0x3d4 */
+    vga_crtc_write(qts, 0x01, 0x4f);    /* 80 columns */
+    vga_crtc_write(qts, 0x07, 0x02);    /* bit 8 of the vertical display end */
+    vga_crtc_write(qts, 0x09, 0x0f);    /* 16 scan lines per cell */
+    vga_crtc_write(qts, 0x0e, 0x3f);    /* cursor outside the screen */
+    vga_crtc_write(qts, 0x0f, 0xff);
+    vga_crtc_write(qts, 0x12, 0x8f);    /* 400 scan lines */
+    vga_crtc_write(qts, 0x13, 0x28);    /* 80 characters per row */
+    qtest_inb(qts, 0x3da);              /* attribute controller to index */
+    qtest_outb(qts, 0x3c0, 0x20);       /* display on */
+    test_dbus_console_settle(&test);
+
+    /*
+     * Cells of 9x12 pixels, as the Windows NT 4.0 setup sets: 720x396.
+     * VGA redraws a text mode one row of cells at a time, and the client
+     * has to know the new size before the first row arrives.
+     */
+    test.watch = true;
+    vga_crtc_write(qts, 0x09, 0x0b);
+    end = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+    while (!test.first && g_get_monotonic_time() < end) {
+        test_dbus_console_turn(10);
+    }
+    g_assert_cmpstr(test.first, ==, "Scanout");
+    g_assert_cmpuint(test.first_width, ==, 720);
+    g_assert_cmpuint(test.first_height, ==, 396);

-    g_clear_object(&test.server);
-    g_clear_object(&test.listener_conn);
+    test_dbus_console_cleanup(&test);
     g_clear_object(&conn);
     qtest_quit(qts);
 }
@@ -462,6 +594,8 @@ main(int argc, char **argv)
     qtest_add_func("/dbus-display/vm", test_dbus_display_vm);
     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/console/text-resize",
+                   test_dbus_display_text_resize);
     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);