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