]> git.armaanb.net Git - st.git/blob - st.c
Remove X and fontconfig from 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 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         struct winsize w = {term.row, term.col, 0, 0};
674
675         if (out) {
676                 term.mode |= MODE_PRINT;
677                 iofd = (!strcmp(out, "-")) ?
678                           1 : open(out, O_WRONLY | O_CREAT, 0666);
679                 if (iofd < 0) {
680                         fprintf(stderr, "Error opening %s:%s\n",
681                                 out, strerror(errno));
682                 }
683         }
684
685         if (line) {
686                 if ((cmdfd = open(line, O_RDWR)) < 0)
687                         die("open line failed: %s\n", strerror(errno));
688                 dup2(cmdfd, 0);
689                 stty(args);
690                 return;
691         }
692
693         /* seems to work fine on linux, openbsd and freebsd */
694         if (openpty(&m, &s, NULL, NULL, &w) < 0)
695                 die("openpty failed: %s\n", strerror(errno));
696
697         switch (pid = fork()) {
698         case -1:
699                 die("fork failed\n");
700                 break;
701         case 0:
702                 close(iofd);
703                 setsid(); /* create a new process group */
704                 dup2(s, 0);
705                 dup2(s, 1);
706                 dup2(s, 2);
707                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
708                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
709                 close(s);
710                 close(m);
711                 execsh(args);
712                 break;
713         default:
714                 close(s);
715                 cmdfd = m;
716                 signal(SIGCHLD, sigchld);
717                 break;
718         }
719 }
720
721 size_t
722 ttyread(void)
723 {
724         static char buf[BUFSIZ];
725         static int buflen = 0;
726         int written;
727         int ret;
728
729         /* append read bytes to unprocessed bytes */
730         if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
731                 die("Couldn't read from shell: %s\n", strerror(errno));
732         buflen += ret;
733
734         written = twrite(buf, buflen, 0);
735         buflen -= written;
736         /* keep any uncomplete utf8 char for the next call */
737         if (buflen > 0)
738                 memmove(buf, buf + written, buflen);
739
740         return ret;
741 }
742
743 void
744 ttywrite(const char *s, size_t n)
745 {
746         fd_set wfd, rfd;
747         ssize_t r;
748         size_t lim = 256;
749
750         /*
751          * Remember that we are using a pty, which might be a modem line.
752          * Writing too much will clog the line. That's why we are doing this
753          * dance.
754          * FIXME: Migrate the world to Plan 9.
755          */
756         while (n > 0) {
757                 FD_ZERO(&wfd);
758                 FD_ZERO(&rfd);
759                 FD_SET(cmdfd, &wfd);
760                 FD_SET(cmdfd, &rfd);
761
762                 /* Check if we can write. */
763                 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
764                         if (errno == EINTR)
765                                 continue;
766                         die("select failed: %s\n", strerror(errno));
767                 }
768                 if (FD_ISSET(cmdfd, &wfd)) {
769                         /*
770                          * Only write the bytes written by ttywrite() or the
771                          * default of 256. This seems to be a reasonable value
772                          * for a serial line. Bigger values might clog the I/O.
773                          */
774                         if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
775                                 goto write_error;
776                         if (r < n) {
777                                 /*
778                                  * We weren't able to write out everything.
779                                  * This means the buffer is getting full
780                                  * again. Empty it.
781                                  */
782                                 if (n < lim)
783                                         lim = ttyread();
784                                 n -= r;
785                                 s += r;
786                         } else {
787                                 /* All bytes have been written. */
788                                 break;
789                         }
790                 }
791                 if (FD_ISSET(cmdfd, &rfd))
792                         lim = ttyread();
793         }
794         return;
795
796 write_error:
797         die("write error on tty: %s\n", strerror(errno));
798 }
799
800 void
801 ttysend(char *s, size_t n)
802 {
803         ttywrite(s, n);
804         if (IS_SET(MODE_ECHO))
805                 twrite(s, n, 1);
806 }
807
808 void
809 ttyresize(int tw, int th)
810 {
811         struct winsize w;
812
813         w.ws_row = term.row;
814         w.ws_col = term.col;
815         w.ws_xpixel = tw;
816         w.ws_ypixel = th;
817         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
818                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
819 }
820
821 int
822 tattrset(int attr)
823 {
824         int i, j;
825
826         for (i = 0; i < term.row-1; i++) {
827                 for (j = 0; j < term.col-1; j++) {
828                         if (term.line[i][j].mode & attr)
829                                 return 1;
830                 }
831         }
832
833         return 0;
834 }
835
836 void
837 tsetdirt(int top, int bot)
838 {
839         int i;
840
841         LIMIT(top, 0, term.row-1);
842         LIMIT(bot, 0, term.row-1);
843
844         for (i = top; i <= bot; i++)
845                 term.dirty[i] = 1;
846 }
847
848 void
849 tsetdirtattr(int attr)
850 {
851         int i, j;
852
853         for (i = 0; i < term.row-1; i++) {
854                 for (j = 0; j < term.col-1; j++) {
855                         if (term.line[i][j].mode & attr) {
856                                 tsetdirt(i, i);
857                                 break;
858                         }
859                 }
860         }
861 }
862
863 void
864 tfulldirt(void)
865 {
866         tsetdirt(0, term.row-1);
867 }
868
869 void
870 tcursor(int mode)
871 {
872         static TCursor c[2];
873         int alt = IS_SET(MODE_ALTSCREEN);
874
875         if (mode == CURSOR_SAVE) {
876                 c[alt] = term.c;
877         } else if (mode == CURSOR_LOAD) {
878                 term.c = c[alt];
879                 tmoveto(c[alt].x, c[alt].y);
880         }
881 }
882
883 void
884 treset(void)
885 {
886         uint i;
887
888         term.c = (TCursor){{
889                 .mode = ATTR_NULL,
890                 .fg = defaultfg,
891                 .bg = defaultbg
892         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
893
894         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
895         for (i = tabspaces; i < term.col; i += tabspaces)
896                 term.tabs[i] = 1;
897         term.top = 0;
898         term.bot = term.row - 1;
899         term.mode = MODE_WRAP|MODE_UTF8;
900         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
901         term.charset = 0;
902
903         for (i = 0; i < 2; i++) {
904                 tmoveto(0, 0);
905                 tcursor(CURSOR_SAVE);
906                 tclearregion(0, 0, term.col-1, term.row-1);
907                 tswapscreen();
908         }
909 }
910
911 void
912 tnew(int col, int row)
913 {
914         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
915         tresize(col, row);
916         term.numlock = 1;
917
918         treset();
919 }
920
921 void
922 tswapscreen(void)
923 {
924         Line *tmp = term.line;
925
926         term.line = term.alt;
927         term.alt = tmp;
928         term.mode ^= MODE_ALTSCREEN;
929         tfulldirt();
930 }
931
932 void
933 tscrolldown(int orig, int n)
934 {
935         int i;
936         Line temp;
937
938         LIMIT(n, 0, term.bot-orig+1);
939
940         tsetdirt(orig, term.bot-n);
941         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
942
943         for (i = term.bot; i >= orig+n; i--) {
944                 temp = term.line[i];
945                 term.line[i] = term.line[i-n];
946                 term.line[i-n] = temp;
947         }
948
949         selscroll(orig, n);
950 }
951
952 void
953 tscrollup(int orig, int n)
954 {
955         int i;
956         Line temp;
957
958         LIMIT(n, 0, term.bot-orig+1);
959
960         tclearregion(0, orig, term.col-1, orig+n-1);
961         tsetdirt(orig+n, term.bot);
962
963         for (i = orig; i <= term.bot-n; i++) {
964                 temp = term.line[i];
965                 term.line[i] = term.line[i+n];
966                 term.line[i+n] = temp;
967         }
968
969         selscroll(orig, -n);
970 }
971
972 void
973 selscroll(int orig, int n)
974 {
975         if (sel.ob.x == -1)
976                 return;
977
978         if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
979                 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
980                         selclear();
981                         return;
982                 }
983                 if (sel.type == SEL_RECTANGULAR) {
984                         if (sel.ob.y < term.top)
985                                 sel.ob.y = term.top;
986                         if (sel.oe.y > term.bot)
987                                 sel.oe.y = term.bot;
988                 } else {
989                         if (sel.ob.y < term.top) {
990                                 sel.ob.y = term.top;
991                                 sel.ob.x = 0;
992                         }
993                         if (sel.oe.y > term.bot) {
994                                 sel.oe.y = term.bot;
995                                 sel.oe.x = term.col;
996                         }
997                 }
998                 selnormalize();
999         }
1000 }
1001
1002 void
1003 tnewline(int first_col)
1004 {
1005         int y = term.c.y;
1006
1007         if (y == term.bot) {
1008                 tscrollup(term.top, 1);
1009         } else {
1010                 y++;
1011         }
1012         tmoveto(first_col ? 0 : term.c.x, y);
1013 }
1014
1015 void
1016 csiparse(void)
1017 {
1018         char *p = csiescseq.buf, *np;
1019         long int v;
1020
1021         csiescseq.narg = 0;
1022         if (*p == '?') {
1023                 csiescseq.priv = 1;
1024                 p++;
1025         }
1026
1027         csiescseq.buf[csiescseq.len] = '\0';
1028         while (p < csiescseq.buf+csiescseq.len) {
1029                 np = NULL;
1030                 v = strtol(p, &np, 10);
1031                 if (np == p)
1032                         v = 0;
1033                 if (v == LONG_MAX || v == LONG_MIN)
1034                         v = -1;
1035                 csiescseq.arg[csiescseq.narg++] = v;
1036                 p = np;
1037                 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1038                         break;
1039                 p++;
1040         }
1041         csiescseq.mode[0] = *p++;
1042         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1043 }
1044
1045 /* for absolute user moves, when decom is set */
1046 void
1047 tmoveato(int x, int y)
1048 {
1049         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1050 }
1051
1052 void
1053 tmoveto(int x, int y)
1054 {
1055         int miny, maxy;
1056
1057         if (term.c.state & CURSOR_ORIGIN) {
1058                 miny = term.top;
1059                 maxy = term.bot;
1060         } else {
1061                 miny = 0;
1062                 maxy = term.row - 1;
1063         }
1064         term.c.state &= ~CURSOR_WRAPNEXT;
1065         term.c.x = LIMIT(x, 0, term.col-1);
1066         term.c.y = LIMIT(y, miny, maxy);
1067 }
1068
1069 void
1070 tsetchar(Rune u, Glyph *attr, int x, int y)
1071 {
1072         static char *vt100_0[62] = { /* 0x41 - 0x7e */
1073                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1074                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1075                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1076                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1077                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1078                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1079                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1080                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1081         };
1082
1083         /*
1084          * The table is proudly stolen from rxvt.
1085          */
1086         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1087            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1088                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1089
1090         if (term.line[y][x].mode & ATTR_WIDE) {
1091                 if (x+1 < term.col) {
1092                         term.line[y][x+1].u = ' ';
1093                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1094                 }
1095         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1096                 term.line[y][x-1].u = ' ';
1097                 term.line[y][x-1].mode &= ~ATTR_WIDE;
1098         }
1099
1100         term.dirty[y] = 1;
1101         term.line[y][x] = *attr;
1102         term.line[y][x].u = u;
1103 }
1104
1105 void
1106 tclearregion(int x1, int y1, int x2, int y2)
1107 {
1108         int x, y, temp;
1109         Glyph *gp;
1110
1111         if (x1 > x2)
1112                 temp = x1, x1 = x2, x2 = temp;
1113         if (y1 > y2)
1114                 temp = y1, y1 = y2, y2 = temp;
1115
1116         LIMIT(x1, 0, term.col-1);
1117         LIMIT(x2, 0, term.col-1);
1118         LIMIT(y1, 0, term.row-1);
1119         LIMIT(y2, 0, term.row-1);
1120
1121         for (y = y1; y <= y2; y++) {
1122                 term.dirty[y] = 1;
1123                 for (x = x1; x <= x2; x++) {
1124                         gp = &term.line[y][x];
1125                         if (selected(x, y))
1126                                 selclear();
1127                         gp->fg = term.c.attr.fg;
1128                         gp->bg = term.c.attr.bg;
1129                         gp->mode = 0;
1130                         gp->u = ' ';
1131                 }
1132         }
1133 }
1134
1135 void
1136 tdeletechar(int n)
1137 {
1138         int dst, src, size;
1139         Glyph *line;
1140
1141         LIMIT(n, 0, term.col - term.c.x);
1142
1143         dst = term.c.x;
1144         src = term.c.x + n;
1145         size = term.col - src;
1146         line = term.line[term.c.y];
1147
1148         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1149         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1150 }
1151
1152 void
1153 tinsertblank(int n)
1154 {
1155         int dst, src, size;
1156         Glyph *line;
1157
1158         LIMIT(n, 0, term.col - term.c.x);
1159
1160         dst = term.c.x + n;
1161         src = term.c.x;
1162         size = term.col - dst;
1163         line = term.line[term.c.y];
1164
1165         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1166         tclearregion(src, term.c.y, dst - 1, term.c.y);
1167 }
1168
1169 void
1170 tinsertblankline(int n)
1171 {
1172         if (BETWEEN(term.c.y, term.top, term.bot))
1173                 tscrolldown(term.c.y, n);
1174 }
1175
1176 void
1177 tdeleteline(int n)
1178 {
1179         if (BETWEEN(term.c.y, term.top, term.bot))
1180                 tscrollup(term.c.y, n);
1181 }
1182
1183 int32_t
1184 tdefcolor(int *attr, int *npar, int l)
1185 {
1186         int32_t idx = -1;
1187         uint r, g, b;
1188
1189         switch (attr[*npar + 1]) {
1190         case 2: /* direct color in RGB space */
1191                 if (*npar + 4 >= l) {
1192                         fprintf(stderr,
1193                                 "erresc(38): Incorrect number of parameters (%d)\n",
1194                                 *npar);
1195                         break;
1196                 }
1197                 r = attr[*npar + 2];
1198                 g = attr[*npar + 3];
1199                 b = attr[*npar + 4];
1200                 *npar += 4;
1201                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1202                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1203                                 r, g, b);
1204                 else
1205                         idx = TRUECOLOR(r, g, b);
1206                 break;
1207         case 5: /* indexed color */
1208                 if (*npar + 2 >= l) {
1209                         fprintf(stderr,
1210                                 "erresc(38): Incorrect number of parameters (%d)\n",
1211                                 *npar);
1212                         break;
1213                 }
1214                 *npar += 2;
1215                 if (!BETWEEN(attr[*npar], 0, 255))
1216                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1217                 else
1218                         idx = attr[*npar];
1219                 break;
1220         case 0: /* implemented defined (only foreground) */
1221         case 1: /* transparent */
1222         case 3: /* direct color in CMY space */
1223         case 4: /* direct color in CMYK space */
1224         default:
1225                 fprintf(stderr,
1226                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1227                 break;
1228         }
1229
1230         return idx;
1231 }
1232
1233 void
1234 tsetattr(int *attr, int l)
1235 {
1236         int i;
1237         int32_t idx;
1238
1239         for (i = 0; i < l; i++) {
1240                 switch (attr[i]) {
1241                 case 0:
1242                         term.c.attr.mode &= ~(
1243                                 ATTR_BOLD       |
1244                                 ATTR_FAINT      |
1245                                 ATTR_ITALIC     |
1246                                 ATTR_UNDERLINE  |
1247                                 ATTR_BLINK      |
1248                                 ATTR_REVERSE    |
1249                                 ATTR_INVISIBLE  |
1250                                 ATTR_STRUCK     );
1251                         term.c.attr.fg = defaultfg;
1252                         term.c.attr.bg = defaultbg;
1253                         break;
1254                 case 1:
1255                         term.c.attr.mode |= ATTR_BOLD;
1256                         break;
1257                 case 2:
1258                         term.c.attr.mode |= ATTR_FAINT;
1259                         break;
1260                 case 3:
1261                         term.c.attr.mode |= ATTR_ITALIC;
1262                         break;
1263                 case 4:
1264                         term.c.attr.mode |= ATTR_UNDERLINE;
1265                         break;
1266                 case 5: /* slow blink */
1267                         /* FALLTHROUGH */
1268                 case 6: /* rapid blink */
1269                         term.c.attr.mode |= ATTR_BLINK;
1270                         break;
1271                 case 7:
1272                         term.c.attr.mode |= ATTR_REVERSE;
1273                         break;
1274                 case 8:
1275                         term.c.attr.mode |= ATTR_INVISIBLE;
1276                         break;
1277                 case 9:
1278                         term.c.attr.mode |= ATTR_STRUCK;
1279                         break;
1280                 case 22:
1281                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1282                         break;
1283                 case 23:
1284                         term.c.attr.mode &= ~ATTR_ITALIC;
1285                         break;
1286                 case 24:
1287                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1288                         break;
1289                 case 25:
1290                         term.c.attr.mode &= ~ATTR_BLINK;
1291                         break;
1292                 case 27:
1293                         term.c.attr.mode &= ~ATTR_REVERSE;
1294                         break;
1295                 case 28:
1296                         term.c.attr.mode &= ~ATTR_INVISIBLE;
1297                         break;
1298                 case 29:
1299                         term.c.attr.mode &= ~ATTR_STRUCK;
1300                         break;
1301                 case 38:
1302                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1303                                 term.c.attr.fg = idx;
1304                         break;
1305                 case 39:
1306                         term.c.attr.fg = defaultfg;
1307                         break;
1308                 case 48:
1309                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1310                                 term.c.attr.bg = idx;
1311                         break;
1312                 case 49:
1313                         term.c.attr.bg = defaultbg;
1314                         break;
1315                 default:
1316                         if (BETWEEN(attr[i], 30, 37)) {
1317                                 term.c.attr.fg = attr[i] - 30;
1318                         } else if (BETWEEN(attr[i], 40, 47)) {
1319                                 term.c.attr.bg = attr[i] - 40;
1320                         } else if (BETWEEN(attr[i], 90, 97)) {
1321                                 term.c.attr.fg = attr[i] - 90 + 8;
1322                         } else if (BETWEEN(attr[i], 100, 107)) {
1323                                 term.c.attr.bg = attr[i] - 100 + 8;
1324                         } else {
1325                                 fprintf(stderr,
1326                                         "erresc(default): gfx attr %d unknown\n",
1327                                         attr[i]), csidump();
1328                         }
1329                         break;
1330                 }
1331         }
1332 }
1333
1334 void
1335 tsetscroll(int t, int b)
1336 {
1337         int temp;
1338
1339         LIMIT(t, 0, term.row-1);
1340         LIMIT(b, 0, term.row-1);
1341         if (t > b) {
1342                 temp = t;
1343                 t = b;
1344                 b = temp;
1345         }
1346         term.top = t;
1347         term.bot = b;
1348 }
1349
1350 void
1351 tsetmode(int priv, int set, int *args, int narg)
1352 {
1353         int *lim, mode;
1354         int alt;
1355
1356         for (lim = args + narg; args < lim; ++args) {
1357                 if (priv) {
1358                         switch (*args) {
1359                         case 1: /* DECCKM -- Cursor key */
1360                                 MODBIT(term.mode, set, MODE_APPCURSOR);
1361                                 break;
1362                         case 5: /* DECSCNM -- Reverse video */
1363                                 mode = term.mode;
1364                                 MODBIT(term.mode, set, MODE_REVERSE);
1365                                 if (mode != term.mode)
1366                                         redraw();
1367                                 break;
1368                         case 6: /* DECOM -- Origin */
1369                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1370                                 tmoveato(0, 0);
1371                                 break;
1372                         case 7: /* DECAWM -- Auto wrap */
1373                                 MODBIT(term.mode, set, MODE_WRAP);
1374                                 break;
1375                         case 0:  /* Error (IGNORED) */
1376                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1377                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1378                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1379                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1380                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1381                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1382                         case 42: /* DECNRCM -- National characters (IGNORED) */
1383                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1384                                 break;
1385                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1386                                 MODBIT(term.mode, !set, MODE_HIDE);
1387                                 break;
1388                         case 9:    /* X10 mouse compatibility mode */
1389                                 xsetpointermotion(0);
1390                                 MODBIT(term.mode, 0, MODE_MOUSE);
1391                                 MODBIT(term.mode, set, MODE_MOUSEX10);
1392                                 break;
1393                         case 1000: /* 1000: report button press */
1394                                 xsetpointermotion(0);
1395                                 MODBIT(term.mode, 0, MODE_MOUSE);
1396                                 MODBIT(term.mode, set, MODE_MOUSEBTN);
1397                                 break;
1398                         case 1002: /* 1002: report motion on button press */
1399                                 xsetpointermotion(0);
1400                                 MODBIT(term.mode, 0, MODE_MOUSE);
1401                                 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1402                                 break;
1403                         case 1003: /* 1003: enable all mouse motions */
1404                                 xsetpointermotion(set);
1405                                 MODBIT(term.mode, 0, MODE_MOUSE);
1406                                 MODBIT(term.mode, set, MODE_MOUSEMANY);
1407                                 break;
1408                         case 1004: /* 1004: send focus events to tty */
1409                                 MODBIT(term.mode, set, MODE_FOCUS);
1410                                 break;
1411                         case 1006: /* 1006: extended reporting mode */
1412                                 MODBIT(term.mode, set, MODE_MOUSESGR);
1413                                 break;
1414                         case 1034:
1415                                 MODBIT(term.mode, set, MODE_8BIT);
1416                                 break;
1417                         case 1049: /* swap screen & set/restore cursor as xterm */
1418                                 if (!allowaltscreen)
1419                                         break;
1420                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1421                                 /* FALLTHROUGH */
1422                         case 47: /* swap screen */
1423                         case 1047:
1424                                 if (!allowaltscreen)
1425                                         break;
1426                                 alt = IS_SET(MODE_ALTSCREEN);
1427                                 if (alt) {
1428                                         tclearregion(0, 0, term.col-1,
1429                                                         term.row-1);
1430                                 }
1431                                 if (set ^ alt) /* set is always 1 or 0 */
1432                                         tswapscreen();
1433                                 if (*args != 1049)
1434                                         break;
1435                                 /* FALLTHROUGH */
1436                         case 1048:
1437                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1438                                 break;
1439                         case 2004: /* 2004: bracketed paste mode */
1440                                 MODBIT(term.mode, set, MODE_BRCKTPASTE);
1441                                 break;
1442                         /* Not implemented mouse modes. See comments there. */
1443                         case 1001: /* mouse highlight mode; can hang the
1444                                       terminal by design when implemented. */
1445                         case 1005: /* UTF-8 mouse mode; will confuse
1446                                       applications not supporting UTF-8
1447                                       and luit. */
1448                         case 1015: /* urxvt mangled mouse mode; incompatible
1449                                       and can be mistaken for other control
1450                                       codes. */
1451                         default:
1452                                 fprintf(stderr,
1453                                         "erresc: unknown private set/reset mode %d\n",
1454                                         *args);
1455                                 break;
1456                         }
1457                 } else {
1458                         switch (*args) {
1459                         case 0:  /* Error (IGNORED) */
1460                                 break;
1461                         case 2:  /* KAM -- keyboard action */
1462                                 MODBIT(term.mode, set, MODE_KBDLOCK);
1463                                 break;
1464                         case 4:  /* IRM -- Insertion-replacement */
1465                                 MODBIT(term.mode, set, MODE_INSERT);
1466                                 break;
1467                         case 12: /* SRM -- Send/Receive */
1468                                 MODBIT(term.mode, !set, MODE_ECHO);
1469                                 break;
1470                         case 20: /* LNM -- Linefeed/new line */
1471                                 MODBIT(term.mode, set, MODE_CRLF);
1472                                 break;
1473                         default:
1474                                 fprintf(stderr,
1475                                         "erresc: unknown set/reset mode %d\n",
1476                                         *args);
1477                                 break;
1478                         }
1479                 }
1480         }
1481 }
1482
1483 void
1484 csihandle(void)
1485 {
1486         char buf[40];
1487         int len;
1488
1489         switch (csiescseq.mode[0]) {
1490         default:
1491         unknown:
1492                 fprintf(stderr, "erresc: unknown csi ");
1493                 csidump();
1494                 /* die(""); */
1495                 break;
1496         case '@': /* ICH -- Insert <n> blank char */
1497                 DEFAULT(csiescseq.arg[0], 1);
1498                 tinsertblank(csiescseq.arg[0]);
1499                 break;
1500         case 'A': /* CUU -- Cursor <n> Up */
1501                 DEFAULT(csiescseq.arg[0], 1);
1502                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1503                 break;
1504         case 'B': /* CUD -- Cursor <n> Down */
1505         case 'e': /* VPR --Cursor <n> Down */
1506                 DEFAULT(csiescseq.arg[0], 1);
1507                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1508                 break;
1509         case 'i': /* MC -- Media Copy */
1510                 switch (csiescseq.arg[0]) {
1511                 case 0:
1512                         tdump();
1513                         break;
1514                 case 1:
1515                         tdumpline(term.c.y);
1516                         break;
1517                 case 2:
1518                         tdumpsel();
1519                         break;
1520                 case 4:
1521                         term.mode &= ~MODE_PRINT;
1522                         break;
1523                 case 5:
1524                         term.mode |= MODE_PRINT;
1525                         break;
1526                 }
1527                 break;
1528         case 'c': /* DA -- Device Attributes */
1529                 if (csiescseq.arg[0] == 0)
1530                         ttywrite(vtiden, strlen(vtiden));
1531                 break;
1532         case 'C': /* CUF -- Cursor <n> Forward */
1533         case 'a': /* HPR -- Cursor <n> Forward */
1534                 DEFAULT(csiescseq.arg[0], 1);
1535                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1536                 break;
1537         case 'D': /* CUB -- Cursor <n> Backward */
1538                 DEFAULT(csiescseq.arg[0], 1);
1539                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1540                 break;
1541         case 'E': /* CNL -- Cursor <n> Down and first col */
1542                 DEFAULT(csiescseq.arg[0], 1);
1543                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1544                 break;
1545         case 'F': /* CPL -- Cursor <n> Up and first col */
1546                 DEFAULT(csiescseq.arg[0], 1);
1547                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1548                 break;
1549         case 'g': /* TBC -- Tabulation clear */
1550                 switch (csiescseq.arg[0]) {
1551                 case 0: /* clear current tab stop */
1552                         term.tabs[term.c.x] = 0;
1553                         break;
1554                 case 3: /* clear all the tabs */
1555                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1556                         break;
1557                 default:
1558                         goto unknown;
1559                 }
1560                 break;
1561         case 'G': /* CHA -- Move to <col> */
1562         case '`': /* HPA */
1563                 DEFAULT(csiescseq.arg[0], 1);
1564                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1565                 break;
1566         case 'H': /* CUP -- Move to <row> <col> */
1567         case 'f': /* HVP */
1568                 DEFAULT(csiescseq.arg[0], 1);
1569                 DEFAULT(csiescseq.arg[1], 1);
1570                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1571                 break;
1572         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1573                 DEFAULT(csiescseq.arg[0], 1);
1574                 tputtab(csiescseq.arg[0]);
1575                 break;
1576         case 'J': /* ED -- Clear screen */
1577                 selclear();
1578                 switch (csiescseq.arg[0]) {
1579                 case 0: /* below */
1580                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1581                         if (term.c.y < term.row-1) {
1582                                 tclearregion(0, term.c.y+1, term.col-1,
1583                                                 term.row-1);
1584                         }
1585                         break;
1586                 case 1: /* above */
1587                         if (term.c.y > 1)
1588                                 tclearregion(0, 0, term.col-1, term.c.y-1);
1589                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1590                         break;
1591                 case 2: /* all */
1592                         tclearregion(0, 0, term.col-1, term.row-1);
1593                         break;
1594                 default:
1595                         goto unknown;
1596                 }
1597                 break;
1598         case 'K': /* EL -- Clear line */
1599                 switch (csiescseq.arg[0]) {
1600                 case 0: /* right */
1601                         tclearregion(term.c.x, term.c.y, term.col-1,
1602                                         term.c.y);
1603                         break;
1604                 case 1: /* left */
1605                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1606                         break;
1607                 case 2: /* all */
1608                         tclearregion(0, term.c.y, term.col-1, term.c.y);
1609                         break;
1610                 }
1611                 break;
1612         case 'S': /* SU -- Scroll <n> line up */
1613                 DEFAULT(csiescseq.arg[0], 1);
1614                 tscrollup(term.top, csiescseq.arg[0]);
1615                 break;
1616         case 'T': /* SD -- Scroll <n> line down */
1617                 DEFAULT(csiescseq.arg[0], 1);
1618                 tscrolldown(term.top, csiescseq.arg[0]);
1619                 break;
1620         case 'L': /* IL -- Insert <n> blank lines */
1621                 DEFAULT(csiescseq.arg[0], 1);
1622                 tinsertblankline(csiescseq.arg[0]);
1623                 break;
1624         case 'l': /* RM -- Reset Mode */
1625                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1626                 break;
1627         case 'M': /* DL -- Delete <n> lines */
1628                 DEFAULT(csiescseq.arg[0], 1);
1629                 tdeleteline(csiescseq.arg[0]);
1630                 break;
1631         case 'X': /* ECH -- Erase <n> char */
1632                 DEFAULT(csiescseq.arg[0], 1);
1633                 tclearregion(term.c.x, term.c.y,
1634                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1635                 break;
1636         case 'P': /* DCH -- Delete <n> char */
1637                 DEFAULT(csiescseq.arg[0], 1);
1638                 tdeletechar(csiescseq.arg[0]);
1639                 break;
1640         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1641                 DEFAULT(csiescseq.arg[0], 1);
1642                 tputtab(-csiescseq.arg[0]);
1643                 break;
1644         case 'd': /* VPA -- Move to <row> */
1645                 DEFAULT(csiescseq.arg[0], 1);
1646                 tmoveato(term.c.x, csiescseq.arg[0]-1);
1647                 break;
1648         case 'h': /* SM -- Set terminal mode */
1649                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1650                 break;
1651         case 'm': /* SGR -- Terminal attribute (color) */
1652                 tsetattr(csiescseq.arg, csiescseq.narg);
1653                 break;
1654         case 'n': /* DSR – Device Status Report (cursor position) */
1655                 if (csiescseq.arg[0] == 6) {
1656                         len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1657                                         term.c.y+1, term.c.x+1);
1658                         ttywrite(buf, len);
1659                 }
1660                 break;
1661         case 'r': /* DECSTBM -- Set Scrolling Region */
1662                 if (csiescseq.priv) {
1663                         goto unknown;
1664                 } else {
1665                         DEFAULT(csiescseq.arg[0], 1);
1666                         DEFAULT(csiescseq.arg[1], term.row);
1667                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1668                         tmoveato(0, 0);
1669                 }
1670                 break;
1671         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1672                 tcursor(CURSOR_SAVE);
1673                 break;
1674         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1675                 tcursor(CURSOR_LOAD);
1676                 break;
1677         case ' ':
1678                 switch (csiescseq.mode[1]) {
1679                 case 'q': /* DECSCUSR -- Set Cursor Style */
1680                         if (xsetcursor(csiescseq.arg[0]))
1681                                 goto unknown;
1682                         break;
1683                 default:
1684                         goto unknown;
1685                 }
1686                 break;
1687         }
1688 }
1689
1690 void
1691 csidump(void)
1692 {
1693         int i;
1694         uint c;
1695
1696         fprintf(stderr, "ESC[");
1697         for (i = 0; i < csiescseq.len; i++) {
1698                 c = csiescseq.buf[i] & 0xff;
1699                 if (isprint(c)) {
1700                         putc(c, stderr);
1701                 } else if (c == '\n') {
1702                         fprintf(stderr, "(\\n)");
1703                 } else if (c == '\r') {
1704                         fprintf(stderr, "(\\r)");
1705                 } else if (c == 0x1b) {
1706                         fprintf(stderr, "(\\e)");
1707                 } else {
1708                         fprintf(stderr, "(%02x)", c);
1709                 }
1710         }
1711         putc('\n', stderr);
1712 }
1713
1714 void
1715 csireset(void)
1716 {
1717         memset(&csiescseq, 0, sizeof(csiescseq));
1718 }
1719
1720 void
1721 strhandle(void)
1722 {
1723         char *p = NULL;
1724         int j, narg, par;
1725
1726         term.esc &= ~(ESC_STR_END|ESC_STR);
1727         strparse();
1728         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1729
1730         switch (strescseq.type) {
1731         case ']': /* OSC -- Operating System Command */
1732                 switch (par) {
1733                 case 0:
1734                 case 1:
1735                 case 2:
1736                         if (narg > 1)
1737                                 xsettitle(strescseq.args[1]);
1738                         return;
1739                 case 52:
1740                         if (narg > 2) {
1741                                 char *dec;
1742
1743                                 dec = base64dec(strescseq.args[2]);
1744                                 if (dec) {
1745                                         xsetsel(dec);
1746                                         xclipcopy();
1747                                 } else {
1748                                         fprintf(stderr, "erresc: invalid base64\n");
1749                                 }
1750                         }
1751                         return;
1752                 case 4: /* color set */
1753                         if (narg < 3)
1754                                 break;
1755                         p = strescseq.args[2];
1756                         /* FALLTHROUGH */
1757                 case 104: /* color reset, here p = NULL */
1758                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1759                         if (xsetcolorname(j, p)) {
1760                                 fprintf(stderr, "erresc: invalid color %s\n", p);
1761                         } else {
1762                                 /*
1763                                  * TODO if defaultbg color is changed, borders
1764                                  * are dirty
1765                                  */
1766                                 redraw();
1767                         }
1768                         return;
1769                 }
1770                 break;
1771         case 'k': /* old title set compatibility */
1772                 xsettitle(strescseq.args[0]);
1773                 return;
1774         case 'P': /* DCS -- Device Control String */
1775                 term.mode |= ESC_DCS;
1776         case '_': /* APC -- Application Program Command */
1777         case '^': /* PM -- Privacy Message */
1778                 return;
1779         }
1780
1781         fprintf(stderr, "erresc: unknown str ");
1782         strdump();
1783 }
1784
1785 void
1786 strparse(void)
1787 {
1788         int c;
1789         char *p = strescseq.buf;
1790
1791         strescseq.narg = 0;
1792         strescseq.buf[strescseq.len] = '\0';
1793
1794         if (*p == '\0')
1795                 return;
1796
1797         while (strescseq.narg < STR_ARG_SIZ) {
1798                 strescseq.args[strescseq.narg++] = p;
1799                 while ((c = *p) != ';' && c != '\0')
1800                         ++p;
1801                 if (c == '\0')
1802                         return;
1803                 *p++ = '\0';
1804         }
1805 }
1806
1807 void
1808 strdump(void)
1809 {
1810         int i;
1811         uint c;
1812
1813         fprintf(stderr, "ESC%c", strescseq.type);
1814         for (i = 0; i < strescseq.len; i++) {
1815                 c = strescseq.buf[i] & 0xff;
1816                 if (c == '\0') {
1817                         putc('\n', stderr);
1818                         return;
1819                 } else if (isprint(c)) {
1820                         putc(c, stderr);
1821                 } else if (c == '\n') {
1822                         fprintf(stderr, "(\\n)");
1823                 } else if (c == '\r') {
1824                         fprintf(stderr, "(\\r)");
1825                 } else if (c == 0x1b) {
1826                         fprintf(stderr, "(\\e)");
1827                 } else {
1828                         fprintf(stderr, "(%02x)", c);
1829                 }
1830         }
1831         fprintf(stderr, "ESC\\\n");
1832 }
1833
1834 void
1835 strreset(void)
1836 {
1837         memset(&strescseq, 0, sizeof(strescseq));
1838 }
1839
1840 void
1841 sendbreak(const Arg *arg)
1842 {
1843         if (tcsendbreak(cmdfd, 0))
1844                 perror("Error sending break");
1845 }
1846
1847 void
1848 tprinter(char *s, size_t len)
1849 {
1850         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1851                 perror("Error writing to output file");
1852                 close(iofd);
1853                 iofd = -1;
1854         }
1855 }
1856
1857 void
1858 iso14755(const Arg *arg)
1859 {
1860         FILE *p;
1861         char *us, *e, codepoint[9], uc[UTF_SIZ];
1862         unsigned long utf32;
1863
1864         if (!(p = popen(ISO14755CMD, "r")))
1865                 return;
1866
1867         us = fgets(codepoint, sizeof(codepoint), p);
1868         pclose(p);
1869
1870         if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
1871                 return;
1872         if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
1873             (*e != '\n' && *e != '\0'))
1874                 return;
1875
1876         ttysend(uc, utf8encode(utf32, uc));
1877 }
1878
1879 void
1880 toggleprinter(const Arg *arg)
1881 {
1882         term.mode ^= MODE_PRINT;
1883 }
1884
1885 void
1886 printscreen(const Arg *arg)
1887 {
1888         tdump();
1889 }
1890
1891 void
1892 printsel(const Arg *arg)
1893 {
1894         tdumpsel();
1895 }
1896
1897 void
1898 tdumpsel(void)
1899 {
1900         char *ptr;
1901
1902         if ((ptr = getsel())) {
1903                 tprinter(ptr, strlen(ptr));
1904                 free(ptr);
1905         }
1906 }
1907
1908 void
1909 tdumpline(int n)
1910 {
1911         char buf[UTF_SIZ];
1912         Glyph *bp, *end;
1913
1914         bp = &term.line[n][0];
1915         end = &bp[MIN(tlinelen(n), term.col) - 1];
1916         if (bp != end || bp->u != ' ') {
1917                 for ( ;bp <= end; ++bp)
1918                         tprinter(buf, utf8encode(bp->u, buf));
1919         }
1920         tprinter("\n", 1);
1921 }
1922
1923 void
1924 tdump(void)
1925 {
1926         int i;
1927
1928         for (i = 0; i < term.row; ++i)
1929                 tdumpline(i);
1930 }
1931
1932 void
1933 tputtab(int n)
1934 {
1935         uint x = term.c.x;
1936
1937         if (n > 0) {
1938                 while (x < term.col && n--)
1939                         for (++x; x < term.col && !term.tabs[x]; ++x)
1940                                 /* nothing */ ;
1941         } else if (n < 0) {
1942                 while (x > 0 && n++)
1943                         for (--x; x > 0 && !term.tabs[x]; --x)
1944                                 /* nothing */ ;
1945         }
1946         term.c.x = LIMIT(x, 0, term.col-1);
1947 }
1948
1949 void
1950 tdefutf8(char ascii)
1951 {
1952         if (ascii == 'G')
1953                 term.mode |= MODE_UTF8;
1954         else if (ascii == '@')
1955                 term.mode &= ~MODE_UTF8;
1956 }
1957
1958 void
1959 tdeftran(char ascii)
1960 {
1961         static char cs[] = "0B";
1962         static int vcs[] = {CS_GRAPHIC0, CS_USA};
1963         char *p;
1964
1965         if ((p = strchr(cs, ascii)) == NULL) {
1966                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
1967         } else {
1968                 term.trantbl[term.icharset] = vcs[p - cs];
1969         }
1970 }
1971
1972 void
1973 tdectest(char c)
1974 {
1975         int x, y;
1976
1977         if (c == '8') { /* DEC screen alignment test. */
1978                 for (x = 0; x < term.col; ++x) {
1979                         for (y = 0; y < term.row; ++y)
1980                                 tsetchar('E', &term.c.attr, x, y);
1981                 }
1982         }
1983 }
1984
1985 void
1986 tstrsequence(uchar c)
1987 {
1988         strreset();
1989
1990         switch (c) {
1991         case 0x90:   /* DCS -- Device Control String */
1992                 c = 'P';
1993                 term.esc |= ESC_DCS;
1994                 break;
1995         case 0x9f:   /* APC -- Application Program Command */
1996                 c = '_';
1997                 break;
1998         case 0x9e:   /* PM -- Privacy Message */
1999                 c = '^';
2000                 break;
2001         case 0x9d:   /* OSC -- Operating System Command */
2002                 c = ']';
2003                 break;
2004         }
2005         strescseq.type = c;
2006         term.esc |= ESC_STR;
2007 }
2008
2009 void
2010 tcontrolcode(uchar ascii)
2011 {
2012         switch (ascii) {
2013         case '\t':   /* HT */
2014                 tputtab(1);
2015                 return;
2016         case '\b':   /* BS */
2017                 tmoveto(term.c.x-1, term.c.y);
2018                 return;
2019         case '\r':   /* CR */
2020                 tmoveto(0, term.c.y);
2021                 return;
2022         case '\f':   /* LF */
2023         case '\v':   /* VT */
2024         case '\n':   /* LF */
2025                 /* go to first col if the mode is set */
2026                 tnewline(IS_SET(MODE_CRLF));
2027                 return;
2028         case '\a':   /* BEL */
2029                 if (term.esc & ESC_STR_END) {
2030                         /* backwards compatibility to xterm */
2031                         strhandle();
2032                 } else {
2033                         xbell();
2034                 }
2035                 break;
2036         case '\033': /* ESC */
2037                 csireset();
2038                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2039                 term.esc |= ESC_START;
2040                 return;
2041         case '\016': /* SO (LS1 -- Locking shift 1) */
2042         case '\017': /* SI (LS0 -- Locking shift 0) */
2043                 term.charset = 1 - (ascii - '\016');
2044                 return;
2045         case '\032': /* SUB */
2046                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2047         case '\030': /* CAN */
2048                 csireset();
2049                 break;
2050         case '\005': /* ENQ (IGNORED) */
2051         case '\000': /* NUL (IGNORED) */
2052         case '\021': /* XON (IGNORED) */
2053         case '\023': /* XOFF (IGNORED) */
2054         case 0177:   /* DEL (IGNORED) */
2055                 return;
2056         case 0x80:   /* TODO: PAD */
2057         case 0x81:   /* TODO: HOP */
2058         case 0x82:   /* TODO: BPH */
2059         case 0x83:   /* TODO: NBH */
2060         case 0x84:   /* TODO: IND */
2061                 break;
2062         case 0x85:   /* NEL -- Next line */
2063                 tnewline(1); /* always go to first col */
2064                 break;
2065         case 0x86:   /* TODO: SSA */
2066         case 0x87:   /* TODO: ESA */
2067                 break;
2068         case 0x88:   /* HTS -- Horizontal tab stop */
2069                 term.tabs[term.c.x] = 1;
2070                 break;
2071         case 0x89:   /* TODO: HTJ */
2072         case 0x8a:   /* TODO: VTS */
2073         case 0x8b:   /* TODO: PLD */
2074         case 0x8c:   /* TODO: PLU */
2075         case 0x8d:   /* TODO: RI */
2076         case 0x8e:   /* TODO: SS2 */
2077         case 0x8f:   /* TODO: SS3 */
2078         case 0x91:   /* TODO: PU1 */
2079         case 0x92:   /* TODO: PU2 */
2080         case 0x93:   /* TODO: STS */
2081         case 0x94:   /* TODO: CCH */
2082         case 0x95:   /* TODO: MW */
2083         case 0x96:   /* TODO: SPA */
2084         case 0x97:   /* TODO: EPA */
2085         case 0x98:   /* TODO: SOS */
2086         case 0x99:   /* TODO: SGCI */
2087                 break;
2088         case 0x9a:   /* DECID -- Identify Terminal */
2089                 ttywrite(vtiden, strlen(vtiden));
2090                 break;
2091         case 0x9b:   /* TODO: CSI */
2092         case 0x9c:   /* TODO: ST */
2093                 break;
2094         case 0x90:   /* DCS -- Device Control String */
2095         case 0x9d:   /* OSC -- Operating System Command */
2096         case 0x9e:   /* PM -- Privacy Message */
2097         case 0x9f:   /* APC -- Application Program Command */
2098                 tstrsequence(ascii);
2099                 return;
2100         }
2101         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2102         term.esc &= ~(ESC_STR_END|ESC_STR);
2103 }
2104
2105 /*
2106  * returns 1 when the sequence is finished and it hasn't to read
2107  * more characters for this sequence, otherwise 0
2108  */
2109 int
2110 eschandle(uchar ascii)
2111 {
2112         switch (ascii) {
2113         case '[':
2114                 term.esc |= ESC_CSI;
2115                 return 0;
2116         case '#':
2117                 term.esc |= ESC_TEST;
2118                 return 0;
2119         case '%':
2120                 term.esc |= ESC_UTF8;
2121                 return 0;
2122         case 'P': /* DCS -- Device Control String */
2123         case '_': /* APC -- Application Program Command */
2124         case '^': /* PM -- Privacy Message */
2125         case ']': /* OSC -- Operating System Command */
2126         case 'k': /* old title set compatibility */
2127                 tstrsequence(ascii);
2128                 return 0;
2129         case 'n': /* LS2 -- Locking shift 2 */
2130         case 'o': /* LS3 -- Locking shift 3 */
2131                 term.charset = 2 + (ascii - 'n');
2132                 break;
2133         case '(': /* GZD4 -- set primary charset G0 */
2134         case ')': /* G1D4 -- set secondary charset G1 */
2135         case '*': /* G2D4 -- set tertiary charset G2 */
2136         case '+': /* G3D4 -- set quaternary charset G3 */
2137                 term.icharset = ascii - '(';
2138                 term.esc |= ESC_ALTCHARSET;
2139                 return 0;
2140         case 'D': /* IND -- Linefeed */
2141                 if (term.c.y == term.bot) {
2142                         tscrollup(term.top, 1);
2143                 } else {
2144                         tmoveto(term.c.x, term.c.y+1);
2145                 }
2146                 break;
2147         case 'E': /* NEL -- Next line */
2148                 tnewline(1); /* always go to first col */
2149                 break;
2150         case 'H': /* HTS -- Horizontal tab stop */
2151                 term.tabs[term.c.x] = 1;
2152                 break;
2153         case 'M': /* RI -- Reverse index */
2154                 if (term.c.y == term.top) {
2155                         tscrolldown(term.top, 1);
2156                 } else {
2157                         tmoveto(term.c.x, term.c.y-1);
2158                 }
2159                 break;
2160         case 'Z': /* DECID -- Identify Terminal */
2161                 ttywrite(vtiden, strlen(vtiden));
2162                 break;
2163         case 'c': /* RIS -- Reset to inital state */
2164                 treset();
2165                 resettitle();
2166                 xloadcols();
2167                 break;
2168         case '=': /* DECPAM -- Application keypad */
2169                 term.mode |= MODE_APPKEYPAD;
2170                 break;
2171         case '>': /* DECPNM -- Normal keypad */
2172                 term.mode &= ~MODE_APPKEYPAD;
2173                 break;
2174         case '7': /* DECSC -- Save Cursor */
2175                 tcursor(CURSOR_SAVE);
2176                 break;
2177         case '8': /* DECRC -- Restore Cursor */
2178                 tcursor(CURSOR_LOAD);
2179                 break;
2180         case '\\': /* ST -- String Terminator */
2181                 if (term.esc & ESC_STR_END)
2182                         strhandle();
2183                 break;
2184         default:
2185                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2186                         (uchar) ascii, isprint(ascii)? ascii:'.');
2187                 break;
2188         }
2189         return 1;
2190 }
2191
2192 void
2193 tputc(Rune u)
2194 {
2195         char c[UTF_SIZ];
2196         int control;
2197         int width, len;
2198         Glyph *gp;
2199
2200         control = ISCONTROL(u);
2201         if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2202                 c[0] = u;
2203                 width = len = 1;
2204         } else {
2205                 len = utf8encode(u, c);
2206                 if (!control && (width = wcwidth(u)) == -1) {
2207                         memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2208                         width = 1;
2209                 }
2210         }
2211
2212         if (IS_SET(MODE_PRINT))
2213                 tprinter(c, len);
2214
2215         /*
2216          * STR sequence must be checked before anything else
2217          * because it uses all following characters until it
2218          * receives a ESC, a SUB, a ST or any other C1 control
2219          * character.
2220          */
2221         if (term.esc & ESC_STR) {
2222                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2223                    ISCONTROLC1(u)) {
2224                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2225                         if (IS_SET(MODE_SIXEL)) {
2226                                 /* TODO: render sixel */;
2227                                 term.mode &= ~MODE_SIXEL;
2228                                 return;
2229                         }
2230                         term.esc |= ESC_STR_END;
2231                         goto check_control_code;
2232                 }
2233
2234
2235                 if (IS_SET(MODE_SIXEL)) {
2236                         /* TODO: implement sixel mode */
2237                         return;
2238                 }
2239                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2240                         term.mode |= MODE_SIXEL;
2241
2242                 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2243                         /*
2244                          * Here is a bug in terminals. If the user never sends
2245                          * some code to stop the str or esc command, then st
2246                          * will stop responding. But this is better than
2247                          * silently failing with unknown characters. At least
2248                          * then users will report back.
2249                          *
2250                          * In the case users ever get fixed, here is the code:
2251                          */
2252                         /*
2253                          * term.esc = 0;
2254                          * strhandle();
2255                          */
2256                         return;
2257                 }
2258
2259                 memmove(&strescseq.buf[strescseq.len], c, len);
2260                 strescseq.len += len;
2261                 return;
2262         }
2263
2264 check_control_code:
2265         /*
2266          * Actions of control codes must be performed as soon they arrive
2267          * because they can be embedded inside a control sequence, and
2268          * they must not cause conflicts with sequences.
2269          */
2270         if (control) {
2271                 tcontrolcode(u);
2272                 /*
2273                  * control codes are not shown ever
2274                  */
2275                 return;
2276         } else if (term.esc & ESC_START) {
2277                 if (term.esc & ESC_CSI) {
2278                         csiescseq.buf[csiescseq.len++] = u;
2279                         if (BETWEEN(u, 0x40, 0x7E)
2280                                         || csiescseq.len >= \
2281                                         sizeof(csiescseq.buf)-1) {
2282                                 term.esc = 0;
2283                                 csiparse();
2284                                 csihandle();
2285                         }
2286                         return;
2287                 } else if (term.esc & ESC_UTF8) {
2288                         tdefutf8(u);
2289                 } else if (term.esc & ESC_ALTCHARSET) {
2290                         tdeftran(u);
2291                 } else if (term.esc & ESC_TEST) {
2292                         tdectest(u);
2293                 } else {
2294                         if (!eschandle(u))
2295                                 return;
2296                         /* sequence already finished */
2297                 }
2298                 term.esc = 0;
2299                 /*
2300                  * All characters which form part of a sequence are not
2301                  * printed
2302                  */
2303                 return;
2304         }
2305         if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2306                 selclear();
2307
2308         gp = &term.line[term.c.y][term.c.x];
2309         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2310                 gp->mode |= ATTR_WRAP;
2311                 tnewline(1);
2312                 gp = &term.line[term.c.y][term.c.x];
2313         }
2314
2315         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2316                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2317
2318         if (term.c.x+width > term.col) {
2319                 tnewline(1);
2320                 gp = &term.line[term.c.y][term.c.x];
2321         }
2322
2323         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2324
2325         if (width == 2) {
2326                 gp->mode |= ATTR_WIDE;
2327                 if (term.c.x+1 < term.col) {
2328                         gp[1].u = '\0';
2329                         gp[1].mode = ATTR_WDUMMY;
2330                 }
2331         }
2332         if (term.c.x+width < term.col) {
2333                 tmoveto(term.c.x+width, term.c.y);
2334         } else {
2335                 term.c.state |= CURSOR_WRAPNEXT;
2336         }
2337 }
2338
2339 int
2340 twrite(const char *buf, int buflen, int show_ctrl)
2341 {
2342         int charsize;
2343         Rune u;
2344         int n;
2345
2346         for (n = 0; n < buflen; n += charsize) {
2347                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2348                         /* process a complete utf8 char */
2349                         charsize = utf8decode(buf + n, &u, buflen - n);
2350                         if (charsize == 0)
2351                                 break;
2352                 } else {
2353                         u = buf[n] & 0xFF;
2354                         charsize = 1;
2355                 }
2356                 if (show_ctrl && ISCONTROL(u)) {
2357                         if (u & 0x80) {
2358                                 u &= 0x7f;
2359                                 tputc('^');
2360                                 tputc('[');
2361                         } else if (u != '\n' && u != '\r' && u != '\t') {
2362                                 u ^= 0x40;
2363                                 tputc('^');
2364                         }
2365                 }
2366                 tputc(u);
2367         }
2368         return n;
2369 }
2370
2371 void
2372 tresize(int col, int row)
2373 {
2374         int i;
2375         int minrow = MIN(row, term.row);
2376         int mincol = MIN(col, term.col);
2377         int *bp;
2378         TCursor c;
2379
2380         if (col < 1 || row < 1) {
2381                 fprintf(stderr,
2382                         "tresize: error resizing to %dx%d\n", col, row);
2383                 return;
2384         }
2385
2386         /*
2387          * slide screen to keep cursor where we expect it -
2388          * tscrollup would work here, but we can optimize to
2389          * memmove because we're freeing the earlier lines
2390          */
2391         for (i = 0; i <= term.c.y - row; i++) {
2392                 free(term.line[i]);
2393                 free(term.alt[i]);
2394         }
2395         /* ensure that both src and dst are not NULL */
2396         if (i > 0) {
2397                 memmove(term.line, term.line + i, row * sizeof(Line));
2398                 memmove(term.alt, term.alt + i, row * sizeof(Line));
2399         }
2400         for (i += row; i < term.row; i++) {
2401                 free(term.line[i]);
2402                 free(term.alt[i]);
2403         }
2404
2405         /* resize to new height */
2406         term.line = xrealloc(term.line, row * sizeof(Line));
2407         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2408         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2409         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2410
2411         /* resize each row to new width, zero-pad if needed */
2412         for (i = 0; i < minrow; i++) {
2413                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2414                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2415         }
2416
2417         /* allocate any new rows */
2418         for (/* i = minrow */; i < row; i++) {
2419                 term.line[i] = xmalloc(col * sizeof(Glyph));
2420                 term.alt[i] = xmalloc(col * sizeof(Glyph));
2421         }
2422         if (col > term.col) {
2423                 bp = term.tabs + term.col;
2424
2425                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2426                 while (--bp > term.tabs && !*bp)
2427                         /* nothing */ ;
2428                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2429                         *bp = 1;
2430         }
2431         /* update terminal size */
2432         term.col = col;
2433         term.row = row;
2434         /* reset scrolling region */
2435         tsetscroll(0, row-1);
2436         /* make use of the LIMIT in tmoveto */
2437         tmoveto(term.c.x, term.c.y);
2438         /* Clearing both screens (it makes dirty all lines) */
2439         c = term.c;
2440         for (i = 0; i < 2; i++) {
2441                 if (mincol < col && 0 < minrow) {
2442                         tclearregion(mincol, 0, col - 1, minrow - 1);
2443                 }
2444                 if (0 < col && minrow < row) {
2445                         tclearregion(0, minrow, col - 1, row - 1);
2446                 }
2447                 tswapscreen();
2448                 tcursor(CURSOR_LOAD);
2449         }
2450         term.c = c;
2451 }
2452
2453 void
2454 resettitle(void)
2455 {
2456         xsettitle(NULL);
2457 }
2458
2459 void
2460 redraw(void)
2461 {
2462         tfulldirt();
2463         draw();
2464 }
2465
2466 void
2467 numlock(const Arg *dummy)
2468 {
2469         term.numlock ^= 1;
2470 }