sistema_progs

Programas para customizar o meu entorno de traballo nos meus equipos persoais
Log | Files | Refs

st.c.bak (57775B)


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