| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/param.h> |
| |
| #include <zircon/device/display.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| |
| #include <fbl/auto_lock.h> |
| |
| #include "vc.h" |
| |
| static uint32_t default_palette[] = { |
| // 0-7 Normal/dark versions of colors |
| 0xff000000, // black |
| 0xffaa0000, // red |
| 0xff00aa00, // green |
| 0xffaa5500, // brown |
| 0xff0000aa, // blue |
| 0xffaa00aa, // zircon |
| 0xff00aaaa, // cyan |
| 0xffaaaaaa, // grey |
| // 8-15 Bright/light versions of colors |
| 0xff555555, // dark grey |
| 0xffff5555, // bright red |
| 0xff55ff55, // bright green |
| 0xffffff55, // yellow |
| 0xff5555ff, // bright blue |
| 0xffff55ff, // bright zircon |
| 0xff55ffff, // bright cyan |
| 0xffffffff, // white |
| }; |
| |
| #define DEFAULT_FRONT_COLOR 0x0 // black |
| #define DEFAULT_BACK_COLOR 0xf // white |
| |
| #define SPECIAL_FRONT_COLOR 0xf // white |
| #define SPECIAL_BACK_COLOR 0x4 // blue |
| |
| #define SCROLLBACK_ROWS 1024 // TODO make configurable |
| |
| #define ABS(val) (((val) >= 0) ? (val) : -(val)) |
| |
| // shared with vc-gfx.cpp |
| extern gfx_surface* vc_gfx; |
| extern gfx_surface* vc_tb_gfx; |
| extern const gfx_font* vc_font; |
| |
| static zx_status_t vc_setup(vc_t* vc, bool special) { |
| // calculate how many rows/columns we have |
| vc->rows = vc_gfx->height / vc->charh; |
| vc->columns = vc_gfx->width / vc->charw; |
| vc->scrollback_rows_max = SCROLLBACK_ROWS; |
| vc->scrollback_rows_count = 0; |
| vc->scrollback_offset = 0; |
| |
| // allocate the text buffer |
| vc->text_buf = reinterpret_cast<vc_char_t*>( |
| calloc(1, vc->rows * vc->columns * sizeof(vc_char_t))); |
| if (!vc->text_buf) |
| return ZX_ERR_NO_MEMORY; |
| |
| // allocate the scrollback buffer |
| vc->scrollback_buf = reinterpret_cast<vc_char_t*>( |
| calloc(1, vc->scrollback_rows_max * vc->columns * sizeof(vc_char_t))); |
| if (!vc->scrollback_buf) { |
| free(vc->text_buf); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // set up the default palette |
| memcpy(&vc->palette, default_palette, sizeof(default_palette)); |
| if (special) { |
| vc->front_color = SPECIAL_FRONT_COLOR; |
| vc->back_color = SPECIAL_BACK_COLOR; |
| } else { |
| vc->front_color = DEFAULT_FRONT_COLOR; |
| vc->back_color = DEFAULT_BACK_COLOR; |
| } |
| |
| return ZX_OK; |
| } |
| |
| static void vc_invalidate(void* cookie, int x0, int y0, int w, int h) { |
| vc_t* vc = reinterpret_cast<vc_t*>(cookie); |
| |
| if (!vc->active) { |
| return; |
| } |
| |
| assert(h >= 0); |
| int y1 = y0 + h; |
| assert(y0 <= static_cast<int>(vc->rows)); |
| assert(y1 <= static_cast<int>(vc->rows)); |
| |
| // Clip the y range so that we don't unnecessarily draw characters |
| // outside the visible range, and so that we don't draw characters into |
| // the bottom margin. |
| int visible_y0 = vc->viewport_y; |
| int visible_y1 = vc->viewport_y + vc_rows(vc); |
| y0 = MAX(y0, visible_y0); |
| y1 = MIN(y1, visible_y1); |
| |
| for (int y = y0; y < y1; y++) { |
| if (y < 0) { |
| // Scrollback row. |
| vc_char_t* row = vc_get_scrollback_line_ptr( |
| vc, y + vc->scrollback_rows_count); |
| for (int x = x0; x < x0 + w; x++) { |
| vc_gfx_draw_char(vc, row[x], x, y - vc->viewport_y, |
| /* invert= */ false); |
| } |
| } else { |
| // Row in the main console region (non-scrollback). |
| vc_char_t* row = &vc->text_buf[y * vc->columns]; |
| for (int x = x0; x < x0 + w; x++) { |
| // Check whether we should display the cursor at this |
| // position. Note that it's possible that the cursor is |
| // outside the display area (vc->cursor_x == |
| // vc->columns). In that case, we won't display the |
| // cursor, even if there's a margin. This matches |
| // gnome-terminal. |
| bool invert = (!vc->hide_cursor && |
| static_cast<unsigned>(x) == vc->cursor_x && |
| static_cast<unsigned>(y) == vc->cursor_y); |
| vc_gfx_draw_char(vc, row[x], x, y - vc->viewport_y, invert); |
| } |
| } |
| } |
| } |
| |
| // implement tc callbacks: |
| |
| static inline void vc_invalidate_lines(vc_t* vc, int y, int h) { |
| if (y < vc->invy0) { |
| vc->invy0 = y; |
| } |
| y += h; |
| if (y > vc->invy1) { |
| vc->invy1 = y; |
| } |
| } |
| |
| static void vc_tc_invalidate(void* cookie, int x0, int y0, int w, int h){ |
| vc_t* vc = reinterpret_cast<vc_t*>(cookie); |
| vc_invalidate(cookie, x0, y0, w, h); |
| vc_invalidate_lines(vc, y0, h); |
| } |
| |
| static void vc_tc_movecursor(void* cookie, int x, int y) { |
| vc_t* vc = reinterpret_cast<vc_t*>(cookie); |
| unsigned old_x = vc->cursor_x; |
| unsigned old_y = vc->cursor_y; |
| vc->cursor_x = x; |
| vc->cursor_y = y; |
| if (vc->active && !vc->hide_cursor) { |
| // Clear the cursor from its old position. |
| vc_invalidate(cookie, old_x, old_y, 1, 1); |
| vc_invalidate_lines(vc, old_y, 1); |
| |
| // Display the cursor in its new position. |
| vc_invalidate(cookie, vc->cursor_x, vc->cursor_y, 1, 1); |
| vc_invalidate_lines(vc, vc->cursor_y, 1); |
| } |
| } |
| |
| static void vc_tc_push_scrollback_line(void* cookie, int y) { |
| vc_t* vc = reinterpret_cast<vc_t*>(cookie); |
| |
| unsigned dest_row; |
| assert(vc->scrollback_rows_count <= vc->scrollback_rows_max); |
| if (vc->scrollback_rows_count < vc->scrollback_rows_max) { |
| // Add a row without dropping any existing rows. |
| assert(vc->scrollback_offset == 0); |
| dest_row = vc->scrollback_rows_count++; |
| } else { |
| // Add a row and drop an existing row. |
| assert(vc->scrollback_offset < vc->scrollback_rows_max); |
| dest_row = vc->scrollback_offset++; |
| if (vc->scrollback_offset == vc->scrollback_rows_max) |
| vc->scrollback_offset = 0; |
| } |
| vc_char_t* dst = &vc->scrollback_buf[dest_row * vc->columns]; |
| vc_char_t* src = &vc->text_buf[y * vc->columns]; |
| memcpy(dst, src, vc->columns * sizeof(vc_char_t)); |
| |
| // If we're displaying only the main console region (and no |
| // scrollback), then keep displaying that (i.e. don't modify |
| // viewport_y). |
| if (vc->viewport_y < 0) { |
| // We are displaying some of the scrollback buffer. |
| if (vc->viewport_y > -static_cast<int>(vc->scrollback_rows_max)) { |
| // Scroll the viewport to continue displaying the same point in |
| // the scrollback buffer. |
| --vc->viewport_y; |
| } else { |
| // We were displaying the line at the top of the scrollback |
| // buffer, but we dropped that line from the buffer. We could |
| // leave the display as it was (which is what gnome-terminal |
| // does) and not scroll the display. However, that causes |
| // problems. If the user later scrolls down, we won't |
| // necessarily be able to display the lines below -- we might |
| // have dropped those too. So, instead, let's scroll the |
| // display and remove the scrollback line that was lost. |
| // |
| // For simplicity, fall back to redrawing everything. |
| vc_invalidate(vc, 0, -vc->scrollback_rows_max, |
| vc->columns, vc_rows(vc)); |
| vc_render(vc); |
| } |
| } |
| } |
| |
| static void vc_set_cursor_hidden(vc_t* vc, bool hide) { |
| if (vc->hide_cursor == hide) |
| return; |
| vc->hide_cursor = hide; |
| if (vc->active) { |
| vc_invalidate(vc, vc->cursor_x, vc->cursor_y, 1, 1); |
| vc_invalidate_lines(vc, vc->cursor_y, 1); |
| } |
| } |
| |
| static void vc_tc_copy_lines(void* cookie, int y_dest, int y_src, int line_count) { |
| vc_t* vc = reinterpret_cast<vc_t*>(cookie); |
| |
| if (vc->viewport_y < 0) { |
| tc_copy_lines(&vc->textcon, y_dest, y_src, line_count); |
| |
| // The viewport is scrolled. For simplicity, fall back to |
| // redrawing all of the non-scrollback lines in this case. |
| int rows = vc_rows(vc); |
| vc_invalidate(vc, 0, 0, vc->columns, rows); |
| vc_invalidate_lines(vc, 0, rows); |
| return; |
| } |
| |
| // Remove the cursor from the display before copying the lines on |
| // screen, otherwise we might be copying a rendering of the cursor to a |
| // position where the cursor isn't. This must be done before the |
| // tc_copy_lines() call, otherwise we might render the wrong character. |
| bool old_hide_cursor = vc->hide_cursor; |
| if (vc->active) { |
| vc_set_cursor_hidden(vc, true); |
| } |
| |
| // The next two calls can be done in any order. |
| tc_copy_lines(&vc->textcon, y_dest, y_src, line_count); |
| |
| if (vc->active) { |
| gfx_copyrect(vc_gfx, 0, y_src * vc->charh, |
| vc_gfx->width, line_count * vc->charh, |
| 0, y_dest * vc->charh); |
| |
| // Restore the cursor. |
| vc_set_cursor_hidden(vc, old_hide_cursor); |
| |
| vc_status_update(); |
| vc_gfx_invalidate_status(); |
| vc_invalidate_lines(vc, 0, vc_rows(vc)); |
| } |
| } |
| |
| static void vc_tc_setparam(void* cookie, int param, uint8_t* arg, size_t arglen) { |
| vc_t* vc = reinterpret_cast<vc_t*>(cookie); |
| switch (param) { |
| case TC_SET_TITLE: |
| strncpy(vc->title, (char*)arg, sizeof(vc->title)); |
| vc->title[sizeof(vc->title) - 1] = '\0'; |
| vc_status_update(); |
| vc_gfx_invalidate_status(); |
| break; |
| case TC_SHOW_CURSOR: |
| vc_set_cursor_hidden(vc, false); |
| break; |
| case TC_HIDE_CURSOR: |
| vc_set_cursor_hidden(vc, true); |
| break; |
| default:; // nothing |
| } |
| } |
| |
| static void vc_clear_gfx(vc_t* vc) { |
| // Fill display with background color |
| if (vc->active) { |
| gfx_fillrect(vc_gfx, 0, 0, vc_gfx->width, vc_gfx->height, |
| palette_to_color(vc, vc->back_color)); |
| } |
| } |
| |
| static void vc_reset(vc_t* vc) { |
| // reset the cursor |
| vc->cursor_x = 0; |
| vc->cursor_y = 0; |
| // reset the viewport position |
| vc->viewport_y = 0; |
| |
| tc_init(&vc->textcon, vc->columns, vc_rows(vc), vc->text_buf, vc->front_color, vc->back_color); |
| vc->textcon.cookie = vc; |
| vc->textcon.invalidate = vc_tc_invalidate; |
| vc->textcon.movecursor = vc_tc_movecursor; |
| vc->textcon.push_scrollback_line = vc_tc_push_scrollback_line; |
| vc->textcon.copy_lines = vc_tc_copy_lines; |
| vc->textcon.setparam = vc_tc_setparam; |
| |
| // fill textbuffer with blank characters |
| size_t count = vc->rows * vc->columns; |
| vc_char_t* ptr = vc->text_buf; |
| while (count--) { |
| *ptr++ = vc_char_make(' ', vc->front_color, vc->back_color); |
| } |
| |
| vc_clear_gfx(vc); |
| vc_gfx_invalidate_all(vc); |
| } |
| |
| |
| void vc_status_clear() { |
| gfx_fillrect(vc_tb_gfx, 0, 0, |
| vc_tb_gfx->width, vc_tb_gfx->height, |
| default_palette[STATUS_COLOR_BG]); |
| } |
| |
| void vc_status_write(int x, unsigned color, const char* text) { |
| char c; |
| unsigned fg = default_palette[color]; |
| unsigned bg = default_palette[STATUS_COLOR_BG]; |
| |
| x *= vc_font->width; |
| while ((c = *text++) != 0) { |
| gfx_putchar(vc_tb_gfx, vc_font, c, x, 0, fg, bg); |
| x += vc_font->width; |
| } |
| } |
| |
| void vc_render(vc_t* vc) { |
| if (vc->active) { |
| vc_status_update(); |
| vc_gfx_invalidate_all(vc); |
| } |
| } |
| |
| void vc_full_repaint(vc_t* vc) { |
| vc_clear_gfx(vc); |
| int scrollback_lines = vc_get_scrollback_lines(vc); |
| vc_invalidate(vc, 0, -scrollback_lines, |
| vc->columns, scrollback_lines + vc->rows); |
| } |
| |
| int vc_get_scrollback_lines(vc_t* vc) { |
| return vc->scrollback_rows_count; |
| } |
| |
| vc_char_t* vc_get_scrollback_line_ptr(vc_t* vc, unsigned row) { |
| assert(row < vc->scrollback_rows_count); |
| row += vc->scrollback_offset; |
| if (row >= vc->scrollback_rows_max) |
| row -= vc->scrollback_rows_max; |
| return &vc->scrollback_buf[row * vc->columns]; |
| } |
| |
| static void vc_scroll_viewport_abs(vc_t* vc, int vpy) { |
| vpy = MIN(vpy, 0); |
| vpy = MAX(vpy, -vc_get_scrollback_lines(vc)); |
| int diff = vpy - vc->viewport_y; |
| if (diff == 0) |
| return; |
| int diff_abs = ABS(diff); |
| vc->viewport_y = vpy; |
| int rows = vc_rows(vc); |
| if (!vc->active) { |
| return; |
| } |
| if (diff_abs >= rows) { |
| // We are scrolling the viewport by a large delta. Invalidate all |
| // of the visible area of the console. |
| vc_invalidate(vc, 0, vpy, vc->columns, rows); |
| } else { |
| if (diff > 0) { |
| gfx_copyrect(vc_gfx, 0, diff_abs * vc->charh, |
| vc_gfx->width, (rows - diff_abs) * vc->charh, 0, 0); |
| vc_invalidate(vc, 0, vpy + rows - diff_abs, vc->columns, |
| diff_abs); |
| } else { |
| gfx_copyrect(vc_gfx, 0, 0, vc_gfx->width, |
| (rows - diff_abs) * vc->charh, 0, |
| diff_abs * vc->charh); |
| vc_invalidate(vc, 0, vpy, vc->columns, diff_abs); |
| } |
| } |
| vc_render(vc); |
| } |
| |
| void vc_scroll_viewport(vc_t* vc, int dir) { |
| vc_scroll_viewport_abs(vc, vc->viewport_y + dir); |
| } |
| |
| void vc_scroll_viewport_top(vc_t* vc) { |
| vc_scroll_viewport_abs(vc, INT_MIN); |
| } |
| |
| void vc_scroll_viewport_bottom(vc_t* vc) { |
| vc_scroll_viewport_abs(vc, 0); |
| } |
| |
| void vc_set_fullscreen(vc_t* vc, bool fullscreen) { |
| unsigned flags; |
| if (fullscreen) { |
| flags = vc->flags | VC_FLAG_FULLSCREEN; |
| } else { |
| flags = vc->flags & ~VC_FLAG_FULLSCREEN; |
| } |
| if (flags != vc->flags) { |
| vc->flags = flags; |
| tc_seth(&vc->textcon, vc_rows(vc)); |
| } |
| vc_render(vc); |
| } |
| |
| const gfx_font* vc_get_font() { |
| char* fname = getenv("virtcon.font"); |
| if (fname) { |
| if (!strcmp(fname, "9x16")) { |
| return &font9x16; |
| } else if (!strcmp(fname, "18x32")) { |
| return &font18x32; |
| } else { |
| printf("gfxconsole: no such font '%s'\n", fname); |
| } |
| } |
| return &font9x16; |
| } |
| |
| zx_status_t vc_alloc(vc_t** out, bool special) { |
| vc_t* vc = |
| reinterpret_cast<vc_t*>(calloc(1, sizeof(vc_t))); |
| if (!vc) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| vc->fd = -1; |
| |
| vc->keymap = qwerty_map; |
| char* keys = getenv("virtcon.keymap"); |
| if (keys) { |
| if (!strcmp(keys, "qwerty")) { |
| vc->keymap = qwerty_map; |
| } else if (!strcmp(keys, "dvorak")) { |
| vc->keymap = dvorak_map; |
| } else { |
| printf("gfxconsole: no such keymap '%s'\n", keys); |
| } |
| } |
| |
| vc->font = vc_get_font(); |
| vc->charw = vc->font->width; |
| vc->charh = vc->font->height; |
| |
| vc_setup(vc, special); |
| vc_reset(vc); |
| |
| *out = vc; |
| return ZX_OK; |
| } |
| |
| void vc_free(vc_t* vc) { |
| if (vc->fd >= 0) { |
| close(vc->fd); |
| } |
| free(vc->text_buf); |
| free(vc->scrollback_buf); |
| free(vc); |
| } |
| |