/* * SPDX-License-Identifier: MIT * QEMU vt100 */ #include "qemu/osdep.h" #include "qemu/timer.h" #include "cp437.h" #include "vgafont.h" #include "vt100.h" #include "trace.h" #define DEFAULT_BACKSCROLL 512 #define CONSOLE_CURSOR_PERIOD 500 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; static QTAILQ_HEAD(QemuVT100Head, QemuVT100) vt100s = QTAILQ_HEAD_INITIALIZER(vt100s); static void image_fill_rect(pixman_image_t *image, int posx, int posy, int width, int height, pixman_color_t color) { pixman_rectangle16_t rect = { .x = posx, .y = posy, .width = width, .height = height }; pixman_image_fill_rectangles(PIXMAN_OP_SRC, image, &color, 1, &rect); } /* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */ static void image_bitblt(pixman_image_t *image, int xs, int ys, int xd, int yd, int w, int h) { pixman_image_composite(PIXMAN_OP_SRC, image, NULL, image, xs, ys, 0, 0, xd, yd, w, h); } static void vt100_putcharxy(QemuVT100 *vt, int x, int y, uint8_t ch, TextAttributes *t_attrib) { static pixman_image_t *glyphs[256]; pixman_color_t fgcol, bgcol; assert(vt->image); 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], vt->image, &fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT); } static void vt100_invalidate_xy(QemuVT100 *vt, int x, int y) { if (vt->update_x0 > x * FONT_WIDTH) { vt->update_x0 = x * FONT_WIDTH; } if (vt->update_y0 > y * FONT_HEIGHT) { vt->update_y0 = y * FONT_HEIGHT; } if (vt->update_x1 < (x + 1) * FONT_WIDTH) { vt->update_x1 = (x + 1) * FONT_WIDTH; } if (vt->update_y1 < (y + 1) * FONT_HEIGHT) { vt->update_y1 = (y + 1) * FONT_HEIGHT; } } static void vt100_show_cursor(QemuVT100 *vt, int show) { TextCell *c; int y, y1; int x = vt->x; vt->cursor_invalidate = 1; if (x >= vt->width) { x = vt->width - 1; } y1 = (vt->y_base + vt->y) % vt->total_height; y = y1 - vt->y_displayed; if (y < 0) { y += vt->total_height; } if (y < vt->height) { c = &vt->cells[y1 * vt->width + x]; if (show && cursor_visible_phase) { TextAttributes t_attrib = TEXT_ATTRIBUTES_DEFAULT; t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */ vt100_putcharxy(vt, x, y, c->ch, &t_attrib); } else { vt100_putcharxy(vt, x, y, c->ch, &(c->t_attrib)); } vt100_invalidate_xy(vt, x, y); } } static void vt100_image_update(QemuVT100 *vt, int x, int y, int width, int height) { vt->image_update(vt, x, y, width, height); } void vt100_refresh(QemuVT100 *vt) { TextCell *c; int x, y, y1; int w = pixman_image_get_width(vt->image); int h = pixman_image_get_height(vt->image); vt->text_x[0] = 0; vt->text_y[0] = 0; vt->text_x[1] = vt->width - 1; vt->text_y[1] = vt->height - 1; vt->cursor_invalidate = 1; image_fill_rect(vt->image, 0, 0, w, h, color_table_rgb[0][QEMU_COLOR_BLACK]); y1 = vt->y_displayed; for (y = 0; y < vt->height; y++) { c = vt->cells + y1 * vt->width; for (x = 0; x < vt->width; x++) { vt100_putcharxy(vt, x, y, c->ch, &(c->t_attrib)); c++; } if (++y1 == vt->total_height) { y1 = 0; } } vt100_show_cursor(vt, 1); vt100_image_update(vt, 0, 0, w, h); } static void vt100_scroll(QemuVT100 *vt, int ydelta) { int i, y1; if (ydelta > 0) { for (i = 0; i < ydelta; i++) { if (vt->y_displayed == vt->y_base) { break; } if (++vt->y_displayed == vt->total_height) { vt->y_displayed = 0; } } } else { ydelta = -ydelta; i = vt->backscroll_height; if (i > vt->total_height - vt->height) { i = vt->total_height - vt->height; } y1 = vt->y_base - i; if (y1 < 0) { y1 += vt->total_height; } for (i = 0; i < ydelta; i++) { if (vt->y_displayed == y1) { break; } if (--vt->y_displayed < 0) { vt->y_displayed = vt->total_height - 1; } } } vt100_refresh(vt); } static void vt100_write(QemuVT100 *vt, const void *buf, size_t len) { uint32_t num_free; num_free = fifo8_num_free(&vt->out_fifo); fifo8_push_all(&vt->out_fifo, buf, MIN(num_free, len)); vt->out_flush(vt); } void vt100_set_image(QemuVT100 *vt, pixman_image_t *image) { TextCell *cells, *c, *c1; int w1, x, y, last_width, w, h; vt->image = image; w = pixman_image_get_width(vt->image) / FONT_WIDTH; h = pixman_image_get_height(vt->image) / FONT_HEIGHT; if (w == vt->width && h == vt->height) { return; } last_width = vt->width; vt->width = w; vt->height = h; w1 = MIN(vt->width, last_width); cells = g_new(TextCell, vt->width * vt->total_height + 1); for (y = 0; y < vt->total_height; y++) { c = &cells[y * vt->width]; if (w1 > 0) { c1 = &vt->cells[y * last_width]; for (x = 0; x < w1; x++) { *c++ = *c1++; } } for (x = w1; x < vt->width; x++) { c->ch = ' '; c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; c++; } } g_free(vt->cells); vt->cells = cells; } static void vt100_put_lf(QemuVT100 *vt) { TextCell *c; int x, y1; vt->y++; if (vt->y >= vt->height) { vt->y = vt->height - 1; if (vt->y_displayed == vt->y_base) { if (++vt->y_displayed == vt->total_height) { vt->y_displayed = 0; } } if (++vt->y_base == vt->total_height) { vt->y_base = 0; } if (vt->backscroll_height < vt->total_height) { vt->backscroll_height++; } y1 = (vt->y_base + vt->height - 1) % vt->total_height; c = &vt->cells[y1 * vt->width]; for (x = 0; x < vt->width; x++) { c->ch = ' '; c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; c++; } if (vt->y_displayed == vt->y_base) { vt->text_x[0] = 0; vt->text_y[0] = 0; vt->text_x[1] = vt->width - 1; vt->text_y[1] = vt->height - 1; image_bitblt(vt->image, 0, FONT_HEIGHT, 0, 0, vt->width * FONT_WIDTH, (vt->height - 1) * FONT_HEIGHT); image_fill_rect(vt->image, 0, (vt->height - 1) * FONT_HEIGHT, vt->width * FONT_WIDTH, FONT_HEIGHT, color_table_rgb[0][TEXT_ATTRIBUTES_DEFAULT.bgcol]); vt->update_x0 = 0; vt->update_y0 = 0; vt->update_x1 = vt->width * FONT_WIDTH; vt->update_y1 = vt->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 vt100_handle_escape(QemuVT100 *vt) { int i; for (i = 0; i < vt->nb_esc_params; i++) { switch (vt->esc_params[i]) { case 0: /* reset all console attributes to default */ vt->t_attrib = TEXT_ATTRIBUTES_DEFAULT; break; case 1: vt->t_attrib.bold = 1; break; case 4: vt->t_attrib.uline = 1; break; case 5: vt->t_attrib.blink = 1; break; case 7: vt->t_attrib.invers = 1; break; case 8: vt->t_attrib.unvisible = 1; break; case 22: vt->t_attrib.bold = 0; break; case 24: vt->t_attrib.uline = 0; break; case 25: vt->t_attrib.blink = 0; break; case 27: vt->t_attrib.invers = 0; break; case 28: vt->t_attrib.unvisible = 0; break; /* set foreground color */ case 30: vt->t_attrib.fgcol = QEMU_COLOR_BLACK; break; case 31: vt->t_attrib.fgcol = QEMU_COLOR_RED; break; case 32: vt->t_attrib.fgcol = QEMU_COLOR_GREEN; break; case 33: vt->t_attrib.fgcol = QEMU_COLOR_YELLOW; break; case 34: vt->t_attrib.fgcol = QEMU_COLOR_BLUE; break; case 35: vt->t_attrib.fgcol = QEMU_COLOR_MAGENTA; break; case 36: vt->t_attrib.fgcol = QEMU_COLOR_CYAN; break; case 37: vt->t_attrib.fgcol = QEMU_COLOR_WHITE; break; /* set background color */ case 40: vt->t_attrib.bgcol = QEMU_COLOR_BLACK; break; case 41: vt->t_attrib.bgcol = QEMU_COLOR_RED; break; case 42: vt->t_attrib.bgcol = QEMU_COLOR_GREEN; break; case 43: vt->t_attrib.bgcol = QEMU_COLOR_YELLOW; break; case 44: vt->t_attrib.bgcol = QEMU_COLOR_BLUE; break; case 45: vt->t_attrib.bgcol = QEMU_COLOR_MAGENTA; break; case 46: vt->t_attrib.bgcol = QEMU_COLOR_CYAN; break; case 47: vt->t_attrib.bgcol = QEMU_COLOR_WHITE; break; } } } static void vt100_update_xy(QemuVT100 *vt, int x, int y) { TextCell *c; int y1, y2; vt->text_x[0] = MIN(vt->text_x[0], x); vt->text_x[1] = MAX(vt->text_x[1], x); vt->text_y[0] = MIN(vt->text_y[0], y); vt->text_y[1] = MAX(vt->text_y[1], y); y1 = (vt->y_base + y) % vt->total_height; y2 = y1 - vt->y_displayed; if (y2 < 0) { y2 += vt->total_height; } if (y2 < vt->height) { if (x >= vt->width) { x = vt->width - 1; } c = &vt->cells[y1 * vt->width + x]; vt100_putcharxy(vt, x, y2, c->ch, &(c->t_attrib)); vt100_invalidate_xy(vt, x, y2); } } static void vt100_clear_xy(QemuVT100 *vt, int x, int y) { int y1 = (vt->y_base + y) % vt->total_height; if (x >= vt->width) { x = vt->width - 1; } TextCell *c = &vt->cells[y1 * vt->width + x]; c->ch = ' '; c->t_attrib = TEXT_ATTRIBUTES_DEFAULT; vt100_update_xy(vt, x, y); } /* * UTF-8 DFA decoder by Bjoern Hoehrmann. * Copyright (c) 2008-2010 Bjoern Hoehrmann * See https://github.com/polijan/utf8_decode for details. * * SPDX-License-Identifier: MIT */ #define BH_UTF8_ACCEPT 0 #define BH_UTF8_REJECT 12 static uint32_t bh_utf8_decode(uint32_t *state, uint32_t *codep, uint8_t byte) { static const uint8_t utf8d[] = { /* character class lookup */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, /* state transition lookup */ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,12,12,12,12,12, }; uint32_t type = utf8d[byte]; *codep = (*state != BH_UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xffu >> type) & (byte); *state = utf8d[256 + *state + type]; return *state; } static void vt100_put_one(QemuVT100 *vt, uint8_t ch) { TextCell *c; int y1; if (vt->x >= vt->width) { /* line wrap */ vt->x = 0; vt100_put_lf(vt); } y1 = (vt->y_base + vt->y) % vt->total_height; c = &vt->cells[y1 * vt->width + vt->x]; c->ch = ch; c->t_attrib = vt->t_attrib; vt100_update_xy(vt, vt->x, vt->y); vt->x++; } /* set cursor, checking bounds */ static void vt100_set_cursor(QemuVT100 *vt, int x, int y) { if (x < 0) { x = 0; } if (y < 0) { y = 0; } if (y >= vt->height) { y = vt->height - 1; } if (x >= vt->width) { x = vt->width - 1; } vt->x = x; vt->y = y; } /** * vt100_csi_P() - (DCH) deletes one or more characters from the cursor * position to the right. As characters are deleted, the remaining * characters between the cursor and right margin move to the * left. Character attributes move with the characters. */ static void vt100_csi_P(QemuVT100 *vt, unsigned int nr) { TextCell *c1, *c2; unsigned int x1, x2, y; unsigned int end, len; if (!nr) { nr = 1; } if (nr > vt->width - vt->x) { nr = vt->width - vt->x; if (!nr) { return; } } x1 = vt->x; x2 = vt->x + nr; len = vt->width - x2; if (len) { y = (vt->y_base + vt->y) % vt->total_height; c1 = &vt->cells[y * vt->width + x1]; c2 = &vt->cells[y * vt->width + x2]; memmove(c1, c2, len * sizeof(*c1)); for (end = x1 + len; x1 < end; x1++) { vt100_update_xy(vt, x1, vt->y); } } /* Clear the rest */ for (; x1 < vt->width; x1++) { vt100_clear_xy(vt, x1, vt->y); } } /** * vt100_csi_at() - (ICH) inserts `nr` blank characters with the default * character attribute. The cursor remains at the beginning of the * blank characters. Text between the cursor and right margin moves to * the right. Characters scrolled past the right margin are lost. */ static void vt100_csi_at(QemuVT100 *vt, unsigned int nr) { TextCell *c1, *c2; unsigned int x1, x2, y; unsigned int end, len; if (!nr) { nr = 1; } if (nr > vt->width - vt->x) { nr = vt->width - vt->x; if (!nr) { return; } } x1 = vt->x + nr; x2 = vt->x; len = vt->width - x1; if (len) { y = (vt->y_base + vt->y) % vt->total_height; c1 = &vt->cells[y * vt->width + x1]; c2 = &vt->cells[y * vt->width + x2]; memmove(c1, c2, len * sizeof(*c1)); for (end = x1 + len; x1 < end; x1++) { vt100_update_xy(vt, x1, vt->y); } } /* Insert blanks */ for (x1 = vt->x; x1 < vt->x + nr; x1++) { vt100_clear_xy(vt, x1, vt->y); } } /** * vt100_save_cursor() - saves cursor position and character attributes. */ static void vt100_save_cursor(QemuVT100 *vt) { vt->x_saved = vt->x; vt->y_saved = vt->y; vt->t_attrib_saved = vt->t_attrib; } /** * vt100_restore_cursor() - restores cursor position and character * attributes from saved state. */ static void vt100_restore_cursor(QemuVT100 *vt) { vt->x = vt->x_saved; vt->y = vt->y_saved; vt->t_attrib = vt->t_attrib_saved; } static void vt100_putchar(QemuVT100 *vt, uint8_t ch) { int i; int x, y; g_autofree char *response = NULL; switch (vt->state) { case TTY_STATE_NORM: if (ch >= 0x80 && vt->encoding == CHARDEV_VC_ENCODING_UTF8) { switch (bh_utf8_decode(&vt->utf8_state, &vt->utf8_codepoint, ch)) { case BH_UTF8_ACCEPT: vt100_put_one(vt, unicode_to_cp437(vt->utf8_codepoint)); break; case BH_UTF8_REJECT: vt->utf8_state = BH_UTF8_ACCEPT; break; default: break; } break; } vt->utf8_state = BH_UTF8_ACCEPT; switch (ch) { case '\r': /* carriage return */ vt->x = 0; break; case '\n': /* newline */ vt100_put_lf(vt); break; case '\b': /* backspace */ if (vt->x > 0) { vt->x--; } break; case '\t': /* tabspace */ if (vt->x + (8 - (vt->x % 8)) > vt->width) { vt->x = 0; vt100_put_lf(vt); } else { vt->x = vt->x + (8 - (vt->x % 8)); } break; case '\a': /* alert aka. bell */ /* TODO: has to be implemented */ break; case 14: /* SO (shift out), character set 1 (ignored) */ break; case 15: /* SI (shift in), character set 0 (ignored) */ break; case 27: /* esc (introducing an escape sequence) */ vt->state = TTY_STATE_ESC; break; default: vt100_put_one(vt, 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++) { vt->esc_params[i] = 0; } vt->nb_esc_params = 0; vt->state = TTY_STATE_CSI; } else if (ch == '(') { vt->state = TTY_STATE_G0; } else if (ch == ')') { vt->state = TTY_STATE_G1; } else if (ch == ']' || ch == 'P' || ch == 'X' || ch == '^' || ch == '_') { /* String sequences: OSC, DCS, SOS, PM, APC */ vt->state = TTY_STATE_OSC; } else if (ch == '7') { vt100_save_cursor(vt); vt->state = TTY_STATE_NORM; } else if (ch == '8') { vt100_restore_cursor(vt); vt->state = TTY_STATE_NORM; } else { vt->state = TTY_STATE_NORM; } break; case TTY_STATE_CSI: /* handle escape sequence parameters */ if (ch >= '0' && ch <= '9') { if (vt->nb_esc_params < MAX_ESC_PARAMS) { int *param = &vt->esc_params[vt->nb_esc_params]; int digit = (ch - '0'); *param = (*param <= (INT_MAX - digit) / 10) ? *param * 10 + digit : INT_MAX; } } else { if (vt->nb_esc_params < MAX_ESC_PARAMS) { vt->nb_esc_params++; } if (ch == ';' || ch == '?') { break; } trace_console_putchar_csi(vt->esc_params[0], vt->esc_params[1], ch, vt->nb_esc_params); vt->state = TTY_STATE_NORM; switch (ch) { case 'A': /* move cursor up */ if (vt->esc_params[0] == 0) { vt->esc_params[0] = 1; } vt100_set_cursor(vt, vt->x, vt->y - vt->esc_params[0]); break; case 'B': /* move cursor down */ if (vt->esc_params[0] == 0) { vt->esc_params[0] = 1; } vt100_set_cursor(vt, vt->x, vt->y + vt->esc_params[0]); break; case 'C': /* move cursor right */ if (vt->esc_params[0] == 0) { vt->esc_params[0] = 1; } vt100_set_cursor(vt, vt->x + vt->esc_params[0], vt->y); break; case 'D': /* move cursor left */ if (vt->esc_params[0] == 0) { vt->esc_params[0] = 1; } vt100_set_cursor(vt, vt->x - vt->esc_params[0], vt->y); break; case 'G': /* move cursor to column */ vt100_set_cursor(vt, vt->esc_params[0] - 1, vt->y); break; case 'f': case 'H': /* move cursor to row, column */ vt100_set_cursor(vt, vt->esc_params[1] - 1, vt->esc_params[0] - 1); break; case 'J': switch (vt->esc_params[0]) { case 0: /* clear to end of screen */ for (y = vt->y; y < vt->height; y++) { for (x = 0; x < vt->width; x++) { if (y == vt->y && x < vt->x) { continue; } vt100_clear_xy(vt, x, y); } } break; case 1: /* clear from beginning of screen */ for (y = 0; y <= vt->y; y++) { for (x = 0; x < vt->width; x++) { if (y == vt->y && x > vt->x) { break; } vt100_clear_xy(vt, x, y); } } break; case 2: /* clear entire screen */ for (y = 0; y < vt->height; y++) { for (x = 0; x < vt->width; x++) { vt100_clear_xy(vt, x, y); } } break; } break; case 'K': switch (vt->esc_params[0]) { case 0: /* clear to eol */ for (x = vt->x; x < vt->width; x++) { vt100_clear_xy(vt, x, vt->y); } break; case 1: /* clear from beginning of line */ for (x = 0; x <= vt->x && x < vt->width; x++) { vt100_clear_xy(vt, x, vt->y); } break; case 2: /* clear entire line */ for (x = 0; x < vt->width; x++) { vt100_clear_xy(vt, x, vt->y); } break; } break; case 'P': vt100_csi_P(vt, vt->esc_params[0]); break; case 'm': vt100_handle_escape(vt); break; case 'n': switch (vt->esc_params[0]) { case 5: /* report console status (always succeed)*/ vt100_write(vt, "\033[0n", 4); break; case 6: /* report cursor position */ response = g_strdup_printf("\033[%d;%dR", vt->y + 1, vt->x + 1); vt100_write(vt, response, strlen(response)); break; } break; case 's': vt100_save_cursor(vt); break; case 'u': vt100_restore_cursor(vt); break; case '@': vt100_csi_at(vt, vt->esc_params[0]); break; default: trace_console_putchar_unhandled(ch); break; } break; } break; case TTY_STATE_OSC: /* Operating System Command: ESC ] ... BEL/ST */ if (ch == '\a') { /* BEL terminates OSC */ vt->state = TTY_STATE_NORM; } else if (ch == 27) { /* ESC might start ST (ESC \) */ vt->state = TTY_STATE_ESC; } /* All other bytes are silently consumed */ break; case TTY_STATE_G0: /* set character sets */ case TTY_STATE_G1: /* set character sets */ switch (ch) { case 'B': /* Latin-1 map */ break; } vt->state = TTY_STATE_NORM; break; } } size_t vt100_input(QemuVT100 *vt, const uint8_t *buf, size_t len) { int i; vt->update_x0 = vt->width * FONT_WIDTH; vt->update_y0 = vt->height * FONT_HEIGHT; vt->update_x1 = 0; vt->update_y1 = 0; vt100_show_cursor(vt, 0); for (i = 0; i < len; i++) { vt100_putchar(vt, buf[i]); } vt100_show_cursor(vt, 1); if (vt->update_x0 < vt->update_x1) { vt100_image_update(vt, vt->update_x0, vt->update_y0, vt->update_x1 - vt->update_x0, vt->update_y1 - vt->update_y0); } return len; } void vt100_keysym(QemuVT100 *vt, int keysym) { uint8_t buf[16], *q; int c; switch (keysym) { case QEMU_KEY_CTRL_UP: vt100_scroll(vt, -1); break; case QEMU_KEY_CTRL_DOWN: vt100_scroll(vt, 1); break; case QEMU_KEY_CTRL_PAGEUP: vt100_scroll(vt, -10); break; case QEMU_KEY_CTRL_PAGEDOWN: vt100_scroll(vt, 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 (vt->echo && (keysym == '\r' || keysym == '\n')) { vt100_input(vt, (uint8_t *)"\r", 1); *q++ = '\n'; } else { *q++ = keysym; } if (vt->echo) { vt100_input(vt, buf, q - buf); } vt100_write(vt, buf, q - buf); break; } } void vt100_update_cursor(void) { QemuVT100 *vt; cursor_visible_phase = !cursor_visible_phase; if (QTAILQ_EMPTY(&vt100s)) { return; } QTAILQ_FOREACH(vt, &vt100s, list) { vt100_refresh(vt); } timer_mod(cursor_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2); } static void cursor_timer_cb(void *opaque) { vt100_update_cursor(); } void vt100_init(QemuVT100 *vt, pixman_image_t *image, ChardevVCEncoding encoding, void (*image_update)(QemuVT100 *vt, int x, int y, int w, int h), void (*out_flush)(QemuVT100 *vt)) { if (!cursor_timer) { cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cursor_timer_cb, NULL); } vt->encoding = encoding; QTAILQ_INSERT_HEAD(&vt100s, vt, list); fifo8_create(&vt->out_fifo, 16); vt->total_height = DEFAULT_BACKSCROLL; vt->image_update = image_update; vt->out_flush = out_flush; /* set current text attributes to default */ vt->t_attrib = TEXT_ATTRIBUTES_DEFAULT; vt100_set_image(vt, image); } void vt100_fini(QemuVT100 *vt) { if (!QTAILQ_IN_USE(vt, list)) { return; } QTAILQ_REMOVE(&vt100s, vt, list); fifo8_destroy(&vt->out_fifo); g_free(vt->cells); }