#include "terminal.h" #include #include using namespace godot; // Use xterm default for default color palette. const uint8_t Terminal::default_color_palette[TSM_COLOR_NUM][3] = { [TSM_COLOR_BLACK] = {0x00, 0x00, 0x00}, [TSM_COLOR_RED] = {0x80, 0x00, 0x00}, [TSM_COLOR_GREEN] = {0x00, 0x80, 0x00}, [TSM_COLOR_YELLOW] = {0x80, 0x80, 0x00}, [TSM_COLOR_BLUE] = {0x00, 0x00, 0x80}, [TSM_COLOR_MAGENTA] = {0x80, 0x00, 0x80}, [TSM_COLOR_CYAN] = {0x00, 0x80, 0x80}, [TSM_COLOR_LIGHT_GREY] = {0xc0, 0xc0, 0xc0}, [TSM_COLOR_DARK_GREY] = {0x80, 0x80, 0x80}, [TSM_COLOR_LIGHT_RED] = {0xff, 0x00, 0x00}, [TSM_COLOR_LIGHT_GREEN] = {0x00, 0xff, 0x00}, [TSM_COLOR_LIGHT_YELLOW] = {0xff, 0xff, 0x00}, [TSM_COLOR_LIGHT_BLUE] = {0x00, 0x00, 0xff}, [TSM_COLOR_LIGHT_MAGENTA] = {0xff, 0x00, 0xff}, [TSM_COLOR_LIGHT_CYAN] = {0x00, 0xff, 0xff}, [TSM_COLOR_WHITE] = {0xff, 0xff, 0xff}, [TSM_COLOR_FOREGROUND] = {0xff, 0xff, 0xff}, [TSM_COLOR_BACKGROUND] = {0x00, 0x00, 0x00}, }; static struct { Color col; bool is_set; } colours[16]; static void term_output(const char *s, size_t len, void *user) { } static void write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data) { Terminal *term = static_cast(data); } static int text_draw_cb(struct tsm_screen *con, uint64_t id, const uint32_t *ch, size_t len, unsigned int width, unsigned int posx, unsigned int posy, const struct tsm_screen_attr *attr, tsm_age_t age, void *data) { Terminal *terminal = static_cast(data); if (age <= terminal->framebuffer_age) return 0; size_t ulen; char buf[5] = {0}; if (len > 0) { char *utf8 = tsm_ucs4_to_utf8_alloc(ch, len, &ulen); memcpy(terminal->cells[posy][posx].ch, utf8, ulen); } else { terminal->cells[posy][posx] = {}; } memcpy(&terminal->cells[posy][posx].attr, attr, sizeof(tsm_screen_attr)); if (!terminal->sleep) terminal->update(); return 0; } void Terminal::_register_methods() { register_method("_init", &Terminal::_init); register_method("_ready", &Terminal::_ready); register_method("_input", &Terminal::_input); register_method("_draw", &Terminal::_draw); register_method("write", &Terminal::write); register_method("update_size", &Terminal::update_size); //register_property("rows", &Terminal::rows, 24); //register_property("cols", &Terminal::cols, 80); } Terminal::Terminal() { } Terminal::~Terminal() { } void Terminal::_init() { sleep = true; if (tsm_screen_new(&screen, NULL, NULL)) { ERR_PRINT("Error creating new tsm screen"); } tsm_screen_set_max_sb(screen, 1000); // TODO: Use config var for scrollback size. if (tsm_vte_new(&vte, screen, write_cb, this, NULL, NULL)) { ERR_PRINT("Error creating new tsm vte"); } update_color_palette(); if (tsm_vte_set_custom_palette(vte, color_palette)) { ERR_PRINT("Error setting custom palette"); } if (tsm_vte_set_palette(vte, "custom")) { ERR_PRINT("Error setting palette"); } update_size(); } void Terminal::_ready() { connect("resized", this, "update_size"); } void Terminal::_notification(int what) { switch (what) { case NOTIFICATION_RESIZED: Godot::print("resized!"); update_size(); break; } } void Terminal::_input(Variant event) { } void Terminal::_draw() { if (sleep) return; /* Draw the background */ draw_rect(get_rect(), get_color("Background", "Terminal")); /* Draw the cell backgrounds */ // Draw the background first so subsequent rows don't overlap // foreground characters such as y that may extend below the baseline. for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { draw_background(row, col, get_cell_colors(row, col).first); } } /* Draw the cell foregrounds */ for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { draw_foreground(row, col, get_cell_colors(row, col).second); } } } void Terminal::update_color_palette() { // Start with a copy of the default color palette memcpy(color_palette, Terminal::default_color_palette, sizeof(Terminal::default_color_palette)); /* Generate color palette based on theme */ // Converts a color from the Control's theme to one that can // be used in a tsm color palette. auto set_pallete_color = [this](tsm_vte_color color, String theme_color) -> void { Color c = get_color(theme_color, "Terminal"); uint32_t argb32 = c.to_ARGB32(); color_palette[color][0] = (argb32 >> (8 * 0)) & 0xff; color_palette[color][1] = (argb32 >> (8 * 1)) & 0xff; color_palette[color][2] = (argb32 >> (8 * 2)) & 0xff; }; set_pallete_color(TSM_COLOR_BLACK, "Black"); set_pallete_color(TSM_COLOR_RED, "Red"); set_pallete_color(TSM_COLOR_GREEN, "Green"); set_pallete_color(TSM_COLOR_YELLOW, "Yellow"); set_pallete_color(TSM_COLOR_BLUE, "Blue"); set_pallete_color(TSM_COLOR_MAGENTA, "Magenta"); set_pallete_color(TSM_COLOR_CYAN, "Cyan"); set_pallete_color(TSM_COLOR_LIGHT_GREY, "Light Grey"); set_pallete_color(TSM_COLOR_DARK_GREY, "Dark Grey"); set_pallete_color(TSM_COLOR_LIGHT_RED, "Light Red"); set_pallete_color(TSM_COLOR_LIGHT_GREEN, "Light Green"); set_pallete_color(TSM_COLOR_LIGHT_YELLOW, "Light Yellow"); set_pallete_color(TSM_COLOR_LIGHT_BLUE, "Light Blue"); set_pallete_color(TSM_COLOR_LIGHT_MAGENTA, "Light Magenta"); set_pallete_color(TSM_COLOR_WHITE, "White"); set_pallete_color(TSM_COLOR_BACKGROUND, "Background"); set_pallete_color(TSM_COLOR_FOREGROUND, "Foreground"); } void Terminal::draw_background(int row, int col, Color bgcolor) { /* Draw the background */ Vector2 background_pos = Vector2(col * cell_size.x, row * cell_size.y); Rect2 background_rect = Rect2(background_pos, cell_size); draw_rect(background_rect, bgcolor); } void Terminal::draw_foreground(int row, int col, Color fgcolor) { struct cell cell = cells[row][col]; /* Set the font */ Ref fontref = get_font(""); if (cell.attr.bold && cell.attr.italic) { fontref = get_font("Bold Italic", "Terminal"); } else if (cell.attr.bold) { fontref = get_font("Bold", "Terminal"); } else if (cell.attr.italic) { fontref = get_font("Italic", "Terminal"); } else { fontref = get_font("Regular", "Terminal"); } /* Draw the foreground */ if (cell.ch == nullptr) return; // No foreground to draw if (cell.attr.blink) ; // TODO: Handle blink int font_height = fontref.ptr()->get_height(); Vector2 foreground_pos = Vector2(col * cell_size.x, row * cell_size.y + font_height); draw_string(fontref, foreground_pos, cell.ch, fgcolor); if (cell.attr.underline) draw_string(fontref, foreground_pos, "_", fgcolor); } std::pair Terminal::get_cell_colors(int row, int col) { struct cell cell = cells[row][col]; Color fgcol, bgcol; float fr = 1, fg = 1, fb = 1, br = 0, bg = 0, bb = 0; Ref fontref = get_font(""); /* Get foreground color */ if (cell.attr.fccode && palette.count(cell.attr.fccode)) { fgcol = palette[cell.attr.fccode]; } else { fr = (float)cell.attr.fr / 255.0; fg = (float)cell.attr.fg / 255.0; fb = (float)cell.attr.fb / 255.0; fgcol = Color(fr, fg, fb); if (cell.attr.fccode) { palette.insert(std::pair(cell.attr.fccode, Color(fr, fg, fb))); } } /* Get background color */ if (cell.attr.bccode && palette.count(cell.attr.bccode)) { bgcol = palette[cell.attr.bccode]; } else { br = (float)cell.attr.br / 255.0; bg = (float)cell.attr.bg / 255.0; bb = (float)cell.attr.bb / 255.0; bgcol = Color(br, bg, bb); if (cell.attr.bccode) { palette.insert(std::pair(cell.attr.bccode, Color(br, bg, bb))); } } if (cell.attr.inverse) std::swap(bgcol, fgcol); return std::make_pair(bgcol, fgcol); } // Recalculates the cell_size and number of cols/rows based on font size and the Control's rect_size void Terminal::update_size() { sleep = true; cell_size = get_font("Regular", "Terminal").ptr()->get_string_size("W"); rows = std::max(2, (int)floor(get_rect().size.y / cell_size.y)); cols = std::max(1, (int)floor(get_rect().size.x / cell_size.x)); Godot::print(String("resized_rows: {0}, resized_cols: {1}").format(Array::make(rows, cols))); cells = {}; for (int x = 0; x < rows; x++) { Row row(cols); for (int y = 0; y < cols; y++) { row[y] = empty_cell; } cells.push_back(row); } tsm_screen_resize(screen, cols, rows); sleep = false; update(); } void Terminal::write(PoolByteArray data) { tsm_vte_input(vte, (char *)data.read().ptr(), data.size()); framebuffer_age = tsm_screen_draw(screen, text_draw_cb, this); }