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