]> git.armaanb.net Git - st.git/blob - st.c
Call xsetenv() in main process instead of child
[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 \"$WINDOWID\" -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
710         signal(SIGCHLD, SIG_DFL);
711         signal(SIGHUP, SIG_DFL);
712         signal(SIGINT, SIG_DFL);
713         signal(SIGQUIT, SIG_DFL);
714         signal(SIGTERM, SIG_DFL);
715         signal(SIGALRM, SIG_DFL);
716
717         execvp(prog, args);
718         _exit(1);
719 }
720
721 void
722 sigchld(int a)
723 {
724         int stat;
725         pid_t p;
726
727         if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
728                 die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
729
730         if (pid != p)
731                 return;
732
733         if (!WIFEXITED(stat) || WEXITSTATUS(stat))
734                 die("child finished with error '%d'\n", stat);
735         exit(0);
736 }
737
738
739 void
740 stty(void)
741 {
742         char cmd[_POSIX_ARG_MAX], **p, *q, *s;
743         size_t n, siz;
744
745         if ((n = strlen(stty_args)) > sizeof(cmd)-1)
746                 die("incorrect stty parameters\n");
747         memcpy(cmd, stty_args, n);
748         q = cmd + n;
749         siz = sizeof(cmd) - n;
750         for (p = opt_cmd; p && (s = *p); ++p) {
751                 if ((n = strlen(s)) > siz-1)
752                         die("stty parameter length too long\n");
753                 *q++ = ' ';
754                 memcpy(q, s, n);
755                 q += n;
756                 siz -= n + 1;
757         }
758         *q = '\0';
759         if (system(cmd) != 0)
760             perror("Couldn't call stty");
761 }
762
763 void
764 ttynew(void)
765 {
766         int m, s;
767         struct winsize w = {term.row, term.col, 0, 0};
768
769         if (opt_io) {
770                 term.mode |= MODE_PRINT;
771                 iofd = (!strcmp(opt_io, "-")) ?
772                           1 : open(opt_io, O_WRONLY | O_CREAT, 0666);
773                 if (iofd < 0) {
774                         fprintf(stderr, "Error opening %s:%s\n",
775                                 opt_io, strerror(errno));
776                 }
777         }
778
779         if (opt_line) {
780                 if ((cmdfd = open(opt_line, O_RDWR)) < 0)
781                         die("open line failed: %s\n", strerror(errno));
782                 dup2(cmdfd, 0);
783                 stty();
784                 return;
785         }
786
787         /* seems to work fine on linux, openbsd and freebsd */
788         if (openpty(&m, &s, NULL, NULL, &w) < 0)
789                 die("openpty failed: %s\n", strerror(errno));
790
791         switch (pid = fork()) {
792         case -1:
793                 die("fork failed\n");
794                 break;
795         case 0:
796                 close(iofd);
797                 setsid(); /* create a new process group */
798                 dup2(s, 0);
799                 dup2(s, 1);
800                 dup2(s, 2);
801                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
802                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
803                 close(s);
804                 close(m);
805                 execsh();
806                 break;
807         default:
808                 close(s);
809                 cmdfd = m;
810                 signal(SIGCHLD, sigchld);
811                 break;
812         }
813 }
814
815 size_t
816 ttyread(void)
817 {
818         static char buf[BUFSIZ];
819         static int buflen = 0;
820         char *ptr;
821         int charsize; /* size of utf8 char in bytes */
822         Rune unicodep;
823         int ret;
824
825         /* append read bytes to unprocessed bytes */
826         if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
827                 die("Couldn't read from shell: %s\n", strerror(errno));
828
829         buflen += ret;
830         ptr = buf;
831
832         for (;;) {
833                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
834                         /* process a complete utf8 char */
835                         charsize = utf8decode(ptr, &unicodep, buflen);
836                         if (charsize == 0)
837                                 break;
838                         tputc(unicodep);
839                         ptr += charsize;
840                         buflen -= charsize;
841
842                 } else {
843                         if (buflen <= 0)
844                                 break;
845                         tputc(*ptr++ & 0xFF);
846                         buflen--;
847                 }
848         }
849         /* keep any uncomplete utf8 char for the next call */
850         if (buflen > 0)
851                 memmove(buf, ptr, buflen);
852
853         return ret;
854 }
855
856 void
857 ttywrite(const char *s, size_t n)
858 {
859         fd_set wfd, rfd;
860         ssize_t r;
861         size_t lim = 256;
862
863         /*
864          * Remember that we are using a pty, which might be a modem line.
865          * Writing too much will clog the line. That's why we are doing this
866          * dance.
867          * FIXME: Migrate the world to Plan 9.
868          */
869         while (n > 0) {
870                 FD_ZERO(&wfd);
871                 FD_ZERO(&rfd);
872                 FD_SET(cmdfd, &wfd);
873                 FD_SET(cmdfd, &rfd);
874
875                 /* Check if we can write. */
876                 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
877                         if (errno == EINTR)
878                                 continue;
879                         die("select failed: %s\n", strerror(errno));
880                 }
881                 if (FD_ISSET(cmdfd, &wfd)) {
882                         /*
883                          * Only write the bytes written by ttywrite() or the
884                          * default of 256. This seems to be a reasonable value
885                          * for a serial line. Bigger values might clog the I/O.
886                          */
887                         if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
888                                 goto write_error;
889                         if (r < n) {
890                                 /*
891                                  * We weren't able to write out everything.
892                                  * This means the buffer is getting full
893                                  * again. Empty it.
894                                  */
895                                 if (n < lim)
896                                         lim = ttyread();
897                                 n -= r;
898                                 s += r;
899                         } else {
900                                 /* All bytes have been written. */
901                                 break;
902                         }
903                 }
904                 if (FD_ISSET(cmdfd, &rfd))
905                         lim = ttyread();
906         }
907         return;
908
909 write_error:
910         die("write error on tty: %s\n", strerror(errno));
911 }
912
913 void
914 ttysend(char *s, size_t n)
915 {
916         int len;
917         char *t, *lim;
918         Rune u;
919
920         ttywrite(s, n);
921         if (!IS_SET(MODE_ECHO))
922                 return;
923
924         lim = &s[n];
925         for (t = s; t < lim; t += len) {
926                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
927                         len = utf8decode(t, &u, n);
928                 } else {
929                         u = *t & 0xFF;
930                         len = 1;
931                 }
932                 if (len <= 0)
933                         break;
934                 techo(u);
935                 n -= len;
936         }
937 }
938
939 void
940 ttyresize(void)
941 {
942         struct winsize w;
943
944         w.ws_row = term.row;
945         w.ws_col = term.col;
946         w.ws_xpixel = win.tw;
947         w.ws_ypixel = win.th;
948         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
949                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
950 }
951
952 int
953 tattrset(int attr)
954 {
955         int i, j;
956
957         for (i = 0; i < term.row-1; i++) {
958                 for (j = 0; j < term.col-1; j++) {
959                         if (term.line[i][j].mode & attr)
960                                 return 1;
961                 }
962         }
963
964         return 0;
965 }
966
967 void
968 tsetdirt(int top, int bot)
969 {
970         int i;
971
972         LIMIT(top, 0, term.row-1);
973         LIMIT(bot, 0, term.row-1);
974
975         for (i = top; i <= bot; i++)
976                 term.dirty[i] = 1;
977 }
978
979 void
980 tsetdirtattr(int attr)
981 {
982         int i, j;
983
984         for (i = 0; i < term.row-1; i++) {
985                 for (j = 0; j < term.col-1; j++) {
986                         if (term.line[i][j].mode & attr) {
987                                 tsetdirt(i, i);
988                                 break;
989                         }
990                 }
991         }
992 }
993
994 void
995 tfulldirt(void)
996 {
997         tsetdirt(0, term.row-1);
998 }
999
1000 void
1001 tcursor(int mode)
1002 {
1003         static TCursor c[2];
1004         int alt = IS_SET(MODE_ALTSCREEN);
1005
1006         if (mode == CURSOR_SAVE) {
1007                 c[alt] = term.c;
1008         } else if (mode == CURSOR_LOAD) {
1009                 term.c = c[alt];
1010                 tmoveto(c[alt].x, c[alt].y);
1011         }
1012 }
1013
1014 void
1015 treset(void)
1016 {
1017         uint i;
1018
1019         term.c = (TCursor){{
1020                 .mode = ATTR_NULL,
1021                 .fg = defaultfg,
1022                 .bg = defaultbg
1023         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1024
1025         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1026         for (i = tabspaces; i < term.col; i += tabspaces)
1027                 term.tabs[i] = 1;
1028         term.top = 0;
1029         term.bot = term.row - 1;
1030         term.mode = MODE_WRAP|MODE_UTF8;
1031         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1032         term.charset = 0;
1033
1034         for (i = 0; i < 2; i++) {
1035                 tmoveto(0, 0);
1036                 tcursor(CURSOR_SAVE);
1037                 tclearregion(0, 0, term.col-1, term.row-1);
1038                 tswapscreen();
1039         }
1040 }
1041
1042 void
1043 tnew(int col, int row)
1044 {
1045         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1046         tresize(col, row);
1047         term.numlock = 1;
1048
1049         treset();
1050 }
1051
1052 void
1053 tswapscreen(void)
1054 {
1055         Line *tmp = term.line;
1056
1057         term.line = term.alt;
1058         term.alt = tmp;
1059         term.mode ^= MODE_ALTSCREEN;
1060         tfulldirt();
1061 }
1062
1063 void
1064 tscrolldown(int orig, int n)
1065 {
1066         int i;
1067         Line temp;
1068
1069         LIMIT(n, 0, term.bot-orig+1);
1070
1071         tsetdirt(orig, term.bot-n);
1072         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1073
1074         for (i = term.bot; i >= orig+n; i--) {
1075                 temp = term.line[i];
1076                 term.line[i] = term.line[i-n];
1077                 term.line[i-n] = temp;
1078         }
1079
1080         selscroll(orig, n);
1081 }
1082
1083 void
1084 tscrollup(int orig, int n)
1085 {
1086         int i;
1087         Line temp;
1088
1089         LIMIT(n, 0, term.bot-orig+1);
1090
1091         tclearregion(0, orig, term.col-1, orig+n-1);
1092         tsetdirt(orig+n, term.bot);
1093
1094         for (i = orig; i <= term.bot-n; i++) {
1095                 temp = term.line[i];
1096                 term.line[i] = term.line[i+n];
1097                 term.line[i+n] = temp;
1098         }
1099
1100         selscroll(orig, -n);
1101 }
1102
1103 void
1104 selscroll(int orig, int n)
1105 {
1106         if (sel.ob.x == -1)
1107                 return;
1108
1109         if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1110                 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1111                         selclear();
1112                         return;
1113                 }
1114                 if (sel.type == SEL_RECTANGULAR) {
1115                         if (sel.ob.y < term.top)
1116                                 sel.ob.y = term.top;
1117                         if (sel.oe.y > term.bot)
1118                                 sel.oe.y = term.bot;
1119                 } else {
1120                         if (sel.ob.y < term.top) {
1121                                 sel.ob.y = term.top;
1122                                 sel.ob.x = 0;
1123                         }
1124                         if (sel.oe.y > term.bot) {
1125                                 sel.oe.y = term.bot;
1126                                 sel.oe.x = term.col;
1127                         }
1128                 }
1129                 selnormalize();
1130         }
1131 }
1132
1133 void
1134 tnewline(int first_col)
1135 {
1136         int y = term.c.y;
1137
1138         if (y == term.bot) {
1139                 tscrollup(term.top, 1);
1140         } else {
1141                 y++;
1142         }
1143         tmoveto(first_col ? 0 : term.c.x, y);
1144 }
1145
1146 void
1147 csiparse(void)
1148 {
1149         char *p = csiescseq.buf, *np;
1150         long int v;
1151
1152         csiescseq.narg = 0;
1153         if (*p == '?') {
1154                 csiescseq.priv = 1;
1155                 p++;
1156         }
1157
1158         csiescseq.buf[csiescseq.len] = '\0';
1159         while (p < csiescseq.buf+csiescseq.len) {
1160                 np = NULL;
1161                 v = strtol(p, &np, 10);
1162                 if (np == p)
1163                         v = 0;
1164                 if (v == LONG_MAX || v == LONG_MIN)
1165                         v = -1;
1166                 csiescseq.arg[csiescseq.narg++] = v;
1167                 p = np;
1168                 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1169                         break;
1170                 p++;
1171         }
1172         csiescseq.mode[0] = *p++;
1173         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1174 }
1175
1176 /* for absolute user moves, when decom is set */
1177 void
1178 tmoveato(int x, int y)
1179 {
1180         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1181 }
1182
1183 void
1184 tmoveto(int x, int y)
1185 {
1186         int miny, maxy;
1187
1188         if (term.c.state & CURSOR_ORIGIN) {
1189                 miny = term.top;
1190                 maxy = term.bot;
1191         } else {
1192                 miny = 0;
1193                 maxy = term.row - 1;
1194         }
1195         term.c.state &= ~CURSOR_WRAPNEXT;
1196         term.c.x = LIMIT(x, 0, term.col-1);
1197         term.c.y = LIMIT(y, miny, maxy);
1198 }
1199
1200 void
1201 tsetchar(Rune u, Glyph *attr, int x, int y)
1202 {
1203         static char *vt100_0[62] = { /* 0x41 - 0x7e */
1204                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1205                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1206                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1207                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1208                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1209                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1210                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1211                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1212         };
1213
1214         /*
1215          * The table is proudly stolen from rxvt.
1216          */
1217         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1218            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1219                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1220
1221         if (term.line[y][x].mode & ATTR_WIDE) {
1222                 if (x+1 < term.col) {
1223                         term.line[y][x+1].u = ' ';
1224                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1225                 }
1226         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1227                 term.line[y][x-1].u = ' ';
1228                 term.line[y][x-1].mode &= ~ATTR_WIDE;
1229         }
1230
1231         term.dirty[y] = 1;
1232         term.line[y][x] = *attr;
1233         term.line[y][x].u = u;
1234 }
1235
1236 void
1237 tclearregion(int x1, int y1, int x2, int y2)
1238 {
1239         int x, y, temp;
1240         Glyph *gp;
1241
1242         if (x1 > x2)
1243                 temp = x1, x1 = x2, x2 = temp;
1244         if (y1 > y2)
1245                 temp = y1, y1 = y2, y2 = temp;
1246
1247         LIMIT(x1, 0, term.col-1);
1248         LIMIT(x2, 0, term.col-1);
1249         LIMIT(y1, 0, term.row-1);
1250         LIMIT(y2, 0, term.row-1);
1251
1252         for (y = y1; y <= y2; y++) {
1253                 term.dirty[y] = 1;
1254                 for (x = x1; x <= x2; x++) {
1255                         gp = &term.line[y][x];
1256                         if (selected(x, y))
1257                                 selclear();
1258                         gp->fg = term.c.attr.fg;
1259                         gp->bg = term.c.attr.bg;
1260                         gp->mode = 0;
1261                         gp->u = ' ';
1262                 }
1263         }
1264 }
1265
1266 void
1267 tdeletechar(int n)
1268 {
1269         int dst, src, size;
1270         Glyph *line;
1271
1272         LIMIT(n, 0, term.col - term.c.x);
1273
1274         dst = term.c.x;
1275         src = term.c.x + n;
1276         size = term.col - src;
1277         line = term.line[term.c.y];
1278
1279         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1280         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1281 }
1282
1283 void
1284 tinsertblank(int n)
1285 {
1286         int dst, src, size;
1287         Glyph *line;
1288
1289         LIMIT(n, 0, term.col - term.c.x);
1290
1291         dst = term.c.x + n;
1292         src = term.c.x;
1293         size = term.col - dst;
1294         line = term.line[term.c.y];
1295
1296         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1297         tclearregion(src, term.c.y, dst - 1, term.c.y);
1298 }
1299
1300 void
1301 tinsertblankline(int n)
1302 {
1303         if (BETWEEN(term.c.y, term.top, term.bot))
1304                 tscrolldown(term.c.y, n);
1305 }
1306
1307 void
1308 tdeleteline(int n)
1309 {
1310         if (BETWEEN(term.c.y, term.top, term.bot))
1311                 tscrollup(term.c.y, n);
1312 }
1313
1314 int32_t
1315 tdefcolor(int *attr, int *npar, int l)
1316 {
1317         int32_t idx = -1;
1318         uint r, g, b;
1319
1320         switch (attr[*npar + 1]) {
1321         case 2: /* direct color in RGB space */
1322                 if (*npar + 4 >= l) {
1323                         fprintf(stderr,
1324                                 "erresc(38): Incorrect number of parameters (%d)\n",
1325                                 *npar);
1326                         break;
1327                 }
1328                 r = attr[*npar + 2];
1329                 g = attr[*npar + 3];
1330                 b = attr[*npar + 4];
1331                 *npar += 4;
1332                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1333                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1334                                 r, g, b);
1335                 else
1336                         idx = TRUECOLOR(r, g, b);
1337                 break;
1338         case 5: /* indexed color */
1339                 if (*npar + 2 >= l) {
1340                         fprintf(stderr,
1341                                 "erresc(38): Incorrect number of parameters (%d)\n",
1342                                 *npar);
1343                         break;
1344                 }
1345                 *npar += 2;
1346                 if (!BETWEEN(attr[*npar], 0, 255))
1347                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1348                 else
1349                         idx = attr[*npar];
1350                 break;
1351         case 0: /* implemented defined (only foreground) */
1352         case 1: /* transparent */
1353         case 3: /* direct color in CMY space */
1354         case 4: /* direct color in CMYK space */
1355         default:
1356                 fprintf(stderr,
1357                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1358                 break;
1359         }
1360
1361         return idx;
1362 }
1363
1364 void
1365 tsetattr(int *attr, int l)
1366 {
1367         int i;
1368         int32_t idx;
1369
1370         for (i = 0; i < l; i++) {
1371                 switch (attr[i]) {
1372                 case 0:
1373                         term.c.attr.mode &= ~(
1374                                 ATTR_BOLD       |
1375                                 ATTR_FAINT      |
1376                                 ATTR_ITALIC     |
1377                                 ATTR_UNDERLINE  |
1378                                 ATTR_BLINK      |
1379                                 ATTR_REVERSE    |
1380                                 ATTR_INVISIBLE  |
1381                                 ATTR_STRUCK     );
1382                         term.c.attr.fg = defaultfg;
1383                         term.c.attr.bg = defaultbg;
1384                         break;
1385                 case 1:
1386                         term.c.attr.mode |= ATTR_BOLD;
1387                         break;
1388                 case 2:
1389                         term.c.attr.mode |= ATTR_FAINT;
1390                         break;
1391                 case 3:
1392                         term.c.attr.mode |= ATTR_ITALIC;
1393                         break;
1394                 case 4:
1395                         term.c.attr.mode |= ATTR_UNDERLINE;
1396                         break;
1397                 case 5: /* slow blink */
1398                         /* FALLTHROUGH */
1399                 case 6: /* rapid blink */
1400                         term.c.attr.mode |= ATTR_BLINK;
1401                         break;
1402                 case 7:
1403                         term.c.attr.mode |= ATTR_REVERSE;
1404                         break;
1405                 case 8:
1406                         term.c.attr.mode |= ATTR_INVISIBLE;
1407                         break;
1408                 case 9:
1409                         term.c.attr.mode |= ATTR_STRUCK;
1410                         break;
1411                 case 22:
1412                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1413                         break;
1414                 case 23:
1415                         term.c.attr.mode &= ~ATTR_ITALIC;
1416                         break;
1417                 case 24:
1418                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1419                         break;
1420                 case 25:
1421                         term.c.attr.mode &= ~ATTR_BLINK;
1422                         break;
1423                 case 27:
1424                         term.c.attr.mode &= ~ATTR_REVERSE;
1425                         break;
1426                 case 28:
1427                         term.c.attr.mode &= ~ATTR_INVISIBLE;
1428                         break;
1429                 case 29:
1430                         term.c.attr.mode &= ~ATTR_STRUCK;
1431                         break;
1432                 case 38:
1433                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1434                                 term.c.attr.fg = idx;
1435                         break;
1436                 case 39:
1437                         term.c.attr.fg = defaultfg;
1438                         break;
1439                 case 48:
1440                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1441                                 term.c.attr.bg = idx;
1442                         break;
1443                 case 49:
1444                         term.c.attr.bg = defaultbg;
1445                         break;
1446                 default:
1447                         if (BETWEEN(attr[i], 30, 37)) {
1448                                 term.c.attr.fg = attr[i] - 30;
1449                         } else if (BETWEEN(attr[i], 40, 47)) {
1450                                 term.c.attr.bg = attr[i] - 40;
1451                         } else if (BETWEEN(attr[i], 90, 97)) {
1452                                 term.c.attr.fg = attr[i] - 90 + 8;
1453                         } else if (BETWEEN(attr[i], 100, 107)) {
1454                                 term.c.attr.bg = attr[i] - 100 + 8;
1455                         } else {
1456                                 fprintf(stderr,
1457                                         "erresc(default): gfx attr %d unknown\n",
1458                                         attr[i]), csidump();
1459                         }
1460                         break;
1461                 }
1462         }
1463 }
1464
1465 void
1466 tsetscroll(int t, int b)
1467 {
1468         int temp;
1469
1470         LIMIT(t, 0, term.row-1);
1471         LIMIT(b, 0, term.row-1);
1472         if (t > b) {
1473                 temp = t;
1474                 t = b;
1475                 b = temp;
1476         }
1477         term.top = t;
1478         term.bot = b;
1479 }
1480
1481 void
1482 tsetmode(int priv, int set, int *args, int narg)
1483 {
1484         int *lim, mode;
1485         int alt;
1486
1487         for (lim = args + narg; args < lim; ++args) {
1488                 if (priv) {
1489                         switch (*args) {
1490                         case 1: /* DECCKM -- Cursor key */
1491                                 MODBIT(term.mode, set, MODE_APPCURSOR);
1492                                 break;
1493                         case 5: /* DECSCNM -- Reverse video */
1494                                 mode = term.mode;
1495                                 MODBIT(term.mode, set, MODE_REVERSE);
1496                                 if (mode != term.mode)
1497                                         redraw();
1498                                 break;
1499                         case 6: /* DECOM -- Origin */
1500                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1501                                 tmoveato(0, 0);
1502                                 break;
1503                         case 7: /* DECAWM -- Auto wrap */
1504                                 MODBIT(term.mode, set, MODE_WRAP);
1505                                 break;
1506                         case 0:  /* Error (IGNORED) */
1507                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1508                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1509                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1510                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1511                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1512                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1513                         case 42: /* DECNRCM -- National characters (IGNORED) */
1514                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1515                                 break;
1516                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1517                                 MODBIT(term.mode, !set, MODE_HIDE);
1518                                 break;
1519                         case 9:    /* X10 mouse compatibility mode */
1520                                 xsetpointermotion(0);
1521                                 MODBIT(term.mode, 0, MODE_MOUSE);
1522                                 MODBIT(term.mode, set, MODE_MOUSEX10);
1523                                 break;
1524                         case 1000: /* 1000: report button press */
1525                                 xsetpointermotion(0);
1526                                 MODBIT(term.mode, 0, MODE_MOUSE);
1527                                 MODBIT(term.mode, set, MODE_MOUSEBTN);
1528                                 break;
1529                         case 1002: /* 1002: report motion on button press */
1530                                 xsetpointermotion(0);
1531                                 MODBIT(term.mode, 0, MODE_MOUSE);
1532                                 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1533                                 break;
1534                         case 1003: /* 1003: enable all mouse motions */
1535                                 xsetpointermotion(set);
1536                                 MODBIT(term.mode, 0, MODE_MOUSE);
1537                                 MODBIT(term.mode, set, MODE_MOUSEMANY);
1538                                 break;
1539                         case 1004: /* 1004: send focus events to tty */
1540                                 MODBIT(term.mode, set, MODE_FOCUS);
1541                                 break;
1542                         case 1006: /* 1006: extended reporting mode */
1543                                 MODBIT(term.mode, set, MODE_MOUSESGR);
1544                                 break;
1545                         case 1034:
1546                                 MODBIT(term.mode, set, MODE_8BIT);
1547                                 break;
1548                         case 1049: /* swap screen & set/restore cursor as xterm */
1549                                 if (!allowaltscreen)
1550                                         break;
1551                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1552                                 /* FALLTHROUGH */
1553                         case 47: /* swap screen */
1554                         case 1047:
1555                                 if (!allowaltscreen)
1556                                         break;
1557                                 alt = IS_SET(MODE_ALTSCREEN);
1558                                 if (alt) {
1559                                         tclearregion(0, 0, term.col-1,
1560                                                         term.row-1);
1561                                 }
1562                                 if (set ^ alt) /* set is always 1 or 0 */
1563                                         tswapscreen();
1564                                 if (*args != 1049)
1565                                         break;
1566                                 /* FALLTHROUGH */
1567                         case 1048:
1568                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1569                                 break;
1570                         case 2004: /* 2004: bracketed paste mode */
1571                                 MODBIT(term.mode, set, MODE_BRCKTPASTE);
1572                                 break;
1573                         /* Not implemented mouse modes. See comments there. */
1574                         case 1001: /* mouse highlight mode; can hang the
1575                                       terminal by design when implemented. */
1576                         case 1005: /* UTF-8 mouse mode; will confuse
1577                                       applications not supporting UTF-8
1578                                       and luit. */
1579                         case 1015: /* urxvt mangled mouse mode; incompatible
1580                                       and can be mistaken for other control
1581                                       codes. */
1582                         default:
1583                                 fprintf(stderr,
1584                                         "erresc: unknown private set/reset mode %d\n",
1585                                         *args);
1586                                 break;
1587                         }
1588                 } else {
1589                         switch (*args) {
1590                         case 0:  /* Error (IGNORED) */
1591                                 break;
1592                         case 2:  /* KAM -- keyboard action */
1593                                 MODBIT(term.mode, set, MODE_KBDLOCK);
1594                                 break;
1595                         case 4:  /* IRM -- Insertion-replacement */
1596                                 MODBIT(term.mode, set, MODE_INSERT);
1597                                 break;
1598                         case 12: /* SRM -- Send/Receive */
1599                                 MODBIT(term.mode, !set, MODE_ECHO);
1600                                 break;
1601                         case 20: /* LNM -- Linefeed/new line */
1602                                 MODBIT(term.mode, set, MODE_CRLF);
1603                                 break;
1604                         default:
1605                                 fprintf(stderr,
1606                                         "erresc: unknown set/reset mode %d\n",
1607                                         *args);
1608                                 break;
1609                         }
1610                 }
1611         }
1612 }
1613
1614 void
1615 csihandle(void)
1616 {
1617         char buf[40];
1618         int len;
1619
1620         switch (csiescseq.mode[0]) {
1621         default:
1622         unknown:
1623                 fprintf(stderr, "erresc: unknown csi ");
1624                 csidump();
1625                 /* die(""); */
1626                 break;
1627         case '@': /* ICH -- Insert <n> blank char */
1628                 DEFAULT(csiescseq.arg[0], 1);
1629                 tinsertblank(csiescseq.arg[0]);
1630                 break;
1631         case 'A': /* CUU -- Cursor <n> Up */
1632                 DEFAULT(csiescseq.arg[0], 1);
1633                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1634                 break;
1635         case 'B': /* CUD -- Cursor <n> Down */
1636         case 'e': /* VPR --Cursor <n> Down */
1637                 DEFAULT(csiescseq.arg[0], 1);
1638                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1639                 break;
1640         case 'i': /* MC -- Media Copy */
1641                 switch (csiescseq.arg[0]) {
1642                 case 0:
1643                         tdump();
1644                         break;
1645                 case 1:
1646                         tdumpline(term.c.y);
1647                         break;
1648                 case 2:
1649                         tdumpsel();
1650                         break;
1651                 case 4:
1652                         term.mode &= ~MODE_PRINT;
1653                         break;
1654                 case 5:
1655                         term.mode |= MODE_PRINT;
1656                         break;
1657                 }
1658                 break;
1659         case 'c': /* DA -- Device Attributes */
1660                 if (csiescseq.arg[0] == 0)
1661                         ttywrite(vtiden, sizeof(vtiden) - 1);
1662                 break;
1663         case 'C': /* CUF -- Cursor <n> Forward */
1664         case 'a': /* HPR -- Cursor <n> Forward */
1665                 DEFAULT(csiescseq.arg[0], 1);
1666                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1667                 break;
1668         case 'D': /* CUB -- Cursor <n> Backward */
1669                 DEFAULT(csiescseq.arg[0], 1);
1670                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1671                 break;
1672         case 'E': /* CNL -- Cursor <n> Down and first col */
1673                 DEFAULT(csiescseq.arg[0], 1);
1674                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1675                 break;
1676         case 'F': /* CPL -- Cursor <n> Up and first col */
1677                 DEFAULT(csiescseq.arg[0], 1);
1678                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1679                 break;
1680         case 'g': /* TBC -- Tabulation clear */
1681                 switch (csiescseq.arg[0]) {
1682                 case 0: /* clear current tab stop */
1683                         term.tabs[term.c.x] = 0;
1684                         break;
1685                 case 3: /* clear all the tabs */
1686                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1687                         break;
1688                 default:
1689                         goto unknown;
1690                 }
1691                 break;
1692         case 'G': /* CHA -- Move to <col> */
1693         case '`': /* HPA */
1694                 DEFAULT(csiescseq.arg[0], 1);
1695                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1696                 break;
1697         case 'H': /* CUP -- Move to <row> <col> */
1698         case 'f': /* HVP */
1699                 DEFAULT(csiescseq.arg[0], 1);
1700                 DEFAULT(csiescseq.arg[1], 1);
1701                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1702                 break;
1703         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1704                 DEFAULT(csiescseq.arg[0], 1);
1705                 tputtab(csiescseq.arg[0]);
1706                 break;
1707         case 'J': /* ED -- Clear screen */
1708                 selclear();
1709                 switch (csiescseq.arg[0]) {
1710                 case 0: /* below */
1711                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1712                         if (term.c.y < term.row-1) {
1713                                 tclearregion(0, term.c.y+1, term.col-1,
1714                                                 term.row-1);
1715                         }
1716                         break;
1717                 case 1: /* above */
1718                         if (term.c.y > 1)
1719                                 tclearregion(0, 0, term.col-1, term.c.y-1);
1720                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1721                         break;
1722                 case 2: /* all */
1723                         tclearregion(0, 0, term.col-1, term.row-1);
1724                         break;
1725                 default:
1726                         goto unknown;
1727                 }
1728                 break;
1729         case 'K': /* EL -- Clear line */
1730                 switch (csiescseq.arg[0]) {
1731                 case 0: /* right */
1732                         tclearregion(term.c.x, term.c.y, term.col-1,
1733                                         term.c.y);
1734                         break;
1735                 case 1: /* left */
1736                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1737                         break;
1738                 case 2: /* all */
1739                         tclearregion(0, term.c.y, term.col-1, term.c.y);
1740                         break;
1741                 }
1742                 break;
1743         case 'S': /* SU -- Scroll <n> line up */
1744                 DEFAULT(csiescseq.arg[0], 1);
1745                 tscrollup(term.top, csiescseq.arg[0]);
1746                 break;
1747         case 'T': /* SD -- Scroll <n> line down */
1748                 DEFAULT(csiescseq.arg[0], 1);
1749                 tscrolldown(term.top, csiescseq.arg[0]);
1750                 break;
1751         case 'L': /* IL -- Insert <n> blank lines */
1752                 DEFAULT(csiescseq.arg[0], 1);
1753                 tinsertblankline(csiescseq.arg[0]);
1754                 break;
1755         case 'l': /* RM -- Reset Mode */
1756                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1757                 break;
1758         case 'M': /* DL -- Delete <n> lines */
1759                 DEFAULT(csiescseq.arg[0], 1);
1760                 tdeleteline(csiescseq.arg[0]);
1761                 break;
1762         case 'X': /* ECH -- Erase <n> char */
1763                 DEFAULT(csiescseq.arg[0], 1);
1764                 tclearregion(term.c.x, term.c.y,
1765                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1766                 break;
1767         case 'P': /* DCH -- Delete <n> char */
1768                 DEFAULT(csiescseq.arg[0], 1);
1769                 tdeletechar(csiescseq.arg[0]);
1770                 break;
1771         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1772                 DEFAULT(csiescseq.arg[0], 1);
1773                 tputtab(-csiescseq.arg[0]);
1774                 break;
1775         case 'd': /* VPA -- Move to <row> */
1776                 DEFAULT(csiescseq.arg[0], 1);
1777                 tmoveato(term.c.x, csiescseq.arg[0]-1);
1778                 break;
1779         case 'h': /* SM -- Set terminal mode */
1780                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1781                 break;
1782         case 'm': /* SGR -- Terminal attribute (color) */
1783                 tsetattr(csiescseq.arg, csiescseq.narg);
1784                 break;
1785         case 'n': /* DSR – Device Status Report (cursor position) */
1786                 if (csiescseq.arg[0] == 6) {
1787                         len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1788                                         term.c.y+1, term.c.x+1);
1789                         ttywrite(buf, len);
1790                 }
1791                 break;
1792         case 'r': /* DECSTBM -- Set Scrolling Region */
1793                 if (csiescseq.priv) {
1794                         goto unknown;
1795                 } else {
1796                         DEFAULT(csiescseq.arg[0], 1);
1797                         DEFAULT(csiescseq.arg[1], term.row);
1798                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1799                         tmoveato(0, 0);
1800                 }
1801                 break;
1802         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1803                 tcursor(CURSOR_SAVE);
1804                 break;
1805         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1806                 tcursor(CURSOR_LOAD);
1807                 break;
1808         case ' ':
1809                 switch (csiescseq.mode[1]) {
1810                 case 'q': /* DECSCUSR -- Set Cursor Style */
1811                         DEFAULT(csiescseq.arg[0], 1);
1812                         if (!BETWEEN(csiescseq.arg[0], 0, 6)) {
1813                                 goto unknown;
1814                         }
1815                         win.cursor = csiescseq.arg[0];
1816                         break;
1817                 default:
1818                         goto unknown;
1819                 }
1820                 break;
1821         }
1822 }
1823
1824 void
1825 csidump(void)
1826 {
1827         int i;
1828         uint c;
1829
1830         fprintf(stderr, "ESC[");
1831         for (i = 0; i < csiescseq.len; i++) {
1832                 c = csiescseq.buf[i] & 0xff;
1833                 if (isprint(c)) {
1834                         putc(c, stderr);
1835                 } else if (c == '\n') {
1836                         fprintf(stderr, "(\\n)");
1837                 } else if (c == '\r') {
1838                         fprintf(stderr, "(\\r)");
1839                 } else if (c == 0x1b) {
1840                         fprintf(stderr, "(\\e)");
1841                 } else {
1842                         fprintf(stderr, "(%02x)", c);
1843                 }
1844         }
1845         putc('\n', stderr);
1846 }
1847
1848 void
1849 csireset(void)
1850 {
1851         memset(&csiescseq, 0, sizeof(csiescseq));
1852 }
1853
1854 void
1855 strhandle(void)
1856 {
1857         char *p = NULL;
1858         int j, narg, par;
1859
1860         term.esc &= ~(ESC_STR_END|ESC_STR);
1861         strparse();
1862         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1863
1864         switch (strescseq.type) {
1865         case ']': /* OSC -- Operating System Command */
1866                 switch (par) {
1867                 case 0:
1868                 case 1:
1869                 case 2:
1870                         if (narg > 1)
1871                                 xsettitle(strescseq.args[1]);
1872                         return;
1873                 case 52:
1874                         if (narg > 2) {
1875                                 char *dec;
1876
1877                                 dec = base64dec(strescseq.args[2]);
1878                                 if (dec) {
1879                                         xsetsel(dec, CurrentTime);
1880                                         clipcopy(NULL);
1881                                 } else {
1882                                         fprintf(stderr, "erresc: invalid base64\n");
1883                                 }
1884                         }
1885                         return;
1886                 case 4: /* color set */
1887                         if (narg < 3)
1888                                 break;
1889                         p = strescseq.args[2];
1890                         /* FALLTHROUGH */
1891                 case 104: /* color reset, here p = NULL */
1892                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1893                         if (xsetcolorname(j, p)) {
1894                                 fprintf(stderr, "erresc: invalid color %s\n", p);
1895                         } else {
1896                                 /*
1897                                  * TODO if defaultbg color is changed, borders
1898                                  * are dirty
1899                                  */
1900                                 redraw();
1901                         }
1902                         return;
1903                 }
1904                 break;
1905         case 'k': /* old title set compatibility */
1906                 xsettitle(strescseq.args[0]);
1907                 return;
1908         case 'P': /* DCS -- Device Control String */
1909                 term.mode |= ESC_DCS;
1910         case '_': /* APC -- Application Program Command */
1911         case '^': /* PM -- Privacy Message */
1912                 return;
1913         }
1914
1915         fprintf(stderr, "erresc: unknown str ");
1916         strdump();
1917 }
1918
1919 void
1920 strparse(void)
1921 {
1922         int c;
1923         char *p = strescseq.buf;
1924
1925         strescseq.narg = 0;
1926         strescseq.buf[strescseq.len] = '\0';
1927
1928         if (*p == '\0')
1929                 return;
1930
1931         while (strescseq.narg < STR_ARG_SIZ) {
1932                 strescseq.args[strescseq.narg++] = p;
1933                 while ((c = *p) != ';' && c != '\0')
1934                         ++p;
1935                 if (c == '\0')
1936                         return;
1937                 *p++ = '\0';
1938         }
1939 }
1940
1941 void
1942 strdump(void)
1943 {
1944         int i;
1945         uint c;
1946
1947         fprintf(stderr, "ESC%c", strescseq.type);
1948         for (i = 0; i < strescseq.len; i++) {
1949                 c = strescseq.buf[i] & 0xff;
1950                 if (c == '\0') {
1951                         putc('\n', stderr);
1952                         return;
1953                 } else if (isprint(c)) {
1954                         putc(c, stderr);
1955                 } else if (c == '\n') {
1956                         fprintf(stderr, "(\\n)");
1957                 } else if (c == '\r') {
1958                         fprintf(stderr, "(\\r)");
1959                 } else if (c == 0x1b) {
1960                         fprintf(stderr, "(\\e)");
1961                 } else {
1962                         fprintf(stderr, "(%02x)", c);
1963                 }
1964         }
1965         fprintf(stderr, "ESC\\\n");
1966 }
1967
1968 void
1969 strreset(void)
1970 {
1971         memset(&strescseq, 0, sizeof(strescseq));
1972 }
1973
1974 void
1975 sendbreak(const Arg *arg)
1976 {
1977         if (tcsendbreak(cmdfd, 0))
1978                 perror("Error sending break");
1979 }
1980
1981 void
1982 tprinter(char *s, size_t len)
1983 {
1984         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1985                 fprintf(stderr, "Error writing in %s:%s\n",
1986                         opt_io, strerror(errno));
1987                 close(iofd);
1988                 iofd = -1;
1989         }
1990 }
1991
1992 void
1993 iso14755(const Arg *arg)
1994 {
1995         FILE *p;
1996         char *us, *e, codepoint[9], uc[UTF_SIZ];
1997         unsigned long utf32;
1998
1999         if (!(p = popen(ISO14755CMD, "r")))
2000                 return;
2001
2002         us = fgets(codepoint, sizeof(codepoint), p);
2003         pclose(p);
2004
2005         if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
2006                 return;
2007         if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
2008             (*e != '\n' && *e != '\0'))
2009                 return;
2010
2011         ttysend(uc, utf8encode(utf32, uc));
2012 }
2013
2014 void
2015 toggleprinter(const Arg *arg)
2016 {
2017         term.mode ^= MODE_PRINT;
2018 }
2019
2020 void
2021 printscreen(const Arg *arg)
2022 {
2023         tdump();
2024 }
2025
2026 void
2027 printsel(const Arg *arg)
2028 {
2029         tdumpsel();
2030 }
2031
2032 void
2033 tdumpsel(void)
2034 {
2035         char *ptr;
2036
2037         if ((ptr = getsel())) {
2038                 tprinter(ptr, strlen(ptr));
2039                 free(ptr);
2040         }
2041 }
2042
2043 void
2044 tdumpline(int n)
2045 {
2046         char buf[UTF_SIZ];
2047         Glyph *bp, *end;
2048
2049         bp = &term.line[n][0];
2050         end = &bp[MIN(tlinelen(n), term.col) - 1];
2051         if (bp != end || bp->u != ' ') {
2052                 for ( ;bp <= end; ++bp)
2053                         tprinter(buf, utf8encode(bp->u, buf));
2054         }
2055         tprinter("\n", 1);
2056 }
2057
2058 void
2059 tdump(void)
2060 {
2061         int i;
2062
2063         for (i = 0; i < term.row; ++i)
2064                 tdumpline(i);
2065 }
2066
2067 void
2068 tputtab(int n)
2069 {
2070         uint x = term.c.x;
2071
2072         if (n > 0) {
2073                 while (x < term.col && n--)
2074                         for (++x; x < term.col && !term.tabs[x]; ++x)
2075                                 /* nothing */ ;
2076         } else if (n < 0) {
2077                 while (x > 0 && n++)
2078                         for (--x; x > 0 && !term.tabs[x]; --x)
2079                                 /* nothing */ ;
2080         }
2081         term.c.x = LIMIT(x, 0, term.col-1);
2082 }
2083
2084 void
2085 techo(Rune u)
2086 {
2087         if (ISCONTROL(u)) { /* control code */
2088                 if (u & 0x80) {
2089                         u &= 0x7f;
2090                         tputc('^');
2091                         tputc('[');
2092                 } else if (u != '\n' && u != '\r' && u != '\t') {
2093                         u ^= 0x40;
2094                         tputc('^');
2095                 }
2096         }
2097         tputc(u);
2098 }
2099
2100 void
2101 tdefutf8(char ascii)
2102 {
2103         if (ascii == 'G')
2104                 term.mode |= MODE_UTF8;
2105         else if (ascii == '@')
2106                 term.mode &= ~MODE_UTF8;
2107 }
2108
2109 void
2110 tdeftran(char ascii)
2111 {
2112         static char cs[] = "0B";
2113         static int vcs[] = {CS_GRAPHIC0, CS_USA};
2114         char *p;
2115
2116         if ((p = strchr(cs, ascii)) == NULL) {
2117                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2118         } else {
2119                 term.trantbl[term.icharset] = vcs[p - cs];
2120         }
2121 }
2122
2123 void
2124 tdectest(char c)
2125 {
2126         int x, y;
2127
2128         if (c == '8') { /* DEC screen alignment test. */
2129                 for (x = 0; x < term.col; ++x) {
2130                         for (y = 0; y < term.row; ++y)
2131                                 tsetchar('E', &term.c.attr, x, y);
2132                 }
2133         }
2134 }
2135
2136 void
2137 tstrsequence(uchar c)
2138 {
2139         strreset();
2140
2141         switch (c) {
2142         case 0x90:   /* DCS -- Device Control String */
2143                 c = 'P';
2144                 term.esc |= ESC_DCS;
2145                 break;
2146         case 0x9f:   /* APC -- Application Program Command */
2147                 c = '_';
2148                 break;
2149         case 0x9e:   /* PM -- Privacy Message */
2150                 c = '^';
2151                 break;
2152         case 0x9d:   /* OSC -- Operating System Command */
2153                 c = ']';
2154                 break;
2155         }
2156         strescseq.type = c;
2157         term.esc |= ESC_STR;
2158 }
2159
2160 void
2161 tcontrolcode(uchar ascii)
2162 {
2163         switch (ascii) {
2164         case '\t':   /* HT */
2165                 tputtab(1);
2166                 return;
2167         case '\b':   /* BS */
2168                 tmoveto(term.c.x-1, term.c.y);
2169                 return;
2170         case '\r':   /* CR */
2171                 tmoveto(0, term.c.y);
2172                 return;
2173         case '\f':   /* LF */
2174         case '\v':   /* VT */
2175         case '\n':   /* LF */
2176                 /* go to first col if the mode is set */
2177                 tnewline(IS_SET(MODE_CRLF));
2178                 return;
2179         case '\a':   /* BEL */
2180                 if (term.esc & ESC_STR_END) {
2181                         /* backwards compatibility to xterm */
2182                         strhandle();
2183                 } else {
2184                         if (!(win.state & WIN_FOCUSED))
2185                                 xseturgency(1);
2186                         if (bellvolume)
2187                                 xbell(bellvolume);
2188                 }
2189                 break;
2190         case '\033': /* ESC */
2191                 csireset();
2192                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2193                 term.esc |= ESC_START;
2194                 return;
2195         case '\016': /* SO (LS1 -- Locking shift 1) */
2196         case '\017': /* SI (LS0 -- Locking shift 0) */
2197                 term.charset = 1 - (ascii - '\016');
2198                 return;
2199         case '\032': /* SUB */
2200                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2201         case '\030': /* CAN */
2202                 csireset();
2203                 break;
2204         case '\005': /* ENQ (IGNORED) */
2205         case '\000': /* NUL (IGNORED) */
2206         case '\021': /* XON (IGNORED) */
2207         case '\023': /* XOFF (IGNORED) */
2208         case 0177:   /* DEL (IGNORED) */
2209                 return;
2210         case 0x80:   /* TODO: PAD */
2211         case 0x81:   /* TODO: HOP */
2212         case 0x82:   /* TODO: BPH */
2213         case 0x83:   /* TODO: NBH */
2214         case 0x84:   /* TODO: IND */
2215                 break;
2216         case 0x85:   /* NEL -- Next line */
2217                 tnewline(1); /* always go to first col */
2218                 break;
2219         case 0x86:   /* TODO: SSA */
2220         case 0x87:   /* TODO: ESA */
2221                 break;
2222         case 0x88:   /* HTS -- Horizontal tab stop */
2223                 term.tabs[term.c.x] = 1;
2224                 break;
2225         case 0x89:   /* TODO: HTJ */
2226         case 0x8a:   /* TODO: VTS */
2227         case 0x8b:   /* TODO: PLD */
2228         case 0x8c:   /* TODO: PLU */
2229         case 0x8d:   /* TODO: RI */
2230         case 0x8e:   /* TODO: SS2 */
2231         case 0x8f:   /* TODO: SS3 */
2232         case 0x91:   /* TODO: PU1 */
2233         case 0x92:   /* TODO: PU2 */
2234         case 0x93:   /* TODO: STS */
2235         case 0x94:   /* TODO: CCH */
2236         case 0x95:   /* TODO: MW */
2237         case 0x96:   /* TODO: SPA */
2238         case 0x97:   /* TODO: EPA */
2239         case 0x98:   /* TODO: SOS */
2240         case 0x99:   /* TODO: SGCI */
2241                 break;
2242         case 0x9a:   /* DECID -- Identify Terminal */
2243                 ttywrite(vtiden, sizeof(vtiden) - 1);
2244                 break;
2245         case 0x9b:   /* TODO: CSI */
2246         case 0x9c:   /* TODO: ST */
2247                 break;
2248         case 0x90:   /* DCS -- Device Control String */
2249         case 0x9d:   /* OSC -- Operating System Command */
2250         case 0x9e:   /* PM -- Privacy Message */
2251         case 0x9f:   /* APC -- Application Program Command */
2252                 tstrsequence(ascii);
2253                 return;
2254         }
2255         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2256         term.esc &= ~(ESC_STR_END|ESC_STR);
2257 }
2258
2259 /*
2260  * returns 1 when the sequence is finished and it hasn't to read
2261  * more characters for this sequence, otherwise 0
2262  */
2263 int
2264 eschandle(uchar ascii)
2265 {
2266         switch (ascii) {
2267         case '[':
2268                 term.esc |= ESC_CSI;
2269                 return 0;
2270         case '#':
2271                 term.esc |= ESC_TEST;
2272                 return 0;
2273         case '%':
2274                 term.esc |= ESC_UTF8;
2275                 return 0;
2276         case 'P': /* DCS -- Device Control String */
2277         case '_': /* APC -- Application Program Command */
2278         case '^': /* PM -- Privacy Message */
2279         case ']': /* OSC -- Operating System Command */
2280         case 'k': /* old title set compatibility */
2281                 tstrsequence(ascii);
2282                 return 0;
2283         case 'n': /* LS2 -- Locking shift 2 */
2284         case 'o': /* LS3 -- Locking shift 3 */
2285                 term.charset = 2 + (ascii - 'n');
2286                 break;
2287         case '(': /* GZD4 -- set primary charset G0 */
2288         case ')': /* G1D4 -- set secondary charset G1 */
2289         case '*': /* G2D4 -- set tertiary charset G2 */
2290         case '+': /* G3D4 -- set quaternary charset G3 */
2291                 term.icharset = ascii - '(';
2292                 term.esc |= ESC_ALTCHARSET;
2293                 return 0;
2294         case 'D': /* IND -- Linefeed */
2295                 if (term.c.y == term.bot) {
2296                         tscrollup(term.top, 1);
2297                 } else {
2298                         tmoveto(term.c.x, term.c.y+1);
2299                 }
2300                 break;
2301         case 'E': /* NEL -- Next line */
2302                 tnewline(1); /* always go to first col */
2303                 break;
2304         case 'H': /* HTS -- Horizontal tab stop */
2305                 term.tabs[term.c.x] = 1;
2306                 break;
2307         case 'M': /* RI -- Reverse index */
2308                 if (term.c.y == term.top) {
2309                         tscrolldown(term.top, 1);
2310                 } else {
2311                         tmoveto(term.c.x, term.c.y-1);
2312                 }
2313                 break;
2314         case 'Z': /* DECID -- Identify Terminal */
2315                 ttywrite(vtiden, sizeof(vtiden) - 1);
2316                 break;
2317         case 'c': /* RIS -- Reset to inital state */
2318                 treset();
2319                 resettitle();
2320                 xloadcols();
2321                 break;
2322         case '=': /* DECPAM -- Application keypad */
2323                 term.mode |= MODE_APPKEYPAD;
2324                 break;
2325         case '>': /* DECPNM -- Normal keypad */
2326                 term.mode &= ~MODE_APPKEYPAD;
2327                 break;
2328         case '7': /* DECSC -- Save Cursor */
2329                 tcursor(CURSOR_SAVE);
2330                 break;
2331         case '8': /* DECRC -- Restore Cursor */
2332                 tcursor(CURSOR_LOAD);
2333                 break;
2334         case '\\': /* ST -- String Terminator */
2335                 if (term.esc & ESC_STR_END)
2336                         strhandle();
2337                 break;
2338         default:
2339                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2340                         (uchar) ascii, isprint(ascii)? ascii:'.');
2341                 break;
2342         }
2343         return 1;
2344 }
2345
2346 void
2347 tputc(Rune u)
2348 {
2349         char c[UTF_SIZ];
2350         int control;
2351         int width, len;
2352         Glyph *gp;
2353
2354         control = ISCONTROL(u);
2355         if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2356                 c[0] = u;
2357                 width = len = 1;
2358         } else {
2359                 len = utf8encode(u, c);
2360                 if (!control && (width = wcwidth(u)) == -1) {
2361                         memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2362                         width = 1;
2363                 }
2364         }
2365
2366         if (IS_SET(MODE_PRINT))
2367                 tprinter(c, len);
2368
2369         /*
2370          * STR sequence must be checked before anything else
2371          * because it uses all following characters until it
2372          * receives a ESC, a SUB, a ST or any other C1 control
2373          * character.
2374          */
2375         if (term.esc & ESC_STR) {
2376                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2377                    ISCONTROLC1(u)) {
2378                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2379                         if (IS_SET(MODE_SIXEL)) {
2380                                 /* TODO: render sixel */;
2381                                 term.mode &= ~MODE_SIXEL;
2382                                 return;
2383                         }
2384                         term.esc |= ESC_STR_END;
2385                         goto check_control_code;
2386                 }
2387
2388
2389                 if (IS_SET(MODE_SIXEL)) {
2390                         /* TODO: implement sixel mode */
2391                         return;
2392                 }
2393                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2394                         term.mode |= MODE_SIXEL;
2395
2396                 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2397                         /*
2398                          * Here is a bug in terminals. If the user never sends
2399                          * some code to stop the str or esc command, then st
2400                          * will stop responding. But this is better than
2401                          * silently failing with unknown characters. At least
2402                          * then users will report back.
2403                          *
2404                          * In the case users ever get fixed, here is the code:
2405                          */
2406                         /*
2407                          * term.esc = 0;
2408                          * strhandle();
2409                          */
2410                         return;
2411                 }
2412
2413                 memmove(&strescseq.buf[strescseq.len], c, len);
2414                 strescseq.len += len;
2415                 return;
2416         }
2417
2418 check_control_code:
2419         /*
2420          * Actions of control codes must be performed as soon they arrive
2421          * because they can be embedded inside a control sequence, and
2422          * they must not cause conflicts with sequences.
2423          */
2424         if (control) {
2425                 tcontrolcode(u);
2426                 /*
2427                  * control codes are not shown ever
2428                  */
2429                 return;
2430         } else if (term.esc & ESC_START) {
2431                 if (term.esc & ESC_CSI) {
2432                         csiescseq.buf[csiescseq.len++] = u;
2433                         if (BETWEEN(u, 0x40, 0x7E)
2434                                         || csiescseq.len >= \
2435                                         sizeof(csiescseq.buf)-1) {
2436                                 term.esc = 0;
2437                                 csiparse();
2438                                 csihandle();
2439                         }
2440                         return;
2441                 } else if (term.esc & ESC_UTF8) {
2442                         tdefutf8(u);
2443                 } else if (term.esc & ESC_ALTCHARSET) {
2444                         tdeftran(u);
2445                 } else if (term.esc & ESC_TEST) {
2446                         tdectest(u);
2447                 } else {
2448                         if (!eschandle(u))
2449                                 return;
2450                         /* sequence already finished */
2451                 }
2452                 term.esc = 0;
2453                 /*
2454                  * All characters which form part of a sequence are not
2455                  * printed
2456                  */
2457                 return;
2458         }
2459         if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2460                 selclear();
2461
2462         gp = &term.line[term.c.y][term.c.x];
2463         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2464                 gp->mode |= ATTR_WRAP;
2465                 tnewline(1);
2466                 gp = &term.line[term.c.y][term.c.x];
2467         }
2468
2469         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2470                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2471
2472         if (term.c.x+width > term.col) {
2473                 tnewline(1);
2474                 gp = &term.line[term.c.y][term.c.x];
2475         }
2476
2477         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2478
2479         if (width == 2) {
2480                 gp->mode |= ATTR_WIDE;
2481                 if (term.c.x+1 < term.col) {
2482                         gp[1].u = '\0';
2483                         gp[1].mode = ATTR_WDUMMY;
2484                 }
2485         }
2486         if (term.c.x+width < term.col) {
2487                 tmoveto(term.c.x+width, term.c.y);
2488         } else {
2489                 term.c.state |= CURSOR_WRAPNEXT;
2490         }
2491 }
2492
2493 void
2494 tresize(int col, int row)
2495 {
2496         int i;
2497         int minrow = MIN(row, term.row);
2498         int mincol = MIN(col, term.col);
2499         int *bp;
2500         TCursor c;
2501
2502         if (col < 1 || row < 1) {
2503                 fprintf(stderr,
2504                         "tresize: error resizing to %dx%d\n", col, row);
2505                 return;
2506         }
2507
2508         /*
2509          * slide screen to keep cursor where we expect it -
2510          * tscrollup would work here, but we can optimize to
2511          * memmove because we're freeing the earlier lines
2512          */
2513         for (i = 0; i <= term.c.y - row; i++) {
2514                 free(term.line[i]);
2515                 free(term.alt[i]);
2516         }
2517         /* ensure that both src and dst are not NULL */
2518         if (i > 0) {
2519                 memmove(term.line, term.line + i, row * sizeof(Line));
2520                 memmove(term.alt, term.alt + i, row * sizeof(Line));
2521         }
2522         for (i += row; i < term.row; i++) {
2523                 free(term.line[i]);
2524                 free(term.alt[i]);
2525         }
2526
2527         /* resize to new width */
2528         term.specbuf = xrealloc(term.specbuf, col * sizeof(GlyphFontSpec));
2529
2530         /* resize to new height */
2531         term.line = xrealloc(term.line, row * sizeof(Line));
2532         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2533         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2534         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2535
2536         /* resize each row to new width, zero-pad if needed */
2537         for (i = 0; i < minrow; i++) {
2538                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2539                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2540         }
2541
2542         /* allocate any new rows */
2543         for (/* i = minrow */; i < row; i++) {
2544                 term.line[i] = xmalloc(col * sizeof(Glyph));
2545                 term.alt[i] = xmalloc(col * sizeof(Glyph));
2546         }
2547         if (col > term.col) {
2548                 bp = term.tabs + term.col;
2549
2550                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2551                 while (--bp > term.tabs && !*bp)
2552                         /* nothing */ ;
2553                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2554                         *bp = 1;
2555         }
2556         /* update terminal size */
2557         term.col = col;
2558         term.row = row;
2559         /* reset scrolling region */
2560         tsetscroll(0, row-1);
2561         /* make use of the LIMIT in tmoveto */
2562         tmoveto(term.c.x, term.c.y);
2563         /* Clearing both screens (it makes dirty all lines) */
2564         c = term.c;
2565         for (i = 0; i < 2; i++) {
2566                 if (mincol < col && 0 < minrow) {
2567                         tclearregion(mincol, 0, col - 1, minrow - 1);
2568                 }
2569                 if (0 < col && minrow < row) {
2570                         tclearregion(0, minrow, col - 1, row - 1);
2571                 }
2572                 tswapscreen();
2573                 tcursor(CURSOR_LOAD);
2574         }
2575         term.c = c;
2576 }
2577
2578 void
2579 zoom(const Arg *arg)
2580 {
2581         Arg larg;
2582
2583         larg.f = usedfontsize + arg->f;
2584         zoomabs(&larg);
2585 }
2586
2587 void
2588 zoomabs(const Arg *arg)
2589 {
2590         xunloadfonts();
2591         xloadfonts(usedfont, arg->f);
2592         cresize(0, 0);
2593         ttyresize();
2594         redraw();
2595         xhints();
2596 }
2597
2598 void
2599 zoomreset(const Arg *arg)
2600 {
2601         Arg larg;
2602
2603         if (defaultfontsize > 0) {
2604                 larg.f = defaultfontsize;
2605                 zoomabs(&larg);
2606         }
2607 }
2608
2609 void
2610 resettitle(void)
2611 {
2612         xsettitle(opt_title ? opt_title : "st");
2613 }
2614
2615 void
2616 redraw(void)
2617 {
2618         tfulldirt();
2619         draw();
2620 }
2621
2622 int
2623 match(uint mask, uint state)
2624 {
2625         return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
2626 }
2627
2628 void
2629 numlock(const Arg *dummy)
2630 {
2631         term.numlock ^= 1;
2632 }
2633
2634 char*
2635 kmap(KeySym k, uint state)
2636 {
2637         Key *kp;
2638         int i;
2639
2640         /* Check for mapped keys out of X11 function keys. */
2641         for (i = 0; i < LEN(mappedkeys); i++) {
2642                 if (mappedkeys[i] == k)
2643                         break;
2644         }
2645         if (i == LEN(mappedkeys)) {
2646                 if ((k & 0xFFFF) < 0xFD00)
2647                         return NULL;
2648         }
2649
2650         for (kp = key; kp < key + LEN(key); kp++) {
2651                 if (kp->k != k)
2652                         continue;
2653
2654                 if (!match(kp->mask, state))
2655                         continue;
2656
2657                 if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
2658                         continue;
2659                 if (term.numlock && kp->appkey == 2)
2660                         continue;
2661
2662                 if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
2663                         continue;
2664
2665                 if (IS_SET(MODE_CRLF) ? kp->crlf < 0 : kp->crlf > 0)
2666                         continue;
2667
2668                 return kp->s;
2669         }
2670
2671         return NULL;
2672 }
2673
2674 void
2675 cresize(int width, int height)
2676 {
2677         int col, row;
2678
2679         if (width != 0)
2680                 win.w = width;
2681         if (height != 0)
2682                 win.h = height;
2683
2684         col = (win.w - 2 * borderpx) / win.cw;
2685         row = (win.h - 2 * borderpx) / win.ch;
2686
2687         tresize(col, row);
2688         xresize(col, row);
2689 }
2690
2691 void
2692 usage(void)
2693 {
2694         die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
2695             " [-n name] [-o file]\n"
2696             "          [-T title] [-t title] [-w windowid]"
2697             " [[-e] command [args ...]]\n"
2698             "       %s [-aiv] [-c class] [-f font] [-g geometry]"
2699             " [-n name] [-o file]\n"
2700             "          [-T title] [-t title] [-w windowid] -l line"
2701             " [stty_args ...]\n", argv0, argv0);
2702 }