st-noxz

[fork] suckless st - personal fork
git clone https://noxz.tech/git/st-noxz.git
Log | Files | README | LICENSE

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