1// Copyright 2016 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <assert.h> 6#include <fcntl.h> 7#include <stdlib.h> 8#include <string.h> 9#include <sys/param.h> 10 11#include <zircon/process.h> 12#include <zircon/syscalls.h> 13 14#include <fbl/auto_lock.h> 15 16#include "vc.h" 17 18static uint32_t default_palette[] = { 19 // 0-7 Normal/dark versions of colors 20 0xff000000, // black 21 0xffaa0000, // red 22 0xff00aa00, // green 23 0xffaa5500, // brown 24 0xff0000aa, // blue 25 0xffaa00aa, // zircon 26 0xff00aaaa, // cyan 27 0xffaaaaaa, // grey 28 // 8-15 Bright/light versions of colors 29 0xff555555, // dark grey 30 0xffff5555, // bright red 31 0xff55ff55, // bright green 32 0xffffff55, // yellow 33 0xff5555ff, // bright blue 34 0xffff55ff, // bright zircon 35 0xff55ffff, // bright cyan 36 0xffffffff, // white 37}; 38 39#define DEFAULT_FRONT_COLOR 0x0 // black 40#define DEFAULT_BACK_COLOR 0xf // white 41 42#define SPECIAL_FRONT_COLOR 0xf // white 43#define SPECIAL_BACK_COLOR 0x4 // blue 44 45// Default height/width (in px) of console before any displays are 46// attached, since we need somewhere to put any data that is recieved. 47#define DEFAULT_WIDTH 1024 48#define DEFAULT_HEIGHT 768 49#define SCROLLBACK_ROWS 1024 // TODO make configurable 50 51#define ABS(val) (((val) >= 0) ? (val) : -(val)) 52 53// shared with vc-gfx.cpp 54extern gfx_surface* vc_gfx; 55extern gfx_surface* vc_tb_gfx; 56extern const gfx_font* vc_font; 57 58static zx_status_t vc_setup(vc_t* vc, bool special) { 59 // calculate how many rows/columns we have 60 vc->rows = DEFAULT_HEIGHT / vc->charh; 61 vc->columns = DEFAULT_WIDTH / vc->charw; 62 vc->scrollback_rows_max = SCROLLBACK_ROWS; 63 vc->scrollback_rows_count = 0; 64 vc->scrollback_offset = 0; 65 66 // allocate the text buffer 67 vc->text_buf = reinterpret_cast<vc_char_t*>( 68 calloc(1, vc->rows * vc->columns * sizeof(vc_char_t))); 69 if (!vc->text_buf) 70 return ZX_ERR_NO_MEMORY; 71 72 // allocate the scrollback buffer 73 vc->scrollback_buf = reinterpret_cast<vc_char_t*>( 74 calloc(1, vc->scrollback_rows_max * vc->columns * sizeof(vc_char_t))); 75 if (!vc->scrollback_buf) { 76 free(vc->text_buf); 77 return ZX_ERR_NO_MEMORY; 78 } 79 80 // set up the default palette 81 memcpy(&vc->palette, default_palette, sizeof(default_palette)); 82 if (special) { 83 vc->front_color = SPECIAL_FRONT_COLOR; 84 vc->back_color = SPECIAL_BACK_COLOR; 85 } else { 86 vc->front_color = DEFAULT_FRONT_COLOR; 87 vc->back_color = DEFAULT_BACK_COLOR; 88 } 89 90 return ZX_OK; 91} 92 93static void vc_invalidate(void* cookie, int x0, int y0, int w, int h) { 94 vc_t* vc = reinterpret_cast<vc_t*>(cookie); 95 96 if (!g_vc_owns_display || !vc->active || !vc_gfx) { 97 return; 98 } 99 100 assert(h >= 0); 101 int y1 = y0 + h; 102 assert(y0 <= static_cast<int>(vc->rows)); 103 assert(y1 <= static_cast<int>(vc->rows)); 104 105 // Clip the y range so that we don't unnecessarily draw characters 106 // outside the visible range, and so that we don't draw characters into 107 // the bottom margin. 108 int visible_y0 = vc->viewport_y; 109 int visible_y1 = vc->viewport_y + vc_rows(vc); 110 y0 = MAX(y0, visible_y0); 111 y1 = MIN(y1, visible_y1); 112 113 for (int y = y0; y < y1; y++) { 114 if (y < 0) { 115 // Scrollback row. 116 vc_char_t* row = vc_get_scrollback_line_ptr( 117 vc, y + vc->scrollback_rows_count); 118 for (int x = x0; x < x0 + w; x++) { 119 vc_gfx_draw_char(vc, row[x], x, y - vc->viewport_y, 120 /* invert= */ false); 121 } 122 } else { 123 // Row in the main console region (non-scrollback). 124 vc_char_t* row = &vc->text_buf[y * vc->columns]; 125 for (int x = x0; x < x0 + w; x++) { 126 // Check whether we should display the cursor at this 127 // position. Note that it's possible that the cursor is 128 // outside the display area (vc->cursor_x == 129 // vc->columns). In that case, we won't display the 130 // cursor, even if there's a margin. This matches 131 // gnome-terminal. 132 bool invert = (!vc->hide_cursor && 133 static_cast<unsigned>(x) == vc->cursor_x && 134 static_cast<unsigned>(y) == vc->cursor_y); 135 vc_gfx_draw_char(vc, row[x], x, y - vc->viewport_y, invert); 136 } 137 } 138 } 139} 140 141// implement tc callbacks: 142 143static inline void vc_invalidate_lines(vc_t* vc, int y, int h) { 144 if (y < vc->invy0) { 145 vc->invy0 = y; 146 } 147 y += h; 148 if (y > vc->invy1) { 149 vc->invy1 = y; 150 } 151} 152 153static void vc_tc_invalidate(void* cookie, int x0, int y0, int w, int h){ 154 vc_t* vc = reinterpret_cast<vc_t*>(cookie); 155 vc_invalidate(cookie, x0, y0, w, h); 156 vc_invalidate_lines(vc, y0, h); 157} 158 159static void vc_tc_movecursor(void* cookie, int x, int y) { 160 vc_t* vc = reinterpret_cast<vc_t*>(cookie); 161 unsigned old_x = vc->cursor_x; 162 unsigned old_y = vc->cursor_y; 163 vc->cursor_x = x; 164 vc->cursor_y = y; 165 if (g_vc_owns_display && vc->active && !vc->hide_cursor) { 166 // Clear the cursor from its old position. 167 vc_invalidate(cookie, old_x, old_y, 1, 1); 168 vc_invalidate_lines(vc, old_y, 1); 169 170 // Display the cursor in its new position. 171 vc_invalidate(cookie, vc->cursor_x, vc->cursor_y, 1, 1); 172 vc_invalidate_lines(vc, vc->cursor_y, 1); 173 } 174} 175 176static void vc_tc_scrollback_buffer_push(vc_t* vc, vc_char_t* src) { 177 unsigned dest_row; 178 assert(vc->scrollback_rows_count <= vc->scrollback_rows_max); 179 if (vc->scrollback_rows_count < vc->scrollback_rows_max) { 180 // Add a row without dropping any existing rows. 181 assert(vc->scrollback_offset == 0); 182 dest_row = vc->scrollback_rows_count++; 183 } else { 184 // Add a row and drop an existing row. 185 assert(vc->scrollback_offset < vc->scrollback_rows_max); 186 dest_row = vc->scrollback_offset++; 187 if (vc->scrollback_offset == vc->scrollback_rows_max) 188 vc->scrollback_offset = 0; 189 } 190 vc_char_t* dst = &vc->scrollback_buf[dest_row * vc->columns]; 191 memcpy(dst, src, vc->columns * sizeof(vc_char_t)); 192} 193 194static void vc_tc_push_scrollback_line(void* cookie, int y) { 195 vc_t* vc = reinterpret_cast<vc_t*>(cookie); 196 vc_char_t* src = &vc->text_buf[y * vc->columns]; 197 vc_tc_scrollback_buffer_push(vc, src); 198 199 // If we're displaying only the main console region (and no 200 // scrollback), then keep displaying that (i.e. don't modify 201 // viewport_y). 202 if (vc->viewport_y < 0) { 203 // We are displaying some of the scrollback buffer. 204 if (vc->viewport_y > -static_cast<int>(vc->scrollback_rows_max)) { 205 // Scroll the viewport to continue displaying the same point in 206 // the scrollback buffer. 207 --vc->viewport_y; 208 } else { 209 // We were displaying the line at the top of the scrollback 210 // buffer, but we dropped that line from the buffer. We could 211 // leave the display as it was (which is what gnome-terminal 212 // does) and not scroll the display. However, that causes 213 // problems. If the user later scrolls down, we won't 214 // necessarily be able to display the lines below -- we might 215 // have dropped those too. So, instead, let's scroll the 216 // display and remove the scrollback line that was lost. 217 // 218 // For simplicity, fall back to redrawing everything. 219 vc_invalidate(vc, 0, -vc->scrollback_rows_max, 220 vc->columns, vc_rows(vc)); 221 vc_render(vc); 222 } 223 } 224} 225 226static void vc_set_cursor_hidden(vc_t* vc, bool hide) { 227 if (vc->hide_cursor == hide) 228 return; 229 vc->hide_cursor = hide; 230 if (g_vc_owns_display && vc->active) { 231 vc_invalidate(vc, vc->cursor_x, vc->cursor_y, 1, 1); 232 vc_invalidate_lines(vc, vc->cursor_y, 1); 233 } 234} 235 236static void vc_tc_copy_lines(void* cookie, int y_dest, int y_src, int line_count) { 237 vc_t* vc = reinterpret_cast<vc_t*>(cookie); 238 239 if (vc->viewport_y < 0) { 240 tc_copy_lines(&vc->textcon, y_dest, y_src, line_count); 241 242 // The viewport is scrolled. For simplicity, fall back to 243 // redrawing all of the non-scrollback lines in this case. 244 int rows = vc_rows(vc); 245 vc_invalidate(vc, 0, 0, vc->columns, rows); 246 vc_invalidate_lines(vc, 0, rows); 247 return; 248 } 249 250 // Remove the cursor from the display before copying the lines on 251 // screen, otherwise we might be copying a rendering of the cursor to a 252 // position where the cursor isn't. This must be done before the 253 // tc_copy_lines() call, otherwise we might render the wrong character. 254 bool old_hide_cursor = vc->hide_cursor; 255 if (g_vc_owns_display && vc->active) { 256 vc_set_cursor_hidden(vc, true); 257 } 258 259 // The next two calls can be done in any order. 260 tc_copy_lines(&vc->textcon, y_dest, y_src, line_count); 261 262 if (g_vc_owns_display && vc->active && vc_gfx) { 263 gfx_copyrect(vc_gfx, 0, y_src * vc->charh, 264 vc_gfx->width, line_count * vc->charh, 265 0, y_dest * vc->charh); 266 267 // Restore the cursor. 268 vc_set_cursor_hidden(vc, old_hide_cursor); 269 270 vc_status_update(); 271 vc_gfx_invalidate_status(); 272 vc_invalidate_lines(vc, 0, vc_rows(vc)); 273 } 274} 275 276static void vc_tc_setparam(void* cookie, int param, uint8_t* arg, size_t arglen) { 277 vc_t* vc = reinterpret_cast<vc_t*>(cookie); 278 switch (param) { 279 case TC_SET_TITLE: 280 strncpy(vc->title, (char*)arg, sizeof(vc->title)); 281 vc->title[sizeof(vc->title) - 1] = '\0'; 282 vc_status_update(); 283 if (g_vc_owns_display && vc_gfx) { 284 vc_gfx_invalidate_status(); 285 } 286 break; 287 case TC_SHOW_CURSOR: 288 vc_set_cursor_hidden(vc, false); 289 break; 290 case TC_HIDE_CURSOR: 291 vc_set_cursor_hidden(vc, true); 292 break; 293 default:; // nothing 294 } 295} 296 297static void vc_clear_gfx(vc_t* vc) { 298 // Fill display with background color 299 if (g_vc_owns_display && vc->active && vc_gfx) { 300 gfx_fillrect(vc_gfx, 0, 0, vc_gfx->width, vc_gfx->height, 301 palette_to_color(vc, vc->back_color)); 302 } 303} 304 305static void vc_reset(vc_t* vc) { 306 // reset the cursor 307 vc->cursor_x = 0; 308 vc->cursor_y = 0; 309 // reset the viewport position 310 vc->viewport_y = 0; 311 312 tc_init(&vc->textcon, vc->columns, vc_rows(vc), vc->text_buf, 313 vc->front_color, vc->back_color, vc->cursor_x, vc->cursor_y); 314 vc->textcon.cookie = vc; 315 vc->textcon.invalidate = vc_tc_invalidate; 316 vc->textcon.movecursor = vc_tc_movecursor; 317 vc->textcon.push_scrollback_line = vc_tc_push_scrollback_line; 318 vc->textcon.copy_lines = vc_tc_copy_lines; 319 vc->textcon.setparam = vc_tc_setparam; 320 321 // fill textbuffer with blank characters 322 size_t count = vc->rows * vc->columns; 323 vc_char_t* ptr = vc->text_buf; 324 while (count--) { 325 *ptr++ = vc_char_make(' ', vc->front_color, vc->back_color); 326 } 327 328 vc_clear_gfx(vc); 329 if (vc_gfx) { 330 vc_gfx_invalidate_all(vc); 331 } 332} 333 334void vc_status_clear() { 335 if (g_vc_owns_display && vc_gfx) { 336 gfx_fillrect(vc_tb_gfx, 0, 0, 337 vc_tb_gfx->width, vc_tb_gfx->height, 338 default_palette[STATUS_COLOR_BG]); 339 } 340} 341 342void vc_status_commit() { 343 if (g_vc_owns_display && vc_gfx) { 344 vc_gfx_invalidate_status(); 345 } 346} 347 348void vc_status_write(int x, unsigned color, const char* text) { 349 char c; 350 unsigned fg = default_palette[color]; 351 unsigned bg = default_palette[STATUS_COLOR_BG]; 352 353 if (g_vc_owns_display && vc_gfx) { 354 x *= vc_font->width; 355 while ((c = *text++) != 0) { 356 gfx_putchar(vc_tb_gfx, vc_font, c, x, 0, fg, bg); 357 x += vc_font->width; 358 } 359 } 360} 361 362void vc_render(vc_t* vc) { 363 if (g_vc_owns_display && vc->active && vc_gfx) { 364 vc_status_update(); 365 vc_gfx_invalidate_all(vc); 366 } 367} 368 369void vc_full_repaint(vc_t* vc) { 370 if (g_vc_owns_display && vc_gfx) { 371 vc_clear_gfx(vc); 372 int scrollback_lines = vc_get_scrollback_lines(vc); 373 vc_invalidate(vc, 0, -scrollback_lines, 374 vc->columns, scrollback_lines + vc->rows); 375 } 376} 377 378int vc_get_scrollback_lines(vc_t* vc) { 379 return vc->scrollback_rows_count; 380} 381 382vc_char_t* vc_get_scrollback_line_ptr(vc_t* vc, unsigned row) { 383 assert(row < vc->scrollback_rows_count); 384 row += vc->scrollback_offset; 385 if (row >= vc->scrollback_rows_max) 386 row -= vc->scrollback_rows_max; 387 return &vc->scrollback_buf[row * vc->columns]; 388} 389 390static void vc_scroll_viewport_abs(vc_t* vc, int vpy) { 391 vpy = MIN(vpy, 0); 392 vpy = MAX(vpy, -vc_get_scrollback_lines(vc)); 393 int diff = vpy - vc->viewport_y; 394 if (diff == 0) 395 return; 396 int diff_abs = ABS(diff); 397 vc->viewport_y = vpy; 398 int rows = vc_rows(vc); 399 if (!g_vc_owns_display || !vc->active || !vc_gfx) { 400 return; 401 } 402 if (diff_abs >= rows) { 403 // We are scrolling the viewport by a large delta. Invalidate all 404 // of the visible area of the console. 405 vc_invalidate(vc, 0, vpy, vc->columns, rows); 406 } else { 407 if (diff > 0) { 408 gfx_copyrect(vc_gfx, 0, diff_abs * vc->charh, 409 vc_gfx->width, (rows - diff_abs) * vc->charh, 0, 0); 410 vc_invalidate(vc, 0, vpy + rows - diff_abs, vc->columns, 411 diff_abs); 412 } else { 413 gfx_copyrect(vc_gfx, 0, 0, vc_gfx->width, 414 (rows - diff_abs) * vc->charh, 0, 415 diff_abs * vc->charh); 416 vc_invalidate(vc, 0, vpy, vc->columns, diff_abs); 417 } 418 } 419 vc_render(vc); 420} 421 422void vc_scroll_viewport(vc_t* vc, int dir) { 423 vc_scroll_viewport_abs(vc, vc->viewport_y + dir); 424} 425 426void vc_scroll_viewport_top(vc_t* vc) { 427 vc_scroll_viewport_abs(vc, INT_MIN); 428} 429 430void vc_scroll_viewport_bottom(vc_t* vc) { 431 vc_scroll_viewport_abs(vc, 0); 432} 433 434void vc_set_fullscreen(vc_t* vc, bool fullscreen) { 435 unsigned flags; 436 if (fullscreen) { 437 flags = vc->flags | VC_FLAG_FULLSCREEN; 438 } else { 439 flags = vc->flags & ~VC_FLAG_FULLSCREEN; 440 } 441 if (flags != vc->flags) { 442 vc->flags = flags; 443 tc_seth(&vc->textcon, vc_rows(vc)); 444 } 445 vc_render(vc); 446} 447 448const gfx_font* vc_get_font() { 449 char* fname = getenv("virtcon.font"); 450 if (fname) { 451 if (!strcmp(fname, "9x16")) { 452 return &font9x16; 453 } else if (!strcmp(fname, "18x32")) { 454 return &font18x32; 455 } else { 456 printf("gfxconsole: no such font '%s'\n", fname); 457 } 458 } 459 return &font9x16; 460} 461 462void vc_attach_gfx(vc_t* vc) { 463 // If the size of the new gfx console doesn't match what we had been 464 // attached to, we need to allocate new memory and copy the existing 465 // data over. 466 unsigned rows = vc_gfx->height / vc->charh; 467 unsigned columns = vc_gfx->width / vc->charw; 468 if (rows == vc->rows && columns == vc->columns) { 469 return; 470 } 471 472 // allocate the new buffers 473 vc_char_t* text_buf = reinterpret_cast<vc_char_t*>( 474 calloc(1, rows * columns * sizeof(vc_char_t))); 475 vc_char_t* scrollback_buf = reinterpret_cast<vc_char_t*>( 476 calloc(1, vc->scrollback_rows_max * columns * sizeof(vc_char_t))); 477 if (text_buf && scrollback_buf) { 478 // fill new text buffer with blank characters 479 size_t count = rows * columns; 480 vc_char_t* ptr = text_buf; 481 while (count--) { 482 *ptr++ = vc_char_make(' ', vc->front_color, vc->back_color); 483 } 484 485 // Copy the most recent data from the old console to the new one. There are 486 // (vc->cursor_y + 1) rows available, and we want (rows - (vc->rows - vc_rows(vc)) 487 // rows. Subtract to get the first row index to copy. 488 unsigned old_i = MAX( 489 static_cast<int>((vc->cursor_y + 1) - (rows - (vc->rows - vc_rows(vc)))), 0); 490 unsigned old_data_start = old_i; 491 unsigned new_i = 0; 492 size_t len = (vc->columns < columns ? vc->columns : columns) * sizeof(vc_char_t); 493 while (new_i < rows && old_i <= vc->cursor_y) { 494 memcpy(text_buf + columns * (new_i++), vc->text_buf + vc->columns * (old_i++), len); 495 } 496 497 // copy the old scrollback buffer 498 for (int i = 0; i < SCROLLBACK_ROWS; i++) { 499 memcpy(scrollback_buf + columns * i, vc->scrollback_buf + vc->columns * i, len); 500 } 501 502 vc_char_t* old_text_buf = vc->text_buf; 503 unsigned old_columns = vc->columns; 504 free(vc->scrollback_buf); 505 506 vc->text_buf = text_buf; 507 vc->scrollback_buf = scrollback_buf; 508 vc->rows = rows; 509 vc->columns = columns; 510 511 // Push any data that fell off of text_buf. Use a temporary buffer of the 512 // right length to handle going to a wider console. Set it to ' 's before 513 // pushing, so we don't merge data from old rows. 514 if (old_data_start) { 515 vc_char_t buf[columns]; 516 for (unsigned i = 0; i < old_data_start; i++) { 517 vc_char_t* ptr = buf; 518 while (ptr < buf + columns) { 519 *ptr++ = vc_char_make(' ', vc->front_color, vc->back_color); 520 } 521 ptr = old_text_buf + i * old_columns; 522 memcpy(buf, ptr, len); 523 524 vc_tc_scrollback_buffer_push(vc, buf); 525 } 526 } 527 528 free(old_text_buf); 529 } else { 530 // If we failed to allocate new buffers, use the old ones as best we can 531 free(text_buf); 532 free(scrollback_buf); 533 534 vc->rows = MIN(vc->rows, rows); 535 vc->columns = MIN(vc->columns, columns); 536 537 printf("vc: buffer resize failed, reusing old buffers (%dx%d)\n", vc->rows, vc->columns); 538 } 539 540 vc->viewport_y = 0; 541 if (vc->cursor_x >= vc->columns) { 542 vc->cursor_x = vc->columns - 1; 543 } 544 if (static_cast<int>(vc->cursor_y) >= vc_rows(vc)) { 545 vc->cursor_y = vc_rows(vc) - 1; 546 } 547 548 tc_init(&vc->textcon, vc->columns, vc_rows(vc), vc->text_buf, 549 vc->front_color, vc->back_color, vc->cursor_x, vc->cursor_y); 550} 551 552zx_status_t vc_alloc(vc_t** out, bool special) { 553 vc_t* vc = 554 reinterpret_cast<vc_t*>(calloc(1, sizeof(vc_t))); 555 if (!vc) { 556 return ZX_ERR_NO_MEMORY; 557 } 558 vc->fd = -1; 559 560 vc->keymap = qwerty_map; 561 char* keys = getenv("virtcon.keymap"); 562 if (keys) { 563 if (!strcmp(keys, "qwerty")) { 564 vc->keymap = qwerty_map; 565 } else if (!strcmp(keys, "dvorak")) { 566 vc->keymap = dvorak_map; 567 } else { 568 printf("gfxconsole: no such keymap '%s'\n", keys); 569 } 570 } 571 572 vc->font = vc_get_font(); 573 vc->charw = vc->font->width; 574 vc->charh = vc->font->height; 575 576 zx_status_t status = vc_setup(vc, special); 577 if (status != ZX_OK) { 578 free(vc); 579 return status; 580 } 581 582 if (vc_gfx) { 583 vc_attach_gfx(vc); 584 } 585 vc_reset(vc); 586 587 *out = vc; 588 return ZX_OK; 589} 590 591void vc_free(vc_t* vc) { 592 if (vc->fd >= 0) { 593 close(vc->fd); 594 } 595 free(vc->text_buf); 596 free(vc->scrollback_buf); 597 free(vc); 598} 599 600void vc_flush(vc_t* vc) { 601 if (g_vc_owns_display && vc_gfx && vc->invy1 >= 0) { 602 int rows = vc_rows(vc); 603 // Adjust for the current viewport position. Convert 604 // console-relative row numbers to screen-relative row numbers. 605 int invalidate_y0 = MIN(vc->invy0 - vc->viewport_y, rows); 606 int invalidate_y1 = MIN(vc->invy1 - vc->viewport_y, rows); 607 vc_gfx_invalidate(vc, 0, invalidate_y0, 608 vc->columns, invalidate_y1 - invalidate_y0); 609 } 610} 611 612void vc_flush_all(vc_t* vc) { 613 if (g_vc_owns_display && vc_gfx) { 614 vc_gfx_invalidate_all(vc); 615 } 616} 617