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