st

Patched version of https://st.suckless.org/
git clone https://git.inz.fi/st/
Log | Files | Refs | README | LICENSE

st.c (61953B)


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