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