typedef struct ScreenChar {
wchar ch;
Attr attr;
Color fg_color;
Color bg_color;
} ScreenChar;
typedef struct ScreenState {
ScreenChar* chars;
int w;
int h;
int cx;
int cy;
Attr curr_attr;
Color curr_fg_color;
Color curr_bg_color;
int param_top;
int params[MAX_PARAMS+1];
int in_esc;
int in_osc8;
} ScreenState;
static ScreenState screen;
static int ttyin; // input text and control sequences
static int ttyout; // output for screen dump
static int quiet = 0;
static int verbose = 0;
static int param_pop(void){
if (num_params() == 0)
return -1; // missing param
return screen.params[screen.param_top--];
}
static int screen_x(int x) {
if (x < 0) x = 0;
if (x >= screen.w) x = screen.w-1;
return x;
}
static int screen_y(int y) {
if (y < 0) y = 0;
if (y >= screen.h) y = screen.h-1;
return y;
}
// Return the char at a given screen position.
static ScreenChar* screen_char(int x, int y) {
x = screen_x(x);
y = screen_y(y);
return &screen.chars[y * screen.w + x];
}
// Step the cursor after printing a char.
static int screen_incr(int* px, int* py) {
if (++(*px) >= screen.w) {
*px = 0;
if (++(*py) >= screen.h) {
*py = 0;
return 0;
}
}
return 1;
}
// Set the value, attributes and colors of a char on the screen.
static void screen_char_set(int x, int y, wchar ch, Attr attr, Color fg_color, Color bg_color) {
ScreenChar* sc = screen_char(x, y);
sc->ch = ch;
sc->attr = attr;
sc->fg_color = fg_color;
sc->bg_color = bg_color;
}
static int screen_clear(int x, int y, int count) {
while (count-- > 0) {
screen_char_set(x, y, '_', 0, NULL_COLOR, NULL_COLOR);
screen_incr(&x, &y);
}
return 1;
}
// Print an encoded image of the current screen to ttyout.
// The LTS_CHAR_* metachars encode changes of color and attribute.
static int screen_read(int x, int y, int count) {
Attr attr = 0;
int fg_color = NULL_COLOR;
int bg_color = NULL_COLOR;
while (count-- > 0) {
byte buf[32];
byte* bufp = buf;
ScreenChar* sc = screen_char(x, y);
if (sc->attr != attr) {
attr = sc->attr;
*bufp++ = LTS_CHAR_ATTR;
store_hex(&bufp, attr);
}
if (sc->fg_color != fg_color) {
fg_color = sc->fg_color;
*bufp++ = LTS_CHAR_FG_COLOR;
store_hex(&bufp, fg_color);
}
if (sc->bg_color != bg_color) {
bg_color = sc->bg_color;
*bufp++ = LTS_CHAR_BG_COLOR;
store_hex(&bufp, bg_color);
}
if (x == screen.cx && y == screen.cy)
*bufp++ = LTS_CHAR_CURSOR;
if (sc->ch == '\\' || sc->ch == LTS_CHAR_ATTR || sc->ch == LTS_CHAR_FG_COLOR || sc->ch == LTS_CHAR_BG_COLOR || sc->ch == LTS_CHAR_CURSOR)
*bufp++ = '\\';
store_wchar(&bufp, sc->ch);
write(ttyout, buf, bufp-buf);
screen_incr(&x, &y);
}
write(ttyout, "\n", 1);
return 1;
}
static int screen_move(int x, int y) {
screen.cx = x;
screen.cy = y;
return 1;
}
static int screen_cr(void) {
screen.cx = 0;
return 1;
}
static int screen_bs(void) {
if (screen.cx <= 0) return 0;
--screen.cx;
return 1;
}
static int screen_scroll(void) {
int len = screen.w * (screen.h-1);
memmove(screen_char(0,0), screen_char(0,1), len * sizeof(ScreenChar));
screen_clear(0, screen.h-1, screen.w);
return 1;
}
static int screen_rscroll(void) {
int len = screen.w * (screen.h-1);
memmove(screen_char(0,1), screen_char(0,0), len * sizeof(ScreenChar));
screen_clear(0, 0, screen.w);
return 1;
}
static int screen_set_attr(int attr) {
screen.curr_attr |= attr;
if (verbose) fprintf(stderr, "[%d,%d] set_attr(%d)=%d\n", screen.cx, screen.cy, attr, screen.curr_attr);
return 1;
}
static int screen_clear_attr(int attr) {
screen.curr_attr &= ~attr;
if (verbose) fprintf(stderr, "[%d,%d] clr_attr(%d)=%d\n", screen.cx, screen.cy, attr, screen.curr_attr);
return 1;
}
// ------------------------------------------------------------------
// lt_screen supports certain ANSI color values.
// This simplifies testing SGR sequences with less -R
// compared to inventing custom color sequences.
static int screen_set_color(int color) {
int ret = 0;
switch (color) {
case 1: ret = screen_set_attr(ATTR_BOLD); break;
case 4: ret = screen_set_attr(ATTR_UNDERLINE); break;
case 5:
case 6: ret = screen_set_attr(ATTR_BLINK); break;
case 7: ret = screen_set_attr(ATTR_STANDOUT); break;
case 21:
case 22: ret = screen_clear_attr(ATTR_BOLD); break;
case 24: ret = screen_clear_attr(ATTR_UNDERLINE); break;
case 25: ret = screen_clear_attr(ATTR_BLINK); break;
case 27: ret = screen_clear_attr(ATTR_STANDOUT); break;
// case 38: break;
// case 48: break;
default:
if (color <= 0) {
screen.curr_fg_color = screen.curr_bg_color = NULL_COLOR;
screen.curr_attr = 0;
ret = 1;
} else if ((color >= 30 && color <= 37) || (color >= 90 && color <= 97)) {
screen.curr_fg_color = color;
ret = 1;
} else if ((color >= 40 && color <= 47) || (color >= 100 && color <= 107)) {
screen.curr_bg_color = color;
ret = 1;
} else {
fprintf(stderr, "[%d,%d] unrecognized color %d\n", screen.cx, screen.cy, color);
}
if (verbose) fprintf(stderr, "[%d,%d] set_color(%d)=%d/%d\n", screen.cx, screen.cy, color, screen.curr_fg_color, screen.curr_bg_color);
break;
}
return ret;
}
static void beep(void) {
if (!quiet)
fprintf(stderr, "\7");
}
// Execute an escape sequence ending with a given char.
static int exec_esc(wchar ch) {
int x, y, count;
if (verbose) {
fprintf(stderr, "exec ESC-%c ", (char)ch);
param_print();
fprintf(stderr, "\n");
}
switch (ch) {
case 'A': // clear all
return screen_clear(0, 0, screen.w * screen.h);
case 'L': // clear from cursor to end of line
return screen_clear(screen.cx, screen.cy, screen.w - screen.cx);
case 'S': // clear from cursor to end of screen
return screen_clear(screen.cx, screen.cy,
(screen.w - screen.cx) + (screen.h - screen.cy -1) * screen.w);
case 'R': // read N3 chars starting at (N1,N2)
count = param_pop();
y = param_pop();
x = param_pop();
if (x < 0) x = 0;
if (y < 0) y = 0;
if (count < 0) count = 0;
return screen_read(x, y, count);
case 'j': // jump cursor to (N1,N2)
y = param_pop();
x = param_pop();
if (x < 0) x = 0;
if (y < 0) y = 0;
return screen_move(x, y);
case 'g': // visual bell
return 0;
case 'h': // cursor home
return screen_move(0, 0);
case 'l': // cursor lower left
return screen_move(0, screen.h-1);
case 'r': // reverse scroll
return screen_rscroll();
case '<': // cursor left to start of line
return screen_cr();
case 'e': // exit bold
return screen_clear_attr(ATTR_BOLD);
case 'b': // enter blink
return screen_set_attr(ATTR_BLINK);
case 'c': // exit blink
return screen_clear_attr(ATTR_BLINK);
case 'm': // SGR (Select Graphics Rendition)
if (num_params() == 0) {
screen_set_color(-1);
} else {
while (num_params() > 0)
screen_set_color(param_pop());
}
return 0;
case '?': // print version string
write(ttyout, version, strlen(version));
return 1;
default:
return 0;
}
}
// Print a char on the screen.
// Handles cursor movement and scrolling.
static int add_char(wchar ch) {
//if (verbose) fprintf(stderr, "add (%c) %lx at %d,%d\n", (char)ch, (long)ch, screen.cx, screen.cy);
screen_char_set(screen.cx, screen.cy, ch, screen.curr_attr, screen.curr_fg_color, screen.curr_bg_color);
int fits = 1;
int zero_width = (is_composing_char(ch) ||
(screen.cx > 0 && is_combining_char(screen_char(screen.cx-1,screen.cy)->ch, ch)));
if (!zero_width) {
fits = screen_incr(&screen.cx, &screen.cy);
if (fits) {
if (is_wide_char(ch)) {
// The "shadow" is the second column used by a wide char.
screen_char_set(screen.cx, screen.cy, WIDESHADOW_CHAR, 0, NULL_COLOR, NULL_COLOR);
fits = screen_incr(&screen.cx, &screen.cy);
} else {
ScreenChar* sc = screen_char(screen.cx, screen.cy);
if (sc->ch == WIDESHADOW_CHAR) {
// We overwrote the first half of a wide character.
// Change the orphaned shadow to an error char.
screen_char_set(screen.cx, screen.cy, ERROR_CHAR, screen.curr_attr, NULL_COLOR, NULL_COLOR);
}
}
}
}
if (!fits) { // Wrap at bottom of screen = scroll
screen.cx = 0;
screen.cy = screen.h-1;
return screen_scroll();
}
return 1;
}
// Remember the last few chars sent to the screen.
static void add_last(wchar ch) {
lastch[lastch_curr++] = ch;
if (lastch_curr >= NUM_LASTCH) lastch_curr = 0;
}
// Do the last entered characters match a string?
static int last_matches_str(char const* str) {
int ci = lastch_curr;
int si;
for (si = strlen(str)-1; si >= 0; --si) {
ci = (ci > 0) ? ci-1 : NUM_LASTCH-1;
if (str[si] != lastch[ci]) return 0;
}
return 1;
}
// Do the last entered characters match any one of a list of strings?
static int last_matches(const char* const* tbl) {
int ti;
for (ti = 0; tbl[ti] != NULL; ++ti) {
if (last_matches_str(tbl[ti]))
return 1;
}
return 0;
}
// Handle a char sent to the screen while it is receiving an escape sequence.
static int process_esc(wchar ch) {
int ok = 1;
if (screen.in_osc8) {
if (last_matches(osc8_end)) {
screen.in_osc8 = screen.in_esc = 0;
} else {
// Discard everything between osc8_start and osc8_end.
}
} else if (last_matches(osc8_start)) {
param_pop(); // pop the '8'
screen.in_osc8 = 1;
} else if (ch >= '0' && ch <= '9') {
int d = (num_params() == 0) ? 0 : screen.params[screen.param_top--];
param_push(10 * d + ch - '0');
} else if (ch == ';') {
param_push(0);
} else if (ch == '[' || ch == ']') {
; // Ignore ANSI marker
} else { // end of escape sequence
screen.in_esc = 0;
ok = exec_esc(ch);
param_clear();
}
return ok;
}
// Handle a char sent to the screen.
// Normally it is just printed, but some control chars are handled specially.
static int process_char(wchar ch) {
int ok = 1;
add_last(ch);
if (screen.in_esc) {
ok = process_esc(ch);
} else if (ch == ESC) {
screen.in_esc = 1;
} else if (ch == '\r') {
screen_cr();
} else if (ch == '\b') {
screen_bs();
} else if (ch == '\n') {
if (screen.cy < screen.h-1)
++screen.cy;
else
screen_scroll();
screen_cr(); // auto CR
} else if (ch == '\7') {
beep();
} else if (ch == '\t') {
ok = add_char(' '); // hardware tabs not supported
} else if (ch >= '\40') { // printable char
ok = add_char(ch);
}
return ok;
}