// 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 "textcon.h" #include #include #include static inline void invalidate(textcon_t* tc, int x, int y, int w, int h) { tc->invalidate(tc->cookie, x, y, w, h); } static inline void movecursor(textcon_t* tc, int x, int y) { tc->movecursor(tc->cookie, x, y); } static inline void push_scrollback_line(textcon_t* tc, int y) { tc->push_scrollback_line(tc->cookie, y); } static inline void setparam(textcon_t* tc, int param, uint8_t* arg, size_t arglen) { tc->setparam(tc->cookie, param, arg, arglen); } // Construct a vc_char_t from the given character using the current colors. static inline vc_char_t make_vc_char(textcon_t* tc, uint8_t ch) { return (vc_char_t)(ch | (((vc_char_t)tc->fg & 15) << 8) | (((vc_char_t)tc->bg & 15) << 12)); } static vc_char_t* dataxy(textcon_t* tc, int x, int y) { assert(x >= 0); assert(x < tc->w); assert(y >= 0); assert(y < tc->h); return tc->data + y * tc->w + x; } static vc_char_t* get_start_of_line(textcon_t* tc, int y) { assert(y >= 0); assert(y <= tc->h); return tc->data + y * tc->w; } static int clampx(textcon_t* tc, int x) { return x < 0 ? 0 : x >= tc->w ? tc->w - 1 : x; } static int clampxatedge(textcon_t* tc, int x) { return x < 0 ? 0 : x > tc->w ? tc->w : x; } static int clampy(textcon_t* tc, int y) { return y < 0 ? 0 : y >= tc->h ? tc->h - 1 : y; } static void moveto(textcon_t* tc, int x, int y) { tc->x = clampx(tc, x); tc->y = clampy(tc, y); } static inline void moverel(textcon_t* tc, int dx, int dy) { moveto(tc, tc->x + dx, tc->y + dy); } static void fill(vc_char_t* ptr, vc_char_t val, size_t count) { while (count-- > 0) { *ptr++ = val; } } static void erase_region(textcon_t* tc, int x0, int y0, int x1, int y1) { if (x0 >= tc->w) { return; } x1 = clampx(tc, x1); vc_char_t* ptr = dataxy(tc, x0, y0); vc_char_t* end = dataxy(tc, x1, y1) + 1; fill(ptr, make_vc_char(tc, ' '), end - ptr); invalidate(tc, x0, y0, x1 - x0 + 1, y1 - y0 + 1); } static void erase_screen(textcon_t* tc, int arg) { switch (arg) { case 0: // erase downward erase_region(tc, tc->x, tc->y, tc->w - 1, tc->h - 1); break; case 1: // erase upward erase_region(tc, 0, 0, tc->x, tc->y); break; case 2: // erase all erase_region(tc, 0, 0, tc->w - 1, tc->h - 1); break; } } static void erase_line(textcon_t* tc, int arg) { switch (arg) { case 0: // erase to eol erase_region(tc, tc->x, tc->y, tc->w - 1, tc->y); break; case 1: // erase from bol erase_region(tc, 0, tc->y, tc->x, tc->y); break; case 2: // erase line erase_region(tc, 0, tc->y, tc->w - 1, tc->y); break; } } static void erase_chars(textcon_t* tc, int arg) { if (tc->x >= tc->w) { return; } if (arg < 0) { arg = 0; } if (arg > tc->w) { arg = tc->w; } vc_char_t* dst = dataxy(tc, tc->x, tc->y); vc_char_t* src = dataxy(tc, tc->x + arg, tc->y); vc_char_t* end = dataxy(tc, tc->x + tc->w, tc->y); while (src < end) { *dst++ = *src++; } while (dst < end) { *dst++ = make_vc_char(tc, ' '); } invalidate(tc, tc->x, tc->y, tc->w - tc->x, 1); } void tc_copy_lines(textcon_t* tc, int y_dest, int y_src, int line_count) { vc_char_t* dest = get_start_of_line(tc, y_dest); vc_char_t* src = get_start_of_line(tc, y_src); memmove(dest, src, line_count * tc->w * sizeof(vc_char_t)); } static void clear_lines(textcon_t* tc, int y, int line_count) { fill(get_start_of_line(tc, y), make_vc_char(tc, ' '), line_count * tc->w); invalidate(tc, 0, y, tc->w, line_count); } // Scroll the region between line |y0| (inclusive) and |y1| (exclusive). // Scroll by |diff| lines, which may be positive (for moving lines up) or // negative (for moving lines down). static void scroll_lines(textcon_t* tc, int y0, int y1, int diff) { int delta = diff > 0 ? diff : -diff; if (delta > y1 - y0) delta = y1 - y0; int copy_count = y1 - y0 - delta; if (diff > 0) { // Scroll up. for (int i = 0; i < delta; ++i) { push_scrollback_line(tc, y0 + i); } tc->copy_lines(tc->cookie, y0, y0 + delta, copy_count); clear_lines(tc, y0 + copy_count, delta); } else { // Scroll down. tc->copy_lines(tc->cookie, y0 + delta, y0, copy_count); clear_lines(tc, y0, delta); } } static void scroll_up(textcon_t* tc) { scroll_lines(tc, tc->scroll_y0, tc->scroll_y1, 1); } // positive = up, negative = down static void scroll_at_pos(textcon_t* tc, int dir) { if (tc->y < tc->scroll_y0) return; if (tc->y >= tc->scroll_y1) return; scroll_lines(tc, tc->y, tc->scroll_y1, dir); } void set_scroll(textcon_t* tc, int y0, int y1) { if (y0 > y1) { return; } tc->scroll_y0 = (y0 < 0) ? 0 : y0; tc->scroll_y1 = (y1 > tc->h) ? tc->h : y1; } static void savecursorpos(textcon_t* tc) { tc->save_x = tc->x; tc->save_y = tc->y; } static void restorecursorpos(textcon_t* tc) { tc->x = clampxatedge(tc, tc->save_x); tc->y = clampy(tc, tc->save_y); } static void putc_plain(textcon_t* tc, uint8_t c); static void putc_escape2(textcon_t* tc, uint8_t c); static void putc_ignore(textcon_t* tc, uint8_t c) { tc->putc = putc_plain; } static void putc_param(textcon_t* tc, uint8_t c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tc->num = tc->num * 10 + (c - '0'); return; case ';': if (tc->argn_count < TC_MAX_ARG) { tc->argn[tc->argn_count++] = tc->num; } tc->putc = putc_escape2; break; default: if (tc->argn_count < TC_MAX_ARG) { tc->argn[tc->argn_count++] = tc->num; } tc->putc = putc_escape2; putc_escape2(tc, c); break; } } #define ARG0(def) ((tc->argn_count > 0) ? tc->argn[0] : (def)) #define ARG1(def) ((tc->argn_count > 1) ? tc->argn[1] : (def)) static void putc_dec(textcon_t* tc, uint8_t c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tc->num = tc->num * 10 + (c - '0'); return; case 'h': if (tc->num == 25) setparam(tc, TC_SHOW_CURSOR, NULL, 0); break; case 'l': if (tc->num == 25) setparam(tc, TC_HIDE_CURSOR, NULL, 0); break; default: putc_plain(tc, c); break; } tc->putc = putc_plain; } static textcon_param_t osc_to_param(int osc) { switch (osc) { case 2: return TC_SET_TITLE; default: return TC_INVALID; } } static void putc_osc2(textcon_t* tc, uint8_t c) { switch (c) { case 7: { // end command textcon_param_t param = osc_to_param(ARG0(-1)); if (param != TC_INVALID && tc->argstr_size) setparam(tc, param, tc->argstr, tc->argstr_size); tc->putc = putc_plain; break; } default: if (tc->argstr_size < TC_MAX_ARG_LENGTH) tc->argstr[tc->argstr_size++] = c; break; } } static void putc_osc(textcon_t* tc, uint8_t c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tc->num = tc->num * 10 + (c - '0'); return; case ';': if (tc->argn_count < TC_MAX_ARG) { tc->argn[tc->argn_count++] = tc->num; } memset(tc->argstr, 0, TC_MAX_ARG_LENGTH); tc->argstr_size = 0; tc->putc = putc_osc2; break; default: if (tc->argn_count < TC_MAX_ARG) { tc->argn[tc->argn_count++] = tc->num; } tc->putc = putc_osc2; putc_osc2(tc, c); break; } } static void putc_escape2(textcon_t* tc, uint8_t c) { int x, y; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tc->num = c - '0'; tc->putc = putc_param; return; case ';': // end parameter if (tc->argn_count < TC_MAX_ARG) { tc->argn[tc->argn_count++] = 0; } return; case '?': tc->num = 0; tc->argn_count = 0; tc->putc = putc_dec; return; case 'A': // (CUU) Cursor Up moverel(tc, 0, -ARG0(1)); break; case 'B': // (CUD) Cursor Down moverel(tc, 0, ARG0(1)); break; case 'C': // (CUF) Cursor Forward moverel(tc, ARG0(1), 0); break; case 'D': // (CUB) Cursor Backward moverel(tc, -ARG0(1), 0); break; case 'E': moveto(tc, 0, tc->y + ARG0(1)); break; case 'F': moveto(tc, 0, tc->y - ARG0(1)); break; case 'G': // move xpos absolute x = ARG0(1); moveto(tc, x ? (x - 1) : 0, tc->y); break; case 'H': // (CUP) Cursor Position case 'f': // (HVP) Horizontal and Vertical Position x = ARG1(1); y = ARG0(1); moveto(tc, x ? (x - 1) : 0, y ? (y - 1) : 0); break; case 'J': // (ED) erase in display erase_screen(tc, ARG0(0)); break; case 'K': // (EL) erase in line erase_line(tc, ARG0(0)); break; case 'L': // (IL) insert line(s) at cursor scroll_at_pos(tc, -ARG0(1)); break; case 'M': // (DL) delete line(s) at cursor scroll_at_pos(tc, ARG0(1)); break; case 'P': // (DCH) delete character(s) erase_chars(tc, ARG0(1)); break; case 'd': // move ypos absolute y = ARG0(1); moveto(tc, tc->x, y ? (y - 1) : 0); break; case 'm': // (SGR) Character Attributes if (tc->argn_count == 0) { // no params == default param tc->argn[0] = 0; tc->argn_count = 1; } for (int i = 0; i < tc->argn_count; i++) { int n = tc->argn[i]; if ((n >= 30) && (n <= 37)) { // set fg tc->fg = (uint8_t)(n - 30); } else if ((n >= 40) && (n <= 47)) { // set bg tc->bg = (uint8_t)(n - 40); } else if ((n == 1) && (tc->fg <= 7)) { // bold tc->fg = (uint8_t)(tc->fg + 8); } else if (n == 0) { // reset tc->fg = 0; tc->bg = 15; } else if (n == 7) { // reverse uint8_t temp = tc->fg; tc->fg = tc->bg; tc->bg = temp; } else if (n == 39) { // default fg tc->fg = 0; } else if (n == 49) { // default bg tc->bg = 15; } } break; case 'r': // set scroll region set_scroll(tc, ARG0(1) - 1, ARG1(tc->h)); break; case 's': // save cursor position ?? savecursorpos(tc); break; case 'u': // restore cursor position ?? restorecursorpos(tc); break; case '@': // (ICH) Insert Blank Character(s) case 'T': // Initiate Hilight Mouse Tracking (xterm) case 'c': // (DA) Send Device Attributes case 'g': // (TBC) Tab Clear case 'h': // (SM) Set Mode (4=Insert,20=AutoNewline) case 'l': // (RM) Reset Mode (4=Replace,20=NormalLinefeed) case 'n': // (DSR) Device Status Report case 'x': // Request Terminal Parameters default: break; } movecursor(tc, tc->x, tc->y); tc->putc = putc_plain; } static void putc_escape(textcon_t* tc, uint8_t c) { switch (c) { case 27: // escape return; case '(': case ')': case '*': case '+': // select various character sets tc->putc = putc_ignore; return; case '[': tc->num = 0; tc->argn_count = 0; tc->putc = putc_escape2; return; case ']': tc->num = 0; tc->argn_count = 0; tc->putc = putc_osc; return; case '7': // (DECSC) Save Cursor savecursorpos(tc); // save attribute break; case '8': // (DECRC) Restore Cursor restorecursorpos(tc); movecursor(tc, tc->x, tc->y); break; case 'E': // (NEL) Next Line tc->x = 0; __FALLTHROUGH; case 'D': // (IND) Index tc->y++; if (tc->y >= tc->scroll_y1) { tc->y--; scroll_up(tc); } movecursor(tc, tc->x, tc->y); break; case 'M': // (RI) Reverse Index) tc->y--; if (tc->y < tc->scroll_y0) { tc->y++; scroll_at_pos(tc, -1); } movecursor(tc, tc->x, tc->y); break; } tc->putc = putc_plain; } static void putc_cr(textcon_t* tc) { tc->x = 0; } static void putc_lf(textcon_t* tc) { tc->y++; if (tc->y >= tc->scroll_y1) { tc->y--; scroll_up(tc); } } static void putc_plain(textcon_t* tc, uint8_t c) { switch (c) { case 7: // bell break; case 8: // backspace / ^H if (tc->x > 0) tc->x--; break; case 9: // tab / ^I moveto(tc, (tc->x + 8) & (~7), tc->y); break; case 10: // newline putc_cr(tc); // should we imply this? putc_lf(tc); break; case 12: erase_screen(tc, 2); break; case 13: // carriage return putc_cr(tc); break; case 27: // escape tc->putc = putc_escape; return; default: if ((c < ' ') || (c > 127)) { return; } if (tc->x >= tc->w) { // apply deferred line wrap upon printing first character beyond // end of current line putc_cr(tc); putc_lf(tc); } dataxy(tc, tc->x, tc->y)[0] = make_vc_char(tc, c); invalidate(tc, tc->x, tc->y, 1, 1); tc->x++; break; } movecursor(tc, tc->x, tc->y); } void tc_init(textcon_t* tc, int w, int h, vc_char_t* data, uint8_t fg, uint8_t bg, int cursor_x, int cursor_y) { tc->w = w; tc->h = h; tc->x = cursor_x; tc->y = cursor_y; tc->data = data; tc->scroll_y0 = 0; tc->scroll_y1 = h; tc->save_x = 0; tc->save_y = 0; tc->fg = fg; tc->bg = bg; tc->putc = putc_plain; } void tc_seth(textcon_t* tc, int h) { // tc->data must be big enough for the new height int old_h = h; tc->h = h; // move contents int y = 0; if (old_h > h) { vc_char_t* dst = dataxy(tc, 0, tc->scroll_y0); vc_char_t* src = dataxy(tc, 0, tc->scroll_y0 + old_h - h); vc_char_t* end = dataxy(tc, 0, tc->scroll_y1); do { push_scrollback_line(tc, y); } while (++y < old_h - h); memmove(dst, src, (end - dst) * sizeof(vc_char_t)); tc->y -= old_h - h; } else if (old_h < h) { do { fill(dataxy(tc, 0, tc->scroll_y1 + y), make_vc_char(tc, ' '), tc->w); } while (++y < h - old_h); } tc->y = clampy(tc, tc->y); // try to fixup the scroll region if (tc->scroll_y0 >= h) { tc->scroll_y0 = 0; } if (tc->scroll_y1 == old_h) { tc->scroll_y1 = h; } else { tc->scroll_y1 = tc->scroll_y1 >= h ? h : tc->scroll_y1; } invalidate(tc, 0, 0, tc->w, tc->h); movecursor(tc, tc->x, tc->y); }