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