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