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