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