/* 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 #include #include #include #include #include #include "stream.h" #define MIN(a, b) ((a) < (b) ? (a) : (b)) 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, }; struct Style { bool bold, italic, underline, reverse; uint bg, fg; }; struct Cell { struct Style style; wchar_t ch; }; static uint rows, cols; static uint y, x; static bool insert; static struct { uint top, bot; } scroll; static struct Style style = { .bg = -1, .fg = -1 }; static struct Cell *cells; static struct Cell *cell(uint y, uint x) { return &cells[y * cols + x]; } static void clear(struct Cell *a, struct Cell *b) { for (; a <= b; ++a) { a->style = style; a->ch = ' '; } } static void move(struct Cell *dst, struct Cell *src, uint len) { memmove(dst, src, sizeof(*dst) * len); } static char updateNUL(wchar_t ch) { switch (ch) { break; case ESC: return ESC; break; case BS: if (x) x--; break; case CR: x = 0; break; case NL: { if (y == scroll.bot) { move( cell(scroll.top, 0), cell(scroll.top + 1, 0), cols * (scroll.bot - scroll.top) ); clear(cell(scroll.bot, 0), cell(scroll.bot, cols - 1)); } else { y = MIN(y + 1, rows - 1); } } break; default: { // FIXME: Nowhere for these warnings to go. if (ch < ' ') { warnx("unhandled \\x%02X", ch); return NUL; } int width = wcwidth(ch); if (width < 0) { warnx("unhandled \\u%X", ch); return NUL; } if (x + width > cols) { warnx("cannot fit '%lc'", ch); return NUL; } if (insert) { move(cell(y, x + width), cell(y, x), cols - x - width); } cell(y, x)->style = style; cell(y, x)->ch = ch; 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); } } return NUL; } enum { CSI = '[', OSC = ']', ST = '\\', }; static char updateESC(wchar_t ch) { static bool discard; if (discard) { discard = false; return NUL; } switch (ch) { case '(': discard = true; return ESC; case '=': return NUL; case '>': return NUL; case CSI: return CSI; case OSC: return OSC; // FIXME: Nowhere for this warning to go. default: warnx("unhandled ESC %lc", ch); return NUL; } } enum { Default, Bold, Italic = 3, Underline, Reverse = 7, NotBold = 22, NotItalic, NotUnderline, NotReverse, Fg0 = 30, Fg7 = 37, Fg, FgDefault, Bg0, Bg7 = 47, Bg, BgDefault, Fg8 = 90, Fg15 = 97, Bg8 = 100, Bg15 = 107, Color256 = 5, }; static void updateSGR(uint ps[], uint n) { for (uint i = 0; i < n; ++i) { switch (ps[i]) { break; case Default: style = (struct Style) { .bg = -1, .fg = -1 }; break; case Bold: style.bold = true; break; case Italic: style.italic = true; break; case Underline: style.underline = true; break; case Reverse: style.reverse = true; break; case NotBold: style.bold = false; break; case NotItalic: style.italic = false; break; case NotUnderline: style.underline = false; break; case NotReverse: style.reverse = false; break; case Fg: { if (++i < n && ps[i] == Color256) { if (++i < n) style.fg = ps[i]; } } break; case Bg: { if (++i < n && ps[i] == Color256) { if (++i < n) style.bg = ps[i]; } } break; case FgDefault: style.fg = -1; break; case BgDefault: style.bg = -1; break; default: { if (ps[i] >= Fg0 && ps[i] <= Fg7) { style.fg = ps[i] - Fg0; } else if (ps[i] >= Bg0 && ps[i] <= Bg7) { style.bg = ps[i] - Bg0; } else if (ps[i] >= Fg8 && ps[i] <= Fg15) { style.fg = 8 + ps[i] - Fg8; } else if (ps[i] >= Bg8 && ps[i] <= Bg15) { style.bg = 8 + ps[i] - Bg8; } } } } } enum { DEC = '?', CUU = 'A', CUD, CUF, CUB, CNL, CPL, CHA, CUP, ED = 'J', EL, DL = 'M', DCH = 'P', VPA = 'd', SM = 'h', RM = 'l', SGR, DECSTBM = 'r', }; static char updateCSI(wchar_t ch) { static bool dec; if (ch == DEC) { dec = true; return CSI; } static uint n, p, ps[8]; if (ch == ';') { n++; p++; ps[p %= 8] = 0; return CSI; } if (ch >= '0' && ch <= '9') { ps[p] *= 10; ps[p] += ch - '0'; if (!n) n++; return CSI; } switch (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 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)); } // FIXME: Nowhere for these warnings to go. break; case SM: { if (dec) break; switch (ps[0]) { break; case 4: insert = true; break; default: warnx("unhandled SM %u", ps[0]); } } break; case RM: { if (dec) break; switch (ps[0]) { break; case 4: insert = false; break; default: warnx("unhandled RM %u", ps[0]); } } break; case SGR: updateSGR(ps, p + 1); break; case DECSTBM: { scroll.top = (n > 0 ? ps[0] - 1 : 0); scroll.bot = (n > 1 ? ps[1] - 1 : rows - 1); } break; case 't': // ignore // FIXME: Nowhere for this warning to go. break; default: warnx("unhandled CSI %lc", ch); } dec = false; 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; } } 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); } } void termInit(uint _rows, uint _cols) { rows = _rows; cols = _cols; cells = calloc(rows * cols, sizeof(*cells)); if (!cells) err(EX_OSERR, "calloc"); clear(cell(0, 0), cell(rows - 1, cols - 1)); scroll.bot = rows - 1; }