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