suckless

my personal suckless patches and customisation (dwm, st, dmenu).
Log | Files | Refs | README

st.c (60955B)


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