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