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