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