Xinqi Bao's Git

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