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