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