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