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