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