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