nixos-dotfiles

nixos-dotfiles

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