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