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