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