Xinqi Bao's Git

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