st

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