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