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