/*-*- 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/assert.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/log/log.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/str/tpenc.h" #include "libc/sysv/errfuns.h" #include "libc/unicode/unicode.h" #include "libc/x/x.h" #include "tool/build/lib/pty.h" /** * @fileoverview Pseudoteletypewriter * * \t TAB * \a BELL * \r CURSOR START * \b CURSOR LEFT * \177 CURSOR LEFT * \n CURSOR DOWN AND START IF OPOST * \f CURSOR DOWN AND START IF OPOST * \v CURSOR DOWN AND START IF OPOST * \eD CURSOR DOWN AND START * \eE CURSOR DOWN * \eM CURSOR UP * \ec FULL RESET * \e7 SAVE CURSOR POSITION * \e8 RESTORE CURSOR POSITION * \e(0 DEC SPECIAL GRAPHICS * \e(B USAS X3.4-1967 * \e#5 SINGLE WIDTH * \e#6 DOUBLE WIDTH * \e#8 SO MANY E * \eZ → \e/Z REPORT IDENTITY * \e[𝑛A CURSOR UP [CLAMPED] * \e[𝑛B CURSOR DOWN [CLAMPED] * \e[𝑛C CURSOR RIGHT [CLAMPED] * \e[𝑛D CURSOR LEFT [CLAMPED] * \e[𝑦;𝑥H SET CURSOR POSITION [CLAMPED] * \e[𝑥G SET CURSOR COLUMN [CLAMPED] * \e[𝑦d SET CURSOR ROW [CLAMPED] * \e[𝑛E CURSOR DOWN AND START [CLAMPED] * \e[𝑛F CURSOR UP AND START [CLAMPED] * \e[𝑛S SCROLL UP * \e[𝑛T SCROLL DOWN * \e[𝑛@ INSERT CELLS * \e[𝑛P DELETE CELLS * \e[𝑛L INSERT LINES * \e[𝑛M DELETE LINES * \e[J ERASE DISPLAY FORWARDS * \e[1J ERASE DISPLAY BACKWARDS * \e[2J ERASE DISPLAY * \e[K ERASE LINE FORWARD * \e[1K ERASE LINE BACKWARD * \e[2K ERASE LINE * \e[𝑛X ERASE CELLS * \e[0m RESET * \e[1m BOLD * \e[2m FAINT * \e[3m ITALIC * \e[4m UNDER * \e[5m BLINK * \e[7m INVERT * \e[8m CONCEAL * \e[9m STRIKE * \e[20m FRAKTUR * \e[21m DUNDER * \e[22m RESET BOLD & FAINT * \e[23m RESET ITALIC & FRAKTUR * \e[24m RESET UNDER & DUNDER * \e[25m RESET BLINK * \e[27m RESET INVERT * \e[28m RESET CONCEAL * \e[29m RESET STRIKE * \e[39m RESET FOREGROUND * \e[49m RESET BACKGROUND * \e[30m BLACK FOREGROUND * \e[31m RED FOREGROUND * \e[32m GREEN FOREGROUND * \e[33m YELLOW FOREGROUND * \e[34m BLUE FOREGROUND * \e[35m MAGENTA FOREGROUND * \e[36m CYAN FOREGROUND * \e[37m WHITE FOREGROUND * \e[40m BLACK BACKGROUND * \e[41m RED BACKGROUND * \e[42m GREEN BACKGROUND * \e[44m YELLOW BACKGROUND * \e[44m BLUE BACKGROUND * \e[45m MAGENTA BACKGROUND * \e[46m CYAN BACKGROUND * \e[47m WHITE BACKGROUND * \e[90m BRIGHT BLACK FOREGROUND * \e[91m BRIGHT RED FOREGROUND * \e[92m BRIGHT GREEN FOREGROUND * \e[93m BRIGHT YELLOW FOREGROUND * \e[94m BRIGHT BLUE FOREGROUND * \e[95m BRIGHT MAGENTA FOREGROUND * \e[96m BRIGHT CYAN FOREGROUND * \e[97m BRIGHT WHITE FOREGROUND * \e[100m BRIGHT BLACK BACKGROUND * \e[101m BRIGHT RED BACKGROUND * \e[102m BRIGHT GREEN BACKGROUND * \e[103m BRIGHT YELLOW BACKGROUND * \e[104m BRIGHT BLUE BACKGROUND * \e[105m BRIGHT MAGENTA BACKGROUND * \e[106m BRIGHT CYAN BACKGROUND * \e[107m BRIGHT WHITE BACKGROUND * \e[38;5;𝑥m XTERM256 FOREGROUND * \e[48;5;𝑥m XTERM256 BACKGROUND * \e[38;2;𝑟;𝑔;𝑏m RGB FOREGROUND * \e[48;2;𝑟;𝑔;𝑏m RGB BACKGROUND * \e[?25h SHOW CURSOR * \e[?25l HIDE CURSOR * \e[s SAVE CURSOR POSITION * \e[u RESTORE CURSOR POSITION * \e[0q RESET LEDS * \e[1q TURN ON FIRST LED * \e[2q TURN ON SECOND LED * \e[3q TURN ON THIRD LED * \e[4q TURN ON FOURTH LED * \e[c → \e[?1;0c REPORT DEVICE TYPE * \e[5n → \e[0n REPORT DEVICE STATUS * \e[6n → \e[𝑦;𝑥R REPORT CURSOR POSITION * \e7\e[9979;9979H\e[6n\e8 → \e[𝑦;𝑥R REPORT DISPLAY DIMENSIONS * * @see https://vt100.net/docs/vt220-rm/chapter4.html * @see https://invisible-island.net/xterm/ * @see ANSI X3.64-1979 * @see ISO/IEC 6429 * @see FIPS-86 * @see ECMA-48 */ struct Pty *NewPty(void) { struct Pty *pty; pty = xcalloc(1, sizeof(struct Pty)); PtyResize(pty, 25, 80); PtyFullReset(pty); PtyErase(pty, 0, pty->yn * pty->xn); return pty; } static void FreePtyPlanes(struct Pty *pty) { free(pty->wcs); free(pty->fgs); free(pty->bgs); free(pty->prs); } void FreePty(struct Pty *pty) { if (pty) { FreePtyPlanes(pty); free(pty); } } static wchar_t *GetXlatAscii(void) { unsigned i; static bool once; static wchar_t xlat[128]; if (!once) { for (i = 0; i < 128; ++i) { xlat[i] = i; } once = true; } return xlat; } static wchar_t *GetXlatLineDrawing(void) { unsigned i; static bool once; static wchar_t xlat[128]; if (!once) { for (i = 0; i < 128; ++i) { if (0x5F <= i && i <= 0x7E) { xlat[i] = u" ◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·"[i - 0x5F]; } else { xlat[i] = i; } } once = true; } return xlat; } static void XlatAlphabet(wchar_t xlat[128], int a, int b) { unsigned i; for (i = 0; i < 128; ++i) { if ('a' <= i && i <= 'z') { xlat[i] = i - 'a' + a; } else if ('A' <= i && i <= 'Z') { xlat[i] = i - 'A' + b; } else { xlat[i] = i; } } } static wchar_t *GetXlatItalic(void) { static bool once; static wchar_t xlat[128]; if (!once) { XlatAlphabet(xlat, L'𝑎', L'𝐴'); once = true; } return xlat; } static wchar_t *GetXlatBoldItalic(void) { static bool once; static wchar_t xlat[128]; if (!once) { XlatAlphabet(xlat, L'𝒂', L'𝑨'); once = true; } return xlat; } static wchar_t *GetXlatBoldFraktur(void) { static bool once; static wchar_t xlat[128]; if (!once) { XlatAlphabet(xlat, L'𝖆', L'𝕬'); once = true; } return xlat; } static wchar_t *GetXlatFraktur(void) { unsigned i; static bool once; static wchar_t xlat[128]; if (!once) { for (i = 0; i < ARRAYLEN(xlat); ++i) { if ('A' <= i && i <= 'Z') { xlat[i] = L"𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ"[i - 'A']; } else if ('a' <= i && i <= 'z') { xlat[i] = i - 'a' + L'𝔞'; } else { xlat[i] = i; } } once = true; } return xlat; } static wchar_t *GetXlatDoubleWidth(void) { unsigned i; static bool once; static wchar_t xlat[128]; if (!once) { for (i = 0; i < ARRAYLEN(xlat); ++i) { if ('!' <= i && i <= '~') { xlat[i] = -(i - '!' + L'!'); } else { xlat[i] = i; } } once = true; } return xlat; } static wchar_t *GetXlatSgr(struct Pty *pty) { switch (!!(pty->pr & kPtyFraktur) << 2 | !!(pty->pr & kPtyItalic) << 1 | !!(pty->pr & kPtyBold) << 0) { case 0b100: case 0b110: return GetXlatFraktur(); case 0b101: case 0b111: return GetXlatBoldFraktur(); case 0b011: return GetXlatBoldItalic(); case 0b010: return GetXlatItalic(); default: return GetXlatAscii(); } } static void PtySetXlat(struct Pty *pty, wchar_t *xlat) { pty->xlat = xlat; pty->pr &= ~(kPtyItalic | kPtyFraktur); } static void PtySetCodepage(struct Pty *pty, char id) { unsigned i; switch (id) { default: case 'B': PtySetXlat(pty, GetXlatAscii()); break; case '0': PtySetXlat(pty, GetXlatLineDrawing()); break; } } void PtyErase(struct Pty *pty, long dst, long n) { DCHECK_LE(dst + n, pty->yn * pty->xn); wmemset((void *)(pty->wcs + dst), ' ', n); wmemset((void *)(pty->prs + dst), 0, n); } void PtyMemmove(struct Pty *pty, long dst, long src, long n) { DCHECK_LE(src + n, pty->yn * pty->xn); DCHECK_LE(dst + n, pty->yn * pty->xn); memmove(pty->wcs + dst, pty->wcs + src, n * 4); memmove(pty->fgs + dst, pty->fgs + src, n * 4); memmove(pty->bgs + dst, pty->bgs + src, n * 4); memmove(pty->prs + dst, pty->prs + src, n * 4); } void PtyFullReset(struct Pty *pty) { pty->y = 0; pty->x = 0; pty->pr = 0; pty->u8 = 0; pty->n8 = 0; pty->conf = 0; pty->save = 0; pty->state = 0; pty->esc.i = 0; pty->input.i = 0; pty->xlat = GetXlatAscii(); PtyErase(pty, 0, pty->yn * pty->xn); } void PtySetY(struct Pty *pty, int y) { pty->conf &= ~kPtyRedzone; pty->y = MAX(0, MIN(pty->yn - 1, y)); } void PtySetX(struct Pty *pty, int x) { pty->conf &= ~kPtyRedzone; pty->x = MAX(0, MIN(pty->xn - 1, x)); } void PtyResize(struct Pty *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); } FreePtyPlanes(pty); pty->wcs = wcs; pty->fgs = fgs; pty->bgs = bgs; pty->prs = prs; pty->yn = yn; pty->xn = xn; PtySetY(pty, pty->y); PtySetX(pty, pty->x); } static void PtyConcatInput(struct Pty *pty, const char *data, size_t n) { CONCAT(&pty->input.p, &pty->input.i, &pty->input.n, data, n); } static void PtyScroll(struct Pty *pty) { PtyMemmove(pty, 0, pty->xn, pty->xn * (pty->yn - 1)); PtyErase(pty, pty->xn * (pty->yn - 1), pty->xn); } static void PtyReverse(struct Pty *pty) { PtyMemmove(pty, pty->xn, 0, pty->xn * (pty->yn - 1)); PtyErase(pty, 0, pty->xn); } static void PtyIndex(struct Pty *pty) { if (pty->y < pty->yn - 1) { ++pty->y; } else { PtyScroll(pty); } } static void PtyReverseIndex(struct Pty *pty) { if (pty->y) { --pty->y; } else { PtyReverse(pty); } } static void PtyCarriageReturn(struct Pty *pty) { PtySetX(pty, 0); } static void PtyNewline(struct Pty *pty) { PtyIndex(pty); if (!(pty->conf & kPtyNoopost)) { PtyCarriageReturn(pty); } } static void PtyAdvance(struct Pty *pty) { DCHECK_EQ(pty->xn - 1, pty->x); DCHECK(pty->conf & kPtyRedzone); pty->conf &= ~kPtyRedzone; pty->x = 0; if (pty->y < pty->yn - 1) { ++pty->y; } else { PtyScroll(pty); } } static void PtyWriteGlyph(struct Pty *pty, wint_t wc, int w) { uint32_t i; if (w < 1) wc = ' ', w = 1; if ((pty->conf & kPtyRedzone) || pty->x + w > pty->xn) { PtyAdvance(pty); } i = pty->y * pty->xn + pty->x; pty->wcs[i] = wc; if ((pty->prs[i] = pty->pr) & (kPtyFg | kPtyBg)) { pty->fgs[i] = pty->fg; pty->bgs[i] = pty->bg; } if ((pty->x += w) >= pty->xn) { pty->x = pty->xn - 1; pty->conf |= kPtyRedzone; } } static void PtyWriteTab(struct Pty *pty) { unsigned x, x2; if (pty->conf & kPtyRedzone) { PtyAdvance(pty); } x2 = MIN(pty->xn, ROUNDUP(pty->x + 1, 8)); for (x = pty->x; x < x2; ++x) { pty->wcs[pty->y * pty->xn + x] = ' '; } if (x2 < pty->xn) { pty->x = x2; } else { pty->x = pty->xn - 1; pty->conf |= kPtyRedzone; } } int PtyAtoi(const char *s, const char **e) { int i; for (i = 0; isdigit(*s); ++s) i *= 10, i += *s - '0'; if (e) *e = s; return i; } static int PtyGetMoveParam(struct Pty *pty) { int x = PtyAtoi(pty->esc.s, NULL); if (x < 1) x = 1; return x; } static void PtySetCursorPosition(struct Pty *pty) { int row, col; const char *s = pty->esc.s; row = max(1, PtyAtoi(s, &s)); if (*s == ';') ++s; col = max(1, PtyAtoi(s, &s)); PtySetY(pty, row - 1); PtySetX(pty, col - 1); } static void PtySetCursorRow(struct Pty *pty) { PtySetY(pty, PtyGetMoveParam(pty) - 1); } static void PtySetCursorColumn(struct Pty *pty) { PtySetX(pty, PtyGetMoveParam(pty) - 1); } static void PtyMoveCursor(struct Pty *pty, int dy, int dx) { int n = PtyGetMoveParam(pty); PtySetY(pty, pty->y + dy * n); PtySetX(pty, pty->x + dx * n); } static void PtyScrollUp(struct Pty *pty) { int n = PtyGetMoveParam(pty); while (n--) PtyScroll(pty); } static void PtyScrollDown(struct Pty *pty) { int n = PtyGetMoveParam(pty); while (n--) PtyReverse(pty); } static void PtySetCursorStatus(struct Pty *pty, bool status) { if (status) { pty->conf &= ~kPtyNocursor; } else { pty->conf |= kPtyNocursor; } } static void PtySetMode(struct Pty *pty, bool status) { const char *p = pty->esc.s; switch (*p++) { case '?': while (isdigit(*p)) { switch (PtyAtoi(p, &p)) { case 25: PtySetCursorStatus(pty, status); break; default: break; } if (*p == ';') { ++p; } } break; default: break; } } static void PtySaveCursorPosition(struct Pty *pty) { pty->save = (pty->y & 0x7FFF) | (pty->x & 0x7FFF) << 16; } static void PtyRestoreCursorPosition(struct Pty *pty) { PtySetY(pty, (pty->save & 0x00007FFF) >> 000); PtySetX(pty, (pty->save & 0x7FFF0000) >> 020); } static void PtyEraseDisplay(struct Pty *pty) { switch (PtyAtoi(pty->esc.s, NULL)) { case 0: PtyErase(pty, pty->y * pty->xn + pty->x, pty->yn * pty->xn - (pty->y * pty->xn + pty->x)); break; case 1: PtyErase(pty, 0, pty->y * pty->xn + pty->x); break; case 2: case 3: PtyErase(pty, 0, pty->yn * pty->xn); break; default: break; } } static void PtyEraseLine(struct Pty *pty) { switch (PtyAtoi(pty->esc.s, NULL)) { case 0: PtyErase(pty, pty->y * pty->xn + pty->x, pty->xn - pty->x); break; case 1: PtyErase(pty, pty->y * pty->xn, pty->x); break; case 2: PtyErase(pty, pty->y * pty->xn, pty->xn); break; default: break; } } static void PtyEraseCells(struct Pty *pty) { int i, n, x; i = pty->y * pty->xn + pty->x; n = pty->yn * pty->xn; x = min(max(PtyAtoi(pty->esc.s, NULL), 1), n - i); PtyErase(pty, i, x); } static int PtyArg1(struct Pty *pty) { return max(1, PtyAtoi(pty->esc.s, NULL)); } static void PtyInsertCells(struct Pty *pty) { int n = min(pty->xn - pty->x, PtyArg1(pty)); PtyMemmove(pty, pty->y * pty->xn + pty->x + n, pty->y * pty->xn + pty->x, pty->xn - (pty->x + n)); PtyErase(pty, pty->y * pty->xn + pty->x, n); } static void PtyInsertLines(struct Pty *pty) { int n = min(pty->yn - pty->y, PtyArg1(pty)); PtyMemmove(pty, (pty->y + n) * pty->xn, pty->y * pty->xn, (pty->yn - pty->y - n) * pty->xn); PtyErase(pty, pty->y * pty->xn, n * pty->xn); } static void PtyDeleteCells(struct Pty *pty) { int n = min(pty->xn - pty->x, PtyArg1(pty)); PtyMemmove(pty, pty->y * pty->xn + pty->x, pty->y * pty->xn + pty->x + n, pty->xn - (pty->x + n)); PtyErase(pty, pty->y * pty->xn + pty->x, n); } static void PtyDeleteLines(struct Pty *pty) { int n = min(pty->yn - pty->y, PtyArg1(pty)); PtyMemmove(pty, pty->y * pty->xn, (pty->y + n) * pty->xn, (pty->yn - pty->y - n) * pty->xn); PtyErase(pty, (pty->y + n) * pty->xn, n * pty->xn); } static void PtyReportDeviceStatus(struct Pty *pty) { PtyWriteInput(pty, "\e[0n", 4); } static void PtyReportPreferredVtType(struct Pty *pty) { PtyWriteInput(pty, "\e[?1;0c", 4); } static void PtyReportPreferredVtIdentity(struct Pty *pty) { PtyWriteInput(pty, "\e/Z", 4); } static void PtyBell(struct Pty *pty) { pty->conf |= kPtyBell; } static void PtyLed(struct Pty *pty) { switch (PtyAtoi(pty->esc.s, NULL)) { case 0: pty->conf &= ~kPtyLed1; pty->conf &= ~kPtyLed2; pty->conf &= ~kPtyLed3; pty->conf &= ~kPtyLed4; break; case 1: pty->conf |= kPtyLed1; break; case 2: pty->conf |= kPtyLed2; break; case 3: pty->conf |= kPtyLed3; break; case 4: pty->conf |= kPtyLed4; break; default: break; } } static void PtyReportCursorPosition(struct Pty *pty) { char *p; char buf[2 + 10 + 1 + 10 + 1]; p = buf; *p++ = '\e'; *p++ = '['; p += uint64toarray_radix10((pty->y + 1) & 0x7fff, p); *p++ = ';'; p += uint64toarray_radix10((pty->x + 1) & 0x7fff, p); *p++ = 'R'; PtyWriteInput(pty, buf, p - buf); } static void PtyCsiN(struct Pty *pty) { switch (PtyAtoi(pty->esc.s, NULL)) { case 5: PtyReportDeviceStatus(pty); break; case 6: PtyReportCursorPosition(pty); break; default: break; } } static void PtySelectGraphicsRendition(struct Pty *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 38: t = kSgrFg; break; case 48: t = kSgrBg; break; case 0: pty->pr = 0; pty->xlat = GetXlatSgr(pty); break; case 1: pty->pr |= kPtyBold; pty->xlat = GetXlatSgr(pty); break; case 2: pty->pr |= kPtyFaint; break; case 3: pty->pr |= kPtyItalic; pty->xlat = GetXlatSgr(pty); break; case 4: pty->pr |= kPtyUnder; break; case 5: pty->pr |= kPtyBlink; break; case 7: pty->pr |= kPtyFlip; break; case 8: pty->pr |= kPtyConceal; break; case 9: pty->pr |= kPtyStrike; break; case 20: pty->pr |= kPtyFraktur; pty->xlat = GetXlatSgr(pty); break; case 21: pty->pr |= kPtyUnder | kPtyDunder; break; case 22: pty->pr &= ~(kPtyFaint | kPtyBold); pty->xlat = GetXlatSgr(pty); break; case 23: pty->pr &= ~kPtyItalic; pty->xlat = GetXlatSgr(pty); break; case 24: pty->pr &= ~(kPtyUnder | kPtyDunder); break; case 25: pty->pr &= ~kPtyBlink; break; case 27: pty->pr &= ~kPtyFlip; break; case 28: pty->pr &= ~kPtyConceal; break; case 29: pty->pr &= ~kPtyStrike; break; case 39: pty->pr &= ~kPtyFg; break; case 49: pty->pr &= ~kPtyBg; break; case 90 ... 97: code[0] -= 90 - 30; code[0] += 8; /* fallthrough */ case 30 ... 37: pty->fg = code[0] - 30; pty->pr |= kPtyFg; pty->pr &= ~kPtyTrue; break; case 100 ... 107: code[0] -= 100 - 40; code[0] += 8; /* fallthrough */ case 40 ... 47: pty->bg = code[0] - 40; pty->pr |= kPtyBg; pty->pr &= ~kPtyTrue; 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 |= kPtyFg; pty->pr |= kPtyTrue; } break; case kSgrBgTrue: if (++code[3] == 3) { code[3] = 0; t = kSgr; pty->bg = READ32LE(code); pty->pr |= kPtyBg; pty->pr |= kPtyTrue; } break; case kSgrFgXterm: t = kSgr; pty->fg = code[0]; pty->pr |= kPtyFg; pty->pr &= ~kPtyTrue; break; case kSgrBgXterm: t = kSgr; pty->bg = code[0]; pty->pr |= kPtyBg; pty->pr &= ~kPtyTrue; break; default: abort(); } break; default: break; } } } static void PtyCsi(struct Pty *pty) { switch (pty->esc.s[pty->esc.i - 1]) { case 'f': case 'H': PtySetCursorPosition(pty); break; case 'G': PtySetCursorColumn(pty); break; case 'd': PtySetCursorRow(pty); break; case 'F': pty->x = 0; /* fallthrough */ case 'A': PtyMoveCursor(pty, -1, +0); break; case 'E': pty->x = 0; /* fallthrough */ case 'B': PtyMoveCursor(pty, +1, +0); break; case 'C': PtyMoveCursor(pty, +0, +1); break; case 'D': PtyMoveCursor(pty, +0, -1); break; case 'S': PtyScrollUp(pty); break; case 'T': PtyScrollDown(pty); break; case '@': PtyInsertCells(pty); break; case 'P': PtyDeleteCells(pty); break; case 'L': PtyInsertLines(pty); break; case 'M': PtyDeleteLines(pty); break; case 'J': PtyEraseDisplay(pty); break; case 'K': PtyEraseLine(pty); break; case 'X': PtyEraseCells(pty); break; case 's': PtySaveCursorPosition(pty); break; case 'u': PtyRestoreCursorPosition(pty); break; case 'n': PtyCsiN(pty); break; case 'm': PtySelectGraphicsRendition(pty); break; case 'h': PtySetMode(pty, true); break; case 'l': PtySetMode(pty, false); break; case 'c': PtyReportPreferredVtType(pty); break; case 'q': PtyLed(pty); break; default: break; } } static void PtyScreenAlignmentDisplay(struct Pty *pty) { wmemset((void *)pty->wcs, 'E', pty->yn * pty->xn); } static void PtyEscHash(struct Pty *pty) { switch (pty->esc.s[1]) { case '5': PtySetXlat(pty, GetXlatAscii()); break; case '6': PtySetXlat(pty, GetXlatDoubleWidth()); break; case '8': PtyScreenAlignmentDisplay(pty); break; default: break; } } static void PtyEsc(struct Pty *pty) { switch (pty->esc.s[0]) { case 'c': PtyFullReset(pty); break; case '7': PtySaveCursorPosition(pty); break; case '8': PtyRestoreCursorPosition(pty); break; case 'E': pty->x = 0; case 'D': PtyIndex(pty); break; case 'M': PtyReverseIndex(pty); break; case 'Z': PtyReportPreferredVtIdentity(pty); break; case '(': PtySetCodepage(pty, pty->esc.s[1]); break; case '#': PtyEscHash(pty); break; default: break; } } static void PtyCntrl(struct Pty *pty, int c01) { switch (c01) { case '\a': PtyBell(pty); break; case 0x85: case '\f': case '\v': case '\n': PtyNewline(pty); break; case '\r': PtyCarriageReturn(pty); break; case '\e': pty->state = kPtyEsc; pty->esc.i = 0; break; case '\t': PtyWriteTab(pty); break; case 0x7F: case '\b': pty->x = MAX(0, pty->x - 1); break; case 0x84: PtyIndex(pty); break; case 0x8D: PtyReverseIndex(pty); break; case 0x9B: pty->state = kPtyCsi; break; default: break; } } static void PtyEscAppend(struct Pty *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 PtyWrite(struct Pty *pty, const void *data, size_t n) { int i; wchar_t wc; const uint8_t *p; for (p = data, i = 0; i < n; ++i) { switch (pty->state) { case kPtyAscii: if (0x00 <= p[i] && p[i] <= 0x7F) { if (0x20 <= p[i] && p[i] <= 0x7E) { if ((wc = pty->xlat[p[i]]) >= 0) { PtyWriteGlyph(pty, wc, 1); } else { PtyWriteGlyph(pty, -wc, 2); } } else { PtyCntrl(pty, p[i]); } } else if (!ThomPikeCont(p[i])) { pty->state = kPtyUtf8; pty->u8 = ThomPikeByte(p[i]); pty->n8 = ThomPikeLen(p[i]) - 1; } break; case kPtyUtf8: if (ThomPikeCont(p[i])) { pty->u8 = ThomPikeMerge(pty->u8, p[i]); if (--pty->n8) break; } wc = pty->u8; if ((0x00 <= wc && wc <= 0x1F) || (0x7F <= wc && wc <= 0x9F)) { PtyCntrl(pty, wc); } else { PtyWriteGlyph(pty, wc, wcwidth(wc)); } pty->state = kPtyAscii; pty->u8 = 0; --i; break; case kPtyEsc: if (p[i] == '[') { pty->state = kPtyCsi; } else if (0x30 <= p[i] && p[i] <= 0x7E) { PtyEscAppend(pty, p[i]); PtyEsc(pty); pty->state = kPtyAscii; } else if (0x20 <= p[i] && p[i] <= 0x2F) { PtyEscAppend(pty, p[i]); } else { pty->state = kPtyAscii; } break; case kPtyCsi: PtyEscAppend(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': PtyCsi(pty); pty->state = kPtyAscii; break; default: pty->state = kPtyAscii; continue; } break; default: unreachable; } } return n; } ssize_t PtyWriteInput(struct Pty *pty, const void *data, size_t n) { PtyConcatInput(pty, data, n); if (!(pty->conf & kPtyNoecho)) { PtyWrite(pty, data, n); } return n; } ssize_t PtyRead(struct Pty *pty, void *buf, size_t size) { char *p; size_t n; n = MIN(size, pty->input.i); if (!(pty->conf & kPtyNocanon)) { 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; } static char *PtyEncodeRgb(char *p, int rgb) { *p++ = '2'; *p++ = ';'; p += uint64toarray_radix10((rgb & 0x0000ff) >> 000, p); *p++ = ';'; p += uint64toarray_radix10((rgb & 0x00ff00) >> 010, p); *p++ = ';'; p += uint64toarray_radix10((rgb & 0xff0000) >> 020, p); return p; } static char *PtyEncodeXterm256(char *p, int xt) { *p++ = '5'; *p++ = ';'; p += uint64toarray_radix10(xt, p); return p; } char *PtyEncodeStyle(char *p, uint32_t xr, uint32_t pr, uint32_t fg, uint32_t bg) { *p++ = '\e'; *p++ = '['; if (pr & (kPtyBold | kPtyFaint | kPtyFlip | kPtyUnder | kPtyDunder | kPtyBlink | kPtyStrike | kPtyFg | kPtyBg)) { if (xr & (kPtyBold | kPtyFaint)) { if ((xr & (kPtyBold | kPtyFaint)) ^ (pr & (kPtyBold | kPtyFaint))) { *p++ = '2'; *p++ = '2'; *p++ = ';'; } if (pr & kPtyBold) { *p++ = '1'; *p++ = ';'; } if (pr & kPtyFaint) { *p++ = '2'; *p++ = ';'; } } if (xr & (kPtyUnder | kPtyDunder)) { if ((xr & (kPtyUnder | kPtyDunder)) ^ (pr & (kPtyUnder | kPtyDunder))) { *p++ = '2'; *p++ = '4'; *p++ = ';'; } if (pr & kPtyUnder) { *p++ = '4'; *p++ = ';'; } if (pr & kPtyDunder) { *p++ = '2'; *p++ = '1'; *p++ = ';'; } } if (xr & (kPtyFlip | kPtyBlink | kPtyStrike)) { if (xr & kPtyFlip) { if (!(pr & kPtyFlip)) *p++ = '2'; *p++ = '7'; *p++ = ';'; } if (xr & kPtyBlink) { if (!(pr & kPtyBlink)) *p++ = '2'; *p++ = '5'; *p++ = ';'; } if (xr & kPtyStrike) { if (!(pr & kPtyStrike)) *p++ = '2'; *p++ = '9'; *p++ = ';'; } } if (xr & (kPtyFg | kPtyTrue)) { *p++ = '3'; if (pr & kPtyFg) { *p++ = '8'; *p++ = ';'; if (pr & kPtyTrue) { p = PtyEncodeRgb(p, fg); } else { p = PtyEncodeXterm256(p, fg); } } else { *p++ = '9'; } *p++ = ';'; } if (xr & (kPtyBg | kPtyTrue)) { *p++ = '4'; if (pr & kPtyBg) { *p++ = '8'; *p++ = ';'; if (pr & kPtyTrue) { p = PtyEncodeRgb(p, bg); } else { p = PtyEncodeXterm256(p, bg); } } else { *p++ = '9'; } *p++ = ';'; } DCHECK_EQ(';', p[-1]); p[-1] = 'm'; } else { *p++ = '0'; *p++ = 'm'; } return p; } int PtyAppendLine(struct Pty *pty, struct Buffer *buf, unsigned y) { uint64_t u; char *p, *pb; uint32_t i, j, n, w, wc, np, xp, pr, fg, bg, ci; if (y >= pty->yn) return einval(); n = buf->i + pty->xn * 60; /* torture character length */ if (n > buf->n) { if (!(p = realloc(buf->p, n))) return -1; buf->p = p; buf->n = n; } i = y * pty->xn; j = (y + 1) * pty->xn; pb = buf->p + buf->i; ci = !(pty->conf & kPtyNocursor) && y == pty->y ? i + pty->x : -1; for (pr = 0; i < j; i += w) { np = pty->prs[i]; if (!(np & kPtyConceal)) { wc = pty->wcs[i]; DCHECK(!(0x00 <= wc && wc <= 0x1F)); DCHECK(!(0x7F <= wc && wc <= 0x9F)); if (0x20 <= wc && wc <= 0x7E) { u = wc; w = 1; } else { u = tpenc(wc); w = max(1, wcwidth(wc)); } } else { u = ' '; w = 1; } if (i == ci) { if (u != ' ') { np ^= kPtyFlip; } else { u = tpenc(u'▂'); if (pty->conf & kPtyBlinkcursor) { np |= kPtyBlink; } } } fg = bg = -1; xp = pr ^ np; if (np & (kPtyFg | kPtyBg)) { if (np & kPtyFg) { if (pty->fgs[i] != fg) xp |= kPtyFg; fg = pty->fgs[i]; } if (np & kPtyBg) { if (pty->bgs[i] != bg) xp |= kPtyBg; bg = pty->bgs[i]; } } p = pb; if (xp) { pr = np; p = PtyEncodeStyle(p, xp, pr, fg, bg); } do { *p++ = u & 0xFF; u >>= 8; } while (u); DCHECK_LE(p - pb, 60); pb = p; } DCHECK_LE(pb - buf->p, buf->n); buf->i = pb - buf->p; return 0; }