st

custom st build (upstream ~> git://st.suckless.org/st)
Log | Files | Refs | README | LICENSE

st.c (57226B)


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