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