Xinqi Bao's Git

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