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