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