Xinqi Bao's Git

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