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