st

st fork
git clone git://git.rr3.xyz/st
Log | Files | Refs | README | LICENSE

st.c (51516B)


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