qemu/ui/console-vc.c
<<
>>
Prefs
   1/*
   2 * SPDX-License-Identifier: MIT
   3 * QEMU VC
   4 */
   5#include "qemu/osdep.h"
   6
   7#include "chardev/char.h"
   8#include "qapi/error.h"
   9#include "qemu/fifo8.h"
  10#include "qemu/option.h"
  11#include "ui/console.h"
  12
  13#include "trace.h"
  14#include "console-priv.h"
  15
  16#define DEFAULT_BACKSCROLL 512
  17#define CONSOLE_CURSOR_PERIOD 500
  18
  19typedef struct TextAttributes {
  20    uint8_t fgcol:4;
  21    uint8_t bgcol:4;
  22    uint8_t bold:1;
  23    uint8_t uline:1;
  24    uint8_t blink:1;
  25    uint8_t invers:1;
  26    uint8_t unvisible:1;
  27} TextAttributes;
  28
  29#define TEXT_ATTRIBUTES_DEFAULT ((TextAttributes) { \
  30    .fgcol = QEMU_COLOR_WHITE,                      \
  31    .bgcol = QEMU_COLOR_BLACK                       \
  32})
  33
  34typedef struct TextCell {
  35    uint8_t ch;
  36    TextAttributes t_attrib;
  37} TextCell;
  38
  39#define MAX_ESC_PARAMS 3
  40
  41enum TTYState {
  42    TTY_STATE_NORM,
  43    TTY_STATE_ESC,
  44    TTY_STATE_CSI,
  45    TTY_STATE_G0,
  46    TTY_STATE_G1,
  47};
  48
  49typedef struct QemuTextConsole {
  50    QemuConsole parent;
  51
  52    int width;
  53    int height;
  54    int total_height;
  55    int backscroll_height;
  56    int x, y;
  57    int y_displayed;
  58    int y_base;
  59    TextCell *cells;
  60    int text_x[2], text_y[2], cursor_invalidate;
  61    int echo;
  62
  63    int update_x0;
  64    int update_y0;
  65    int update_x1;
  66    int update_y1;
  67
  68    Chardev *chr;
  69    /* fifo for key pressed */
  70    Fifo8 out_fifo;
  71} QemuTextConsole;
  72
  73typedef QemuConsoleClass QemuTextConsoleClass;
  74
  75OBJECT_DEFINE_TYPE(QemuTextConsole, qemu_text_console, QEMU_TEXT_CONSOLE, QEMU_CONSOLE)
  76
  77typedef struct QemuFixedTextConsole {
  78    QemuTextConsole parent;
  79} QemuFixedTextConsole;
  80
  81typedef QemuTextConsoleClass QemuFixedTextConsoleClass;
  82
  83OBJECT_DEFINE_TYPE(QemuFixedTextConsole, qemu_fixed_text_console, QEMU_FIXED_TEXT_CONSOLE, QEMU_TEXT_CONSOLE)
  84
  85struct VCChardev {
  86    Chardev parent;
  87    QemuTextConsole *console;
  88
  89    enum TTYState state;
  90    int esc_params[MAX_ESC_PARAMS];
  91    int nb_esc_params;
  92    TextAttributes t_attrib; /* currently active text attributes */
  93    TextAttributes t_attrib_saved;
  94    int x_saved, y_saved;
  95};
  96typedef struct VCChardev VCChardev;
  97
  98static const pixman_color_t color_table_rgb[2][8] = {
  99    {   /* dark */
 100        [QEMU_COLOR_BLACK]   = QEMU_PIXMAN_COLOR_BLACK,
 101        [QEMU_COLOR_BLUE]    = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xaa),  /* blue */
 102        [QEMU_COLOR_GREEN]   = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0x00),  /* green */
 103        [QEMU_COLOR_CYAN]    = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0xaa),  /* cyan */
 104        [QEMU_COLOR_RED]     = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0x00),  /* red */
 105        [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0xaa),  /* magenta */
 106        [QEMU_COLOR_YELLOW]  = QEMU_PIXMAN_COLOR(0xaa, 0xaa, 0x00),  /* yellow */
 107        [QEMU_COLOR_WHITE]   = QEMU_PIXMAN_COLOR_GRAY,
 108    },
 109    {   /* bright */
 110        [QEMU_COLOR_BLACK]   = QEMU_PIXMAN_COLOR_BLACK,
 111        [QEMU_COLOR_BLUE]    = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xff),  /* blue */
 112        [QEMU_COLOR_GREEN]   = QEMU_PIXMAN_COLOR(0x00, 0xff, 0x00),  /* green */
 113        [QEMU_COLOR_CYAN]    = QEMU_PIXMAN_COLOR(0x00, 0xff, 0xff),  /* cyan */
 114        [QEMU_COLOR_RED]     = QEMU_PIXMAN_COLOR(0xff, 0x00, 0x00),  /* red */
 115        [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xff, 0x00, 0xff),  /* magenta */
 116        [QEMU_COLOR_YELLOW]  = QEMU_PIXMAN_COLOR(0xff, 0xff, 0x00),  /* yellow */
 117        [QEMU_COLOR_WHITE]   = QEMU_PIXMAN_COLOR(0xff, 0xff, 0xff),  /* white */
 118    }
 119};
 120
 121static bool cursor_visible_phase;
 122static QEMUTimer *cursor_timer;
 123
 124const char *
 125qemu_text_console_get_label(QemuTextConsole *c)
 126{
 127    return c->chr ? c->chr->label : NULL;
 128}
 129
 130static void qemu_console_fill_rect(QemuConsole *con, int posx, int posy,
 131                                   int width, int height, pixman_color_t color)
 132{
 133    DisplaySurface *surface = qemu_console_surface(con);
 134    pixman_rectangle16_t rect = {
 135        .x = posx, .y = posy, .width = width, .height = height
 136    };
 137
 138    assert(surface);
 139    pixman_image_fill_rectangles(PIXMAN_OP_SRC, surface->image,
 140                                 &color, 1, &rect);
 141}
 142
 143/* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */
 144static void qemu_console_bitblt(QemuConsole *con,
 145                                int xs, int ys, int xd, int yd, int w, int h)
 146{
 147    DisplaySurface *surface = qemu_console_surface(con);
 148
 149    assert(surface);
 150    pixman_image_composite(PIXMAN_OP_SRC,
 151                           surface->image, NULL, surface->image,
 152                           xs, ys, 0, 0, xd, yd, w, h);
 153}
 154
 155static void vga_putcharxy(QemuConsole *s, int x, int y, int ch,
 156                          TextAttributes *t_attrib)
 157{
 158    static pixman_image_t *glyphs[256];
 159    DisplaySurface *surface = qemu_console_surface(s);
 160    pixman_color_t fgcol, bgcol;
 161
 162    assert(surface);
 163    if (t_attrib->invers) {
 164        bgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
 165        fgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
 166    } else {
 167        fgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
 168        bgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
 169    }
 170
 171    if (!glyphs[ch]) {
 172        glyphs[ch] = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, ch);
 173    }
 174    qemu_pixman_glyph_render(glyphs[ch], surface->image,
 175                             &fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT);
 176}
 177
 178static void invalidate_xy(QemuTextConsole *s, int x, int y)
 179{
 180    if (!qemu_console_is_visible(QEMU_CONSOLE(s))) {
 181        return;
 182    }
 183    if (s->update_x0 > x * FONT_WIDTH)
 184        s->update_x0 = x * FONT_WIDTH;
 185    if (s->update_y0 > y * FONT_HEIGHT)
 186        s->update_y0 = y * FONT_HEIGHT;
 187    if (s->update_x1 < (x + 1) * FONT_WIDTH)
 188        s->update_x1 = (x + 1) * FONT_WIDTH;
 189    if (s->update_y1 < (y + 1) * FONT_HEIGHT)
 190        s->update_y1 = (y + 1) * FONT_HEIGHT;
 191}
 192
 193static void console_show_cursor(QemuTextConsole *s, int show)
 194{
 195    TextCell *c;
 196    int y, y1;
 197    int x = s->x;
 198
 199    s->cursor_invalidate = 1;
 200
 201    if (x >= s->width) {
 202        x = s->width - 1;
 203    }
 204    y1 = (s->y_base + s->y) % s->total_height;
 205    y = y1 - s->y_displayed;
 206    if (y < 0) {
 207        y += s->total_height;
 208    }
 209    if (y < s->height) {
 210        c = &s->cells[y1 * s->width + x];
 211        if (show && cursor_visible_phase) {
 212            TextAttributes t_attrib = TEXT_ATTRIBUTES_DEFAULT;
 213            t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */
 214            vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &t_attrib);
 215        } else {
 216            vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &(c->t_attrib));
 217        }
 218        invalidate_xy(s, x, y);
 219    }
 220}
 221
 222static void console_refresh(QemuTextConsole *s)
 223{
 224    DisplaySurface *surface = qemu_console_surface(QEMU_CONSOLE(s));
 225    TextCell *c;
 226    int x, y, y1;
 227
 228    assert(surface);
 229    s->text_x[0] = 0;
 230    s->text_y[0] = 0;
 231    s->text_x[1] = s->width - 1;
 232    s->text_y[1] = s->height - 1;
 233    s->cursor_invalidate = 1;
 234
 235    qemu_console_fill_rect(QEMU_CONSOLE(s), 0, 0, surface_width(surface), surface_height(surface),
 236                           color_table_rgb[0][QEMU_COLOR_BLACK]);
 237    y1 = s->y_displayed;
 238    for (y = 0; y < s->height; y++) {
 239        c = s->cells + y1 * s->width;
 240        for (x = 0; x < s->width; x++) {
 241            vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch,
 242                          &(c->t_attrib));
 243            c++;
 244        }
 245        if (++y1 == s->total_height) {
 246            y1 = 0;
 247        }
 248    }
 249    console_show_cursor(s, 1);
 250    dpy_gfx_update(QEMU_CONSOLE(s), 0, 0,
 251                   surface_width(surface), surface_height(surface));
 252}
 253
 254static void console_scroll(QemuTextConsole *s, int ydelta)
 255{
 256    int i, y1;
 257
 258    if (ydelta > 0) {
 259        for(i = 0; i < ydelta; i++) {
 260            if (s->y_displayed == s->y_base)
 261                break;
 262            if (++s->y_displayed == s->total_height)
 263                s->y_displayed = 0;
 264        }
 265    } else {
 266        ydelta = -ydelta;
 267        i = s->backscroll_height;
 268        if (i > s->total_height - s->height)
 269            i = s->total_height - s->height;
 270        y1 = s->y_base - i;
 271        if (y1 < 0)
 272            y1 += s->total_height;
 273        for(i = 0; i < ydelta; i++) {
 274            if (s->y_displayed == y1)
 275                break;
 276            if (--s->y_displayed < 0)
 277                s->y_displayed = s->total_height - 1;
 278        }
 279    }
 280    console_refresh(s);
 281}
 282
 283static void kbd_send_chars(QemuTextConsole *s)
 284{
 285    uint32_t len, avail;
 286
 287    len = qemu_chr_be_can_write(s->chr);
 288    avail = fifo8_num_used(&s->out_fifo);
 289    while (len > 0 && avail > 0) {
 290        const uint8_t *buf;
 291        uint32_t size;
 292
 293        buf = fifo8_pop_bufptr(&s->out_fifo, MIN(len, avail), &size);
 294        qemu_chr_be_write(s->chr, buf, size);
 295        len = qemu_chr_be_can_write(s->chr);
 296        avail -= size;
 297    }
 298}
 299
 300/* called when an ascii key is pressed */
 301void qemu_text_console_handle_keysym(QemuTextConsole *s, int keysym)
 302{
 303    uint8_t buf[16], *q;
 304    int c;
 305    uint32_t num_free;
 306
 307    switch(keysym) {
 308    case QEMU_KEY_CTRL_UP:
 309        console_scroll(s, -1);
 310        break;
 311    case QEMU_KEY_CTRL_DOWN:
 312        console_scroll(s, 1);
 313        break;
 314    case QEMU_KEY_CTRL_PAGEUP:
 315        console_scroll(s, -10);
 316        break;
 317    case QEMU_KEY_CTRL_PAGEDOWN:
 318        console_scroll(s, 10);
 319        break;
 320    default:
 321        /* convert the QEMU keysym to VT100 key string */
 322        q = buf;
 323        if (keysym >= 0xe100 && keysym <= 0xe11f) {
 324            *q++ = '\033';
 325            *q++ = '[';
 326            c = keysym - 0xe100;
 327            if (c >= 10)
 328                *q++ = '0' + (c / 10);
 329            *q++ = '0' + (c % 10);
 330            *q++ = '~';
 331        } else if (keysym >= 0xe120 && keysym <= 0xe17f) {
 332            *q++ = '\033';
 333            *q++ = '[';
 334            *q++ = keysym & 0xff;
 335        } else if (s->echo && (keysym == '\r' || keysym == '\n')) {
 336            qemu_chr_write(s->chr, (uint8_t *)"\r", 1, true);
 337            *q++ = '\n';
 338        } else {
 339            *q++ = keysym;
 340        }
 341        if (s->echo) {
 342            qemu_chr_write(s->chr, buf, q - buf, true);
 343        }
 344        num_free = fifo8_num_free(&s->out_fifo);
 345        fifo8_push_all(&s->out_fifo, buf, MIN(num_free, q - buf));
 346        kbd_send_chars(s);
 347        break;
 348    }
 349}
 350
 351static void text_console_update(void *opaque, console_ch_t *chardata)
 352{
 353    QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque);
 354    int i, j, src;
 355
 356    if (s->text_x[0] <= s->text_x[1]) {
 357        src = (s->y_base + s->text_y[0]) * s->width;
 358        chardata += s->text_y[0] * s->width;
 359        for (i = s->text_y[0]; i <= s->text_y[1]; i ++)
 360            for (j = 0; j < s->width; j++, src++) {
 361                console_write_ch(chardata ++,
 362                                 ATTR2CHTYPE(s->cells[src].ch,
 363                                             s->cells[src].t_attrib.fgcol,
 364                                             s->cells[src].t_attrib.bgcol,
 365                                             s->cells[src].t_attrib.bold));
 366            }
 367        dpy_text_update(QEMU_CONSOLE(s), s->text_x[0], s->text_y[0],
 368                        s->text_x[1] - s->text_x[0], i - s->text_y[0]);
 369        s->text_x[0] = s->width;
 370        s->text_y[0] = s->height;
 371        s->text_x[1] = 0;
 372        s->text_y[1] = 0;
 373    }
 374    if (s->cursor_invalidate) {
 375        dpy_text_cursor(QEMU_CONSOLE(s), s->x, s->y);
 376        s->cursor_invalidate = 0;
 377    }
 378}
 379
 380static void text_console_resize(QemuTextConsole *t)
 381{
 382    QemuConsole *s = QEMU_CONSOLE(t);
 383    TextCell *cells, *c, *c1;
 384    int w1, x, y, last_width, w, h;
 385
 386    assert(s->scanout.kind == SCANOUT_SURFACE);
 387
 388    w = surface_width(s->surface) / FONT_WIDTH;
 389    h = surface_height(s->surface) / FONT_HEIGHT;
 390    if (w == t->width && h == t->height) {
 391        return;
 392    }
 393
 394    last_width = t->width;
 395    t->width = w;
 396    t->height = h;
 397
 398    w1 = MIN(t->width, last_width);
 399
 400    cells = g_new(TextCell, t->width * t->total_height + 1);
 401    for (y = 0; y < t->total_height; y++) {
 402        c = &cells[y * t->width];
 403        if (w1 > 0) {
 404            c1 = &t->cells[y * last_width];
 405            for (x = 0; x < w1; x++) {
 406                *c++ = *c1++;
 407            }
 408        }
 409        for (x = w1; x < t->width; x++) {
 410            c->ch = ' ';
 411            c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
 412            c++;
 413        }
 414    }
 415    g_free(t->cells);
 416    t->cells = cells;
 417}
 418
 419static void vc_put_lf(VCChardev *vc)
 420{
 421    QemuTextConsole *s = vc->console;
 422    TextCell *c;
 423    int x, y1;
 424
 425    s->y++;
 426    if (s->y >= s->height) {
 427        s->y = s->height - 1;
 428
 429        if (s->y_displayed == s->y_base) {
 430            if (++s->y_displayed == s->total_height)
 431                s->y_displayed = 0;
 432        }
 433        if (++s->y_base == s->total_height)
 434            s->y_base = 0;
 435        if (s->backscroll_height < s->total_height)
 436            s->backscroll_height++;
 437        y1 = (s->y_base + s->height - 1) % s->total_height;
 438        c = &s->cells[y1 * s->width];
 439        for(x = 0; x < s->width; x++) {
 440            c->ch = ' ';
 441            c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
 442            c++;
 443        }
 444        if (s->y_displayed == s->y_base) {
 445            s->text_x[0] = 0;
 446            s->text_y[0] = 0;
 447            s->text_x[1] = s->width - 1;
 448            s->text_y[1] = s->height - 1;
 449
 450            qemu_console_bitblt(QEMU_CONSOLE(s), 0, FONT_HEIGHT, 0, 0,
 451                                s->width * FONT_WIDTH,
 452                                (s->height - 1) * FONT_HEIGHT);
 453            qemu_console_fill_rect(QEMU_CONSOLE(s), 0, (s->height - 1) * FONT_HEIGHT,
 454                                   s->width * FONT_WIDTH, FONT_HEIGHT,
 455                                   color_table_rgb[0][TEXT_ATTRIBUTES_DEFAULT.bgcol]);
 456            s->update_x0 = 0;
 457            s->update_y0 = 0;
 458            s->update_x1 = s->width * FONT_WIDTH;
 459            s->update_y1 = s->height * FONT_HEIGHT;
 460        }
 461    }
 462}
 463
 464/* Set console attributes depending on the current escape codes.
 465 * NOTE: I know this code is not very efficient (checking every color for it
 466 * self) but it is more readable and better maintainable.
 467 */
 468static void vc_handle_escape(VCChardev *vc)
 469{
 470    int i;
 471
 472    for (i = 0; i < vc->nb_esc_params; i++) {
 473        switch (vc->esc_params[i]) {
 474            case 0: /* reset all console attributes to default */
 475                vc->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
 476                break;
 477            case 1:
 478                vc->t_attrib.bold = 1;
 479                break;
 480            case 4:
 481                vc->t_attrib.uline = 1;
 482                break;
 483            case 5:
 484                vc->t_attrib.blink = 1;
 485                break;
 486            case 7:
 487                vc->t_attrib.invers = 1;
 488                break;
 489            case 8:
 490                vc->t_attrib.unvisible = 1;
 491                break;
 492            case 22:
 493                vc->t_attrib.bold = 0;
 494                break;
 495            case 24:
 496                vc->t_attrib.uline = 0;
 497                break;
 498            case 25:
 499                vc->t_attrib.blink = 0;
 500                break;
 501            case 27:
 502                vc->t_attrib.invers = 0;
 503                break;
 504            case 28:
 505                vc->t_attrib.unvisible = 0;
 506                break;
 507            /* set foreground color */
 508            case 30:
 509                vc->t_attrib.fgcol = QEMU_COLOR_BLACK;
 510                break;
 511            case 31:
 512                vc->t_attrib.fgcol = QEMU_COLOR_RED;
 513                break;
 514            case 32:
 515                vc->t_attrib.fgcol = QEMU_COLOR_GREEN;
 516                break;
 517            case 33:
 518                vc->t_attrib.fgcol = QEMU_COLOR_YELLOW;
 519                break;
 520            case 34:
 521                vc->t_attrib.fgcol = QEMU_COLOR_BLUE;
 522                break;
 523            case 35:
 524                vc->t_attrib.fgcol = QEMU_COLOR_MAGENTA;
 525                break;
 526            case 36:
 527                vc->t_attrib.fgcol = QEMU_COLOR_CYAN;
 528                break;
 529            case 37:
 530                vc->t_attrib.fgcol = QEMU_COLOR_WHITE;
 531                break;
 532            /* set background color */
 533            case 40:
 534                vc->t_attrib.bgcol = QEMU_COLOR_BLACK;
 535                break;
 536            case 41:
 537                vc->t_attrib.bgcol = QEMU_COLOR_RED;
 538                break;
 539            case 42:
 540                vc->t_attrib.bgcol = QEMU_COLOR_GREEN;
 541                break;
 542            case 43:
 543                vc->t_attrib.bgcol = QEMU_COLOR_YELLOW;
 544                break;
 545            case 44:
 546                vc->t_attrib.bgcol = QEMU_COLOR_BLUE;
 547                break;
 548            case 45:
 549                vc->t_attrib.bgcol = QEMU_COLOR_MAGENTA;
 550                break;
 551            case 46:
 552                vc->t_attrib.bgcol = QEMU_COLOR_CYAN;
 553                break;
 554            case 47:
 555                vc->t_attrib.bgcol = QEMU_COLOR_WHITE;
 556                break;
 557        }
 558    }
 559}
 560
 561static void vc_update_xy(VCChardev *vc, int x, int y)
 562{
 563    QemuTextConsole *s = vc->console;
 564    TextCell *c;
 565    int y1, y2;
 566
 567    s->text_x[0] = MIN(s->text_x[0], x);
 568    s->text_x[1] = MAX(s->text_x[1], x);
 569    s->text_y[0] = MIN(s->text_y[0], y);
 570    s->text_y[1] = MAX(s->text_y[1], y);
 571
 572    y1 = (s->y_base + y) % s->total_height;
 573    y2 = y1 - s->y_displayed;
 574    if (y2 < 0) {
 575        y2 += s->total_height;
 576    }
 577    if (y2 < s->height) {
 578        if (x >= s->width) {
 579            x = s->width - 1;
 580        }
 581        c = &s->cells[y1 * s->width + x];
 582        vga_putcharxy(QEMU_CONSOLE(s), x, y2, c->ch,
 583                      &(c->t_attrib));
 584        invalidate_xy(s, x, y2);
 585    }
 586}
 587
 588static void vc_clear_xy(VCChardev *vc, int x, int y)
 589{
 590    QemuTextConsole *s = vc->console;
 591    int y1 = (s->y_base + y) % s->total_height;
 592    if (x >= s->width) {
 593        x = s->width - 1;
 594    }
 595    TextCell *c = &s->cells[y1 * s->width + x];
 596    c->ch = ' ';
 597    c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
 598    vc_update_xy(vc, x, y);
 599}
 600
 601static void vc_put_one(VCChardev *vc, int ch)
 602{
 603    QemuTextConsole *s = vc->console;
 604    TextCell *c;
 605    int y1;
 606    if (s->x >= s->width) {
 607        /* line wrap */
 608        s->x = 0;
 609        vc_put_lf(vc);
 610    }
 611    y1 = (s->y_base + s->y) % s->total_height;
 612    c = &s->cells[y1 * s->width + s->x];
 613    c->ch = ch;
 614    c->t_attrib = vc->t_attrib;
 615    vc_update_xy(vc, s->x, s->y);
 616    s->x++;
 617}
 618
 619static void vc_respond_str(VCChardev *vc, const char *buf)
 620{
 621    QemuTextConsole *s = vc->console;
 622
 623    qemu_chr_be_write(s->chr, (const uint8_t *)buf, strlen(buf));
 624}
 625
 626/* set cursor, checking bounds */
 627static void vc_set_cursor(VCChardev *vc, int x, int y)
 628{
 629    QemuTextConsole *s = vc->console;
 630
 631    if (x < 0) {
 632        x = 0;
 633    }
 634    if (y < 0) {
 635        y = 0;
 636    }
 637    if (y >= s->height) {
 638        y = s->height - 1;
 639    }
 640    if (x >= s->width) {
 641        x = s->width - 1;
 642    }
 643
 644    s->x = x;
 645    s->y = y;
 646}
 647
 648/**
 649 * vc_csi_P() - (DCH) deletes one or more characters from the cursor
 650 * position to the right. As characters are deleted, the remaining
 651 * characters between the cursor and right margin move to the
 652 * left. Character attributes move with the characters.
 653 */
 654static void vc_csi_P(struct VCChardev *vc, unsigned int nr)
 655{
 656    QemuTextConsole *s = vc->console;
 657    TextCell *c1, *c2;
 658    unsigned int x1, x2, y;
 659    unsigned int end, len;
 660
 661    if (!nr) {
 662        nr = 1;
 663    }
 664    if (nr > s->width - s->x) {
 665        nr = s->width - s->x;
 666        if (!nr) {
 667            return;
 668        }
 669    }
 670
 671    x1 = s->x;
 672    x2 = s->x + nr;
 673    len = s->width - x2;
 674    if (len) {
 675        y = (s->y_base + s->y) % s->total_height;
 676        c1 = &s->cells[y * s->width + x1];
 677        c2 = &s->cells[y * s->width + x2];
 678        memmove(c1, c2, len * sizeof(*c1));
 679        for (end = x1 + len; x1 < end; x1++) {
 680            vc_update_xy(vc, x1, s->y);
 681        }
 682    }
 683    /* Clear the rest */
 684    for (; x1 < s->width; x1++) {
 685        vc_clear_xy(vc, x1, s->y);
 686    }
 687}
 688
 689/**
 690 * vc_csi_at() - (ICH) inserts `nr` blank characters with the default
 691 * character attribute. The cursor remains at the beginning of the
 692 * blank characters. Text between the cursor and right margin moves to
 693 * the right. Characters scrolled past the right margin are lost.
 694 */
 695static void vc_csi_at(struct VCChardev *vc, unsigned int nr)
 696{
 697    QemuTextConsole *s = vc->console;
 698    TextCell *c1, *c2;
 699    unsigned int x1, x2, y;
 700    unsigned int end, len;
 701
 702    if (!nr) {
 703        nr = 1;
 704    }
 705    if (nr > s->width - s->x) {
 706        nr = s->width - s->x;
 707        if (!nr) {
 708            return;
 709        }
 710    }
 711
 712    x1 = s->x + nr;
 713    x2 = s->x;
 714    len = s->width - x1;
 715    if (len) {
 716        y = (s->y_base + s->y) % s->total_height;
 717        c1 = &s->cells[y * s->width + x1];
 718        c2 = &s->cells[y * s->width + x2];
 719        memmove(c1, c2, len * sizeof(*c1));
 720        for (end = x1 + len; x1 < end; x1++) {
 721            vc_update_xy(vc, x1, s->y);
 722        }
 723    }
 724    /* Insert blanks */
 725    for (x1 = s->x; x1 < s->x + nr; x1++) {
 726        vc_clear_xy(vc, x1, s->y);
 727    }
 728}
 729
 730/**
 731 * vc_save_cursor() - saves cursor position and character attributes.
 732 */
 733static void vc_save_cursor(VCChardev *vc)
 734{
 735    QemuTextConsole *s = vc->console;
 736
 737    vc->x_saved = s->x;
 738    vc->y_saved = s->y;
 739    vc->t_attrib_saved = vc->t_attrib;
 740}
 741
 742/**
 743 * vc_restore_cursor() - restores cursor position and character
 744 * attributes from saved state.
 745 */
 746static void vc_restore_cursor(VCChardev *vc)
 747{
 748    QemuTextConsole *s = vc->console;
 749
 750    s->x = vc->x_saved;
 751    s->y = vc->y_saved;
 752    vc->t_attrib = vc->t_attrib_saved;
 753}
 754
 755static void vc_putchar(VCChardev *vc, int ch)
 756{
 757    QemuTextConsole *s = vc->console;
 758    int i;
 759    int x, y;
 760    g_autofree char *response = NULL;
 761
 762    switch(vc->state) {
 763    case TTY_STATE_NORM:
 764        switch(ch) {
 765        case '\r':  /* carriage return */
 766            s->x = 0;
 767            break;
 768        case '\n':  /* newline */
 769            vc_put_lf(vc);
 770            break;
 771        case '\b':  /* backspace */
 772            if (s->x > 0)
 773                s->x--;
 774            break;
 775        case '\t':  /* tabspace */
 776            if (s->x + (8 - (s->x % 8)) > s->width) {
 777                s->x = 0;
 778                vc_put_lf(vc);
 779            } else {
 780                s->x = s->x + (8 - (s->x % 8));
 781            }
 782            break;
 783        case '\a':  /* alert aka. bell */
 784            /* TODO: has to be implemented */
 785            break;
 786        case 14:
 787            /* SI (shift in), character set 0 (ignored) */
 788            break;
 789        case 15:
 790            /* SO (shift out), character set 1 (ignored) */
 791            break;
 792        case 27:    /* esc (introducing an escape sequence) */
 793            vc->state = TTY_STATE_ESC;
 794            break;
 795        default:
 796            vc_put_one(vc, ch);
 797            break;
 798        }
 799        break;
 800    case TTY_STATE_ESC: /* check if it is a terminal escape sequence */
 801        if (ch == '[') {
 802            for(i=0;i<MAX_ESC_PARAMS;i++)
 803                vc->esc_params[i] = 0;
 804            vc->nb_esc_params = 0;
 805            vc->state = TTY_STATE_CSI;
 806        } else if (ch == '(') {
 807            vc->state = TTY_STATE_G0;
 808        } else if (ch == ')') {
 809            vc->state = TTY_STATE_G1;
 810        } else if (ch == '7') {
 811            vc_save_cursor(vc);
 812            vc->state = TTY_STATE_NORM;
 813        } else if (ch == '8') {
 814            vc_restore_cursor(vc);
 815            vc->state = TTY_STATE_NORM;
 816        } else {
 817            vc->state = TTY_STATE_NORM;
 818        }
 819        break;
 820    case TTY_STATE_CSI: /* handle escape sequence parameters */
 821        if (ch >= '0' && ch <= '9') {
 822            if (vc->nb_esc_params < MAX_ESC_PARAMS) {
 823                int *param = &vc->esc_params[vc->nb_esc_params];
 824                int digit = (ch - '0');
 825
 826                *param = (*param <= (INT_MAX - digit) / 10) ?
 827                         *param * 10 + digit : INT_MAX;
 828            }
 829        } else {
 830            if (vc->nb_esc_params < MAX_ESC_PARAMS)
 831                vc->nb_esc_params++;
 832            if (ch == ';' || ch == '?') {
 833                break;
 834            }
 835            trace_console_putchar_csi(vc->esc_params[0], vc->esc_params[1],
 836                                      ch, vc->nb_esc_params);
 837            vc->state = TTY_STATE_NORM;
 838            switch(ch) {
 839            case 'A':
 840                /* move cursor up */
 841                if (vc->esc_params[0] == 0) {
 842                    vc->esc_params[0] = 1;
 843                }
 844                vc_set_cursor(vc, s->x, s->y - vc->esc_params[0]);
 845                break;
 846            case 'B':
 847                /* move cursor down */
 848                if (vc->esc_params[0] == 0) {
 849                    vc->esc_params[0] = 1;
 850                }
 851                vc_set_cursor(vc, s->x, s->y + vc->esc_params[0]);
 852                break;
 853            case 'C':
 854                /* move cursor right */
 855                if (vc->esc_params[0] == 0) {
 856                    vc->esc_params[0] = 1;
 857                }
 858                vc_set_cursor(vc, s->x + vc->esc_params[0], s->y);
 859                break;
 860            case 'D':
 861                /* move cursor left */
 862                if (vc->esc_params[0] == 0) {
 863                    vc->esc_params[0] = 1;
 864                }
 865                vc_set_cursor(vc, s->x - vc->esc_params[0], s->y);
 866                break;
 867            case 'G':
 868                /* move cursor to column */
 869                vc_set_cursor(vc, vc->esc_params[0] - 1, s->y);
 870                break;
 871            case 'f':
 872            case 'H':
 873                /* move cursor to row, column */
 874                vc_set_cursor(vc, vc->esc_params[1] - 1, vc->esc_params[0] - 1);
 875                break;
 876            case 'J':
 877                switch (vc->esc_params[0]) {
 878                case 0:
 879                    /* clear to end of screen */
 880                    for (y = s->y; y < s->height; y++) {
 881                        for (x = 0; x < s->width; x++) {
 882                            if (y == s->y && x < s->x) {
 883                                continue;
 884                            }
 885                            vc_clear_xy(vc, x, y);
 886                        }
 887                    }
 888                    break;
 889                case 1:
 890                    /* clear from beginning of screen */
 891                    for (y = 0; y <= s->y; y++) {
 892                        for (x = 0; x < s->width; x++) {
 893                            if (y == s->y && x > s->x) {
 894                                break;
 895                            }
 896                            vc_clear_xy(vc, x, y);
 897                        }
 898                    }
 899                    break;
 900                case 2:
 901                    /* clear entire screen */
 902                    for (y = 0; y <= s->height; y++) {
 903                        for (x = 0; x < s->width; x++) {
 904                            vc_clear_xy(vc, x, y);
 905                        }
 906                    }
 907                    break;
 908                }
 909                break;
 910            case 'K':
 911                switch (vc->esc_params[0]) {
 912                case 0:
 913                    /* clear to eol */
 914                    for(x = s->x; x < s->width; x++) {
 915                        vc_clear_xy(vc, x, s->y);
 916                    }
 917                    break;
 918                case 1:
 919                    /* clear from beginning of line */
 920                    for (x = 0; x <= s->x && x < s->width; x++) {
 921                        vc_clear_xy(vc, x, s->y);
 922                    }
 923                    break;
 924                case 2:
 925                    /* clear entire line */
 926                    for(x = 0; x < s->width; x++) {
 927                        vc_clear_xy(vc, x, s->y);
 928                    }
 929                    break;
 930                }
 931                break;
 932            case 'P':
 933                vc_csi_P(vc, vc->esc_params[0]);
 934                break;
 935            case 'm':
 936                vc_handle_escape(vc);
 937                break;
 938            case 'n':
 939                switch (vc->esc_params[0]) {
 940                case 5:
 941                    /* report console status (always succeed)*/
 942                    vc_respond_str(vc, "\033[0n");
 943                    break;
 944                case 6:
 945                    /* report cursor position */
 946                    response = g_strdup_printf("\033[%d;%dR",
 947                                               s->y + 1, s->x + 1);
 948                    vc_respond_str(vc, response);
 949                    break;
 950                }
 951                break;
 952            case 's':
 953                vc_save_cursor(vc);
 954                break;
 955            case 'u':
 956                vc_restore_cursor(vc);
 957                break;
 958            case '@':
 959                vc_csi_at(vc, vc->esc_params[0]);
 960                break;
 961            default:
 962                trace_console_putchar_unhandled(ch);
 963                break;
 964            }
 965            break;
 966        }
 967        break;
 968    case TTY_STATE_G0: /* set character sets */
 969    case TTY_STATE_G1: /* set character sets */
 970        switch (ch) {
 971        case 'B':
 972            /* Latin-1 map */
 973            break;
 974        }
 975        vc->state = TTY_STATE_NORM;
 976        break;
 977    }
 978}
 979
 980#define TYPE_CHARDEV_VC "chardev-vc"
 981DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV,
 982                         TYPE_CHARDEV_VC)
 983
 984static int vc_chr_write(Chardev *chr, const uint8_t *buf, int len)
 985{
 986    VCChardev *drv = VC_CHARDEV(chr);
 987    QemuTextConsole *s = drv->console;
 988    int i;
 989
 990    s->update_x0 = s->width * FONT_WIDTH;
 991    s->update_y0 = s->height * FONT_HEIGHT;
 992    s->update_x1 = 0;
 993    s->update_y1 = 0;
 994    console_show_cursor(s, 0);
 995    for(i = 0; i < len; i++) {
 996        vc_putchar(drv, buf[i]);
 997    }
 998    console_show_cursor(s, 1);
 999    if (s->update_x0 < s->update_x1) {
1000        dpy_gfx_update(QEMU_CONSOLE(s), s->update_x0, s->update_y0,
1001                       s->update_x1 - s->update_x0,
1002                       s->update_y1 - s->update_y0);
1003    }
1004    return len;
1005}
1006
1007void qemu_text_console_update_cursor(void)
1008{
1009    cursor_visible_phase = !cursor_visible_phase;
1010
1011    if (qemu_invalidate_text_consoles()) {
1012        timer_mod(cursor_timer,
1013                  qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2);
1014    }
1015}
1016
1017static void
1018cursor_timer_cb(void *opaque)
1019{
1020    qemu_text_console_update_cursor();
1021}
1022
1023static void text_console_invalidate(void *opaque)
1024{
1025    QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque);
1026
1027    if (!QEMU_IS_FIXED_TEXT_CONSOLE(s)) {
1028        text_console_resize(QEMU_TEXT_CONSOLE(s));
1029    }
1030    console_refresh(s);
1031}
1032
1033static void
1034qemu_text_console_finalize(Object *obj)
1035{
1036}
1037
1038static void
1039qemu_text_console_class_init(ObjectClass *oc, const void *data)
1040{
1041    if (!cursor_timer) {
1042        cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cursor_timer_cb, NULL);
1043    }
1044}
1045
1046static const GraphicHwOps text_console_ops = {
1047    .invalidate  = text_console_invalidate,
1048    .text_update = text_console_update,
1049};
1050
1051static void
1052qemu_text_console_init(Object *obj)
1053{
1054    QemuTextConsole *c = QEMU_TEXT_CONSOLE(obj);
1055
1056    fifo8_create(&c->out_fifo, 16);
1057    c->total_height = DEFAULT_BACKSCROLL;
1058    QEMU_CONSOLE(c)->hw_ops = &text_console_ops;
1059    QEMU_CONSOLE(c)->hw = c;
1060}
1061
1062static void
1063qemu_fixed_text_console_finalize(Object *obj)
1064{
1065}
1066
1067static void
1068qemu_fixed_text_console_class_init(ObjectClass *oc, const void *data)
1069{
1070}
1071
1072static void
1073qemu_fixed_text_console_init(Object *obj)
1074{
1075}
1076
1077static void vc_chr_accept_input(Chardev *chr)
1078{
1079    VCChardev *drv = VC_CHARDEV(chr);
1080
1081    kbd_send_chars(drv->console);
1082}
1083
1084static void vc_chr_set_echo(Chardev *chr, bool echo)
1085{
1086    VCChardev *drv = VC_CHARDEV(chr);
1087
1088    drv->console->echo = echo;
1089}
1090
1091void qemu_text_console_update_size(QemuTextConsole *c)
1092{
1093    dpy_text_resize(QEMU_CONSOLE(c), c->width, c->height);
1094}
1095
1096static void vc_chr_open(Chardev *chr,
1097                        ChardevBackend *backend,
1098                        bool *be_opened,
1099                        Error **errp)
1100{
1101    ChardevVC *vc = backend->u.vc.data;
1102    VCChardev *drv = VC_CHARDEV(chr);
1103    QemuTextConsole *s;
1104    unsigned width = 0;
1105    unsigned height = 0;
1106
1107    if (vc->has_width) {
1108        width = vc->width;
1109    } else if (vc->has_cols) {
1110        width = vc->cols * FONT_WIDTH;
1111    }
1112
1113    if (vc->has_height) {
1114        height = vc->height;
1115    } else if (vc->has_rows) {
1116        height = vc->rows * FONT_HEIGHT;
1117    }
1118
1119    trace_console_txt_new(width, height);
1120    if (width == 0 || height == 0) {
1121        s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_TEXT_CONSOLE));
1122        width = 80 * FONT_WIDTH;
1123        height = 24 * FONT_HEIGHT;
1124    } else {
1125        s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_FIXED_TEXT_CONSOLE));
1126    }
1127
1128    dpy_gfx_replace_surface(QEMU_CONSOLE(s), qemu_create_displaysurface(width, height));
1129
1130    s->chr = chr;
1131    drv->console = s;
1132
1133    /* set current text attributes to default */
1134    drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
1135    text_console_resize(s);
1136
1137    if (chr->label) {
1138        char *msg;
1139
1140        drv->t_attrib.bgcol = QEMU_COLOR_BLUE;
1141        msg = g_strdup_printf("%s console\r\n", chr->label);
1142        qemu_chr_write(chr, (uint8_t *)msg, strlen(msg), true);
1143        g_free(msg);
1144        drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
1145    }
1146
1147    *be_opened = true;
1148}
1149
1150static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp)
1151{
1152    int val;
1153    ChardevVC *vc;
1154
1155    backend->type = CHARDEV_BACKEND_KIND_VC;
1156    vc = backend->u.vc.data = g_new0(ChardevVC, 1);
1157    qemu_chr_parse_common(opts, qapi_ChardevVC_base(vc));
1158
1159    val = qemu_opt_get_number(opts, "width", 0);
1160    if (val != 0) {
1161        vc->has_width = true;
1162        vc->width = val;
1163    }
1164
1165    val = qemu_opt_get_number(opts, "height", 0);
1166    if (val != 0) {
1167        vc->has_height = true;
1168        vc->height = val;
1169    }
1170
1171    val = qemu_opt_get_number(opts, "cols", 0);
1172    if (val != 0) {
1173        vc->has_cols = true;
1174        vc->cols = val;
1175    }
1176
1177    val = qemu_opt_get_number(opts, "rows", 0);
1178    if (val != 0) {
1179        vc->has_rows = true;
1180        vc->rows = val;
1181    }
1182}
1183
1184static void char_vc_class_init(ObjectClass *oc, const void *data)
1185{
1186    ChardevClass *cc = CHARDEV_CLASS(oc);
1187
1188    cc->parse = vc_chr_parse;
1189    cc->open = vc_chr_open;
1190    cc->chr_write = vc_chr_write;
1191    cc->chr_accept_input = vc_chr_accept_input;
1192    cc->chr_set_echo = vc_chr_set_echo;
1193}
1194
1195static const TypeInfo char_vc_type_info = {
1196    .name = TYPE_CHARDEV_VC,
1197    .parent = TYPE_CHARDEV,
1198    .instance_size = sizeof(VCChardev),
1199    .class_init = char_vc_class_init,
1200};
1201
1202void qemu_console_early_init(void)
1203{
1204    /* set the default vc driver */
1205    if (!object_class_by_name(TYPE_CHARDEV_VC)) {
1206        type_register_static(&char_vc_type_info);
1207    }
1208}
1209