tst.c - st - [fork] customized build of st, the simple terminal
 (HTM) git clone git://src.adamsgaard.dk/st
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tst.c (57274B)
       ---
            1 /* See LICENSE for license details. */
            2 #include <ctype.h>
            3 #include <errno.h>
            4 #include <fcntl.h>
            5 #include <limits.h>
            6 #include <pwd.h>
            7 #include <stdarg.h>
            8 #include <stdio.h>
            9 #include <stdlib.h>
           10 #include <string.h>
           11 #include <signal.h>
           12 #include <sys/ioctl.h>
           13 #include <sys/select.h>
           14 #include <sys/types.h>
           15 #include <sys/wait.h>
           16 #include <termios.h>
           17 #include <unistd.h>
           18 #include <wchar.h>
           19 
           20 #include "st.h"
           21 #include "win.h"
           22 
           23 #if   defined(__linux)
           24  #include <pty.h>
           25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
           26  #include <util.h>
           27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
           28  #include <libutil.h>
           29 #endif
           30 
           31 /* Arbitrary sizes */
           32 #define UTF_INVALID   0xFFFD
           33 #define UTF_SIZ       4
           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
           38 
           39 /* macros */
           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))
           45 
           46 enum term_mode {
           47         MODE_WRAP        = 1 << 0,
           48         MODE_INSERT      = 1 << 1,
           49         MODE_ALTSCREEN   = 1 << 2,
           50         MODE_CRLF        = 1 << 3,
           51         MODE_ECHO        = 1 << 4,
           52         MODE_PRINT       = 1 << 5,
           53         MODE_UTF8        = 1 << 6,
           54 };
           55 
           56 enum cursor_movement {
           57         CURSOR_SAVE,
           58         CURSOR_LOAD
           59 };
           60 
           61 enum cursor_state {
           62         CURSOR_DEFAULT  = 0,
           63         CURSOR_WRAPNEXT = 1,
           64         CURSOR_ORIGIN   = 2
           65 };
           66 
           67 enum charset {
           68         CS_GRAPHIC0,
           69         CS_GRAPHIC1,
           70         CS_UK,
           71         CS_USA,
           72         CS_MULTI,
           73         CS_GER,
           74         CS_FIN
           75 };
           76 
           77 enum escape_state {
           78         ESC_START      = 1,
           79         ESC_CSI        = 2,
           80         ESC_STR        = 4,  /* DCS, OSC, PM, APC */
           81         ESC_ALTCHARSET = 8,
           82         ESC_STR_END    = 16, /* a final string was encountered */
           83         ESC_TEST       = 32, /* Enter in test mode */
           84         ESC_UTF8       = 64,
           85 };
           86 
           87 typedef struct {
           88         Glyph attr; /* current char attributes */
           89         int x;
           90         int y;
           91         char state;
           92 } TCursor;
           93 
           94 typedef struct {
           95         int mode;
           96         int type;
           97         int snap;
           98         /*
           99          * Selection variables:
          100          * nb – normalized coordinates of the beginning of the selection
          101          * ne – normalized coordinates of the end of the selection
          102          * ob – original coordinates of the beginning of the selection
          103          * oe – original coordinates of the end of the selection
          104          */
          105         struct {
          106                 int x, y;
          107         } nb, ne, ob, oe;
          108 
          109         int alt;
          110 } Selection;
          111 
          112 /* Internal representation of the screen */
          113 typedef struct {
          114         int row;      /* nb row */
          115         int col;      /* nb col */
          116         Line *line;   /* screen */
          117         Line *alt;    /* alternate screen */
          118         int *dirty;   /* dirtyness of lines */
          119         TCursor c;    /* cursor */
          120         int ocx;      /* old cursor col */
          121         int ocy;      /* old cursor row */
          122         int top;      /* top    scroll limit */
          123         int bot;      /* bottom scroll limit */
          124         int mode;     /* terminal mode flags */
          125         int esc;      /* escape state flags */
          126         char trantbl[4]; /* charset table translation */
          127         int charset;  /* current charset */
          128         int icharset; /* selected charset for sequence */
          129         int *tabs;
          130         Rune lastc;   /* last printed char outside of sequence, 0 if control */
          131 } Term;
          132 
          133 /* CSI Escape sequence structs */
          134 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
          135 typedef struct {
          136         char buf[ESC_BUF_SIZ]; /* raw string */
          137         size_t len;            /* raw string length */
          138         char priv;
          139         int arg[ESC_ARG_SIZ];
          140         int narg;              /* nb of args */
          141         char mode[2];
          142 } CSIEscape;
          143 
          144 /* STR Escape sequence structs */
          145 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
          146 typedef struct {
          147         char type;             /* ESC type ... */
          148         char *buf;             /* allocated raw string */
          149         size_t siz;            /* allocation size */
          150         size_t len;            /* raw string length */
          151         char *args[STR_ARG_SIZ];
          152         int narg;              /* nb of args */
          153 } STREscape;
          154 
          155 static void execsh(char *, char **);
          156 static void stty(char **);
          157 static void sigchld(int);
          158 static void ttywriteraw(const char *, size_t);
          159 
          160 static void csidump(void);
          161 static void csihandle(void);
          162 static void csiparse(void);
          163 static void csireset(void);
          164 static int eschandle(uchar);
          165 static void strdump(void);
          166 static void strhandle(void);
          167 static void strparse(void);
          168 static void strreset(void);
          169 
          170 static void tprinter(char *, size_t);
          171 static void tdumpsel(void);
          172 static void tdumpline(int);
          173 static void tdump(void);
          174 static void tclearregion(int, int, int, int);
          175 static void tcursor(int);
          176 static void tdeletechar(int);
          177 static void tdeleteline(int);
          178 static void tinsertblank(int);
          179 static void tinsertblankline(int);
          180 static int tlinelen(int);
          181 static void tmoveto(int, int);
          182 static void tmoveato(int, int);
          183 static void tnewline(int);
          184 static void tputtab(int);
          185 static void tputc(Rune);
          186 static void treset(void);
          187 static void tscrollup(int, int);
          188 static void tscrolldown(int, int);
          189 static void tsetattr(const int *, int);
          190 static void tsetchar(Rune, const Glyph *, int, int);
          191 static void tsetdirt(int, int);
          192 static void tsetscroll(int, int);
          193 static void tswapscreen(void);
          194 static void tsetmode(int, int, const int *, int);
          195 static int twrite(const char *, int, int);
          196 static void tfulldirt(void);
          197 static void tcontrolcode(uchar );
          198 static void tdectest(char );
          199 static void tdefutf8(char);
          200 static int32_t tdefcolor(const int *, int *, int);
          201 static void tdeftran(char);
          202 static void tstrsequence(uchar);
          203 
          204 static void drawregion(int, int, int, int);
          205 
          206 static void selnormalize(void);
          207 static void selscroll(int, int);
          208 static void selsnap(int *, int *, int);
          209 
          210 static size_t utf8decode(const char *, Rune *, size_t);
          211 static Rune utf8decodebyte(char, size_t *);
          212 static char utf8encodebyte(Rune, size_t);
          213 static size_t utf8validate(Rune *, size_t);
          214 
          215 static char *base64dec(const char *);
          216 static char base64dec_getc(const char **);
          217 
          218 static ssize_t xwrite(int, const char *, size_t);
          219 
          220 /* Globals */
          221 static Term term;
          222 static Selection sel;
          223 static CSIEscape csiescseq;
          224 static STREscape strescseq;
          225 static int iofd = 1;
          226 static int cmdfd;
          227 static pid_t pid;
          228 
          229 static const uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
          230 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
          231 static const Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
          232 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
          233 
          234 ssize_t
          235 xwrite(int fd, const char *s, size_t len)
          236 {
          237         size_t aux = len;
          238         ssize_t r;
          239 
          240         while (len > 0) {
          241                 r = write(fd, s, len);
          242                 if (r < 0)
          243                         return r;
          244                 len -= r;
          245                 s += r;
          246         }
          247 
          248         return aux;
          249 }
          250 
          251 void *
          252 xmalloc(size_t len)
          253 {
          254         void *p;
          255 
          256         if (!(p = malloc(len)))
          257                 die("malloc: %s\n", strerror(errno));
          258 
          259         return p;
          260 }
          261 
          262 void *
          263 xrealloc(void *p, size_t len)
          264 {
          265         if ((p = realloc(p, len)) == NULL)
          266                 die("realloc: %s\n", strerror(errno));
          267 
          268         return p;
          269 }
          270 
          271 char *
          272 xstrdup(const char *s)
          273 {
          274         char *p;
          275 
          276         if ((p = strdup(s)) == NULL)
          277                 die("strdup: %s\n", strerror(errno));
          278 
          279         return p;
          280 }
          281 
          282 size_t
          283 utf8decode(const char *c, Rune *u, size_t clen)
          284 {
          285         size_t i, j, len, type;
          286         Rune udecoded;
          287 
          288         *u = UTF_INVALID;
          289         if (!clen)
          290                 return 0;
          291         udecoded = utf8decodebyte(c[0], &len);
          292         if (!BETWEEN(len, 1, UTF_SIZ))
          293                 return 1;
          294         for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
          295                 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
          296                 if (type != 0)
          297                         return j;
          298         }
          299         if (j < len)
          300                 return 0;
          301         *u = udecoded;
          302         utf8validate(u, len);
          303 
          304         return len;
          305 }
          306 
          307 Rune
          308 utf8decodebyte(char c, size_t *i)
          309 {
          310         for (*i = 0; *i < LEN(utfmask); ++(*i))
          311                 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
          312                         return (uchar)c & ~utfmask[*i];
          313 
          314         return 0;
          315 }
          316 
          317 size_t
          318 utf8encode(Rune u, char *c)
          319 {
          320         size_t len, i;
          321 
          322         len = utf8validate(&u, 0);
          323         if (len > UTF_SIZ)
          324                 return 0;
          325 
          326         for (i = len - 1; i != 0; --i) {
          327                 c[i] = utf8encodebyte(u, 0);
          328                 u >>= 6;
          329         }
          330         c[0] = utf8encodebyte(u, len);
          331 
          332         return len;
          333 }
          334 
          335 char
          336 utf8encodebyte(Rune u, size_t i)
          337 {
          338         return utfbyte[i] | (u & ~utfmask[i]);
          339 }
          340 
          341 size_t
          342 utf8validate(Rune *u, size_t i)
          343 {
          344         if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
          345                 *u = UTF_INVALID;
          346         for (i = 1; *u > utfmax[i]; ++i)
          347                 ;
          348 
          349         return i;
          350 }
          351 
          352 char
          353 base64dec_getc(const char **src)
          354 {
          355         while (**src && !isprint((unsigned char)**src))
          356                 (*src)++;
          357         return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
          358 }
          359 
          360 char *
          361 base64dec(const char *src)
          362 {
          363         size_t in_len = strlen(src);
          364         char *result, *dst;
          365         static const char base64_digits[256] = {
          366                 [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
          367                 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
          368                 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
          369                 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
          370                 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
          371         };
          372 
          373         if (in_len % 4)
          374                 in_len += 4 - (in_len % 4);
          375         result = dst = xmalloc(in_len / 4 * 3 + 1);
          376         while (*src) {
          377                 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
          378                 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
          379                 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
          380                 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
          381 
          382                 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
          383                 if (a == -1 || b == -1)
          384                         break;
          385 
          386                 *dst++ = (a << 2) | ((b & 0x30) >> 4);
          387                 if (c == -1)
          388                         break;
          389                 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
          390                 if (d == -1)
          391                         break;
          392                 *dst++ = ((c & 0x03) << 6) | d;
          393         }
          394         *dst = '\0';
          395         return result;
          396 }
          397 
          398 void
          399 selinit(void)
          400 {
          401         sel.mode = SEL_IDLE;
          402         sel.snap = 0;
          403         sel.ob.x = -1;
          404 }
          405 
          406 int
          407 tlinelen(int y)
          408 {
          409         int i = term.col;
          410 
          411         if (term.line[y][i - 1].mode & ATTR_WRAP)
          412                 return i;
          413 
          414         while (i > 0 && term.line[y][i - 1].u == ' ')
          415                 --i;
          416 
          417         return i;
          418 }
          419 
          420 void
          421 selstart(int col, int row, int snap)
          422 {
          423         selclear();
          424         sel.mode = SEL_EMPTY;
          425         sel.type = SEL_REGULAR;
          426         sel.alt = IS_SET(MODE_ALTSCREEN);
          427         sel.snap = snap;
          428         sel.oe.x = sel.ob.x = col;
          429         sel.oe.y = sel.ob.y = row;
          430         selnormalize();
          431 
          432         if (sel.snap != 0)
          433                 sel.mode = SEL_READY;
          434         tsetdirt(sel.nb.y, sel.ne.y);
          435 }
          436 
          437 void
          438 selextend(int col, int row, int type, int done)
          439 {
          440         int oldey, oldex, oldsby, oldsey, oldtype;
          441 
          442         if (sel.mode == SEL_IDLE)
          443                 return;
          444         if (done && sel.mode == SEL_EMPTY) {
          445                 selclear();
          446                 return;
          447         }
          448 
          449         oldey = sel.oe.y;
          450         oldex = sel.oe.x;
          451         oldsby = sel.nb.y;
          452         oldsey = sel.ne.y;
          453         oldtype = sel.type;
          454 
          455         sel.oe.x = col;
          456         sel.oe.y = row;
          457         selnormalize();
          458         sel.type = type;
          459 
          460         if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
          461                 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
          462 
          463         sel.mode = done ? SEL_IDLE : SEL_READY;
          464 }
          465 
          466 void
          467 selnormalize(void)
          468 {
          469         int i;
          470 
          471         if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
          472                 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
          473                 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
          474         } else {
          475                 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
          476                 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
          477         }
          478         sel.nb.y = MIN(sel.ob.y, sel.oe.y);
          479         sel.ne.y = MAX(sel.ob.y, sel.oe.y);
          480 
          481         selsnap(&sel.nb.x, &sel.nb.y, -1);
          482         selsnap(&sel.ne.x, &sel.ne.y, +1);
          483 
          484         /* expand selection over line breaks */
          485         if (sel.type == SEL_RECTANGULAR)
          486                 return;
          487         i = tlinelen(sel.nb.y);
          488         if (i < sel.nb.x)
          489                 sel.nb.x = i;
          490         if (tlinelen(sel.ne.y) <= sel.ne.x)
          491                 sel.ne.x = term.col - 1;
          492 }
          493 
          494 int
          495 selected(int x, int y)
          496 {
          497         if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
          498                         sel.alt != IS_SET(MODE_ALTSCREEN))
          499                 return 0;
          500 
          501         if (sel.type == SEL_RECTANGULAR)
          502                 return BETWEEN(y, sel.nb.y, sel.ne.y)
          503                     && BETWEEN(x, sel.nb.x, sel.ne.x);
          504 
          505         return BETWEEN(y, sel.nb.y, sel.ne.y)
          506             && (y != sel.nb.y || x >= sel.nb.x)
          507             && (y != sel.ne.y || x <= sel.ne.x);
          508 }
          509 
          510 void
          511 selsnap(int *x, int *y, int direction)
          512 {
          513         int newx, newy, xt, yt;
          514         int delim, prevdelim;
          515         const Glyph *gp, *prevgp;
          516 
          517         switch (sel.snap) {
          518         case SNAP_WORD:
          519                 /*
          520                  * Snap around if the word wraps around at the end or
          521                  * beginning of a line.
          522                  */
          523                 prevgp = &term.line[*y][*x];
          524                 prevdelim = ISDELIM(prevgp->u);
          525                 for (;;) {
          526                         newx = *x + direction;
          527                         newy = *y;
          528                         if (!BETWEEN(newx, 0, term.col - 1)) {
          529                                 newy += direction;
          530                                 newx = (newx + term.col) % term.col;
          531                                 if (!BETWEEN(newy, 0, term.row - 1))
          532                                         break;
          533 
          534                                 if (direction > 0)
          535                                         yt = *y, xt = *x;
          536                                 else
          537                                         yt = newy, xt = newx;
          538                                 if (!(term.line[yt][xt].mode & ATTR_WRAP))
          539                                         break;
          540                         }
          541 
          542                         if (newx >= tlinelen(newy))
          543                                 break;
          544 
          545                         gp = &term.line[newy][newx];
          546                         delim = ISDELIM(gp->u);
          547                         if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
          548                                         || (delim && gp->u != prevgp->u)))
          549                                 break;
          550 
          551                         *x = newx;
          552                         *y = newy;
          553                         prevgp = gp;
          554                         prevdelim = delim;
          555                 }
          556                 break;
          557         case SNAP_LINE:
          558                 /*
          559                  * Snap around if the the previous line or the current one
          560                  * has set ATTR_WRAP at its end. Then the whole next or
          561                  * previous line will be selected.
          562                  */
          563                 *x = (direction < 0) ? 0 : term.col - 1;
          564                 if (direction < 0) {
          565                         for (; *y > 0; *y += direction) {
          566                                 if (!(term.line[*y-1][term.col-1].mode
          567                                                 & ATTR_WRAP)) {
          568                                         break;
          569                                 }
          570                         }
          571                 } else if (direction > 0) {
          572                         for (; *y < term.row-1; *y += direction) {
          573                                 if (!(term.line[*y][term.col-1].mode
          574                                                 & ATTR_WRAP)) {
          575                                         break;
          576                                 }
          577                         }
          578                 }
          579                 break;
          580         }
          581 }
          582 
          583 char *
          584 getsel(void)
          585 {
          586         char *str, *ptr;
          587         int y, bufsize, lastx, linelen;
          588         const Glyph *gp, *last;
          589 
          590         if (sel.ob.x == -1)
          591                 return NULL;
          592 
          593         bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
          594         ptr = str = xmalloc(bufsize);
          595 
          596         /* append every set & selected glyph to the selection */
          597         for (y = sel.nb.y; y <= sel.ne.y; y++) {
          598                 if ((linelen = tlinelen(y)) == 0) {
          599                         *ptr++ = '\n';
          600                         continue;
          601                 }
          602 
          603                 if (sel.type == SEL_RECTANGULAR) {
          604                         gp = &term.line[y][sel.nb.x];
          605                         lastx = sel.ne.x;
          606                 } else {
          607                         gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
          608                         lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
          609                 }
          610                 last = &term.line[y][MIN(lastx, linelen-1)];
          611                 while (last >= gp && last->u == ' ')
          612                         --last;
          613 
          614                 for ( ; gp <= last; ++gp) {
          615                         if (gp->mode & ATTR_WDUMMY)
          616                                 continue;
          617 
          618                         ptr += utf8encode(gp->u, ptr);
          619                 }
          620 
          621                 /*
          622                  * Copy and pasting of line endings is inconsistent
          623                  * in the inconsistent terminal and GUI world.
          624                  * The best solution seems like to produce '\n' when
          625                  * something is copied from st and convert '\n' to
          626                  * '\r', when something to be pasted is received by
          627                  * st.
          628                  * FIXME: Fix the computer world.
          629                  */
          630                 if ((y < sel.ne.y || lastx >= linelen) &&
          631                     (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
          632                         *ptr++ = '\n';
          633         }
          634         *ptr = 0;
          635         return str;
          636 }
          637 
          638 void
          639 selclear(void)
          640 {
          641         if (sel.ob.x == -1)
          642                 return;
          643         sel.mode = SEL_IDLE;
          644         sel.ob.x = -1;
          645         tsetdirt(sel.nb.y, sel.ne.y);
          646 }
          647 
          648 void
          649 die(const char *errstr, ...)
          650 {
          651         va_list ap;
          652 
          653         va_start(ap, errstr);
          654         vfprintf(stderr, errstr, ap);
          655         va_end(ap);
          656         exit(1);
          657 }
          658 
          659 void
          660 execsh(char *cmd, char **args)
          661 {
          662         char *sh, *prog, *arg;
          663         const struct passwd *pw;
          664 
          665         errno = 0;
          666         if ((pw = getpwuid(getuid())) == NULL) {
          667                 if (errno)
          668                         die("getpwuid: %s\n", strerror(errno));
          669                 else
          670                         die("who are you?\n");
          671         }
          672 
          673         if ((sh = getenv("SHELL")) == NULL)
          674                 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
          675 
          676         if (args) {
          677                 prog = args[0];
          678                 arg = NULL;
          679         } else if (scroll) {
          680                 prog = scroll;
          681                 arg = utmp ? utmp : sh;
          682         } else if (utmp) {
          683                 prog = utmp;
          684                 arg = NULL;
          685         } else {
          686                 prog = sh;
          687                 arg = NULL;
          688         }
          689         DEFAULT(args, ((char *[]) {prog, arg, NULL}));
          690 
          691         unsetenv("COLUMNS");
          692         unsetenv("LINES");
          693         unsetenv("TERMCAP");
          694         setenv("LOGNAME", pw->pw_name, 1);
          695         setenv("USER", pw->pw_name, 1);
          696         setenv("SHELL", sh, 1);
          697         setenv("HOME", pw->pw_dir, 1);
          698         setenv("TERM", termname, 1);
          699 
          700         signal(SIGCHLD, SIG_DFL);
          701         signal(SIGHUP, SIG_DFL);
          702         signal(SIGINT, SIG_DFL);
          703         signal(SIGQUIT, SIG_DFL);
          704         signal(SIGTERM, SIG_DFL);
          705         signal(SIGALRM, SIG_DFL);
          706 
          707         execvp(prog, args);
          708         _exit(1);
          709 }
          710 
          711 void
          712 sigchld(int a)
          713 {
          714         int stat;
          715         pid_t p;
          716 
          717         if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
          718                 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
          719 
          720         if (pid != p)
          721                 return;
          722 
          723         if (WIFEXITED(stat) && WEXITSTATUS(stat))
          724                 die("child exited with status %d\n", WEXITSTATUS(stat));
          725         else if (WIFSIGNALED(stat))
          726                 die("child terminated due to signal %d\n", WTERMSIG(stat));
          727         _exit(0);
          728 }
          729 
          730 void
          731 stty(char **args)
          732 {
          733         char cmd[_POSIX_ARG_MAX], **p, *q, *s;
          734         size_t n, siz;
          735 
          736         if ((n = strlen(stty_args)) > sizeof(cmd)-1)
          737                 die("incorrect stty parameters\n");
          738         memcpy(cmd, stty_args, n);
          739         q = cmd + n;
          740         siz = sizeof(cmd) - n;
          741         for (p = args; p && (s = *p); ++p) {
          742                 if ((n = strlen(s)) > siz-1)
          743                         die("stty parameter length too long\n");
          744                 *q++ = ' ';
          745                 memcpy(q, s, n);
          746                 q += n;
          747                 siz -= n + 1;
          748         }
          749         *q = '\0';
          750         if (system(cmd) != 0)
          751                 perror("Couldn't call stty");
          752 }
          753 
          754 int
          755 ttynew(const char *line, char *cmd, const char *out, char **args)
          756 {
          757         int m, s;
          758 
          759         if (out) {
          760                 term.mode |= MODE_PRINT;
          761                 iofd = (!strcmp(out, "-")) ?
          762                           1 : open(out, O_WRONLY | O_CREAT, 0666);
          763                 if (iofd < 0) {
          764                         fprintf(stderr, "Error opening %s:%s\n",
          765                                 out, strerror(errno));
          766                 }
          767         }
          768 
          769         if (line) {
          770                 if ((cmdfd = open(line, O_RDWR)) < 0)
          771                         die("open line '%s' failed: %s\n",
          772                             line, strerror(errno));
          773                 dup2(cmdfd, 0);
          774                 stty(args);
          775                 return cmdfd;
          776         }
          777 
          778         /* seems to work fine on linux, openbsd and freebsd */
          779         if (openpty(&m, &s, NULL, NULL, NULL) < 0)
          780                 die("openpty failed: %s\n", strerror(errno));
          781 
          782         switch (pid = fork()) {
          783         case -1:
          784                 die("fork failed: %s\n", strerror(errno));
          785                 break;
          786         case 0:
          787                 close(iofd);
          788                 close(m);
          789                 setsid(); /* create a new process group */
          790                 dup2(s, 0);
          791                 dup2(s, 1);
          792                 dup2(s, 2);
          793                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
          794                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
          795                 if (s > 2)
          796                         close(s);
          797 #ifdef __OpenBSD__
          798                 if (pledge("stdio getpw proc exec", NULL) == -1)
          799                         die("pledge\n");
          800 #endif
          801                 execsh(cmd, args);
          802                 break;
          803         default:
          804 #ifdef __OpenBSD__
          805                 if (pledge("stdio rpath tty proc", NULL) == -1)
          806                         die("pledge\n");
          807 #endif
          808                 close(s);
          809                 cmdfd = m;
          810                 signal(SIGCHLD, sigchld);
          811                 break;
          812         }
          813         return cmdfd;
          814 }
          815 
          816 size_t
          817 ttyread(void)
          818 {
          819         static char buf[BUFSIZ];
          820         static int buflen = 0;
          821         int ret, written;
          822 
          823         /* append read bytes to unprocessed bytes */
          824         ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
          825 
          826         switch (ret) {
          827         case 0:
          828                 exit(0);
          829         case -1:
          830                 die("couldn't read from shell: %s\n", strerror(errno));
          831         default:
          832                 buflen += ret;
          833                 written = twrite(buf, buflen, 0);
          834                 buflen -= written;
          835                 /* keep any incomplete UTF-8 byte sequence for the next call */
          836                 if (buflen > 0)
          837                         memmove(buf, buf + written, buflen);
          838                 return ret;
          839         }
          840 }
          841 
          842 void
          843 ttywrite(const char *s, size_t n, int may_echo)
          844 {
          845         const char *next;
          846 
          847         if (may_echo && IS_SET(MODE_ECHO))
          848                 twrite(s, n, 1);
          849 
          850         if (!IS_SET(MODE_CRLF)) {
          851                 ttywriteraw(s, n);
          852                 return;
          853         }
          854 
          855         /* This is similar to how the kernel handles ONLCR for ttys */
          856         while (n > 0) {
          857                 if (*s == '\r') {
          858                         next = s + 1;
          859                         ttywriteraw("\r\n", 2);
          860                 } else {
          861                         next = memchr(s, '\r', n);
          862                         DEFAULT(next, s + n);
          863                         ttywriteraw(s, next - s);
          864                 }
          865                 n -= next - s;
          866                 s = next;
          867         }
          868 }
          869 
          870 void
          871 ttywriteraw(const char *s, size_t n)
          872 {
          873         fd_set wfd, rfd;
          874         ssize_t r;
          875         size_t lim = 256;
          876 
          877         /*
          878          * Remember that we are using a pty, which might be a modem line.
          879          * Writing too much will clog the line. That's why we are doing this
          880          * dance.
          881          * FIXME: Migrate the world to Plan 9.
          882          */
          883         while (n > 0) {
          884                 FD_ZERO(&wfd);
          885                 FD_ZERO(&rfd);
          886                 FD_SET(cmdfd, &wfd);
          887                 FD_SET(cmdfd, &rfd);
          888 
          889                 /* Check if we can write. */
          890                 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
          891                         if (errno == EINTR)
          892                                 continue;
          893                         die("select failed: %s\n", strerror(errno));
          894                 }
          895                 if (FD_ISSET(cmdfd, &wfd)) {
          896                         /*
          897                          * Only write the bytes written by ttywrite() or the
          898                          * default of 256. This seems to be a reasonable value
          899                          * for a serial line. Bigger values might clog the I/O.
          900                          */
          901                         if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
          902                                 goto write_error;
          903                         if (r < n) {
          904                                 /*
          905                                  * We weren't able to write out everything.
          906                                  * This means the buffer is getting full
          907                                  * again. Empty it.
          908                                  */
          909                                 if (n < lim)
          910                                         lim = ttyread();
          911                                 n -= r;
          912                                 s += r;
          913                         } else {
          914                                 /* All bytes have been written. */
          915                                 break;
          916                         }
          917                 }
          918                 if (FD_ISSET(cmdfd, &rfd))
          919                         lim = ttyread();
          920         }
          921         return;
          922 
          923 write_error:
          924         die("write error on tty: %s\n", strerror(errno));
          925 }
          926 
          927 void
          928 ttyresize(int tw, int th)
          929 {
          930         struct winsize w;
          931 
          932         w.ws_row = term.row;
          933         w.ws_col = term.col;
          934         w.ws_xpixel = tw;
          935         w.ws_ypixel = th;
          936         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
          937                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
          938 }
          939 
          940 void
          941 ttyhangup()
          942 {
          943         /* Send SIGHUP to shell */
          944         kill(pid, SIGHUP);
          945 }
          946 
          947 int
          948 tattrset(int attr)
          949 {
          950         int i, j;
          951 
          952         for (i = 0; i < term.row-1; i++) {
          953                 for (j = 0; j < term.col-1; j++) {
          954                         if (term.line[i][j].mode & attr)
          955                                 return 1;
          956                 }
          957         }
          958 
          959         return 0;
          960 }
          961 
          962 void
          963 tsetdirt(int top, int bot)
          964 {
          965         int i;
          966 
          967         LIMIT(top, 0, term.row-1);
          968         LIMIT(bot, 0, term.row-1);
          969 
          970         for (i = top; i <= bot; i++)
          971                 term.dirty[i] = 1;
          972 }
          973 
          974 void
          975 tsetdirtattr(int attr)
          976 {
          977         int i, j;
          978 
          979         for (i = 0; i < term.row-1; i++) {
          980                 for (j = 0; j < term.col-1; j++) {
          981                         if (term.line[i][j].mode & attr) {
          982                                 tsetdirt(i, i);
          983                                 break;
          984                         }
          985                 }
          986         }
          987 }
          988 
          989 void
          990 tfulldirt(void)
          991 {
          992         tsetdirt(0, term.row-1);
          993 }
          994 
          995 void
          996 tcursor(int mode)
          997 {
          998         static TCursor c[2];
          999         int alt = IS_SET(MODE_ALTSCREEN);
         1000 
         1001         if (mode == CURSOR_SAVE) {
         1002                 c[alt] = term.c;
         1003         } else if (mode == CURSOR_LOAD) {
         1004                 term.c = c[alt];
         1005                 tmoveto(c[alt].x, c[alt].y);
         1006         }
         1007 }
         1008 
         1009 void
         1010 treset(void)
         1011 {
         1012         uint i;
         1013 
         1014         term.c = (TCursor){{
         1015                 .mode = ATTR_NULL,
         1016                 .fg = defaultfg,
         1017                 .bg = defaultbg
         1018         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
         1019 
         1020         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
         1021         for (i = tabspaces; i < term.col; i += tabspaces)
         1022                 term.tabs[i] = 1;
         1023         term.top = 0;
         1024         term.bot = term.row - 1;
         1025         term.mode = MODE_WRAP|MODE_UTF8;
         1026         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
         1027         term.charset = 0;
         1028 
         1029         for (i = 0; i < 2; i++) {
         1030                 tmoveto(0, 0);
         1031                 tcursor(CURSOR_SAVE);
         1032                 tclearregion(0, 0, term.col-1, term.row-1);
         1033                 tswapscreen();
         1034         }
         1035 }
         1036 
         1037 void
         1038 tnew(int col, int row)
         1039 {
         1040         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
         1041         tresize(col, row);
         1042         treset();
         1043 }
         1044 
         1045 void
         1046 tswapscreen(void)
         1047 {
         1048         Line *tmp = term.line;
         1049 
         1050         term.line = term.alt;
         1051         term.alt = tmp;
         1052         term.mode ^= MODE_ALTSCREEN;
         1053         tfulldirt();
         1054 }
         1055 
         1056 void
         1057 tscrolldown(int orig, int n)
         1058 {
         1059         int i;
         1060         Line temp;
         1061 
         1062         LIMIT(n, 0, term.bot-orig+1);
         1063 
         1064         tsetdirt(orig, term.bot-n);
         1065         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
         1066 
         1067         for (i = term.bot; i >= orig+n; i--) {
         1068                 temp = term.line[i];
         1069                 term.line[i] = term.line[i-n];
         1070                 term.line[i-n] = temp;
         1071         }
         1072 
         1073         selscroll(orig, n);
         1074 }
         1075 
         1076 void
         1077 tscrollup(int orig, int n)
         1078 {
         1079         int i;
         1080         Line temp;
         1081 
         1082         LIMIT(n, 0, term.bot-orig+1);
         1083 
         1084         tclearregion(0, orig, term.col-1, orig+n-1);
         1085         tsetdirt(orig+n, term.bot);
         1086 
         1087         for (i = orig; i <= term.bot-n; i++) {
         1088                 temp = term.line[i];
         1089                 term.line[i] = term.line[i+n];
         1090                 term.line[i+n] = temp;
         1091         }
         1092 
         1093         selscroll(orig, -n);
         1094 }
         1095 
         1096 void
         1097 selscroll(int orig, int n)
         1098 {
         1099         if (sel.ob.x == -1)
         1100                 return;
         1101 
         1102         if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
         1103                 selclear();
         1104         } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
         1105                 sel.ob.y += n;
         1106                 sel.oe.y += n;
         1107                 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
         1108                     sel.oe.y < term.top || sel.oe.y > term.bot) {
         1109                         selclear();
         1110                 } else {
         1111                         selnormalize();
         1112                 }
         1113         }
         1114 }
         1115 
         1116 void
         1117 tnewline(int first_col)
         1118 {
         1119         int y = term.c.y;
         1120 
         1121         if (y == term.bot) {
         1122                 tscrollup(term.top, 1);
         1123         } else {
         1124                 y++;
         1125         }
         1126         tmoveto(first_col ? 0 : term.c.x, y);
         1127 }
         1128 
         1129 void
         1130 csiparse(void)
         1131 {
         1132         char *p = csiescseq.buf, *np;
         1133         long int v;
         1134 
         1135         csiescseq.narg = 0;
         1136         if (*p == '?') {
         1137                 csiescseq.priv = 1;
         1138                 p++;
         1139         }
         1140 
         1141         csiescseq.buf[csiescseq.len] = '\0';
         1142         while (p < csiescseq.buf+csiescseq.len) {
         1143                 np = NULL;
         1144                 v = strtol(p, &np, 10);
         1145                 if (np == p)
         1146                         v = 0;
         1147                 if (v == LONG_MAX || v == LONG_MIN)
         1148                         v = -1;
         1149                 csiescseq.arg[csiescseq.narg++] = v;
         1150                 p = np;
         1151                 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
         1152                         break;
         1153                 p++;
         1154         }
         1155         csiescseq.mode[0] = *p++;
         1156         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
         1157 }
         1158 
         1159 /* for absolute user moves, when decom is set */
         1160 void
         1161 tmoveato(int x, int y)
         1162 {
         1163         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
         1164 }
         1165 
         1166 void
         1167 tmoveto(int x, int y)
         1168 {
         1169         int miny, maxy;
         1170 
         1171         if (term.c.state & CURSOR_ORIGIN) {
         1172                 miny = term.top;
         1173                 maxy = term.bot;
         1174         } else {
         1175                 miny = 0;
         1176                 maxy = term.row - 1;
         1177         }
         1178         term.c.state &= ~CURSOR_WRAPNEXT;
         1179         term.c.x = LIMIT(x, 0, term.col-1);
         1180         term.c.y = LIMIT(y, miny, maxy);
         1181 }
         1182 
         1183 void
         1184 tsetchar(Rune u, const Glyph *attr, int x, int y)
         1185 {
         1186         static const char *vt100_0[62] = { /* 0x41 - 0x7e */
         1187                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
         1188                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
         1189                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
         1190                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
         1191                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
         1192                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
         1193                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
         1194                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
         1195         };
         1196 
         1197         /*
         1198          * The table is proudly stolen from rxvt.
         1199          */
         1200         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
         1201            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
         1202                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
         1203 
         1204         if (term.line[y][x].mode & ATTR_WIDE) {
         1205                 if (x+1 < term.col) {
         1206                         term.line[y][x+1].u = ' ';
         1207                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
         1208                 }
         1209         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
         1210                 term.line[y][x-1].u = ' ';
         1211                 term.line[y][x-1].mode &= ~ATTR_WIDE;
         1212         }
         1213 
         1214         term.dirty[y] = 1;
         1215         term.line[y][x] = *attr;
         1216         term.line[y][x].u = u;
         1217 }
         1218 
         1219 void
         1220 tclearregion(int x1, int y1, int x2, int y2)
         1221 {
         1222         int x, y, temp;
         1223         Glyph *gp;
         1224 
         1225         if (x1 > x2)
         1226                 temp = x1, x1 = x2, x2 = temp;
         1227         if (y1 > y2)
         1228                 temp = y1, y1 = y2, y2 = temp;
         1229 
         1230         LIMIT(x1, 0, term.col-1);
         1231         LIMIT(x2, 0, term.col-1);
         1232         LIMIT(y1, 0, term.row-1);
         1233         LIMIT(y2, 0, term.row-1);
         1234 
         1235         for (y = y1; y <= y2; y++) {
         1236                 term.dirty[y] = 1;
         1237                 for (x = x1; x <= x2; x++) {
         1238                         gp = &term.line[y][x];
         1239                         if (selected(x, y))
         1240                                 selclear();
         1241                         gp->fg = term.c.attr.fg;
         1242                         gp->bg = term.c.attr.bg;
         1243                         gp->mode = 0;
         1244                         gp->u = ' ';
         1245                 }
         1246         }
         1247 }
         1248 
         1249 void
         1250 tdeletechar(int n)
         1251 {
         1252         int dst, src, size;
         1253         Glyph *line;
         1254 
         1255         LIMIT(n, 0, term.col - term.c.x);
         1256 
         1257         dst = term.c.x;
         1258         src = term.c.x + n;
         1259         size = term.col - src;
         1260         line = term.line[term.c.y];
         1261 
         1262         memmove(&line[dst], &line[src], size * sizeof(Glyph));
         1263         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
         1264 }
         1265 
         1266 void
         1267 tinsertblank(int n)
         1268 {
         1269         int dst, src, size;
         1270         Glyph *line;
         1271 
         1272         LIMIT(n, 0, term.col - term.c.x);
         1273 
         1274         dst = term.c.x + n;
         1275         src = term.c.x;
         1276         size = term.col - dst;
         1277         line = term.line[term.c.y];
         1278 
         1279         memmove(&line[dst], &line[src], size * sizeof(Glyph));
         1280         tclearregion(src, term.c.y, dst - 1, term.c.y);
         1281 }
         1282 
         1283 void
         1284 tinsertblankline(int n)
         1285 {
         1286         if (BETWEEN(term.c.y, term.top, term.bot))
         1287                 tscrolldown(term.c.y, n);
         1288 }
         1289 
         1290 void
         1291 tdeleteline(int n)
         1292 {
         1293         if (BETWEEN(term.c.y, term.top, term.bot))
         1294                 tscrollup(term.c.y, n);
         1295 }
         1296 
         1297 int32_t
         1298 tdefcolor(const int *attr, int *npar, int l)
         1299 {
         1300         int32_t idx = -1;
         1301         uint r, g, b;
         1302 
         1303         switch (attr[*npar + 1]) {
         1304         case 2: /* direct color in RGB space */
         1305                 if (*npar + 4 >= l) {
         1306                         fprintf(stderr,
         1307                                 "erresc(38): Incorrect number of parameters (%d)\n",
         1308                                 *npar);
         1309                         break;
         1310                 }
         1311                 r = attr[*npar + 2];
         1312                 g = attr[*npar + 3];
         1313                 b = attr[*npar + 4];
         1314                 *npar += 4;
         1315                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
         1316                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
         1317                                 r, g, b);
         1318                 else
         1319                         idx = TRUECOLOR(r, g, b);
         1320                 break;
         1321         case 5: /* indexed color */
         1322                 if (*npar + 2 >= l) {
         1323                         fprintf(stderr,
         1324                                 "erresc(38): Incorrect number of parameters (%d)\n",
         1325                                 *npar);
         1326                         break;
         1327                 }
         1328                 *npar += 2;
         1329                 if (!BETWEEN(attr[*npar], 0, 255))
         1330                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
         1331                 else
         1332                         idx = attr[*npar];
         1333                 break;
         1334         case 0: /* implemented defined (only foreground) */
         1335         case 1: /* transparent */
         1336         case 3: /* direct color in CMY space */
         1337         case 4: /* direct color in CMYK space */
         1338         default:
         1339                 fprintf(stderr,
         1340                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
         1341                 break;
         1342         }
         1343 
         1344         return idx;
         1345 }
         1346 
         1347 void
         1348 tsetattr(const int *attr, int l)
         1349 {
         1350         int i;
         1351         int32_t idx;
         1352 
         1353         for (i = 0; i < l; i++) {
         1354                 switch (attr[i]) {
         1355                 case 0:
         1356                         term.c.attr.mode &= ~(
         1357                                 ATTR_BOLD       |
         1358                                 ATTR_FAINT      |
         1359                                 ATTR_ITALIC     |
         1360                                 ATTR_UNDERLINE  |
         1361                                 ATTR_BLINK      |
         1362                                 ATTR_REVERSE    |
         1363                                 ATTR_INVISIBLE  |
         1364                                 ATTR_STRUCK     );
         1365                         term.c.attr.fg = defaultfg;
         1366                         term.c.attr.bg = defaultbg;
         1367                         break;
         1368                 case 1:
         1369                         term.c.attr.mode |= ATTR_BOLD;
         1370                         break;
         1371                 case 2:
         1372                         term.c.attr.mode |= ATTR_FAINT;
         1373                         break;
         1374                 case 3:
         1375                         term.c.attr.mode |= ATTR_ITALIC;
         1376                         break;
         1377                 case 4:
         1378                         term.c.attr.mode |= ATTR_UNDERLINE;
         1379                         break;
         1380                 case 5: /* slow blink */
         1381                         /* FALLTHROUGH */
         1382                 case 6: /* rapid blink */
         1383                         term.c.attr.mode |= ATTR_BLINK;
         1384                         break;
         1385                 case 7:
         1386                         term.c.attr.mode |= ATTR_REVERSE;
         1387                         break;
         1388                 case 8:
         1389                         term.c.attr.mode |= ATTR_INVISIBLE;
         1390                         break;
         1391                 case 9:
         1392                         term.c.attr.mode |= ATTR_STRUCK;
         1393                         break;
         1394                 case 22:
         1395                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
         1396                         break;
         1397                 case 23:
         1398                         term.c.attr.mode &= ~ATTR_ITALIC;
         1399                         break;
         1400                 case 24:
         1401                         term.c.attr.mode &= ~ATTR_UNDERLINE;
         1402                         break;
         1403                 case 25:
         1404                         term.c.attr.mode &= ~ATTR_BLINK;
         1405                         break;
         1406                 case 27:
         1407                         term.c.attr.mode &= ~ATTR_REVERSE;
         1408                         break;
         1409                 case 28:
         1410                         term.c.attr.mode &= ~ATTR_INVISIBLE;
         1411                         break;
         1412                 case 29:
         1413                         term.c.attr.mode &= ~ATTR_STRUCK;
         1414                         break;
         1415                 case 38:
         1416                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
         1417                                 term.c.attr.fg = idx;
         1418                         break;
         1419                 case 39:
         1420                         term.c.attr.fg = defaultfg;
         1421                         break;
         1422                 case 48:
         1423                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
         1424                                 term.c.attr.bg = idx;
         1425                         break;
         1426                 case 49:
         1427                         term.c.attr.bg = defaultbg;
         1428                         break;
         1429                 default:
         1430                         if (BETWEEN(attr[i], 30, 37)) {
         1431                                 term.c.attr.fg = attr[i] - 30;
         1432                         } else if (BETWEEN(attr[i], 40, 47)) {
         1433                                 term.c.attr.bg = attr[i] - 40;
         1434                         } else if (BETWEEN(attr[i], 90, 97)) {
         1435                                 term.c.attr.fg = attr[i] - 90 + 8;
         1436                         } else if (BETWEEN(attr[i], 100, 107)) {
         1437                                 term.c.attr.bg = attr[i] - 100 + 8;
         1438                         } else {
         1439                                 fprintf(stderr,
         1440                                         "erresc(default): gfx attr %d unknown\n",
         1441                                         attr[i]);
         1442                                 csidump();
         1443                         }
         1444                         break;
         1445                 }
         1446         }
         1447 }
         1448 
         1449 void
         1450 tsetscroll(int t, int b)
         1451 {
         1452         int temp;
         1453 
         1454         LIMIT(t, 0, term.row-1);
         1455         LIMIT(b, 0, term.row-1);
         1456         if (t > b) {
         1457                 temp = t;
         1458                 t = b;
         1459                 b = temp;
         1460         }
         1461         term.top = t;
         1462         term.bot = b;
         1463 }
         1464 
         1465 void
         1466 tsetmode(int priv, int set, const int *args, int narg)
         1467 {
         1468         int alt; const int *lim;
         1469 
         1470         for (lim = args + narg; args < lim; ++args) {
         1471                 if (priv) {
         1472                         switch (*args) {
         1473                         case 1: /* DECCKM -- Cursor key */
         1474                                 xsetmode(set, MODE_APPCURSOR);
         1475                                 break;
         1476                         case 5: /* DECSCNM -- Reverse video */
         1477                                 xsetmode(set, MODE_REVERSE);
         1478                                 break;
         1479                         case 6: /* DECOM -- Origin */
         1480                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
         1481                                 tmoveato(0, 0);
         1482                                 break;
         1483                         case 7: /* DECAWM -- Auto wrap */
         1484                                 MODBIT(term.mode, set, MODE_WRAP);
         1485                                 break;
         1486                         case 0:  /* Error (IGNORED) */
         1487                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
         1488                         case 3:  /* DECCOLM -- Column  (IGNORED) */
         1489                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
         1490                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
         1491                         case 18: /* DECPFF -- Printer feed (IGNORED) */
         1492                         case 19: /* DECPEX -- Printer extent (IGNORED) */
         1493                         case 42: /* DECNRCM -- National characters (IGNORED) */
         1494                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
         1495                                 break;
         1496                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
         1497                                 xsetmode(!set, MODE_HIDE);
         1498                                 break;
         1499                         case 9:    /* X10 mouse compatibility mode */
         1500                                 xsetpointermotion(0);
         1501                                 xsetmode(0, MODE_MOUSE);
         1502                                 xsetmode(set, MODE_MOUSEX10);
         1503                                 break;
         1504                         case 1000: /* 1000: report button press */
         1505                                 xsetpointermotion(0);
         1506                                 xsetmode(0, MODE_MOUSE);
         1507                                 xsetmode(set, MODE_MOUSEBTN);
         1508                                 break;
         1509                         case 1002: /* 1002: report motion on button press */
         1510                                 xsetpointermotion(0);
         1511                                 xsetmode(0, MODE_MOUSE);
         1512                                 xsetmode(set, MODE_MOUSEMOTION);
         1513                                 break;
         1514                         case 1003: /* 1003: enable all mouse motions */
         1515                                 xsetpointermotion(set);
         1516                                 xsetmode(0, MODE_MOUSE);
         1517                                 xsetmode(set, MODE_MOUSEMANY);
         1518                                 break;
         1519                         case 1004: /* 1004: send focus events to tty */
         1520                                 xsetmode(set, MODE_FOCUS);
         1521                                 break;
         1522                         case 1006: /* 1006: extended reporting mode */
         1523                                 xsetmode(set, MODE_MOUSESGR);
         1524                                 break;
         1525                         case 1034:
         1526                                 xsetmode(set, MODE_8BIT);
         1527                                 break;
         1528                         case 1049: /* swap screen & set/restore cursor as xterm */
         1529                                 if (!allowaltscreen)
         1530                                         break;
         1531                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
         1532                                 /* FALLTHROUGH */
         1533                         case 47: /* swap screen */
         1534                         case 1047:
         1535                                 if (!allowaltscreen)
         1536                                         break;
         1537                                 alt = IS_SET(MODE_ALTSCREEN);
         1538                                 if (alt) {
         1539                                         tclearregion(0, 0, term.col-1,
         1540                                                         term.row-1);
         1541                                 }
         1542                                 if (set ^ alt) /* set is always 1 or 0 */
         1543                                         tswapscreen();
         1544                                 if (*args != 1049)
         1545                                         break;
         1546                                 /* FALLTHROUGH */
         1547                         case 1048:
         1548                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
         1549                                 break;
         1550                         case 2004: /* 2004: bracketed paste mode */
         1551                                 xsetmode(set, MODE_BRCKTPASTE);
         1552                                 break;
         1553                         /* Not implemented mouse modes. See comments there. */
         1554                         case 1001: /* mouse highlight mode; can hang the
         1555                                       terminal by design when implemented. */
         1556                         case 1005: /* UTF-8 mouse mode; will confuse
         1557                                       applications not supporting UTF-8
         1558                                       and luit. */
         1559                         case 1015: /* urxvt mangled mouse mode; incompatible
         1560                                       and can be mistaken for other control
         1561                                       codes. */
         1562                                 break;
         1563                         default:
         1564                                 fprintf(stderr,
         1565                                         "erresc: unknown private set/reset mode %d\n",
         1566                                         *args);
         1567                                 break;
         1568                         }
         1569                 } else {
         1570                         switch (*args) {
         1571                         case 0:  /* Error (IGNORED) */
         1572                                 break;
         1573                         case 2:
         1574                                 xsetmode(set, MODE_KBDLOCK);
         1575                                 break;
         1576                         case 4:  /* IRM -- Insertion-replacement */
         1577                                 MODBIT(term.mode, set, MODE_INSERT);
         1578                                 break;
         1579                         case 12: /* SRM -- Send/Receive */
         1580                                 MODBIT(term.mode, !set, MODE_ECHO);
         1581                                 break;
         1582                         case 20: /* LNM -- Linefeed/new line */
         1583                                 MODBIT(term.mode, set, MODE_CRLF);
         1584                                 break;
         1585                         default:
         1586                                 fprintf(stderr,
         1587                                         "erresc: unknown set/reset mode %d\n",
         1588                                         *args);
         1589                                 break;
         1590                         }
         1591                 }
         1592         }
         1593 }
         1594 
         1595 void
         1596 csihandle(void)
         1597 {
         1598         char buf[40];
         1599         int len;
         1600 
         1601         switch (csiescseq.mode[0]) {
         1602         default:
         1603         unknown:
         1604                 fprintf(stderr, "erresc: unknown csi ");
         1605                 csidump();
         1606                 /* die(""); */
         1607                 break;
         1608         case '@': /* ICH -- Insert <n> blank char */
         1609                 DEFAULT(csiescseq.arg[0], 1);
         1610                 tinsertblank(csiescseq.arg[0]);
         1611                 break;
         1612         case 'A': /* CUU -- Cursor <n> Up */
         1613                 DEFAULT(csiescseq.arg[0], 1);
         1614                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
         1615                 break;
         1616         case 'B': /* CUD -- Cursor <n> Down */
         1617         case 'e': /* VPR --Cursor <n> Down */
         1618                 DEFAULT(csiescseq.arg[0], 1);
         1619                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
         1620                 break;
         1621         case 'i': /* MC -- Media Copy */
         1622                 switch (csiescseq.arg[0]) {
         1623                 case 0:
         1624                         tdump();
         1625                         break;
         1626                 case 1:
         1627                         tdumpline(term.c.y);
         1628                         break;
         1629                 case 2:
         1630                         tdumpsel();
         1631                         break;
         1632                 case 4:
         1633                         term.mode &= ~MODE_PRINT;
         1634                         break;
         1635                 case 5:
         1636                         term.mode |= MODE_PRINT;
         1637                         break;
         1638                 }
         1639                 break;
         1640         case 'c': /* DA -- Device Attributes */
         1641                 if (csiescseq.arg[0] == 0)
         1642                         ttywrite(vtiden, strlen(vtiden), 0);
         1643                 break;
         1644         case 'b': /* REP -- if last char is printable print it <n> more times */
         1645                 DEFAULT(csiescseq.arg[0], 1);
         1646                 if (term.lastc)
         1647                         while (csiescseq.arg[0]-- > 0)
         1648                                 tputc(term.lastc);
         1649                 break;
         1650         case 'C': /* CUF -- Cursor <n> Forward */
         1651         case 'a': /* HPR -- Cursor <n> Forward */
         1652                 DEFAULT(csiescseq.arg[0], 1);
         1653                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
         1654                 break;
         1655         case 'D': /* CUB -- Cursor <n> Backward */
         1656                 DEFAULT(csiescseq.arg[0], 1);
         1657                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
         1658                 break;
         1659         case 'E': /* CNL -- Cursor <n> Down and first col */
         1660                 DEFAULT(csiescseq.arg[0], 1);
         1661                 tmoveto(0, term.c.y+csiescseq.arg[0]);
         1662                 break;
         1663         case 'F': /* CPL -- Cursor <n> Up and first col */
         1664                 DEFAULT(csiescseq.arg[0], 1);
         1665                 tmoveto(0, term.c.y-csiescseq.arg[0]);
         1666                 break;
         1667         case 'g': /* TBC -- Tabulation clear */
         1668                 switch (csiescseq.arg[0]) {
         1669                 case 0: /* clear current tab stop */
         1670                         term.tabs[term.c.x] = 0;
         1671                         break;
         1672                 case 3: /* clear all the tabs */
         1673                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
         1674                         break;
         1675                 default:
         1676                         goto unknown;
         1677                 }
         1678                 break;
         1679         case 'G': /* CHA -- Move to <col> */
         1680         case '`': /* HPA */
         1681                 DEFAULT(csiescseq.arg[0], 1);
         1682                 tmoveto(csiescseq.arg[0]-1, term.c.y);
         1683                 break;
         1684         case 'H': /* CUP -- Move to <row> <col> */
         1685         case 'f': /* HVP */
         1686                 DEFAULT(csiescseq.arg[0], 1);
         1687                 DEFAULT(csiescseq.arg[1], 1);
         1688                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
         1689                 break;
         1690         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
         1691                 DEFAULT(csiescseq.arg[0], 1);
         1692                 tputtab(csiescseq.arg[0]);
         1693                 break;
         1694         case 'J': /* ED -- Clear screen */
         1695                 switch (csiescseq.arg[0]) {
         1696                 case 0: /* below */
         1697                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
         1698                         if (term.c.y < term.row-1) {
         1699                                 tclearregion(0, term.c.y+1, term.col-1,
         1700                                                 term.row-1);
         1701                         }
         1702                         break;
         1703                 case 1: /* above */
         1704                         if (term.c.y > 1)
         1705                                 tclearregion(0, 0, term.col-1, term.c.y-1);
         1706                         tclearregion(0, term.c.y, term.c.x, term.c.y);
         1707                         break;
         1708                 case 2: /* all */
         1709                         tclearregion(0, 0, term.col-1, term.row-1);
         1710                         break;
         1711                 default:
         1712                         goto unknown;
         1713                 }
         1714                 break;
         1715         case 'K': /* EL -- Clear line */
         1716                 switch (csiescseq.arg[0]) {
         1717                 case 0: /* right */
         1718                         tclearregion(term.c.x, term.c.y, term.col-1,
         1719                                         term.c.y);
         1720                         break;
         1721                 case 1: /* left */
         1722                         tclearregion(0, term.c.y, term.c.x, term.c.y);
         1723                         break;
         1724                 case 2: /* all */
         1725                         tclearregion(0, term.c.y, term.col-1, term.c.y);
         1726                         break;
         1727                 }
         1728                 break;
         1729         case 'S': /* SU -- Scroll <n> line up */
         1730                 DEFAULT(csiescseq.arg[0], 1);
         1731                 tscrollup(term.top, csiescseq.arg[0]);
         1732                 break;
         1733         case 'T': /* SD -- Scroll <n> line down */
         1734                 DEFAULT(csiescseq.arg[0], 1);
         1735                 tscrolldown(term.top, csiescseq.arg[0]);
         1736                 break;
         1737         case 'L': /* IL -- Insert <n> blank lines */
         1738                 DEFAULT(csiescseq.arg[0], 1);
         1739                 tinsertblankline(csiescseq.arg[0]);
         1740                 break;
         1741         case 'l': /* RM -- Reset Mode */
         1742                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
         1743                 break;
         1744         case 'M': /* DL -- Delete <n> lines */
         1745                 DEFAULT(csiescseq.arg[0], 1);
         1746                 tdeleteline(csiescseq.arg[0]);
         1747                 break;
         1748         case 'X': /* ECH -- Erase <n> char */
         1749                 DEFAULT(csiescseq.arg[0], 1);
         1750                 tclearregion(term.c.x, term.c.y,
         1751                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
         1752                 break;
         1753         case 'P': /* DCH -- Delete <n> char */
         1754                 DEFAULT(csiescseq.arg[0], 1);
         1755                 tdeletechar(csiescseq.arg[0]);
         1756                 break;
         1757         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
         1758                 DEFAULT(csiescseq.arg[0], 1);
         1759                 tputtab(-csiescseq.arg[0]);
         1760                 break;
         1761         case 'd': /* VPA -- Move to <row> */
         1762                 DEFAULT(csiescseq.arg[0], 1);
         1763                 tmoveato(term.c.x, csiescseq.arg[0]-1);
         1764                 break;
         1765         case 'h': /* SM -- Set terminal mode */
         1766                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
         1767                 break;
         1768         case 'm': /* SGR -- Terminal attribute (color) */
         1769                 tsetattr(csiescseq.arg, csiescseq.narg);
         1770                 break;
         1771         case 'n': /* DSR – Device Status Report (cursor position) */
         1772                 if (csiescseq.arg[0] == 6) {
         1773                         len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
         1774                                         term.c.y+1, term.c.x+1);
         1775                         ttywrite(buf, len, 0);
         1776                 }
         1777                 break;
         1778         case 'r': /* DECSTBM -- Set Scrolling Region */
         1779                 if (csiescseq.priv) {
         1780                         goto unknown;
         1781                 } else {
         1782                         DEFAULT(csiescseq.arg[0], 1);
         1783                         DEFAULT(csiescseq.arg[1], term.row);
         1784                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
         1785                         tmoveato(0, 0);
         1786                 }
         1787                 break;
         1788         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
         1789                 tcursor(CURSOR_SAVE);
         1790                 break;
         1791         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
         1792                 tcursor(CURSOR_LOAD);
         1793                 break;
         1794         case ' ':
         1795                 switch (csiescseq.mode[1]) {
         1796                 case 'q': /* DECSCUSR -- Set Cursor Style */
         1797                         if (xsetcursor(csiescseq.arg[0]))
         1798                                 goto unknown;
         1799                         break;
         1800                 default:
         1801                         goto unknown;
         1802                 }
         1803                 break;
         1804         }
         1805 }
         1806 
         1807 void
         1808 csidump(void)
         1809 {
         1810         size_t i;
         1811         uint c;
         1812 
         1813         fprintf(stderr, "ESC[");
         1814         for (i = 0; i < csiescseq.len; i++) {
         1815                 c = csiescseq.buf[i] & 0xff;
         1816                 if (isprint(c)) {
         1817                         putc(c, stderr);
         1818                 } else if (c == '\n') {
         1819                         fprintf(stderr, "(\\n)");
         1820                 } else if (c == '\r') {
         1821                         fprintf(stderr, "(\\r)");
         1822                 } else if (c == 0x1b) {
         1823                         fprintf(stderr, "(\\e)");
         1824                 } else {
         1825                         fprintf(stderr, "(%02x)", c);
         1826                 }
         1827         }
         1828         putc('\n', stderr);
         1829 }
         1830 
         1831 void
         1832 csireset(void)
         1833 {
         1834         memset(&csiescseq, 0, sizeof(csiescseq));
         1835 }
         1836 
         1837 void
         1838 osc4_color_response(int num)
         1839 {
         1840         int n;
         1841         char buf[32];
         1842         unsigned char r, g, b;
         1843 
         1844         if (xgetcolor(num, &r, &g, &b)) {
         1845                 fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num);
         1846                 return;
         1847         }
         1848 
         1849         n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
         1850                      num, r, r, g, g, b, b);
         1851 
         1852         ttywrite(buf, n, 1);
         1853 }
         1854 
         1855 void
         1856 osc_color_response(int index, int num)
         1857 {
         1858         int n;
         1859         char buf[32];
         1860         unsigned char r, g, b;
         1861 
         1862         if (xgetcolor(index, &r, &g, &b)) {
         1863                 fprintf(stderr, "erresc: failed to fetch osc color %d\n", index);
         1864                 return;
         1865         }
         1866 
         1867         n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
         1868                      num, r, r, g, g, b, b);
         1869 
         1870         ttywrite(buf, n, 1);
         1871 }
         1872 
         1873 void
         1874 strhandle(void)
         1875 {
         1876         char *p = NULL, *dec;
         1877         int j, narg, par;
         1878 
         1879         term.esc &= ~(ESC_STR_END|ESC_STR);
         1880         strparse();
         1881         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
         1882 
         1883         switch (strescseq.type) {
         1884         case ']': /* OSC -- Operating System Command */
         1885                 switch (par) {
         1886                 case 0:
         1887                         if (narg > 1) {
         1888                                 xsettitle(strescseq.args[1]);
         1889                                 xseticontitle(strescseq.args[1]);
         1890                         }
         1891                         return;
         1892                 case 1:
         1893                         if (narg > 1)
         1894                                 xseticontitle(strescseq.args[1]);
         1895                         return;
         1896                 case 2:
         1897                         if (narg > 1)
         1898                                 xsettitle(strescseq.args[1]);
         1899                         return;
         1900                 case 52:
         1901                         if (narg > 2 && allowwindowops) {
         1902                                 dec = base64dec(strescseq.args[2]);
         1903                                 if (dec) {
         1904                                         xsetsel(dec);
         1905                                         xclipcopy();
         1906                                 } else {
         1907                                         fprintf(stderr, "erresc: invalid base64\n");
         1908                                 }
         1909                         }
         1910                         return;
         1911                 case 10:
         1912                         if (narg < 2)
         1913                                 break;
         1914 
         1915                         p = strescseq.args[1];
         1916 
         1917                         if (!strcmp(p, "?"))
         1918                                 osc_color_response(defaultfg, 10);
         1919                         else if (xsetcolorname(defaultfg, p))
         1920                                 fprintf(stderr, "erresc: invalid foreground color: %s\n", p);
         1921                         else
         1922                                 tfulldirt();
         1923                         return;
         1924                 case 11:
         1925                         if (narg < 2)
         1926                                 break;
         1927 
         1928                         p = strescseq.args[1];
         1929 
         1930                         if (!strcmp(p, "?"))
         1931                                 osc_color_response(defaultbg, 11);
         1932                         else if (xsetcolorname(defaultbg, p))
         1933                                 fprintf(stderr, "erresc: invalid background color: %s\n", p);
         1934                         else
         1935                                 tfulldirt();
         1936                         return;
         1937                 case 12:
         1938                         if (narg < 2)
         1939                                 break;
         1940 
         1941                         p = strescseq.args[1];
         1942 
         1943                         if (!strcmp(p, "?"))
         1944                                 osc_color_response(defaultcs, 12);
         1945                         else if (xsetcolorname(defaultcs, p))
         1946                                 fprintf(stderr, "erresc: invalid cursor color: %s\n", p);
         1947                         else
         1948                                 tfulldirt();
         1949                         return;
         1950                 case 4: /* color set */
         1951                         if (narg < 3)
         1952                                 break;
         1953                         p = strescseq.args[2];
         1954                         /* FALLTHROUGH */
         1955                 case 104: /* color reset */
         1956                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
         1957 
         1958                         if (p && !strcmp(p, "?"))
         1959                                 osc4_color_response(j);
         1960                         else if (xsetcolorname(j, p)) {
         1961                                 if (par == 104 && narg <= 1)
         1962                                         return; /* color reset without parameter */
         1963                                 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
         1964                                         j, p ? p : "(null)");
         1965                         } else {
         1966                                 /*
         1967                                  * TODO if defaultbg color is changed, borders
         1968                                  * are dirty
         1969                                  */
         1970                                 tfulldirt();
         1971                         }
         1972                         return;
         1973                 }
         1974                 break;
         1975         case 'k': /* old title set compatibility */
         1976                 xsettitle(strescseq.args[0]);
         1977                 return;
         1978         case 'P': /* DCS -- Device Control String */
         1979         case '_': /* APC -- Application Program Command */
         1980         case '^': /* PM -- Privacy Message */
         1981                 return;
         1982         }
         1983 
         1984         fprintf(stderr, "erresc: unknown str ");
         1985         strdump();
         1986 }
         1987 
         1988 void
         1989 strparse(void)
         1990 {
         1991         int c;
         1992         char *p = strescseq.buf;
         1993 
         1994         strescseq.narg = 0;
         1995         strescseq.buf[strescseq.len] = '\0';
         1996 
         1997         if (*p == '\0')
         1998                 return;
         1999 
         2000         while (strescseq.narg < STR_ARG_SIZ) {
         2001                 strescseq.args[strescseq.narg++] = p;
         2002                 while ((c = *p) != ';' && c != '\0')
         2003                         ++p;
         2004                 if (c == '\0')
         2005                         return;
         2006                 *p++ = '\0';
         2007         }
         2008 }
         2009 
         2010 void
         2011 strdump(void)
         2012 {
         2013         size_t i;
         2014         uint c;
         2015 
         2016         fprintf(stderr, "ESC%c", strescseq.type);
         2017         for (i = 0; i < strescseq.len; i++) {
         2018                 c = strescseq.buf[i] & 0xff;
         2019                 if (c == '\0') {
         2020                         putc('\n', stderr);
         2021                         return;
         2022                 } else if (isprint(c)) {
         2023                         putc(c, stderr);
         2024                 } else if (c == '\n') {
         2025                         fprintf(stderr, "(\\n)");
         2026                 } else if (c == '\r') {
         2027                         fprintf(stderr, "(\\r)");
         2028                 } else if (c == 0x1b) {
         2029                         fprintf(stderr, "(\\e)");
         2030                 } else {
         2031                         fprintf(stderr, "(%02x)", c);
         2032                 }
         2033         }
         2034         fprintf(stderr, "ESC\\\n");
         2035 }
         2036 
         2037 void
         2038 strreset(void)
         2039 {
         2040         strescseq = (STREscape){
         2041                 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
         2042                 .siz = STR_BUF_SIZ,
         2043         };
         2044 }
         2045 
         2046 void
         2047 sendbreak(const Arg *arg)
         2048 {
         2049         if (tcsendbreak(cmdfd, 0))
         2050                 perror("Error sending break");
         2051 }
         2052 
         2053 void
         2054 tprinter(char *s, size_t len)
         2055 {
         2056         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
         2057                 perror("Error writing to output file");
         2058                 close(iofd);
         2059                 iofd = -1;
         2060         }
         2061 }
         2062 
         2063 void
         2064 toggleprinter(const Arg *arg)
         2065 {
         2066         term.mode ^= MODE_PRINT;
         2067 }
         2068 
         2069 void
         2070 printscreen(const Arg *arg)
         2071 {
         2072         tdump();
         2073 }
         2074 
         2075 void
         2076 printsel(const Arg *arg)
         2077 {
         2078         tdumpsel();
         2079 }
         2080 
         2081 void
         2082 tdumpsel(void)
         2083 {
         2084         char *ptr;
         2085 
         2086         if ((ptr = getsel())) {
         2087                 tprinter(ptr, strlen(ptr));
         2088                 free(ptr);
         2089         }
         2090 }
         2091 
         2092 void
         2093 tdumpline(int n)
         2094 {
         2095         char buf[UTF_SIZ];
         2096         const Glyph *bp, *end;
         2097 
         2098         bp = &term.line[n][0];
         2099         end = &bp[MIN(tlinelen(n), term.col) - 1];
         2100         if (bp != end || bp->u != ' ') {
         2101                 for ( ; bp <= end; ++bp)
         2102                         tprinter(buf, utf8encode(bp->u, buf));
         2103         }
         2104         tprinter("\n", 1);
         2105 }
         2106 
         2107 void
         2108 tdump(void)
         2109 {
         2110         int i;
         2111 
         2112         for (i = 0; i < term.row; ++i)
         2113                 tdumpline(i);
         2114 }
         2115 
         2116 void
         2117 tputtab(int n)
         2118 {
         2119         uint x = term.c.x;
         2120 
         2121         if (n > 0) {
         2122                 while (x < term.col && n--)
         2123                         for (++x; x < term.col && !term.tabs[x]; ++x)
         2124                                 /* nothing */ ;
         2125         } else if (n < 0) {
         2126                 while (x > 0 && n++)
         2127                         for (--x; x > 0 && !term.tabs[x]; --x)
         2128                                 /* nothing */ ;
         2129         }
         2130         term.c.x = LIMIT(x, 0, term.col-1);
         2131 }
         2132 
         2133 void
         2134 tdefutf8(char ascii)
         2135 {
         2136         if (ascii == 'G')
         2137                 term.mode |= MODE_UTF8;
         2138         else if (ascii == '@')
         2139                 term.mode &= ~MODE_UTF8;
         2140 }
         2141 
         2142 void
         2143 tdeftran(char ascii)
         2144 {
         2145         static char cs[] = "0B";
         2146         static int vcs[] = {CS_GRAPHIC0, CS_USA};
         2147         char *p;
         2148 
         2149         if ((p = strchr(cs, ascii)) == NULL) {
         2150                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
         2151         } else {
         2152                 term.trantbl[term.icharset] = vcs[p - cs];
         2153         }
         2154 }
         2155 
         2156 void
         2157 tdectest(char c)
         2158 {
         2159         int x, y;
         2160 
         2161         if (c == '8') { /* DEC screen alignment test. */
         2162                 for (x = 0; x < term.col; ++x) {
         2163                         for (y = 0; y < term.row; ++y)
         2164                                 tsetchar('E', &term.c.attr, x, y);
         2165                 }
         2166         }
         2167 }
         2168 
         2169 void
         2170 tstrsequence(uchar c)
         2171 {
         2172         switch (c) {
         2173         case 0x90:   /* DCS -- Device Control String */
         2174                 c = 'P';
         2175                 break;
         2176         case 0x9f:   /* APC -- Application Program Command */
         2177                 c = '_';
         2178                 break;
         2179         case 0x9e:   /* PM -- Privacy Message */
         2180                 c = '^';
         2181                 break;
         2182         case 0x9d:   /* OSC -- Operating System Command */
         2183                 c = ']';
         2184                 break;
         2185         }
         2186         strreset();
         2187         strescseq.type = c;
         2188         term.esc |= ESC_STR;
         2189 }
         2190 
         2191 void
         2192 tcontrolcode(uchar ascii)
         2193 {
         2194         switch (ascii) {
         2195         case '\t':   /* HT */
         2196                 tputtab(1);
         2197                 return;
         2198         case '\b':   /* BS */
         2199                 tmoveto(term.c.x-1, term.c.y);
         2200                 return;
         2201         case '\r':   /* CR */
         2202                 tmoveto(0, term.c.y);
         2203                 return;
         2204         case '\f':   /* LF */
         2205         case '\v':   /* VT */
         2206         case '\n':   /* LF */
         2207                 /* go to first col if the mode is set */
         2208                 tnewline(IS_SET(MODE_CRLF));
         2209                 return;
         2210         case '\a':   /* BEL */
         2211                 if (term.esc & ESC_STR_END) {
         2212                         /* backwards compatibility to xterm */
         2213                         strhandle();
         2214                 } else {
         2215                         xbell();
         2216                 }
         2217                 break;
         2218         case '\033': /* ESC */
         2219                 csireset();
         2220                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
         2221                 term.esc |= ESC_START;
         2222                 return;
         2223         case '\016': /* SO (LS1 -- Locking shift 1) */
         2224         case '\017': /* SI (LS0 -- Locking shift 0) */
         2225                 term.charset = 1 - (ascii - '\016');
         2226                 return;
         2227         case '\032': /* SUB */
         2228                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
         2229                 /* FALLTHROUGH */
         2230         case '\030': /* CAN */
         2231                 csireset();
         2232                 break;
         2233         case '\005': /* ENQ (IGNORED) */
         2234         case '\000': /* NUL (IGNORED) */
         2235         case '\021': /* XON (IGNORED) */
         2236         case '\023': /* XOFF (IGNORED) */
         2237         case 0177:   /* DEL (IGNORED) */
         2238                 return;
         2239         case 0x80:   /* TODO: PAD */
         2240         case 0x81:   /* TODO: HOP */
         2241         case 0x82:   /* TODO: BPH */
         2242         case 0x83:   /* TODO: NBH */
         2243         case 0x84:   /* TODO: IND */
         2244                 break;
         2245         case 0x85:   /* NEL -- Next line */
         2246                 tnewline(1); /* always go to first col */
         2247                 break;
         2248         case 0x86:   /* TODO: SSA */
         2249         case 0x87:   /* TODO: ESA */
         2250                 break;
         2251         case 0x88:   /* HTS -- Horizontal tab stop */
         2252                 term.tabs[term.c.x] = 1;
         2253                 break;
         2254         case 0x89:   /* TODO: HTJ */
         2255         case 0x8a:   /* TODO: VTS */
         2256         case 0x8b:   /* TODO: PLD */
         2257         case 0x8c:   /* TODO: PLU */
         2258         case 0x8d:   /* TODO: RI */
         2259         case 0x8e:   /* TODO: SS2 */
         2260         case 0x8f:   /* TODO: SS3 */
         2261         case 0x91:   /* TODO: PU1 */
         2262         case 0x92:   /* TODO: PU2 */
         2263         case 0x93:   /* TODO: STS */
         2264         case 0x94:   /* TODO: CCH */
         2265         case 0x95:   /* TODO: MW */
         2266         case 0x96:   /* TODO: SPA */
         2267         case 0x97:   /* TODO: EPA */
         2268         case 0x98:   /* TODO: SOS */
         2269         case 0x99:   /* TODO: SGCI */
         2270                 break;
         2271         case 0x9a:   /* DECID -- Identify Terminal */
         2272                 ttywrite(vtiden, strlen(vtiden), 0);
         2273                 break;
         2274         case 0x9b:   /* TODO: CSI */
         2275         case 0x9c:   /* TODO: ST */
         2276                 break;
         2277         case 0x90:   /* DCS -- Device Control String */
         2278         case 0x9d:   /* OSC -- Operating System Command */
         2279         case 0x9e:   /* PM -- Privacy Message */
         2280         case 0x9f:   /* APC -- Application Program Command */
         2281                 tstrsequence(ascii);
         2282                 return;
         2283         }
         2284         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
         2285         term.esc &= ~(ESC_STR_END|ESC_STR);
         2286 }
         2287 
         2288 /*
         2289  * returns 1 when the sequence is finished and it hasn't to read
         2290  * more characters for this sequence, otherwise 0
         2291  */
         2292 int
         2293 eschandle(uchar ascii)
         2294 {
         2295         switch (ascii) {
         2296         case '[':
         2297                 term.esc |= ESC_CSI;
         2298                 return 0;
         2299         case '#':
         2300                 term.esc |= ESC_TEST;
         2301                 return 0;
         2302         case '%':
         2303                 term.esc |= ESC_UTF8;
         2304                 return 0;
         2305         case 'P': /* DCS -- Device Control String */
         2306         case '_': /* APC -- Application Program Command */
         2307         case '^': /* PM -- Privacy Message */
         2308         case ']': /* OSC -- Operating System Command */
         2309         case 'k': /* old title set compatibility */
         2310                 tstrsequence(ascii);
         2311                 return 0;
         2312         case 'n': /* LS2 -- Locking shift 2 */
         2313         case 'o': /* LS3 -- Locking shift 3 */
         2314                 term.charset = 2 + (ascii - 'n');
         2315                 break;
         2316         case '(': /* GZD4 -- set primary charset G0 */
         2317         case ')': /* G1D4 -- set secondary charset G1 */
         2318         case '*': /* G2D4 -- set tertiary charset G2 */
         2319         case '+': /* G3D4 -- set quaternary charset G3 */
         2320                 term.icharset = ascii - '(';
         2321                 term.esc |= ESC_ALTCHARSET;
         2322                 return 0;
         2323         case 'D': /* IND -- Linefeed */
         2324                 if (term.c.y == term.bot) {
         2325                         tscrollup(term.top, 1);
         2326                 } else {
         2327                         tmoveto(term.c.x, term.c.y+1);
         2328                 }
         2329                 break;
         2330         case 'E': /* NEL -- Next line */
         2331                 tnewline(1); /* always go to first col */
         2332                 break;
         2333         case 'H': /* HTS -- Horizontal tab stop */
         2334                 term.tabs[term.c.x] = 1;
         2335                 break;
         2336         case 'M': /* RI -- Reverse index */
         2337                 if (term.c.y == term.top) {
         2338                         tscrolldown(term.top, 1);
         2339                 } else {
         2340                         tmoveto(term.c.x, term.c.y-1);
         2341                 }
         2342                 break;
         2343         case 'Z': /* DECID -- Identify Terminal */
         2344                 ttywrite(vtiden, strlen(vtiden), 0);
         2345                 break;
         2346         case 'c': /* RIS -- Reset to initial state */
         2347                 treset();
         2348                 resettitle();
         2349                 xloadcols();
         2350                 break;
         2351         case '=': /* DECPAM -- Application keypad */
         2352                 xsetmode(1, MODE_APPKEYPAD);
         2353                 break;
         2354         case '>': /* DECPNM -- Normal keypad */
         2355                 xsetmode(0, MODE_APPKEYPAD);
         2356                 break;
         2357         case '7': /* DECSC -- Save Cursor */
         2358                 tcursor(CURSOR_SAVE);
         2359                 break;
         2360         case '8': /* DECRC -- Restore Cursor */
         2361                 tcursor(CURSOR_LOAD);
         2362                 break;
         2363         case '\\': /* ST -- String Terminator */
         2364                 if (term.esc & ESC_STR_END)
         2365                         strhandle();
         2366                 break;
         2367         default:
         2368                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
         2369                         (uchar) ascii, isprint(ascii)? ascii:'.');
         2370                 break;
         2371         }
         2372         return 1;
         2373 }
         2374 
         2375 void
         2376 tputc(Rune u)
         2377 {
         2378         char c[UTF_SIZ];
         2379         int control;
         2380         int width, len;
         2381         Glyph *gp;
         2382 
         2383         control = ISCONTROL(u);
         2384         if (u < 127 || !IS_SET(MODE_UTF8)) {
         2385                 c[0] = u;
         2386                 width = len = 1;
         2387         } else {
         2388                 len = utf8encode(u, c);
         2389                 if (!control && (width = wcwidth(u)) == -1)
         2390                         width = 1;
         2391         }
         2392 
         2393         if (IS_SET(MODE_PRINT))
         2394                 tprinter(c, len);
         2395 
         2396         /*
         2397          * STR sequence must be checked before anything else
         2398          * because it uses all following characters until it
         2399          * receives a ESC, a SUB, a ST or any other C1 control
         2400          * character.
         2401          */
         2402         if (term.esc & ESC_STR) {
         2403                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
         2404                    ISCONTROLC1(u)) {
         2405                         term.esc &= ~(ESC_START|ESC_STR);
         2406                         term.esc |= ESC_STR_END;
         2407                         goto check_control_code;
         2408                 }
         2409 
         2410                 if (strescseq.len+len >= strescseq.siz) {
         2411                         /*
         2412                          * Here is a bug in terminals. If the user never sends
         2413                          * some code to stop the str or esc command, then st
         2414                          * will stop responding. But this is better than
         2415                          * silently failing with unknown characters. At least
         2416                          * then users will report back.
         2417                          *
         2418                          * In the case users ever get fixed, here is the code:
         2419                          */
         2420                         /*
         2421                          * term.esc = 0;
         2422                          * strhandle();
         2423                          */
         2424                         if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
         2425                                 return;
         2426                         strescseq.siz *= 2;
         2427                         strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
         2428                 }
         2429 
         2430                 memmove(&strescseq.buf[strescseq.len], c, len);
         2431                 strescseq.len += len;
         2432                 return;
         2433         }
         2434 
         2435 check_control_code:
         2436         /*
         2437          * Actions of control codes must be performed as soon they arrive
         2438          * because they can be embedded inside a control sequence, and
         2439          * they must not cause conflicts with sequences.
         2440          */
         2441         if (control) {
         2442                 tcontrolcode(u);
         2443                 /*
         2444                  * control codes are not shown ever
         2445                  */
         2446                 if (!term.esc)
         2447                         term.lastc = 0;
         2448                 return;
         2449         } else if (term.esc & ESC_START) {
         2450                 if (term.esc & ESC_CSI) {
         2451                         csiescseq.buf[csiescseq.len++] = u;
         2452                         if (BETWEEN(u, 0x40, 0x7E)
         2453                                         || csiescseq.len >= \
         2454                                         sizeof(csiescseq.buf)-1) {
         2455                                 term.esc = 0;
         2456                                 csiparse();
         2457                                 csihandle();
         2458                         }
         2459                         return;
         2460                 } else if (term.esc & ESC_UTF8) {
         2461                         tdefutf8(u);
         2462                 } else if (term.esc & ESC_ALTCHARSET) {
         2463                         tdeftran(u);
         2464                 } else if (term.esc & ESC_TEST) {
         2465                         tdectest(u);
         2466                 } else {
         2467                         if (!eschandle(u))
         2468                                 return;
         2469                         /* sequence already finished */
         2470                 }
         2471                 term.esc = 0;
         2472                 /*
         2473                  * All characters which form part of a sequence are not
         2474                  * printed
         2475                  */
         2476                 return;
         2477         }
         2478         if (selected(term.c.x, term.c.y))
         2479                 selclear();
         2480 
         2481         gp = &term.line[term.c.y][term.c.x];
         2482         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
         2483                 gp->mode |= ATTR_WRAP;
         2484                 tnewline(1);
         2485                 gp = &term.line[term.c.y][term.c.x];
         2486         }
         2487 
         2488         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
         2489                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
         2490 
         2491         if (term.c.x+width > term.col) {
         2492                 tnewline(1);
         2493                 gp = &term.line[term.c.y][term.c.x];
         2494         }
         2495 
         2496         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
         2497         term.lastc = u;
         2498 
         2499         if (width == 2) {
         2500                 gp->mode |= ATTR_WIDE;
         2501                 if (term.c.x+1 < term.col) {
         2502                         if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
         2503                                 gp[2].u = ' ';
         2504                                 gp[2].mode &= ~ATTR_WDUMMY;
         2505                         }
         2506                         gp[1].u = '\0';
         2507                         gp[1].mode = ATTR_WDUMMY;
         2508                 }
         2509         }
         2510         if (term.c.x+width < term.col) {
         2511                 tmoveto(term.c.x+width, term.c.y);
         2512         } else {
         2513                 term.c.state |= CURSOR_WRAPNEXT;
         2514         }
         2515 }
         2516 
         2517 int
         2518 twrite(const char *buf, int buflen, int show_ctrl)
         2519 {
         2520         int charsize;
         2521         Rune u;
         2522         int n;
         2523 
         2524         for (n = 0; n < buflen; n += charsize) {
         2525                 if (IS_SET(MODE_UTF8)) {
         2526                         /* process a complete utf8 char */
         2527                         charsize = utf8decode(buf + n, &u, buflen - n);
         2528                         if (charsize == 0)
         2529                                 break;
         2530                 } else {
         2531                         u = buf[n] & 0xFF;
         2532                         charsize = 1;
         2533                 }
         2534                 if (show_ctrl && ISCONTROL(u)) {
         2535                         if (u & 0x80) {
         2536                                 u &= 0x7f;
         2537                                 tputc('^');
         2538                                 tputc('[');
         2539                         } else if (u != '\n' && u != '\r' && u != '\t') {
         2540                                 u ^= 0x40;
         2541                                 tputc('^');
         2542                         }
         2543                 }
         2544                 tputc(u);
         2545         }
         2546         return n;
         2547 }
         2548 
         2549 void
         2550 tresize(int col, int row)
         2551 {
         2552         int i;
         2553         int minrow = MIN(row, term.row);
         2554         int mincol = MIN(col, term.col);
         2555         int *bp;
         2556         TCursor c;
         2557 
         2558         if (col < 1 || row < 1) {
         2559                 fprintf(stderr,
         2560                         "tresize: error resizing to %dx%d\n", col, row);
         2561                 return;
         2562         }
         2563 
         2564         /*
         2565          * slide screen to keep cursor where we expect it -
         2566          * tscrollup would work here, but we can optimize to
         2567          * memmove because we're freeing the earlier lines
         2568          */
         2569         for (i = 0; i <= term.c.y - row; i++) {
         2570                 free(term.line[i]);
         2571                 free(term.alt[i]);
         2572         }
         2573         /* ensure that both src and dst are not NULL */
         2574         if (i > 0) {
         2575                 memmove(term.line, term.line + i, row * sizeof(Line));
         2576                 memmove(term.alt, term.alt + i, row * sizeof(Line));
         2577         }
         2578         for (i += row; i < term.row; i++) {
         2579                 free(term.line[i]);
         2580                 free(term.alt[i]);
         2581         }
         2582 
         2583         /* resize to new height */
         2584         term.line = xrealloc(term.line, row * sizeof(Line));
         2585         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
         2586         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
         2587         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
         2588 
         2589         /* resize each row to new width, zero-pad if needed */
         2590         for (i = 0; i < minrow; i++) {
         2591                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
         2592                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
         2593         }
         2594 
         2595         /* allocate any new rows */
         2596         for (/* i = minrow */; i < row; i++) {
         2597                 term.line[i] = xmalloc(col * sizeof(Glyph));
         2598                 term.alt[i] = xmalloc(col * sizeof(Glyph));
         2599         }
         2600         if (col > term.col) {
         2601                 bp = term.tabs + term.col;
         2602 
         2603                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
         2604                 while (--bp > term.tabs && !*bp)
         2605                         /* nothing */ ;
         2606                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
         2607                         *bp = 1;
         2608         }
         2609         /* update terminal size */
         2610         term.col = col;
         2611         term.row = row;
         2612         /* reset scrolling region */
         2613         tsetscroll(0, row-1);
         2614         /* make use of the LIMIT in tmoveto */
         2615         tmoveto(term.c.x, term.c.y);
         2616         /* Clearing both screens (it makes dirty all lines) */
         2617         c = term.c;
         2618         for (i = 0; i < 2; i++) {
         2619                 if (mincol < col && 0 < minrow) {
         2620                         tclearregion(mincol, 0, col - 1, minrow - 1);
         2621                 }
         2622                 if (0 < col && minrow < row) {
         2623                         tclearregion(0, minrow, col - 1, row - 1);
         2624                 }
         2625                 tswapscreen();
         2626                 tcursor(CURSOR_LOAD);
         2627         }
         2628         term.c = c;
         2629 }
         2630 
         2631 void
         2632 resettitle(void)
         2633 {
         2634         xsettitle(NULL);
         2635 }
         2636 
         2637 void
         2638 drawregion(int x1, int y1, int x2, int y2)
         2639 {
         2640         int y;
         2641 
         2642         for (y = y1; y < y2; y++) {
         2643                 if (!term.dirty[y])
         2644                         continue;
         2645 
         2646                 term.dirty[y] = 0;
         2647                 xdrawline(term.line[y], x1, y, x2);
         2648         }
         2649 }
         2650 
         2651 void
         2652 draw(void)
         2653 {
         2654         int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
         2655 
         2656         if (!xstartdraw())
         2657                 return;
         2658 
         2659         /* adjust cursor position */
         2660         LIMIT(term.ocx, 0, term.col-1);
         2661         LIMIT(term.ocy, 0, term.row-1);
         2662         if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
         2663                 term.ocx--;
         2664         if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
         2665                 cx--;
         2666 
         2667         drawregion(0, 0, term.col, term.row);
         2668         xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
         2669                         term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
         2670         term.ocx = cx;
         2671         term.ocy = term.c.y;
         2672         xfinishdraw();
         2673         if (ocx != term.ocx || ocy != term.ocy)
         2674                 xximspot(term.ocx, term.ocy);
         2675 }
         2676 
         2677 void
         2678 redraw(void)
         2679 {
         2680         tfulldirt();
         2681         draw();
         2682 }