]> 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         int sep = ';'; /* colon or semi-colon, but not both */
1136
1137         csiescseq.narg = 0;
1138         if (*p == '?') {
1139                 csiescseq.priv = 1;
1140                 p++;
1141         }
1142
1143         csiescseq.buf[csiescseq.len] = '\0';
1144         while (p < csiescseq.buf+csiescseq.len) {
1145                 np = NULL;
1146                 v = strtol(p, &np, 10);
1147                 if (np == p)
1148                         v = 0;
1149                 if (v == LONG_MAX || v == LONG_MIN)
1150                         v = -1;
1151                 csiescseq.arg[csiescseq.narg++] = v;
1152                 p = np;
1153                 if (sep == ';' && *p == ':')
1154                         sep = ':'; /* allow override to colon once */
1155                 if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
1156                         break;
1157                 p++;
1158         }
1159         csiescseq.mode[0] = *p++;
1160         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1161 }
1162
1163 /* for absolute user moves, when decom is set */
1164 void
1165 tmoveato(int x, int y)
1166 {
1167         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1168 }
1169
1170 void
1171 tmoveto(int x, int y)
1172 {
1173         int miny, maxy;
1174
1175         if (term.c.state & CURSOR_ORIGIN) {
1176                 miny = term.top;
1177                 maxy = term.bot;
1178         } else {
1179                 miny = 0;
1180                 maxy = term.row - 1;
1181         }
1182         term.c.state &= ~CURSOR_WRAPNEXT;
1183         term.c.x = LIMIT(x, 0, term.col-1);
1184         term.c.y = LIMIT(y, miny, maxy);
1185 }
1186
1187 void
1188 tsetchar(Rune u, const Glyph *attr, int x, int y)
1189 {
1190         static const char *vt100_0[62] = { /* 0x41 - 0x7e */
1191                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1192                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1193                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1194                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1195                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1196                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1197                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1198                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1199         };
1200
1201         /*
1202          * The table is proudly stolen from rxvt.
1203          */
1204         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1205            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1206                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1207
1208         if (term.line[y][x].mode & ATTR_WIDE) {
1209                 if (x+1 < term.col) {
1210                         term.line[y][x+1].u = ' ';
1211                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1212                 }
1213         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1214                 term.line[y][x-1].u = ' ';
1215                 term.line[y][x-1].mode &= ~ATTR_WIDE;
1216         }
1217
1218         term.dirty[y] = 1;
1219         term.line[y][x] = *attr;
1220         term.line[y][x].u = u;
1221 }
1222
1223 void
1224 tclearregion(int x1, int y1, int x2, int y2)
1225 {
1226         int x, y, temp;
1227         Glyph *gp;
1228
1229         if (x1 > x2)
1230                 temp = x1, x1 = x2, x2 = temp;
1231         if (y1 > y2)
1232                 temp = y1, y1 = y2, y2 = temp;
1233
1234         LIMIT(x1, 0, term.col-1);
1235         LIMIT(x2, 0, term.col-1);
1236         LIMIT(y1, 0, term.row-1);
1237         LIMIT(y2, 0, term.row-1);
1238
1239         for (y = y1; y <= y2; y++) {
1240                 term.dirty[y] = 1;
1241                 for (x = x1; x <= x2; x++) {
1242                         gp = &term.line[y][x];
1243                         if (selected(x, y))
1244                                 selclear();
1245                         gp->fg = term.c.attr.fg;
1246                         gp->bg = term.c.attr.bg;
1247                         gp->mode = 0;
1248                         gp->u = ' ';
1249                 }
1250         }
1251 }
1252
1253 void
1254 tdeletechar(int n)
1255 {
1256         int dst, src, size;
1257         Glyph *line;
1258
1259         LIMIT(n, 0, term.col - term.c.x);
1260
1261         dst = term.c.x;
1262         src = term.c.x + n;
1263         size = term.col - src;
1264         line = term.line[term.c.y];
1265
1266         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1267         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1268 }
1269
1270 void
1271 tinsertblank(int n)
1272 {
1273         int dst, src, size;
1274         Glyph *line;
1275
1276         LIMIT(n, 0, term.col - term.c.x);
1277
1278         dst = term.c.x + n;
1279         src = term.c.x;
1280         size = term.col - dst;
1281         line = term.line[term.c.y];
1282
1283         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1284         tclearregion(src, term.c.y, dst - 1, term.c.y);
1285 }
1286
1287 void
1288 tinsertblankline(int n)
1289 {
1290         if (BETWEEN(term.c.y, term.top, term.bot))
1291                 tscrolldown(term.c.y, n);
1292 }
1293
1294 void
1295 tdeleteline(int n)
1296 {
1297         if (BETWEEN(term.c.y, term.top, term.bot))
1298                 tscrollup(term.c.y, n);
1299 }
1300
1301 int32_t
1302 tdefcolor(const int *attr, int *npar, int l)
1303 {
1304         int32_t idx = -1;
1305         uint r, g, b;
1306
1307         switch (attr[*npar + 1]) {
1308         case 2: /* direct color in RGB space */
1309                 if (*npar + 4 >= l) {
1310                         fprintf(stderr,
1311                                 "erresc(38): Incorrect number of parameters (%d)\n",
1312                                 *npar);
1313                         break;
1314                 }
1315                 r = attr[*npar + 2];
1316                 g = attr[*npar + 3];
1317                 b = attr[*npar + 4];
1318                 *npar += 4;
1319                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1320                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1321                                 r, g, b);
1322                 else
1323                         idx = TRUECOLOR(r, g, b);
1324                 break;
1325         case 5: /* indexed color */
1326                 if (*npar + 2 >= l) {
1327                         fprintf(stderr,
1328                                 "erresc(38): Incorrect number of parameters (%d)\n",
1329                                 *npar);
1330                         break;
1331                 }
1332                 *npar += 2;
1333                 if (!BETWEEN(attr[*npar], 0, 255))
1334                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1335                 else
1336                         idx = attr[*npar];
1337                 break;
1338         case 0: /* implemented defined (only foreground) */
1339         case 1: /* transparent */
1340         case 3: /* direct color in CMY space */
1341         case 4: /* direct color in CMYK space */
1342         default:
1343                 fprintf(stderr,
1344                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1345                 break;
1346         }
1347
1348         return idx;
1349 }
1350
1351 void
1352 tsetattr(const int *attr, int l)
1353 {
1354         int i;
1355         int32_t idx;
1356
1357         for (i = 0; i < l; i++) {
1358                 switch (attr[i]) {
1359                 case 0:
1360                         term.c.attr.mode &= ~(
1361                                 ATTR_BOLD       |
1362                                 ATTR_FAINT      |
1363                                 ATTR_ITALIC     |
1364                                 ATTR_UNDERLINE  |
1365                                 ATTR_BLINK      |
1366                                 ATTR_REVERSE    |
1367                                 ATTR_INVISIBLE  |
1368                                 ATTR_STRUCK     );
1369                         term.c.attr.fg = defaultfg;
1370                         term.c.attr.bg = defaultbg;
1371                         break;
1372                 case 1:
1373                         term.c.attr.mode |= ATTR_BOLD;
1374                         break;
1375                 case 2:
1376                         term.c.attr.mode |= ATTR_FAINT;
1377                         break;
1378                 case 3:
1379                         term.c.attr.mode |= ATTR_ITALIC;
1380                         break;
1381                 case 4:
1382                         term.c.attr.mode |= ATTR_UNDERLINE;
1383                         break;
1384                 case 5: /* slow blink */
1385                         /* FALLTHROUGH */
1386                 case 6: /* rapid blink */
1387                         term.c.attr.mode |= ATTR_BLINK;
1388                         break;
1389                 case 7:
1390                         term.c.attr.mode |= ATTR_REVERSE;
1391                         break;
1392                 case 8:
1393                         term.c.attr.mode |= ATTR_INVISIBLE;
1394                         break;
1395                 case 9:
1396                         term.c.attr.mode |= ATTR_STRUCK;
1397                         break;
1398                 case 22:
1399                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1400                         break;
1401                 case 23:
1402                         term.c.attr.mode &= ~ATTR_ITALIC;
1403                         break;
1404                 case 24:
1405                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1406                         break;
1407                 case 25:
1408                         term.c.attr.mode &= ~ATTR_BLINK;
1409                         break;
1410                 case 27:
1411                         term.c.attr.mode &= ~ATTR_REVERSE;
1412                         break;
1413                 case 28:
1414                         term.c.attr.mode &= ~ATTR_INVISIBLE;
1415                         break;
1416                 case 29:
1417                         term.c.attr.mode &= ~ATTR_STRUCK;
1418                         break;
1419                 case 38:
1420                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1421                                 term.c.attr.fg = idx;
1422                         break;
1423                 case 39:
1424                         term.c.attr.fg = defaultfg;
1425                         break;
1426                 case 48:
1427                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1428                                 term.c.attr.bg = idx;
1429                         break;
1430                 case 49:
1431                         term.c.attr.bg = defaultbg;
1432                         break;
1433                 default:
1434                         if (BETWEEN(attr[i], 30, 37)) {
1435                                 term.c.attr.fg = attr[i] - 30;
1436                         } else if (BETWEEN(attr[i], 40, 47)) {
1437                                 term.c.attr.bg = attr[i] - 40;
1438                         } else if (BETWEEN(attr[i], 90, 97)) {
1439                                 term.c.attr.fg = attr[i] - 90 + 8;
1440                         } else if (BETWEEN(attr[i], 100, 107)) {
1441                                 term.c.attr.bg = attr[i] - 100 + 8;
1442                         } else {
1443                                 fprintf(stderr,
1444                                         "erresc(default): gfx attr %d unknown\n",
1445                                         attr[i]);
1446                                 csidump();
1447                         }
1448                         break;
1449                 }
1450         }
1451 }
1452
1453 void
1454 tsetscroll(int t, int b)
1455 {
1456         int temp;
1457
1458         LIMIT(t, 0, term.row-1);
1459         LIMIT(b, 0, term.row-1);
1460         if (t > b) {
1461                 temp = t;
1462                 t = b;
1463                 b = temp;
1464         }
1465         term.top = t;
1466         term.bot = b;
1467 }
1468
1469 void
1470 tsetmode(int priv, int set, const int *args, int narg)
1471 {
1472         int alt; const int *lim;
1473
1474         for (lim = args + narg; args < lim; ++args) {
1475                 if (priv) {
1476                         switch (*args) {
1477                         case 1: /* DECCKM -- Cursor key */
1478                                 xsetmode(set, MODE_APPCURSOR);
1479                                 break;
1480                         case 5: /* DECSCNM -- Reverse video */
1481                                 xsetmode(set, MODE_REVERSE);
1482                                 break;
1483                         case 6: /* DECOM -- Origin */
1484                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1485                                 tmoveato(0, 0);
1486                                 break;
1487                         case 7: /* DECAWM -- Auto wrap */
1488                                 MODBIT(term.mode, set, MODE_WRAP);
1489                                 break;
1490                         case 0:  /* Error (IGNORED) */
1491                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1492                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1493                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1494                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1495                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1496                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1497                         case 42: /* DECNRCM -- National characters (IGNORED) */
1498                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1499                                 break;
1500                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1501                                 xsetmode(!set, MODE_HIDE);
1502                                 break;
1503                         case 9:    /* X10 mouse compatibility mode */
1504                                 xsetpointermotion(0);
1505                                 xsetmode(0, MODE_MOUSE);
1506                                 xsetmode(set, MODE_MOUSEX10);
1507                                 break;
1508                         case 1000: /* 1000: report button press */
1509                                 xsetpointermotion(0);
1510                                 xsetmode(0, MODE_MOUSE);
1511                                 xsetmode(set, MODE_MOUSEBTN);
1512                                 break;
1513                         case 1002: /* 1002: report motion on button press */
1514                                 xsetpointermotion(0);
1515                                 xsetmode(0, MODE_MOUSE);
1516                                 xsetmode(set, MODE_MOUSEMOTION);
1517                                 break;
1518                         case 1003: /* 1003: enable all mouse motions */
1519                                 xsetpointermotion(set);
1520                                 xsetmode(0, MODE_MOUSE);
1521                                 xsetmode(set, MODE_MOUSEMANY);
1522                                 break;
1523                         case 1004: /* 1004: send focus events to tty */
1524                                 xsetmode(set, MODE_FOCUS);
1525                                 break;
1526                         case 1006: /* 1006: extended reporting mode */
1527                                 xsetmode(set, MODE_MOUSESGR);
1528                                 break;
1529                         case 1034:
1530                                 xsetmode(set, MODE_8BIT);
1531                                 break;
1532                         case 1049: /* swap screen & set/restore cursor as xterm */
1533                                 if (!allowaltscreen)
1534                                         break;
1535                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1536                                 /* FALLTHROUGH */
1537                         case 47: /* swap screen */
1538                         case 1047:
1539                                 if (!allowaltscreen)
1540                                         break;
1541                                 alt = IS_SET(MODE_ALTSCREEN);
1542                                 if (alt) {
1543                                         tclearregion(0, 0, term.col-1,
1544                                                         term.row-1);
1545                                 }
1546                                 if (set ^ alt) /* set is always 1 or 0 */
1547                                         tswapscreen();
1548                                 if (*args != 1049)
1549                                         break;
1550                                 /* FALLTHROUGH */
1551                         case 1048:
1552                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1553                                 break;
1554                         case 2004: /* 2004: bracketed paste mode */
1555                                 xsetmode(set, MODE_BRCKTPASTE);
1556                                 break;
1557                         /* Not implemented mouse modes. See comments there. */
1558                         case 1001: /* mouse highlight mode; can hang the
1559                                       terminal by design when implemented. */
1560                         case 1005: /* UTF-8 mouse mode; will confuse
1561                                       applications not supporting UTF-8
1562                                       and luit. */
1563                         case 1015: /* urxvt mangled mouse mode; incompatible
1564                                       and can be mistaken for other control
1565                                       codes. */
1566                                 break;
1567                         default:
1568                                 fprintf(stderr,
1569                                         "erresc: unknown private set/reset mode %d\n",
1570                                         *args);
1571                                 break;
1572                         }
1573                 } else {
1574                         switch (*args) {
1575                         case 0:  /* Error (IGNORED) */
1576                                 break;
1577                         case 2:
1578                                 xsetmode(set, MODE_KBDLOCK);
1579                                 break;
1580                         case 4:  /* IRM -- Insertion-replacement */
1581                                 MODBIT(term.mode, set, MODE_INSERT);
1582                                 break;
1583                         case 12: /* SRM -- Send/Receive */
1584                                 MODBIT(term.mode, !set, MODE_ECHO);
1585                                 break;
1586                         case 20: /* LNM -- Linefeed/new line */
1587                                 MODBIT(term.mode, set, MODE_CRLF);
1588                                 break;
1589                         default:
1590                                 fprintf(stderr,
1591                                         "erresc: unknown set/reset mode %d\n",
1592                                         *args);
1593                                 break;
1594                         }
1595                 }
1596         }
1597 }
1598
1599 void
1600 csihandle(void)
1601 {
1602         char buf[40];
1603         int len;
1604
1605         switch (csiescseq.mode[0]) {
1606         default:
1607         unknown:
1608                 fprintf(stderr, "erresc: unknown csi ");
1609                 csidump();
1610                 /* die(""); */
1611                 break;
1612         case '@': /* ICH -- Insert <n> blank char */
1613                 DEFAULT(csiescseq.arg[0], 1);
1614                 tinsertblank(csiescseq.arg[0]);
1615                 break;
1616         case 'A': /* CUU -- Cursor <n> Up */
1617                 DEFAULT(csiescseq.arg[0], 1);
1618                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1619                 break;
1620         case 'B': /* CUD -- Cursor <n> Down */
1621         case 'e': /* VPR --Cursor <n> Down */
1622                 DEFAULT(csiescseq.arg[0], 1);
1623                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1624                 break;
1625         case 'i': /* MC -- Media Copy */
1626                 switch (csiescseq.arg[0]) {
1627                 case 0:
1628                         tdump();
1629                         break;
1630                 case 1:
1631                         tdumpline(term.c.y);
1632                         break;
1633                 case 2:
1634                         tdumpsel();
1635                         break;
1636                 case 4:
1637                         term.mode &= ~MODE_PRINT;
1638                         break;
1639                 case 5:
1640                         term.mode |= MODE_PRINT;
1641                         break;
1642                 }
1643                 break;
1644         case 'c': /* DA -- Device Attributes */
1645                 if (csiescseq.arg[0] == 0)
1646                         ttywrite(vtiden, strlen(vtiden), 0);
1647                 break;
1648         case 'b': /* REP -- if last char is printable print it <n> more times */
1649                 LIMIT(csiescseq.arg[0], 1, 65535);
1650                 if (term.lastc)
1651                         while (csiescseq.arg[0]-- > 0)
1652                                 tputc(term.lastc);
1653                 break;
1654         case 'C': /* CUF -- Cursor <n> Forward */
1655         case 'a': /* HPR -- Cursor <n> Forward */
1656                 DEFAULT(csiescseq.arg[0], 1);
1657                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1658                 break;
1659         case 'D': /* CUB -- Cursor <n> Backward */
1660                 DEFAULT(csiescseq.arg[0], 1);
1661                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1662                 break;
1663         case 'E': /* CNL -- Cursor <n> Down and first col */
1664                 DEFAULT(csiescseq.arg[0], 1);
1665                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1666                 break;
1667         case 'F': /* CPL -- Cursor <n> Up and first col */
1668                 DEFAULT(csiescseq.arg[0], 1);
1669                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1670                 break;
1671         case 'g': /* TBC -- Tabulation clear */
1672                 switch (csiescseq.arg[0]) {
1673                 case 0: /* clear current tab stop */
1674                         term.tabs[term.c.x] = 0;
1675                         break;
1676                 case 3: /* clear all the tabs */
1677                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1678                         break;
1679                 default:
1680                         goto unknown;
1681                 }
1682                 break;
1683         case 'G': /* CHA -- Move to <col> */
1684         case '`': /* HPA */
1685                 DEFAULT(csiescseq.arg[0], 1);
1686                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1687                 break;
1688         case 'H': /* CUP -- Move to <row> <col> */
1689         case 'f': /* HVP */
1690                 DEFAULT(csiescseq.arg[0], 1);
1691                 DEFAULT(csiescseq.arg[1], 1);
1692                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1693                 break;
1694         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1695                 DEFAULT(csiescseq.arg[0], 1);
1696                 tputtab(csiescseq.arg[0]);
1697                 break;
1698         case 'J': /* ED -- Clear screen */
1699                 switch (csiescseq.arg[0]) {
1700                 case 0: /* below */
1701                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1702                         if (term.c.y < term.row-1) {
1703                                 tclearregion(0, term.c.y+1, term.col-1,
1704                                                 term.row-1);
1705                         }
1706                         break;
1707                 case 1: /* above */
1708                         if (term.c.y > 1)
1709                                 tclearregion(0, 0, term.col-1, term.c.y-1);
1710                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1711                         break;
1712                 case 2: /* all */
1713                         tclearregion(0, 0, term.col-1, term.row-1);
1714                         break;
1715                 default:
1716                         goto unknown;
1717                 }
1718                 break;
1719         case 'K': /* EL -- Clear line */
1720                 switch (csiescseq.arg[0]) {
1721                 case 0: /* right */
1722                         tclearregion(term.c.x, term.c.y, term.col-1,
1723                                         term.c.y);
1724                         break;
1725                 case 1: /* left */
1726                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1727                         break;
1728                 case 2: /* all */
1729                         tclearregion(0, term.c.y, term.col-1, term.c.y);
1730                         break;
1731                 }
1732                 break;
1733         case 'S': /* SU -- Scroll <n> line up */
1734                 if (csiescseq.priv) break;
1735                 DEFAULT(csiescseq.arg[0], 1);
1736                 tscrollup(term.top, csiescseq.arg[0]);
1737                 break;
1738         case 'T': /* SD -- Scroll <n> line down */
1739                 DEFAULT(csiescseq.arg[0], 1);
1740                 tscrolldown(term.top, csiescseq.arg[0]);
1741                 break;
1742         case 'L': /* IL -- Insert <n> blank lines */
1743                 DEFAULT(csiescseq.arg[0], 1);
1744                 tinsertblankline(csiescseq.arg[0]);
1745                 break;
1746         case 'l': /* RM -- Reset Mode */
1747                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1748                 break;
1749         case 'M': /* DL -- Delete <n> lines */
1750                 DEFAULT(csiescseq.arg[0], 1);
1751                 tdeleteline(csiescseq.arg[0]);
1752                 break;
1753         case 'X': /* ECH -- Erase <n> char */
1754                 DEFAULT(csiescseq.arg[0], 1);
1755                 tclearregion(term.c.x, term.c.y,
1756                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1757                 break;
1758         case 'P': /* DCH -- Delete <n> char */
1759                 DEFAULT(csiescseq.arg[0], 1);
1760                 tdeletechar(csiescseq.arg[0]);
1761                 break;
1762         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1763                 DEFAULT(csiescseq.arg[0], 1);
1764                 tputtab(-csiescseq.arg[0]);
1765                 break;
1766         case 'd': /* VPA -- Move to <row> */
1767                 DEFAULT(csiescseq.arg[0], 1);
1768                 tmoveato(term.c.x, csiescseq.arg[0]-1);
1769                 break;
1770         case 'h': /* SM -- Set terminal mode */
1771                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1772                 break;
1773         case 'm': /* SGR -- Terminal attribute (color) */
1774                 tsetattr(csiescseq.arg, csiescseq.narg);
1775                 break;
1776         case 'n': /* DSR -- Device Status Report */
1777                 switch (csiescseq.arg[0]) {
1778                 case 5: /* Status Report "OK" `0n` */
1779                         ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
1780                         break;
1781                 case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
1782                         len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1783                                        term.c.y+1, term.c.x+1);
1784                         ttywrite(buf, len, 0);
1785                         break;
1786                 default:
1787                         goto unknown;
1788                 }
1789                 break;
1790         case 'r': /* DECSTBM -- Set Scrolling Region */
1791                 if (csiescseq.priv) {
1792                         goto unknown;
1793                 } else {
1794                         DEFAULT(csiescseq.arg[0], 1);
1795                         DEFAULT(csiescseq.arg[1], term.row);
1796                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1797                         tmoveato(0, 0);
1798                 }
1799                 break;
1800         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1801                 tcursor(CURSOR_SAVE);
1802                 break;
1803         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1804                 tcursor(CURSOR_LOAD);
1805                 break;
1806         case ' ':
1807                 switch (csiescseq.mode[1]) {
1808                 case 'q': /* DECSCUSR -- Set Cursor Style */
1809                         if (xsetcursor(csiescseq.arg[0]))
1810                                 goto unknown;
1811                         break;
1812                 default:
1813                         goto unknown;
1814                 }
1815                 break;
1816         }
1817 }
1818
1819 void
1820 csidump(void)
1821 {
1822         size_t i;
1823         uint c;
1824
1825         fprintf(stderr, "ESC[");
1826         for (i = 0; i < csiescseq.len; i++) {
1827                 c = csiescseq.buf[i] & 0xff;
1828                 if (isprint(c)) {
1829                         putc(c, stderr);
1830                 } else if (c == '\n') {
1831                         fprintf(stderr, "(\\n)");
1832                 } else if (c == '\r') {
1833                         fprintf(stderr, "(\\r)");
1834                 } else if (c == 0x1b) {
1835                         fprintf(stderr, "(\\e)");
1836                 } else {
1837                         fprintf(stderr, "(%02x)", c);
1838                 }
1839         }
1840         putc('\n', stderr);
1841 }
1842
1843 void
1844 csireset(void)
1845 {
1846         memset(&csiescseq, 0, sizeof(csiescseq));
1847 }
1848
1849 void
1850 osc_color_response(int num, int index, int is_osc4)
1851 {
1852         int n;
1853         char buf[32];
1854         unsigned char r, g, b;
1855
1856         if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
1857                 fprintf(stderr, "erresc: failed to fetch %s color %d\n",
1858                         is_osc4 ? "osc4" : "osc",
1859                         is_osc4 ? num : index);
1860                 return;
1861         }
1862
1863         n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1864                      is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
1865         if (n < 0 || n >= sizeof(buf)) {
1866                 fprintf(stderr, "error: %s while printing %s response\n",
1867                         n < 0 ? "snprintf failed" : "truncation occurred",
1868                         is_osc4 ? "osc4" : "osc");
1869         } else {
1870                 ttywrite(buf, n, 1);
1871         }
1872 }
1873
1874 void
1875 strhandle(void)
1876 {
1877         char *p = NULL, *dec;
1878         int j, narg, par;
1879         const struct { int idx; char *str; } osc_table[] = {
1880                 { defaultfg, "foreground" },
1881                 { defaultbg, "background" },
1882                 { defaultcs, "cursor" }
1883         };
1884
1885         term.esc &= ~(ESC_STR_END|ESC_STR);
1886         strparse();
1887         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1888
1889         switch (strescseq.type) {
1890         case ']': /* OSC -- Operating System Command */
1891                 switch (par) {
1892                 case 0:
1893                         if (narg > 1) {
1894                                 xsettitle(strescseq.args[1]);
1895                                 xseticontitle(strescseq.args[1]);
1896                         }
1897                         return;
1898                 case 1:
1899                         if (narg > 1)
1900                                 xseticontitle(strescseq.args[1]);
1901                         return;
1902                 case 2:
1903                         if (narg > 1)
1904                                 xsettitle(strescseq.args[1]);
1905                         return;
1906                 case 52:
1907                         if (narg > 2 && allowwindowops) {
1908                                 dec = base64dec(strescseq.args[2]);
1909                                 if (dec) {
1910                                         xsetsel(dec);
1911                                         xclipcopy();
1912                                 } else {
1913                                         fprintf(stderr, "erresc: invalid base64\n");
1914                                 }
1915                         }
1916                         return;
1917                 case 10:
1918                 case 11:
1919                 case 12:
1920                         if (narg < 2)
1921                                 break;
1922                         p = strescseq.args[1];
1923                         if ((j = par - 10) < 0 || j >= LEN(osc_table))
1924                                 break; /* shouldn't be possible */
1925
1926                         if (!strcmp(p, "?")) {
1927                                 osc_color_response(par, osc_table[j].idx, 0);
1928                         } else if (xsetcolorname(osc_table[j].idx, p)) {
1929                                 fprintf(stderr, "erresc: invalid %s color: %s\n",
1930                                         osc_table[j].str, p);
1931                         } else {
1932                                 tfulldirt();
1933                         }
1934                         return;
1935                 case 4: /* color set */
1936                         if (narg < 3)
1937                                 break;
1938                         p = strescseq.args[2];
1939                         /* FALLTHROUGH */
1940                 case 104: /* color reset */
1941                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1942
1943                         if (p && !strcmp(p, "?")) {
1944                                 osc_color_response(j, 0, 1);
1945                         } else if (xsetcolorname(j, p)) {
1946                                 if (par == 104 && narg <= 1) {
1947                                         xloadcols();
1948                                         return; /* color reset without parameter */
1949                                 }
1950                                 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1951                                         j, p ? p : "(null)");
1952                         } else {
1953                                 /*
1954                                  * TODO if defaultbg color is changed, borders
1955                                  * are dirty
1956                                  */
1957                                 tfulldirt();
1958                         }
1959                         return;
1960                 }
1961                 break;
1962         case 'k': /* old title set compatibility */
1963                 xsettitle(strescseq.args[0]);
1964                 return;
1965         case 'P': /* DCS -- Device Control String */
1966         case '_': /* APC -- Application Program Command */
1967         case '^': /* PM -- Privacy Message */
1968                 return;
1969         }
1970
1971         fprintf(stderr, "erresc: unknown str ");
1972         strdump();
1973 }
1974
1975 void
1976 strparse(void)
1977 {
1978         int c;
1979         char *p = strescseq.buf;
1980
1981         strescseq.narg = 0;
1982         strescseq.buf[strescseq.len] = '\0';
1983
1984         if (*p == '\0')
1985                 return;
1986
1987         while (strescseq.narg < STR_ARG_SIZ) {
1988                 strescseq.args[strescseq.narg++] = p;
1989                 while ((c = *p) != ';' && c != '\0')
1990                         ++p;
1991                 if (c == '\0')
1992                         return;
1993                 *p++ = '\0';
1994         }
1995 }
1996
1997 void
1998 strdump(void)
1999 {
2000         size_t i;
2001         uint c;
2002
2003         fprintf(stderr, "ESC%c", strescseq.type);
2004         for (i = 0; i < strescseq.len; i++) {
2005                 c = strescseq.buf[i] & 0xff;
2006                 if (c == '\0') {
2007                         putc('\n', stderr);
2008                         return;
2009                 } else if (isprint(c)) {
2010                         putc(c, stderr);
2011                 } else if (c == '\n') {
2012                         fprintf(stderr, "(\\n)");
2013                 } else if (c == '\r') {
2014                         fprintf(stderr, "(\\r)");
2015                 } else if (c == 0x1b) {
2016                         fprintf(stderr, "(\\e)");
2017                 } else {
2018                         fprintf(stderr, "(%02x)", c);
2019                 }
2020         }
2021         fprintf(stderr, "ESC\\\n");
2022 }
2023
2024 void
2025 strreset(void)
2026 {
2027         strescseq = (STREscape){
2028                 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
2029                 .siz = STR_BUF_SIZ,
2030         };
2031 }
2032
2033 void
2034 sendbreak(const Arg *arg)
2035 {
2036         if (tcsendbreak(cmdfd, 0))
2037                 perror("Error sending break");
2038 }
2039
2040 void
2041 tprinter(char *s, size_t len)
2042 {
2043         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2044                 perror("Error writing to output file");
2045                 close(iofd);
2046                 iofd = -1;
2047         }
2048 }
2049
2050 void
2051 toggleprinter(const Arg *arg)
2052 {
2053         term.mode ^= MODE_PRINT;
2054 }
2055
2056 void
2057 printscreen(const Arg *arg)
2058 {
2059         tdump();
2060 }
2061
2062 void
2063 printsel(const Arg *arg)
2064 {
2065         tdumpsel();
2066 }
2067
2068 void
2069 tdumpsel(void)
2070 {
2071         char *ptr;
2072
2073         if ((ptr = getsel())) {
2074                 tprinter(ptr, strlen(ptr));
2075                 free(ptr);
2076         }
2077 }
2078
2079 void
2080 tdumpline(int n)
2081 {
2082         char buf[UTF_SIZ];
2083         const Glyph *bp, *end;
2084
2085         bp = &term.line[n][0];
2086         end = &bp[MIN(tlinelen(n), term.col) - 1];
2087         if (bp != end || bp->u != ' ') {
2088                 for ( ; bp <= end; ++bp)
2089                         tprinter(buf, utf8encode(bp->u, buf));
2090         }
2091         tprinter("\n", 1);
2092 }
2093
2094 void
2095 tdump(void)
2096 {
2097         int i;
2098
2099         for (i = 0; i < term.row; ++i)
2100                 tdumpline(i);
2101 }
2102
2103 void
2104 tputtab(int n)
2105 {
2106         uint x = term.c.x;
2107
2108         if (n > 0) {
2109                 while (x < term.col && n--)
2110                         for (++x; x < term.col && !term.tabs[x]; ++x)
2111                                 /* nothing */ ;
2112         } else if (n < 0) {
2113                 while (x > 0 && n++)
2114                         for (--x; x > 0 && !term.tabs[x]; --x)
2115                                 /* nothing */ ;
2116         }
2117         term.c.x = LIMIT(x, 0, term.col-1);
2118 }
2119
2120 void
2121 tdefutf8(char ascii)
2122 {
2123         if (ascii == 'G')
2124                 term.mode |= MODE_UTF8;
2125         else if (ascii == '@')
2126                 term.mode &= ~MODE_UTF8;
2127 }
2128
2129 void
2130 tdeftran(char ascii)
2131 {
2132         static char cs[] = "0B";
2133         static int vcs[] = {CS_GRAPHIC0, CS_USA};
2134         char *p;
2135
2136         if ((p = strchr(cs, ascii)) == NULL) {
2137                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2138         } else {
2139                 term.trantbl[term.icharset] = vcs[p - cs];
2140         }
2141 }
2142
2143 void
2144 tdectest(char c)
2145 {
2146         int x, y;
2147
2148         if (c == '8') { /* DEC screen alignment test. */
2149                 for (x = 0; x < term.col; ++x) {
2150                         for (y = 0; y < term.row; ++y)
2151                                 tsetchar('E', &term.c.attr, x, y);
2152                 }
2153         }
2154 }
2155
2156 void
2157 tstrsequence(uchar c)
2158 {
2159         switch (c) {
2160         case 0x90:   /* DCS -- Device Control String */
2161                 c = 'P';
2162                 break;
2163         case 0x9f:   /* APC -- Application Program Command */
2164                 c = '_';
2165                 break;
2166         case 0x9e:   /* PM -- Privacy Message */
2167                 c = '^';
2168                 break;
2169         case 0x9d:   /* OSC -- Operating System Command */
2170                 c = ']';
2171                 break;
2172         }
2173         strreset();
2174         strescseq.type = c;
2175         term.esc |= ESC_STR;
2176 }
2177
2178 void
2179 tcontrolcode(uchar ascii)
2180 {
2181         switch (ascii) {
2182         case '\t':   /* HT */
2183                 tputtab(1);
2184                 return;
2185         case '\b':   /* BS */
2186                 tmoveto(term.c.x-1, term.c.y);
2187                 return;
2188         case '\r':   /* CR */
2189                 tmoveto(0, term.c.y);
2190                 return;
2191         case '\f':   /* LF */
2192         case '\v':   /* VT */
2193         case '\n':   /* LF */
2194                 /* go to first col if the mode is set */
2195                 tnewline(IS_SET(MODE_CRLF));
2196                 return;
2197         case '\a':   /* BEL */
2198                 if (term.esc & ESC_STR_END) {
2199                         /* backwards compatibility to xterm */
2200                         strhandle();
2201                 } else {
2202                         xbell();
2203                 }
2204                 break;
2205         case '\033': /* ESC */
2206                 csireset();
2207                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2208                 term.esc |= ESC_START;
2209                 return;
2210         case '\016': /* SO (LS1 -- Locking shift 1) */
2211         case '\017': /* SI (LS0 -- Locking shift 0) */
2212                 term.charset = 1 - (ascii - '\016');
2213                 return;
2214         case '\032': /* SUB */
2215                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2216                 /* FALLTHROUGH */
2217         case '\030': /* CAN */
2218                 csireset();
2219                 break;
2220         case '\005': /* ENQ (IGNORED) */
2221         case '\000': /* NUL (IGNORED) */
2222         case '\021': /* XON (IGNORED) */
2223         case '\023': /* XOFF (IGNORED) */
2224         case 0177:   /* DEL (IGNORED) */
2225                 return;
2226         case 0x80:   /* TODO: PAD */
2227         case 0x81:   /* TODO: HOP */
2228         case 0x82:   /* TODO: BPH */
2229         case 0x83:   /* TODO: NBH */
2230         case 0x84:   /* TODO: IND */
2231                 break;
2232         case 0x85:   /* NEL -- Next line */
2233                 tnewline(1); /* always go to first col */
2234                 break;
2235         case 0x86:   /* TODO: SSA */
2236         case 0x87:   /* TODO: ESA */
2237                 break;
2238         case 0x88:   /* HTS -- Horizontal tab stop */
2239                 term.tabs[term.c.x] = 1;
2240                 break;
2241         case 0x89:   /* TODO: HTJ */
2242         case 0x8a:   /* TODO: VTS */
2243         case 0x8b:   /* TODO: PLD */
2244         case 0x8c:   /* TODO: PLU */
2245         case 0x8d:   /* TODO: RI */
2246         case 0x8e:   /* TODO: SS2 */
2247         case 0x8f:   /* TODO: SS3 */
2248         case 0x91:   /* TODO: PU1 */
2249         case 0x92:   /* TODO: PU2 */
2250         case 0x93:   /* TODO: STS */
2251         case 0x94:   /* TODO: CCH */
2252         case 0x95:   /* TODO: MW */
2253         case 0x96:   /* TODO: SPA */
2254         case 0x97:   /* TODO: EPA */
2255         case 0x98:   /* TODO: SOS */
2256         case 0x99:   /* TODO: SGCI */
2257                 break;
2258         case 0x9a:   /* DECID -- Identify Terminal */
2259                 ttywrite(vtiden, strlen(vtiden), 0);
2260                 break;
2261         case 0x9b:   /* TODO: CSI */
2262         case 0x9c:   /* TODO: ST */
2263                 break;
2264         case 0x90:   /* DCS -- Device Control String */
2265         case 0x9d:   /* OSC -- Operating System Command */
2266         case 0x9e:   /* PM -- Privacy Message */
2267         case 0x9f:   /* APC -- Application Program Command */
2268                 tstrsequence(ascii);
2269                 return;
2270         }
2271         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2272         term.esc &= ~(ESC_STR_END|ESC_STR);
2273 }
2274
2275 /*
2276  * returns 1 when the sequence is finished and it hasn't to read
2277  * more characters for this sequence, otherwise 0
2278  */
2279 int
2280 eschandle(uchar ascii)
2281 {
2282         switch (ascii) {
2283         case '[':
2284                 term.esc |= ESC_CSI;
2285                 return 0;
2286         case '#':
2287                 term.esc |= ESC_TEST;
2288                 return 0;
2289         case '%':
2290                 term.esc |= ESC_UTF8;
2291                 return 0;
2292         case 'P': /* DCS -- Device Control String */
2293         case '_': /* APC -- Application Program Command */
2294         case '^': /* PM -- Privacy Message */
2295         case ']': /* OSC -- Operating System Command */
2296         case 'k': /* old title set compatibility */
2297                 tstrsequence(ascii);
2298                 return 0;
2299         case 'n': /* LS2 -- Locking shift 2 */
2300         case 'o': /* LS3 -- Locking shift 3 */
2301                 term.charset = 2 + (ascii - 'n');
2302                 break;
2303         case '(': /* GZD4 -- set primary charset G0 */
2304         case ')': /* G1D4 -- set secondary charset G1 */
2305         case '*': /* G2D4 -- set tertiary charset G2 */
2306         case '+': /* G3D4 -- set quaternary charset G3 */
2307                 term.icharset = ascii - '(';
2308                 term.esc |= ESC_ALTCHARSET;
2309                 return 0;
2310         case 'D': /* IND -- Linefeed */
2311                 if (term.c.y == term.bot) {
2312                         tscrollup(term.top, 1);
2313                 } else {
2314                         tmoveto(term.c.x, term.c.y+1);
2315                 }
2316                 break;
2317         case 'E': /* NEL -- Next line */
2318                 tnewline(1); /* always go to first col */
2319                 break;
2320         case 'H': /* HTS -- Horizontal tab stop */
2321                 term.tabs[term.c.x] = 1;
2322                 break;
2323         case 'M': /* RI -- Reverse index */
2324                 if (term.c.y == term.top) {
2325                         tscrolldown(term.top, 1);
2326                 } else {
2327                         tmoveto(term.c.x, term.c.y-1);
2328                 }
2329                 break;
2330         case 'Z': /* DECID -- Identify Terminal */
2331                 ttywrite(vtiden, strlen(vtiden), 0);
2332                 break;
2333         case 'c': /* RIS -- Reset to initial state */
2334                 treset();
2335                 resettitle();
2336                 xloadcols();
2337                 xsetmode(0, MODE_HIDE);
2338                 break;
2339         case '=': /* DECPAM -- Application keypad */
2340                 xsetmode(1, MODE_APPKEYPAD);
2341                 break;
2342         case '>': /* DECPNM -- Normal keypad */
2343                 xsetmode(0, MODE_APPKEYPAD);
2344                 break;
2345         case '7': /* DECSC -- Save Cursor */
2346                 tcursor(CURSOR_SAVE);
2347                 break;
2348         case '8': /* DECRC -- Restore Cursor */
2349                 tcursor(CURSOR_LOAD);
2350                 break;
2351         case '\\': /* ST -- String Terminator */
2352                 if (term.esc & ESC_STR_END)
2353                         strhandle();
2354                 break;
2355         default:
2356                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2357                         (uchar) ascii, isprint(ascii)? ascii:'.');
2358                 break;
2359         }
2360         return 1;
2361 }
2362
2363 void
2364 tputc(Rune u)
2365 {
2366         char c[UTF_SIZ];
2367         int control;
2368         int width, len;
2369         Glyph *gp;
2370
2371         control = ISCONTROL(u);
2372         if (u < 127 || !IS_SET(MODE_UTF8)) {
2373                 c[0] = u;
2374                 width = len = 1;
2375         } else {
2376                 len = utf8encode(u, c);
2377                 if (!control && (width = wcwidth(u)) == -1)
2378                         width = 1;
2379         }
2380
2381         if (IS_SET(MODE_PRINT))
2382                 tprinter(c, len);
2383
2384         /*
2385          * STR sequence must be checked before anything else
2386          * because it uses all following characters until it
2387          * receives a ESC, a SUB, a ST or any other C1 control
2388          * character.
2389          */
2390         if (term.esc & ESC_STR) {
2391                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2392                    ISCONTROLC1(u)) {
2393                         term.esc &= ~(ESC_START|ESC_STR);
2394                         term.esc |= ESC_STR_END;
2395                         goto check_control_code;
2396                 }
2397
2398                 if (strescseq.len+len >= strescseq.siz) {
2399                         /*
2400                          * Here is a bug in terminals. If the user never sends
2401                          * some code to stop the str or esc command, then st
2402                          * will stop responding. But this is better than
2403                          * silently failing with unknown characters. At least
2404                          * then users will report back.
2405                          *
2406                          * In the case users ever get fixed, here is the code:
2407                          */
2408                         /*
2409                          * term.esc = 0;
2410                          * strhandle();
2411                          */
2412                         if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2413                                 return;
2414                         strescseq.siz *= 2;
2415                         strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2416                 }
2417
2418                 memmove(&strescseq.buf[strescseq.len], c, len);
2419                 strescseq.len += len;
2420                 return;
2421         }
2422
2423 check_control_code:
2424         /*
2425          * Actions of control codes must be performed as soon they arrive
2426          * because they can be embedded inside a control sequence, and
2427          * they must not cause conflicts with sequences.
2428          */
2429         if (control) {
2430                 /* in UTF-8 mode ignore handling C1 control characters */
2431                 if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
2432                         return;
2433                 tcontrolcode(u);
2434                 /*
2435                  * control codes are not shown ever
2436                  */
2437                 if (!term.esc)
2438                         term.lastc = 0;
2439                 return;
2440         } else if (term.esc & ESC_START) {
2441                 if (term.esc & ESC_CSI) {
2442                         csiescseq.buf[csiescseq.len++] = u;
2443                         if (BETWEEN(u, 0x40, 0x7E)
2444                                         || csiescseq.len >= \
2445                                         sizeof(csiescseq.buf)-1) {
2446                                 term.esc = 0;
2447                                 csiparse();
2448                                 csihandle();
2449                         }
2450                         return;
2451                 } else if (term.esc & ESC_UTF8) {
2452                         tdefutf8(u);
2453                 } else if (term.esc & ESC_ALTCHARSET) {
2454                         tdeftran(u);
2455                 } else if (term.esc & ESC_TEST) {
2456                         tdectest(u);
2457                 } else {
2458                         if (!eschandle(u))
2459                                 return;
2460                         /* sequence already finished */
2461                 }
2462                 term.esc = 0;
2463                 /*
2464                  * All characters which form part of a sequence are not
2465                  * printed
2466                  */
2467                 return;
2468         }
2469         if (selected(term.c.x, term.c.y))
2470                 selclear();
2471
2472         gp = &term.line[term.c.y][term.c.x];
2473         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2474                 gp->mode |= ATTR_WRAP;
2475                 tnewline(1);
2476                 gp = &term.line[term.c.y][term.c.x];
2477         }
2478
2479         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
2480                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2481                 gp->mode &= ~ATTR_WIDE;
2482         }
2483
2484         if (term.c.x+width > term.col) {
2485                 if (IS_SET(MODE_WRAP))
2486                         tnewline(1);
2487                 else
2488                         tmoveto(term.col - width, term.c.y);
2489                 gp = &term.line[term.c.y][term.c.x];
2490         }
2491
2492         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2493         term.lastc = u;
2494
2495         if (width == 2) {
2496                 gp->mode |= ATTR_WIDE;
2497                 if (term.c.x+1 < term.col) {
2498                         if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
2499                                 gp[2].u = ' ';
2500                                 gp[2].mode &= ~ATTR_WDUMMY;
2501                         }
2502                         gp[1].u = '\0';
2503                         gp[1].mode = ATTR_WDUMMY;
2504                 }
2505         }
2506         if (term.c.x+width < term.col) {
2507                 tmoveto(term.c.x+width, term.c.y);
2508         } else {
2509                 term.c.state |= CURSOR_WRAPNEXT;
2510         }
2511 }
2512
2513 int
2514 twrite(const char *buf, int buflen, int show_ctrl)
2515 {
2516         int charsize;
2517         Rune u;
2518         int n;
2519
2520         for (n = 0; n < buflen; n += charsize) {
2521                 if (IS_SET(MODE_UTF8)) {
2522                         /* process a complete utf8 char */
2523                         charsize = utf8decode(buf + n, &u, buflen - n);
2524                         if (charsize == 0)
2525                                 break;
2526                 } else {
2527                         u = buf[n] & 0xFF;
2528                         charsize = 1;
2529                 }
2530                 if (show_ctrl && ISCONTROL(u)) {
2531                         if (u & 0x80) {
2532                                 u &= 0x7f;
2533                                 tputc('^');
2534                                 tputc('[');
2535                         } else if (u != '\n' && u != '\r' && u != '\t') {
2536                                 u ^= 0x40;
2537                                 tputc('^');
2538                         }
2539                 }
2540                 tputc(u);
2541         }
2542         return n;
2543 }
2544
2545 void
2546 tresize(int col, int row)
2547 {
2548         int i;
2549         int minrow = MIN(row, term.row);
2550         int mincol = MIN(col, term.col);
2551         int *bp;
2552         TCursor c;
2553
2554         if (col < 1 || row < 1) {
2555                 fprintf(stderr,
2556                         "tresize: error resizing to %dx%d\n", col, row);
2557                 return;
2558         }
2559
2560         /*
2561          * slide screen to keep cursor where we expect it -
2562          * tscrollup would work here, but we can optimize to
2563          * memmove because we're freeing the earlier lines
2564          */
2565         for (i = 0; i <= term.c.y - row; i++) {
2566                 free(term.line[i]);
2567                 free(term.alt[i]);
2568         }
2569         /* ensure that both src and dst are not NULL */
2570         if (i > 0) {
2571                 memmove(term.line, term.line + i, row * sizeof(Line));
2572                 memmove(term.alt, term.alt + i, row * sizeof(Line));
2573         }
2574         for (i += row; i < term.row; i++) {
2575                 free(term.line[i]);
2576                 free(term.alt[i]);
2577         }
2578
2579         /* resize to new height */
2580         term.line = xrealloc(term.line, row * sizeof(Line));
2581         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2582         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2583         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2584
2585         /* resize each row to new width, zero-pad if needed */
2586         for (i = 0; i < minrow; i++) {
2587                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2588                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2589         }
2590
2591         /* allocate any new rows */
2592         for (/* i = minrow */; i < row; i++) {
2593                 term.line[i] = xmalloc(col * sizeof(Glyph));
2594                 term.alt[i] = xmalloc(col * sizeof(Glyph));
2595         }
2596         if (col > term.col) {
2597                 bp = term.tabs + term.col;
2598
2599                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2600                 while (--bp > term.tabs && !*bp)
2601                         /* nothing */ ;
2602                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2603                         *bp = 1;
2604         }
2605         /* update terminal size */
2606         term.col = col;
2607         term.row = row;
2608         /* reset scrolling region */
2609         tsetscroll(0, row-1);
2610         /* make use of the LIMIT in tmoveto */
2611         tmoveto(term.c.x, term.c.y);
2612         /* Clearing both screens (it makes dirty all lines) */
2613         c = term.c;
2614         for (i = 0; i < 2; i++) {
2615                 if (mincol < col && 0 < minrow) {
2616                         tclearregion(mincol, 0, col - 1, minrow - 1);
2617                 }
2618                 if (0 < col && minrow < row) {
2619                         tclearregion(0, minrow, col - 1, row - 1);
2620                 }
2621                 tswapscreen();
2622                 tcursor(CURSOR_LOAD);
2623         }
2624         term.c = c;
2625 }
2626
2627 void
2628 resettitle(void)
2629 {
2630         xsettitle(NULL);
2631 }
2632
2633 void
2634 drawregion(int x1, int y1, int x2, int y2)
2635 {
2636         int y;
2637
2638         for (y = y1; y < y2; y++) {
2639                 if (!term.dirty[y])
2640                         continue;
2641
2642                 term.dirty[y] = 0;
2643                 xdrawline(term.line[y], x1, y, x2);
2644         }
2645 }
2646
2647 void
2648 draw(void)
2649 {
2650         int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2651
2652         if (!xstartdraw())
2653                 return;
2654
2655         /* adjust cursor position */
2656         LIMIT(term.ocx, 0, term.col-1);
2657         LIMIT(term.ocy, 0, term.row-1);
2658         if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2659                 term.ocx--;
2660         if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2661                 cx--;
2662
2663         drawregion(0, 0, term.col, term.row);
2664         xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2665                         term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2666         term.ocx = cx;
2667         term.ocy = term.c.y;
2668         xfinishdraw();
2669         if (ocx != term.ocx || ocy != term.ocy)
2670                 xximspot(term.ocx, term.ocy);
2671 }
2672
2673 void
2674 redraw(void)
2675 {
2676         tfulldirt();
2677         draw();
2678 }