Xinqi Bao's Git

b7e215e7841ab5de18726ce8b2ca3c1309143773
[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)
788 {
789 fd_set wfd, rfd;
790 ssize_t r;
791 size_t lim = 256;
792
793 /*
794 * Remember that we are using a pty, which might be a modem line.
795 * Writing too much will clog the line. That's why we are doing this
796 * dance.
797 * FIXME: Migrate the world to Plan 9.
798 */
799 while (n > 0) {
800 FD_ZERO(&wfd);
801 FD_ZERO(&rfd);
802 FD_SET(cmdfd, &wfd);
803 FD_SET(cmdfd, &rfd);
804
805 /* Check if we can write. */
806 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
807 if (errno == EINTR)
808 continue;
809 die("select failed: %s\n", strerror(errno));
810 }
811 if (FD_ISSET(cmdfd, &wfd)) {
812 /*
813 * Only write the bytes written by ttywrite() or the
814 * default of 256. This seems to be a reasonable value
815 * for a serial line. Bigger values might clog the I/O.
816 */
817 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
818 goto write_error;
819 if (r < n) {
820 /*
821 * We weren't able to write out everything.
822 * This means the buffer is getting full
823 * again. Empty it.
824 */
825 if (n < lim)
826 lim = ttyread();
827 n -= r;
828 s += r;
829 } else {
830 /* All bytes have been written. */
831 break;
832 }
833 }
834 if (FD_ISSET(cmdfd, &rfd))
835 lim = ttyread();
836 }
837 return;
838
839 write_error:
840 die("write error on tty: %s\n", strerror(errno));
841 }
842
843 void
844 ttysend(char *s, size_t n)
845 {
846 ttywrite(s, n);
847 if (IS_SET(MODE_ECHO))
848 twrite(s, n, 1);
849 }
850
851 void
852 ttyresize(int tw, int th)
853 {
854 struct winsize w;
855
856 w.ws_row = term.row;
857 w.ws_col = term.col;
858 w.ws_xpixel = tw;
859 w.ws_ypixel = th;
860 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
861 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
862 }
863
864 int
865 tattrset(int attr)
866 {
867 int i, j;
868
869 for (i = 0; i < term.row-1; i++) {
870 for (j = 0; j < term.col-1; j++) {
871 if (term.line[i][j].mode & attr)
872 return 1;
873 }
874 }
875
876 return 0;
877 }
878
879 void
880 tsetdirt(int top, int bot)
881 {
882 int i;
883
884 LIMIT(top, 0, term.row-1);
885 LIMIT(bot, 0, term.row-1);
886
887 for (i = top; i <= bot; i++)
888 term.dirty[i] = 1;
889 }
890
891 void
892 tsetdirtattr(int attr)
893 {
894 int i, j;
895
896 for (i = 0; i < term.row-1; i++) {
897 for (j = 0; j < term.col-1; j++) {
898 if (term.line[i][j].mode & attr) {
899 tsetdirt(i, i);
900 break;
901 }
902 }
903 }
904 }
905
906 void
907 tfulldirt(void)
908 {
909 tsetdirt(0, term.row-1);
910 }
911
912 void
913 tcursor(int mode)
914 {
915 static TCursor c[2];
916 int alt = IS_SET(MODE_ALTSCREEN);
917
918 if (mode == CURSOR_SAVE) {
919 c[alt] = term.c;
920 } else if (mode == CURSOR_LOAD) {
921 term.c = c[alt];
922 tmoveto(c[alt].x, c[alt].y);
923 }
924 }
925
926 void
927 treset(void)
928 {
929 uint i;
930
931 term.c = (TCursor){{
932 .mode = ATTR_NULL,
933 .fg = defaultfg,
934 .bg = defaultbg
935 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
936
937 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
938 for (i = tabspaces; i < term.col; i += tabspaces)
939 term.tabs[i] = 1;
940 term.top = 0;
941 term.bot = term.row - 1;
942 term.mode = MODE_WRAP|MODE_UTF8;
943 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
944 term.charset = 0;
945
946 for (i = 0; i < 2; i++) {
947 tmoveto(0, 0);
948 tcursor(CURSOR_SAVE);
949 tclearregion(0, 0, term.col-1, term.row-1);
950 tswapscreen();
951 }
952 }
953
954 void
955 tnew(int col, int row)
956 {
957 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
958 tresize(col, row);
959 term.numlock = 1;
960
961 treset();
962 }
963
964 void
965 tswapscreen(void)
966 {
967 Line *tmp = term.line;
968
969 term.line = term.alt;
970 term.alt = tmp;
971 term.mode ^= MODE_ALTSCREEN;
972 tfulldirt();
973 }
974
975 void
976 tscrolldown(int orig, int n)
977 {
978 int i;
979 Line temp;
980
981 LIMIT(n, 0, term.bot-orig+1);
982
983 tsetdirt(orig, term.bot-n);
984 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
985
986 for (i = term.bot; i >= orig+n; i--) {
987 temp = term.line[i];
988 term.line[i] = term.line[i-n];
989 term.line[i-n] = temp;
990 }
991
992 selscroll(orig, n);
993 }
994
995 void
996 tscrollup(int orig, int n)
997 {
998 int i;
999 Line temp;
1000
1001 LIMIT(n, 0, term.bot-orig+1);
1002
1003 tclearregion(0, orig, term.col-1, orig+n-1);
1004 tsetdirt(orig+n, term.bot);
1005
1006 for (i = orig; i <= term.bot-n; i++) {
1007 temp = term.line[i];
1008 term.line[i] = term.line[i+n];
1009 term.line[i+n] = temp;
1010 }
1011
1012 selscroll(orig, -n);
1013 }
1014
1015 void
1016 selscroll(int orig, int n)
1017 {
1018 if (sel.ob.x == -1)
1019 return;
1020
1021 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1022 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1023 selclear();
1024 return;
1025 }
1026 if (sel.type == SEL_RECTANGULAR) {
1027 if (sel.ob.y < term.top)
1028 sel.ob.y = term.top;
1029 if (sel.oe.y > term.bot)
1030 sel.oe.y = term.bot;
1031 } else {
1032 if (sel.ob.y < term.top) {
1033 sel.ob.y = term.top;
1034 sel.ob.x = 0;
1035 }
1036 if (sel.oe.y > term.bot) {
1037 sel.oe.y = term.bot;
1038 sel.oe.x = term.col;
1039 }
1040 }
1041 selnormalize();
1042 }
1043 }
1044
1045 void
1046 tnewline(int first_col)
1047 {
1048 int y = term.c.y;
1049
1050 if (y == term.bot) {
1051 tscrollup(term.top, 1);
1052 } else {
1053 y++;
1054 }
1055 tmoveto(first_col ? 0 : term.c.x, y);
1056 }
1057
1058 void
1059 csiparse(void)
1060 {
1061 char *p = csiescseq.buf, *np;
1062 long int v;
1063
1064 csiescseq.narg = 0;
1065 if (*p == '?') {
1066 csiescseq.priv = 1;
1067 p++;
1068 }
1069
1070 csiescseq.buf[csiescseq.len] = '\0';
1071 while (p < csiescseq.buf+csiescseq.len) {
1072 np = NULL;
1073 v = strtol(p, &np, 10);
1074 if (np == p)
1075 v = 0;
1076 if (v == LONG_MAX || v == LONG_MIN)
1077 v = -1;
1078 csiescseq.arg[csiescseq.narg++] = v;
1079 p = np;
1080 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1081 break;
1082 p++;
1083 }
1084 csiescseq.mode[0] = *p++;
1085 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1086 }
1087
1088 /* for absolute user moves, when decom is set */
1089 void
1090 tmoveato(int x, int y)
1091 {
1092 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1093 }
1094
1095 void
1096 tmoveto(int x, int y)
1097 {
1098 int miny, maxy;
1099
1100 if (term.c.state & CURSOR_ORIGIN) {
1101 miny = term.top;
1102 maxy = term.bot;
1103 } else {
1104 miny = 0;
1105 maxy = term.row - 1;
1106 }
1107 term.c.state &= ~CURSOR_WRAPNEXT;
1108 term.c.x = LIMIT(x, 0, term.col-1);
1109 term.c.y = LIMIT(y, miny, maxy);
1110 }
1111
1112 void
1113 tsetchar(Rune u, Glyph *attr, int x, int y)
1114 {
1115 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1116 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1117 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1118 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1119 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1120 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1121 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1122 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1123 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1124 };
1125
1126 /*
1127 * The table is proudly stolen from rxvt.
1128 */
1129 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1130 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1131 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1132
1133 if (term.line[y][x].mode & ATTR_WIDE) {
1134 if (x+1 < term.col) {
1135 term.line[y][x+1].u = ' ';
1136 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1137 }
1138 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1139 term.line[y][x-1].u = ' ';
1140 term.line[y][x-1].mode &= ~ATTR_WIDE;
1141 }
1142
1143 term.dirty[y] = 1;
1144 term.line[y][x] = *attr;
1145 term.line[y][x].u = u;
1146 }
1147
1148 void
1149 tclearregion(int x1, int y1, int x2, int y2)
1150 {
1151 int x, y, temp;
1152 Glyph *gp;
1153
1154 if (x1 > x2)
1155 temp = x1, x1 = x2, x2 = temp;
1156 if (y1 > y2)
1157 temp = y1, y1 = y2, y2 = temp;
1158
1159 LIMIT(x1, 0, term.col-1);
1160 LIMIT(x2, 0, term.col-1);
1161 LIMIT(y1, 0, term.row-1);
1162 LIMIT(y2, 0, term.row-1);
1163
1164 for (y = y1; y <= y2; y++) {
1165 term.dirty[y] = 1;
1166 for (x = x1; x <= x2; x++) {
1167 gp = &term.line[y][x];
1168 if (selected(x, y))
1169 selclear();
1170 gp->fg = term.c.attr.fg;
1171 gp->bg = term.c.attr.bg;
1172 gp->mode = 0;
1173 gp->u = ' ';
1174 }
1175 }
1176 }
1177
1178 void
1179 tdeletechar(int n)
1180 {
1181 int dst, src, size;
1182 Glyph *line;
1183
1184 LIMIT(n, 0, term.col - term.c.x);
1185
1186 dst = term.c.x;
1187 src = term.c.x + n;
1188 size = term.col - src;
1189 line = term.line[term.c.y];
1190
1191 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1192 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1193 }
1194
1195 void
1196 tinsertblank(int n)
1197 {
1198 int dst, src, size;
1199 Glyph *line;
1200
1201 LIMIT(n, 0, term.col - term.c.x);
1202
1203 dst = term.c.x + n;
1204 src = term.c.x;
1205 size = term.col - dst;
1206 line = term.line[term.c.y];
1207
1208 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1209 tclearregion(src, term.c.y, dst - 1, term.c.y);
1210 }
1211
1212 void
1213 tinsertblankline(int n)
1214 {
1215 if (BETWEEN(term.c.y, term.top, term.bot))
1216 tscrolldown(term.c.y, n);
1217 }
1218
1219 void
1220 tdeleteline(int n)
1221 {
1222 if (BETWEEN(term.c.y, term.top, term.bot))
1223 tscrollup(term.c.y, n);
1224 }
1225
1226 int32_t
1227 tdefcolor(int *attr, int *npar, int l)
1228 {
1229 int32_t idx = -1;
1230 uint r, g, b;
1231
1232 switch (attr[*npar + 1]) {
1233 case 2: /* direct color in RGB space */
1234 if (*npar + 4 >= l) {
1235 fprintf(stderr,
1236 "erresc(38): Incorrect number of parameters (%d)\n",
1237 *npar);
1238 break;
1239 }
1240 r = attr[*npar + 2];
1241 g = attr[*npar + 3];
1242 b = attr[*npar + 4];
1243 *npar += 4;
1244 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1245 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1246 r, g, b);
1247 else
1248 idx = TRUECOLOR(r, g, b);
1249 break;
1250 case 5: /* indexed color */
1251 if (*npar + 2 >= l) {
1252 fprintf(stderr,
1253 "erresc(38): Incorrect number of parameters (%d)\n",
1254 *npar);
1255 break;
1256 }
1257 *npar += 2;
1258 if (!BETWEEN(attr[*npar], 0, 255))
1259 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1260 else
1261 idx = attr[*npar];
1262 break;
1263 case 0: /* implemented defined (only foreground) */
1264 case 1: /* transparent */
1265 case 3: /* direct color in CMY space */
1266 case 4: /* direct color in CMYK space */
1267 default:
1268 fprintf(stderr,
1269 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1270 break;
1271 }
1272
1273 return idx;
1274 }
1275
1276 void
1277 tsetattr(int *attr, int l)
1278 {
1279 int i;
1280 int32_t idx;
1281
1282 for (i = 0; i < l; i++) {
1283 switch (attr[i]) {
1284 case 0:
1285 term.c.attr.mode &= ~(
1286 ATTR_BOLD |
1287 ATTR_FAINT |
1288 ATTR_ITALIC |
1289 ATTR_UNDERLINE |
1290 ATTR_BLINK |
1291 ATTR_REVERSE |
1292 ATTR_INVISIBLE |
1293 ATTR_STRUCK );
1294 term.c.attr.fg = defaultfg;
1295 term.c.attr.bg = defaultbg;
1296 break;
1297 case 1:
1298 term.c.attr.mode |= ATTR_BOLD;
1299 break;
1300 case 2:
1301 term.c.attr.mode |= ATTR_FAINT;
1302 break;
1303 case 3:
1304 term.c.attr.mode |= ATTR_ITALIC;
1305 break;
1306 case 4:
1307 term.c.attr.mode |= ATTR_UNDERLINE;
1308 break;
1309 case 5: /* slow blink */
1310 /* FALLTHROUGH */
1311 case 6: /* rapid blink */
1312 term.c.attr.mode |= ATTR_BLINK;
1313 break;
1314 case 7:
1315 term.c.attr.mode |= ATTR_REVERSE;
1316 break;
1317 case 8:
1318 term.c.attr.mode |= ATTR_INVISIBLE;
1319 break;
1320 case 9:
1321 term.c.attr.mode |= ATTR_STRUCK;
1322 break;
1323 case 22:
1324 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1325 break;
1326 case 23:
1327 term.c.attr.mode &= ~ATTR_ITALIC;
1328 break;
1329 case 24:
1330 term.c.attr.mode &= ~ATTR_UNDERLINE;
1331 break;
1332 case 25:
1333 term.c.attr.mode &= ~ATTR_BLINK;
1334 break;
1335 case 27:
1336 term.c.attr.mode &= ~ATTR_REVERSE;
1337 break;
1338 case 28:
1339 term.c.attr.mode &= ~ATTR_INVISIBLE;
1340 break;
1341 case 29:
1342 term.c.attr.mode &= ~ATTR_STRUCK;
1343 break;
1344 case 38:
1345 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1346 term.c.attr.fg = idx;
1347 break;
1348 case 39:
1349 term.c.attr.fg = defaultfg;
1350 break;
1351 case 48:
1352 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1353 term.c.attr.bg = idx;
1354 break;
1355 case 49:
1356 term.c.attr.bg = defaultbg;
1357 break;
1358 default:
1359 if (BETWEEN(attr[i], 30, 37)) {
1360 term.c.attr.fg = attr[i] - 30;
1361 } else if (BETWEEN(attr[i], 40, 47)) {
1362 term.c.attr.bg = attr[i] - 40;
1363 } else if (BETWEEN(attr[i], 90, 97)) {
1364 term.c.attr.fg = attr[i] - 90 + 8;
1365 } else if (BETWEEN(attr[i], 100, 107)) {
1366 term.c.attr.bg = attr[i] - 100 + 8;
1367 } else {
1368 fprintf(stderr,
1369 "erresc(default): gfx attr %d unknown\n",
1370 attr[i]), csidump();
1371 }
1372 break;
1373 }
1374 }
1375 }
1376
1377 void
1378 tsetscroll(int t, int b)
1379 {
1380 int temp;
1381
1382 LIMIT(t, 0, term.row-1);
1383 LIMIT(b, 0, term.row-1);
1384 if (t > b) {
1385 temp = t;
1386 t = b;
1387 b = temp;
1388 }
1389 term.top = t;
1390 term.bot = b;
1391 }
1392
1393 void
1394 tsetmode(int priv, int set, int *args, int narg)
1395 {
1396 int *lim, mode;
1397 int alt;
1398
1399 for (lim = args + narg; args < lim; ++args) {
1400 if (priv) {
1401 switch (*args) {
1402 case 1: /* DECCKM -- Cursor key */
1403 MODBIT(term.mode, set, MODE_APPCURSOR);
1404 break;
1405 case 5: /* DECSCNM -- Reverse video */
1406 mode = term.mode;
1407 MODBIT(term.mode, set, MODE_REVERSE);
1408 if (mode != term.mode)
1409 redraw();
1410 break;
1411 case 6: /* DECOM -- Origin */
1412 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1413 tmoveato(0, 0);
1414 break;
1415 case 7: /* DECAWM -- Auto wrap */
1416 MODBIT(term.mode, set, MODE_WRAP);
1417 break;
1418 case 0: /* Error (IGNORED) */
1419 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1420 case 3: /* DECCOLM -- Column (IGNORED) */
1421 case 4: /* DECSCLM -- Scroll (IGNORED) */
1422 case 8: /* DECARM -- Auto repeat (IGNORED) */
1423 case 18: /* DECPFF -- Printer feed (IGNORED) */
1424 case 19: /* DECPEX -- Printer extent (IGNORED) */
1425 case 42: /* DECNRCM -- National characters (IGNORED) */
1426 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1427 break;
1428 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1429 MODBIT(term.mode, !set, MODE_HIDE);
1430 break;
1431 case 9: /* X10 mouse compatibility mode */
1432 xsetpointermotion(0);
1433 MODBIT(term.mode, 0, MODE_MOUSE);
1434 MODBIT(term.mode, set, MODE_MOUSEX10);
1435 break;
1436 case 1000: /* 1000: report button press */
1437 xsetpointermotion(0);
1438 MODBIT(term.mode, 0, MODE_MOUSE);
1439 MODBIT(term.mode, set, MODE_MOUSEBTN);
1440 break;
1441 case 1002: /* 1002: report motion on button press */
1442 xsetpointermotion(0);
1443 MODBIT(term.mode, 0, MODE_MOUSE);
1444 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1445 break;
1446 case 1003: /* 1003: enable all mouse motions */
1447 xsetpointermotion(set);
1448 MODBIT(term.mode, 0, MODE_MOUSE);
1449 MODBIT(term.mode, set, MODE_MOUSEMANY);
1450 break;
1451 case 1004: /* 1004: send focus events to tty */
1452 MODBIT(term.mode, set, MODE_FOCUS);
1453 break;
1454 case 1006: /* 1006: extended reporting mode */
1455 MODBIT(term.mode, set, MODE_MOUSESGR);
1456 break;
1457 case 1034:
1458 MODBIT(term.mode, set, MODE_8BIT);
1459 break;
1460 case 1049: /* swap screen & set/restore cursor as xterm */
1461 if (!allowaltscreen)
1462 break;
1463 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1464 /* FALLTHROUGH */
1465 case 47: /* swap screen */
1466 case 1047:
1467 if (!allowaltscreen)
1468 break;
1469 alt = IS_SET(MODE_ALTSCREEN);
1470 if (alt) {
1471 tclearregion(0, 0, term.col-1,
1472 term.row-1);
1473 }
1474 if (set ^ alt) /* set is always 1 or 0 */
1475 tswapscreen();
1476 if (*args != 1049)
1477 break;
1478 /* FALLTHROUGH */
1479 case 1048:
1480 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1481 break;
1482 case 2004: /* 2004: bracketed paste mode */
1483 MODBIT(term.mode, set, MODE_BRCKTPASTE);
1484 break;
1485 /* Not implemented mouse modes. See comments there. */
1486 case 1001: /* mouse highlight mode; can hang the
1487 terminal by design when implemented. */
1488 case 1005: /* UTF-8 mouse mode; will confuse
1489 applications not supporting UTF-8
1490 and luit. */
1491 case 1015: /* urxvt mangled mouse mode; incompatible
1492 and can be mistaken for other control
1493 codes. */
1494 default:
1495 fprintf(stderr,
1496 "erresc: unknown private set/reset mode %d\n",
1497 *args);
1498 break;
1499 }
1500 } else {
1501 switch (*args) {
1502 case 0: /* Error (IGNORED) */
1503 break;
1504 case 2: /* KAM -- keyboard action */
1505 MODBIT(term.mode, set, MODE_KBDLOCK);
1506 break;
1507 case 4: /* IRM -- Insertion-replacement */
1508 MODBIT(term.mode, set, MODE_INSERT);
1509 break;
1510 case 12: /* SRM -- Send/Receive */
1511 MODBIT(term.mode, !set, MODE_ECHO);
1512 break;
1513 case 20: /* LNM -- Linefeed/new line */
1514 MODBIT(term.mode, set, MODE_CRLF);
1515 break;
1516 default:
1517 fprintf(stderr,
1518 "erresc: unknown set/reset mode %d\n",
1519 *args);
1520 break;
1521 }
1522 }
1523 }
1524 }
1525
1526 void
1527 csihandle(void)
1528 {
1529 char buf[40];
1530 int len;
1531
1532 switch (csiescseq.mode[0]) {
1533 default:
1534 unknown:
1535 fprintf(stderr, "erresc: unknown csi ");
1536 csidump();
1537 /* die(""); */
1538 break;
1539 case '@': /* ICH -- Insert <n> blank char */
1540 DEFAULT(csiescseq.arg[0], 1);
1541 tinsertblank(csiescseq.arg[0]);
1542 break;
1543 case 'A': /* CUU -- Cursor <n> Up */
1544 DEFAULT(csiescseq.arg[0], 1);
1545 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1546 break;
1547 case 'B': /* CUD -- Cursor <n> Down */
1548 case 'e': /* VPR --Cursor <n> Down */
1549 DEFAULT(csiescseq.arg[0], 1);
1550 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1551 break;
1552 case 'i': /* MC -- Media Copy */
1553 switch (csiescseq.arg[0]) {
1554 case 0:
1555 tdump();
1556 break;
1557 case 1:
1558 tdumpline(term.c.y);
1559 break;
1560 case 2:
1561 tdumpsel();
1562 break;
1563 case 4:
1564 term.mode &= ~MODE_PRINT;
1565 break;
1566 case 5:
1567 term.mode |= MODE_PRINT;
1568 break;
1569 }
1570 break;
1571 case 'c': /* DA -- Device Attributes */
1572 if (csiescseq.arg[0] == 0)
1573 ttywrite(vtiden, strlen(vtiden));
1574 break;
1575 case 'C': /* CUF -- Cursor <n> Forward */
1576 case 'a': /* HPR -- Cursor <n> Forward */
1577 DEFAULT(csiescseq.arg[0], 1);
1578 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1579 break;
1580 case 'D': /* CUB -- Cursor <n> Backward */
1581 DEFAULT(csiescseq.arg[0], 1);
1582 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1583 break;
1584 case 'E': /* CNL -- Cursor <n> Down and first col */
1585 DEFAULT(csiescseq.arg[0], 1);
1586 tmoveto(0, term.c.y+csiescseq.arg[0]);
1587 break;
1588 case 'F': /* CPL -- Cursor <n> Up and first col */
1589 DEFAULT(csiescseq.arg[0], 1);
1590 tmoveto(0, term.c.y-csiescseq.arg[0]);
1591 break;
1592 case 'g': /* TBC -- Tabulation clear */
1593 switch (csiescseq.arg[0]) {
1594 case 0: /* clear current tab stop */
1595 term.tabs[term.c.x] = 0;
1596 break;
1597 case 3: /* clear all the tabs */
1598 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1599 break;
1600 default:
1601 goto unknown;
1602 }
1603 break;
1604 case 'G': /* CHA -- Move to <col> */
1605 case '`': /* HPA */
1606 DEFAULT(csiescseq.arg[0], 1);
1607 tmoveto(csiescseq.arg[0]-1, term.c.y);
1608 break;
1609 case 'H': /* CUP -- Move to <row> <col> */
1610 case 'f': /* HVP */
1611 DEFAULT(csiescseq.arg[0], 1);
1612 DEFAULT(csiescseq.arg[1], 1);
1613 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1614 break;
1615 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1616 DEFAULT(csiescseq.arg[0], 1);
1617 tputtab(csiescseq.arg[0]);
1618 break;
1619 case 'J': /* ED -- Clear screen */
1620 selclear();
1621 switch (csiescseq.arg[0]) {
1622 case 0: /* below */
1623 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1624 if (term.c.y < term.row-1) {
1625 tclearregion(0, term.c.y+1, term.col-1,
1626 term.row-1);
1627 }
1628 break;
1629 case 1: /* above */
1630 if (term.c.y > 1)
1631 tclearregion(0, 0, term.col-1, term.c.y-1);
1632 tclearregion(0, term.c.y, term.c.x, term.c.y);
1633 break;
1634 case 2: /* all */
1635 tclearregion(0, 0, term.col-1, term.row-1);
1636 break;
1637 default:
1638 goto unknown;
1639 }
1640 break;
1641 case 'K': /* EL -- Clear line */
1642 switch (csiescseq.arg[0]) {
1643 case 0: /* right */
1644 tclearregion(term.c.x, term.c.y, term.col-1,
1645 term.c.y);
1646 break;
1647 case 1: /* left */
1648 tclearregion(0, term.c.y, term.c.x, term.c.y);
1649 break;
1650 case 2: /* all */
1651 tclearregion(0, term.c.y, term.col-1, term.c.y);
1652 break;
1653 }
1654 break;
1655 case 'S': /* SU -- Scroll <n> line up */
1656 DEFAULT(csiescseq.arg[0], 1);
1657 tscrollup(term.top, csiescseq.arg[0]);
1658 break;
1659 case 'T': /* SD -- Scroll <n> line down */
1660 DEFAULT(csiescseq.arg[0], 1);
1661 tscrolldown(term.top, csiescseq.arg[0]);
1662 break;
1663 case 'L': /* IL -- Insert <n> blank lines */
1664 DEFAULT(csiescseq.arg[0], 1);
1665 tinsertblankline(csiescseq.arg[0]);
1666 break;
1667 case 'l': /* RM -- Reset Mode */
1668 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1669 break;
1670 case 'M': /* DL -- Delete <n> lines */
1671 DEFAULT(csiescseq.arg[0], 1);
1672 tdeleteline(csiescseq.arg[0]);
1673 break;
1674 case 'X': /* ECH -- Erase <n> char */
1675 DEFAULT(csiescseq.arg[0], 1);
1676 tclearregion(term.c.x, term.c.y,
1677 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1678 break;
1679 case 'P': /* DCH -- Delete <n> char */
1680 DEFAULT(csiescseq.arg[0], 1);
1681 tdeletechar(csiescseq.arg[0]);
1682 break;
1683 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1684 DEFAULT(csiescseq.arg[0], 1);
1685 tputtab(-csiescseq.arg[0]);
1686 break;
1687 case 'd': /* VPA -- Move to <row> */
1688 DEFAULT(csiescseq.arg[0], 1);
1689 tmoveato(term.c.x, csiescseq.arg[0]-1);
1690 break;
1691 case 'h': /* SM -- Set terminal mode */
1692 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1693 break;
1694 case 'm': /* SGR -- Terminal attribute (color) */
1695 tsetattr(csiescseq.arg, csiescseq.narg);
1696 break;
1697 case 'n': /* DSR – Device Status Report (cursor position) */
1698 if (csiescseq.arg[0] == 6) {
1699 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1700 term.c.y+1, term.c.x+1);
1701 ttywrite(buf, len);
1702 }
1703 break;
1704 case 'r': /* DECSTBM -- Set Scrolling Region */
1705 if (csiescseq.priv) {
1706 goto unknown;
1707 } else {
1708 DEFAULT(csiescseq.arg[0], 1);
1709 DEFAULT(csiescseq.arg[1], term.row);
1710 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1711 tmoveato(0, 0);
1712 }
1713 break;
1714 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1715 tcursor(CURSOR_SAVE);
1716 break;
1717 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1718 tcursor(CURSOR_LOAD);
1719 break;
1720 case ' ':
1721 switch (csiescseq.mode[1]) {
1722 case 'q': /* DECSCUSR -- Set Cursor Style */
1723 if (xsetcursor(csiescseq.arg[0]))
1724 goto unknown;
1725 break;
1726 default:
1727 goto unknown;
1728 }
1729 break;
1730 }
1731 }
1732
1733 void
1734 csidump(void)
1735 {
1736 int i;
1737 uint c;
1738
1739 fprintf(stderr, "ESC[");
1740 for (i = 0; i < csiescseq.len; i++) {
1741 c = csiescseq.buf[i] & 0xff;
1742 if (isprint(c)) {
1743 putc(c, stderr);
1744 } else if (c == '\n') {
1745 fprintf(stderr, "(\\n)");
1746 } else if (c == '\r') {
1747 fprintf(stderr, "(\\r)");
1748 } else if (c == 0x1b) {
1749 fprintf(stderr, "(\\e)");
1750 } else {
1751 fprintf(stderr, "(%02x)", c);
1752 }
1753 }
1754 putc('\n', stderr);
1755 }
1756
1757 void
1758 csireset(void)
1759 {
1760 memset(&csiescseq, 0, sizeof(csiescseq));
1761 }
1762
1763 void
1764 strhandle(void)
1765 {
1766 char *p = NULL;
1767 int j, narg, par;
1768
1769 term.esc &= ~(ESC_STR_END|ESC_STR);
1770 strparse();
1771 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1772
1773 switch (strescseq.type) {
1774 case ']': /* OSC -- Operating System Command */
1775 switch (par) {
1776 case 0:
1777 case 1:
1778 case 2:
1779 if (narg > 1)
1780 xsettitle(strescseq.args[1]);
1781 return;
1782 case 52:
1783 if (narg > 2) {
1784 char *dec;
1785
1786 dec = base64dec(strescseq.args[2]);
1787 if (dec) {
1788 xsetsel(dec);
1789 xclipcopy();
1790 } else {
1791 fprintf(stderr, "erresc: invalid base64\n");
1792 }
1793 }
1794 return;
1795 case 4: /* color set */
1796 if (narg < 3)
1797 break;
1798 p = strescseq.args[2];
1799 /* FALLTHROUGH */
1800 case 104: /* color reset, here p = NULL */
1801 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1802 if (xsetcolorname(j, p)) {
1803 fprintf(stderr, "erresc: invalid color %s\n", p);
1804 } else {
1805 /*
1806 * TODO if defaultbg color is changed, borders
1807 * are dirty
1808 */
1809 redraw();
1810 }
1811 return;
1812 }
1813 break;
1814 case 'k': /* old title set compatibility */
1815 xsettitle(strescseq.args[0]);
1816 return;
1817 case 'P': /* DCS -- Device Control String */
1818 term.mode |= ESC_DCS;
1819 case '_': /* APC -- Application Program Command */
1820 case '^': /* PM -- Privacy Message */
1821 return;
1822 }
1823
1824 fprintf(stderr, "erresc: unknown str ");
1825 strdump();
1826 }
1827
1828 void
1829 strparse(void)
1830 {
1831 int c;
1832 char *p = strescseq.buf;
1833
1834 strescseq.narg = 0;
1835 strescseq.buf[strescseq.len] = '\0';
1836
1837 if (*p == '\0')
1838 return;
1839
1840 while (strescseq.narg < STR_ARG_SIZ) {
1841 strescseq.args[strescseq.narg++] = p;
1842 while ((c = *p) != ';' && c != '\0')
1843 ++p;
1844 if (c == '\0')
1845 return;
1846 *p++ = '\0';
1847 }
1848 }
1849
1850 void
1851 strdump(void)
1852 {
1853 int i;
1854 uint c;
1855
1856 fprintf(stderr, "ESC%c", strescseq.type);
1857 for (i = 0; i < strescseq.len; i++) {
1858 c = strescseq.buf[i] & 0xff;
1859 if (c == '\0') {
1860 putc('\n', stderr);
1861 return;
1862 } else if (isprint(c)) {
1863 putc(c, stderr);
1864 } else if (c == '\n') {
1865 fprintf(stderr, "(\\n)");
1866 } else if (c == '\r') {
1867 fprintf(stderr, "(\\r)");
1868 } else if (c == 0x1b) {
1869 fprintf(stderr, "(\\e)");
1870 } else {
1871 fprintf(stderr, "(%02x)", c);
1872 }
1873 }
1874 fprintf(stderr, "ESC\\\n");
1875 }
1876
1877 void
1878 strreset(void)
1879 {
1880 memset(&strescseq, 0, sizeof(strescseq));
1881 }
1882
1883 void
1884 sendbreak(const Arg *arg)
1885 {
1886 if (tcsendbreak(cmdfd, 0))
1887 perror("Error sending break");
1888 }
1889
1890 void
1891 tprinter(char *s, size_t len)
1892 {
1893 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1894 perror("Error writing to output file");
1895 close(iofd);
1896 iofd = -1;
1897 }
1898 }
1899
1900 void
1901 iso14755(const Arg *arg)
1902 {
1903 FILE *p;
1904 char *us, *e, codepoint[9], uc[UTF_SIZ];
1905 unsigned long utf32;
1906
1907 if (!(p = popen(ISO14755CMD, "r")))
1908 return;
1909
1910 us = fgets(codepoint, sizeof(codepoint), p);
1911 pclose(p);
1912
1913 if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
1914 return;
1915 if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
1916 (*e != '\n' && *e != '\0'))
1917 return;
1918
1919 ttysend(uc, utf8encode(utf32, uc));
1920 }
1921
1922 void
1923 toggleprinter(const Arg *arg)
1924 {
1925 term.mode ^= MODE_PRINT;
1926 }
1927
1928 void
1929 printscreen(const Arg *arg)
1930 {
1931 tdump();
1932 }
1933
1934 void
1935 printsel(const Arg *arg)
1936 {
1937 tdumpsel();
1938 }
1939
1940 void
1941 tdumpsel(void)
1942 {
1943 char *ptr;
1944
1945 if ((ptr = getsel())) {
1946 tprinter(ptr, strlen(ptr));
1947 free(ptr);
1948 }
1949 }
1950
1951 void
1952 tdumpline(int n)
1953 {
1954 char buf[UTF_SIZ];
1955 Glyph *bp, *end;
1956
1957 bp = &term.line[n][0];
1958 end = &bp[MIN(tlinelen(n), term.col) - 1];
1959 if (bp != end || bp->u != ' ') {
1960 for ( ;bp <= end; ++bp)
1961 tprinter(buf, utf8encode(bp->u, buf));
1962 }
1963 tprinter("\n", 1);
1964 }
1965
1966 void
1967 tdump(void)
1968 {
1969 int i;
1970
1971 for (i = 0; i < term.row; ++i)
1972 tdumpline(i);
1973 }
1974
1975 void
1976 tputtab(int n)
1977 {
1978 uint x = term.c.x;
1979
1980 if (n > 0) {
1981 while (x < term.col && n--)
1982 for (++x; x < term.col && !term.tabs[x]; ++x)
1983 /* nothing */ ;
1984 } else if (n < 0) {
1985 while (x > 0 && n++)
1986 for (--x; x > 0 && !term.tabs[x]; --x)
1987 /* nothing */ ;
1988 }
1989 term.c.x = LIMIT(x, 0, term.col-1);
1990 }
1991
1992 void
1993 tdefutf8(char ascii)
1994 {
1995 if (ascii == 'G')
1996 term.mode |= MODE_UTF8;
1997 else if (ascii == '@')
1998 term.mode &= ~MODE_UTF8;
1999 }
2000
2001 void
2002 tdeftran(char ascii)
2003 {
2004 static char cs[] = "0B";
2005 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2006 char *p;
2007
2008 if ((p = strchr(cs, ascii)) == NULL) {
2009 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2010 } else {
2011 term.trantbl[term.icharset] = vcs[p - cs];
2012 }
2013 }
2014
2015 void
2016 tdectest(char c)
2017 {
2018 int x, y;
2019
2020 if (c == '8') { /* DEC screen alignment test. */
2021 for (x = 0; x < term.col; ++x) {
2022 for (y = 0; y < term.row; ++y)
2023 tsetchar('E', &term.c.attr, x, y);
2024 }
2025 }
2026 }
2027
2028 void
2029 tstrsequence(uchar c)
2030 {
2031 strreset();
2032
2033 switch (c) {
2034 case 0x90: /* DCS -- Device Control String */
2035 c = 'P';
2036 term.esc |= ESC_DCS;
2037 break;
2038 case 0x9f: /* APC -- Application Program Command */
2039 c = '_';
2040 break;
2041 case 0x9e: /* PM -- Privacy Message */
2042 c = '^';
2043 break;
2044 case 0x9d: /* OSC -- Operating System Command */
2045 c = ']';
2046 break;
2047 }
2048 strescseq.type = c;
2049 term.esc |= ESC_STR;
2050 }
2051
2052 void
2053 tcontrolcode(uchar ascii)
2054 {
2055 switch (ascii) {
2056 case '\t': /* HT */
2057 tputtab(1);
2058 return;
2059 case '\b': /* BS */
2060 tmoveto(term.c.x-1, term.c.y);
2061 return;
2062 case '\r': /* CR */
2063 tmoveto(0, term.c.y);
2064 return;
2065 case '\f': /* LF */
2066 case '\v': /* VT */
2067 case '\n': /* LF */
2068 /* go to first col if the mode is set */
2069 tnewline(IS_SET(MODE_CRLF));
2070 return;
2071 case '\a': /* BEL */
2072 if (term.esc & ESC_STR_END) {
2073 /* backwards compatibility to xterm */
2074 strhandle();
2075 } else {
2076 xbell();
2077 }
2078 break;
2079 case '\033': /* ESC */
2080 csireset();
2081 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2082 term.esc |= ESC_START;
2083 return;
2084 case '\016': /* SO (LS1 -- Locking shift 1) */
2085 case '\017': /* SI (LS0 -- Locking shift 0) */
2086 term.charset = 1 - (ascii - '\016');
2087 return;
2088 case '\032': /* SUB */
2089 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2090 case '\030': /* CAN */
2091 csireset();
2092 break;
2093 case '\005': /* ENQ (IGNORED) */
2094 case '\000': /* NUL (IGNORED) */
2095 case '\021': /* XON (IGNORED) */
2096 case '\023': /* XOFF (IGNORED) */
2097 case 0177: /* DEL (IGNORED) */
2098 return;
2099 case 0x80: /* TODO: PAD */
2100 case 0x81: /* TODO: HOP */
2101 case 0x82: /* TODO: BPH */
2102 case 0x83: /* TODO: NBH */
2103 case 0x84: /* TODO: IND */
2104 break;
2105 case 0x85: /* NEL -- Next line */
2106 tnewline(1); /* always go to first col */
2107 break;
2108 case 0x86: /* TODO: SSA */
2109 case 0x87: /* TODO: ESA */
2110 break;
2111 case 0x88: /* HTS -- Horizontal tab stop */
2112 term.tabs[term.c.x] = 1;
2113 break;
2114 case 0x89: /* TODO: HTJ */
2115 case 0x8a: /* TODO: VTS */
2116 case 0x8b: /* TODO: PLD */
2117 case 0x8c: /* TODO: PLU */
2118 case 0x8d: /* TODO: RI */
2119 case 0x8e: /* TODO: SS2 */
2120 case 0x8f: /* TODO: SS3 */
2121 case 0x91: /* TODO: PU1 */
2122 case 0x92: /* TODO: PU2 */
2123 case 0x93: /* TODO: STS */
2124 case 0x94: /* TODO: CCH */
2125 case 0x95: /* TODO: MW */
2126 case 0x96: /* TODO: SPA */
2127 case 0x97: /* TODO: EPA */
2128 case 0x98: /* TODO: SOS */
2129 case 0x99: /* TODO: SGCI */
2130 break;
2131 case 0x9a: /* DECID -- Identify Terminal */
2132 ttywrite(vtiden, strlen(vtiden));
2133 break;
2134 case 0x9b: /* TODO: CSI */
2135 case 0x9c: /* TODO: ST */
2136 break;
2137 case 0x90: /* DCS -- Device Control String */
2138 case 0x9d: /* OSC -- Operating System Command */
2139 case 0x9e: /* PM -- Privacy Message */
2140 case 0x9f: /* APC -- Application Program Command */
2141 tstrsequence(ascii);
2142 return;
2143 }
2144 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2145 term.esc &= ~(ESC_STR_END|ESC_STR);
2146 }
2147
2148 /*
2149 * returns 1 when the sequence is finished and it hasn't to read
2150 * more characters for this sequence, otherwise 0
2151 */
2152 int
2153 eschandle(uchar ascii)
2154 {
2155 switch (ascii) {
2156 case '[':
2157 term.esc |= ESC_CSI;
2158 return 0;
2159 case '#':
2160 term.esc |= ESC_TEST;
2161 return 0;
2162 case '%':
2163 term.esc |= ESC_UTF8;
2164 return 0;
2165 case 'P': /* DCS -- Device Control String */
2166 case '_': /* APC -- Application Program Command */
2167 case '^': /* PM -- Privacy Message */
2168 case ']': /* OSC -- Operating System Command */
2169 case 'k': /* old title set compatibility */
2170 tstrsequence(ascii);
2171 return 0;
2172 case 'n': /* LS2 -- Locking shift 2 */
2173 case 'o': /* LS3 -- Locking shift 3 */
2174 term.charset = 2 + (ascii - 'n');
2175 break;
2176 case '(': /* GZD4 -- set primary charset G0 */
2177 case ')': /* G1D4 -- set secondary charset G1 */
2178 case '*': /* G2D4 -- set tertiary charset G2 */
2179 case '+': /* G3D4 -- set quaternary charset G3 */
2180 term.icharset = ascii - '(';
2181 term.esc |= ESC_ALTCHARSET;
2182 return 0;
2183 case 'D': /* IND -- Linefeed */
2184 if (term.c.y == term.bot) {
2185 tscrollup(term.top, 1);
2186 } else {
2187 tmoveto(term.c.x, term.c.y+1);
2188 }
2189 break;
2190 case 'E': /* NEL -- Next line */
2191 tnewline(1); /* always go to first col */
2192 break;
2193 case 'H': /* HTS -- Horizontal tab stop */
2194 term.tabs[term.c.x] = 1;
2195 break;
2196 case 'M': /* RI -- Reverse index */
2197 if (term.c.y == term.top) {
2198 tscrolldown(term.top, 1);
2199 } else {
2200 tmoveto(term.c.x, term.c.y-1);
2201 }
2202 break;
2203 case 'Z': /* DECID -- Identify Terminal */
2204 ttywrite(vtiden, strlen(vtiden));
2205 break;
2206 case 'c': /* RIS -- Reset to inital state */
2207 treset();
2208 resettitle();
2209 xloadcols();
2210 break;
2211 case '=': /* DECPAM -- Application keypad */
2212 term.mode |= MODE_APPKEYPAD;
2213 break;
2214 case '>': /* DECPNM -- Normal keypad */
2215 term.mode &= ~MODE_APPKEYPAD;
2216 break;
2217 case '7': /* DECSC -- Save Cursor */
2218 tcursor(CURSOR_SAVE);
2219 break;
2220 case '8': /* DECRC -- Restore Cursor */
2221 tcursor(CURSOR_LOAD);
2222 break;
2223 case '\\': /* ST -- String Terminator */
2224 if (term.esc & ESC_STR_END)
2225 strhandle();
2226 break;
2227 default:
2228 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2229 (uchar) ascii, isprint(ascii)? ascii:'.');
2230 break;
2231 }
2232 return 1;
2233 }
2234
2235 void
2236 tputc(Rune u)
2237 {
2238 char c[UTF_SIZ];
2239 int control;
2240 int width, len;
2241 Glyph *gp;
2242
2243 control = ISCONTROL(u);
2244 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2245 c[0] = u;
2246 width = len = 1;
2247 } else {
2248 len = utf8encode(u, c);
2249 if (!control && (width = wcwidth(u)) == -1) {
2250 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2251 width = 1;
2252 }
2253 }
2254
2255 if (IS_SET(MODE_PRINT))
2256 tprinter(c, len);
2257
2258 /*
2259 * STR sequence must be checked before anything else
2260 * because it uses all following characters until it
2261 * receives a ESC, a SUB, a ST or any other C1 control
2262 * character.
2263 */
2264 if (term.esc & ESC_STR) {
2265 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2266 ISCONTROLC1(u)) {
2267 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2268 if (IS_SET(MODE_SIXEL)) {
2269 /* TODO: render sixel */;
2270 term.mode &= ~MODE_SIXEL;
2271 return;
2272 }
2273 term.esc |= ESC_STR_END;
2274 goto check_control_code;
2275 }
2276
2277
2278 if (IS_SET(MODE_SIXEL)) {
2279 /* TODO: implement sixel mode */
2280 return;
2281 }
2282 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2283 term.mode |= MODE_SIXEL;
2284
2285 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2286 /*
2287 * Here is a bug in terminals. If the user never sends
2288 * some code to stop the str or esc command, then st
2289 * will stop responding. But this is better than
2290 * silently failing with unknown characters. At least
2291 * then users will report back.
2292 *
2293 * In the case users ever get fixed, here is the code:
2294 */
2295 /*
2296 * term.esc = 0;
2297 * strhandle();
2298 */
2299 return;
2300 }
2301
2302 memmove(&strescseq.buf[strescseq.len], c, len);
2303 strescseq.len += len;
2304 return;
2305 }
2306
2307 check_control_code:
2308 /*
2309 * Actions of control codes must be performed as soon they arrive
2310 * because they can be embedded inside a control sequence, and
2311 * they must not cause conflicts with sequences.
2312 */
2313 if (control) {
2314 tcontrolcode(u);
2315 /*
2316 * control codes are not shown ever
2317 */
2318 return;
2319 } else if (term.esc & ESC_START) {
2320 if (term.esc & ESC_CSI) {
2321 csiescseq.buf[csiescseq.len++] = u;
2322 if (BETWEEN(u, 0x40, 0x7E)
2323 || csiescseq.len >= \
2324 sizeof(csiescseq.buf)-1) {
2325 term.esc = 0;
2326 csiparse();
2327 csihandle();
2328 }
2329 return;
2330 } else if (term.esc & ESC_UTF8) {
2331 tdefutf8(u);
2332 } else if (term.esc & ESC_ALTCHARSET) {
2333 tdeftran(u);
2334 } else if (term.esc & ESC_TEST) {
2335 tdectest(u);
2336 } else {
2337 if (!eschandle(u))
2338 return;
2339 /* sequence already finished */
2340 }
2341 term.esc = 0;
2342 /*
2343 * All characters which form part of a sequence are not
2344 * printed
2345 */
2346 return;
2347 }
2348 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2349 selclear();
2350
2351 gp = &term.line[term.c.y][term.c.x];
2352 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2353 gp->mode |= ATTR_WRAP;
2354 tnewline(1);
2355 gp = &term.line[term.c.y][term.c.x];
2356 }
2357
2358 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2359 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2360
2361 if (term.c.x+width > term.col) {
2362 tnewline(1);
2363 gp = &term.line[term.c.y][term.c.x];
2364 }
2365
2366 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2367
2368 if (width == 2) {
2369 gp->mode |= ATTR_WIDE;
2370 if (term.c.x+1 < term.col) {
2371 gp[1].u = '\0';
2372 gp[1].mode = ATTR_WDUMMY;
2373 }
2374 }
2375 if (term.c.x+width < term.col) {
2376 tmoveto(term.c.x+width, term.c.y);
2377 } else {
2378 term.c.state |= CURSOR_WRAPNEXT;
2379 }
2380 }
2381
2382 int
2383 twrite(const char *buf, int buflen, int show_ctrl)
2384 {
2385 int charsize;
2386 Rune u;
2387 int n;
2388
2389 for (n = 0; n < buflen; n += charsize) {
2390 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2391 /* process a complete utf8 char */
2392 charsize = utf8decode(buf + n, &u, buflen - n);
2393 if (charsize == 0)
2394 break;
2395 } else {
2396 u = buf[n] & 0xFF;
2397 charsize = 1;
2398 }
2399 if (show_ctrl && ISCONTROL(u)) {
2400 if (u & 0x80) {
2401 u &= 0x7f;
2402 tputc('^');
2403 tputc('[');
2404 } else if (u != '\n' && u != '\r' && u != '\t') {
2405 u ^= 0x40;
2406 tputc('^');
2407 }
2408 }
2409 tputc(u);
2410 }
2411 return n;
2412 }
2413
2414 void
2415 tresize(int col, int row)
2416 {
2417 int i;
2418 int minrow = MIN(row, term.row);
2419 int mincol = MIN(col, term.col);
2420 int *bp;
2421 TCursor c;
2422
2423 if (col < 1 || row < 1) {
2424 fprintf(stderr,
2425 "tresize: error resizing to %dx%d\n", col, row);
2426 return;
2427 }
2428
2429 /*
2430 * slide screen to keep cursor where we expect it -
2431 * tscrollup would work here, but we can optimize to
2432 * memmove because we're freeing the earlier lines
2433 */
2434 for (i = 0; i <= term.c.y - row; i++) {
2435 free(term.line[i]);
2436 free(term.alt[i]);
2437 }
2438 /* ensure that both src and dst are not NULL */
2439 if (i > 0) {
2440 memmove(term.line, term.line + i, row * sizeof(Line));
2441 memmove(term.alt, term.alt + i, row * sizeof(Line));
2442 }
2443 for (i += row; i < term.row; i++) {
2444 free(term.line[i]);
2445 free(term.alt[i]);
2446 }
2447
2448 /* resize to new height */
2449 term.line = xrealloc(term.line, row * sizeof(Line));
2450 term.alt = xrealloc(term.alt, row * sizeof(Line));
2451 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2452 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2453
2454 /* resize each row to new width, zero-pad if needed */
2455 for (i = 0; i < minrow; i++) {
2456 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2457 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2458 }
2459
2460 /* allocate any new rows */
2461 for (/* i = minrow */; i < row; i++) {
2462 term.line[i] = xmalloc(col * sizeof(Glyph));
2463 term.alt[i] = xmalloc(col * sizeof(Glyph));
2464 }
2465 if (col > term.col) {
2466 bp = term.tabs + term.col;
2467
2468 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2469 while (--bp > term.tabs && !*bp)
2470 /* nothing */ ;
2471 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2472 *bp = 1;
2473 }
2474 /* update terminal size */
2475 term.col = col;
2476 term.row = row;
2477 /* reset scrolling region */
2478 tsetscroll(0, row-1);
2479 /* make use of the LIMIT in tmoveto */
2480 tmoveto(term.c.x, term.c.y);
2481 /* Clearing both screens (it makes dirty all lines) */
2482 c = term.c;
2483 for (i = 0; i < 2; i++) {
2484 if (mincol < col && 0 < minrow) {
2485 tclearregion(mincol, 0, col - 1, minrow - 1);
2486 }
2487 if (0 < col && minrow < row) {
2488 tclearregion(0, minrow, col - 1, row - 1);
2489 }
2490 tswapscreen();
2491 tcursor(CURSOR_LOAD);
2492 }
2493 term.c = c;
2494 }
2495
2496 void
2497 resettitle(void)
2498 {
2499 xsettitle(NULL);
2500 }
2501
2502 void
2503 redraw(void)
2504 {
2505 tfulldirt();
2506 draw();
2507 }
2508
2509 void
2510 numlock(const Arg *dummy)
2511 {
2512 term.numlock ^= 1;
2513 }