|  | /* | 
|  | * SPDX-License-Identifier: MIT | 
|  | * QEMU VC | 
|  | */ | 
|  | #include "qemu/osdep.h" | 
|  |  | 
|  | #include "chardev/char.h" | 
|  | #include "qapi/error.h" | 
|  | #include "qemu/fifo8.h" | 
|  | #include "qemu/option.h" | 
|  | #include "ui/console.h" | 
|  |  | 
|  | #include "trace.h" | 
|  | #include "console-priv.h" | 
|  |  | 
|  | #define DEFAULT_BACKSCROLL 512 | 
|  | #define CONSOLE_CURSOR_PERIOD 500 | 
|  |  | 
|  | typedef struct TextAttributes { | 
|  | uint8_t fgcol:4; | 
|  | uint8_t bgcol:4; | 
|  | uint8_t bold:1; | 
|  | uint8_t uline:1; | 
|  | uint8_t blink:1; | 
|  | uint8_t invers:1; | 
|  | uint8_t unvisible:1; | 
|  | } TextAttributes; | 
|  |  | 
|  | #define TEXT_ATTRIBUTES_DEFAULT ((TextAttributes) { \ | 
|  | .fgcol = QEMU_COLOR_WHITE,                      \ | 
|  | .bgcol = QEMU_COLOR_BLACK                       \ | 
|  | }) | 
|  |  | 
|  | typedef struct TextCell { | 
|  | uint8_t ch; | 
|  | TextAttributes t_attrib; | 
|  | } TextCell; | 
|  |  | 
|  | #define MAX_ESC_PARAMS 3 | 
|  |  | 
|  | enum TTYState { | 
|  | TTY_STATE_NORM, | 
|  | TTY_STATE_ESC, | 
|  | TTY_STATE_CSI, | 
|  | }; | 
|  |  | 
|  | typedef struct QemuTextConsole { | 
|  | QemuConsole parent; | 
|  |  | 
|  | int width; | 
|  | int height; | 
|  | int total_height; | 
|  | int backscroll_height; | 
|  | int x, y; | 
|  | int y_displayed; | 
|  | int y_base; | 
|  | TextCell *cells; | 
|  | int text_x[2], text_y[2], cursor_invalidate; | 
|  | int echo; | 
|  |  | 
|  | int update_x0; | 
|  | int update_y0; | 
|  | int update_x1; | 
|  | int update_y1; | 
|  |  | 
|  | Chardev *chr; | 
|  | /* fifo for key pressed */ | 
|  | Fifo8 out_fifo; | 
|  | } QemuTextConsole; | 
|  |  | 
|  | typedef QemuConsoleClass QemuTextConsoleClass; | 
|  |  | 
|  | OBJECT_DEFINE_TYPE(QemuTextConsole, qemu_text_console, QEMU_TEXT_CONSOLE, QEMU_CONSOLE) | 
|  |  | 
|  | typedef struct QemuFixedTextConsole { | 
|  | QemuTextConsole parent; | 
|  | } QemuFixedTextConsole; | 
|  |  | 
|  | typedef QemuTextConsoleClass QemuFixedTextConsoleClass; | 
|  |  | 
|  | OBJECT_DEFINE_TYPE(QemuFixedTextConsole, qemu_fixed_text_console, QEMU_FIXED_TEXT_CONSOLE, QEMU_TEXT_CONSOLE) | 
|  |  | 
|  | struct VCChardev { | 
|  | Chardev parent; | 
|  | QemuTextConsole *console; | 
|  |  | 
|  | enum TTYState state; | 
|  | int esc_params[MAX_ESC_PARAMS]; | 
|  | int nb_esc_params; | 
|  | TextAttributes t_attrib; /* currently active text attributes */ | 
|  | int x_saved, y_saved; | 
|  | }; | 
|  | typedef struct VCChardev VCChardev; | 
|  |  | 
|  | static const pixman_color_t color_table_rgb[2][8] = { | 
|  | {   /* dark */ | 
|  | [QEMU_COLOR_BLACK]   = QEMU_PIXMAN_COLOR_BLACK, | 
|  | [QEMU_COLOR_BLUE]    = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xaa),  /* blue */ | 
|  | [QEMU_COLOR_GREEN]   = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0x00),  /* green */ | 
|  | [QEMU_COLOR_CYAN]    = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0xaa),  /* cyan */ | 
|  | [QEMU_COLOR_RED]     = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0x00),  /* red */ | 
|  | [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0xaa),  /* magenta */ | 
|  | [QEMU_COLOR_YELLOW]  = QEMU_PIXMAN_COLOR(0xaa, 0xaa, 0x00),  /* yellow */ | 
|  | [QEMU_COLOR_WHITE]   = QEMU_PIXMAN_COLOR_GRAY, | 
|  | }, | 
|  | {   /* bright */ | 
|  | [QEMU_COLOR_BLACK]   = QEMU_PIXMAN_COLOR_BLACK, | 
|  | [QEMU_COLOR_BLUE]    = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xff),  /* blue */ | 
|  | [QEMU_COLOR_GREEN]   = QEMU_PIXMAN_COLOR(0x00, 0xff, 0x00),  /* green */ | 
|  | [QEMU_COLOR_CYAN]    = QEMU_PIXMAN_COLOR(0x00, 0xff, 0xff),  /* cyan */ | 
|  | [QEMU_COLOR_RED]     = QEMU_PIXMAN_COLOR(0xff, 0x00, 0x00),  /* red */ | 
|  | [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xff, 0x00, 0xff),  /* magenta */ | 
|  | [QEMU_COLOR_YELLOW]  = QEMU_PIXMAN_COLOR(0xff, 0xff, 0x00),  /* yellow */ | 
|  | [QEMU_COLOR_WHITE]   = QEMU_PIXMAN_COLOR(0xff, 0xff, 0xff),  /* white */ | 
|  | } | 
|  | }; | 
|  |  | 
|  | static bool cursor_visible_phase; | 
|  | static QEMUTimer *cursor_timer; | 
|  |  | 
|  | const char * | 
|  | qemu_text_console_get_label(QemuTextConsole *c) | 
|  | { | 
|  | return c->chr ? c->chr->label : NULL; | 
|  | } | 
|  |  | 
|  | static void qemu_console_fill_rect(QemuConsole *con, int posx, int posy, | 
|  | int width, int height, pixman_color_t color) | 
|  | { | 
|  | DisplaySurface *surface = qemu_console_surface(con); | 
|  | pixman_rectangle16_t rect = { | 
|  | .x = posx, .y = posy, .width = width, .height = height | 
|  | }; | 
|  |  | 
|  | assert(surface); | 
|  | pixman_image_fill_rectangles(PIXMAN_OP_SRC, surface->image, | 
|  | &color, 1, &rect); | 
|  | } | 
|  |  | 
|  | /* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */ | 
|  | static void qemu_console_bitblt(QemuConsole *con, | 
|  | int xs, int ys, int xd, int yd, int w, int h) | 
|  | { | 
|  | DisplaySurface *surface = qemu_console_surface(con); | 
|  |  | 
|  | assert(surface); | 
|  | pixman_image_composite(PIXMAN_OP_SRC, | 
|  | surface->image, NULL, surface->image, | 
|  | xs, ys, 0, 0, xd, yd, w, h); | 
|  | } | 
|  |  | 
|  | static void vga_putcharxy(QemuConsole *s, int x, int y, int ch, | 
|  | TextAttributes *t_attrib) | 
|  | { | 
|  | static pixman_image_t *glyphs[256]; | 
|  | DisplaySurface *surface = qemu_console_surface(s); | 
|  | pixman_color_t fgcol, bgcol; | 
|  |  | 
|  | assert(surface); | 
|  | if (t_attrib->invers) { | 
|  | bgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol]; | 
|  | fgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol]; | 
|  | } else { | 
|  | fgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol]; | 
|  | bgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol]; | 
|  | } | 
|  |  | 
|  | if (!glyphs[ch]) { | 
|  | glyphs[ch] = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, ch); | 
|  | } | 
|  | qemu_pixman_glyph_render(glyphs[ch], surface->image, | 
|  | &fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT); | 
|  | } | 
|  |  | 
|  | static void invalidate_xy(QemuTextConsole *s, int x, int y) | 
|  | { | 
|  | if (!qemu_console_is_visible(QEMU_CONSOLE(s))) { | 
|  | return; | 
|  | } | 
|  | if (s->update_x0 > x * FONT_WIDTH) | 
|  | s->update_x0 = x * FONT_WIDTH; | 
|  | if (s->update_y0 > y * FONT_HEIGHT) | 
|  | s->update_y0 = y * FONT_HEIGHT; | 
|  | if (s->update_x1 < (x + 1) * FONT_WIDTH) | 
|  | s->update_x1 = (x + 1) * FONT_WIDTH; | 
|  | if (s->update_y1 < (y + 1) * FONT_HEIGHT) | 
|  | s->update_y1 = (y + 1) * FONT_HEIGHT; | 
|  | } | 
|  |  | 
|  | static void console_show_cursor(QemuTextConsole *s, int show) | 
|  | { | 
|  | TextCell *c; | 
|  | int y, y1; | 
|  | int x = s->x; | 
|  |  | 
|  | s->cursor_invalidate = 1; | 
|  |  | 
|  | if (x >= s->width) { | 
|  | x = s->width - 1; | 
|  | } | 
|  | y1 = (s->y_base + s->y) % s->total_height; | 
|  | y = y1 - s->y_displayed; | 
|  | if (y < 0) { | 
|  | y += s->total_height; | 
|  | } | 
|  | if (y < s->height) { | 
|  | c = &s->cells[y1 * s->width + x]; | 
|  | if (show && cursor_visible_phase) { | 
|  | TextAttributes t_attrib = TEXT_ATTRIBUTES_DEFAULT; | 
|  | t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */ | 
|  | vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &t_attrib); | 
|  | } else { | 
|  | vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &(c->t_attrib)); | 
|  | } | 
|  | invalidate_xy(s, x, y); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void console_refresh(QemuTextConsole *s) | 
|  | { | 
|  | DisplaySurface *surface = qemu_console_surface(QEMU_CONSOLE(s)); | 
|  | TextCell *c; | 
|  | int x, y, y1; | 
|  |  | 
|  | assert(surface); | 
|  | s->text_x[0] = 0; | 
|  | s->text_y[0] = 0; | 
|  | s->text_x[1] = s->width - 1; | 
|  | s->text_y[1] = s->height - 1; | 
|  | s->cursor_invalidate = 1; | 
|  |  | 
|  | qemu_console_fill_rect(QEMU_CONSOLE(s), 0, 0, surface_width(surface), surface_height(surface), | 
|  | color_table_rgb[0][QEMU_COLOR_BLACK]); | 
|  | y1 = s->y_displayed; | 
|  | for (y = 0; y < s->height; y++) { | 
|  | c = s->cells + y1 * s->width; | 
|  | for (x = 0; x < s->width; x++) { | 
|  | vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, | 
|  | &(c->t_attrib)); | 
|  | c++; | 
|  | } | 
|  | if (++y1 == s->total_height) { | 
|  | y1 = 0; | 
|  | } | 
|  | } | 
|  | console_show_cursor(s, 1); | 
|  | dpy_gfx_update(QEMU_CONSOLE(s), 0, 0, | 
|  | surface_width(surface), surface_height(surface)); | 
|  | } | 
|  |  | 
|  | static void console_scroll(QemuTextConsole *s, int ydelta) | 
|  | { | 
|  | int i, y1; | 
|  |  | 
|  | if (ydelta > 0) { | 
|  | for(i = 0; i < ydelta; i++) { | 
|  | if (s->y_displayed == s->y_base) | 
|  | break; | 
|  | if (++s->y_displayed == s->total_height) | 
|  | s->y_displayed = 0; | 
|  | } | 
|  | } else { | 
|  | ydelta = -ydelta; | 
|  | i = s->backscroll_height; | 
|  | if (i > s->total_height - s->height) | 
|  | i = s->total_height - s->height; | 
|  | y1 = s->y_base - i; | 
|  | if (y1 < 0) | 
|  | y1 += s->total_height; | 
|  | for(i = 0; i < ydelta; i++) { | 
|  | if (s->y_displayed == y1) | 
|  | break; | 
|  | if (--s->y_displayed < 0) | 
|  | s->y_displayed = s->total_height - 1; | 
|  | } | 
|  | } | 
|  | console_refresh(s); | 
|  | } | 
|  |  | 
|  | static void kbd_send_chars(QemuTextConsole *s) | 
|  | { | 
|  | uint32_t len, avail; | 
|  |  | 
|  | len = qemu_chr_be_can_write(s->chr); | 
|  | avail = fifo8_num_used(&s->out_fifo); | 
|  | while (len > 0 && avail > 0) { | 
|  | const uint8_t *buf; | 
|  | uint32_t size; | 
|  |  | 
|  | buf = fifo8_pop_buf(&s->out_fifo, MIN(len, avail), &size); | 
|  | qemu_chr_be_write(s->chr, buf, size); | 
|  | len = qemu_chr_be_can_write(s->chr); | 
|  | avail -= size; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* called when an ascii key is pressed */ | 
|  | void qemu_text_console_handle_keysym(QemuTextConsole *s, int keysym) | 
|  | { | 
|  | uint8_t buf[16], *q; | 
|  | int c; | 
|  | uint32_t num_free; | 
|  |  | 
|  | switch(keysym) { | 
|  | case QEMU_KEY_CTRL_UP: | 
|  | console_scroll(s, -1); | 
|  | break; | 
|  | case QEMU_KEY_CTRL_DOWN: | 
|  | console_scroll(s, 1); | 
|  | break; | 
|  | case QEMU_KEY_CTRL_PAGEUP: | 
|  | console_scroll(s, -10); | 
|  | break; | 
|  | case QEMU_KEY_CTRL_PAGEDOWN: | 
|  | console_scroll(s, 10); | 
|  | break; | 
|  | default: | 
|  | /* convert the QEMU keysym to VT100 key string */ | 
|  | q = buf; | 
|  | if (keysym >= 0xe100 && keysym <= 0xe11f) { | 
|  | *q++ = '\033'; | 
|  | *q++ = '['; | 
|  | c = keysym - 0xe100; | 
|  | if (c >= 10) | 
|  | *q++ = '0' + (c / 10); | 
|  | *q++ = '0' + (c % 10); | 
|  | *q++ = '~'; | 
|  | } else if (keysym >= 0xe120 && keysym <= 0xe17f) { | 
|  | *q++ = '\033'; | 
|  | *q++ = '['; | 
|  | *q++ = keysym & 0xff; | 
|  | } else if (s->echo && (keysym == '\r' || keysym == '\n')) { | 
|  | qemu_chr_write(s->chr, (uint8_t *)"\r", 1, true); | 
|  | *q++ = '\n'; | 
|  | } else { | 
|  | *q++ = keysym; | 
|  | } | 
|  | if (s->echo) { | 
|  | qemu_chr_write(s->chr, buf, q - buf, true); | 
|  | } | 
|  | num_free = fifo8_num_free(&s->out_fifo); | 
|  | fifo8_push_all(&s->out_fifo, buf, MIN(num_free, q - buf)); | 
|  | kbd_send_chars(s); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void text_console_update(void *opaque, console_ch_t *chardata) | 
|  | { | 
|  | QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque); | 
|  | int i, j, src; | 
|  |  | 
|  | if (s->text_x[0] <= s->text_x[1]) { | 
|  | src = (s->y_base + s->text_y[0]) * s->width; | 
|  | chardata += s->text_y[0] * s->width; | 
|  | for (i = s->text_y[0]; i <= s->text_y[1]; i ++) | 
|  | for (j = 0; j < s->width; j++, src++) { | 
|  | console_write_ch(chardata ++, | 
|  | ATTR2CHTYPE(s->cells[src].ch, | 
|  | s->cells[src].t_attrib.fgcol, | 
|  | s->cells[src].t_attrib.bgcol, | 
|  | s->cells[src].t_attrib.bold)); | 
|  | } | 
|  | dpy_text_update(QEMU_CONSOLE(s), s->text_x[0], s->text_y[0], | 
|  | s->text_x[1] - s->text_x[0], i - s->text_y[0]); | 
|  | s->text_x[0] = s->width; | 
|  | s->text_y[0] = s->height; | 
|  | s->text_x[1] = 0; | 
|  | s->text_y[1] = 0; | 
|  | } | 
|  | if (s->cursor_invalidate) { | 
|  | dpy_text_cursor(QEMU_CONSOLE(s), s->x, s->y); | 
|  | s->cursor_invalidate = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void text_console_resize(QemuTextConsole *t) | 
|  | { | 
|  | QemuConsole *s = QEMU_CONSOLE(t); | 
|  | TextCell *cells, *c, *c1; | 
|  | int w1, x, y, last_width, w, h; | 
|  |  | 
|  | assert(s->scanout.kind == SCANOUT_SURFACE); | 
|  |  | 
|  | w = surface_width(s->surface) / FONT_WIDTH; | 
|  | h = surface_height(s->surface) / FONT_HEIGHT; | 
|  | if (w == t->width && h == t->height) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | last_width = t->width; | 
|  | t->width = w; | 
|  | t->height = h; | 
|  |  | 
|  | w1 = MIN(t->width, last_width); | 
|  |  | 
|  | cells = g_new(TextCell, t->width * t->total_height + 1); | 
|  | for (y = 0; y < t->total_height; y++) { | 
|  | c = &cells[y * t->width]; | 
|  | if (w1 > 0) { | 
|  | c1 = &t->cells[y * last_width]; | 
|  | for (x = 0; x < w1; x++) { | 
|  | *c++ = *c1++; | 
|  | } | 
|  | } | 
|  | for (x = w1; x < t->width; x++) { | 
|  | c->ch = ' '; | 
|  | c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | 
|  | c++; | 
|  | } | 
|  | } | 
|  | g_free(t->cells); | 
|  | t->cells = cells; | 
|  | } | 
|  |  | 
|  | static void vc_put_lf(VCChardev *vc) | 
|  | { | 
|  | QemuTextConsole *s = vc->console; | 
|  | TextCell *c; | 
|  | int x, y1; | 
|  |  | 
|  | s->y++; | 
|  | if (s->y >= s->height) { | 
|  | s->y = s->height - 1; | 
|  |  | 
|  | if (s->y_displayed == s->y_base) { | 
|  | if (++s->y_displayed == s->total_height) | 
|  | s->y_displayed = 0; | 
|  | } | 
|  | if (++s->y_base == s->total_height) | 
|  | s->y_base = 0; | 
|  | if (s->backscroll_height < s->total_height) | 
|  | s->backscroll_height++; | 
|  | y1 = (s->y_base + s->height - 1) % s->total_height; | 
|  | c = &s->cells[y1 * s->width]; | 
|  | for(x = 0; x < s->width; x++) { | 
|  | c->ch = ' '; | 
|  | c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | 
|  | c++; | 
|  | } | 
|  | if (s->y_displayed == s->y_base) { | 
|  | s->text_x[0] = 0; | 
|  | s->text_y[0] = 0; | 
|  | s->text_x[1] = s->width - 1; | 
|  | s->text_y[1] = s->height - 1; | 
|  |  | 
|  | qemu_console_bitblt(QEMU_CONSOLE(s), 0, FONT_HEIGHT, 0, 0, | 
|  | s->width * FONT_WIDTH, | 
|  | (s->height - 1) * FONT_HEIGHT); | 
|  | qemu_console_fill_rect(QEMU_CONSOLE(s), 0, (s->height - 1) * FONT_HEIGHT, | 
|  | s->width * FONT_WIDTH, FONT_HEIGHT, | 
|  | color_table_rgb[0][TEXT_ATTRIBUTES_DEFAULT.bgcol]); | 
|  | s->update_x0 = 0; | 
|  | s->update_y0 = 0; | 
|  | s->update_x1 = s->width * FONT_WIDTH; | 
|  | s->update_y1 = s->height * FONT_HEIGHT; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Set console attributes depending on the current escape codes. | 
|  | * NOTE: I know this code is not very efficient (checking every color for it | 
|  | * self) but it is more readable and better maintainable. | 
|  | */ | 
|  | static void vc_handle_escape(VCChardev *vc) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < vc->nb_esc_params; i++) { | 
|  | switch (vc->esc_params[i]) { | 
|  | case 0: /* reset all console attributes to default */ | 
|  | vc->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | 
|  | break; | 
|  | case 1: | 
|  | vc->t_attrib.bold = 1; | 
|  | break; | 
|  | case 4: | 
|  | vc->t_attrib.uline = 1; | 
|  | break; | 
|  | case 5: | 
|  | vc->t_attrib.blink = 1; | 
|  | break; | 
|  | case 7: | 
|  | vc->t_attrib.invers = 1; | 
|  | break; | 
|  | case 8: | 
|  | vc->t_attrib.unvisible = 1; | 
|  | break; | 
|  | case 22: | 
|  | vc->t_attrib.bold = 0; | 
|  | break; | 
|  | case 24: | 
|  | vc->t_attrib.uline = 0; | 
|  | break; | 
|  | case 25: | 
|  | vc->t_attrib.blink = 0; | 
|  | break; | 
|  | case 27: | 
|  | vc->t_attrib.invers = 0; | 
|  | break; | 
|  | case 28: | 
|  | vc->t_attrib.unvisible = 0; | 
|  | break; | 
|  | /* set foreground color */ | 
|  | case 30: | 
|  | vc->t_attrib.fgcol = QEMU_COLOR_BLACK; | 
|  | break; | 
|  | case 31: | 
|  | vc->t_attrib.fgcol = QEMU_COLOR_RED; | 
|  | break; | 
|  | case 32: | 
|  | vc->t_attrib.fgcol = QEMU_COLOR_GREEN; | 
|  | break; | 
|  | case 33: | 
|  | vc->t_attrib.fgcol = QEMU_COLOR_YELLOW; | 
|  | break; | 
|  | case 34: | 
|  | vc->t_attrib.fgcol = QEMU_COLOR_BLUE; | 
|  | break; | 
|  | case 35: | 
|  | vc->t_attrib.fgcol = QEMU_COLOR_MAGENTA; | 
|  | break; | 
|  | case 36: | 
|  | vc->t_attrib.fgcol = QEMU_COLOR_CYAN; | 
|  | break; | 
|  | case 37: | 
|  | vc->t_attrib.fgcol = QEMU_COLOR_WHITE; | 
|  | break; | 
|  | /* set background color */ | 
|  | case 40: | 
|  | vc->t_attrib.bgcol = QEMU_COLOR_BLACK; | 
|  | break; | 
|  | case 41: | 
|  | vc->t_attrib.bgcol = QEMU_COLOR_RED; | 
|  | break; | 
|  | case 42: | 
|  | vc->t_attrib.bgcol = QEMU_COLOR_GREEN; | 
|  | break; | 
|  | case 43: | 
|  | vc->t_attrib.bgcol = QEMU_COLOR_YELLOW; | 
|  | break; | 
|  | case 44: | 
|  | vc->t_attrib.bgcol = QEMU_COLOR_BLUE; | 
|  | break; | 
|  | case 45: | 
|  | vc->t_attrib.bgcol = QEMU_COLOR_MAGENTA; | 
|  | break; | 
|  | case 46: | 
|  | vc->t_attrib.bgcol = QEMU_COLOR_CYAN; | 
|  | break; | 
|  | case 47: | 
|  | vc->t_attrib.bgcol = QEMU_COLOR_WHITE; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void vc_update_xy(VCChardev *vc, int x, int y) | 
|  | { | 
|  | QemuTextConsole *s = vc->console; | 
|  | TextCell *c; | 
|  | int y1, y2; | 
|  |  | 
|  | s->text_x[0] = MIN(s->text_x[0], x); | 
|  | s->text_x[1] = MAX(s->text_x[1], x); | 
|  | s->text_y[0] = MIN(s->text_y[0], y); | 
|  | s->text_y[1] = MAX(s->text_y[1], y); | 
|  |  | 
|  | y1 = (s->y_base + y) % s->total_height; | 
|  | y2 = y1 - s->y_displayed; | 
|  | if (y2 < 0) { | 
|  | y2 += s->total_height; | 
|  | } | 
|  | if (y2 < s->height) { | 
|  | if (x >= s->width) { | 
|  | x = s->width - 1; | 
|  | } | 
|  | c = &s->cells[y1 * s->width + x]; | 
|  | vga_putcharxy(QEMU_CONSOLE(s), x, y2, c->ch, | 
|  | &(c->t_attrib)); | 
|  | invalidate_xy(s, x, y2); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void vc_clear_xy(VCChardev *vc, int x, int y) | 
|  | { | 
|  | QemuTextConsole *s = vc->console; | 
|  | int y1 = (s->y_base + y) % s->total_height; | 
|  | if (x >= s->width) { | 
|  | x = s->width - 1; | 
|  | } | 
|  | TextCell *c = &s->cells[y1 * s->width + x]; | 
|  | c->ch = ' '; | 
|  | c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | 
|  | vc_update_xy(vc, x, y); | 
|  | } | 
|  |  | 
|  | static void vc_put_one(VCChardev *vc, int ch) | 
|  | { | 
|  | QemuTextConsole *s = vc->console; | 
|  | TextCell *c; | 
|  | int y1; | 
|  | if (s->x >= s->width) { | 
|  | /* line wrap */ | 
|  | s->x = 0; | 
|  | vc_put_lf(vc); | 
|  | } | 
|  | y1 = (s->y_base + s->y) % s->total_height; | 
|  | c = &s->cells[y1 * s->width + s->x]; | 
|  | c->ch = ch; | 
|  | c->t_attrib = vc->t_attrib; | 
|  | vc_update_xy(vc, s->x, s->y); | 
|  | s->x++; | 
|  | } | 
|  |  | 
|  | static void vc_respond_str(VCChardev *vc, const char *buf) | 
|  | { | 
|  | while (*buf) { | 
|  | vc_put_one(vc, *buf); | 
|  | buf++; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* set cursor, checking bounds */ | 
|  | static void vc_set_cursor(VCChardev *vc, int x, int y) | 
|  | { | 
|  | QemuTextConsole *s = vc->console; | 
|  |  | 
|  | if (x < 0) { | 
|  | x = 0; | 
|  | } | 
|  | if (y < 0) { | 
|  | y = 0; | 
|  | } | 
|  | if (y >= s->height) { | 
|  | y = s->height - 1; | 
|  | } | 
|  | if (x >= s->width) { | 
|  | x = s->width - 1; | 
|  | } | 
|  |  | 
|  | s->x = x; | 
|  | s->y = y; | 
|  | } | 
|  |  | 
|  | static void vc_putchar(VCChardev *vc, int ch) | 
|  | { | 
|  | QemuTextConsole *s = vc->console; | 
|  | int i; | 
|  | int x, y; | 
|  | char response[40]; | 
|  |  | 
|  | switch(vc->state) { | 
|  | case TTY_STATE_NORM: | 
|  | switch(ch) { | 
|  | case '\r':  /* carriage return */ | 
|  | s->x = 0; | 
|  | break; | 
|  | case '\n':  /* newline */ | 
|  | vc_put_lf(vc); | 
|  | break; | 
|  | case '\b':  /* backspace */ | 
|  | if (s->x > 0) | 
|  | s->x--; | 
|  | break; | 
|  | case '\t':  /* tabspace */ | 
|  | if (s->x + (8 - (s->x % 8)) > s->width) { | 
|  | s->x = 0; | 
|  | vc_put_lf(vc); | 
|  | } else { | 
|  | s->x = s->x + (8 - (s->x % 8)); | 
|  | } | 
|  | break; | 
|  | case '\a':  /* alert aka. bell */ | 
|  | /* TODO: has to be implemented */ | 
|  | break; | 
|  | case 14: | 
|  | /* SI (shift in), character set 0 (ignored) */ | 
|  | break; | 
|  | case 15: | 
|  | /* SO (shift out), character set 1 (ignored) */ | 
|  | break; | 
|  | case 27:    /* esc (introducing an escape sequence) */ | 
|  | vc->state = TTY_STATE_ESC; | 
|  | break; | 
|  | default: | 
|  | vc_put_one(vc, ch); | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case TTY_STATE_ESC: /* check if it is a terminal escape sequence */ | 
|  | if (ch == '[') { | 
|  | for(i=0;i<MAX_ESC_PARAMS;i++) | 
|  | vc->esc_params[i] = 0; | 
|  | vc->nb_esc_params = 0; | 
|  | vc->state = TTY_STATE_CSI; | 
|  | } else { | 
|  | vc->state = TTY_STATE_NORM; | 
|  | } | 
|  | break; | 
|  | case TTY_STATE_CSI: /* handle escape sequence parameters */ | 
|  | if (ch >= '0' && ch <= '9') { | 
|  | if (vc->nb_esc_params < MAX_ESC_PARAMS) { | 
|  | int *param = &vc->esc_params[vc->nb_esc_params]; | 
|  | int digit = (ch - '0'); | 
|  |  | 
|  | *param = (*param <= (INT_MAX - digit) / 10) ? | 
|  | *param * 10 + digit : INT_MAX; | 
|  | } | 
|  | } else { | 
|  | if (vc->nb_esc_params < MAX_ESC_PARAMS) | 
|  | vc->nb_esc_params++; | 
|  | if (ch == ';' || ch == '?') { | 
|  | break; | 
|  | } | 
|  | trace_console_putchar_csi(vc->esc_params[0], vc->esc_params[1], | 
|  | ch, vc->nb_esc_params); | 
|  | vc->state = TTY_STATE_NORM; | 
|  | switch(ch) { | 
|  | case 'A': | 
|  | /* move cursor up */ | 
|  | if (vc->esc_params[0] == 0) { | 
|  | vc->esc_params[0] = 1; | 
|  | } | 
|  | vc_set_cursor(vc, s->x, s->y - vc->esc_params[0]); | 
|  | break; | 
|  | case 'B': | 
|  | /* move cursor down */ | 
|  | if (vc->esc_params[0] == 0) { | 
|  | vc->esc_params[0] = 1; | 
|  | } | 
|  | vc_set_cursor(vc, s->x, s->y + vc->esc_params[0]); | 
|  | break; | 
|  | case 'C': | 
|  | /* move cursor right */ | 
|  | if (vc->esc_params[0] == 0) { | 
|  | vc->esc_params[0] = 1; | 
|  | } | 
|  | vc_set_cursor(vc, s->x + vc->esc_params[0], s->y); | 
|  | break; | 
|  | case 'D': | 
|  | /* move cursor left */ | 
|  | if (vc->esc_params[0] == 0) { | 
|  | vc->esc_params[0] = 1; | 
|  | } | 
|  | vc_set_cursor(vc, s->x - vc->esc_params[0], s->y); | 
|  | break; | 
|  | case 'G': | 
|  | /* move cursor to column */ | 
|  | vc_set_cursor(vc, vc->esc_params[0] - 1, s->y); | 
|  | break; | 
|  | case 'f': | 
|  | case 'H': | 
|  | /* move cursor to row, column */ | 
|  | vc_set_cursor(vc, vc->esc_params[1] - 1, vc->esc_params[0] - 1); | 
|  | break; | 
|  | case 'J': | 
|  | switch (vc->esc_params[0]) { | 
|  | case 0: | 
|  | /* clear to end of screen */ | 
|  | for (y = s->y; y < s->height; y++) { | 
|  | for (x = 0; x < s->width; x++) { | 
|  | if (y == s->y && x < s->x) { | 
|  | continue; | 
|  | } | 
|  | vc_clear_xy(vc, x, y); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case 1: | 
|  | /* clear from beginning of screen */ | 
|  | for (y = 0; y <= s->y; y++) { | 
|  | for (x = 0; x < s->width; x++) { | 
|  | if (y == s->y && x > s->x) { | 
|  | break; | 
|  | } | 
|  | vc_clear_xy(vc, x, y); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case 2: | 
|  | /* clear entire screen */ | 
|  | for (y = 0; y <= s->height; y++) { | 
|  | for (x = 0; x < s->width; x++) { | 
|  | vc_clear_xy(vc, x, y); | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case 'K': | 
|  | switch (vc->esc_params[0]) { | 
|  | case 0: | 
|  | /* clear to eol */ | 
|  | for(x = s->x; x < s->width; x++) { | 
|  | vc_clear_xy(vc, x, s->y); | 
|  | } | 
|  | break; | 
|  | case 1: | 
|  | /* clear from beginning of line */ | 
|  | for (x = 0; x <= s->x && x < s->width; x++) { | 
|  | vc_clear_xy(vc, x, s->y); | 
|  | } | 
|  | break; | 
|  | case 2: | 
|  | /* clear entire line */ | 
|  | for(x = 0; x < s->width; x++) { | 
|  | vc_clear_xy(vc, x, s->y); | 
|  | } | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case 'm': | 
|  | vc_handle_escape(vc); | 
|  | break; | 
|  | case 'n': | 
|  | switch (vc->esc_params[0]) { | 
|  | case 5: | 
|  | /* report console status (always succeed)*/ | 
|  | vc_respond_str(vc, "\033[0n"); | 
|  | break; | 
|  | case 6: | 
|  | /* report cursor position */ | 
|  | sprintf(response, "\033[%d;%dR", | 
|  | (s->y_base + s->y) % s->total_height + 1, | 
|  | s->x + 1); | 
|  | vc_respond_str(vc, response); | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case 's': | 
|  | /* save cursor position */ | 
|  | vc->x_saved = s->x; | 
|  | vc->y_saved = s->y; | 
|  | break; | 
|  | case 'u': | 
|  | /* restore cursor position */ | 
|  | s->x = vc->x_saved; | 
|  | s->y = vc->y_saved; | 
|  | break; | 
|  | default: | 
|  | trace_console_putchar_unhandled(ch); | 
|  | break; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #define TYPE_CHARDEV_VC "chardev-vc" | 
|  | DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV, | 
|  | TYPE_CHARDEV_VC) | 
|  |  | 
|  | static int vc_chr_write(Chardev *chr, const uint8_t *buf, int len) | 
|  | { | 
|  | VCChardev *drv = VC_CHARDEV(chr); | 
|  | QemuTextConsole *s = drv->console; | 
|  | int i; | 
|  |  | 
|  | s->update_x0 = s->width * FONT_WIDTH; | 
|  | s->update_y0 = s->height * FONT_HEIGHT; | 
|  | s->update_x1 = 0; | 
|  | s->update_y1 = 0; | 
|  | console_show_cursor(s, 0); | 
|  | for(i = 0; i < len; i++) { | 
|  | vc_putchar(drv, buf[i]); | 
|  | } | 
|  | console_show_cursor(s, 1); | 
|  | if (s->update_x0 < s->update_x1) { | 
|  | dpy_gfx_update(QEMU_CONSOLE(s), s->update_x0, s->update_y0, | 
|  | s->update_x1 - s->update_x0, | 
|  | s->update_y1 - s->update_y0); | 
|  | } | 
|  | return len; | 
|  | } | 
|  |  | 
|  | void qemu_text_console_update_cursor(void) | 
|  | { | 
|  | cursor_visible_phase = !cursor_visible_phase; | 
|  |  | 
|  | if (qemu_invalidate_text_consoles()) { | 
|  | timer_mod(cursor_timer, | 
|  | qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | cursor_timer_cb(void *opaque) | 
|  | { | 
|  | qemu_text_console_update_cursor(); | 
|  | } | 
|  |  | 
|  | static void text_console_invalidate(void *opaque) | 
|  | { | 
|  | QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque); | 
|  |  | 
|  | if (!QEMU_IS_FIXED_TEXT_CONSOLE(s)) { | 
|  | text_console_resize(QEMU_TEXT_CONSOLE(s)); | 
|  | } | 
|  | console_refresh(s); | 
|  | } | 
|  |  | 
|  | static void | 
|  | qemu_text_console_finalize(Object *obj) | 
|  | { | 
|  | } | 
|  |  | 
|  | static void | 
|  | qemu_text_console_class_init(ObjectClass *oc, void *data) | 
|  | { | 
|  | if (!cursor_timer) { | 
|  | cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cursor_timer_cb, NULL); | 
|  | } | 
|  | } | 
|  |  | 
|  | static const GraphicHwOps text_console_ops = { | 
|  | .invalidate  = text_console_invalidate, | 
|  | .text_update = text_console_update, | 
|  | }; | 
|  |  | 
|  | static void | 
|  | qemu_text_console_init(Object *obj) | 
|  | { | 
|  | QemuTextConsole *c = QEMU_TEXT_CONSOLE(obj); | 
|  |  | 
|  | fifo8_create(&c->out_fifo, 16); | 
|  | c->total_height = DEFAULT_BACKSCROLL; | 
|  | QEMU_CONSOLE(c)->hw_ops = &text_console_ops; | 
|  | QEMU_CONSOLE(c)->hw = c; | 
|  | } | 
|  |  | 
|  | static void | 
|  | qemu_fixed_text_console_finalize(Object *obj) | 
|  | { | 
|  | } | 
|  |  | 
|  | static void | 
|  | qemu_fixed_text_console_class_init(ObjectClass *oc, void *data) | 
|  | { | 
|  | } | 
|  |  | 
|  | static void | 
|  | qemu_fixed_text_console_init(Object *obj) | 
|  | { | 
|  | } | 
|  |  | 
|  | static void vc_chr_accept_input(Chardev *chr) | 
|  | { | 
|  | VCChardev *drv = VC_CHARDEV(chr); | 
|  |  | 
|  | kbd_send_chars(drv->console); | 
|  | } | 
|  |  | 
|  | static void vc_chr_set_echo(Chardev *chr, bool echo) | 
|  | { | 
|  | VCChardev *drv = VC_CHARDEV(chr); | 
|  |  | 
|  | drv->console->echo = echo; | 
|  | } | 
|  |  | 
|  | void qemu_text_console_update_size(QemuTextConsole *c) | 
|  | { | 
|  | dpy_text_resize(QEMU_CONSOLE(c), c->width, c->height); | 
|  | } | 
|  |  | 
|  | static void vc_chr_open(Chardev *chr, | 
|  | ChardevBackend *backend, | 
|  | bool *be_opened, | 
|  | Error **errp) | 
|  | { | 
|  | ChardevVC *vc = backend->u.vc.data; | 
|  | VCChardev *drv = VC_CHARDEV(chr); | 
|  | QemuTextConsole *s; | 
|  | unsigned width = 0; | 
|  | unsigned height = 0; | 
|  |  | 
|  | if (vc->has_width) { | 
|  | width = vc->width; | 
|  | } else if (vc->has_cols) { | 
|  | width = vc->cols * FONT_WIDTH; | 
|  | } | 
|  |  | 
|  | if (vc->has_height) { | 
|  | height = vc->height; | 
|  | } else if (vc->has_rows) { | 
|  | height = vc->rows * FONT_HEIGHT; | 
|  | } | 
|  |  | 
|  | trace_console_txt_new(width, height); | 
|  | if (width == 0 || height == 0) { | 
|  | s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_TEXT_CONSOLE)); | 
|  | width = 80 * FONT_WIDTH; | 
|  | height = 24 * FONT_HEIGHT; | 
|  | } else { | 
|  | s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_FIXED_TEXT_CONSOLE)); | 
|  | } | 
|  |  | 
|  | dpy_gfx_replace_surface(QEMU_CONSOLE(s), qemu_create_displaysurface(width, height)); | 
|  |  | 
|  | s->chr = chr; | 
|  | drv->console = s; | 
|  |  | 
|  | /* set current text attributes to default */ | 
|  | drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | 
|  | text_console_resize(s); | 
|  |  | 
|  | if (chr->label) { | 
|  | char *msg; | 
|  |  | 
|  | drv->t_attrib.bgcol = QEMU_COLOR_BLUE; | 
|  | msg = g_strdup_printf("%s console\r\n", chr->label); | 
|  | qemu_chr_write(chr, (uint8_t *)msg, strlen(msg), true); | 
|  | g_free(msg); | 
|  | drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT; | 
|  | } | 
|  |  | 
|  | *be_opened = true; | 
|  | } | 
|  |  | 
|  | static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp) | 
|  | { | 
|  | int val; | 
|  | ChardevVC *vc; | 
|  |  | 
|  | backend->type = CHARDEV_BACKEND_KIND_VC; | 
|  | vc = backend->u.vc.data = g_new0(ChardevVC, 1); | 
|  | qemu_chr_parse_common(opts, qapi_ChardevVC_base(vc)); | 
|  |  | 
|  | val = qemu_opt_get_number(opts, "width", 0); | 
|  | if (val != 0) { | 
|  | vc->has_width = true; | 
|  | vc->width = val; | 
|  | } | 
|  |  | 
|  | val = qemu_opt_get_number(opts, "height", 0); | 
|  | if (val != 0) { | 
|  | vc->has_height = true; | 
|  | vc->height = val; | 
|  | } | 
|  |  | 
|  | val = qemu_opt_get_number(opts, "cols", 0); | 
|  | if (val != 0) { | 
|  | vc->has_cols = true; | 
|  | vc->cols = val; | 
|  | } | 
|  |  | 
|  | val = qemu_opt_get_number(opts, "rows", 0); | 
|  | if (val != 0) { | 
|  | vc->has_rows = true; | 
|  | vc->rows = val; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void char_vc_class_init(ObjectClass *oc, void *data) | 
|  | { | 
|  | ChardevClass *cc = CHARDEV_CLASS(oc); | 
|  |  | 
|  | cc->parse = vc_chr_parse; | 
|  | cc->open = vc_chr_open; | 
|  | cc->chr_write = vc_chr_write; | 
|  | cc->chr_accept_input = vc_chr_accept_input; | 
|  | cc->chr_set_echo = vc_chr_set_echo; | 
|  | } | 
|  |  | 
|  | static const TypeInfo char_vc_type_info = { | 
|  | .name = TYPE_CHARDEV_VC, | 
|  | .parent = TYPE_CHARDEV, | 
|  | .instance_size = sizeof(VCChardev), | 
|  | .class_init = char_vc_class_init, | 
|  | }; | 
|  |  | 
|  | void qemu_console_early_init(void) | 
|  | { | 
|  | /* set the default vc driver */ | 
|  | if (!object_class_by_name(TYPE_CHARDEV_VC)) { | 
|  | type_register(&char_vc_type_info); | 
|  | } | 
|  | } |