Xinqi Bao's Git

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