/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ │ This program is free software; you can redistribute it and/or modify │ │ it under the terms of the GNU General Public License as published by │ │ the Free Software Foundation; version 2 of the License. │ │ │ │ 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 │ │ General Public License for more details. │ │ │ │ You should have received a copy of the GNU General Public License │ │ along with this program; if not, write to the Free Software │ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/alg/arraylist2.h" #include "libc/bits/bits.h" #include "libc/bits/safemacros.h" #include "libc/conv/conv.h" #include "libc/conv/itoa.h" #include "libc/log/check.h" #include "libc/macros.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/bsr.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" #include "libc/str/thompike.h" #include "libc/unicode/unicode.h" #include "libc/x/x.h" #include "tool/build/lib/pty.h" struct MachinePty *MachinePtyNew(void) { struct MachinePty *pty; pty = xcalloc(1, sizeof(struct MachinePty)); MachinePtyResize(pty, 25, 80); return pty; } void MachinePtyResize(struct MachinePty *pty, int yn, int xn) { unsigned y, ym, xm, y0; uint32_t *wcs, *fgs, *bgs, *prs; if (xn < 80) xn = 80; if (yn < 25) yn = 25; if (xn == pty->xn && yn == pty->yn) return; wcs = xcalloc(yn * xn, 4); fgs = xcalloc(yn * xn, 4); bgs = xcalloc(yn * xn, 4); prs = xcalloc(yn * xn, 4); y0 = yn > pty->y + 1 ? 0 : pty->y + 1 - yn; ym = MIN(yn, pty->yn) + y0; xm = MIN(xn, pty->xn); for (y = y0; y < ym; ++y) { memcpy(wcs + y * xn, pty->wcs + y * pty->xn, xm * 4); memcpy(fgs + y * xn, pty->fgs + y * pty->xn, xm * 4); memcpy(bgs + y * xn, pty->bgs + y * pty->xn, xm * 4); memcpy(prs + y * xn, pty->prs + y * pty->xn, xm * 4); } free(pty->wcs); free(pty->fgs); free(pty->bgs); free(pty->prs); pty->wcs = wcs; pty->fgs = fgs; pty->bgs = bgs; pty->prs = prs; pty->y = MIN(pty->y, yn - 1); pty->x = MIN(pty->x, xn - 1); pty->yn = yn; pty->xn = xn; } void MachinePtyFree(struct MachinePty *pty) { if (pty) { free(pty->wcs); free(pty->fgs); free(pty->bgs); free(pty->prs); free(pty); } } static void MachinePtyScrollPlane(struct MachinePty *pty, uint32_t *p) { memcpy(p, p + pty->xn, sizeof(p[0]) * pty->xn * (pty->yn - 1)); memset(p + pty->xn * (pty->yn - 1), 0, sizeof(p[0]) * pty->xn); } static void MachinePtyScroll(struct MachinePty *pty) { MachinePtyScrollPlane(pty, pty->wcs); MachinePtyScrollPlane(pty, pty->fgs); MachinePtyScrollPlane(pty, pty->bgs); MachinePtyScrollPlane(pty, pty->prs); } static void MachinePtyNewline(struct MachinePty *pty) { pty->x = 0; if (++pty->y == pty->yn) { --pty->y; if (!(pty->conf & kMachinePtyNoopost)) { MachinePtyScroll(pty); } } } static void SetMachinePtyCell(struct MachinePty *pty, wint_t wc) { uint32_t w, i; if ((w = MAX(0, wcwidth(wc))) > 0) { i = pty->y * pty->xn + pty->x; pty->wcs[i] = wc; pty->fgs[i] = pty->fg; pty->bgs[i] = pty->bg; pty->prs[i] = pty->pr; if ((pty->x += w) >= pty->xn) { pty->x = 0; MachinePtyNewline(pty); } } } static void MachinePtyTab(struct MachinePty *pty) { unsigned x, x2; x2 = pty->x + ROUNDUP(pty->x + 1, 8); if (x2 >= pty->xn) x2 = pty->xn - 1; for (x = pty->x; x < x2; ++x) { pty->wcs[pty->y * pty->xn + x] = 0; } pty->x = x2; } static void MachinePtyCursorSet(struct MachinePty *pty, int y, int x) { pty->y = MAX(0, MIN(pty->yn - 1, y)); pty->x = MAX(0, MIN(pty->xn - 1, x)); } static void MachinePtyCursorMove(struct MachinePty *pty, int dy, int dx) { int n; if (pty->esc.i > 1) { n = atoi(pty->esc.s); dy *= n; dx *= n; } MachinePtyCursorSet(pty, pty->y + dy, pty->x + dx); } static void MachinePtyCursorPosition(struct MachinePty *pty) { int row, col; row = MAX(1, atoi(pty->esc.s)); col = MAX(1, atoi((char *)firstnonnull(strchr(pty->esc.s, ';'), "x") + 1)); MachinePtyCursorSet(pty, row - 1, col - 1); } static void MachinePtyEraseRange(struct MachinePty *pty, size_t i, size_t j) { size_t n; n = (j - i) * sizeof(uint32_t); memset(pty->wcs + i, 0, n); memset(pty->fgs + i, 0, n); memset(pty->bgs + i, 0, n); memset(pty->prs + i, 0, n); } static void MachinePtyEraseDisplay(struct MachinePty *pty) { switch (atoi(pty->esc.s)) { case 3: case 2: pty->y = 0; pty->x = 0; case 0: MachinePtyEraseRange(pty, pty->y * pty->xn + pty->x, pty->yn * pty->xn); break; case 1: MachinePtyEraseRange(pty, 0, pty->y * pty->xn + pty->x); break; default: break; } } static void MachinePtyEraseLine(struct MachinePty *pty) { switch (atoi(pty->esc.s)) { case 0: MachinePtyEraseRange(pty, pty->y * pty->xn + pty->x, pty->y * pty->xn + pty->xn); break; case 1: MachinePtyEraseRange(pty, pty->y * pty->xn + pty->xn, pty->y * pty->xn + pty->x); break; case 2: MachinePtyEraseRange(pty, pty->y * pty->xn, pty->y * pty->xn + pty->xn); break; default: break; } } static void MachinePtySelectGraphicsRendition(struct MachinePty *pty) { char *p, c; unsigned x; uint8_t code[4]; enum { kSgr, kSgrFg = 010, kSgrFgTrue = 012, kSgrFgXterm = 015, kSgrBg = 020, kSgrBgTrue = 022, kSgrBgXterm = 025, } t; x = 0; t = kSgr; p = pty->esc.s; memset(code, 0, sizeof(code)); for (;;) { c = *p++; switch (c) { case '\0': return; case '0' ... '9': x *= 10; x += c - '0'; break; case ';': case 'm': code[code[3]] = x; x = 0; switch (t) { case kSgr: switch (code[0]) { case 0: pty->fg = 0; pty->bg = 0; pty->pr = 0; break; case 1: pty->pr |= kMachinePtyBold; break; case 21: pty->pr &= ~kMachinePtyBold; break; case 2: pty->pr |= kMachinePtyFaint; break; case 22: pty->pr &= ~kMachinePtyFaint; break; case 7: pty->pr |= kMachinePtyFlip; break; case 27: pty->pr &= ~kMachinePtyFlip; break; case 90 ... 97: code[0] -= 90 - 30; code[0] += 8; case 30 ... 37: pty->fg = code[0] - 30; pty->pr |= kMachinePtyFg; pty->pr &= ~kMachinePtyTrue; break; case 38: t = kSgrFg; break; case 39: pty->pr &= ~kMachinePtyFg; break; case 100 ... 107: code[0] -= 100 - 40; code[0] += 8; case 40 ... 47: pty->bg = code[0] - 40; pty->pr |= kMachinePtyBg; pty->pr &= ~kMachinePtyTrue; break; case 48: t = kSgrBg; break; case 49: pty->pr &= ~kMachinePtyBg; break; default: break; } break; case kSgrFg: case kSgrBg: switch (code[0]) { case 2: case 5: t += code[0]; break; default: t = kSgr; break; } break; case kSgrFgTrue: if (++code[3] == 3) { code[3] = 0; t = kSgr; pty->fg = READ32LE(code); pty->pr |= kMachinePtyFg; pty->pr |= kMachinePtyTrue; } break; case kSgrBgTrue: if (++code[3] == 3) { code[3] = 0; t = kSgr; pty->bg = READ32LE(code); pty->pr |= kMachinePtyBg; pty->pr |= kMachinePtyTrue; } break; case kSgrFgXterm: t = kSgr; pty->fg = code[0]; pty->pr |= kMachinePtyFg; pty->pr &= ~kMachinePtyTrue; break; case kSgrBgXterm: t = kSgr; pty->bg = code[0]; pty->pr |= kMachinePtyBg; pty->pr &= ~kMachinePtyTrue; break; default: abort(); } break; default: break; } } } static void MachinePtyHideCursor(struct MachinePty *pty) { pty->conf |= kMachinePtyNocursor; } static void MachinePtyShowCursor(struct MachinePty *pty) { pty->conf &= ~kMachinePtyNocursor; } static void MachinePtyReportCursorPosition(struct MachinePty *pty) { char *p; char buf[2 + 10 + 1 + 10 + 1]; p = buf; *p++ = '\e'; *p++ = '['; p += uint64toarray_radix10((pty->y + 1) & 0xffffffff, p); *p++ = ';'; p += uint64toarray_radix10((pty->x + 1) & 0xffffffff, p); *p++ = 'R'; CONCAT(&pty->input.p, &pty->input.i, &pty->input.n, buf, p - buf); } static void MachinePtyCsiN(struct MachinePty *pty) { switch (atoi(pty->esc.s)) { case 6: MachinePtyReportCursorPosition(pty); break; default: break; } } static void MachinePtyCsiScrollUp(struct MachinePty *pty) { int n; n = atoi(pty->esc.s); n = MAX(1, n); while (n--) { MachinePtyScroll(pty); } } static void MachinePtyCsi(struct MachinePty *pty) { switch (pty->esc.s[pty->esc.i - 1]) { case 'A': MachinePtyCursorMove(pty, -1, +0); break; case 'B': MachinePtyCursorMove(pty, +1, +0); break; case 'C': MachinePtyCursorMove(pty, +0, +1); break; case 'D': MachinePtyCursorMove(pty, +0, -1); break; case 'f': case 'H': MachinePtyCursorPosition(pty); break; case 'J': MachinePtyEraseDisplay(pty); break; case 'K': MachinePtyEraseLine(pty); break; case 'm': MachinePtySelectGraphicsRendition(pty); break; case 'n': MachinePtyCsiN(pty); break; case 'S': MachinePtyCsiScrollUp(pty); break; case 'l': if (strcmp(pty->esc.s, "?25l") == 0) { MachinePtyHideCursor(pty); } break; case 'h': if (strcmp(pty->esc.s, "?25h") == 0) { MachinePtyShowCursor(pty); } break; default: break; } } static void MachinePtyEsc(struct MachinePty *pty) { } static void MachinePtyEscAppend(struct MachinePty *pty, char c) { pty->esc.i = MIN(pty->esc.i + 1, ARRAYLEN(pty->esc.s) - 1); pty->esc.s[pty->esc.i - 1] = c; pty->esc.s[pty->esc.i - 0] = '\0'; } ssize_t MachinePtyWrite(struct MachinePty *pty, const void *data, size_t n) { int i; const uint8_t *p; for (p = data, i = 0; i < n; ++i) { switch (pty->state) { case kMachinePtyAscii: if (p[i] < 0b10000000) { switch (p[i]) { case '\e': pty->state = kMachinePtyEsc; pty->esc.i = 0; break; case '\t': MachinePtyTab(pty); break; case '\r': pty->x = 0; break; case '\n': MachinePtyNewline(pty); break; case 0177: case '\b': pty->x = MAX(0, pty->x - 1); break; case '\f': break; case '\a': break; default: SetMachinePtyCell(pty, p[i]); break; } } else if (!ThomPikeCont(p[i])) { pty->state = kMachinePtyUtf8; pty->u8 = ThomPikeByte(p[i]); pty->n8 = ThomPikeLen(p[i]) - 1; } break; case kMachinePtyUtf8: if (ThomPikeCont(p[i])) { pty->u8 = ThomPikeMerge(pty->u8, p[i]); if (--pty->n8) break; } SetMachinePtyCell(pty, pty->u8); pty->state = kMachinePtyAscii; pty->u8 = 0; --i; break; case kMachinePtyEsc: if (p[i] == '[') { pty->state = kMachinePtyCsi; } else if (0x30 <= p[i] && p[i] <= 0x7e) { MachinePtyEscAppend(pty, p[i]); MachinePtyEsc(pty); pty->state = kMachinePtyAscii; } else if (0x20 <= p[i] && p[i] <= 0x2f) { MachinePtyEscAppend(pty, p[i]); } else { pty->state = kMachinePtyAscii; } break; case kMachinePtyCsi: MachinePtyEscAppend(pty, p[i]); switch (p[i]) { case ':': case ';': case '<': case '=': case '>': case '?': case '0' ... '9': break; case '`': case '~': case '^': case '@': case '[': case ']': case '{': case '}': case '_': case '|': case '\\': case 'A' ... 'Z': case 'a' ... 'z': MachinePtyCsi(pty); pty->state = kMachinePtyAscii; break; default: pty->state = kMachinePtyAscii; continue; } break; default: abort(); } } return n; } ssize_t MachinePtyWriteInput(struct MachinePty *pty, const void *data, size_t n) { const char *p = data; CONCAT(&pty->input.p, &pty->input.i, &pty->input.n, data, n); if (!(pty->conf & kMachinePtyNoecho)) { MachinePtyWrite(pty, data, n); } return n; } ssize_t MachinePtyRead(struct MachinePty *pty, void *buf, size_t size) { char *p; size_t n; n = MIN(size, pty->input.i); if (!(pty->conf & kMachinePtyNocanon)) { if ((p = memchr(pty->input.p, '\n', n))) { n = MIN(n, pty->input.p - p + 1); } else { n = 0; } } memcpy(buf, pty->input.p, n); memcpy(pty->input.p, pty->input.p + n, pty->input.i - n); pty->input.i -= n; return n; } void MachinePtyAppendLine(struct MachinePty *pty, struct Buffer *buf, unsigned y) { bool atcursor; uint32_t x, i, fg, bg, pr, wc, w; if (y >= pty->yn) return; for (fg = bg = pr = x = 0; x < pty->xn; x += w) { i = y * pty->xn + x; wc = pty->wcs[i]; w = MAX(0, wcwidth(wc)); atcursor = y == pty->y && x == pty->x; if ((w && atcursor) || (pty->prs[i] != pr || pty->fgs[i] != fg || pty->bgs[i] != bg)) { fg = pty->fgs[i]; bg = pty->bgs[i]; pr = pty->prs[i]; AppendStr(buf, "\e[0"); if (w && atcursor) { pr ^= kMachinePtyFlip; } if (pr & kMachinePtyBold) AppendStr(buf, ";1"); if (pr & kMachinePtyFaint) AppendStr(buf, ";2"); if (pr & kMachinePtyBlink) AppendStr(buf, ";5"); if (pr & kMachinePtyFlip) AppendStr(buf, ";7"); if (pr & kMachinePtyFg) { if (pr & kMachinePtyTrue) { AppendFmt(buf, ";38;2;%d;%d;%d", (fg & 0x0000ff) >> 000, (fg & 0x00ff00) >> 010, (fg & 0xff0000) >> 020); } else { AppendFmt(buf, ";38;5;%d", fg); } } if (pr & kMachinePtyBg) { if (pr & kMachinePtyTrue) { AppendFmt(buf, ";48;2;%d;%d;%d", (bg & 0x0000ff) >> 000, (bg & 0x00ff00) >> 010, (bg & 0xff0000) >> 020); } else { AppendFmt(buf, ";48;5;%d", bg); } } AppendStr(buf, "m"); } if (w) { AppendWide(buf, wc); } else { w = 1; if (atcursor) { if (!(pty->conf & kMachinePtyNocursor)) { if (pty->conf & kMachinePtyBlinkcursor) { AppendStr(buf, "\e[5m"); } AppendWide(buf, u'▂'); if (pty->conf & kMachinePtyBlinkcursor) { AppendStr(buf, "\e[25m"); } } } else { AppendChar(buf, ' '); } } } AppendStr(buf, "\e[0m"); }