Xinqi Bao's Git

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