]> git.armaanb.net Git - st.git/blob - st.c
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 21:
1406                         term.c.attr.mode &= ~ATTR_BOLD;
1407                         break;
1408                 case 22:
1409                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1410                         break;
1411                 case 23:
1412                         term.c.attr.mode &= ~ATTR_ITALIC;
1413                         break;
1414                 case 24:
1415                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1416                         break;
1417                 case 25:
1418                         term.c.attr.mode &= ~ATTR_BLINK;
1419                         break;
1420                 case 27:
1421                         term.c.attr.mode &= ~ATTR_REVERSE;
1422                         break;
1423                 case 28:
1424                         term.c.attr.mode &= ~ATTR_INVISIBLE;
1425                         break;
1426                 case 29:
1427                         term.c.attr.mode &= ~ATTR_STRUCK;
1428                         break;
1429                 case 38:
1430                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1431                                 term.c.attr.fg = idx;
1432                         break;
1433                 case 39:
1434                         term.c.attr.fg = defaultfg;
1435                         break;
1436                 case 48:
1437                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1438                                 term.c.attr.bg = idx;
1439                         break;
1440                 case 49:
1441                         term.c.attr.bg = defaultbg;
1442                         break;
1443                 default:
1444                         if (BETWEEN(attr[i], 30, 37)) {
1445                                 term.c.attr.fg = attr[i] - 30;
1446                         } else if (BETWEEN(attr[i], 40, 47)) {
1447                                 term.c.attr.bg = attr[i] - 40;
1448                         } else if (BETWEEN(attr[i], 90, 97)) {
1449                                 term.c.attr.fg = attr[i] - 90 + 8;
1450                         } else if (BETWEEN(attr[i], 100, 107)) {
1451                                 term.c.attr.bg = attr[i] - 100 + 8;
1452                         } else {
1453                                 fprintf(stderr,
1454                                         "erresc(default): gfx attr %d unknown\n",
1455                                         attr[i]), csidump();
1456                         }
1457                         break;
1458                 }
1459         }
1460 }
1461
1462 void
1463 tsetscroll(int t, int b)
1464 {
1465         int temp;
1466
1467         LIMIT(t, 0, term.row-1);
1468         LIMIT(b, 0, term.row-1);
1469         if (t > b) {
1470                 temp = t;
1471                 t = b;
1472                 b = temp;
1473         }
1474         term.top = t;
1475         term.bot = b;
1476 }
1477
1478 void
1479 tsetmode(int priv, int set, int *args, int narg)
1480 {
1481         int *lim, mode;
1482         int alt;
1483
1484         for (lim = args + narg; args < lim; ++args) {
1485                 if (priv) {
1486                         switch (*args) {
1487                         case 1: /* DECCKM -- Cursor key */
1488                                 MODBIT(term.mode, set, MODE_APPCURSOR);
1489                                 break;
1490                         case 5: /* DECSCNM -- Reverse video */
1491                                 mode = term.mode;
1492                                 MODBIT(term.mode, set, MODE_REVERSE);
1493                                 if (mode != term.mode)
1494                                         redraw();
1495                                 break;
1496                         case 6: /* DECOM -- Origin */
1497                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1498                                 tmoveato(0, 0);
1499                                 break;
1500                         case 7: /* DECAWM -- Auto wrap */
1501                                 MODBIT(term.mode, set, MODE_WRAP);
1502                                 break;
1503                         case 0:  /* Error (IGNORED) */
1504                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1505                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1506                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1507                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1508                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1509                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1510                         case 42: /* DECNRCM -- National characters (IGNORED) */
1511                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1512                                 break;
1513                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1514                                 MODBIT(term.mode, !set, MODE_HIDE);
1515                                 break;
1516                         case 9:    /* X10 mouse compatibility mode */
1517                                 xsetpointermotion(0);
1518                                 MODBIT(term.mode, 0, MODE_MOUSE);
1519                                 MODBIT(term.mode, set, MODE_MOUSEX10);
1520                                 break;
1521                         case 1000: /* 1000: report button press */
1522                                 xsetpointermotion(0);
1523                                 MODBIT(term.mode, 0, MODE_MOUSE);
1524                                 MODBIT(term.mode, set, MODE_MOUSEBTN);
1525                                 break;
1526                         case 1002: /* 1002: report motion on button press */
1527                                 xsetpointermotion(0);
1528                                 MODBIT(term.mode, 0, MODE_MOUSE);
1529                                 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1530                                 break;
1531                         case 1003: /* 1003: enable all mouse motions */
1532                                 xsetpointermotion(set);
1533                                 MODBIT(term.mode, 0, MODE_MOUSE);
1534                                 MODBIT(term.mode, set, MODE_MOUSEMANY);
1535                                 break;
1536                         case 1004: /* 1004: send focus events to tty */
1537                                 MODBIT(term.mode, set, MODE_FOCUS);
1538                                 break;
1539                         case 1006: /* 1006: extended reporting mode */
1540                                 MODBIT(term.mode, set, MODE_MOUSESGR);
1541                                 break;
1542                         case 1034:
1543                                 MODBIT(term.mode, set, MODE_8BIT);
1544                                 break;
1545                         case 1049: /* swap screen & set/restore cursor as xterm */
1546                                 if (!allowaltscreen)
1547                                         break;
1548                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1549                                 /* FALLTHROUGH */
1550                         case 47: /* swap screen */
1551                         case 1047:
1552                                 if (!allowaltscreen)
1553                                         break;
1554                                 alt = IS_SET(MODE_ALTSCREEN);
1555                                 if (alt) {
1556                                         tclearregion(0, 0, term.col-1,
1557                                                         term.row-1);
1558                                 }
1559                                 if (set ^ alt) /* set is always 1 or 0 */
1560                                         tswapscreen();
1561                                 if (*args != 1049)
1562                                         break;
1563                                 /* FALLTHROUGH */
1564                         case 1048:
1565                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1566                                 break;
1567                         case 2004: /* 2004: bracketed paste mode */
1568                                 MODBIT(term.mode, set, MODE_BRCKTPASTE);
1569                                 break;
1570                         /* Not implemented mouse modes. See comments there. */
1571                         case 1001: /* mouse highlight mode; can hang the
1572                                       terminal by design when implemented. */
1573                         case 1005: /* UTF-8 mouse mode; will confuse
1574                                       applications not supporting UTF-8
1575                                       and luit. */
1576                         case 1015: /* urxvt mangled mouse mode; incompatible
1577                                       and can be mistaken for other control
1578                                       codes. */
1579                         default:
1580                                 fprintf(stderr,
1581                                         "erresc: unknown private set/reset mode %d\n",
1582                                         *args);
1583                                 break;
1584                         }
1585                 } else {
1586                         switch (*args) {
1587                         case 0:  /* Error (IGNORED) */
1588                                 break;
1589                         case 2:  /* KAM -- keyboard action */
1590                                 MODBIT(term.mode, set, MODE_KBDLOCK);
1591                                 break;
1592                         case 4:  /* IRM -- Insertion-replacement */
1593                                 MODBIT(term.mode, set, MODE_INSERT);
1594                                 break;
1595                         case 12: /* SRM -- Send/Receive */
1596                                 MODBIT(term.mode, !set, MODE_ECHO);
1597                                 break;
1598                         case 20: /* LNM -- Linefeed/new line */
1599                                 MODBIT(term.mode, set, MODE_CRLF);
1600                                 break;
1601                         default:
1602                                 fprintf(stderr,
1603                                         "erresc: unknown set/reset mode %d\n",
1604                                         *args);
1605                                 break;
1606                         }
1607                 }
1608         }
1609 }
1610
1611 void
1612 csihandle(void)
1613 {
1614         char buf[40];
1615         int len;
1616
1617         switch (csiescseq.mode[0]) {
1618         default:
1619         unknown:
1620                 fprintf(stderr, "erresc: unknown csi ");
1621                 csidump();
1622                 /* die(""); */
1623                 break;
1624         case '@': /* ICH -- Insert <n> blank char */
1625                 DEFAULT(csiescseq.arg[0], 1);
1626                 tinsertblank(csiescseq.arg[0]);
1627                 break;
1628         case 'A': /* CUU -- Cursor <n> Up */
1629                 DEFAULT(csiescseq.arg[0], 1);
1630                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1631                 break;
1632         case 'B': /* CUD -- Cursor <n> Down */
1633         case 'e': /* VPR --Cursor <n> Down */
1634                 DEFAULT(csiescseq.arg[0], 1);
1635                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1636                 break;
1637         case 'i': /* MC -- Media Copy */
1638                 switch (csiescseq.arg[0]) {
1639                 case 0:
1640                         tdump();
1641                         break;
1642                 case 1:
1643                         tdumpline(term.c.y);
1644                         break;
1645                 case 2:
1646                         tdumpsel();
1647                         break;
1648                 case 4:
1649                         term.mode &= ~MODE_PRINT;
1650                         break;
1651                 case 5:
1652                         term.mode |= MODE_PRINT;
1653                         break;
1654                 }
1655                 break;
1656         case 'c': /* DA -- Device Attributes */
1657                 if (csiescseq.arg[0] == 0)
1658                         ttywrite(vtiden, sizeof(vtiden) - 1);
1659                 break;
1660         case 'C': /* CUF -- Cursor <n> Forward */
1661         case 'a': /* HPR -- Cursor <n> Forward */
1662                 DEFAULT(csiescseq.arg[0], 1);
1663                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1664                 break;
1665         case 'D': /* CUB -- Cursor <n> Backward */
1666                 DEFAULT(csiescseq.arg[0], 1);
1667                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1668                 break;
1669         case 'E': /* CNL -- Cursor <n> Down and first col */
1670                 DEFAULT(csiescseq.arg[0], 1);
1671                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1672                 break;
1673         case 'F': /* CPL -- Cursor <n> Up and first col */
1674                 DEFAULT(csiescseq.arg[0], 1);
1675                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1676                 break;
1677         case 'g': /* TBC -- Tabulation clear */
1678                 switch (csiescseq.arg[0]) {
1679                 case 0: /* clear current tab stop */
1680                         term.tabs[term.c.x] = 0;
1681                         break;
1682                 case 3: /* clear all the tabs */
1683                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1684                         break;
1685                 default:
1686                         goto unknown;
1687                 }
1688                 break;
1689         case 'G': /* CHA -- Move to <col> */
1690         case '`': /* HPA */
1691                 DEFAULT(csiescseq.arg[0], 1);
1692                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1693                 break;
1694         case 'H': /* CUP -- Move to <row> <col> */
1695         case 'f': /* HVP */
1696                 DEFAULT(csiescseq.arg[0], 1);
1697                 DEFAULT(csiescseq.arg[1], 1);
1698                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1699                 break;
1700         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1701                 DEFAULT(csiescseq.arg[0], 1);
1702                 tputtab(csiescseq.arg[0]);
1703                 break;
1704         case 'J': /* ED -- Clear screen */
1705                 selclear();
1706                 switch (csiescseq.arg[0]) {
1707                 case 0: /* below */
1708                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1709                         if (term.c.y < term.row-1) {
1710                                 tclearregion(0, term.c.y+1, term.col-1,
1711                                                 term.row-1);
1712                         }
1713                         break;
1714                 case 1: /* above */
1715                         if (term.c.y > 1)
1716                                 tclearregion(0, 0, term.col-1, term.c.y-1);
1717                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1718                         break;
1719                 case 2: /* all */
1720                         tclearregion(0, 0, term.col-1, term.row-1);
1721                         break;
1722                 default:
1723                         goto unknown;
1724                 }
1725                 break;
1726         case 'K': /* EL -- Clear line */
1727                 switch (csiescseq.arg[0]) {
1728                 case 0: /* right */
1729                         tclearregion(term.c.x, term.c.y, term.col-1,
1730                                         term.c.y);
1731                         break;
1732                 case 1: /* left */
1733                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1734                         break;
1735                 case 2: /* all */
1736                         tclearregion(0, term.c.y, term.col-1, term.c.y);
1737                         break;
1738                 }
1739                 break;
1740         case 'S': /* SU -- Scroll <n> line up */
1741                 DEFAULT(csiescseq.arg[0], 1);
1742                 tscrollup(term.top, csiescseq.arg[0]);
1743                 break;
1744         case 'T': /* SD -- Scroll <n> line down */
1745                 DEFAULT(csiescseq.arg[0], 1);
1746                 tscrolldown(term.top, csiescseq.arg[0]);
1747                 break;
1748         case 'L': /* IL -- Insert <n> blank lines */
1749                 DEFAULT(csiescseq.arg[0], 1);
1750                 tinsertblankline(csiescseq.arg[0]);
1751                 break;
1752         case 'l': /* RM -- Reset Mode */
1753                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1754                 break;
1755         case 'M': /* DL -- Delete <n> lines */
1756                 DEFAULT(csiescseq.arg[0], 1);
1757                 tdeleteline(csiescseq.arg[0]);
1758                 break;
1759         case 'X': /* ECH -- Erase <n> char */
1760                 DEFAULT(csiescseq.arg[0], 1);
1761                 tclearregion(term.c.x, term.c.y,
1762                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1763                 break;
1764         case 'P': /* DCH -- Delete <n> char */
1765                 DEFAULT(csiescseq.arg[0], 1);
1766                 tdeletechar(csiescseq.arg[0]);
1767                 break;
1768         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1769                 DEFAULT(csiescseq.arg[0], 1);
1770                 tputtab(-csiescseq.arg[0]);
1771                 break;
1772         case 'd': /* VPA -- Move to <row> */
1773                 DEFAULT(csiescseq.arg[0], 1);
1774                 tmoveato(term.c.x, csiescseq.arg[0]-1);
1775                 break;
1776         case 'h': /* SM -- Set terminal mode */
1777                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1778                 break;
1779         case 'm': /* SGR -- Terminal attribute (color) */
1780                 tsetattr(csiescseq.arg, csiescseq.narg);
1781                 break;
1782         case 'n': /* DSR – Device Status Report (cursor position) */
1783                 if (csiescseq.arg[0] == 6) {
1784                         len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1785                                         term.c.y+1, term.c.x+1);
1786                         ttywrite(buf, len);
1787                 }
1788                 break;
1789         case 'r': /* DECSTBM -- Set Scrolling Region */
1790                 if (csiescseq.priv) {
1791                         goto unknown;
1792                 } else {
1793                         DEFAULT(csiescseq.arg[0], 1);
1794                         DEFAULT(csiescseq.arg[1], term.row);
1795                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1796                         tmoveato(0, 0);
1797                 }
1798                 break;
1799         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1800                 tcursor(CURSOR_SAVE);
1801                 break;
1802         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1803                 tcursor(CURSOR_LOAD);
1804                 break;
1805         case ' ':
1806                 switch (csiescseq.mode[1]) {
1807                 case 'q': /* DECSCUSR -- Set Cursor Style */
1808                         DEFAULT(csiescseq.arg[0], 1);
1809                         if (!BETWEEN(csiescseq.arg[0], 0, 6)) {
1810                                 goto unknown;
1811                         }
1812                         win.cursor = csiescseq.arg[0];
1813                         break;
1814                 default:
1815                         goto unknown;
1816                 }
1817                 break;
1818         }
1819 }
1820
1821 void
1822 csidump(void)
1823 {
1824         int i;
1825         uint c;
1826
1827         fprintf(stderr, "ESC[");
1828         for (i = 0; i < csiescseq.len; i++) {
1829                 c = csiescseq.buf[i] & 0xff;
1830                 if (isprint(c)) {
1831                         putc(c, stderr);
1832                 } else if (c == '\n') {
1833                         fprintf(stderr, "(\\n)");
1834                 } else if (c == '\r') {
1835                         fprintf(stderr, "(\\r)");
1836                 } else if (c == 0x1b) {
1837                         fprintf(stderr, "(\\e)");
1838                 } else {
1839                         fprintf(stderr, "(%02x)", c);
1840                 }
1841         }
1842         putc('\n', stderr);
1843 }
1844
1845 void
1846 csireset(void)
1847 {
1848         memset(&csiescseq, 0, sizeof(csiescseq));
1849 }
1850
1851 void
1852 strhandle(void)
1853 {
1854         char *p = NULL;
1855         int j, narg, par;
1856
1857         term.esc &= ~(ESC_STR_END|ESC_STR);
1858         strparse();
1859         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1860
1861         switch (strescseq.type) {
1862         case ']': /* OSC -- Operating System Command */
1863                 switch (par) {
1864                 case 0:
1865                 case 1:
1866                 case 2:
1867                         if (narg > 1)
1868                                 xsettitle(strescseq.args[1]);
1869                         return;
1870                 case 52:
1871                         if (narg > 2) {
1872                                 char *dec;
1873
1874                                 dec = base64dec(strescseq.args[2]);
1875                                 if (dec) {
1876                                         xsetsel(dec, CurrentTime);
1877                                         clipcopy(NULL);
1878                                 } else {
1879                                         fprintf(stderr, "erresc: invalid base64\n");
1880                                 }
1881                         }
1882                         return;
1883                 case 4: /* color set */
1884                         if (narg < 3)
1885                                 break;
1886                         p = strescseq.args[2];
1887                         /* FALLTHROUGH */
1888                 case 104: /* color reset, here p = NULL */
1889                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1890                         if (xsetcolorname(j, p)) {
1891                                 fprintf(stderr, "erresc: invalid color %s\n", p);
1892                         } else {
1893                                 /*
1894                                  * TODO if defaultbg color is changed, borders
1895                                  * are dirty
1896                                  */
1897                                 redraw();
1898                         }
1899                         return;
1900                 }
1901                 break;
1902         case 'k': /* old title set compatibility */
1903                 xsettitle(strescseq.args[0]);
1904                 return;
1905         case 'P': /* DCS -- Device Control String */
1906                 term.mode |= ESC_DCS;
1907         case '_': /* APC -- Application Program Command */
1908         case '^': /* PM -- Privacy Message */
1909                 return;
1910         }
1911
1912         fprintf(stderr, "erresc: unknown str ");
1913         strdump();
1914 }
1915
1916 void
1917 strparse(void)
1918 {
1919         int c;
1920         char *p = strescseq.buf;
1921
1922         strescseq.narg = 0;
1923         strescseq.buf[strescseq.len] = '\0';
1924
1925         if (*p == '\0')
1926                 return;
1927
1928         while (strescseq.narg < STR_ARG_SIZ) {
1929                 strescseq.args[strescseq.narg++] = p;
1930                 while ((c = *p) != ';' && c != '\0')
1931                         ++p;
1932                 if (c == '\0')
1933                         return;
1934                 *p++ = '\0';
1935         }
1936 }
1937
1938 void
1939 strdump(void)
1940 {
1941         int i;
1942         uint c;
1943
1944         fprintf(stderr, "ESC%c", strescseq.type);
1945         for (i = 0; i < strescseq.len; i++) {
1946                 c = strescseq.buf[i] & 0xff;
1947                 if (c == '\0') {
1948                         putc('\n', stderr);
1949                         return;
1950                 } else if (isprint(c)) {
1951                         putc(c, stderr);
1952                 } else if (c == '\n') {
1953                         fprintf(stderr, "(\\n)");
1954                 } else if (c == '\r') {
1955                         fprintf(stderr, "(\\r)");
1956                 } else if (c == 0x1b) {
1957                         fprintf(stderr, "(\\e)");
1958                 } else {
1959                         fprintf(stderr, "(%02x)", c);
1960                 }
1961         }
1962         fprintf(stderr, "ESC\\\n");
1963 }
1964
1965 void
1966 strreset(void)
1967 {
1968         memset(&strescseq, 0, sizeof(strescseq));
1969 }
1970
1971 void
1972 sendbreak(const Arg *arg)
1973 {
1974         if (tcsendbreak(cmdfd, 0))
1975                 perror("Error sending break");
1976 }
1977
1978 void
1979 tprinter(char *s, size_t len)
1980 {
1981         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1982                 fprintf(stderr, "Error writing in %s:%s\n",
1983                         opt_io, strerror(errno));
1984                 close(iofd);
1985                 iofd = -1;
1986         }
1987 }
1988
1989 void
1990 iso14755(const Arg *arg)
1991 {
1992         unsigned long id = xwinid();
1993         char cmd[sizeof(ISO14755CMD) + NUMMAXLEN(id)];
1994         FILE *p;
1995         char *us, *e, codepoint[9], uc[UTF_SIZ];
1996         unsigned long utf32;
1997
1998         snprintf(cmd, sizeof(cmd), ISO14755CMD, id);
1999         if (!(p = popen(cmd, "r")))
2000                 return;
2001
2002         us = fgets(codepoint, sizeof(codepoint), p);
2003         pclose(p);
2004
2005         if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
2006                 return;
2007         if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
2008             (*e != '\n' && *e != '\0'))
2009                 return;
2010
2011         ttysend(uc, utf8encode(utf32, uc));
2012 }
2013
2014 void
2015 toggleprinter(const Arg *arg)
2016 {
2017         term.mode ^= MODE_PRINT;
2018 }
2019
2020 void
2021 printscreen(const Arg *arg)
2022 {
2023         tdump();
2024 }
2025
2026 void
2027 printsel(const Arg *arg)
2028 {
2029         tdumpsel();
2030 }
2031
2032 void
2033 tdumpsel(void)
2034 {
2035         char *ptr;
2036
2037         if ((ptr = getsel())) {
2038                 tprinter(ptr, strlen(ptr));
2039                 free(ptr);
2040         }
2041 }
2042
2043 void
2044 tdumpline(int n)
2045 {
2046         char buf[UTF_SIZ];
2047         Glyph *bp, *end;
2048
2049         bp = &term.line[n][0];
2050         end = &bp[MIN(tlinelen(n), term.col) - 1];
2051         if (bp != end || bp->u != ' ') {
2052                 for ( ;bp <= end; ++bp)
2053                         tprinter(buf, utf8encode(bp->u, buf));
2054         }
2055         tprinter("\n", 1);
2056 }
2057
2058 void
2059 tdump(void)
2060 {
2061         int i;
2062
2063         for (i = 0; i < term.row; ++i)
2064                 tdumpline(i);
2065 }
2066
2067 void
2068 tputtab(int n)
2069 {
2070         uint x = term.c.x;
2071
2072         if (n > 0) {
2073                 while (x < term.col && n--)
2074                         for (++x; x < term.col && !term.tabs[x]; ++x)
2075                                 /* nothing */ ;
2076         } else if (n < 0) {
2077                 while (x > 0 && n++)
2078                         for (--x; x > 0 && !term.tabs[x]; --x)
2079                                 /* nothing */ ;
2080         }
2081         term.c.x = LIMIT(x, 0, term.col-1);
2082 }
2083
2084 void
2085 techo(Rune u)
2086 {
2087         if (ISCONTROL(u)) { /* control code */
2088                 if (u & 0x80) {
2089                         u &= 0x7f;
2090                         tputc('^');
2091                         tputc('[');
2092                 } else if (u != '\n' && u != '\r' && u != '\t') {
2093                         u ^= 0x40;
2094                         tputc('^');
2095                 }
2096         }
2097         tputc(u);
2098 }
2099
2100 void
2101 tdefutf8(char ascii)
2102 {
2103         if (ascii == 'G')
2104                 term.mode |= MODE_UTF8;
2105         else if (ascii == '@')
2106                 term.mode &= ~MODE_UTF8;
2107 }
2108
2109 void
2110 tdeftran(char ascii)
2111 {
2112         static char cs[] = "0B";
2113         static int vcs[] = {CS_GRAPHIC0, CS_USA};
2114         char *p;
2115
2116         if ((p = strchr(cs, ascii)) == NULL) {
2117                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2118         } else {
2119                 term.trantbl[term.icharset] = vcs[p - cs];
2120         }
2121 }
2122
2123 void
2124 tdectest(char c)
2125 {
2126         int x, y;
2127
2128         if (c == '8') { /* DEC screen alignment test. */
2129                 for (x = 0; x < term.col; ++x) {
2130                         for (y = 0; y < term.row; ++y)
2131                                 tsetchar('E', &term.c.attr, x, y);
2132                 }
2133         }
2134 }
2135
2136 void
2137 tstrsequence(uchar c)
2138 {
2139         strreset();
2140
2141         switch (c) {
2142         case 0x90:   /* DCS -- Device Control String */
2143                 c = 'P';
2144                 term.esc |= ESC_DCS;
2145                 break;
2146         case 0x9f:   /* APC -- Application Program Command */
2147                 c = '_';
2148                 break;
2149         case 0x9e:   /* PM -- Privacy Message */
2150                 c = '^';
2151                 break;
2152         case 0x9d:   /* OSC -- Operating System Command */
2153                 c = ']';
2154                 break;
2155         }
2156         strescseq.type = c;
2157         term.esc |= ESC_STR;
2158 }
2159
2160 void
2161 tcontrolcode(uchar ascii)
2162 {
2163         switch (ascii) {
2164         case '\t':   /* HT */
2165                 tputtab(1);
2166                 return;
2167         case '\b':   /* BS */
2168                 tmoveto(term.c.x-1, term.c.y);
2169                 return;
2170         case '\r':   /* CR */
2171                 tmoveto(0, term.c.y);
2172                 return;
2173         case '\f':   /* LF */
2174         case '\v':   /* VT */
2175         case '\n':   /* LF */
2176                 /* go to first col if the mode is set */
2177                 tnewline(IS_SET(MODE_CRLF));
2178                 return;
2179         case '\a':   /* BEL */
2180                 if (term.esc & ESC_STR_END) {
2181                         /* backwards compatibility to xterm */
2182                         strhandle();
2183                 } else {
2184                         if (!(win.state & WIN_FOCUSED))
2185                                 xseturgency(1);
2186                         if (bellvolume)
2187                                 xbell(bellvolume);
2188                 }
2189                 break;
2190         case '\033': /* ESC */
2191                 csireset();
2192                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2193                 term.esc |= ESC_START;
2194                 return;
2195         case '\016': /* SO (LS1 -- Locking shift 1) */
2196         case '\017': /* SI (LS0 -- Locking shift 0) */
2197                 term.charset = 1 - (ascii - '\016');
2198                 return;
2199         case '\032': /* SUB */
2200                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2201         case '\030': /* CAN */
2202                 csireset();
2203                 break;
2204         case '\005': /* ENQ (IGNORED) */
2205         case '\000': /* NUL (IGNORED) */
2206         case '\021': /* XON (IGNORED) */
2207         case '\023': /* XOFF (IGNORED) */
2208         case 0177:   /* DEL (IGNORED) */
2209                 return;
2210         case 0x80:   /* TODO: PAD */
2211         case 0x81:   /* TODO: HOP */
2212         case 0x82:   /* TODO: BPH */
2213         case 0x83:   /* TODO: NBH */
2214         case 0x84:   /* TODO: IND */
2215                 break;
2216         case 0x85:   /* NEL -- Next line */
2217                 tnewline(1); /* always go to first col */
2218                 break;
2219         case 0x86:   /* TODO: SSA */
2220         case 0x87:   /* TODO: ESA */
2221                 break;
2222         case 0x88:   /* HTS -- Horizontal tab stop */
2223                 term.tabs[term.c.x] = 1;
2224                 break;
2225         case 0x89:   /* TODO: HTJ */
2226         case 0x8a:   /* TODO: VTS */
2227         case 0x8b:   /* TODO: PLD */
2228         case 0x8c:   /* TODO: PLU */
2229         case 0x8d:   /* TODO: RI */
2230         case 0x8e:   /* TODO: SS2 */
2231         case 0x8f:   /* TODO: SS3 */
2232         case 0x91:   /* TODO: PU1 */
2233         case 0x92:   /* TODO: PU2 */
2234         case 0x93:   /* TODO: STS */
2235         case 0x94:   /* TODO: CCH */
2236         case 0x95:   /* TODO: MW */
2237         case 0x96:   /* TODO: SPA */
2238         case 0x97:   /* TODO: EPA */
2239         case 0x98:   /* TODO: SOS */
2240         case 0x99:   /* TODO: SGCI */
2241                 break;
2242         case 0x9a:   /* DECID -- Identify Terminal */
2243                 ttywrite(vtiden, sizeof(vtiden) - 1);
2244                 break;
2245         case 0x9b:   /* TODO: CSI */
2246         case 0x9c:   /* TODO: ST */
2247                 break;
2248         case 0x90:   /* DCS -- Device Control String */
2249         case 0x9d:   /* OSC -- Operating System Command */
2250         case 0x9e:   /* PM -- Privacy Message */
2251         case 0x9f:   /* APC -- Application Program Command */
2252                 tstrsequence(ascii);
2253                 return;
2254         }
2255         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2256         term.esc &= ~(ESC_STR_END|ESC_STR);
2257 }
2258
2259 /*
2260  * returns 1 when the sequence is finished and it hasn't to read
2261  * more characters for this sequence, otherwise 0
2262  */
2263 int
2264 eschandle(uchar ascii)
2265 {
2266         switch (ascii) {
2267         case '[':
2268                 term.esc |= ESC_CSI;
2269                 return 0;
2270         case '#':
2271                 term.esc |= ESC_TEST;
2272                 return 0;
2273         case '%':
2274                 term.esc |= ESC_UTF8;
2275                 return 0;
2276         case 'P': /* DCS -- Device Control String */
2277         case '_': /* APC -- Application Program Command */
2278         case '^': /* PM -- Privacy Message */
2279         case ']': /* OSC -- Operating System Command */
2280         case 'k': /* old title set compatibility */
2281                 tstrsequence(ascii);
2282                 return 0;
2283         case 'n': /* LS2 -- Locking shift 2 */
2284         case 'o': /* LS3 -- Locking shift 3 */
2285                 term.charset = 2 + (ascii - 'n');
2286                 break;
2287         case '(': /* GZD4 -- set primary charset G0 */
2288         case ')': /* G1D4 -- set secondary charset G1 */
2289         case '*': /* G2D4 -- set tertiary charset G2 */
2290         case '+': /* G3D4 -- set quaternary charset G3 */
2291                 term.icharset = ascii - '(';
2292                 term.esc |= ESC_ALTCHARSET;
2293                 return 0;
2294         case 'D': /* IND -- Linefeed */
2295                 if (term.c.y == term.bot) {
2296                         tscrollup(term.top, 1);
2297                 } else {
2298                         tmoveto(term.c.x, term.c.y+1);
2299                 }
2300                 break;
2301         case 'E': /* NEL -- Next line */
2302                 tnewline(1); /* always go to first col */
2303                 break;
2304         case 'H': /* HTS -- Horizontal tab stop */
2305                 term.tabs[term.c.x] = 1;
2306                 break;
2307         case 'M': /* RI -- Reverse index */
2308                 if (term.c.y == term.top) {
2309                         tscrolldown(term.top, 1);
2310                 } else {
2311                         tmoveto(term.c.x, term.c.y-1);
2312                 }
2313                 break;
2314         case 'Z': /* DECID -- Identify Terminal */
2315                 ttywrite(vtiden, sizeof(vtiden) - 1);
2316                 break;
2317         case 'c': /* RIS -- Reset to inital state */
2318                 treset();
2319                 resettitle();
2320                 xloadcols();
2321                 break;
2322         case '=': /* DECPAM -- Application keypad */
2323                 term.mode |= MODE_APPKEYPAD;
2324                 break;
2325         case '>': /* DECPNM -- Normal keypad */
2326                 term.mode &= ~MODE_APPKEYPAD;
2327                 break;
2328         case '7': /* DECSC -- Save Cursor */
2329                 tcursor(CURSOR_SAVE);
2330                 break;
2331         case '8': /* DECRC -- Restore Cursor */
2332                 tcursor(CURSOR_LOAD);
2333                 break;
2334         case '\\': /* ST -- String Terminator */
2335                 if (term.esc & ESC_STR_END)
2336                         strhandle();
2337                 break;
2338         default:
2339                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2340                         (uchar) ascii, isprint(ascii)? ascii:'.');
2341                 break;
2342         }
2343         return 1;
2344 }
2345
2346 void
2347 tputc(Rune u)
2348 {
2349         char c[UTF_SIZ];
2350         int control;
2351         int width, len;
2352         Glyph *gp;
2353
2354         control = ISCONTROL(u);
2355         if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2356                 c[0] = u;
2357                 width = len = 1;
2358         } else {
2359                 len = utf8encode(u, c);
2360                 if (!control && (width = wcwidth(u)) == -1) {
2361                         memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2362                         width = 1;
2363                 }
2364         }
2365
2366         if (IS_SET(MODE_PRINT))
2367                 tprinter(c, len);
2368
2369         /*
2370          * STR sequence must be checked before anything else
2371          * because it uses all following characters until it
2372          * receives a ESC, a SUB, a ST or any other C1 control
2373          * character.
2374          */
2375         if (term.esc & ESC_STR) {
2376                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2377                    ISCONTROLC1(u)) {
2378                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2379                         if (IS_SET(MODE_SIXEL)) {
2380                                 /* TODO: render sixel */;
2381                                 term.mode &= ~MODE_SIXEL;
2382                                 return;
2383                         }
2384                         term.esc |= ESC_STR_END;
2385                         goto check_control_code;
2386                 }
2387
2388
2389                 if (IS_SET(MODE_SIXEL)) {
2390                         /* TODO: implement sixel mode */
2391                         return;
2392                 }
2393                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2394                         term.mode |= MODE_SIXEL;
2395
2396                 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2397                         /*
2398                          * Here is a bug in terminals. If the user never sends
2399                          * some code to stop the str or esc command, then st
2400                          * will stop responding. But this is better than
2401                          * silently failing with unknown characters. At least
2402                          * then users will report back.
2403                          *
2404                          * In the case users ever get fixed, here is the code:
2405                          */
2406                         /*
2407                          * term.esc = 0;
2408                          * strhandle();
2409                          */
2410                         return;
2411                 }
2412
2413                 memmove(&strescseq.buf[strescseq.len], c, len);
2414                 strescseq.len += len;
2415                 return;
2416         }
2417
2418 check_control_code:
2419         /*
2420          * Actions of control codes must be performed as soon they arrive
2421          * because they can be embedded inside a control sequence, and
2422          * they must not cause conflicts with sequences.
2423          */
2424         if (control) {
2425                 tcontrolcode(u);
2426                 /*
2427                  * control codes are not shown ever
2428                  */
2429                 return;
2430         } else if (term.esc & ESC_START) {
2431                 if (term.esc & ESC_CSI) {
2432                         csiescseq.buf[csiescseq.len++] = u;
2433                         if (BETWEEN(u, 0x40, 0x7E)
2434                                         || csiescseq.len >= \
2435                                         sizeof(csiescseq.buf)-1) {
2436                                 term.esc = 0;
2437                                 csiparse();
2438                                 csihandle();
2439                         }
2440                         return;
2441                 } else if (term.esc & ESC_UTF8) {
2442                         tdefutf8(u);
2443                 } else if (term.esc & ESC_ALTCHARSET) {
2444                         tdeftran(u);
2445                 } else if (term.esc & ESC_TEST) {
2446                         tdectest(u);
2447                 } else {
2448                         if (!eschandle(u))
2449                                 return;
2450                         /* sequence already finished */
2451                 }
2452                 term.esc = 0;
2453                 /*
2454                  * All characters which form part of a sequence are not
2455                  * printed
2456                  */
2457                 return;
2458         }
2459         if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2460                 selclear();
2461
2462         gp = &term.line[term.c.y][term.c.x];
2463         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2464                 gp->mode |= ATTR_WRAP;
2465                 tnewline(1);
2466                 gp = &term.line[term.c.y][term.c.x];
2467         }
2468
2469         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2470                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2471
2472         if (term.c.x+width > term.col) {
2473                 tnewline(1);
2474                 gp = &term.line[term.c.y][term.c.x];
2475         }
2476
2477         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2478
2479         if (width == 2) {
2480                 gp->mode |= ATTR_WIDE;
2481                 if (term.c.x+1 < term.col) {
2482                         gp[1].u = '\0';
2483                         gp[1].mode = ATTR_WDUMMY;
2484                 }
2485         }
2486         if (term.c.x+width < term.col) {
2487                 tmoveto(term.c.x+width, term.c.y);
2488         } else {
2489                 term.c.state |= CURSOR_WRAPNEXT;
2490         }
2491 }
2492
2493 void
2494 tresize(int col, int row)
2495 {
2496         int i;
2497         int minrow = MIN(row, term.row);
2498         int mincol = MIN(col, term.col);
2499         int *bp;
2500         TCursor c;
2501
2502         if (col < 1 || row < 1) {
2503                 fprintf(stderr,
2504                         "tresize: error resizing to %dx%d\n", col, row);
2505                 return;
2506         }
2507
2508         /*
2509          * slide screen to keep cursor where we expect it -
2510          * tscrollup would work here, but we can optimize to
2511          * memmove because we're freeing the earlier lines
2512          */
2513         for (i = 0; i <= term.c.y - row; i++) {
2514                 free(term.line[i]);
2515                 free(term.alt[i]);
2516         }
2517         /* ensure that both src and dst are not NULL */
2518         if (i > 0) {
2519                 memmove(term.line, term.line + i, row * sizeof(Line));
2520                 memmove(term.alt, term.alt + i, row * sizeof(Line));
2521         }
2522         for (i += row; i < term.row; i++) {
2523                 free(term.line[i]);
2524                 free(term.alt[i]);
2525         }
2526
2527         /* resize to new width */
2528         term.specbuf = xrealloc(term.specbuf, col * sizeof(GlyphFontSpec));
2529
2530         /* resize to new height */
2531         term.line = xrealloc(term.line, row * sizeof(Line));
2532         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2533         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2534         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2535
2536         /* resize each row to new width, zero-pad if needed */
2537         for (i = 0; i < minrow; i++) {
2538                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2539                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2540         }
2541
2542         /* allocate any new rows */
2543         for (/* i = minrow */; i < row; i++) {
2544                 term.line[i] = xmalloc(col * sizeof(Glyph));
2545                 term.alt[i] = xmalloc(col * sizeof(Glyph));
2546         }
2547         if (col > term.col) {
2548                 bp = term.tabs + term.col;
2549
2550                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2551                 while (--bp > term.tabs && !*bp)
2552                         /* nothing */ ;
2553                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2554                         *bp = 1;
2555         }
2556         /* update terminal size */
2557         term.col = col;
2558         term.row = row;
2559         /* reset scrolling region */
2560         tsetscroll(0, row-1);
2561         /* make use of the LIMIT in tmoveto */
2562         tmoveto(term.c.x, term.c.y);
2563         /* Clearing both screens (it makes dirty all lines) */
2564         c = term.c;
2565         for (i = 0; i < 2; i++) {
2566                 if (mincol < col && 0 < minrow) {
2567                         tclearregion(mincol, 0, col - 1, minrow - 1);
2568                 }
2569                 if (0 < col && minrow < row) {
2570                         tclearregion(0, minrow, col - 1, row - 1);
2571                 }
2572                 tswapscreen();
2573                 tcursor(CURSOR_LOAD);
2574         }
2575         term.c = c;
2576 }
2577
2578 void
2579 zoom(const Arg *arg)
2580 {
2581         Arg larg;
2582
2583         larg.f = usedfontsize + arg->f;
2584         zoomabs(&larg);
2585 }
2586
2587 void
2588 zoomabs(const Arg *arg)
2589 {
2590         xunloadfonts();
2591         xloadfonts(usedfont, arg->f);
2592         cresize(0, 0);
2593         ttyresize();
2594         redraw();
2595         xhints();
2596 }
2597
2598 void
2599 zoomreset(const Arg *arg)
2600 {
2601         Arg larg;
2602
2603         if (defaultfontsize > 0) {
2604                 larg.f = defaultfontsize;
2605                 zoomabs(&larg);
2606         }
2607 }
2608
2609 void
2610 resettitle(void)
2611 {
2612         xsettitle(opt_title ? opt_title : "st");
2613 }
2614
2615 void
2616 redraw(void)
2617 {
2618         tfulldirt();
2619         draw();
2620 }
2621
2622 int
2623 match(uint mask, uint state)
2624 {
2625         return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
2626 }
2627
2628 void
2629 numlock(const Arg *dummy)
2630 {
2631         term.numlock ^= 1;
2632 }
2633
2634 char*
2635 kmap(KeySym k, uint state)
2636 {
2637         Key *kp;
2638         int i;
2639
2640         /* Check for mapped keys out of X11 function keys. */
2641         for (i = 0; i < LEN(mappedkeys); i++) {
2642                 if (mappedkeys[i] == k)
2643                         break;
2644         }
2645         if (i == LEN(mappedkeys)) {
2646                 if ((k & 0xFFFF) < 0xFD00)
2647                         return NULL;
2648         }
2649
2650         for (kp = key; kp < key + LEN(key); kp++) {
2651                 if (kp->k != k)
2652                         continue;
2653
2654                 if (!match(kp->mask, state))
2655                         continue;
2656
2657                 if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
2658                         continue;
2659                 if (term.numlock && kp->appkey == 2)
2660                         continue;
2661
2662                 if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
2663                         continue;
2664
2665                 if (IS_SET(MODE_CRLF) ? kp->crlf < 0 : kp->crlf > 0)
2666                         continue;
2667
2668                 return kp->s;
2669         }
2670
2671         return NULL;
2672 }
2673
2674 void
2675 cresize(int width, int height)
2676 {
2677         int col, row;
2678
2679         if (width != 0)
2680                 win.w = width;
2681         if (height != 0)
2682                 win.h = height;
2683
2684         col = (win.w - 2 * borderpx) / win.cw;
2685         row = (win.h - 2 * borderpx) / win.ch;
2686
2687         tresize(col, row);
2688         xresize(col, row);
2689 }
2690
2691 void
2692 usage(void)
2693 {
2694         die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
2695             " [-n name] [-o file]\n"
2696             "          [-T title] [-t title] [-w windowid]"
2697             " [[-e] command [args ...]]\n"
2698             "       %s [-aiv] [-c class] [-f font] [-g geometry]"
2699             " [-n name] [-o file]\n"
2700             "          [-T title] [-t title] [-w windowid] -l line"
2701             " [stty_args ...]\n", argv0, argv0);
2702 }