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