1 /* See LICENSE for license details. */
 
  12 #include <sys/ioctl.h>
 
  13 #include <sys/select.h>
 
  14 #include <sys/types.h>
 
  25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
 
  27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
 
  32 #define UTF_INVALID   0xFFFD
 
  34 #define ESC_BUF_SIZ   (128*UTF_SIZ)
 
  35 #define ESC_ARG_SIZ   16
 
  36 #define STR_BUF_SIZ   ESC_BUF_SIZ
 
  37 #define STR_ARG_SIZ   ESC_ARG_SIZ
 
  40 #define IS_SET(flag)            ((term.mode & (flag)) != 0)
 
  41 #define ISCONTROLC0(c)          (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
 
  42 #define ISCONTROLC1(c)          (BETWEEN(c, 0x80, 0x9f))
 
  43 #define ISCONTROL(c)            (ISCONTROLC0(c) || ISCONTROLC1(c))
 
  44 #define ISDELIM(u)              (u && wcschr(worddelimiters, u))
 
  49         MODE_ALTSCREEN   = 1 << 2,
 
  57 enum cursor_movement {
 
  81         ESC_STR        = 4,  /* OSC, PM, APC */
 
  83         ESC_STR_END    = 16, /* a final string was encountered */
 
  84         ESC_TEST       = 32, /* Enter in test mode */
 
  90         Glyph attr; /* current char attributes */
 
 101          * Selection variables:
 
 102          * nb – normalized coordinates of the beginning of the selection
 
 103          * ne – normalized coordinates of the end of the selection
 
 104          * ob – original coordinates of the beginning of the selection
 
 105          * oe – original coordinates of the end of the selection
 
 114 /* Internal representation of the screen */
 
 116         int row;      /* nb row */
 
 117         int col;      /* nb col */
 
 118         Line *line;   /* screen */
 
 119         Line *alt;    /* alternate screen */
 
 120         int *dirty;   /* dirtyness of lines */
 
 121         TCursor c;    /* cursor */
 
 122         int ocx;      /* old cursor col */
 
 123         int ocy;      /* old cursor row */
 
 124         int top;      /* top    scroll limit */
 
 125         int bot;      /* bottom scroll limit */
 
 126         int mode;     /* terminal mode flags */
 
 127         int esc;      /* escape state flags */
 
 128         char trantbl[4]; /* charset table translation */
 
 129         int charset;  /* current charset */
 
 130         int icharset; /* selected charset for sequence */
 
 132         Rune lastc;   /* last printed char outside of sequence, 0 if control */
 
 135 /* CSI Escape sequence structs */
 
 136 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
 
 138         char buf[ESC_BUF_SIZ]; /* raw string */
 
 139         size_t len;            /* raw string length */
 
 141         int arg[ESC_ARG_SIZ];
 
 142         int narg;              /* nb of args */
 
 146 /* STR Escape sequence structs */
 
 147 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
 
 149         char type;             /* ESC type ... */
 
 150         char *buf;             /* allocated raw string */
 
 151         size_t siz;            /* allocation size */
 
 152         size_t len;            /* raw string length */
 
 153         char *args[STR_ARG_SIZ];
 
 154         int narg;              /* nb of args */
 
 157 static void execsh(char *, char **);
 
 158 static void stty(char **);
 
 159 static void sigchld(int);
 
 160 static void ttywriteraw(const char *, size_t);
 
 162 static void csidump(void);
 
 163 static void csihandle(void);
 
 164 static void csiparse(void);
 
 165 static void csireset(void);
 
 166 static int eschandle(uchar);
 
 167 static void strdump(void);
 
 168 static void strhandle(void);
 
 169 static void strparse(void);
 
 170 static void strreset(void);
 
 172 static void tprinter(char *, size_t);
 
 173 static void tdumpsel(void);
 
 174 static void tdumpline(int);
 
 175 static void tdump(void);
 
 176 static void tclearregion(int, int, int, int);
 
 177 static void tcursor(int);
 
 178 static void tdeletechar(int);
 
 179 static void tdeleteline(int);
 
 180 static void tinsertblank(int);
 
 181 static void tinsertblankline(int);
 
 182 static int tlinelen(int);
 
 183 static void tmoveto(int, int);
 
 184 static void tmoveato(int, int);
 
 185 static void tnewline(int);
 
 186 static void tputtab(int);
 
 187 static void tputc(Rune);
 
 188 static void treset(void);
 
 189 static void tscrollup(int, int);
 
 190 static void tscrolldown(int, int);
 
 191 static void tsetattr(int *, int);
 
 192 static void tsetchar(Rune, Glyph *, int, int);
 
 193 static void tsetdirt(int, int);
 
 194 static void tsetscroll(int, int);
 
 195 static void tswapscreen(void);
 
 196 static void tsetmode(int, int, int *, int);
 
 197 static int twrite(const char *, int, int);
 
 198 static void tfulldirt(void);
 
 199 static void tcontrolcode(uchar );
 
 200 static void tdectest(char );
 
 201 static void tdefutf8(char);
 
 202 static int32_t tdefcolor(int *, int *, int);
 
 203 static void tdeftran(char);
 
 204 static void tstrsequence(uchar);
 
 206 static void drawregion(int, int, int, int);
 
 208 static void selnormalize(void);
 
 209 static void selscroll(int, int);
 
 210 static void selsnap(int *, int *, int);
 
 212 static size_t utf8decode(const char *, Rune *, size_t);
 
 213 static Rune utf8decodebyte(char, size_t *);
 
 214 static char utf8encodebyte(Rune, size_t);
 
 215 static size_t utf8validate(Rune *, size_t);
 
 217 static char *base64dec(const char *);
 
 218 static char base64dec_getc(const char **);
 
 220 static ssize_t xwrite(int, const char *, size_t);
 
 224 static Selection sel;
 
 225 static CSIEscape csiescseq;
 
 226 static STREscape strescseq;
 
 231 static uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
 
 232 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
 
 233 static Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
 
 234 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
 
 237 xwrite(int fd, const char *s, size_t len)
 
 243                 r = write(fd, s, len);
 
 258         if (!(p = malloc(len)))
 
 259                 die("malloc: %s\n", strerror(errno));
 
 265 xrealloc(void *p, size_t len)
 
 267         if ((p = realloc(p, len)) == NULL)
 
 268                 die("realloc: %s\n", strerror(errno));
 
 276         if ((s = strdup(s)) == NULL)
 
 277                 die("strdup: %s\n", strerror(errno));
 
 283 utf8decode(const char *c, Rune *u, size_t clen)
 
 285         size_t i, j, len, type;
 
 291         udecoded = utf8decodebyte(c[0], &len);
 
 292         if (!BETWEEN(len, 1, UTF_SIZ))
 
 294         for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
 
 295                 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
 
 302         utf8validate(u, len);
 
 308 utf8decodebyte(char c, size_t *i)
 
 310         for (*i = 0; *i < LEN(utfmask); ++(*i))
 
 311                 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
 
 312                         return (uchar)c & ~utfmask[*i];
 
 318 utf8encode(Rune u, char *c)
 
 322         len = utf8validate(&u, 0);
 
 326         for (i = len - 1; i != 0; --i) {
 
 327                 c[i] = utf8encodebyte(u, 0);
 
 330         c[0] = utf8encodebyte(u, len);
 
 336 utf8encodebyte(Rune u, size_t i)
 
 338         return utfbyte[i] | (u & ~utfmask[i]);
 
 342 utf8validate(Rune *u, size_t i)
 
 344         if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
 
 346         for (i = 1; *u > utfmax[i]; ++i)
 
 352 static const char base64_digits[] = {
 
 353         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
 354         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
 
 355         63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
 
 356         2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
 
 357         22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
 
 358         35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
 
 359         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
 360         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
 361         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
 362         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
 363         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
 364         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
 
 368 base64dec_getc(const char **src)
 
 370         while (**src && !isprint(**src))
 
 372         return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
 
 376 base64dec(const char *src)
 
 378         size_t in_len = strlen(src);
 
 382                 in_len += 4 - (in_len % 4);
 
 383         result = dst = xmalloc(in_len / 4 * 3 + 1);
 
 385                 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
 
 386                 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
 
 387                 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
 
 388                 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
 
 390                 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
 
 391                 if (a == -1 || b == -1)
 
 394                 *dst++ = (a << 2) | ((b & 0x30) >> 4);
 
 397                 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
 
 400                 *dst++ = ((c & 0x03) << 6) | d;
 
 419         if (term.line[y][i - 1].mode & ATTR_WRAP)
 
 422         while (i > 0 && term.line[y][i - 1].u == ' ')
 
 429 selstart(int col, int row, int snap)
 
 432         sel.mode = SEL_EMPTY;
 
 433         sel.type = SEL_REGULAR;
 
 434         sel.alt = IS_SET(MODE_ALTSCREEN);
 
 436         sel.oe.x = sel.ob.x = col;
 
 437         sel.oe.y = sel.ob.y = row;
 
 441                 sel.mode = SEL_READY;
 
 442         tsetdirt(sel.nb.y, sel.ne.y);
 
 446 selextend(int col, int row, int type, int done)
 
 448         int oldey, oldex, oldsby, oldsey, oldtype;
 
 450         if (sel.mode == SEL_IDLE)
 
 452         if (done && sel.mode == SEL_EMPTY) {
 
 468         if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
 
 469                 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
 
 471         sel.mode = done ? SEL_IDLE : SEL_READY;
 
 479         if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
 
 480                 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
 
 481                 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
 
 483                 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
 
 484                 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
 
 486         sel.nb.y = MIN(sel.ob.y, sel.oe.y);
 
 487         sel.ne.y = MAX(sel.ob.y, sel.oe.y);
 
 489         selsnap(&sel.nb.x, &sel.nb.y, -1);
 
 490         selsnap(&sel.ne.x, &sel.ne.y, +1);
 
 492         /* expand selection over line breaks */
 
 493         if (sel.type == SEL_RECTANGULAR)
 
 495         i = tlinelen(sel.nb.y);
 
 498         if (tlinelen(sel.ne.y) <= sel.ne.x)
 
 499                 sel.ne.x = term.col - 1;
 
 503 selected(int x, int y)
 
 505         if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
 
 506                         sel.alt != IS_SET(MODE_ALTSCREEN))
 
 509         if (sel.type == SEL_RECTANGULAR)
 
 510                 return BETWEEN(y, sel.nb.y, sel.ne.y)
 
 511                     && BETWEEN(x, sel.nb.x, sel.ne.x);
 
 513         return BETWEEN(y, sel.nb.y, sel.ne.y)
 
 514             && (y != sel.nb.y || x >= sel.nb.x)
 
 515             && (y != sel.ne.y || x <= sel.ne.x);
 
 519 selsnap(int *x, int *y, int direction)
 
 521         int newx, newy, xt, yt;
 
 522         int delim, prevdelim;
 
 528                  * Snap around if the word wraps around at the end or
 
 529                  * beginning of a line.
 
 531                 prevgp = &term.line[*y][*x];
 
 532                 prevdelim = ISDELIM(prevgp->u);
 
 534                         newx = *x + direction;
 
 536                         if (!BETWEEN(newx, 0, term.col - 1)) {
 
 538                                 newx = (newx + term.col) % term.col;
 
 539                                 if (!BETWEEN(newy, 0, term.row - 1))
 
 545                                         yt = newy, xt = newx;
 
 546                                 if (!(term.line[yt][xt].mode & ATTR_WRAP))
 
 550                         if (newx >= tlinelen(newy))
 
 553                         gp = &term.line[newy][newx];
 
 554                         delim = ISDELIM(gp->u);
 
 555                         if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
 
 556                                         || (delim && gp->u != prevgp->u)))
 
 567                  * Snap around if the the previous line or the current one
 
 568                  * has set ATTR_WRAP at its end. Then the whole next or
 
 569                  * previous line will be selected.
 
 571                 *x = (direction < 0) ? 0 : term.col - 1;
 
 573                         for (; *y > 0; *y += direction) {
 
 574                                 if (!(term.line[*y-1][term.col-1].mode
 
 579                 } else if (direction > 0) {
 
 580                         for (; *y < term.row-1; *y += direction) {
 
 581                                 if (!(term.line[*y][term.col-1].mode
 
 595         int y, bufsize, lastx, linelen;
 
 601         bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
 
 602         ptr = str = xmalloc(bufsize);
 
 604         /* append every set & selected glyph to the selection */
 
 605         for (y = sel.nb.y; y <= sel.ne.y; y++) {
 
 606                 if ((linelen = tlinelen(y)) == 0) {
 
 611                 if (sel.type == SEL_RECTANGULAR) {
 
 612                         gp = &term.line[y][sel.nb.x];
 
 615                         gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
 
 616                         lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
 
 618                 last = &term.line[y][MIN(lastx, linelen-1)];
 
 619                 while (last >= gp && last->u == ' ')
 
 622                 for ( ; gp <= last; ++gp) {
 
 623                         if (gp->mode & ATTR_WDUMMY)
 
 626                         ptr += utf8encode(gp->u, ptr);
 
 630                  * Copy and pasting of line endings is inconsistent
 
 631                  * in the inconsistent terminal and GUI world.
 
 632                  * The best solution seems like to produce '\n' when
 
 633                  * something is copied from st and convert '\n' to
 
 634                  * '\r', when something to be pasted is received by
 
 636                  * FIXME: Fix the computer world.
 
 638                 if ((y < sel.ne.y || lastx >= linelen) &&
 
 639                     (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
 
 653         tsetdirt(sel.nb.y, sel.ne.y);
 
 657 die(const char *errstr, ...)
 
 661         va_start(ap, errstr);
 
 662         vfprintf(stderr, errstr, ap);
 
 668 execsh(char *cmd, char **args)
 
 670         char *sh, *prog, *arg;
 
 671         const struct passwd *pw;
 
 674         if ((pw = getpwuid(getuid())) == NULL) {
 
 676                         die("getpwuid: %s\n", strerror(errno));
 
 678                         die("who are you?\n");
 
 681         if ((sh = getenv("SHELL")) == NULL)
 
 682                 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
 
 689                 arg = utmp ? utmp : sh;
 
 697         DEFAULT(args, ((char *[]) {prog, arg, NULL}));
 
 702         setenv("LOGNAME", pw->pw_name, 1);
 
 703         setenv("USER", pw->pw_name, 1);
 
 704         setenv("SHELL", sh, 1);
 
 705         setenv("HOME", pw->pw_dir, 1);
 
 706         setenv("TERM", termname, 1);
 
 708         signal(SIGCHLD, SIG_DFL);
 
 709         signal(SIGHUP, SIG_DFL);
 
 710         signal(SIGINT, SIG_DFL);
 
 711         signal(SIGQUIT, SIG_DFL);
 
 712         signal(SIGTERM, SIG_DFL);
 
 713         signal(SIGALRM, SIG_DFL);
 
 725         if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
 
 726                 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
 
 731         if (WIFEXITED(stat) && WEXITSTATUS(stat))
 
 732                 die("child exited with status %d\n", WEXITSTATUS(stat));
 
 733         else if (WIFSIGNALED(stat))
 
 734                 die("child terminated due to signal %d\n", WTERMSIG(stat));
 
 741         char cmd[_POSIX_ARG_MAX], **p, *q, *s;
 
 744         if ((n = strlen(stty_args)) > sizeof(cmd)-1)
 
 745                 die("incorrect stty parameters\n");
 
 746         memcpy(cmd, stty_args, n);
 
 748         siz = sizeof(cmd) - n;
 
 749         for (p = args; p && (s = *p); ++p) {
 
 750                 if ((n = strlen(s)) > siz-1)
 
 751                         die("stty parameter length too long\n");
 
 758         if (system(cmd) != 0)
 
 759                 perror("Couldn't call stty");
 
 763 ttynew(char *line, char *cmd, char *out, char **args)
 
 768                 term.mode |= MODE_PRINT;
 
 769                 iofd = (!strcmp(out, "-")) ?
 
 770                           1 : open(out, O_WRONLY | O_CREAT, 0666);
 
 772                         fprintf(stderr, "Error opening %s:%s\n",
 
 773                                 out, strerror(errno));
 
 778                 if ((cmdfd = open(line, O_RDWR)) < 0)
 
 779                         die("open line '%s' failed: %s\n",
 
 780                             line, strerror(errno));
 
 786         /* seems to work fine on linux, openbsd and freebsd */
 
 787         if (openpty(&m, &s, NULL, NULL, NULL) < 0)
 
 788                 die("openpty failed: %s\n", strerror(errno));
 
 790         switch (pid = fork()) {
 
 792                 die("fork failed: %s\n", strerror(errno));
 
 796                 setsid(); /* create a new process group */
 
 800                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
 
 801                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
 
 805                 if (pledge("stdio getpw proc exec", NULL) == -1)
 
 812                 if (pledge("stdio rpath tty proc", NULL) == -1)
 
 817                 signal(SIGCHLD, sigchld);
 
 826         static char buf[BUFSIZ];
 
 827         static int buflen = 0;
 
 830         /* append read bytes to unprocessed bytes */
 
 831         ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
 
 837                 die("couldn't read from shell: %s\n", strerror(errno));
 
 840                 written = twrite(buf, buflen, 0);
 
 842                 /* keep any incomplete UTF-8 byte sequence for the next call */
 
 844                         memmove(buf, buf + written, buflen);
 
 850 ttywrite(const char *s, size_t n, int may_echo)
 
 854         if (may_echo && IS_SET(MODE_ECHO))
 
 857         if (!IS_SET(MODE_CRLF)) {
 
 862         /* This is similar to how the kernel handles ONLCR for ttys */
 
 866                         ttywriteraw("\r\n", 2);
 
 868                         next = memchr(s, '\r', n);
 
 869                         DEFAULT(next, s + n);
 
 870                         ttywriteraw(s, next - s);
 
 878 ttywriteraw(const char *s, size_t n)
 
 885          * Remember that we are using a pty, which might be a modem line.
 
 886          * Writing too much will clog the line. That's why we are doing this
 
 888          * FIXME: Migrate the world to Plan 9.
 
 896                 /* Check if we can write. */
 
 897                 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
 
 900                         die("select failed: %s\n", strerror(errno));
 
 902                 if (FD_ISSET(cmdfd, &wfd)) {
 
 904                          * Only write the bytes written by ttywrite() or the
 
 905                          * default of 256. This seems to be a reasonable value
 
 906                          * for a serial line. Bigger values might clog the I/O.
 
 908                         if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
 
 912                                  * We weren't able to write out everything.
 
 913                                  * This means the buffer is getting full
 
 921                                 /* All bytes have been written. */
 
 925                 if (FD_ISSET(cmdfd, &rfd))
 
 931         die("write error on tty: %s\n", strerror(errno));
 
 935 ttyresize(int tw, int th)
 
 943         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
 
 944                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
 
 950         /* Send SIGHUP to shell */
 
 959         for (i = 0; i < term.row-1; i++) {
 
 960                 for (j = 0; j < term.col-1; j++) {
 
 961                         if (term.line[i][j].mode & attr)
 
 970 tsetdirt(int top, int bot)
 
 974         LIMIT(top, 0, term.row-1);
 
 975         LIMIT(bot, 0, term.row-1);
 
 977         for (i = top; i <= bot; i++)
 
 982 tsetdirtattr(int attr)
 
 986         for (i = 0; i < term.row-1; i++) {
 
 987                 for (j = 0; j < term.col-1; j++) {
 
 988                         if (term.line[i][j].mode & attr) {
 
 999         tsetdirt(0, term.row-1);
 
1005         static TCursor c[2];
 
1006         int alt = IS_SET(MODE_ALTSCREEN);
 
1008         if (mode == CURSOR_SAVE) {
 
1010         } else if (mode == CURSOR_LOAD) {
 
1012                 tmoveto(c[alt].x, c[alt].y);
 
1021         term.c = (TCursor){{
 
1025         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
 
1027         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
 
1028         for (i = tabspaces; i < term.col; i += tabspaces)
 
1031         term.bot = term.row - 1;
 
1032         term.mode = MODE_WRAP|MODE_UTF8;
 
1033         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
 
1036         for (i = 0; i < 2; i++) {
 
1038                 tcursor(CURSOR_SAVE);
 
1039                 tclearregion(0, 0, term.col-1, term.row-1);
 
1045 tnew(int col, int row)
 
1047         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
 
1055         Line *tmp = term.line;
 
1057         term.line = term.alt;
 
1059         term.mode ^= MODE_ALTSCREEN;
 
1064 tscrolldown(int orig, int n)
 
1069         LIMIT(n, 0, term.bot-orig+1);
 
1071         tsetdirt(orig, term.bot-n);
 
1072         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
 
1074         for (i = term.bot; i >= orig+n; i--) {
 
1075                 temp = term.line[i];
 
1076                 term.line[i] = term.line[i-n];
 
1077                 term.line[i-n] = temp;
 
1084 tscrollup(int orig, int n)
 
1089         LIMIT(n, 0, term.bot-orig+1);
 
1091         tclearregion(0, orig, term.col-1, orig+n-1);
 
1092         tsetdirt(orig+n, term.bot);
 
1094         for (i = orig; i <= term.bot-n; i++) {
 
1095                 temp = term.line[i];
 
1096                 term.line[i] = term.line[i+n];
 
1097                 term.line[i+n] = temp;
 
1100         selscroll(orig, -n);
 
1104 selscroll(int orig, int n)
 
1109         if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
 
1111         } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
 
1114                 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
 
1115                     sel.oe.y < term.top || sel.oe.y > term.bot) {
 
1124 tnewline(int first_col)
 
1128         if (y == term.bot) {
 
1129                 tscrollup(term.top, 1);
 
1133         tmoveto(first_col ? 0 : term.c.x, y);
 
1139         char *p = csiescseq.buf, *np;
 
1148         csiescseq.buf[csiescseq.len] = '\0';
 
1149         while (p < csiescseq.buf+csiescseq.len) {
 
1151                 v = strtol(p, &np, 10);
 
1154                 if (v == LONG_MAX || v == LONG_MIN)
 
1156                 csiescseq.arg[csiescseq.narg++] = v;
 
1158                 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
 
1162         csiescseq.mode[0] = *p++;
 
1163         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
 
1166 /* for absolute user moves, when decom is set */
 
1168 tmoveato(int x, int y)
 
1170         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
 
1174 tmoveto(int x, int y)
 
1178         if (term.c.state & CURSOR_ORIGIN) {
 
1183                 maxy = term.row - 1;
 
1185         term.c.state &= ~CURSOR_WRAPNEXT;
 
1186         term.c.x = LIMIT(x, 0, term.col-1);
 
1187         term.c.y = LIMIT(y, miny, maxy);
 
1191 tsetchar(Rune u, Glyph *attr, int x, int y)
 
1193         static char *vt100_0[62] = { /* 0x41 - 0x7e */
 
1194                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
 
1195                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
 
1196                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
 
1197                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
 
1198                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
 
1199                 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
 
1200                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
 
1201                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
 
1205          * The table is proudly stolen from rxvt.
 
1207         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
 
1208            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
 
1209                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
 
1211         if (term.line[y][x].mode & ATTR_WIDE) {
 
1212                 if (x+1 < term.col) {
 
1213                         term.line[y][x+1].u = ' ';
 
1214                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
 
1216         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
 
1217                 term.line[y][x-1].u = ' ';
 
1218                 term.line[y][x-1].mode &= ~ATTR_WIDE;
 
1222         term.line[y][x] = *attr;
 
1223         term.line[y][x].u = u;
 
1227 tclearregion(int x1, int y1, int x2, int y2)
 
1233                 temp = x1, x1 = x2, x2 = temp;
 
1235                 temp = y1, y1 = y2, y2 = temp;
 
1237         LIMIT(x1, 0, term.col-1);
 
1238         LIMIT(x2, 0, term.col-1);
 
1239         LIMIT(y1, 0, term.row-1);
 
1240         LIMIT(y2, 0, term.row-1);
 
1242         for (y = y1; y <= y2; y++) {
 
1244                 for (x = x1; x <= x2; x++) {
 
1245                         gp = &term.line[y][x];
 
1248                         gp->fg = term.c.attr.fg;
 
1249                         gp->bg = term.c.attr.bg;
 
1262         LIMIT(n, 0, term.col - term.c.x);
 
1266         size = term.col - src;
 
1267         line = term.line[term.c.y];
 
1269         memmove(&line[dst], &line[src], size * sizeof(Glyph));
 
1270         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
 
1279         LIMIT(n, 0, term.col - term.c.x);
 
1283         size = term.col - dst;
 
1284         line = term.line[term.c.y];
 
1286         memmove(&line[dst], &line[src], size * sizeof(Glyph));
 
1287         tclearregion(src, term.c.y, dst - 1, term.c.y);
 
1291 tinsertblankline(int n)
 
1293         if (BETWEEN(term.c.y, term.top, term.bot))
 
1294                 tscrolldown(term.c.y, n);
 
1300         if (BETWEEN(term.c.y, term.top, term.bot))
 
1301                 tscrollup(term.c.y, n);
 
1305 tdefcolor(int *attr, int *npar, int l)
 
1310         switch (attr[*npar + 1]) {
 
1311         case 2: /* direct color in RGB space */
 
1312                 if (*npar + 4 >= l) {
 
1314                                 "erresc(38): Incorrect number of parameters (%d)\n",
 
1318                 r = attr[*npar + 2];
 
1319                 g = attr[*npar + 3];
 
1320                 b = attr[*npar + 4];
 
1322                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
 
1323                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
 
1326                         idx = TRUECOLOR(r, g, b);
 
1328         case 5: /* indexed color */
 
1329                 if (*npar + 2 >= l) {
 
1331                                 "erresc(38): Incorrect number of parameters (%d)\n",
 
1336                 if (!BETWEEN(attr[*npar], 0, 255))
 
1337                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
 
1341         case 0: /* implemented defined (only foreground) */
 
1342         case 1: /* transparent */
 
1343         case 3: /* direct color in CMY space */
 
1344         case 4: /* direct color in CMYK space */
 
1347                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
 
1355 tsetattr(int *attr, int l)
 
1360         for (i = 0; i < l; i++) {
 
1363                         term.c.attr.mode &= ~(
 
1372                         term.c.attr.fg = defaultfg;
 
1373                         term.c.attr.bg = defaultbg;
 
1376                         term.c.attr.mode |= ATTR_BOLD;
 
1379                         term.c.attr.mode |= ATTR_FAINT;
 
1382                         term.c.attr.mode |= ATTR_ITALIC;
 
1385                         term.c.attr.mode |= ATTR_UNDERLINE;
 
1387                 case 5: /* slow blink */
 
1389                 case 6: /* rapid blink */
 
1390                         term.c.attr.mode |= ATTR_BLINK;
 
1393                         term.c.attr.mode |= ATTR_REVERSE;
 
1396                         term.c.attr.mode |= ATTR_INVISIBLE;
 
1399                         term.c.attr.mode |= ATTR_STRUCK;
 
1402                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
 
1405                         term.c.attr.mode &= ~ATTR_ITALIC;
 
1408                         term.c.attr.mode &= ~ATTR_UNDERLINE;
 
1411                         term.c.attr.mode &= ~ATTR_BLINK;
 
1414                         term.c.attr.mode &= ~ATTR_REVERSE;
 
1417                         term.c.attr.mode &= ~ATTR_INVISIBLE;
 
1420                         term.c.attr.mode &= ~ATTR_STRUCK;
 
1423                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
 
1424                                 term.c.attr.fg = idx;
 
1427                         term.c.attr.fg = defaultfg;
 
1430                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
 
1431                                 term.c.attr.bg = idx;
 
1434                         term.c.attr.bg = defaultbg;
 
1437                         if (BETWEEN(attr[i], 30, 37)) {
 
1438                                 term.c.attr.fg = attr[i] - 30;
 
1439                         } else if (BETWEEN(attr[i], 40, 47)) {
 
1440                                 term.c.attr.bg = attr[i] - 40;
 
1441                         } else if (BETWEEN(attr[i], 90, 97)) {
 
1442                                 term.c.attr.fg = attr[i] - 90 + 8;
 
1443                         } else if (BETWEEN(attr[i], 100, 107)) {
 
1444                                 term.c.attr.bg = attr[i] - 100 + 8;
 
1447                                         "erresc(default): gfx attr %d unknown\n",
 
1457 tsetscroll(int t, int b)
 
1461         LIMIT(t, 0, term.row-1);
 
1462         LIMIT(b, 0, term.row-1);
 
1473 tsetmode(int priv, int set, int *args, int narg)
 
1477         for (lim = args + narg; args < lim; ++args) {
 
1480                         case 1: /* DECCKM -- Cursor key */
 
1481                                 xsetmode(set, MODE_APPCURSOR);
 
1483                         case 5: /* DECSCNM -- Reverse video */
 
1484                                 xsetmode(set, MODE_REVERSE);
 
1486                         case 6: /* DECOM -- Origin */
 
1487                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
 
1490                         case 7: /* DECAWM -- Auto wrap */
 
1491                                 MODBIT(term.mode, set, MODE_WRAP);
 
1493                         case 0:  /* Error (IGNORED) */
 
1494                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
 
1495                         case 3:  /* DECCOLM -- Column  (IGNORED) */
 
1496                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
 
1497                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
 
1498                         case 18: /* DECPFF -- Printer feed (IGNORED) */
 
1499                         case 19: /* DECPEX -- Printer extent (IGNORED) */
 
1500                         case 42: /* DECNRCM -- National characters (IGNORED) */
 
1501                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
 
1503                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
 
1504                                 xsetmode(!set, MODE_HIDE);
 
1506                         case 9:    /* X10 mouse compatibility mode */
 
1507                                 xsetpointermotion(0);
 
1508                                 xsetmode(0, MODE_MOUSE);
 
1509                                 xsetmode(set, MODE_MOUSEX10);
 
1511                         case 1000: /* 1000: report button press */
 
1512                                 xsetpointermotion(0);
 
1513                                 xsetmode(0, MODE_MOUSE);
 
1514                                 xsetmode(set, MODE_MOUSEBTN);
 
1516                         case 1002: /* 1002: report motion on button press */
 
1517                                 xsetpointermotion(0);
 
1518                                 xsetmode(0, MODE_MOUSE);
 
1519                                 xsetmode(set, MODE_MOUSEMOTION);
 
1521                         case 1003: /* 1003: enable all mouse motions */
 
1522                                 xsetpointermotion(set);
 
1523                                 xsetmode(0, MODE_MOUSE);
 
1524                                 xsetmode(set, MODE_MOUSEMANY);
 
1526                         case 1004: /* 1004: send focus events to tty */
 
1527                                 xsetmode(set, MODE_FOCUS);
 
1529                         case 1006: /* 1006: extended reporting mode */
 
1530                                 xsetmode(set, MODE_MOUSESGR);
 
1533                                 xsetmode(set, MODE_8BIT);
 
1535                         case 1049: /* swap screen & set/restore cursor as xterm */
 
1536                                 if (!allowaltscreen)
 
1538                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
 
1540                         case 47: /* swap screen */
 
1542                                 if (!allowaltscreen)
 
1544                                 alt = IS_SET(MODE_ALTSCREEN);
 
1546                                         tclearregion(0, 0, term.col-1,
 
1549                                 if (set ^ alt) /* set is always 1 or 0 */
 
1555                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
 
1557                         case 2004: /* 2004: bracketed paste mode */
 
1558                                 xsetmode(set, MODE_BRCKTPASTE);
 
1560                         /* Not implemented mouse modes. See comments there. */
 
1561                         case 1001: /* mouse highlight mode; can hang the
 
1562                                       terminal by design when implemented. */
 
1563                         case 1005: /* UTF-8 mouse mode; will confuse
 
1564                                       applications not supporting UTF-8
 
1566                         case 1015: /* urxvt mangled mouse mode; incompatible
 
1567                                       and can be mistaken for other control
 
1572                                         "erresc: unknown private set/reset mode %d\n",
 
1578                         case 0:  /* Error (IGNORED) */
 
1581                                 xsetmode(set, MODE_KBDLOCK);
 
1583                         case 4:  /* IRM -- Insertion-replacement */
 
1584                                 MODBIT(term.mode, set, MODE_INSERT);
 
1586                         case 12: /* SRM -- Send/Receive */
 
1587                                 MODBIT(term.mode, !set, MODE_ECHO);
 
1589                         case 20: /* LNM -- Linefeed/new line */
 
1590                                 MODBIT(term.mode, set, MODE_CRLF);
 
1594                                         "erresc: unknown set/reset mode %d\n",
 
1608         switch (csiescseq.mode[0]) {
 
1611                 fprintf(stderr, "erresc: unknown csi ");
 
1615         case '@': /* ICH -- Insert <n> blank char */
 
1616                 DEFAULT(csiescseq.arg[0], 1);
 
1617                 tinsertblank(csiescseq.arg[0]);
 
1619         case 'A': /* CUU -- Cursor <n> Up */
 
1620                 DEFAULT(csiescseq.arg[0], 1);
 
1621                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
 
1623         case 'B': /* CUD -- Cursor <n> Down */
 
1624         case 'e': /* VPR --Cursor <n> Down */
 
1625                 DEFAULT(csiescseq.arg[0], 1);
 
1626                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
 
1628         case 'i': /* MC -- Media Copy */
 
1629                 switch (csiescseq.arg[0]) {
 
1634                         tdumpline(term.c.y);
 
1640                         term.mode &= ~MODE_PRINT;
 
1643                         term.mode |= MODE_PRINT;
 
1647         case 'c': /* DA -- Device Attributes */
 
1648                 if (csiescseq.arg[0] == 0)
 
1649                         ttywrite(vtiden, strlen(vtiden), 0);
 
1651         case 'b': /* REP -- if last char is printable print it <n> more times */
 
1652                 DEFAULT(csiescseq.arg[0], 1);
 
1654                         while (csiescseq.arg[0]-- > 0)
 
1657         case 'C': /* CUF -- Cursor <n> Forward */
 
1658         case 'a': /* HPR -- Cursor <n> Forward */
 
1659                 DEFAULT(csiescseq.arg[0], 1);
 
1660                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
 
1662         case 'D': /* CUB -- Cursor <n> Backward */
 
1663                 DEFAULT(csiescseq.arg[0], 1);
 
1664                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
 
1666         case 'E': /* CNL -- Cursor <n> Down and first col */
 
1667                 DEFAULT(csiescseq.arg[0], 1);
 
1668                 tmoveto(0, term.c.y+csiescseq.arg[0]);
 
1670         case 'F': /* CPL -- Cursor <n> Up and first col */
 
1671                 DEFAULT(csiescseq.arg[0], 1);
 
1672                 tmoveto(0, term.c.y-csiescseq.arg[0]);
 
1674         case 'g': /* TBC -- Tabulation clear */
 
1675                 switch (csiescseq.arg[0]) {
 
1676                 case 0: /* clear current tab stop */
 
1677                         term.tabs[term.c.x] = 0;
 
1679                 case 3: /* clear all the tabs */
 
1680                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
 
1686         case 'G': /* CHA -- Move to <col> */
 
1688                 DEFAULT(csiescseq.arg[0], 1);
 
1689                 tmoveto(csiescseq.arg[0]-1, term.c.y);
 
1691         case 'H': /* CUP -- Move to <row> <col> */
 
1693                 DEFAULT(csiescseq.arg[0], 1);
 
1694                 DEFAULT(csiescseq.arg[1], 1);
 
1695                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
 
1697         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
 
1698                 DEFAULT(csiescseq.arg[0], 1);
 
1699                 tputtab(csiescseq.arg[0]);
 
1701         case 'J': /* ED -- Clear screen */
 
1702                 switch (csiescseq.arg[0]) {
 
1704                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
 
1705                         if (term.c.y < term.row-1) {
 
1706                                 tclearregion(0, term.c.y+1, term.col-1,
 
1712                                 tclearregion(0, 0, term.col-1, term.c.y-1);
 
1713                         tclearregion(0, term.c.y, term.c.x, term.c.y);
 
1716                         tclearregion(0, 0, term.col-1, term.row-1);
 
1722         case 'K': /* EL -- Clear line */
 
1723                 switch (csiescseq.arg[0]) {
 
1725                         tclearregion(term.c.x, term.c.y, term.col-1,
 
1729                         tclearregion(0, term.c.y, term.c.x, term.c.y);
 
1732                         tclearregion(0, term.c.y, term.col-1, term.c.y);
 
1736         case 'S': /* SU -- Scroll <n> line up */
 
1737                 DEFAULT(csiescseq.arg[0], 1);
 
1738                 tscrollup(term.top, csiescseq.arg[0]);
 
1740         case 'T': /* SD -- Scroll <n> line down */
 
1741                 DEFAULT(csiescseq.arg[0], 1);
 
1742                 tscrolldown(term.top, csiescseq.arg[0]);
 
1744         case 'L': /* IL -- Insert <n> blank lines */
 
1745                 DEFAULT(csiescseq.arg[0], 1);
 
1746                 tinsertblankline(csiescseq.arg[0]);
 
1748         case 'l': /* RM -- Reset Mode */
 
1749                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
 
1751         case 'M': /* DL -- Delete <n> lines */
 
1752                 DEFAULT(csiescseq.arg[0], 1);
 
1753                 tdeleteline(csiescseq.arg[0]);
 
1755         case 'X': /* ECH -- Erase <n> char */
 
1756                 DEFAULT(csiescseq.arg[0], 1);
 
1757                 tclearregion(term.c.x, term.c.y,
 
1758                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
 
1760         case 'P': /* DCH -- Delete <n> char */
 
1761                 DEFAULT(csiescseq.arg[0], 1);
 
1762                 tdeletechar(csiescseq.arg[0]);
 
1764         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
 
1765                 DEFAULT(csiescseq.arg[0], 1);
 
1766                 tputtab(-csiescseq.arg[0]);
 
1768         case 'd': /* VPA -- Move to <row> */
 
1769                 DEFAULT(csiescseq.arg[0], 1);
 
1770                 tmoveato(term.c.x, csiescseq.arg[0]-1);
 
1772         case 'h': /* SM -- Set terminal mode */
 
1773                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
 
1775         case 'm': /* SGR -- Terminal attribute (color) */
 
1776                 tsetattr(csiescseq.arg, csiescseq.narg);
 
1778         case 'n': /* DSR – Device Status Report (cursor position) */
 
1779                 if (csiescseq.arg[0] == 6) {
 
1780                         len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
 
1781                                         term.c.y+1, term.c.x+1);
 
1782                         ttywrite(buf, len, 0);
 
1785         case 'r': /* DECSTBM -- Set Scrolling Region */
 
1786                 if (csiescseq.priv) {
 
1789                         DEFAULT(csiescseq.arg[0], 1);
 
1790                         DEFAULT(csiescseq.arg[1], term.row);
 
1791                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
 
1795         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
 
1796                 tcursor(CURSOR_SAVE);
 
1798         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
 
1799                 tcursor(CURSOR_LOAD);
 
1802                 switch (csiescseq.mode[1]) {
 
1803                 case 'q': /* DECSCUSR -- Set Cursor Style */
 
1804                         if (xsetcursor(csiescseq.arg[0]))
 
1820         fprintf(stderr, "ESC[");
 
1821         for (i = 0; i < csiescseq.len; i++) {
 
1822                 c = csiescseq.buf[i] & 0xff;
 
1825                 } else if (c == '\n') {
 
1826                         fprintf(stderr, "(\\n)");
 
1827                 } else if (c == '\r') {
 
1828                         fprintf(stderr, "(\\r)");
 
1829                 } else if (c == 0x1b) {
 
1830                         fprintf(stderr, "(\\e)");
 
1832                         fprintf(stderr, "(%02x)", c);
 
1841         memset(&csiescseq, 0, sizeof(csiescseq));
 
1847         char *p = NULL, *dec;
 
1850         term.esc &= ~(ESC_STR_END|ESC_STR);
 
1852         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
 
1854         switch (strescseq.type) {
 
1855         case ']': /* OSC -- Operating System Command */
 
1861                                 xsettitle(strescseq.args[1]);
 
1864                         if (narg > 2 && allowwindowops) {
 
1865                                 dec = base64dec(strescseq.args[2]);
 
1870                                         fprintf(stderr, "erresc: invalid base64\n");
 
1874                 case 4: /* color set */
 
1877                         p = strescseq.args[2];
 
1879                 case 104: /* color reset, here p = NULL */
 
1880                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
 
1881                         if (xsetcolorname(j, p)) {
 
1882                                 if (par == 104 && narg <= 1)
 
1883                                         return; /* color reset without parameter */
 
1884                                 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
 
1885                                         j, p ? p : "(null)");
 
1888                                  * TODO if defaultbg color is changed, borders
 
1896         case 'k': /* old title set compatibility */
 
1897                 xsettitle(strescseq.args[0]);
 
1899         case 'P': /* DCS -- Device Control String */
 
1900                 term.mode |= ESC_DCS;
 
1901         case '_': /* APC -- Application Program Command */
 
1902         case '^': /* PM -- Privacy Message */
 
1906         fprintf(stderr, "erresc: unknown str ");
 
1914         char *p = strescseq.buf;
 
1917         strescseq.buf[strescseq.len] = '\0';
 
1922         while (strescseq.narg < STR_ARG_SIZ) {
 
1923                 strescseq.args[strescseq.narg++] = p;
 
1924                 while ((c = *p) != ';' && c != '\0')
 
1938         fprintf(stderr, "ESC%c", strescseq.type);
 
1939         for (i = 0; i < strescseq.len; i++) {
 
1940                 c = strescseq.buf[i] & 0xff;
 
1944                 } else if (isprint(c)) {
 
1946                 } else if (c == '\n') {
 
1947                         fprintf(stderr, "(\\n)");
 
1948                 } else if (c == '\r') {
 
1949                         fprintf(stderr, "(\\r)");
 
1950                 } else if (c == 0x1b) {
 
1951                         fprintf(stderr, "(\\e)");
 
1953                         fprintf(stderr, "(%02x)", c);
 
1956         fprintf(stderr, "ESC\\\n");
 
1962         strescseq = (STREscape){
 
1963                 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
 
1969 sendbreak(const Arg *arg)
 
1971         if (tcsendbreak(cmdfd, 0))
 
1972                 perror("Error sending break");
 
1976 tprinter(char *s, size_t len)
 
1978         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
 
1979                 perror("Error writing to output file");
 
1986 toggleprinter(const Arg *arg)
 
1988         term.mode ^= MODE_PRINT;
 
1992 printscreen(const Arg *arg)
 
1998 printsel(const Arg *arg)
 
2008         if ((ptr = getsel())) {
 
2009                 tprinter(ptr, strlen(ptr));
 
2020         bp = &term.line[n][0];
 
2021         end = &bp[MIN(tlinelen(n), term.col) - 1];
 
2022         if (bp != end || bp->u != ' ') {
 
2023                 for ( ; bp <= end; ++bp)
 
2024                         tprinter(buf, utf8encode(bp->u, buf));
 
2034         for (i = 0; i < term.row; ++i)
 
2044                 while (x < term.col && n--)
 
2045                         for (++x; x < term.col && !term.tabs[x]; ++x)
 
2048                 while (x > 0 && n++)
 
2049                         for (--x; x > 0 && !term.tabs[x]; --x)
 
2052         term.c.x = LIMIT(x, 0, term.col-1);
 
2056 tdefutf8(char ascii)
 
2059                 term.mode |= MODE_UTF8;
 
2060         else if (ascii == '@')
 
2061                 term.mode &= ~MODE_UTF8;
 
2065 tdeftran(char ascii)
 
2067         static char cs[] = "0B";
 
2068         static int vcs[] = {CS_GRAPHIC0, CS_USA};
 
2071         if ((p = strchr(cs, ascii)) == NULL) {
 
2072                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
 
2074                 term.trantbl[term.icharset] = vcs[p - cs];
 
2083         if (c == '8') { /* DEC screen alignment test. */
 
2084                 for (x = 0; x < term.col; ++x) {
 
2085                         for (y = 0; y < term.row; ++y)
 
2086                                 tsetchar('E', &term.c.attr, x, y);
 
2092 tstrsequence(uchar c)
 
2097         case 0x90:   /* DCS -- Device Control String */
 
2099                 term.esc |= ESC_DCS;
 
2101         case 0x9f:   /* APC -- Application Program Command */
 
2104         case 0x9e:   /* PM -- Privacy Message */
 
2107         case 0x9d:   /* OSC -- Operating System Command */
 
2112         term.esc |= ESC_STR;
 
2116 tcontrolcode(uchar ascii)
 
2123                 tmoveto(term.c.x-1, term.c.y);
 
2126                 tmoveto(0, term.c.y);
 
2131                 /* go to first col if the mode is set */
 
2132                 tnewline(IS_SET(MODE_CRLF));
 
2134         case '\a':   /* BEL */
 
2135                 if (term.esc & ESC_STR_END) {
 
2136                         /* backwards compatibility to xterm */
 
2142         case '\033': /* ESC */
 
2144                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
 
2145                 term.esc |= ESC_START;
 
2147         case '\016': /* SO (LS1 -- Locking shift 1) */
 
2148         case '\017': /* SI (LS0 -- Locking shift 0) */
 
2149                 term.charset = 1 - (ascii - '\016');
 
2151         case '\032': /* SUB */
 
2152                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
 
2154         case '\030': /* CAN */
 
2157         case '\005': /* ENQ (IGNORED) */
 
2158         case '\000': /* NUL (IGNORED) */
 
2159         case '\021': /* XON (IGNORED) */
 
2160         case '\023': /* XOFF (IGNORED) */
 
2161         case 0177:   /* DEL (IGNORED) */
 
2163         case 0x80:   /* TODO: PAD */
 
2164         case 0x81:   /* TODO: HOP */
 
2165         case 0x82:   /* TODO: BPH */
 
2166         case 0x83:   /* TODO: NBH */
 
2167         case 0x84:   /* TODO: IND */
 
2169         case 0x85:   /* NEL -- Next line */
 
2170                 tnewline(1); /* always go to first col */
 
2172         case 0x86:   /* TODO: SSA */
 
2173         case 0x87:   /* TODO: ESA */
 
2175         case 0x88:   /* HTS -- Horizontal tab stop */
 
2176                 term.tabs[term.c.x] = 1;
 
2178         case 0x89:   /* TODO: HTJ */
 
2179         case 0x8a:   /* TODO: VTS */
 
2180         case 0x8b:   /* TODO: PLD */
 
2181         case 0x8c:   /* TODO: PLU */
 
2182         case 0x8d:   /* TODO: RI */
 
2183         case 0x8e:   /* TODO: SS2 */
 
2184         case 0x8f:   /* TODO: SS3 */
 
2185         case 0x91:   /* TODO: PU1 */
 
2186         case 0x92:   /* TODO: PU2 */
 
2187         case 0x93:   /* TODO: STS */
 
2188         case 0x94:   /* TODO: CCH */
 
2189         case 0x95:   /* TODO: MW */
 
2190         case 0x96:   /* TODO: SPA */
 
2191         case 0x97:   /* TODO: EPA */
 
2192         case 0x98:   /* TODO: SOS */
 
2193         case 0x99:   /* TODO: SGCI */
 
2195         case 0x9a:   /* DECID -- Identify Terminal */
 
2196                 ttywrite(vtiden, strlen(vtiden), 0);
 
2198         case 0x9b:   /* TODO: CSI */
 
2199         case 0x9c:   /* TODO: ST */
 
2201         case 0x90:   /* DCS -- Device Control String */
 
2202         case 0x9d:   /* OSC -- Operating System Command */
 
2203         case 0x9e:   /* PM -- Privacy Message */
 
2204         case 0x9f:   /* APC -- Application Program Command */
 
2205                 tstrsequence(ascii);
 
2208         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
 
2209         term.esc &= ~(ESC_STR_END|ESC_STR);
 
2213  * returns 1 when the sequence is finished and it hasn't to read
 
2214  * more characters for this sequence, otherwise 0
 
2217 eschandle(uchar ascii)
 
2221                 term.esc |= ESC_CSI;
 
2224                 term.esc |= ESC_TEST;
 
2227                 term.esc |= ESC_UTF8;
 
2229         case 'P': /* DCS -- Device Control String */
 
2230         case '_': /* APC -- Application Program Command */
 
2231         case '^': /* PM -- Privacy Message */
 
2232         case ']': /* OSC -- Operating System Command */
 
2233         case 'k': /* old title set compatibility */
 
2234                 tstrsequence(ascii);
 
2236         case 'n': /* LS2 -- Locking shift 2 */
 
2237         case 'o': /* LS3 -- Locking shift 3 */
 
2238                 term.charset = 2 + (ascii - 'n');
 
2240         case '(': /* GZD4 -- set primary charset G0 */
 
2241         case ')': /* G1D4 -- set secondary charset G1 */
 
2242         case '*': /* G2D4 -- set tertiary charset G2 */
 
2243         case '+': /* G3D4 -- set quaternary charset G3 */
 
2244                 term.icharset = ascii - '(';
 
2245                 term.esc |= ESC_ALTCHARSET;
 
2247         case 'D': /* IND -- Linefeed */
 
2248                 if (term.c.y == term.bot) {
 
2249                         tscrollup(term.top, 1);
 
2251                         tmoveto(term.c.x, term.c.y+1);
 
2254         case 'E': /* NEL -- Next line */
 
2255                 tnewline(1); /* always go to first col */
 
2257         case 'H': /* HTS -- Horizontal tab stop */
 
2258                 term.tabs[term.c.x] = 1;
 
2260         case 'M': /* RI -- Reverse index */
 
2261                 if (term.c.y == term.top) {
 
2262                         tscrolldown(term.top, 1);
 
2264                         tmoveto(term.c.x, term.c.y-1);
 
2267         case 'Z': /* DECID -- Identify Terminal */
 
2268                 ttywrite(vtiden, strlen(vtiden), 0);
 
2270         case 'c': /* RIS -- Reset to initial state */
 
2275         case '=': /* DECPAM -- Application keypad */
 
2276                 xsetmode(1, MODE_APPKEYPAD);
 
2278         case '>': /* DECPNM -- Normal keypad */
 
2279                 xsetmode(0, MODE_APPKEYPAD);
 
2281         case '7': /* DECSC -- Save Cursor */
 
2282                 tcursor(CURSOR_SAVE);
 
2284         case '8': /* DECRC -- Restore Cursor */
 
2285                 tcursor(CURSOR_LOAD);
 
2287         case '\\': /* ST -- String Terminator */
 
2288                 if (term.esc & ESC_STR_END)
 
2292                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
 
2293                         (uchar) ascii, isprint(ascii)? ascii:'.');
 
2307         control = ISCONTROL(u);
 
2308         if (u < 127 || !IS_SET(MODE_UTF8 | MODE_SIXEL)) {
 
2312                 len = utf8encode(u, c);
 
2313                 if (!control && (width = wcwidth(u)) == -1)
 
2317         if (IS_SET(MODE_PRINT))
 
2321          * STR sequence must be checked before anything else
 
2322          * because it uses all following characters until it
 
2323          * receives a ESC, a SUB, a ST or any other C1 control
 
2326         if (term.esc & ESC_STR) {
 
2327                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
 
2329                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
 
2330                         if (IS_SET(MODE_SIXEL)) {
 
2331                                 /* TODO: render sixel */;
 
2332                                 term.mode &= ~MODE_SIXEL;
 
2335                         term.esc |= ESC_STR_END;
 
2336                         goto check_control_code;
 
2339                 if (IS_SET(MODE_SIXEL)) {
 
2340                         /* TODO: implement sixel mode */
 
2343                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
 
2344                         term.mode |= MODE_SIXEL;
 
2346                 if (strescseq.len+len >= strescseq.siz) {
 
2348                          * Here is a bug in terminals. If the user never sends
 
2349                          * some code to stop the str or esc command, then st
 
2350                          * will stop responding. But this is better than
 
2351                          * silently failing with unknown characters. At least
 
2352                          * then users will report back.
 
2354                          * In the case users ever get fixed, here is the code:
 
2360                         if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
 
2363                         strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
 
2366                 memmove(&strescseq.buf[strescseq.len], c, len);
 
2367                 strescseq.len += len;
 
2373          * Actions of control codes must be performed as soon they arrive
 
2374          * because they can be embedded inside a control sequence, and
 
2375          * they must not cause conflicts with sequences.
 
2380                  * control codes are not shown ever
 
2385         } else if (term.esc & ESC_START) {
 
2386                 if (term.esc & ESC_CSI) {
 
2387                         csiescseq.buf[csiescseq.len++] = u;
 
2388                         if (BETWEEN(u, 0x40, 0x7E)
 
2389                                         || csiescseq.len >= \
 
2390                                         sizeof(csiescseq.buf)-1) {
 
2396                 } else if (term.esc & ESC_UTF8) {
 
2398                 } else if (term.esc & ESC_ALTCHARSET) {
 
2400                 } else if (term.esc & ESC_TEST) {
 
2405                         /* sequence already finished */
 
2409                  * All characters which form part of a sequence are not
 
2414         if (selected(term.c.x, term.c.y))
 
2417         gp = &term.line[term.c.y][term.c.x];
 
2418         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
 
2419                 gp->mode |= ATTR_WRAP;
 
2421                 gp = &term.line[term.c.y][term.c.x];
 
2424         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
 
2425                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
 
2427         if (term.c.x+width > term.col) {
 
2429                 gp = &term.line[term.c.y][term.c.x];
 
2432         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
 
2436                 gp->mode |= ATTR_WIDE;
 
2437                 if (term.c.x+1 < term.col) {
 
2439                         gp[1].mode = ATTR_WDUMMY;
 
2442         if (term.c.x+width < term.col) {
 
2443                 tmoveto(term.c.x+width, term.c.y);
 
2445                 term.c.state |= CURSOR_WRAPNEXT;
 
2450 twrite(const char *buf, int buflen, int show_ctrl)
 
2456         for (n = 0; n < buflen; n += charsize) {
 
2457                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
 
2458                         /* process a complete utf8 char */
 
2459                         charsize = utf8decode(buf + n, &u, buflen - n);
 
2466                 if (show_ctrl && ISCONTROL(u)) {
 
2471                         } else if (u != '\n' && u != '\r' && u != '\t') {
 
2482 tresize(int col, int row)
 
2485         int minrow = MIN(row, term.row);
 
2486         int mincol = MIN(col, term.col);
 
2490         if (col < 1 || row < 1) {
 
2492                         "tresize: error resizing to %dx%d\n", col, row);
 
2497          * slide screen to keep cursor where we expect it -
 
2498          * tscrollup would work here, but we can optimize to
 
2499          * memmove because we're freeing the earlier lines
 
2501         for (i = 0; i <= term.c.y - row; i++) {
 
2505         /* ensure that both src and dst are not NULL */
 
2507                 memmove(term.line, term.line + i, row * sizeof(Line));
 
2508                 memmove(term.alt, term.alt + i, row * sizeof(Line));
 
2510         for (i += row; i < term.row; i++) {
 
2515         /* resize to new height */
 
2516         term.line = xrealloc(term.line, row * sizeof(Line));
 
2517         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
 
2518         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
 
2519         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
 
2521         /* resize each row to new width, zero-pad if needed */
 
2522         for (i = 0; i < minrow; i++) {
 
2523                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
 
2524                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
 
2527         /* allocate any new rows */
 
2528         for (/* i = minrow */; i < row; i++) {
 
2529                 term.line[i] = xmalloc(col * sizeof(Glyph));
 
2530                 term.alt[i] = xmalloc(col * sizeof(Glyph));
 
2532         if (col > term.col) {
 
2533                 bp = term.tabs + term.col;
 
2535                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
 
2536                 while (--bp > term.tabs && !*bp)
 
2538                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
 
2541         /* update terminal size */
 
2544         /* reset scrolling region */
 
2545         tsetscroll(0, row-1);
 
2546         /* make use of the LIMIT in tmoveto */
 
2547         tmoveto(term.c.x, term.c.y);
 
2548         /* Clearing both screens (it makes dirty all lines) */
 
2550         for (i = 0; i < 2; i++) {
 
2551                 if (mincol < col && 0 < minrow) {
 
2552                         tclearregion(mincol, 0, col - 1, minrow - 1);
 
2554                 if (0 < col && minrow < row) {
 
2555                         tclearregion(0, minrow, col - 1, row - 1);
 
2558                 tcursor(CURSOR_LOAD);
 
2570 drawregion(int x1, int y1, int x2, int y2)
 
2574         for (y = y1; y < y2; y++) {
 
2579                 xdrawline(term.line[y], x1, y, x2);
 
2586         int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
 
2591         /* adjust cursor position */
 
2592         LIMIT(term.ocx, 0, term.col-1);
 
2593         LIMIT(term.ocy, 0, term.row-1);
 
2594         if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
 
2596         if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
 
2599         drawregion(0, 0, term.col, term.row);
 
2600         xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
 
2601                         term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
 
2603         term.ocy = term.c.y;
 
2605         if (ocx != term.ocx || ocy != term.ocy)
 
2606                 xximspot(term.ocx, term.ocy);