Xinqi Bao's Git

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