st

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

st.c (64176B)


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