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