| /* |
| * 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, int 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 <bjoern@hoehrmann.de> |
| * 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, uint32_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, int 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, int 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) |
| { |
| QTAILQ_REMOVE(&vt100s, vt, list); |
| fifo8_destroy(&vt->out_fifo); |
| g_free(vt->cells); |
| } |