From 6d7320a10ded22619ee49eb71e8380fd514b849b Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Sat, 10 Aug 2019 15:47:59 -0400 Subject: Rewrite terminal emulator --- Makefile | 2 +- ingest.c | 8 +- stream.h | 53 ----- term.c | 777 ++++++++++++++++++++++++++++++--------------------------------- term.h | 81 +++++++ view.c | 22 +- 6 files changed, 471 insertions(+), 472 deletions(-) delete mode 100644 stream.h create mode 100644 term.h diff --git a/Makefile b/Makefile index 9b8c704..db34005 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ ingest: ingest.o term.o view: view.o term.o ${CC} ${LDFLAGS} view.o term.o ${LDLIBS} -o $@ -ingest.o term.o view.o: stream.h +ingest.o term.o view.o: term.h tags: *.c *.h ctags -w *.c *.h diff --git a/ingest.c b/ingest.c index 67328a8..3991adc 100644 --- a/ingest.c +++ b/ingest.c @@ -29,7 +29,7 @@ #include #include -#include "stream.h" +#include "term.h" int main(void) { int error; @@ -37,7 +37,7 @@ int main(void) { // TODO: Read info from file. const char *path = "example.sock"; - termInit(24, 80); + struct Term *term = termAlloc(24, 80); int server = socket(PF_LOCAL, SOCK_STREAM, 0); if (server < 0) err(EX_OSERR, "socket"); @@ -90,7 +90,7 @@ int main(void) { wchar_t ch; int n = mbtowc(&ch, &buf[pos], rlen - pos); if (n <= 0) break; - termUpdate(ch); + termUpdate(term, ch); pos += n; } } @@ -107,7 +107,7 @@ int main(void) { continue; } - error = termSnapshot(client); + error = termSnapshot(term, client); if (error) { close(client); continue; diff --git a/stream.h b/stream.h deleted file mode 100644 index 4610647..0000000 --- a/stream.h +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright (C) 2019 C. McEnroe - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - -typedef unsigned uint; - -enum Attr { - Bold = 1 << 0, - Dim = 1 << 1, - Italic = 1 << 2, - Underline = 1 << 3, - Blink = 1 << 4, - Reverse = 1 << 5, -}; - -struct Style { - enum Attr attr; - int bg, fg; -}; - -struct Cell { - struct Style style; - wchar_t ch; -}; - -struct Display { - bool cursor; - uint rows, cols; - uint y, x; - const struct Cell *cells; -}; - -void termInit(uint rows, uint cols); -void termUpdate(wchar_t ch); -int termSnapshot(int fd); -struct Display termDisplay(void); diff --git a/term.c b/term.c index f4bb566..b7c7e66 100644 --- a/term.c +++ b/term.c @@ -14,9 +14,9 @@ * along with this program. If not, see . */ +#include #include #include -#include #include #include #include @@ -24,54 +24,28 @@ #include #include -#include "stream.h" +#include "term.h" -#define E(c, e) e = c - -enum C0 { - NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, - BS, HT, NL, VT, NP, CR, SO, SI, - DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, - CAN, EM, SUB, ESC, FS, GS, RS, US, - DEL = 0x7F, -}; - -static char unhandled(const char *format, ...) { - if (isatty(STDERR_FILENO)) return NUL; +static void unhandled(const char *format, ...) { + if (isatty(STDERR_FILENO)) return; va_list ap; va_start(ap, format); char buf[256]; vsnprintf(buf, sizeof(buf), format, ap); warnx("unhandled %s", buf); va_end(ap); - return NUL; } -static const struct Style Default = { .bg = -1, .fg = -1 }; - -static uint rows, cols; -static uint y, x; - -static bool cursor = true; -static bool insert; -static struct { - uint top, bot; -} scroll; -static struct { - uint y, x; -} save; - -static struct Style style; -static struct Cell *cells; - -static struct Cell *cell(uint y, uint x) { - return &cells[y * cols + x]; +static struct Cell *cell(struct Term *t, uint y, uint x) { + assert(y < t->rows); + assert(x < t->cols); + return &t->cells[y * t->cols + x]; } -static void clear(struct Cell *a, struct Cell *b) { +static void erase(struct Style style, struct Cell *a, struct Cell *b) { for (; a <= b; ++a) { a->style = style; - a->ch = ' '; + a->ch = L' '; } } @@ -79,471 +53,479 @@ static void move(struct Cell *dst, struct Cell *src, uint len) { memmove(dst, src, sizeof(*dst) * len); } -static void scrollUp(uint n) { +static void scrollUp(struct Term *t, uint n) { move( - cell(scroll.top, 0), cell(scroll.top + n, 0), - cols * (scroll.bot - scroll.top - (n - 1)) + cell(t, t->scroll.top, 0), + cell(t, t->scroll.top + n, 0), + t->cols * (1 + t->scroll.bot - t->scroll.top - n) + ); + erase( + t->style, + cell(t, 1 + t->scroll.bot - n, 0), + cell(t, t->scroll.bot, t->cols - 1) ); - clear(cell(scroll.bot - (n - 1), 0), cell(scroll.bot, cols - 1)); } -static void scrollDown(uint n) { +static void scrollDown(struct Term *t, uint n) { move( - cell(scroll.top + n, 0), cell(scroll.top, 0), - cols * (scroll.bot - scroll.top - (n - 1)) + cell(t, t->scroll.top + n, 0), + cell(t, t->scroll.top, 0), + t->cols * (1 + t->scroll.bot - t->scroll.top - n) + ); + erase( + t->style, + cell(t, t->scroll.top, 0), + cell(t, t->scroll.top + n - 1, t->cols - 1) ); - clear(cell(scroll.top, 0), cell(scroll.top + (n - 1), cols - 1)); } -static char updateNUL(wchar_t ch) { - switch (ch) { - break; case ESC: return ESC; - - break; case BS: if (x) x--; - break; case CR: x = 0; +typedef void Action(struct Term *, wchar_t ch); - break; case NL: { - if (y == scroll.bot) { - scrollUp(1); - } else { - y = MIN(y + 1, rows - 1); - } - } +#define ACTION(name) \ + static void name(struct Term *t, wchar_t _ch __attribute__((__unused__))) - break; default: { - int width = wcwidth(ch); - if (ch < ' ') return unhandled("\\x%02X", ch); - if (width < 0) return unhandled("\\u%X", ch); - if (x + width > cols) return unhandled("'%lc' too wide", ch); +enum { + G0 = '(', + CSI = '[', + ST = '\\', + OSC = ']', +}; - if (insert) { - move(cell(y, x + width), cell(y, x), cols - x - width); - } - cell(y, x)->style = style; - cell(y, x)->ch = ch; +ACTION(nop) { (void)t; } +ACTION(esc) { t->state = ESC; } +ACTION(g0) { t->state = G0; } +ACTION(osc) { t->state = OSC; } +ACTION(st) { t->state = ST; } +ACTION(csi) { + t->state = CSI; + memset(&t->param, 0, sizeof(t->param)); +} - for (int i = 1; i < width; ++i) { - cell(y, x + i)->style = style; - cell(y, x + i)->ch = '\0'; - } - x = MIN(x + width, cols - 1); - } +static void csiParam(struct Term *t, wchar_t ch) { + if (ch == L'?') { + t->param.q = true; + } else if (ch == L';') { + t->param.n++; + t->param.i++; + t->param.i %= ParamCap; + } else if (ch >= L'0' && ch <= L'9') { + t->param.s[t->param.i] *= 10; + t->param.s[t->param.i] += ch - L'0'; + if (!t->param.n) t->param.n++; + } else { + // FIXME: Allow other characters in CSI params. + unhandled("CSI %lc", ch); + return; } - return NUL; + t->state = CSI; } -enum C1 { - E('7', DECSC), - E('8', DECRC), - E('D', IND), - E('M', RI), - E('[', CSI), - E('\\', ST), - E(']', OSC), -}; +static void escUnhandled(struct Term *t, wchar_t ch) { + (void)t; + unhandled("ESC %lc", ch); +} -static char updateESC(wchar_t ch) { - static bool discard; - if (discard) { - discard = false; - return NUL; - } +#define Y t->y +#define X t->x +#define B (t->rows - 1) +#define R (t->cols - 1) +#define C(y, x) cell(t, y, x) +#define P(i, z) ((i) < t->param.n ? t->param.s[(i)] : (z)) + +ACTION(bs) { if (X) X--; } +ACTION(cr) { X = 0; } +ACTION(cuu) { Y -= MIN(P(0, 1), Y); } +ACTION(cud) { Y = MIN(Y + P(0, 1), B); } +ACTION(cuf) { X = MIN(X + P(0, 1), R); } +ACTION(cub) { X -= MIN(P(0, 1), X); } +ACTION(cnl) { X = 0; cud(t, 0); } +ACTION(cpl) { X = 0; cuu(t, 0); } +ACTION(cha) { X = MIN(P(0, 1) - 1, R); } +ACTION(vpa) { Y = MIN(P(0, 1) - 1, B); } +ACTION(cup) { + Y = MIN(P(0, 1) - 1, B); + X = MIN(P(1, 1) - 1, R); +} +ACTION(decsc) { + t->save.y = Y; + t->save.x = X; +} +ACTION(decrc) { + Y = t->save.y; + X = t->save.x; +} - switch (ch) { - break; case CSI: return CSI; - break; case OSC: return OSC; +ACTION(ed) { + erase( + t->style, + (P(0, 0) == 0 ? C(Y, X) : C(0, 0)), + (P(0, 0) == 1 ? C(Y, X) : C(B, R)) + ); +} +ACTION(el) { + erase( + t->style, + (P(0, 0) == 0 ? C(Y, X) : C(Y, 0)), + (P(0, 0) == 1 ? C(Y, X) : C(Y, R)) + ); +} +ACTION(ech) { + erase(t->style, C(Y, X), C(Y, MIN(X + P(0, 1) - 1, R))); +} - break; case DECSC: save.y = y; save.x = x; - break; case DECRC: y = save.y; x = save.x; +ACTION(dl) { + uint n = MIN(P(0, 1), t->rows - Y); + move(C(Y, 0), C(Y + n, 0), t->cols * (t->rows - Y - n)); + erase(t->style, C(t->rows - n, 0), C(B, R)); +} +ACTION(dch) { + uint n = MIN(P(0, 1), t->cols - X); + move(C(Y, X), C(Y, X + n), t->cols - X - n); + erase(t->style, C(Y, t->cols - n), C(Y, R)); +} +ACTION(il) { + uint n = MIN(P(0, 1), t->rows - Y); + move(C(Y + n, 0), C(Y, 0), t->cols * (t->rows - Y - n)); + erase(t->style, C(Y, 0), C(Y + n - 1, R)); +} +ACTION(ich) { + uint n = MIN(P(0, 1), t->cols - X); + move(C(Y, X + n), C(Y, X), t->cols - X - n); + erase(t->style, C(Y, X), C(Y, X + n - 1)); +} - break; case IND: scrollUp(1); - break; case RI: scrollDown(1); +ACTION(nl) { + if (Y == t->scroll.bot) { + scrollUp(t, 1); + } else { + Y = MIN(Y + 1, B); + } +} +ACTION(ind) { scrollUp(t, 1); } +ACTION(ri) { scrollDown(t, 1); } +ACTION(su) { scrollUp(t, MIN(P(0, 1), t->scroll.bot - t->scroll.top)); } +ACTION(sd) { scrollDown(t, MIN(P(0, 1), t->scroll.bot - t->scroll.top)); } +ACTION(decstbm) { + t->scroll.bot = MIN(P(1, t->rows) - 1, B); + t->scroll.top = MIN(P(0, 1) - 1, t->scroll.bot); +} - break; case '(': discard = true; return ESC; - break; case '=': // ignore - break; case '>': // ignore +enum { + IRM = 4, + DECAWM = 7, + DECTCEM = 25, +}; - break; default: return unhandled("ESC %lc", ch); +static void mode(struct Term *t, wchar_t ch) { + enum Mode mode = 0; + for (uint i = 0; i < t->param.n; ++i) { + if (t->param.q) { + switch (t->param.s[i]) { + break; case 1: // ignore + break; case DECAWM: mode |= Wrap; + break; case DECTCEM: mode |= Cursor; + break; default: unhandled("DECSET/DECRST %u", t->param.s[i]); + } + } else { + switch (t->param.s[i]) { + break; case IRM: mode |= Insert; + break; default: unhandled("SM/RM %u", t->param.s[i]); + } + } } - return NUL; + t->mode = (ch == L'h' ? t->mode | mode : t->mode & ~mode); } -enum SGR { - E(0, Reset), - E(1, SetBold), - E(2, SetDim), - E(3, SetItalic), - E(4, SetUnderline), - E(5, SetBlink), - E(7, SetReverse), - - E(22, UnsetBoldDim), - E(23, UnsetItalic), - E(24, UnsetUnderline), - E(25, UnsetBlink), - E(27, UnsetReverse), - - E(30, SetFg0), - E(37, SetFg7), - E(38, SetFg), - E(39, ResetFg), - E(40, SetBg0), - E(47, SetBg7), - E(48, SetBg), - E(49, ResetBg), - - E(90, SetFg8), - E(97, SetFg15), - E(100, SetBg8), - E(107, SetBg15), - - E(5, Color256), +enum { + Reset, + SetBold, + SetDim, + SetItalic, + SetUnderline, + SetBlink, + SetReverse = 7, + + UnsetBoldDim = 22, + UnsetItalic, + UnsetUnderline, + UnsetBlink, + UnsetReverse = 27, + + SetFg0 = 30, + SetFg7 = 37, + SetFg, + ResetFg, + SetBg0 = 40, + SetBg7 = 47, + SetBg, + ResetBg, + + SetFg8 = 90, + SetFgF = 97, + SetBg8 = 100, + SetBgF = 107, + + Color256 = 5, }; -static void updateSGR(uint ps[], uint n) { +static const struct Style Default = { .bg = -1, .fg = -1 }; + +ACTION(sgr) { + uint n = t->param.i + 1; for (uint i = 0; i < n; ++i) { - switch (ps[i]) { - break; case Reset: style = Default; - - break; case SetBold: style.attr &= ~Dim; style.attr |= Bold; - break; case SetDim: style.attr &= ~Bold; style.attr |= Dim; - break; case SetItalic: style.attr |= Italic; - break; case SetUnderline: style.attr |= Underline; - break; case SetBlink: style.attr |= Blink; - break; case SetReverse: style.attr |= Reverse; - - break; case UnsetBoldDim: style.attr &= ~(Bold | Dim); - break; case UnsetItalic: style.attr &= ~Italic; - break; case UnsetUnderline: style.attr &= ~Underline; - break; case UnsetBlink: style.attr &= ~Blink; - break; case UnsetReverse: style.attr &= ~Reverse; + switch (t->param.s[i]) { + break; case Reset: t->style = Default; + + break; case SetBold: t->style.attr &= ~Dim; t->style.attr |= Bold; + break; case SetDim: t->style.attr &= ~Bold; t->style.attr |= Dim; + break; case SetItalic: t->style.attr |= Italic; + break; case SetUnderline: t->style.attr |= Underline; + break; case SetBlink: t->style.attr |= Blink; + break; case SetReverse: t->style.attr |= Reverse; + + break; case UnsetBoldDim: t->style.attr &= ~(Bold | Dim); + break; case UnsetItalic: t->style.attr &= ~Italic; + break; case UnsetUnderline: t->style.attr &= ~Underline; + break; case UnsetBlink: t->style.attr &= ~Blink; + break; case UnsetReverse: t->style.attr &= ~Reverse; break; case SetFg: { - if (++i < n && ps[i] == Color256) { - if (++i < n) style.fg = ps[i]; + if (++i < n && t->param.s[i] == Color256) { + if (++i < n) t->style.fg = t->param.s[i]; } } break; case SetBg: { - if (++i < n && ps[i] == Color256) { - if (++i < n) style.bg = ps[i]; + if (++i < n && t->param.s[i] == Color256) { + if (++i < n) t->style.bg = t->param.s[i]; } } - break; case ResetFg: style.fg = -1; - break; case ResetBg: style.bg = -1; + break; case ResetFg: t->style.fg = Default.fg; + break; case ResetBg: t->style.bg = Default.bg; break; default: { - if (ps[i] >= SetFg0 && ps[i] <= SetFg7) { - style.fg = ps[i] - SetFg0; - } else if (ps[i] >= SetBg0 && ps[i] <= SetBg7) { - style.bg = ps[i] - SetBg0; - } else if (ps[i] >= SetFg8 && ps[i] <= SetFg15) { - style.fg = 8 + ps[i] - SetFg8; - } else if (ps[i] >= SetBg8 && ps[i] <= SetBg15) { - style.bg = 8 + ps[i] - SetBg8; + if (t->param.s[i] >= SetFg0 && t->param.s[i] <= SetFg7) { + t->style.fg = t->param.s[i] - SetFg0; + } else if (t->param.s[i] >= SetBg0 && t->param.s[i] <= SetBg7) { + t->style.bg = t->param.s[i] - SetBg0; + } else if (t->param.s[i] >= SetFg8 && t->param.s[i] <= SetFgF) { + t->style.fg = 8 + t->param.s[i] - SetFg8; + } else if (t->param.s[i] >= SetBg8 && t->param.s[i] <= SetBgF) { + t->style.bg = 8 + t->param.s[i] - SetBg8; } } } } } -// SGR toggling all attributes and setting both colors in 256 palette. -enum { CSIMax = 5 + 2 * 3 }; - -enum CSI { - E('?', DEC), - E('@', ICH), - E('A', CUU), - E('B', CUD), - E('C', CUF), - E('D', CUB), - E('E', CNL), - E('F', CPL), - E('G', CHA), - E('H', CUP), - E('J', ED), - E('K', EL), - E('L', IL), - E('M', DL), - E('P', DCH), - E('S', SU), - E('T', SD), - E('X', ECH), - E('d', VPA), - E('h', SM), - E('l', RM), - E('m', SGR), - E('r', DECSTBM), - E(DEC + SM, DECSET), - E(DEC + RM, DECRST), -}; - -enum Mode { - E(4, IRM), - - E(1, DECCKM), - E(7, DECAWM), - E(25, DECTCEM), -}; - -static char updateCSI(wchar_t ch) { - static char dec; - if (ch == DEC) { - dec = DEC; - return CSI; - } - - static uint n, p, ps[CSIMax]; - if (ch == ';') { - n++; - p++; - ps[p %= CSIMax] = 0; - return CSI; +static void add(struct Term *t, wchar_t ch) { + int width = wcwidth(ch); + if (width < 0) { + unhandled("\\u%02X", ch); + return; } - if (ch >= '0' && ch <= '9') { - ps[p] *= 10; - ps[p] += ch - '0'; - if (!n) n++; - return CSI; + if (X + width > t->cols) { + unhandled("'%lc' too wide", ch); + return; } - switch (dec + ch) { - break; case CUU: y -= MIN((n ? ps[0] : 1), y); - break; case CUD: y = MIN(y + (n ? ps[0] : 1), rows - 1); - break; case CUF: x = MIN(x + (n ? ps[0] : 1), cols - 1); - break; case CUB: x -= MIN((n ? ps[0] : 1), x); - break; case CNL: y = MIN(y + (n ? ps[0] : 1), rows - 1); x = 0; - break; case CPL: y -= MIN((n ? ps[0] : 1), y); x = 0; - break; case CHA: x = MIN((n ? ps[0] - 1 : 0), cols - 1); - break; case VPA: y = MIN((n ? ps[0] - 1 : 0), rows - 1); - break; case CUP: { - y = MIN((n > 0 ? ps[0] - 1 : 0), rows - 1); - x = MIN((n > 1 ? ps[1] - 1 : 0), cols - 1); - } - - break; case ED: { - struct Cell *a = cell(0, 0); - struct Cell *b = cell(rows - 1, cols - 1); - if (ps[0] == 0) a = cell(y, x); - if (ps[0] == 1) b = cell(y, x); - clear(a, b); - } - break; case EL: { - struct Cell *a = cell(y, 0); - struct Cell *b = cell(y, cols - 1); - if (ps[0] == 0) a = cell(y, x); - if (ps[0] == 1) b = cell(y, x); - clear(a, b); - } - break; case ECH: { - struct Cell *a = cell(y, x); - struct Cell *b = cell(y, MIN(x + (n > 0 ? ps[0] - 1 : 0), cols - 1)); - clear(a, b); - } - - break; case IL: { - uint i = MIN((n ? ps[0] : 1), rows - y); - move(cell(y + i, 0), cell(y, 0), cols * (rows - y - i)); - clear(cell(y, 0), cell(y + i - 1, cols - 1)); - } - break; case ICH: { - uint i = MIN((n ? ps[0] : 1), cols - x); - move(cell(y, x + i), cell(y, x), cols - x - i); - clear(cell(y, x), cell(y, x + i - 1)); - } - - break; case DL: { - uint i = MIN((n ? ps[0] : 1), rows - y); - move(cell(y, 0), cell(y + i, 0), cols * (rows - y - i)); - clear(cell(rows - i, 0), cell(rows - 1, cols - 1)); - } - break; case DCH: { - uint i = MIN((n ? ps[0] : 1), cols - x); - move(cell(y, x), cell(y, x + i), cols - x - i); - clear(cell(y, cols - i), cell(y, cols - 1)); - } - - break; case SU: scrollUp(MIN((n ? ps[0] : 1), scroll.bot - scroll.top)); - break; case SD: scrollDown(MIN((n ? ps[0] : 1), scroll.bot - scroll.top)); - - break; case SM: { - switch (ps[0]) { - break; case IRM: insert = true; - break; default: unhandled("SM %u", ps[0]); - } - } - break; case RM: { - switch (ps[0]) { - break; case IRM: insert = false; - break; default: unhandled("RM %u", ps[0]); - } - } - - break; case SGR: updateSGR(ps, p + 1); - - break; case DECSTBM: { - // FIXME: Prevent bot < top. - scroll.top = (n > 0 ? ps[0] - 1 : 0); - scroll.bot = (n > 1 ? ps[1] - 1 : rows - 1); - } - - break; case DECSET: { - switch (ps[0]) { - break; case DECCKM: // ignore - break; case DECAWM: // TODO - break; case 12: // ignore - break; case DECTCEM: cursor = true; - break; default: if (ps[0] < 1000) unhandled("DECSET %u", ps[0]); - } - } - break; case DECRST: { - switch (ps[0]) { - break; case DECCKM: // ignore - break; case DECAWM: // TODO - break; case 12: // ignore - break; case DECTCEM: cursor = false; - break; default: if (ps[0] < 1000) unhandled("DECRST %u", ps[0]); - } - } - - break; case 't': // ignore - break; default: unhandled("CSI %s%lc", (dec ? "? " : ""), ch); + if (t->mode & Insert) { + move(C(Y, X + width), C(Y, X), t->cols - X - width); } - dec = 0; - ps[n = p = 0] = 0; - return NUL; -} - -static char updateOSC(wchar_t ch) { - static bool esc; - switch (ch) { - break; case BEL: return NUL; - break; case ESC: esc = true; - break; case ST: { - if (!esc) break; - esc = false; - return NUL; - } + C(Y, X)->style = t->style; + C(Y, X)->ch = ch; + for (int i = 1; i < width; ++i) { + C(Y, X + i)->style = t->style; + C(Y, X + i)->ch = L'\0'; } - return OSC; -} -void termUpdate(wchar_t ch) { - static char seq; - switch (seq) { - break; case NUL: seq = updateNUL(ch); - break; case ESC: seq = updateESC(ch); - break; case CSI: seq = updateCSI(ch); - break; case OSC: seq = updateOSC(ch); + if (t->mode & Wrap && X + width >= t->cols) { + cr(t, 0); + nl(t, 0); + } else { + X = MIN(X + width, R); } } -void termInit(uint _rows, uint _cols) { - rows = _rows; - cols = _cols; - cells = calloc(rows * cols, sizeof(*cells)); - if (!cells) err(EX_OSERR, "calloc"); +static Action *Actions[][128] = { + [NUL][0] = add, + [NUL][BEL] = nop, + [NUL][BS] = bs, + [NUL][NL] = nl, + [NUL][CR] = cr, + [NUL][ESC] = esc, + + [ESC][0] = escUnhandled, + [ESC]['('] = g0, + [ESC]['7'] = decsc, + [ESC]['8'] = decrc, + [ESC]['='] = nop, + [ESC]['>'] = nop, + [ESC]['D'] = ind, + [ESC]['M'] = ri, + [ESC]['['] = csi, + [ESC][']'] = osc, + + [G0][0] = nop, + + [CSI][0] = csiParam, + [CSI]['@'] = ich, + [CSI]['A'] = cuu, + [CSI]['B'] = cud, + [CSI]['C'] = cuf, + [CSI]['D'] = cub, + [CSI]['E'] = cnl, + [CSI]['F'] = cpl, + [CSI]['G'] = cha, + [CSI]['H'] = cup, + [CSI]['J'] = ed, + [CSI]['K'] = el, + [CSI]['L'] = il, + [CSI]['M'] = dl, + [CSI]['P'] = dch, + [CSI]['S'] = su, + [CSI]['T'] = sd, + [CSI]['X'] = ech, + [CSI]['d'] = vpa, + [CSI]['h'] = mode, + [CSI]['l'] = mode, + [CSI]['m'] = sgr, + [CSI]['r'] = decstbm, + [CSI]['t'] = nop, + + [OSC][0] = osc, + [OSC][BEL] = nop, + [OSC][ESC] = st, + [ST][0] = osc, + [ST]['\\'] = nop, +}; + +void termUpdate(struct Term *term, wchar_t ch) { + assert((uint)term->state < sizeof(Actions) / sizeof(Actions[0])); + Action *action = NULL; + if (ch < 128) action = Actions[(uint)term->state][ch]; + if (!action) action = Actions[(uint)term->state][0]; + assert(action); + term->state = NUL; + action(term, ch); +} - style = Default; - clear(cell(0, 0), cell(rows - 1, cols - 1)); - scroll.bot = rows - 1; +struct Term *termAlloc(uint rows, uint cols) { + size_t size = sizeof(struct Term) + sizeof(struct Cell) * rows * cols; + struct Term *term = malloc(size); + if (!term) err(EX_OSERR, "malloc"); + + memset(term, 0, sizeof(*term)); + term->rows = rows; + term->cols = cols; + term->mode = Wrap | Cursor; + term->scroll.top = 0; + term->scroll.bot = rows - 1; + term->style = Default; + erase(Default, cell(term, 0, 0), cell(term, rows - 1, cols - 1)); + + return term; } static int styleDiff(FILE *file, const struct Style *prev, const struct Style *next) { if (!memcmp(prev, next, sizeof(*prev))) return 0; - if (!memcmp(next, &Default, sizeof(*next))) { - return fprintf(file, "%c%c%c", ESC, CSI, SGR); - } + if (!memcmp(next, &Default, sizeof(*next))) return fprintf(file, "\33[m"); - uint ps[CSIMax]; + uint p[ParamCap]; uint n = 0; enum Attr diff = prev->attr ^ next->attr; if (diff & (Bold | Dim)) { - ps[n++] = ( + p[n++] = ( next->attr & Bold ? SetBold : next->attr & Dim ? SetDim : UnsetBoldDim ); } if (diff & Italic) { - ps[n++] = (next->attr & Italic ? SetItalic : UnsetItalic); + p[n++] = (next->attr & Italic ? SetItalic : UnsetItalic); } if (diff & Underline) { - ps[n++] = (next->attr & Underline ? SetUnderline : UnsetUnderline); + p[n++] = (next->attr & Underline ? SetUnderline : UnsetUnderline); } if (diff & Blink) { - ps[n++] = (next->attr & Blink ? SetBlink : UnsetBlink); + p[n++] = (next->attr & Blink ? SetBlink : UnsetBlink); } if (diff & Reverse) { - ps[n++] = (next->attr & Reverse ? SetReverse : UnsetReverse); + p[n++] = (next->attr & Reverse ? SetReverse : UnsetReverse); } if (next->bg != prev->bg) { if (next->bg == -1) { - ps[n++] = ResetBg; + p[n++] = ResetBg; } else if (next->bg < 8) { - ps[n++] = SetBg0 + next->bg; + p[n++] = SetBg0 + next->bg; } else if (next->bg < 16) { - ps[n++] = SetBg8 + next->bg - 8; + p[n++] = SetBg8 + next->bg - 8; } else { - ps[n++] = SetBg; - ps[n++] = Color256; - ps[n++] = next->bg; + p[n++] = SetBg; + p[n++] = Color256; + p[n++] = next->bg; } } if (next->fg != prev->fg) { if (next->fg == -1) { - ps[n++] = ResetFg; + p[n++] = ResetFg; } else if (next->fg < 8) { - ps[n++] = SetFg0 + next->fg; + p[n++] = SetFg0 + next->fg; } else if (next->fg < 16) { - ps[n++] = SetFg8 + next->fg - 8; + p[n++] = SetFg8 + next->fg - 8; } else { - ps[n++] = SetFg; - ps[n++] = Color256; - ps[n++] = next->fg; + p[n++] = SetFg; + p[n++] = Color256; + p[n++] = next->fg; } } - if (0 > fprintf(file, "%c%c%u", ESC, CSI, ps[0])) return -1; + if (0 > fprintf(file, "\33[%u", p[0])) return -1; for (uint i = 1; i < n; ++i) { - if (0 > fprintf(file, ";%u", ps[i])) return -1; + if (0 > fprintf(file, ";%u", p[i])) return -1; } - return fprintf(file, "%c", SGR); + return fprintf(file, "m"); } -int termSnapshot(int _fd) { +int termSnapshot(struct Term *term, int _fd) { int fd = dup(_fd); if (fd < 0) return -1; FILE *file = fdopen(fd, "w"); if (!file) return -1; + int n = fprintf(file, "\33[%dl\33[?%dl\33[r\33[H\33[m", IRM, DECAWM); + if (n < 0) goto fail; + struct Style prev = Default; - for (uint y = 0; y < rows; ++y) { + for (uint y = 0; y < term->rows; ++y) { if (y && 0 > fprintf(file, "\r\n")) goto fail; - for (uint x = 0; x < cols; ++x) { - if (!cell(y, x)->ch) continue; - if (0 > styleDiff(file, &prev, &cell(y, x)->style)) goto fail; - if (0 > fprintf(file, "%lc", cell(y, x)->ch)) goto fail; - prev = cell(y, x)->style; + for (uint x = 0; x < term->cols; ++x) { + struct Cell *c = cell(term, y, x); + if (!c->ch) continue; + if (0 > styleDiff(file, &prev, &c->style)) goto fail; + if (0 > fprintf(file, "%lc", c->ch)) goto fail; + prev = c->style; } } - if (0 > styleDiff(file, &prev, &style)) goto fail; + if (0 > styleDiff(file, &prev, &term->style)) goto fail; - int n = fprintf( + n = fprintf( file, - "%c%c%d%c" - "%c%c%c%d%c" - "%c%c%u;%u%c" - "%c%c%u;%u%c", - ESC, CSI, IRM, (insert ? SM : RM), - ESC, CSI, DEC, DECTCEM, (cursor ? DECSET : DECRST) - DEC, - ESC, CSI, 1 + scroll.top, 1 + scroll.bot, DECSTBM, - ESC, CSI, 1 + y, 1 + x, CUP + "\33[%d%c" + "\33[?%d%c" + "\33[?%d%c" + "\33[%u;%ur" + "\33[%u;%uH", + IRM, (term->mode & Insert ? 'h' : 'l'), + DECAWM, (term->mode & Wrap ? 'h' : 'l'), + DECTCEM, (term->mode & Cursor ? 'h' : 'l'), + 1 + term->scroll.top, 1 + term->scroll.bot, + 1 + term->y, 1 + term->x ); if (n < 0) goto fail; @@ -553,14 +535,3 @@ fail: fclose(file); return -1; } - -struct Display termDisplay(void) { - return (struct Display) { - .cursor = cursor, - .rows = rows, - .cols = cols, - .y = y, - .x = x, - .cells = cells, - }; -} diff --git a/term.h b/term.h new file mode 100644 index 0000000..03d9ac5 --- /dev/null +++ b/term.h @@ -0,0 +1,81 @@ +/* Copyright (C) 2019 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +typedef unsigned uint; + +enum { + NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, + BS, HT, NL, VT, NP, CR, SO, SI, + DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, + CAN, EM, SUB, ESC, FS, GS, RS, US, + DEL = 0x7F, +}; + +enum Attr { + Bold = 1 << 0, + Dim = 1 << 1, + Italic = 1 << 2, + Underline = 1 << 3, + Blink = 1 << 4, + Reverse = 1 << 5, +}; + +struct Style { + enum Attr attr; + int bg, fg; +}; + +struct Cell { + struct Style style; + wchar_t ch; +}; + +enum Mode { + Insert = 1 << 0, + Wrap = 1 << 1, + Cursor = 1 << 2, +}; + +enum { ParamCap = 16 }; + +struct Term { + uint rows, cols; + char state; + struct { + bool q; + uint s[ParamCap]; + uint n, i; + } param; + enum Mode mode; + struct { + uint y, x; + } save; + struct { + uint top, bot; + } scroll; + uint y, x; + struct Style style; + struct Cell cells[]; +}; + +struct Term *termAlloc(uint rows, uint cols); +void termUpdate(struct Term *term, wchar_t ch); +int termSnapshot(struct Term *term, int fd); diff --git a/view.c b/view.c index a5861c4..7a40123 100644 --- a/view.c +++ b/view.c @@ -27,7 +27,7 @@ #include #include -#include "stream.h" +#include "term.h" #ifndef A_ITALIC #define A_ITALIC A_UNDERLINE @@ -86,10 +86,10 @@ static attr_t styleAttr(struct Style style) { return attr; } -static void render(WINDOW *win, struct Display term) { - for (uint y = 0; y < term.rows; ++y) { - for (uint x = 0; x < term.cols; ++x) { - struct Cell cell = term.cells[term.cols * y + x]; +static void render(WINDOW *win, const struct Term *term) { + for (uint y = 0; y < term->rows; ++y) { + for (uint x = 0; x < term->cols; ++x) { + struct Cell cell = term->cells[term->cols * y + x]; if (!cell.ch) continue; wattr_set( win, @@ -100,9 +100,9 @@ static void render(WINDOW *win, struct Display term) { mvwaddnwstr(win, y, x, &cell.ch, 1); } } - curs_set(term.cursor); - leaveok(win, !term.cursor); - wmove(win, term.y, term.x); + curs_set(!!(term->mode & Cursor)); + leaveok(win, !(term->mode & Cursor)); + wmove(win, term->y, term->x); } int main(void) { @@ -111,7 +111,7 @@ int main(void) { // TODO: Read info from file. const char *path = "example.sock"; - termInit(24, 80); + struct Term *term = termAlloc(24, 80); int client = socket(PF_LOCAL, SOCK_STREAM, 0); if (client < 0) err(EX_OSERR, "socket"); @@ -134,11 +134,11 @@ int main(void) { wchar_t ch; int n = mbtowc(&ch, &buf[pos], len - pos); if (n <= 0) break; - termUpdate(ch); + termUpdate(term, ch); pos += n; } - render(win, termDisplay()); + render(win, term); wrefresh(win); } } -- cgit 1.4.1