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         case '_': /* APC -- Application Program Command */
 
1901         case '^': /* PM -- Privacy Message */
 
1905         fprintf(stderr, "erresc: unknown str ");
 
1913         char *p = strescseq.buf;
 
1916         strescseq.buf[strescseq.len] = '\0';
 
1921         while (strescseq.narg < STR_ARG_SIZ) {
 
1922                 strescseq.args[strescseq.narg++] = p;
 
1923                 while ((c = *p) != ';' && c != '\0')
 
1937         fprintf(stderr, "ESC%c", strescseq.type);
 
1938         for (i = 0; i < strescseq.len; i++) {
 
1939                 c = strescseq.buf[i] & 0xff;
 
1943                 } else if (isprint(c)) {
 
1945                 } else if (c == '\n') {
 
1946                         fprintf(stderr, "(\\n)");
 
1947                 } else if (c == '\r') {
 
1948                         fprintf(stderr, "(\\r)");
 
1949                 } else if (c == 0x1b) {
 
1950                         fprintf(stderr, "(\\e)");
 
1952                         fprintf(stderr, "(%02x)", c);
 
1955         fprintf(stderr, "ESC\\\n");
 
1961         strescseq = (STREscape){
 
1962                 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
 
1968 sendbreak(const Arg *arg)
 
1970         if (tcsendbreak(cmdfd, 0))
 
1971                 perror("Error sending break");
 
1975 tprinter(char *s, size_t len)
 
1977         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
 
1978                 perror("Error writing to output file");
 
1985 toggleprinter(const Arg *arg)
 
1987         term.mode ^= MODE_PRINT;
 
1991 printscreen(const Arg *arg)
 
1997 printsel(const Arg *arg)
 
2007         if ((ptr = getsel())) {
 
2008                 tprinter(ptr, strlen(ptr));
 
2019         bp = &term.line[n][0];
 
2020         end = &bp[MIN(tlinelen(n), term.col) - 1];
 
2021         if (bp != end || bp->u != ' ') {
 
2022                 for ( ; bp <= end; ++bp)
 
2023                         tprinter(buf, utf8encode(bp->u, buf));
 
2033         for (i = 0; i < term.row; ++i)
 
2043                 while (x < term.col && n--)
 
2044                         for (++x; x < term.col && !term.tabs[x]; ++x)
 
2047                 while (x > 0 && n++)
 
2048                         for (--x; x > 0 && !term.tabs[x]; --x)
 
2051         term.c.x = LIMIT(x, 0, term.col-1);
 
2055 tdefutf8(char ascii)
 
2058                 term.mode |= MODE_UTF8;
 
2059         else if (ascii == '@')
 
2060                 term.mode &= ~MODE_UTF8;
 
2064 tdeftran(char ascii)
 
2066         static char cs[] = "0B";
 
2067         static int vcs[] = {CS_GRAPHIC0, CS_USA};
 
2070         if ((p = strchr(cs, ascii)) == NULL) {
 
2071                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
 
2073                 term.trantbl[term.icharset] = vcs[p - cs];
 
2082         if (c == '8') { /* DEC screen alignment test. */
 
2083                 for (x = 0; x < term.col; ++x) {
 
2084                         for (y = 0; y < term.row; ++y)
 
2085                                 tsetchar('E', &term.c.attr, x, y);
 
2091 tstrsequence(uchar c)
 
2096         case 0x90:   /* DCS -- Device Control String */
 
2098                 term.esc |= ESC_DCS;
 
2100         case 0x9f:   /* APC -- Application Program Command */
 
2103         case 0x9e:   /* PM -- Privacy Message */
 
2106         case 0x9d:   /* OSC -- Operating System Command */
 
2111         term.esc |= ESC_STR;
 
2115 tcontrolcode(uchar ascii)
 
2122                 tmoveto(term.c.x-1, term.c.y);
 
2125                 tmoveto(0, term.c.y);
 
2130                 /* go to first col if the mode is set */
 
2131                 tnewline(IS_SET(MODE_CRLF));
 
2133         case '\a':   /* BEL */
 
2134                 if (term.esc & ESC_STR_END) {
 
2135                         /* backwards compatibility to xterm */
 
2141         case '\033': /* ESC */
 
2143                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
 
2144                 term.esc |= ESC_START;
 
2146         case '\016': /* SO (LS1 -- Locking shift 1) */
 
2147         case '\017': /* SI (LS0 -- Locking shift 0) */
 
2148                 term.charset = 1 - (ascii - '\016');
 
2150         case '\032': /* SUB */
 
2151                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
 
2153         case '\030': /* CAN */
 
2156         case '\005': /* ENQ (IGNORED) */
 
2157         case '\000': /* NUL (IGNORED) */
 
2158         case '\021': /* XON (IGNORED) */
 
2159         case '\023': /* XOFF (IGNORED) */
 
2160         case 0177:   /* DEL (IGNORED) */
 
2162         case 0x80:   /* TODO: PAD */
 
2163         case 0x81:   /* TODO: HOP */
 
2164         case 0x82:   /* TODO: BPH */
 
2165         case 0x83:   /* TODO: NBH */
 
2166         case 0x84:   /* TODO: IND */
 
2168         case 0x85:   /* NEL -- Next line */
 
2169                 tnewline(1); /* always go to first col */
 
2171         case 0x86:   /* TODO: SSA */
 
2172         case 0x87:   /* TODO: ESA */
 
2174         case 0x88:   /* HTS -- Horizontal tab stop */
 
2175                 term.tabs[term.c.x] = 1;
 
2177         case 0x89:   /* TODO: HTJ */
 
2178         case 0x8a:   /* TODO: VTS */
 
2179         case 0x8b:   /* TODO: PLD */
 
2180         case 0x8c:   /* TODO: PLU */
 
2181         case 0x8d:   /* TODO: RI */
 
2182         case 0x8e:   /* TODO: SS2 */
 
2183         case 0x8f:   /* TODO: SS3 */
 
2184         case 0x91:   /* TODO: PU1 */
 
2185         case 0x92:   /* TODO: PU2 */
 
2186         case 0x93:   /* TODO: STS */
 
2187         case 0x94:   /* TODO: CCH */
 
2188         case 0x95:   /* TODO: MW */
 
2189         case 0x96:   /* TODO: SPA */
 
2190         case 0x97:   /* TODO: EPA */
 
2191         case 0x98:   /* TODO: SOS */
 
2192         case 0x99:   /* TODO: SGCI */
 
2194         case 0x9a:   /* DECID -- Identify Terminal */
 
2195                 ttywrite(vtiden, strlen(vtiden), 0);
 
2197         case 0x9b:   /* TODO: CSI */
 
2198         case 0x9c:   /* TODO: ST */
 
2200         case 0x90:   /* DCS -- Device Control String */
 
2201         case 0x9d:   /* OSC -- Operating System Command */
 
2202         case 0x9e:   /* PM -- Privacy Message */
 
2203         case 0x9f:   /* APC -- Application Program Command */
 
2204                 tstrsequence(ascii);
 
2207         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
 
2208         term.esc &= ~(ESC_STR_END|ESC_STR);
 
2212  * returns 1 when the sequence is finished and it hasn't to read
 
2213  * more characters for this sequence, otherwise 0
 
2216 eschandle(uchar ascii)
 
2220                 term.esc |= ESC_CSI;
 
2223                 term.esc |= ESC_TEST;
 
2226                 term.esc |= ESC_UTF8;
 
2228         case 'P': /* DCS -- Device Control String */
 
2229         case '_': /* APC -- Application Program Command */
 
2230         case '^': /* PM -- Privacy Message */
 
2231         case ']': /* OSC -- Operating System Command */
 
2232         case 'k': /* old title set compatibility */
 
2233                 tstrsequence(ascii);
 
2235         case 'n': /* LS2 -- Locking shift 2 */
 
2236         case 'o': /* LS3 -- Locking shift 3 */
 
2237                 term.charset = 2 + (ascii - 'n');
 
2239         case '(': /* GZD4 -- set primary charset G0 */
 
2240         case ')': /* G1D4 -- set secondary charset G1 */
 
2241         case '*': /* G2D4 -- set tertiary charset G2 */
 
2242         case '+': /* G3D4 -- set quaternary charset G3 */
 
2243                 term.icharset = ascii - '(';
 
2244                 term.esc |= ESC_ALTCHARSET;
 
2246         case 'D': /* IND -- Linefeed */
 
2247                 if (term.c.y == term.bot) {
 
2248                         tscrollup(term.top, 1);
 
2250                         tmoveto(term.c.x, term.c.y+1);
 
2253         case 'E': /* NEL -- Next line */
 
2254                 tnewline(1); /* always go to first col */
 
2256         case 'H': /* HTS -- Horizontal tab stop */
 
2257                 term.tabs[term.c.x] = 1;
 
2259         case 'M': /* RI -- Reverse index */
 
2260                 if (term.c.y == term.top) {
 
2261                         tscrolldown(term.top, 1);
 
2263                         tmoveto(term.c.x, term.c.y-1);
 
2266         case 'Z': /* DECID -- Identify Terminal */
 
2267                 ttywrite(vtiden, strlen(vtiden), 0);
 
2269         case 'c': /* RIS -- Reset to initial state */
 
2274         case '=': /* DECPAM -- Application keypad */
 
2275                 xsetmode(1, MODE_APPKEYPAD);
 
2277         case '>': /* DECPNM -- Normal keypad */
 
2278                 xsetmode(0, MODE_APPKEYPAD);
 
2280         case '7': /* DECSC -- Save Cursor */
 
2281                 tcursor(CURSOR_SAVE);
 
2283         case '8': /* DECRC -- Restore Cursor */
 
2284                 tcursor(CURSOR_LOAD);
 
2286         case '\\': /* ST -- String Terminator */
 
2287                 if (term.esc & ESC_STR_END)
 
2291                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
 
2292                         (uchar) ascii, isprint(ascii)? ascii:'.');
 
2306         control = ISCONTROL(u);
 
2307         if (u < 127 || !IS_SET(MODE_UTF8 | MODE_SIXEL)) {
 
2311                 len = utf8encode(u, c);
 
2312                 if (!control && (width = wcwidth(u)) == -1)
 
2316         if (IS_SET(MODE_PRINT))
 
2320          * STR sequence must be checked before anything else
 
2321          * because it uses all following characters until it
 
2322          * receives a ESC, a SUB, a ST or any other C1 control
 
2325         if (term.esc & ESC_STR) {
 
2326                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
 
2328                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
 
2329                         if (IS_SET(MODE_SIXEL)) {
 
2330                                 /* TODO: render sixel */;
 
2331                                 term.mode &= ~MODE_SIXEL;
 
2334                         term.esc |= ESC_STR_END;
 
2335                         goto check_control_code;
 
2338                 if (IS_SET(MODE_SIXEL)) {
 
2339                         /* TODO: implement sixel mode */
 
2342                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
 
2343                         term.mode |= MODE_SIXEL;
 
2345                 if (strescseq.len+len >= strescseq.siz) {
 
2347                          * Here is a bug in terminals. If the user never sends
 
2348                          * some code to stop the str or esc command, then st
 
2349                          * will stop responding. But this is better than
 
2350                          * silently failing with unknown characters. At least
 
2351                          * then users will report back.
 
2353                          * In the case users ever get fixed, here is the code:
 
2359                         if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
 
2362                         strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
 
2365                 memmove(&strescseq.buf[strescseq.len], c, len);
 
2366                 strescseq.len += len;
 
2372          * Actions of control codes must be performed as soon they arrive
 
2373          * because they can be embedded inside a control sequence, and
 
2374          * they must not cause conflicts with sequences.
 
2379                  * control codes are not shown ever
 
2384         } else if (term.esc & ESC_START) {
 
2385                 if (term.esc & ESC_CSI) {
 
2386                         csiescseq.buf[csiescseq.len++] = u;
 
2387                         if (BETWEEN(u, 0x40, 0x7E)
 
2388                                         || csiescseq.len >= \
 
2389                                         sizeof(csiescseq.buf)-1) {
 
2395                 } else if (term.esc & ESC_UTF8) {
 
2397                 } else if (term.esc & ESC_ALTCHARSET) {
 
2399                 } else if (term.esc & ESC_TEST) {
 
2404                         /* sequence already finished */
 
2408                  * All characters which form part of a sequence are not
 
2413         if (selected(term.c.x, term.c.y))
 
2416         gp = &term.line[term.c.y][term.c.x];
 
2417         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
 
2418                 gp->mode |= ATTR_WRAP;
 
2420                 gp = &term.line[term.c.y][term.c.x];
 
2423         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
 
2424                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
 
2426         if (term.c.x+width > term.col) {
 
2428                 gp = &term.line[term.c.y][term.c.x];
 
2431         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
 
2435                 gp->mode |= ATTR_WIDE;
 
2436                 if (term.c.x+1 < term.col) {
 
2438                         gp[1].mode = ATTR_WDUMMY;
 
2441         if (term.c.x+width < term.col) {
 
2442                 tmoveto(term.c.x+width, term.c.y);
 
2444                 term.c.state |= CURSOR_WRAPNEXT;
 
2449 twrite(const char *buf, int buflen, int show_ctrl)
 
2455         for (n = 0; n < buflen; n += charsize) {
 
2456                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
 
2457                         /* process a complete utf8 char */
 
2458                         charsize = utf8decode(buf + n, &u, buflen - n);
 
2465                 if (show_ctrl && ISCONTROL(u)) {
 
2470                         } else if (u != '\n' && u != '\r' && u != '\t') {
 
2481 tresize(int col, int row)
 
2484         int minrow = MIN(row, term.row);
 
2485         int mincol = MIN(col, term.col);
 
2489         if (col < 1 || row < 1) {
 
2491                         "tresize: error resizing to %dx%d\n", col, row);
 
2496          * slide screen to keep cursor where we expect it -
 
2497          * tscrollup would work here, but we can optimize to
 
2498          * memmove because we're freeing the earlier lines
 
2500         for (i = 0; i <= term.c.y - row; i++) {
 
2504         /* ensure that both src and dst are not NULL */
 
2506                 memmove(term.line, term.line + i, row * sizeof(Line));
 
2507                 memmove(term.alt, term.alt + i, row * sizeof(Line));
 
2509         for (i += row; i < term.row; i++) {
 
2514         /* resize to new height */
 
2515         term.line = xrealloc(term.line, row * sizeof(Line));
 
2516         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
 
2517         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
 
2518         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
 
2520         /* resize each row to new width, zero-pad if needed */
 
2521         for (i = 0; i < minrow; i++) {
 
2522                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
 
2523                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
 
2526         /* allocate any new rows */
 
2527         for (/* i = minrow */; i < row; i++) {
 
2528                 term.line[i] = xmalloc(col * sizeof(Glyph));
 
2529                 term.alt[i] = xmalloc(col * sizeof(Glyph));
 
2531         if (col > term.col) {
 
2532                 bp = term.tabs + term.col;
 
2534                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
 
2535                 while (--bp > term.tabs && !*bp)
 
2537                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
 
2540         /* update terminal size */
 
2543         /* reset scrolling region */
 
2544         tsetscroll(0, row-1);
 
2545         /* make use of the LIMIT in tmoveto */
 
2546         tmoveto(term.c.x, term.c.y);
 
2547         /* Clearing both screens (it makes dirty all lines) */
 
2549         for (i = 0; i < 2; i++) {
 
2550                 if (mincol < col && 0 < minrow) {
 
2551                         tclearregion(mincol, 0, col - 1, minrow - 1);
 
2553                 if (0 < col && minrow < row) {
 
2554                         tclearregion(0, minrow, col - 1, row - 1);
 
2557                 tcursor(CURSOR_LOAD);
 
2569 drawregion(int x1, int y1, int x2, int y2)
 
2573         for (y = y1; y < y2; y++) {
 
2578                 xdrawline(term.line[y], x1, y, x2);
 
2585         int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
 
2590         /* adjust cursor position */
 
2591         LIMIT(term.ocx, 0, term.col-1);
 
2592         LIMIT(term.ocy, 0, term.row-1);
 
2593         if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
 
2595         if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
 
2598         drawregion(0, 0, term.col, term.row);
 
2599         xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
 
2600                         term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
 
2602         term.ocy = term.c.y;
 
2604         if (ocx != term.ocx || ocy != term.ocy)
 
2605                 xximspot(term.ocx, term.ocy);