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