Xinqi Bao's Git

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