Fix FAQ typo
[azarus-st.git] / st.c
blob7c7ddffd7b7c53cb976f1e2521d453e415ef471a
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>
27 /* X11 */
28 #include <X11/cursorfont.h>
29 #include <X11/Xft/Xft.h>
31 char *argv0;
33 #define Glyph Glyph_
34 #define Font Font_
36 #include "win.h"
37 #include "st.h"
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
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
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)
62 /* constants */
63 #define ISO14755CMD "dmenu -w %lu -p codepoint: </dev/null"
65 enum cursor_movement {
66 CURSOR_SAVE,
67 CURSOR_LOAD
70 enum cursor_state {
71 CURSOR_DEFAULT = 0,
72 CURSOR_WRAPNEXT = 1,
73 CURSOR_ORIGIN = 2
76 enum charset {
77 CS_GRAPHIC0,
78 CS_GRAPHIC1,
79 CS_UK,
80 CS_USA,
81 CS_MULTI,
82 CS_GER,
83 CS_FIN
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,
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;
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;
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;
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 *);
142 /* config.h for applying patches and the configuration. */
143 #include "config.h"
145 static void execsh(void);
146 static void stty(void);
147 static void sigchld(int);
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);
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);
193 static void selscroll(int, int);
194 static void selsnap(int *, int *, int);
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);
201 static char *base64dec(const char *);
203 static ssize_t xwrite(int, const char *, size_t);
204 static void *xrealloc(void *, size_t);
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 */
222 static CSIEscape csiescseq;
223 static STREscape strescseq;
224 static int iofd = 1;
226 char *usedfont = NULL;
227 double usedfontsize = 0;
228 double defaultfontsize = 0;
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};
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);
241 ssize_t
242 xwrite(int fd, const char *s, size_t len)
244 size_t aux = len;
245 ssize_t r;
247 while (len > 0) {
248 r = write(fd, s, len);
249 if (r < 0)
250 return r;
251 len -= r;
252 s += r;
255 return aux;
258 void *
259 xmalloc(size_t len)
261 void *p = malloc(len);
263 if (!p)
264 die("Out of memory\n");
266 return p;
269 void *
270 xrealloc(void *p, size_t len)
272 if ((p = realloc(p, len)) == NULL)
273 die("Out of memory\n");
275 return p;
278 char *
279 xstrdup(char *s)
281 if ((s = strdup(s)) == NULL)
282 die("Out of memory\n");
284 return s;
287 size_t
288 utf8decode(char *c, Rune *u, size_t clen)
290 size_t i, j, len, type;
291 Rune udecoded;
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;
304 if (j < len)
305 return 0;
306 *u = udecoded;
307 utf8validate(u, len);
309 return len;
312 Rune
313 utf8decodebyte(char c, size_t *i)
315 for (*i = 0; *i < LEN(utfmask); ++(*i))
316 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
317 return (uchar)c & ~utfmask[*i];
319 return 0;
322 size_t
323 utf8encode(Rune u, char *c)
325 size_t len, i;
327 len = utf8validate(&u, 0);
328 if (len > UTF_SIZ)
329 return 0;
331 for (i = len - 1; i != 0; --i) {
332 c[i] = utf8encodebyte(u, 0);
333 u >>= 6;
335 c[0] = utf8encodebyte(u, len);
337 return len;
340 char
341 utf8encodebyte(Rune u, size_t i)
343 return utfbyte[i] | (u & ~utfmask[i]);
346 char *
347 utf8strchr(char *s, Rune u)
349 Rune r;
350 size_t i, j, len;
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]);
360 return NULL;
363 size_t
364 utf8validate(Rune *u, size_t i)
366 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
367 *u = UTF_INVALID;
368 for (i = 1; *u > utfmax[i]; ++i)
371 return i;
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
389 char
390 base64dec_getc(const char **src)
392 while (**src && !isprint(**src)) (*src)++;
393 return *((*src)++);
396 char *
397 base64dec(const char *src)
399 size_t in_len = strlen(src);
400 char *result, *dst;
402 if (in_len % 4)
403 in_len += 4 - (in_len % 4);
404 result = dst = xmalloc(in_len / 4 * 3 + 1);
405 while (*src) {
406 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
407 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
408 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
409 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
411 *dst++ = (a << 2) | ((b & 0x30) >> 4);
412 if (c == -1)
413 break;
414 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
415 if (d == -1)
416 break;
417 *dst++ = ((c & 0x03) << 6) | d;
419 *dst = '\0';
420 return result;
423 void
424 selinit(void)
426 clock_gettime(CLOCK_MONOTONIC, &sel.tclick1);
427 clock_gettime(CLOCK_MONOTONIC, &sel.tclick2);
428 sel.mode = SEL_IDLE;
429 sel.snap = 0;
430 sel.ob.x = -1;
431 sel.primary = NULL;
432 sel.clipboard = NULL;
436 x2col(int x)
438 x -= borderpx;
439 x /= win.cw;
441 return LIMIT(x, 0, term.col-1);
445 y2row(int y)
447 y -= borderpx;
448 y /= win.ch;
450 return LIMIT(y, 0, term.row-1);
454 tlinelen(int y)
456 int i = term.col;
458 if (term.line[y][i - 1].mode & ATTR_WRAP)
459 return i;
461 while (i > 0 && term.line[y][i - 1].u == ' ')
462 --i;
464 return i;
467 void
468 selnormalize(void)
470 int i;
472 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
473 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
474 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
475 } else {
476 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
477 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
479 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
480 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
482 selsnap(&sel.nb.x, &sel.nb.y, -1);
483 selsnap(&sel.ne.x, &sel.ne.y, +1);
485 /* expand selection over line breaks */
486 if (sel.type == SEL_RECTANGULAR)
487 return;
488 i = tlinelen(sel.nb.y);
489 if (i < sel.nb.x)
490 sel.nb.x = i;
491 if (tlinelen(sel.ne.y) <= sel.ne.x)
492 sel.ne.x = term.col - 1;
496 selected(int x, int y)
498 if (sel.mode == SEL_EMPTY)
499 return 0;
501 if (sel.type == SEL_RECTANGULAR)
502 return BETWEEN(y, sel.nb.y, sel.ne.y)
503 && BETWEEN(x, sel.nb.x, sel.ne.x);
505 return BETWEEN(y, sel.nb.y, sel.ne.y)
506 && (y != sel.nb.y || x >= sel.nb.x)
507 && (y != sel.ne.y || x <= sel.ne.x);
510 void
511 selsnap(int *x, int *y, int direction)
513 int newx, newy, xt, yt;
514 int delim, prevdelim;
515 Glyph *gp, *prevgp;
517 switch (sel.snap) {
518 case SNAP_WORD:
520 * Snap around if the word wraps around at the end or
521 * beginning of a line.
523 prevgp = &term.line[*y][*x];
524 prevdelim = ISDELIM(prevgp->u);
525 for (;;) {
526 newx = *x + direction;
527 newy = *y;
528 if (!BETWEEN(newx, 0, term.col - 1)) {
529 newy += direction;
530 newx = (newx + term.col) % term.col;
531 if (!BETWEEN(newy, 0, term.row - 1))
532 break;
534 if (direction > 0)
535 yt = *y, xt = *x;
536 else
537 yt = newy, xt = newx;
538 if (!(term.line[yt][xt].mode & ATTR_WRAP))
539 break;
542 if (newx >= tlinelen(newy))
543 break;
545 gp = &term.line[newy][newx];
546 delim = ISDELIM(gp->u);
547 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
548 || (delim && gp->u != prevgp->u)))
549 break;
551 *x = newx;
552 *y = newy;
553 prevgp = gp;
554 prevdelim = delim;
556 break;
557 case SNAP_LINE:
559 * Snap around if the the previous line or the current one
560 * has set ATTR_WRAP at its end. Then the whole next or
561 * previous line will be selected.
563 *x = (direction < 0) ? 0 : term.col - 1;
564 if (direction < 0) {
565 for (; *y > 0; *y += direction) {
566 if (!(term.line[*y-1][term.col-1].mode
567 & ATTR_WRAP)) {
568 break;
571 } else if (direction > 0) {
572 for (; *y < term.row-1; *y += direction) {
573 if (!(term.line[*y][term.col-1].mode
574 & ATTR_WRAP)) {
575 break;
579 break;
583 char *
584 getsel(void)
586 char *str, *ptr;
587 int y, bufsize, lastx, linelen;
588 Glyph *gp, *last;
590 if (sel.ob.x == -1)
591 return NULL;
593 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
594 ptr = str = xmalloc(bufsize);
596 /* append every set & selected glyph to the selection */
597 for (y = sel.nb.y; y <= sel.ne.y; y++) {
598 if ((linelen = tlinelen(y)) == 0) {
599 *ptr++ = '\n';
600 continue;
603 if (sel.type == SEL_RECTANGULAR) {
604 gp = &term.line[y][sel.nb.x];
605 lastx = sel.ne.x;
606 } else {
607 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
608 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
610 last = &term.line[y][MIN(lastx, linelen-1)];
611 while (last >= gp && last->u == ' ')
612 --last;
614 for ( ; gp <= last; ++gp) {
615 if (gp->mode & ATTR_WDUMMY)
616 continue;
618 ptr += utf8encode(gp->u, ptr);
622 * Copy and pasting of line endings is inconsistent
623 * in the inconsistent terminal and GUI world.
624 * The best solution seems like to produce '\n' when
625 * something is copied from st and convert '\n' to
626 * '\r', when something to be pasted is received by
627 * st.
628 * FIXME: Fix the computer world.
630 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
631 *ptr++ = '\n';
633 *ptr = 0;
634 return str;
637 void
638 selpaste(const Arg *dummy)
640 xselpaste();
643 void
644 clipcopy(const Arg *dummy)
646 xclipcopy();
649 void
650 clippaste(const Arg *dummy)
652 xclippaste();
655 void
656 selclear(void)
658 if (sel.ob.x == -1)
659 return;
660 sel.mode = SEL_IDLE;
661 sel.ob.x = -1;
662 tsetdirt(sel.nb.y, sel.ne.y);
665 void
666 die(const char *errstr, ...)
668 va_list ap;
670 va_start(ap, errstr);
671 vfprintf(stderr, errstr, ap);
672 va_end(ap);
673 exit(1);
676 void
677 execsh(void)
679 char **args, *sh, *prog;
680 const struct passwd *pw;
682 errno = 0;
683 if ((pw = getpwuid(getuid())) == NULL) {
684 if (errno)
685 die("getpwuid:%s\n", strerror(errno));
686 else
687 die("who are you?\n");
690 if ((sh = getenv("SHELL")) == NULL)
691 sh = (pw->pw_shell[0]) ? pw->pw_shell : shell;
693 if (opt_cmd)
694 prog = opt_cmd[0];
695 else if (utmp)
696 prog = utmp;
697 else
698 prog = sh;
699 args = (opt_cmd) ? opt_cmd : (char *[]) {prog, NULL};
701 unsetenv("COLUMNS");
702 unsetenv("LINES");
703 unsetenv("TERMCAP");
704 setenv("LOGNAME", pw->pw_name, 1);
705 setenv("USER", pw->pw_name, 1);
706 setenv("SHELL", sh, 1);
707 setenv("HOME", pw->pw_dir, 1);
708 setenv("TERM", termname, 1);
709 xsetenv();
711 signal(SIGCHLD, SIG_DFL);
712 signal(SIGHUP, SIG_DFL);
713 signal(SIGINT, SIG_DFL);
714 signal(SIGQUIT, SIG_DFL);
715 signal(SIGTERM, SIG_DFL);
716 signal(SIGALRM, SIG_DFL);
718 execvp(prog, args);
719 _exit(1);
722 void
723 sigchld(int a)
725 int stat;
726 pid_t p;
728 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
729 die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
731 if (pid != p)
732 return;
734 if (!WIFEXITED(stat) || WEXITSTATUS(stat))
735 die("child finished with error '%d'\n", stat);
736 exit(0);
740 void
741 stty(void)
743 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
744 size_t n, siz;
746 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
747 die("incorrect stty parameters\n");
748 memcpy(cmd, stty_args, n);
749 q = cmd + n;
750 siz = sizeof(cmd) - n;
751 for (p = opt_cmd; p && (s = *p); ++p) {
752 if ((n = strlen(s)) > siz-1)
753 die("stty parameter length too long\n");
754 *q++ = ' ';
755 memcpy(q, s, n);
756 q += n;
757 siz -= n + 1;
759 *q = '\0';
760 if (system(cmd) != 0)
761 perror("Couldn't call stty");
764 void
765 ttynew(void)
767 int m, s;
768 struct winsize w = {term.row, term.col, 0, 0};
770 if (opt_io) {
771 term.mode |= MODE_PRINT;
772 iofd = (!strcmp(opt_io, "-")) ?
773 1 : open(opt_io, O_WRONLY | O_CREAT, 0666);
774 if (iofd < 0) {
775 fprintf(stderr, "Error opening %s:%s\n",
776 opt_io, strerror(errno));
780 if (opt_line) {
781 if ((cmdfd = open(opt_line, O_RDWR)) < 0)
782 die("open line failed: %s\n", strerror(errno));
783 dup2(cmdfd, 0);
784 stty();
785 return;
788 /* seems to work fine on linux, openbsd and freebsd */
789 if (openpty(&m, &s, NULL, NULL, &w) < 0)
790 die("openpty failed: %s\n", strerror(errno));
792 switch (pid = fork()) {
793 case -1:
794 die("fork failed\n");
795 break;
796 case 0:
797 close(iofd);
798 setsid(); /* create a new process group */
799 dup2(s, 0);
800 dup2(s, 1);
801 dup2(s, 2);
802 if (ioctl(s, TIOCSCTTY, NULL) < 0)
803 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
804 close(s);
805 close(m);
806 execsh();
807 break;
808 default:
809 close(s);
810 cmdfd = m;
811 signal(SIGCHLD, sigchld);
812 break;
816 size_t
817 ttyread(void)
819 static char buf[BUFSIZ];
820 static int buflen = 0;
821 char *ptr;
822 int charsize; /* size of utf8 char in bytes */
823 Rune unicodep;
824 int ret;
826 /* append read bytes to unprocessed bytes */
827 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
828 die("Couldn't read from shell: %s\n", strerror(errno));
830 buflen += ret;
831 ptr = buf;
833 for (;;) {
834 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
835 /* process a complete utf8 char */
836 charsize = utf8decode(ptr, &unicodep, buflen);
837 if (charsize == 0)
838 break;
839 tputc(unicodep);
840 ptr += charsize;
841 buflen -= charsize;
843 } else {
844 if (buflen <= 0)
845 break;
846 tputc(*ptr++ & 0xFF);
847 buflen--;
850 /* keep any uncomplete utf8 char for the next call */
851 if (buflen > 0)
852 memmove(buf, ptr, buflen);
854 return ret;
857 void
858 ttywrite(const char *s, size_t n)
860 fd_set wfd, rfd;
861 ssize_t r;
862 size_t lim = 256;
865 * Remember that we are using a pty, which might be a modem line.
866 * Writing too much will clog the line. That's why we are doing this
867 * dance.
868 * FIXME: Migrate the world to Plan 9.
870 while (n > 0) {
871 FD_ZERO(&wfd);
872 FD_ZERO(&rfd);
873 FD_SET(cmdfd, &wfd);
874 FD_SET(cmdfd, &rfd);
876 /* Check if we can write. */
877 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
878 if (errno == EINTR)
879 continue;
880 die("select failed: %s\n", strerror(errno));
882 if (FD_ISSET(cmdfd, &wfd)) {
884 * Only write the bytes written by ttywrite() or the
885 * default of 256. This seems to be a reasonable value
886 * for a serial line. Bigger values might clog the I/O.
888 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
889 goto write_error;
890 if (r < n) {
892 * We weren't able to write out everything.
893 * This means the buffer is getting full
894 * again. Empty it.
896 if (n < lim)
897 lim = ttyread();
898 n -= r;
899 s += r;
900 } else {
901 /* All bytes have been written. */
902 break;
905 if (FD_ISSET(cmdfd, &rfd))
906 lim = ttyread();
908 return;
910 write_error:
911 die("write error on tty: %s\n", strerror(errno));
914 void
915 ttysend(char *s, size_t n)
917 int len;
918 char *t, *lim;
919 Rune u;
921 ttywrite(s, n);
922 if (!IS_SET(MODE_ECHO))
923 return;
925 lim = &s[n];
926 for (t = s; t < lim; t += len) {
927 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
928 len = utf8decode(t, &u, n);
929 } else {
930 u = *t & 0xFF;
931 len = 1;
933 if (len <= 0)
934 break;
935 techo(u);
936 n -= len;
940 void
941 ttyresize(void)
943 struct winsize w;
945 w.ws_row = term.row;
946 w.ws_col = term.col;
947 w.ws_xpixel = win.tw;
948 w.ws_ypixel = win.th;
949 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
950 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
954 tattrset(int attr)
956 int i, j;
958 for (i = 0; i < term.row-1; i++) {
959 for (j = 0; j < term.col-1; j++) {
960 if (term.line[i][j].mode & attr)
961 return 1;
965 return 0;
968 void
969 tsetdirt(int top, int bot)
971 int i;
973 LIMIT(top, 0, term.row-1);
974 LIMIT(bot, 0, term.row-1);
976 for (i = top; i <= bot; i++)
977 term.dirty[i] = 1;
980 void
981 tsetdirtattr(int attr)
983 int i, j;
985 for (i = 0; i < term.row-1; i++) {
986 for (j = 0; j < term.col-1; j++) {
987 if (term.line[i][j].mode & attr) {
988 tsetdirt(i, i);
989 break;
995 void
996 tfulldirt(void)
998 tsetdirt(0, term.row-1);
1001 void
1002 tcursor(int mode)
1004 static TCursor c[2];
1005 int alt = IS_SET(MODE_ALTSCREEN);
1007 if (mode == CURSOR_SAVE) {
1008 c[alt] = term.c;
1009 } else if (mode == CURSOR_LOAD) {
1010 term.c = c[alt];
1011 tmoveto(c[alt].x, c[alt].y);
1015 void
1016 treset(void)
1018 uint i;
1020 term.c = (TCursor){{
1021 .mode = ATTR_NULL,
1022 .fg = defaultfg,
1023 .bg = defaultbg
1024 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1026 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1027 for (i = tabspaces; i < term.col; i += tabspaces)
1028 term.tabs[i] = 1;
1029 term.top = 0;
1030 term.bot = term.row - 1;
1031 term.mode = MODE_WRAP|MODE_UTF8;
1032 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1033 term.charset = 0;
1035 for (i = 0; i < 2; i++) {
1036 tmoveto(0, 0);
1037 tcursor(CURSOR_SAVE);
1038 tclearregion(0, 0, term.col-1, term.row-1);
1039 tswapscreen();
1043 void
1044 tnew(int col, int row)
1046 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1047 tresize(col, row);
1048 term.numlock = 1;
1050 treset();
1053 void
1054 tswapscreen(void)
1056 Line *tmp = term.line;
1058 term.line = term.alt;
1059 term.alt = tmp;
1060 term.mode ^= MODE_ALTSCREEN;
1061 tfulldirt();
1064 void
1065 tscrolldown(int orig, int n)
1067 int i;
1068 Line temp;
1070 LIMIT(n, 0, term.bot-orig+1);
1072 tsetdirt(orig, term.bot-n);
1073 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1075 for (i = term.bot; i >= orig+n; i--) {
1076 temp = term.line[i];
1077 term.line[i] = term.line[i-n];
1078 term.line[i-n] = temp;
1081 selscroll(orig, n);
1084 void
1085 tscrollup(int orig, int n)
1087 int i;
1088 Line temp;
1090 LIMIT(n, 0, term.bot-orig+1);
1092 tclearregion(0, orig, term.col-1, orig+n-1);
1093 tsetdirt(orig+n, term.bot);
1095 for (i = orig; i <= term.bot-n; i++) {
1096 temp = term.line[i];
1097 term.line[i] = term.line[i+n];
1098 term.line[i+n] = temp;
1101 selscroll(orig, -n);
1104 void
1105 selscroll(int orig, int n)
1107 if (sel.ob.x == -1)
1108 return;
1110 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1111 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1112 selclear();
1113 return;
1115 if (sel.type == SEL_RECTANGULAR) {
1116 if (sel.ob.y < term.top)
1117 sel.ob.y = term.top;
1118 if (sel.oe.y > term.bot)
1119 sel.oe.y = term.bot;
1120 } else {
1121 if (sel.ob.y < term.top) {
1122 sel.ob.y = term.top;
1123 sel.ob.x = 0;
1125 if (sel.oe.y > term.bot) {
1126 sel.oe.y = term.bot;
1127 sel.oe.x = term.col;
1130 selnormalize();
1134 void
1135 tnewline(int first_col)
1137 int y = term.c.y;
1139 if (y == term.bot) {
1140 tscrollup(term.top, 1);
1141 } else {
1142 y++;
1144 tmoveto(first_col ? 0 : term.c.x, y);
1147 void
1148 csiparse(void)
1150 char *p = csiescseq.buf, *np;
1151 long int v;
1153 csiescseq.narg = 0;
1154 if (*p == '?') {
1155 csiescseq.priv = 1;
1156 p++;
1159 csiescseq.buf[csiescseq.len] = '\0';
1160 while (p < csiescseq.buf+csiescseq.len) {
1161 np = NULL;
1162 v = strtol(p, &np, 10);
1163 if (np == p)
1164 v = 0;
1165 if (v == LONG_MAX || v == LONG_MIN)
1166 v = -1;
1167 csiescseq.arg[csiescseq.narg++] = v;
1168 p = np;
1169 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1170 break;
1171 p++;
1173 csiescseq.mode[0] = *p++;
1174 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1177 /* for absolute user moves, when decom is set */
1178 void
1179 tmoveato(int x, int y)
1181 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1184 void
1185 tmoveto(int x, int y)
1187 int miny, maxy;
1189 if (term.c.state & CURSOR_ORIGIN) {
1190 miny = term.top;
1191 maxy = term.bot;
1192 } else {
1193 miny = 0;
1194 maxy = term.row - 1;
1196 term.c.state &= ~CURSOR_WRAPNEXT;
1197 term.c.x = LIMIT(x, 0, term.col-1);
1198 term.c.y = LIMIT(y, miny, maxy);
1201 void
1202 tsetchar(Rune u, Glyph *attr, int x, int y)
1204 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1205 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1206 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1207 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1208 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1209 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1210 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1211 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1212 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1216 * The table is proudly stolen from rxvt.
1218 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1219 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1220 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1222 if (term.line[y][x].mode & ATTR_WIDE) {
1223 if (x+1 < term.col) {
1224 term.line[y][x+1].u = ' ';
1225 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1227 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1228 term.line[y][x-1].u = ' ';
1229 term.line[y][x-1].mode &= ~ATTR_WIDE;
1232 term.dirty[y] = 1;
1233 term.line[y][x] = *attr;
1234 term.line[y][x].u = u;
1237 void
1238 tclearregion(int x1, int y1, int x2, int y2)
1240 int x, y, temp;
1241 Glyph *gp;
1243 if (x1 > x2)
1244 temp = x1, x1 = x2, x2 = temp;
1245 if (y1 > y2)
1246 temp = y1, y1 = y2, y2 = temp;
1248 LIMIT(x1, 0, term.col-1);
1249 LIMIT(x2, 0, term.col-1);
1250 LIMIT(y1, 0, term.row-1);
1251 LIMIT(y2, 0, term.row-1);
1253 for (y = y1; y <= y2; y++) {
1254 term.dirty[y] = 1;
1255 for (x = x1; x <= x2; x++) {
1256 gp = &term.line[y][x];
1257 if (selected(x, y))
1258 selclear();
1259 gp->fg = term.c.attr.fg;
1260 gp->bg = term.c.attr.bg;
1261 gp->mode = 0;
1262 gp->u = ' ';
1267 void
1268 tdeletechar(int n)
1270 int dst, src, size;
1271 Glyph *line;
1273 LIMIT(n, 0, term.col - term.c.x);
1275 dst = term.c.x;
1276 src = term.c.x + n;
1277 size = term.col - src;
1278 line = term.line[term.c.y];
1280 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1281 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1284 void
1285 tinsertblank(int n)
1287 int dst, src, size;
1288 Glyph *line;
1290 LIMIT(n, 0, term.col - term.c.x);
1292 dst = term.c.x + n;
1293 src = term.c.x;
1294 size = term.col - dst;
1295 line = term.line[term.c.y];
1297 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1298 tclearregion(src, term.c.y, dst - 1, term.c.y);
1301 void
1302 tinsertblankline(int n)
1304 if (BETWEEN(term.c.y, term.top, term.bot))
1305 tscrolldown(term.c.y, n);
1308 void
1309 tdeleteline(int n)
1311 if (BETWEEN(term.c.y, term.top, term.bot))
1312 tscrollup(term.c.y, n);
1315 int32_t
1316 tdefcolor(int *attr, int *npar, int l)
1318 int32_t idx = -1;
1319 uint r, g, b;
1321 switch (attr[*npar + 1]) {
1322 case 2: /* direct color in RGB space */
1323 if (*npar + 4 >= l) {
1324 fprintf(stderr,
1325 "erresc(38): Incorrect number of parameters (%d)\n",
1326 *npar);
1327 break;
1329 r = attr[*npar + 2];
1330 g = attr[*npar + 3];
1331 b = attr[*npar + 4];
1332 *npar += 4;
1333 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1334 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1335 r, g, b);
1336 else
1337 idx = TRUECOLOR(r, g, b);
1338 break;
1339 case 5: /* indexed color */
1340 if (*npar + 2 >= l) {
1341 fprintf(stderr,
1342 "erresc(38): Incorrect number of parameters (%d)\n",
1343 *npar);
1344 break;
1346 *npar += 2;
1347 if (!BETWEEN(attr[*npar], 0, 255))
1348 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1349 else
1350 idx = attr[*npar];
1351 break;
1352 case 0: /* implemented defined (only foreground) */
1353 case 1: /* transparent */
1354 case 3: /* direct color in CMY space */
1355 case 4: /* direct color in CMYK space */
1356 default:
1357 fprintf(stderr,
1358 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1359 break;
1362 return idx;
1365 void
1366 tsetattr(int *attr, int l)
1368 int i;
1369 int32_t idx;
1371 for (i = 0; i < l; i++) {
1372 switch (attr[i]) {
1373 case 0:
1374 term.c.attr.mode &= ~(
1375 ATTR_BOLD |
1376 ATTR_FAINT |
1377 ATTR_ITALIC |
1378 ATTR_UNDERLINE |
1379 ATTR_BLINK |
1380 ATTR_REVERSE |
1381 ATTR_INVISIBLE |
1382 ATTR_STRUCK );
1383 term.c.attr.fg = defaultfg;
1384 term.c.attr.bg = defaultbg;
1385 break;
1386 case 1:
1387 term.c.attr.mode |= ATTR_BOLD;
1388 break;
1389 case 2:
1390 term.c.attr.mode |= ATTR_FAINT;
1391 break;
1392 case 3:
1393 term.c.attr.mode |= ATTR_ITALIC;
1394 break;
1395 case 4:
1396 term.c.attr.mode |= ATTR_UNDERLINE;
1397 break;
1398 case 5: /* slow blink */
1399 /* FALLTHROUGH */
1400 case 6: /* rapid blink */
1401 term.c.attr.mode |= ATTR_BLINK;
1402 break;
1403 case 7:
1404 term.c.attr.mode |= ATTR_REVERSE;
1405 break;
1406 case 8:
1407 term.c.attr.mode |= ATTR_INVISIBLE;
1408 break;
1409 case 9:
1410 term.c.attr.mode |= ATTR_STRUCK;
1411 break;
1412 case 22:
1413 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1414 break;
1415 case 23:
1416 term.c.attr.mode &= ~ATTR_ITALIC;
1417 break;
1418 case 24:
1419 term.c.attr.mode &= ~ATTR_UNDERLINE;
1420 break;
1421 case 25:
1422 term.c.attr.mode &= ~ATTR_BLINK;
1423 break;
1424 case 27:
1425 term.c.attr.mode &= ~ATTR_REVERSE;
1426 break;
1427 case 28:
1428 term.c.attr.mode &= ~ATTR_INVISIBLE;
1429 break;
1430 case 29:
1431 term.c.attr.mode &= ~ATTR_STRUCK;
1432 break;
1433 case 38:
1434 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1435 term.c.attr.fg = idx;
1436 break;
1437 case 39:
1438 term.c.attr.fg = defaultfg;
1439 break;
1440 case 48:
1441 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1442 term.c.attr.bg = idx;
1443 break;
1444 case 49:
1445 term.c.attr.bg = defaultbg;
1446 break;
1447 default:
1448 if (BETWEEN(attr[i], 30, 37)) {
1449 term.c.attr.fg = attr[i] - 30;
1450 } else if (BETWEEN(attr[i], 40, 47)) {
1451 term.c.attr.bg = attr[i] - 40;
1452 } else if (BETWEEN(attr[i], 90, 97)) {
1453 term.c.attr.fg = attr[i] - 90 + 8;
1454 } else if (BETWEEN(attr[i], 100, 107)) {
1455 term.c.attr.bg = attr[i] - 100 + 8;
1456 } else {
1457 fprintf(stderr,
1458 "erresc(default): gfx attr %d unknown\n",
1459 attr[i]), csidump();
1461 break;
1466 void
1467 tsetscroll(int t, int b)
1469 int temp;
1471 LIMIT(t, 0, term.row-1);
1472 LIMIT(b, 0, term.row-1);
1473 if (t > b) {
1474 temp = t;
1475 t = b;
1476 b = temp;
1478 term.top = t;
1479 term.bot = b;
1482 void
1483 tsetmode(int priv, int set, int *args, int narg)
1485 int *lim, mode;
1486 int alt;
1488 for (lim = args + narg; args < lim; ++args) {
1489 if (priv) {
1490 switch (*args) {
1491 case 1: /* DECCKM -- Cursor key */
1492 MODBIT(term.mode, set, MODE_APPCURSOR);
1493 break;
1494 case 5: /* DECSCNM -- Reverse video */
1495 mode = term.mode;
1496 MODBIT(term.mode, set, MODE_REVERSE);
1497 if (mode != term.mode)
1498 redraw();
1499 break;
1500 case 6: /* DECOM -- Origin */
1501 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1502 tmoveato(0, 0);
1503 break;
1504 case 7: /* DECAWM -- Auto wrap */
1505 MODBIT(term.mode, set, MODE_WRAP);
1506 break;
1507 case 0: /* Error (IGNORED) */
1508 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1509 case 3: /* DECCOLM -- Column (IGNORED) */
1510 case 4: /* DECSCLM -- Scroll (IGNORED) */
1511 case 8: /* DECARM -- Auto repeat (IGNORED) */
1512 case 18: /* DECPFF -- Printer feed (IGNORED) */
1513 case 19: /* DECPEX -- Printer extent (IGNORED) */
1514 case 42: /* DECNRCM -- National characters (IGNORED) */
1515 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1516 break;
1517 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1518 MODBIT(term.mode, !set, MODE_HIDE);
1519 break;
1520 case 9: /* X10 mouse compatibility mode */
1521 xsetpointermotion(0);
1522 MODBIT(term.mode, 0, MODE_MOUSE);
1523 MODBIT(term.mode, set, MODE_MOUSEX10);
1524 break;
1525 case 1000: /* 1000: report button press */
1526 xsetpointermotion(0);
1527 MODBIT(term.mode, 0, MODE_MOUSE);
1528 MODBIT(term.mode, set, MODE_MOUSEBTN);
1529 break;
1530 case 1002: /* 1002: report motion on button press */
1531 xsetpointermotion(0);
1532 MODBIT(term.mode, 0, MODE_MOUSE);
1533 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1534 break;
1535 case 1003: /* 1003: enable all mouse motions */
1536 xsetpointermotion(set);
1537 MODBIT(term.mode, 0, MODE_MOUSE);
1538 MODBIT(term.mode, set, MODE_MOUSEMANY);
1539 break;
1540 case 1004: /* 1004: send focus events to tty */
1541 MODBIT(term.mode, set, MODE_FOCUS);
1542 break;
1543 case 1006: /* 1006: extended reporting mode */
1544 MODBIT(term.mode, set, MODE_MOUSESGR);
1545 break;
1546 case 1034:
1547 MODBIT(term.mode, set, MODE_8BIT);
1548 break;
1549 case 1049: /* swap screen & set/restore cursor as xterm */
1550 if (!allowaltscreen)
1551 break;
1552 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1553 /* FALLTHROUGH */
1554 case 47: /* swap screen */
1555 case 1047:
1556 if (!allowaltscreen)
1557 break;
1558 alt = IS_SET(MODE_ALTSCREEN);
1559 if (alt) {
1560 tclearregion(0, 0, term.col-1,
1561 term.row-1);
1563 if (set ^ alt) /* set is always 1 or 0 */
1564 tswapscreen();
1565 if (*args != 1049)
1566 break;
1567 /* FALLTHROUGH */
1568 case 1048:
1569 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1570 break;
1571 case 2004: /* 2004: bracketed paste mode */
1572 MODBIT(term.mode, set, MODE_BRCKTPASTE);
1573 break;
1574 /* Not implemented mouse modes. See comments there. */
1575 case 1001: /* mouse highlight mode; can hang the
1576 terminal by design when implemented. */
1577 case 1005: /* UTF-8 mouse mode; will confuse
1578 applications not supporting UTF-8
1579 and luit. */
1580 case 1015: /* urxvt mangled mouse mode; incompatible
1581 and can be mistaken for other control
1582 codes. */
1583 default:
1584 fprintf(stderr,
1585 "erresc: unknown private set/reset mode %d\n",
1586 *args);
1587 break;
1589 } else {
1590 switch (*args) {
1591 case 0: /* Error (IGNORED) */
1592 break;
1593 case 2: /* KAM -- keyboard action */
1594 MODBIT(term.mode, set, MODE_KBDLOCK);
1595 break;
1596 case 4: /* IRM -- Insertion-replacement */
1597 MODBIT(term.mode, set, MODE_INSERT);
1598 break;
1599 case 12: /* SRM -- Send/Receive */
1600 MODBIT(term.mode, !set, MODE_ECHO);
1601 break;
1602 case 20: /* LNM -- Linefeed/new line */
1603 MODBIT(term.mode, set, MODE_CRLF);
1604 break;
1605 default:
1606 fprintf(stderr,
1607 "erresc: unknown set/reset mode %d\n",
1608 *args);
1609 break;
1615 void
1616 csihandle(void)
1618 char buf[40];
1619 int len;
1621 switch (csiescseq.mode[0]) {
1622 default:
1623 unknown:
1624 fprintf(stderr, "erresc: unknown csi ");
1625 csidump();
1626 /* die(""); */
1627 break;
1628 case '@': /* ICH -- Insert <n> blank char */
1629 DEFAULT(csiescseq.arg[0], 1);
1630 tinsertblank(csiescseq.arg[0]);
1631 break;
1632 case 'A': /* CUU -- Cursor <n> Up */
1633 DEFAULT(csiescseq.arg[0], 1);
1634 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1635 break;
1636 case 'B': /* CUD -- Cursor <n> Down */
1637 case 'e': /* VPR --Cursor <n> Down */
1638 DEFAULT(csiescseq.arg[0], 1);
1639 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1640 break;
1641 case 'i': /* MC -- Media Copy */
1642 switch (csiescseq.arg[0]) {
1643 case 0:
1644 tdump();
1645 break;
1646 case 1:
1647 tdumpline(term.c.y);
1648 break;
1649 case 2:
1650 tdumpsel();
1651 break;
1652 case 4:
1653 term.mode &= ~MODE_PRINT;
1654 break;
1655 case 5:
1656 term.mode |= MODE_PRINT;
1657 break;
1659 break;
1660 case 'c': /* DA -- Device Attributes */
1661 if (csiescseq.arg[0] == 0)
1662 ttywrite(vtiden, sizeof(vtiden) - 1);
1663 break;
1664 case 'C': /* CUF -- Cursor <n> Forward */
1665 case 'a': /* HPR -- Cursor <n> Forward */
1666 DEFAULT(csiescseq.arg[0], 1);
1667 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1668 break;
1669 case 'D': /* CUB -- Cursor <n> Backward */
1670 DEFAULT(csiescseq.arg[0], 1);
1671 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1672 break;
1673 case 'E': /* CNL -- Cursor <n> Down and first col */
1674 DEFAULT(csiescseq.arg[0], 1);
1675 tmoveto(0, term.c.y+csiescseq.arg[0]);
1676 break;
1677 case 'F': /* CPL -- Cursor <n> Up and first col */
1678 DEFAULT(csiescseq.arg[0], 1);
1679 tmoveto(0, term.c.y-csiescseq.arg[0]);
1680 break;
1681 case 'g': /* TBC -- Tabulation clear */
1682 switch (csiescseq.arg[0]) {
1683 case 0: /* clear current tab stop */
1684 term.tabs[term.c.x] = 0;
1685 break;
1686 case 3: /* clear all the tabs */
1687 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1688 break;
1689 default:
1690 goto unknown;
1692 break;
1693 case 'G': /* CHA -- Move to <col> */
1694 case '`': /* HPA */
1695 DEFAULT(csiescseq.arg[0], 1);
1696 tmoveto(csiescseq.arg[0]-1, term.c.y);
1697 break;
1698 case 'H': /* CUP -- Move to <row> <col> */
1699 case 'f': /* HVP */
1700 DEFAULT(csiescseq.arg[0], 1);
1701 DEFAULT(csiescseq.arg[1], 1);
1702 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1703 break;
1704 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1705 DEFAULT(csiescseq.arg[0], 1);
1706 tputtab(csiescseq.arg[0]);
1707 break;
1708 case 'J': /* ED -- Clear screen */
1709 selclear();
1710 switch (csiescseq.arg[0]) {
1711 case 0: /* below */
1712 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1713 if (term.c.y < term.row-1) {
1714 tclearregion(0, term.c.y+1, term.col-1,
1715 term.row-1);
1717 break;
1718 case 1: /* above */
1719 if (term.c.y > 1)
1720 tclearregion(0, 0, term.col-1, term.c.y-1);
1721 tclearregion(0, term.c.y, term.c.x, term.c.y);
1722 break;
1723 case 2: /* all */
1724 tclearregion(0, 0, term.col-1, term.row-1);
1725 break;
1726 default:
1727 goto unknown;
1729 break;
1730 case 'K': /* EL -- Clear line */
1731 switch (csiescseq.arg[0]) {
1732 case 0: /* right */
1733 tclearregion(term.c.x, term.c.y, term.col-1,
1734 term.c.y);
1735 break;
1736 case 1: /* left */
1737 tclearregion(0, term.c.y, term.c.x, term.c.y);
1738 break;
1739 case 2: /* all */
1740 tclearregion(0, term.c.y, term.col-1, term.c.y);
1741 break;
1743 break;
1744 case 'S': /* SU -- Scroll <n> line up */
1745 DEFAULT(csiescseq.arg[0], 1);
1746 tscrollup(term.top, csiescseq.arg[0]);
1747 break;
1748 case 'T': /* SD -- Scroll <n> line down */
1749 DEFAULT(csiescseq.arg[0], 1);
1750 tscrolldown(term.top, csiescseq.arg[0]);
1751 break;
1752 case 'L': /* IL -- Insert <n> blank lines */
1753 DEFAULT(csiescseq.arg[0], 1);
1754 tinsertblankline(csiescseq.arg[0]);
1755 break;
1756 case 'l': /* RM -- Reset Mode */
1757 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1758 break;
1759 case 'M': /* DL -- Delete <n> lines */
1760 DEFAULT(csiescseq.arg[0], 1);
1761 tdeleteline(csiescseq.arg[0]);
1762 break;
1763 case 'X': /* ECH -- Erase <n> char */
1764 DEFAULT(csiescseq.arg[0], 1);
1765 tclearregion(term.c.x, term.c.y,
1766 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1767 break;
1768 case 'P': /* DCH -- Delete <n> char */
1769 DEFAULT(csiescseq.arg[0], 1);
1770 tdeletechar(csiescseq.arg[0]);
1771 break;
1772 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1773 DEFAULT(csiescseq.arg[0], 1);
1774 tputtab(-csiescseq.arg[0]);
1775 break;
1776 case 'd': /* VPA -- Move to <row> */
1777 DEFAULT(csiescseq.arg[0], 1);
1778 tmoveato(term.c.x, csiescseq.arg[0]-1);
1779 break;
1780 case 'h': /* SM -- Set terminal mode */
1781 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1782 break;
1783 case 'm': /* SGR -- Terminal attribute (color) */
1784 tsetattr(csiescseq.arg, csiescseq.narg);
1785 break;
1786 case 'n': /* DSR – Device Status Report (cursor position) */
1787 if (csiescseq.arg[0] == 6) {
1788 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1789 term.c.y+1, term.c.x+1);
1790 ttywrite(buf, len);
1792 break;
1793 case 'r': /* DECSTBM -- Set Scrolling Region */
1794 if (csiescseq.priv) {
1795 goto unknown;
1796 } else {
1797 DEFAULT(csiescseq.arg[0], 1);
1798 DEFAULT(csiescseq.arg[1], term.row);
1799 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1800 tmoveato(0, 0);
1802 break;
1803 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1804 tcursor(CURSOR_SAVE);
1805 break;
1806 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1807 tcursor(CURSOR_LOAD);
1808 break;
1809 case ' ':
1810 switch (csiescseq.mode[1]) {
1811 case 'q': /* DECSCUSR -- Set Cursor Style */
1812 DEFAULT(csiescseq.arg[0], 1);
1813 if (!BETWEEN(csiescseq.arg[0], 0, 6)) {
1814 goto unknown;
1816 win.cursor = csiescseq.arg[0];
1817 break;
1818 default:
1819 goto unknown;
1821 break;
1825 void
1826 csidump(void)
1828 int i;
1829 uint c;
1831 fprintf(stderr, "ESC[");
1832 for (i = 0; i < csiescseq.len; i++) {
1833 c = csiescseq.buf[i] & 0xff;
1834 if (isprint(c)) {
1835 putc(c, stderr);
1836 } else if (c == '\n') {
1837 fprintf(stderr, "(\\n)");
1838 } else if (c == '\r') {
1839 fprintf(stderr, "(\\r)");
1840 } else if (c == 0x1b) {
1841 fprintf(stderr, "(\\e)");
1842 } else {
1843 fprintf(stderr, "(%02x)", c);
1846 putc('\n', stderr);
1849 void
1850 csireset(void)
1852 memset(&csiescseq, 0, sizeof(csiescseq));
1855 void
1856 strhandle(void)
1858 char *p = NULL;
1859 int j, narg, par;
1861 term.esc &= ~(ESC_STR_END|ESC_STR);
1862 strparse();
1863 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1865 switch (strescseq.type) {
1866 case ']': /* OSC -- Operating System Command */
1867 switch (par) {
1868 case 0:
1869 case 1:
1870 case 2:
1871 if (narg > 1)
1872 xsettitle(strescseq.args[1]);
1873 return;
1874 case 52:
1875 if (narg > 2) {
1876 char *dec;
1878 dec = base64dec(strescseq.args[2]);
1879 if (dec) {
1880 xsetsel(dec, CurrentTime);
1881 clipcopy(NULL);
1882 } else {
1883 fprintf(stderr, "erresc: invalid base64\n");
1886 return;
1887 case 4: /* color set */
1888 if (narg < 3)
1889 break;
1890 p = strescseq.args[2];
1891 /* FALLTHROUGH */
1892 case 104: /* color reset, here p = NULL */
1893 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1894 if (xsetcolorname(j, p)) {
1895 fprintf(stderr, "erresc: invalid color %s\n", p);
1896 } else {
1898 * TODO if defaultbg color is changed, borders
1899 * are dirty
1901 redraw();
1903 return;
1905 break;
1906 case 'k': /* old title set compatibility */
1907 xsettitle(strescseq.args[0]);
1908 return;
1909 case 'P': /* DCS -- Device Control String */
1910 term.mode |= ESC_DCS;
1911 case '_': /* APC -- Application Program Command */
1912 case '^': /* PM -- Privacy Message */
1913 return;
1916 fprintf(stderr, "erresc: unknown str ");
1917 strdump();
1920 void
1921 strparse(void)
1923 int c;
1924 char *p = strescseq.buf;
1926 strescseq.narg = 0;
1927 strescseq.buf[strescseq.len] = '\0';
1929 if (*p == '\0')
1930 return;
1932 while (strescseq.narg < STR_ARG_SIZ) {
1933 strescseq.args[strescseq.narg++] = p;
1934 while ((c = *p) != ';' && c != '\0')
1935 ++p;
1936 if (c == '\0')
1937 return;
1938 *p++ = '\0';
1942 void
1943 strdump(void)
1945 int i;
1946 uint c;
1948 fprintf(stderr, "ESC%c", strescseq.type);
1949 for (i = 0; i < strescseq.len; i++) {
1950 c = strescseq.buf[i] & 0xff;
1951 if (c == '\0') {
1952 putc('\n', stderr);
1953 return;
1954 } else if (isprint(c)) {
1955 putc(c, stderr);
1956 } else if (c == '\n') {
1957 fprintf(stderr, "(\\n)");
1958 } else if (c == '\r') {
1959 fprintf(stderr, "(\\r)");
1960 } else if (c == 0x1b) {
1961 fprintf(stderr, "(\\e)");
1962 } else {
1963 fprintf(stderr, "(%02x)", c);
1966 fprintf(stderr, "ESC\\\n");
1969 void
1970 strreset(void)
1972 memset(&strescseq, 0, sizeof(strescseq));
1975 void
1976 sendbreak(const Arg *arg)
1978 if (tcsendbreak(cmdfd, 0))
1979 perror("Error sending break");
1982 void
1983 tprinter(char *s, size_t len)
1985 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1986 fprintf(stderr, "Error writing in %s:%s\n",
1987 opt_io, strerror(errno));
1988 close(iofd);
1989 iofd = -1;
1993 void
1994 iso14755(const Arg *arg)
1996 unsigned long id = xwinid();
1997 char cmd[sizeof(ISO14755CMD) + NUMMAXLEN(id)];
1998 FILE *p;
1999 char *us, *e, codepoint[9], uc[UTF_SIZ];
2000 unsigned long utf32;
2002 snprintf(cmd, sizeof(cmd), ISO14755CMD, id);
2003 if (!(p = popen(cmd, "r")))
2004 return;
2006 us = fgets(codepoint, sizeof(codepoint), p);
2007 pclose(p);
2009 if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
2010 return;
2011 if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
2012 (*e != '\n' && *e != '\0'))
2013 return;
2015 ttysend(uc, utf8encode(utf32, uc));
2018 void
2019 toggleprinter(const Arg *arg)
2021 term.mode ^= MODE_PRINT;
2024 void
2025 printscreen(const Arg *arg)
2027 tdump();
2030 void
2031 printsel(const Arg *arg)
2033 tdumpsel();
2036 void
2037 tdumpsel(void)
2039 char *ptr;
2041 if ((ptr = getsel())) {
2042 tprinter(ptr, strlen(ptr));
2043 free(ptr);
2047 void
2048 tdumpline(int n)
2050 char buf[UTF_SIZ];
2051 Glyph *bp, *end;
2053 bp = &term.line[n][0];
2054 end = &bp[MIN(tlinelen(n), term.col) - 1];
2055 if (bp != end || bp->u != ' ') {
2056 for ( ;bp <= end; ++bp)
2057 tprinter(buf, utf8encode(bp->u, buf));
2059 tprinter("\n", 1);
2062 void
2063 tdump(void)
2065 int i;
2067 for (i = 0; i < term.row; ++i)
2068 tdumpline(i);
2071 void
2072 tputtab(int n)
2074 uint x = term.c.x;
2076 if (n > 0) {
2077 while (x < term.col && n--)
2078 for (++x; x < term.col && !term.tabs[x]; ++x)
2079 /* nothing */ ;
2080 } else if (n < 0) {
2081 while (x > 0 && n++)
2082 for (--x; x > 0 && !term.tabs[x]; --x)
2083 /* nothing */ ;
2085 term.c.x = LIMIT(x, 0, term.col-1);
2088 void
2089 techo(Rune u)
2091 if (ISCONTROL(u)) { /* control code */
2092 if (u & 0x80) {
2093 u &= 0x7f;
2094 tputc('^');
2095 tputc('[');
2096 } else if (u != '\n' && u != '\r' && u != '\t') {
2097 u ^= 0x40;
2098 tputc('^');
2101 tputc(u);
2104 void
2105 tdefutf8(char ascii)
2107 if (ascii == 'G')
2108 term.mode |= MODE_UTF8;
2109 else if (ascii == '@')
2110 term.mode &= ~MODE_UTF8;
2113 void
2114 tdeftran(char ascii)
2116 static char cs[] = "0B";
2117 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2118 char *p;
2120 if ((p = strchr(cs, ascii)) == NULL) {
2121 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2122 } else {
2123 term.trantbl[term.icharset] = vcs[p - cs];
2127 void
2128 tdectest(char c)
2130 int x, y;
2132 if (c == '8') { /* DEC screen alignment test. */
2133 for (x = 0; x < term.col; ++x) {
2134 for (y = 0; y < term.row; ++y)
2135 tsetchar('E', &term.c.attr, x, y);
2140 void
2141 tstrsequence(uchar c)
2143 strreset();
2145 switch (c) {
2146 case 0x90: /* DCS -- Device Control String */
2147 c = 'P';
2148 term.esc |= ESC_DCS;
2149 break;
2150 case 0x9f: /* APC -- Application Program Command */
2151 c = '_';
2152 break;
2153 case 0x9e: /* PM -- Privacy Message */
2154 c = '^';
2155 break;
2156 case 0x9d: /* OSC -- Operating System Command */
2157 c = ']';
2158 break;
2160 strescseq.type = c;
2161 term.esc |= ESC_STR;
2164 void
2165 tcontrolcode(uchar ascii)
2167 switch (ascii) {
2168 case '\t': /* HT */
2169 tputtab(1);
2170 return;
2171 case '\b': /* BS */
2172 tmoveto(term.c.x-1, term.c.y);
2173 return;
2174 case '\r': /* CR */
2175 tmoveto(0, term.c.y);
2176 return;
2177 case '\f': /* LF */
2178 case '\v': /* VT */
2179 case '\n': /* LF */
2180 /* go to first col if the mode is set */
2181 tnewline(IS_SET(MODE_CRLF));
2182 return;
2183 case '\a': /* BEL */
2184 if (term.esc & ESC_STR_END) {
2185 /* backwards compatibility to xterm */
2186 strhandle();
2187 } else {
2188 if (!(win.state & WIN_FOCUSED))
2189 xseturgency(1);
2190 if (bellvolume)
2191 xbell(bellvolume);
2193 break;
2194 case '\033': /* ESC */
2195 csireset();
2196 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2197 term.esc |= ESC_START;
2198 return;
2199 case '\016': /* SO (LS1 -- Locking shift 1) */
2200 case '\017': /* SI (LS0 -- Locking shift 0) */
2201 term.charset = 1 - (ascii - '\016');
2202 return;
2203 case '\032': /* SUB */
2204 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2205 case '\030': /* CAN */
2206 csireset();
2207 break;
2208 case '\005': /* ENQ (IGNORED) */
2209 case '\000': /* NUL (IGNORED) */
2210 case '\021': /* XON (IGNORED) */
2211 case '\023': /* XOFF (IGNORED) */
2212 case 0177: /* DEL (IGNORED) */
2213 return;
2214 case 0x80: /* TODO: PAD */
2215 case 0x81: /* TODO: HOP */
2216 case 0x82: /* TODO: BPH */
2217 case 0x83: /* TODO: NBH */
2218 case 0x84: /* TODO: IND */
2219 break;
2220 case 0x85: /* NEL -- Next line */
2221 tnewline(1); /* always go to first col */
2222 break;
2223 case 0x86: /* TODO: SSA */
2224 case 0x87: /* TODO: ESA */
2225 break;
2226 case 0x88: /* HTS -- Horizontal tab stop */
2227 term.tabs[term.c.x] = 1;
2228 break;
2229 case 0x89: /* TODO: HTJ */
2230 case 0x8a: /* TODO: VTS */
2231 case 0x8b: /* TODO: PLD */
2232 case 0x8c: /* TODO: PLU */
2233 case 0x8d: /* TODO: RI */
2234 case 0x8e: /* TODO: SS2 */
2235 case 0x8f: /* TODO: SS3 */
2236 case 0x91: /* TODO: PU1 */
2237 case 0x92: /* TODO: PU2 */
2238 case 0x93: /* TODO: STS */
2239 case 0x94: /* TODO: CCH */
2240 case 0x95: /* TODO: MW */
2241 case 0x96: /* TODO: SPA */
2242 case 0x97: /* TODO: EPA */
2243 case 0x98: /* TODO: SOS */
2244 case 0x99: /* TODO: SGCI */
2245 break;
2246 case 0x9a: /* DECID -- Identify Terminal */
2247 ttywrite(vtiden, sizeof(vtiden) - 1);
2248 break;
2249 case 0x9b: /* TODO: CSI */
2250 case 0x9c: /* TODO: ST */
2251 break;
2252 case 0x90: /* DCS -- Device Control String */
2253 case 0x9d: /* OSC -- Operating System Command */
2254 case 0x9e: /* PM -- Privacy Message */
2255 case 0x9f: /* APC -- Application Program Command */
2256 tstrsequence(ascii);
2257 return;
2259 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2260 term.esc &= ~(ESC_STR_END|ESC_STR);
2264 * returns 1 when the sequence is finished and it hasn't to read
2265 * more characters for this sequence, otherwise 0
2268 eschandle(uchar ascii)
2270 switch (ascii) {
2271 case '[':
2272 term.esc |= ESC_CSI;
2273 return 0;
2274 case '#':
2275 term.esc |= ESC_TEST;
2276 return 0;
2277 case '%':
2278 term.esc |= ESC_UTF8;
2279 return 0;
2280 case 'P': /* DCS -- Device Control String */
2281 case '_': /* APC -- Application Program Command */
2282 case '^': /* PM -- Privacy Message */
2283 case ']': /* OSC -- Operating System Command */
2284 case 'k': /* old title set compatibility */
2285 tstrsequence(ascii);
2286 return 0;
2287 case 'n': /* LS2 -- Locking shift 2 */
2288 case 'o': /* LS3 -- Locking shift 3 */
2289 term.charset = 2 + (ascii - 'n');
2290 break;
2291 case '(': /* GZD4 -- set primary charset G0 */
2292 case ')': /* G1D4 -- set secondary charset G1 */
2293 case '*': /* G2D4 -- set tertiary charset G2 */
2294 case '+': /* G3D4 -- set quaternary charset G3 */
2295 term.icharset = ascii - '(';
2296 term.esc |= ESC_ALTCHARSET;
2297 return 0;
2298 case 'D': /* IND -- Linefeed */
2299 if (term.c.y == term.bot) {
2300 tscrollup(term.top, 1);
2301 } else {
2302 tmoveto(term.c.x, term.c.y+1);
2304 break;
2305 case 'E': /* NEL -- Next line */
2306 tnewline(1); /* always go to first col */
2307 break;
2308 case 'H': /* HTS -- Horizontal tab stop */
2309 term.tabs[term.c.x] = 1;
2310 break;
2311 case 'M': /* RI -- Reverse index */
2312 if (term.c.y == term.top) {
2313 tscrolldown(term.top, 1);
2314 } else {
2315 tmoveto(term.c.x, term.c.y-1);
2317 break;
2318 case 'Z': /* DECID -- Identify Terminal */
2319 ttywrite(vtiden, sizeof(vtiden) - 1);
2320 break;
2321 case 'c': /* RIS -- Reset to inital state */
2322 treset();
2323 resettitle();
2324 xloadcols();
2325 break;
2326 case '=': /* DECPAM -- Application keypad */
2327 term.mode |= MODE_APPKEYPAD;
2328 break;
2329 case '>': /* DECPNM -- Normal keypad */
2330 term.mode &= ~MODE_APPKEYPAD;
2331 break;
2332 case '7': /* DECSC -- Save Cursor */
2333 tcursor(CURSOR_SAVE);
2334 break;
2335 case '8': /* DECRC -- Restore Cursor */
2336 tcursor(CURSOR_LOAD);
2337 break;
2338 case '\\': /* ST -- String Terminator */
2339 if (term.esc & ESC_STR_END)
2340 strhandle();
2341 break;
2342 default:
2343 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2344 (uchar) ascii, isprint(ascii)? ascii:'.');
2345 break;
2347 return 1;
2350 void
2351 tputc(Rune u)
2353 char c[UTF_SIZ];
2354 int control;
2355 int width, len;
2356 Glyph *gp;
2358 control = ISCONTROL(u);
2359 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2360 c[0] = u;
2361 width = len = 1;
2362 } else {
2363 len = utf8encode(u, c);
2364 if (!control && (width = wcwidth(u)) == -1) {
2365 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2366 width = 1;
2370 if (IS_SET(MODE_PRINT))
2371 tprinter(c, len);
2374 * STR sequence must be checked before anything else
2375 * because it uses all following characters until it
2376 * receives a ESC, a SUB, a ST or any other C1 control
2377 * character.
2379 if (term.esc & ESC_STR) {
2380 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2381 ISCONTROLC1(u)) {
2382 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2383 if (IS_SET(MODE_SIXEL)) {
2384 /* TODO: render sixel */;
2385 term.mode &= ~MODE_SIXEL;
2386 return;
2388 term.esc |= ESC_STR_END;
2389 goto check_control_code;
2393 if (IS_SET(MODE_SIXEL)) {
2394 /* TODO: implement sixel mode */
2395 return;
2397 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2398 term.mode |= MODE_SIXEL;
2400 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2402 * Here is a bug in terminals. If the user never sends
2403 * some code to stop the str or esc command, then st
2404 * will stop responding. But this is better than
2405 * silently failing with unknown characters. At least
2406 * then users will report back.
2408 * In the case users ever get fixed, here is the code:
2411 * term.esc = 0;
2412 * strhandle();
2414 return;
2417 memmove(&strescseq.buf[strescseq.len], c, len);
2418 strescseq.len += len;
2419 return;
2422 check_control_code:
2424 * Actions of control codes must be performed as soon they arrive
2425 * because they can be embedded inside a control sequence, and
2426 * they must not cause conflicts with sequences.
2428 if (control) {
2429 tcontrolcode(u);
2431 * control codes are not shown ever
2433 return;
2434 } else if (term.esc & ESC_START) {
2435 if (term.esc & ESC_CSI) {
2436 csiescseq.buf[csiescseq.len++] = u;
2437 if (BETWEEN(u, 0x40, 0x7E)
2438 || csiescseq.len >= \
2439 sizeof(csiescseq.buf)-1) {
2440 term.esc = 0;
2441 csiparse();
2442 csihandle();
2444 return;
2445 } else if (term.esc & ESC_UTF8) {
2446 tdefutf8(u);
2447 } else if (term.esc & ESC_ALTCHARSET) {
2448 tdeftran(u);
2449 } else if (term.esc & ESC_TEST) {
2450 tdectest(u);
2451 } else {
2452 if (!eschandle(u))
2453 return;
2454 /* sequence already finished */
2456 term.esc = 0;
2458 * All characters which form part of a sequence are not
2459 * printed
2461 return;
2463 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2464 selclear();
2466 gp = &term.line[term.c.y][term.c.x];
2467 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2468 gp->mode |= ATTR_WRAP;
2469 tnewline(1);
2470 gp = &term.line[term.c.y][term.c.x];
2473 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2474 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2476 if (term.c.x+width > term.col) {
2477 tnewline(1);
2478 gp = &term.line[term.c.y][term.c.x];
2481 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2483 if (width == 2) {
2484 gp->mode |= ATTR_WIDE;
2485 if (term.c.x+1 < term.col) {
2486 gp[1].u = '\0';
2487 gp[1].mode = ATTR_WDUMMY;
2490 if (term.c.x+width < term.col) {
2491 tmoveto(term.c.x+width, term.c.y);
2492 } else {
2493 term.c.state |= CURSOR_WRAPNEXT;
2497 void
2498 tresize(int col, int row)
2500 int i;
2501 int minrow = MIN(row, term.row);
2502 int mincol = MIN(col, term.col);
2503 int *bp;
2504 TCursor c;
2506 if (col < 1 || row < 1) {
2507 fprintf(stderr,
2508 "tresize: error resizing to %dx%d\n", col, row);
2509 return;
2513 * slide screen to keep cursor where we expect it -
2514 * tscrollup would work here, but we can optimize to
2515 * memmove because we're freeing the earlier lines
2517 for (i = 0; i <= term.c.y - row; i++) {
2518 free(term.line[i]);
2519 free(term.alt[i]);
2521 /* ensure that both src and dst are not NULL */
2522 if (i > 0) {
2523 memmove(term.line, term.line + i, row * sizeof(Line));
2524 memmove(term.alt, term.alt + i, row * sizeof(Line));
2526 for (i += row; i < term.row; i++) {
2527 free(term.line[i]);
2528 free(term.alt[i]);
2531 /* resize to new width */
2532 term.specbuf = xrealloc(term.specbuf, col * sizeof(GlyphFontSpec));
2534 /* resize to new height */
2535 term.line = xrealloc(term.line, row * sizeof(Line));
2536 term.alt = xrealloc(term.alt, row * sizeof(Line));
2537 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2538 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2540 /* resize each row to new width, zero-pad if needed */
2541 for (i = 0; i < minrow; i++) {
2542 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2543 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2546 /* allocate any new rows */
2547 for (/* i = minrow */; i < row; i++) {
2548 term.line[i] = xmalloc(col * sizeof(Glyph));
2549 term.alt[i] = xmalloc(col * sizeof(Glyph));
2551 if (col > term.col) {
2552 bp = term.tabs + term.col;
2554 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2555 while (--bp > term.tabs && !*bp)
2556 /* nothing */ ;
2557 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2558 *bp = 1;
2560 /* update terminal size */
2561 term.col = col;
2562 term.row = row;
2563 /* reset scrolling region */
2564 tsetscroll(0, row-1);
2565 /* make use of the LIMIT in tmoveto */
2566 tmoveto(term.c.x, term.c.y);
2567 /* Clearing both screens (it makes dirty all lines) */
2568 c = term.c;
2569 for (i = 0; i < 2; i++) {
2570 if (mincol < col && 0 < minrow) {
2571 tclearregion(mincol, 0, col - 1, minrow - 1);
2573 if (0 < col && minrow < row) {
2574 tclearregion(0, minrow, col - 1, row - 1);
2576 tswapscreen();
2577 tcursor(CURSOR_LOAD);
2579 term.c = c;
2582 void
2583 zoom(const Arg *arg)
2585 Arg larg;
2587 larg.f = usedfontsize + arg->f;
2588 zoomabs(&larg);
2591 void
2592 zoomabs(const Arg *arg)
2594 xunloadfonts();
2595 xloadfonts(usedfont, arg->f);
2596 cresize(0, 0);
2597 ttyresize();
2598 redraw();
2599 xhints();
2602 void
2603 zoomreset(const Arg *arg)
2605 Arg larg;
2607 if (defaultfontsize > 0) {
2608 larg.f = defaultfontsize;
2609 zoomabs(&larg);
2613 void
2614 resettitle(void)
2616 xsettitle(opt_title ? opt_title : "st");
2619 void
2620 redraw(void)
2622 tfulldirt();
2623 draw();
2627 match(uint mask, uint state)
2629 return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
2632 void
2633 numlock(const Arg *dummy)
2635 term.numlock ^= 1;
2638 char*
2639 kmap(KeySym k, uint state)
2641 Key *kp;
2642 int i;
2644 /* Check for mapped keys out of X11 function keys. */
2645 for (i = 0; i < LEN(mappedkeys); i++) {
2646 if (mappedkeys[i] == k)
2647 break;
2649 if (i == LEN(mappedkeys)) {
2650 if ((k & 0xFFFF) < 0xFD00)
2651 return NULL;
2654 for (kp = key; kp < key + LEN(key); kp++) {
2655 if (kp->k != k)
2656 continue;
2658 if (!match(kp->mask, state))
2659 continue;
2661 if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
2662 continue;
2663 if (term.numlock && kp->appkey == 2)
2664 continue;
2666 if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
2667 continue;
2669 if (IS_SET(MODE_CRLF) ? kp->crlf < 0 : kp->crlf > 0)
2670 continue;
2672 return kp->s;
2675 return NULL;
2678 void
2679 cresize(int width, int height)
2681 int col, row;
2683 if (width != 0)
2684 win.w = width;
2685 if (height != 0)
2686 win.h = height;
2688 col = (win.w - 2 * borderpx) / win.cw;
2689 row = (win.h - 2 * borderpx) / win.ch;
2691 tresize(col, row);
2692 xresize(col, row);
2695 void
2696 usage(void)
2698 die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
2699 " [-n name] [-o file]\n"
2700 " [-T title] [-t title] [-w windowid]"
2701 " [[-e] command [args ...]]\n"
2702 " %s [-aiv] [-c class] [-f font] [-g geometry]"
2703 " [-n name] [-o file]\n"
2704 " [-T title] [-t title] [-w windowid] -l line"
2705 " [stty_args ...]\n", argv0, argv0);