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