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