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