suckless

my configuration for dwm, st, dmenu.
git clone https://s.sonu.ch/~rei/suckless.git
Log | Files | Refs | README

st.c (57522B)


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