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