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