Commit a0e5242e1c for qemu.org

commit a0e5242e1c670b368e9639bf265e22e11c0c65fe
Author: Marc-André Lureau <marcandre.lureau@redhat.com>
Date:   Mon May 25 12:19:44 2026 +0400

    vga: implement text mode character blink

    When bit 3 of the VGA Attribute Mode Control register is set, attribute
    bit 7 switches from selecting bright background colors to enabling
    character blink.

    Implement this by tracking a separate blink phase timer that toggles
    every 32 frames (matching real VGA hardware frame counter bit 5 @60hz),
    and rendering blinking characters by replacing their foreground with
    background during the off phase.

    As with cursor, no VMState migration of the fields, as they are
    transient display-side states.

    Related to: https://gitlab.com/qemu-project/qemu/-/work_items/1585
    Acked-by: Gerd Hoffmann <kraxel@redhat.com>
    Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
    Message-ID: <20260525081944.1494798-1-marcandre.lureau@redhat.com>

diff --git a/hw/display/vga.c b/hw/display/vga.c
index 3ea20d7b0f..abe3f8e077 100644
--- a/hw/display/vga.c
+++ b/hw/display/vga.c
@@ -45,8 +45,10 @@

 bool have_vga = true;

-/* 16 state changes per vertical frame @60 Hz */
+/* frame counter bit 4: cursor blink toggles every 16 frames @60 Hz */
 #define VGA_TEXT_CURSOR_PERIOD_MS       (1000 * 2 * 16 / 60)
+/* frame counter bit 5: character blink toggles every 32 frames @60 Hz */
+#define VGA_TEXT_BLINK_PERIOD_MS        (1000 * 2 * 32 / 60)

 /* Address mask for non-VESA modes.  */
 #define VGA_VRAM_SIZE                   (256 * KiB)
@@ -1190,7 +1192,6 @@ static void vga_get_text_resolution(VGACommonState *s, int *pwidth, int *pheight
  * - double scan
  * - double width
  * - underline
- * - flashing
  */
 static void vga_draw_text(VGACommonState *s, int full_update)
 {
@@ -1286,6 +1287,13 @@ static void vga_draw_text(VGACommonState *s, int full_update)
         s->cursor_blink_time = now + VGA_TEXT_CURSOR_PERIOD_MS / 2;
         s->cursor_visible_phase = !s->cursor_visible_phase;
     }
+    if (now >= s->blink_time) {
+        s->blink_time = now + VGA_TEXT_BLINK_PERIOD_MS / 2;
+        s->blink_visible_phase = !s->blink_visible_phase;
+        if (s->ar[VGA_ATC_MODE] & 0x08) {
+            full_update = 1;
+        }
+    }

     dest = surface_data(surface);
     linesize = surface_stride(surface);
@@ -1317,8 +1325,17 @@ static void vga_draw_text(VGACommonState *s, int full_update)
 #endif
                 font_ptr = font_base[(cattr >> 3) & 1];
                 font_ptr += 32 * 4 * ch;
-                bgcol = palette[cattr >> 4];
-                fgcol = palette[cattr & 0x0f];
+                if (s->ar[VGA_ATC_MODE] & 0x08) {
+                    bgcol = palette[(cattr >> 4) & 0x07];
+                    if ((cattr & 0x80) && !s->blink_visible_phase) {
+                        fgcol = bgcol;
+                    } else {
+                        fgcol = palette[cattr & 0x0f];
+                    }
+                } else {
+                    bgcol = palette[cattr >> 4];
+                    fgcol = palette[cattr & 0x0f];
+                }
                 if (cw == 16) {
                     vga_draw_glyph16(d1, linesize,
                                      font_ptr, cheight, fgcol, bgcol);
diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h
index 747b5cc6cf..5664317ecd 100644
--- a/hw/display/vga_int.h
+++ b/hw/display/vga_int.h
@@ -130,6 +130,8 @@ typedef struct VGACommonState {
     uint8_t cursor_start, cursor_end;
     bool cursor_visible_phase;
     int64_t cursor_blink_time;
+    bool blink_visible_phase;
+    int64_t blink_time;
     uint32_t cursor_offset;
     const GraphicHwOps *hw_ops;
     bool full_update_text;