| /* |
| * 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_select(QemuTextConsole *c) |
| { |
| dpy_text_resize(QEMU_CONSOLE(c), c->width, c->height); |
| qemu_text_console_update_cursor(); |
| } |
| |
| 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 = qemu_console_get_width(NULL, 80 * FONT_WIDTH); |
| height = qemu_console_get_height(NULL, 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); |
| } |
| } |