my config
[azarus-st.git] / st.c
blob76bb3eafbe0ba110af5c85465f3a40906cf5f886
1 /* See LICENSE for license details. */
2 #include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <pwd.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <signal.h>
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <termios.h>
17 #include <unistd.h>
18 #include <wchar.h>
20 #include "st.h"
21 #include "win.h"
23 #if defined(__linux)
24 #include <pty.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
26 #include <util.h>
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
28 #include <libutil.h>
29 #endif
31 /* Arbitrary sizes */
32 #define UTF_INVALID 0xFFFD
33 #define UTF_SIZ 4
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
39 /* macros */
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
45 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
47 /* constants */
48 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
50 enum term_mode {
51 MODE_WRAP = 1 << 0,
52 MODE_INSERT = 1 << 1,
53 MODE_ALTSCREEN = 1 << 2,
54 MODE_CRLF = 1 << 3,
55 MODE_ECHO = 1 << 4,
56 MODE_PRINT = 1 << 5,
57 MODE_UTF8 = 1 << 6,
58 MODE_SIXEL = 1 << 7,
61 enum cursor_movement {
62 CURSOR_SAVE,
63 CURSOR_LOAD
66 enum cursor_state {
67 CURSOR_DEFAULT = 0,
68 CURSOR_WRAPNEXT = 1,
69 CURSOR_ORIGIN = 2
72 enum charset {
73 CS_GRAPHIC0,
74 CS_GRAPHIC1,
75 CS_UK,
76 CS_USA,
77 CS_MULTI,
78 CS_GER,
79 CS_FIN
82 enum escape_state {
83 ESC_START = 1,
84 ESC_CSI = 2,
85 ESC_STR = 4, /* OSC, PM, APC */
86 ESC_ALTCHARSET = 8,
87 ESC_STR_END = 16, /* a final string was encountered */
88 ESC_TEST = 32, /* Enter in test mode */
89 ESC_UTF8 = 64,
90 ESC_DCS =128,
93 typedef struct {
94 Glyph attr; /* current char attributes */
95 int x;
96 int y;
97 char state;
98 } TCursor;
100 typedef struct {
101 int mode;
102 int type;
103 int snap;
105 * Selection variables:
106 * nb – normalized coordinates of the beginning of the selection
107 * ne – normalized coordinates of the end of the selection
108 * ob – original coordinates of the beginning of the selection
109 * oe – original coordinates of the end of the selection
111 struct {
112 int x, y;
113 } nb, ne, ob, oe;
115 int alt;
116 } Selection;
118 /* Internal representation of the screen */
119 typedef struct {
120 int row; /* nb row */
121 int col; /* nb col */
122 Line *line; /* screen */
123 Line *alt; /* alternate screen */
124 int *dirty; /* dirtyness of lines */
125 TCursor c; /* cursor */
126 int ocx; /* old cursor col */
127 int ocy; /* old cursor row */
128 int top; /* top scroll limit */
129 int bot; /* bottom scroll limit */
130 int mode; /* terminal mode flags */
131 int esc; /* escape state flags */
132 char trantbl[4]; /* charset table translation */
133 int charset; /* current charset */
134 int icharset; /* selected charset for sequence */
135 int *tabs;
136 } Term;
138 /* CSI Escape sequence structs */
139 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
140 typedef struct {
141 char buf[ESC_BUF_SIZ]; /* raw string */
142 int len; /* raw string length */
143 char priv;
144 int arg[ESC_ARG_SIZ];
145 int narg; /* nb of args */
146 char mode[2];
147 } CSIEscape;
149 /* STR Escape sequence structs */
150 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
151 typedef struct {
152 char type; /* ESC type ... */
153 char buf[STR_BUF_SIZ]; /* raw string */
154 int len; /* raw string length */
155 char *args[STR_ARG_SIZ];
156 int narg; /* nb of args */
157 } STREscape;
159 static void execsh(char *, char **);
160 static void stty(char **);
161 static void sigchld(int);
162 static void ttywriteraw(const char *, size_t);
164 static void csidump(void);
165 static void csihandle(void);
166 static void csiparse(void);
167 static void csireset(void);
168 static int eschandle(uchar);
169 static void strdump(void);
170 static void strhandle(void);
171 static void strparse(void);
172 static void strreset(void);
174 static void tprinter(char *, size_t);
175 static void tdumpsel(void);
176 static void tdumpline(int);
177 static void tdump(void);
178 static void tclearregion(int, int, int, int);
179 static void tcursor(int);
180 static void tdeletechar(int);
181 static void tdeleteline(int);
182 static void tinsertblank(int);
183 static void tinsertblankline(int);
184 static int tlinelen(int);
185 static void tmoveto(int, int);
186 static void tmoveato(int, int);
187 static void tnewline(int);
188 static void tputtab(int);
189 static void tputc(Rune);
190 static void treset(void);
191 static void tscrollup(int, int);
192 static void tscrolldown(int, int);
193 static void tsetattr(int *, int);
194 static void tsetchar(Rune, Glyph *, int, int);
195 static void tsetdirt(int, int);
196 static void tsetscroll(int, int);
197 static void tswapscreen(void);
198 static void tsetmode(int, int, int *, int);
199 static int twrite(const char *, int, int);
200 static void tfulldirt(void);
201 static void tcontrolcode(uchar );
202 static void tdectest(char );
203 static void tdefutf8(char);
204 static int32_t tdefcolor(int *, int *, int);
205 static void tdeftran(char);
206 static void tstrsequence(uchar);
208 static void drawregion(int, int, int, int);
210 static void selnormalize(void);
211 static void selscroll(int, int);
212 static void selsnap(int *, int *, int);
214 static size_t utf8decode(const char *, Rune *, size_t);
215 static Rune utf8decodebyte(char, size_t *);
216 static char utf8encodebyte(Rune, size_t);
217 static char *utf8strchr(char *, Rune);
218 static size_t utf8validate(Rune *, size_t);
220 static char *base64dec(const char *);
221 static char base64dec_getc(const char **);
223 static ssize_t xwrite(int, const char *, size_t);
225 /* Globals */
226 static Term term;
227 static Selection sel;
228 static CSIEscape csiescseq;
229 static STREscape strescseq;
230 static int iofd = 1;
231 static int cmdfd;
232 static pid_t pid;
234 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
235 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
236 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
237 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
239 ssize_t
240 xwrite(int fd, const char *s, size_t len)
242 size_t aux = len;
243 ssize_t r;
245 while (len > 0) {
246 r = write(fd, s, len);
247 if (r < 0)
248 return r;
249 len -= r;
250 s += r;
253 return aux;
256 void *
257 xmalloc(size_t len)
259 void *p;
261 if (!(p = malloc(len)))
262 die("malloc: %s\n", strerror(errno));
264 return p;
267 void *
268 xrealloc(void *p, size_t len)
270 if ((p = realloc(p, len)) == NULL)
271 die("realloc: %s\n", strerror(errno));
273 return p;
276 char *
277 xstrdup(char *s)
279 if ((s = strdup(s)) == NULL)
280 die("strdup: %s\n", strerror(errno));
282 return s;
285 size_t
286 utf8decode(const char *c, Rune *u, size_t clen)
288 size_t i, j, len, type;
289 Rune udecoded;
291 *u = UTF_INVALID;
292 if (!clen)
293 return 0;
294 udecoded = utf8decodebyte(c[0], &len);
295 if (!BETWEEN(len, 1, UTF_SIZ))
296 return 1;
297 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
298 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
299 if (type != 0)
300 return j;
302 if (j < len)
303 return 0;
304 *u = udecoded;
305 utf8validate(u, len);
307 return len;
310 Rune
311 utf8decodebyte(char c, size_t *i)
313 for (*i = 0; *i < LEN(utfmask); ++(*i))
314 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
315 return (uchar)c & ~utfmask[*i];
317 return 0;
320 size_t
321 utf8encode(Rune u, char *c)
323 size_t len, i;
325 len = utf8validate(&u, 0);
326 if (len > UTF_SIZ)
327 return 0;
329 for (i = len - 1; i != 0; --i) {
330 c[i] = utf8encodebyte(u, 0);
331 u >>= 6;
333 c[0] = utf8encodebyte(u, len);
335 return len;
338 char
339 utf8encodebyte(Rune u, size_t i)
341 return utfbyte[i] | (u & ~utfmask[i]);
344 char *
345 utf8strchr(char *s, Rune u)
347 Rune r;
348 size_t i, j, len;
350 len = strlen(s);
351 for (i = 0, j = 0; i < len; i += j) {
352 if (!(j = utf8decode(&s[i], &r, len - i)))
353 break;
354 if (r == u)
355 return &(s[i]);
358 return NULL;
361 size_t
362 utf8validate(Rune *u, size_t i)
364 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
365 *u = UTF_INVALID;
366 for (i = 1; *u > utfmax[i]; ++i)
369 return i;
372 static const char base64_digits[] = {
373 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
374 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
375 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
376 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
377 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
378 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
379 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
387 char
388 base64dec_getc(const char **src)
390 while (**src && !isprint(**src)) (*src)++;
391 return *((*src)++);
394 char *
395 base64dec(const char *src)
397 size_t in_len = strlen(src);
398 char *result, *dst;
400 if (in_len % 4)
401 in_len += 4 - (in_len % 4);
402 result = dst = xmalloc(in_len / 4 * 3 + 1);
403 while (*src) {
404 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
405 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
406 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
407 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
409 *dst++ = (a << 2) | ((b & 0x30) >> 4);
410 if (c == -1)
411 break;
412 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
413 if (d == -1)
414 break;
415 *dst++ = ((c & 0x03) << 6) | d;
417 *dst = '\0';
418 return result;
421 void
422 selinit(void)
424 sel.mode = SEL_IDLE;
425 sel.snap = 0;
426 sel.ob.x = -1;
430 tlinelen(int y)
432 int i = term.col;
434 if (term.line[y][i - 1].mode & ATTR_WRAP)
435 return i;
437 while (i > 0 && term.line[y][i - 1].u == ' ')
438 --i;
440 return i;
443 void
444 selstart(int col, int row, int snap)
446 selclear();
447 sel.mode = SEL_EMPTY;
448 sel.type = SEL_REGULAR;
449 sel.alt = IS_SET(MODE_ALTSCREEN);
450 sel.snap = snap;
451 sel.oe.x = sel.ob.x = col;
452 sel.oe.y = sel.ob.y = row;
453 selnormalize();
455 if (sel.snap != 0)
456 sel.mode = SEL_READY;
457 tsetdirt(sel.nb.y, sel.ne.y);
460 void
461 selextend(int col, int row, int type, int done)
463 int oldey, oldex, oldsby, oldsey, oldtype;
465 if (sel.mode == SEL_IDLE)
466 return;
467 if (done && sel.mode == SEL_EMPTY) {
468 selclear();
469 return;
472 oldey = sel.oe.y;
473 oldex = sel.oe.x;
474 oldsby = sel.nb.y;
475 oldsey = sel.ne.y;
476 oldtype = sel.type;
478 sel.oe.x = col;
479 sel.oe.y = row;
480 selnormalize();
481 sel.type = type;
483 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type)
484 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
486 sel.mode = done ? SEL_IDLE : SEL_READY;
489 void
490 selnormalize(void)
492 int i;
494 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
495 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
496 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
497 } else {
498 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
499 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
501 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
502 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
504 selsnap(&sel.nb.x, &sel.nb.y, -1);
505 selsnap(&sel.ne.x, &sel.ne.y, +1);
507 /* expand selection over line breaks */
508 if (sel.type == SEL_RECTANGULAR)
509 return;
510 i = tlinelen(sel.nb.y);
511 if (i < sel.nb.x)
512 sel.nb.x = i;
513 if (tlinelen(sel.ne.y) <= sel.ne.x)
514 sel.ne.x = term.col - 1;
518 selected(int x, int y)
520 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
521 sel.alt != IS_SET(MODE_ALTSCREEN))
522 return 0;
524 if (sel.type == SEL_RECTANGULAR)
525 return BETWEEN(y, sel.nb.y, sel.ne.y)
526 && BETWEEN(x, sel.nb.x, sel.ne.x);
528 return BETWEEN(y, sel.nb.y, sel.ne.y)
529 && (y != sel.nb.y || x >= sel.nb.x)
530 && (y != sel.ne.y || x <= sel.ne.x);
533 void
534 selsnap(int *x, int *y, int direction)
536 int newx, newy, xt, yt;
537 int delim, prevdelim;
538 Glyph *gp, *prevgp;
540 switch (sel.snap) {
541 case SNAP_WORD:
543 * Snap around if the word wraps around at the end or
544 * beginning of a line.
546 prevgp = &term.line[*y][*x];
547 prevdelim = ISDELIM(prevgp->u);
548 for (;;) {
549 newx = *x + direction;
550 newy = *y;
551 if (!BETWEEN(newx, 0, term.col - 1)) {
552 newy += direction;
553 newx = (newx + term.col) % term.col;
554 if (!BETWEEN(newy, 0, term.row - 1))
555 break;
557 if (direction > 0)
558 yt = *y, xt = *x;
559 else
560 yt = newy, xt = newx;
561 if (!(term.line[yt][xt].mode & ATTR_WRAP))
562 break;
565 if (newx >= tlinelen(newy))
566 break;
568 gp = &term.line[newy][newx];
569 delim = ISDELIM(gp->u);
570 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
571 || (delim && gp->u != prevgp->u)))
572 break;
574 *x = newx;
575 *y = newy;
576 prevgp = gp;
577 prevdelim = delim;
579 break;
580 case SNAP_LINE:
582 * Snap around if the the previous line or the current one
583 * has set ATTR_WRAP at its end. Then the whole next or
584 * previous line will be selected.
586 *x = (direction < 0) ? 0 : term.col - 1;
587 if (direction < 0) {
588 for (; *y > 0; *y += direction) {
589 if (!(term.line[*y-1][term.col-1].mode
590 & ATTR_WRAP)) {
591 break;
594 } else if (direction > 0) {
595 for (; *y < term.row-1; *y += direction) {
596 if (!(term.line[*y][term.col-1].mode
597 & ATTR_WRAP)) {
598 break;
602 break;
606 char *
607 getsel(void)
609 char *str, *ptr;
610 int y, bufsize, lastx, linelen;
611 Glyph *gp, *last;
613 if (sel.ob.x == -1)
614 return NULL;
616 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
617 ptr = str = xmalloc(bufsize);
619 /* append every set & selected glyph to the selection */
620 for (y = sel.nb.y; y <= sel.ne.y; y++) {
621 if ((linelen = tlinelen(y)) == 0) {
622 *ptr++ = '\n';
623 continue;
626 if (sel.type == SEL_RECTANGULAR) {
627 gp = &term.line[y][sel.nb.x];
628 lastx = sel.ne.x;
629 } else {
630 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
631 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
633 last = &term.line[y][MIN(lastx, linelen-1)];
634 while (last >= gp && last->u == ' ')
635 --last;
637 for ( ; gp <= last; ++gp) {
638 if (gp->mode & ATTR_WDUMMY)
639 continue;
641 ptr += utf8encode(gp->u, ptr);
645 * Copy and pasting of line endings is inconsistent
646 * in the inconsistent terminal and GUI world.
647 * The best solution seems like to produce '\n' when
648 * something is copied from st and convert '\n' to
649 * '\r', when something to be pasted is received by
650 * st.
651 * FIXME: Fix the computer world.
653 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
654 *ptr++ = '\n';
656 *ptr = 0;
657 return str;
660 void
661 selclear(void)
663 if (sel.ob.x == -1)
664 return;
665 sel.mode = SEL_IDLE;
666 sel.ob.x = -1;
667 tsetdirt(sel.nb.y, sel.ne.y);
670 void
671 die(const char *errstr, ...)
673 va_list ap;
675 va_start(ap, errstr);
676 vfprintf(stderr, errstr, ap);
677 va_end(ap);
678 exit(1);
681 void
682 execsh(char *cmd, char **args)
684 char *sh, *prog;
685 const struct passwd *pw;
687 errno = 0;
688 if ((pw = getpwuid(getuid())) == NULL) {
689 if (errno)
690 die("getpwuid: %s\n", strerror(errno));
691 else
692 die("who are you?\n");
695 if ((sh = getenv("SHELL")) == NULL)
696 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
698 if (args)
699 prog = args[0];
700 else if (utmp)
701 prog = utmp;
702 else
703 prog = sh;
704 DEFAULT(args, ((char *[]) {prog, NULL}));
706 unsetenv("COLUMNS");
707 unsetenv("LINES");
708 unsetenv("TERMCAP");
709 setenv("LOGNAME", pw->pw_name, 1);
710 setenv("USER", pw->pw_name, 1);
711 setenv("SHELL", sh, 1);
712 setenv("HOME", pw->pw_dir, 1);
713 setenv("TERM", termname, 1);
715 signal(SIGCHLD, SIG_DFL);
716 signal(SIGHUP, SIG_DFL);
717 signal(SIGINT, SIG_DFL);
718 signal(SIGQUIT, SIG_DFL);
719 signal(SIGTERM, SIG_DFL);
720 signal(SIGALRM, SIG_DFL);
722 execvp(prog, args);
723 _exit(1);
726 void
727 sigchld(int a)
729 int stat;
730 pid_t p;
732 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
733 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
735 if (pid != p)
736 return;
738 if (!WIFEXITED(stat) || WEXITSTATUS(stat))
739 die("child finished with error '%d'\n", stat);
740 exit(0);
743 void
744 stty(char **args)
746 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
747 size_t n, siz;
749 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
750 die("incorrect stty parameters\n");
751 memcpy(cmd, stty_args, n);
752 q = cmd + n;
753 siz = sizeof(cmd) - n;
754 for (p = args; p && (s = *p); ++p) {
755 if ((n = strlen(s)) > siz-1)
756 die("stty parameter length too long\n");
757 *q++ = ' ';
758 memcpy(q, s, n);
759 q += n;
760 siz -= n + 1;
762 *q = '\0';
763 if (system(cmd) != 0)
764 perror("Couldn't call stty");
768 ttynew(char *line, char *cmd, char *out, char **args)
770 int m, s;
772 if (out) {
773 term.mode |= MODE_PRINT;
774 iofd = (!strcmp(out, "-")) ?
775 1 : open(out, O_WRONLY | O_CREAT, 0666);
776 if (iofd < 0) {
777 fprintf(stderr, "Error opening %s:%s\n",
778 out, strerror(errno));
782 if (line) {
783 if ((cmdfd = open(line, O_RDWR)) < 0)
784 die("open line '%s' failed: %s\n",
785 line, strerror(errno));
786 dup2(cmdfd, 0);
787 stty(args);
788 return cmdfd;
791 /* seems to work fine on linux, openbsd and freebsd */
792 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
793 die("openpty failed: %s\n", strerror(errno));
795 switch (pid = fork()) {
796 case -1:
797 die("fork failed: %s\n", strerror(errno));
798 break;
799 case 0:
800 close(iofd);
801 setsid(); /* create a new process group */
802 dup2(s, 0);
803 dup2(s, 1);
804 dup2(s, 2);
805 if (ioctl(s, TIOCSCTTY, NULL) < 0)
806 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
807 close(s);
808 close(m);
809 #ifdef __OpenBSD__
810 if (pledge("stdio getpw proc exec", NULL) == -1)
811 die("pledge\n");
812 #endif
813 execsh(cmd, args);
814 break;
815 default:
816 #ifdef __OpenBSD__
817 if (pledge("stdio rpath tty proc", NULL) == -1)
818 die("pledge\n");
819 #endif
820 close(s);
821 cmdfd = m;
822 signal(SIGCHLD, sigchld);
823 break;
825 return cmdfd;
828 size_t
829 ttyread(void)
831 static char buf[BUFSIZ];
832 static int buflen = 0;
833 int written;
834 int ret;
836 /* append read bytes to unprocessed bytes */
837 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
838 die("couldn't read from shell: %s\n", strerror(errno));
839 buflen += ret;
841 written = twrite(buf, buflen, 0);
842 buflen -= written;
843 /* keep any uncomplete utf8 char for the next call */
844 if (buflen > 0)
845 memmove(buf, buf + written, buflen);
847 return ret;
850 void
851 ttywrite(const char *s, size_t n, int may_echo)
853 const char *next;
855 if (may_echo && IS_SET(MODE_ECHO))
856 twrite(s, n, 1);
858 if (!IS_SET(MODE_CRLF)) {
859 ttywriteraw(s, n);
860 return;
863 /* This is similar to how the kernel handles ONLCR for ttys */
864 while (n > 0) {
865 if (*s == '\r') {
866 next = s + 1;
867 ttywriteraw("\r\n", 2);
868 } else {
869 next = memchr(s, '\r', n);
870 DEFAULT(next, s + n);
871 ttywriteraw(s, next - s);
873 n -= next - s;
874 s = next;
878 void
879 ttywriteraw(const char *s, size_t n)
881 fd_set wfd, rfd;
882 ssize_t r;
883 size_t lim = 256;
886 * Remember that we are using a pty, which might be a modem line.
887 * Writing too much will clog the line. That's why we are doing this
888 * dance.
889 * FIXME: Migrate the world to Plan 9.
891 while (n > 0) {
892 FD_ZERO(&wfd);
893 FD_ZERO(&rfd);
894 FD_SET(cmdfd, &wfd);
895 FD_SET(cmdfd, &rfd);
897 /* Check if we can write. */
898 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
899 if (errno == EINTR)
900 continue;
901 die("select failed: %s\n", strerror(errno));
903 if (FD_ISSET(cmdfd, &wfd)) {
905 * Only write the bytes written by ttywrite() or the
906 * default of 256. This seems to be a reasonable value
907 * for a serial line. Bigger values might clog the I/O.
909 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
910 goto write_error;
911 if (r < n) {
913 * We weren't able to write out everything.
914 * This means the buffer is getting full
915 * again. Empty it.
917 if (n < lim)
918 lim = ttyread();
919 n -= r;
920 s += r;
921 } else {
922 /* All bytes have been written. */
923 break;
926 if (FD_ISSET(cmdfd, &rfd))
927 lim = ttyread();
929 return;
931 write_error:
932 die("write error on tty: %s\n", strerror(errno));
935 void
936 ttyresize(int tw, int th)
938 struct winsize w;
940 w.ws_row = term.row;
941 w.ws_col = term.col;
942 w.ws_xpixel = tw;
943 w.ws_ypixel = th;
944 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
945 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
948 void
949 ttyhangup()
951 /* Send SIGHUP to shell */
952 kill(pid, SIGHUP);
956 tattrset(int attr)
958 int i, j;
960 for (i = 0; i < term.row-1; i++) {
961 for (j = 0; j < term.col-1; j++) {
962 if (term.line[i][j].mode & attr)
963 return 1;
967 return 0;
970 void
971 tsetdirt(int top, int bot)
973 int i;
975 LIMIT(top, 0, term.row-1);
976 LIMIT(bot, 0, term.row-1);
978 for (i = top; i <= bot; i++)
979 term.dirty[i] = 1;
982 void
983 tsetdirtattr(int attr)
985 int i, j;
987 for (i = 0; i < term.row-1; i++) {
988 for (j = 0; j < term.col-1; j++) {
989 if (term.line[i][j].mode & attr) {
990 tsetdirt(i, i);
991 break;
997 void
998 tfulldirt(void)
1000 tsetdirt(0, term.row-1);
1003 void
1004 tcursor(int mode)
1006 static TCursor c[2];
1007 int alt = IS_SET(MODE_ALTSCREEN);
1009 if (mode == CURSOR_SAVE) {
1010 c[alt] = term.c;
1011 } else if (mode == CURSOR_LOAD) {
1012 term.c = c[alt];
1013 tmoveto(c[alt].x, c[alt].y);
1017 void
1018 treset(void)
1020 uint i;
1022 term.c = (TCursor){{
1023 .mode = ATTR_NULL,
1024 .fg = defaultfg,
1025 .bg = defaultbg
1026 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1028 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1029 for (i = tabspaces; i < term.col; i += tabspaces)
1030 term.tabs[i] = 1;
1031 term.top = 0;
1032 term.bot = term.row - 1;
1033 term.mode = MODE_WRAP|MODE_UTF8;
1034 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1035 term.charset = 0;
1037 for (i = 0; i < 2; i++) {
1038 tmoveto(0, 0);
1039 tcursor(CURSOR_SAVE);
1040 tclearregion(0, 0, term.col-1, term.row-1);
1041 tswapscreen();
1045 void
1046 tnew(int col, int row)
1048 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1049 tresize(col, row);
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 alt, *lim;
1487 for (lim = args + narg; args < lim; ++args) {
1488 if (priv) {
1489 switch (*args) {
1490 case 1: /* DECCKM -- Cursor key */
1491 xsetmode(set, MODE_APPCURSOR);
1492 break;
1493 case 5: /* DECSCNM -- Reverse video */
1494 xsetmode(set, MODE_REVERSE);
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 xsetmode(!set, MODE_HIDE);
1515 break;
1516 case 9: /* X10 mouse compatibility mode */
1517 xsetpointermotion(0);
1518 xsetmode(0, MODE_MOUSE);
1519 xsetmode(set, MODE_MOUSEX10);
1520 break;
1521 case 1000: /* 1000: report button press */
1522 xsetpointermotion(0);
1523 xsetmode(0, MODE_MOUSE);
1524 xsetmode(set, MODE_MOUSEBTN);
1525 break;
1526 case 1002: /* 1002: report motion on button press */
1527 xsetpointermotion(0);
1528 xsetmode(0, MODE_MOUSE);
1529 xsetmode(set, MODE_MOUSEMOTION);
1530 break;
1531 case 1003: /* 1003: enable all mouse motions */
1532 xsetpointermotion(set);
1533 xsetmode(0, MODE_MOUSE);
1534 xsetmode(set, MODE_MOUSEMANY);
1535 break;
1536 case 1004: /* 1004: send focus events to tty */
1537 xsetmode(set, MODE_FOCUS);
1538 break;
1539 case 1006: /* 1006: extended reporting mode */
1540 xsetmode(set, MODE_MOUSESGR);
1541 break;
1542 case 1034:
1543 xsetmode(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);
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 xsetmode(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;
1585 } else {
1586 switch (*args) {
1587 case 0: /* Error (IGNORED) */
1588 break;
1589 case 2:
1590 xsetmode(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;
1611 void
1612 csihandle(void)
1614 char buf[40];
1615 int len;
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;
1655 break;
1656 case 'c': /* DA -- Device Attributes */
1657 if (csiescseq.arg[0] == 0)
1658 ttywrite(vtiden, strlen(vtiden), 0);
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;
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 switch (csiescseq.arg[0]) {
1706 case 0: /* below */
1707 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1708 if (term.c.y < term.row-1) {
1709 tclearregion(0, term.c.y+1, term.col-1,
1710 term.row-1);
1712 break;
1713 case 1: /* above */
1714 if (term.c.y > 1)
1715 tclearregion(0, 0, term.col-1, term.c.y-1);
1716 tclearregion(0, term.c.y, term.c.x, term.c.y);
1717 break;
1718 case 2: /* all */
1719 tclearregion(0, 0, term.col-1, term.row-1);
1720 break;
1721 default:
1722 goto unknown;
1724 break;
1725 case 'K': /* EL -- Clear line */
1726 switch (csiescseq.arg[0]) {
1727 case 0: /* right */
1728 tclearregion(term.c.x, term.c.y, term.col-1,
1729 term.c.y);
1730 break;
1731 case 1: /* left */
1732 tclearregion(0, term.c.y, term.c.x, term.c.y);
1733 break;
1734 case 2: /* all */
1735 tclearregion(0, term.c.y, term.col-1, term.c.y);
1736 break;
1738 break;
1739 case 'S': /* SU -- Scroll <n> line up */
1740 DEFAULT(csiescseq.arg[0], 1);
1741 tscrollup(term.top, csiescseq.arg[0]);
1742 break;
1743 case 'T': /* SD -- Scroll <n> line down */
1744 DEFAULT(csiescseq.arg[0], 1);
1745 tscrolldown(term.top, csiescseq.arg[0]);
1746 break;
1747 case 'L': /* IL -- Insert <n> blank lines */
1748 DEFAULT(csiescseq.arg[0], 1);
1749 tinsertblankline(csiescseq.arg[0]);
1750 break;
1751 case 'l': /* RM -- Reset Mode */
1752 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1753 break;
1754 case 'M': /* DL -- Delete <n> lines */
1755 DEFAULT(csiescseq.arg[0], 1);
1756 tdeleteline(csiescseq.arg[0]);
1757 break;
1758 case 'X': /* ECH -- Erase <n> char */
1759 DEFAULT(csiescseq.arg[0], 1);
1760 tclearregion(term.c.x, term.c.y,
1761 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1762 break;
1763 case 'P': /* DCH -- Delete <n> char */
1764 DEFAULT(csiescseq.arg[0], 1);
1765 tdeletechar(csiescseq.arg[0]);
1766 break;
1767 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1768 DEFAULT(csiescseq.arg[0], 1);
1769 tputtab(-csiescseq.arg[0]);
1770 break;
1771 case 'd': /* VPA -- Move to <row> */
1772 DEFAULT(csiescseq.arg[0], 1);
1773 tmoveato(term.c.x, csiescseq.arg[0]-1);
1774 break;
1775 case 'h': /* SM -- Set terminal mode */
1776 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1777 break;
1778 case 'm': /* SGR -- Terminal attribute (color) */
1779 tsetattr(csiescseq.arg, csiescseq.narg);
1780 break;
1781 case 'n': /* DSR – Device Status Report (cursor position) */
1782 if (csiescseq.arg[0] == 6) {
1783 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1784 term.c.y+1, term.c.x+1);
1785 ttywrite(buf, len, 0);
1787 break;
1788 case 'r': /* DECSTBM -- Set Scrolling Region */
1789 if (csiescseq.priv) {
1790 goto unknown;
1791 } else {
1792 DEFAULT(csiescseq.arg[0], 1);
1793 DEFAULT(csiescseq.arg[1], term.row);
1794 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1795 tmoveato(0, 0);
1797 break;
1798 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1799 tcursor(CURSOR_SAVE);
1800 break;
1801 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1802 tcursor(CURSOR_LOAD);
1803 break;
1804 case ' ':
1805 switch (csiescseq.mode[1]) {
1806 case 'q': /* DECSCUSR -- Set Cursor Style */
1807 if (xsetcursor(csiescseq.arg[0]))
1808 goto unknown;
1809 break;
1810 default:
1811 goto unknown;
1813 break;
1817 void
1818 csidump(void)
1820 int i;
1821 uint c;
1823 fprintf(stderr, "ESC[");
1824 for (i = 0; i < csiescseq.len; i++) {
1825 c = csiescseq.buf[i] & 0xff;
1826 if (isprint(c)) {
1827 putc(c, stderr);
1828 } else if (c == '\n') {
1829 fprintf(stderr, "(\\n)");
1830 } else if (c == '\r') {
1831 fprintf(stderr, "(\\r)");
1832 } else if (c == 0x1b) {
1833 fprintf(stderr, "(\\e)");
1834 } else {
1835 fprintf(stderr, "(%02x)", c);
1838 putc('\n', stderr);
1841 void
1842 csireset(void)
1844 memset(&csiescseq, 0, sizeof(csiescseq));
1847 void
1848 strhandle(void)
1850 char *p = NULL;
1851 int j, narg, par;
1853 term.esc &= ~(ESC_STR_END|ESC_STR);
1854 strparse();
1855 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1857 switch (strescseq.type) {
1858 case ']': /* OSC -- Operating System Command */
1859 switch (par) {
1860 case 0:
1861 case 1:
1862 case 2:
1863 if (narg > 1)
1864 xsettitle(strescseq.args[1]);
1865 return;
1866 case 52:
1867 if (narg > 2) {
1868 char *dec;
1870 dec = base64dec(strescseq.args[2]);
1871 if (dec) {
1872 xsetsel(dec);
1873 xclipcopy();
1874 } else {
1875 fprintf(stderr, "erresc: invalid base64\n");
1878 return;
1879 case 4: /* color set */
1880 if (narg < 3)
1881 break;
1882 p = strescseq.args[2];
1883 /* FALLTHROUGH */
1884 case 104: /* color reset, here p = NULL */
1885 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1886 if (xsetcolorname(j, p)) {
1887 fprintf(stderr, "erresc: invalid color %s\n", p);
1888 } else {
1890 * TODO if defaultbg color is changed, borders
1891 * are dirty
1893 redraw();
1895 return;
1897 break;
1898 case 'k': /* old title set compatibility */
1899 xsettitle(strescseq.args[0]);
1900 return;
1901 case 'P': /* DCS -- Device Control String */
1902 term.mode |= ESC_DCS;
1903 case '_': /* APC -- Application Program Command */
1904 case '^': /* PM -- Privacy Message */
1905 return;
1908 fprintf(stderr, "erresc: unknown str ");
1909 strdump();
1912 void
1913 strparse(void)
1915 int c;
1916 char *p = strescseq.buf;
1918 strescseq.narg = 0;
1919 strescseq.buf[strescseq.len] = '\0';
1921 if (*p == '\0')
1922 return;
1924 while (strescseq.narg < STR_ARG_SIZ) {
1925 strescseq.args[strescseq.narg++] = p;
1926 while ((c = *p) != ';' && c != '\0')
1927 ++p;
1928 if (c == '\0')
1929 return;
1930 *p++ = '\0';
1934 void
1935 strdump(void)
1937 int i;
1938 uint c;
1940 fprintf(stderr, "ESC%c", strescseq.type);
1941 for (i = 0; i < strescseq.len; i++) {
1942 c = strescseq.buf[i] & 0xff;
1943 if (c == '\0') {
1944 putc('\n', stderr);
1945 return;
1946 } else if (isprint(c)) {
1947 putc(c, stderr);
1948 } else if (c == '\n') {
1949 fprintf(stderr, "(\\n)");
1950 } else if (c == '\r') {
1951 fprintf(stderr, "(\\r)");
1952 } else if (c == 0x1b) {
1953 fprintf(stderr, "(\\e)");
1954 } else {
1955 fprintf(stderr, "(%02x)", c);
1958 fprintf(stderr, "ESC\\\n");
1961 void
1962 strreset(void)
1964 memset(&strescseq, 0, sizeof(strescseq));
1967 void
1968 sendbreak(const Arg *arg)
1970 if (tcsendbreak(cmdfd, 0))
1971 perror("Error sending break");
1974 void
1975 tprinter(char *s, size_t len)
1977 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1978 perror("Error writing to output file");
1979 close(iofd);
1980 iofd = -1;
1984 void
1985 iso14755(const Arg *arg)
1987 FILE *p;
1988 char *us, *e, codepoint[9], uc[UTF_SIZ];
1989 unsigned long utf32;
1991 if (!(p = popen(ISO14755CMD, "r")))
1992 return;
1994 us = fgets(codepoint, sizeof(codepoint), p);
1995 pclose(p);
1997 if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
1998 return;
1999 if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
2000 (*e != '\n' && *e != '\0'))
2001 return;
2003 ttywrite(uc, utf8encode(utf32, uc), 1);
2006 void
2007 toggleprinter(const Arg *arg)
2009 term.mode ^= MODE_PRINT;
2012 void
2013 printscreen(const Arg *arg)
2015 tdump();
2018 void
2019 printsel(const Arg *arg)
2021 tdumpsel();
2024 void
2025 tdumpsel(void)
2027 char *ptr;
2029 if ((ptr = getsel())) {
2030 tprinter(ptr, strlen(ptr));
2031 free(ptr);
2035 void
2036 tdumpline(int n)
2038 char buf[UTF_SIZ];
2039 Glyph *bp, *end;
2041 bp = &term.line[n][0];
2042 end = &bp[MIN(tlinelen(n), term.col) - 1];
2043 if (bp != end || bp->u != ' ') {
2044 for ( ;bp <= end; ++bp)
2045 tprinter(buf, utf8encode(bp->u, buf));
2047 tprinter("\n", 1);
2050 void
2051 tdump(void)
2053 int i;
2055 for (i = 0; i < term.row; ++i)
2056 tdumpline(i);
2059 void
2060 tputtab(int n)
2062 uint x = term.c.x;
2064 if (n > 0) {
2065 while (x < term.col && n--)
2066 for (++x; x < term.col && !term.tabs[x]; ++x)
2067 /* nothing */ ;
2068 } else if (n < 0) {
2069 while (x > 0 && n++)
2070 for (--x; x > 0 && !term.tabs[x]; --x)
2071 /* nothing */ ;
2073 term.c.x = LIMIT(x, 0, term.col-1);
2076 void
2077 tdefutf8(char ascii)
2079 if (ascii == 'G')
2080 term.mode |= MODE_UTF8;
2081 else if (ascii == '@')
2082 term.mode &= ~MODE_UTF8;
2085 void
2086 tdeftran(char ascii)
2088 static char cs[] = "0B";
2089 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2090 char *p;
2092 if ((p = strchr(cs, ascii)) == NULL) {
2093 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2094 } else {
2095 term.trantbl[term.icharset] = vcs[p - cs];
2099 void
2100 tdectest(char c)
2102 int x, y;
2104 if (c == '8') { /* DEC screen alignment test. */
2105 for (x = 0; x < term.col; ++x) {
2106 for (y = 0; y < term.row; ++y)
2107 tsetchar('E', &term.c.attr, x, y);
2112 void
2113 tstrsequence(uchar c)
2115 strreset();
2117 switch (c) {
2118 case 0x90: /* DCS -- Device Control String */
2119 c = 'P';
2120 term.esc |= ESC_DCS;
2121 break;
2122 case 0x9f: /* APC -- Application Program Command */
2123 c = '_';
2124 break;
2125 case 0x9e: /* PM -- Privacy Message */
2126 c = '^';
2127 break;
2128 case 0x9d: /* OSC -- Operating System Command */
2129 c = ']';
2130 break;
2132 strescseq.type = c;
2133 term.esc |= ESC_STR;
2136 void
2137 tcontrolcode(uchar ascii)
2139 switch (ascii) {
2140 case '\t': /* HT */
2141 tputtab(1);
2142 return;
2143 case '\b': /* BS */
2144 tmoveto(term.c.x-1, term.c.y);
2145 return;
2146 case '\r': /* CR */
2147 tmoveto(0, term.c.y);
2148 return;
2149 case '\f': /* LF */
2150 case '\v': /* VT */
2151 case '\n': /* LF */
2152 /* go to first col if the mode is set */
2153 tnewline(IS_SET(MODE_CRLF));
2154 return;
2155 case '\a': /* BEL */
2156 if (term.esc & ESC_STR_END) {
2157 /* backwards compatibility to xterm */
2158 strhandle();
2159 } else {
2160 xbell();
2162 break;
2163 case '\033': /* ESC */
2164 csireset();
2165 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2166 term.esc |= ESC_START;
2167 return;
2168 case '\016': /* SO (LS1 -- Locking shift 1) */
2169 case '\017': /* SI (LS0 -- Locking shift 0) */
2170 term.charset = 1 - (ascii - '\016');
2171 return;
2172 case '\032': /* SUB */
2173 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2174 case '\030': /* CAN */
2175 csireset();
2176 break;
2177 case '\005': /* ENQ (IGNORED) */
2178 case '\000': /* NUL (IGNORED) */
2179 case '\021': /* XON (IGNORED) */
2180 case '\023': /* XOFF (IGNORED) */
2181 case 0177: /* DEL (IGNORED) */
2182 return;
2183 case 0x80: /* TODO: PAD */
2184 case 0x81: /* TODO: HOP */
2185 case 0x82: /* TODO: BPH */
2186 case 0x83: /* TODO: NBH */
2187 case 0x84: /* TODO: IND */
2188 break;
2189 case 0x85: /* NEL -- Next line */
2190 tnewline(1); /* always go to first col */
2191 break;
2192 case 0x86: /* TODO: SSA */
2193 case 0x87: /* TODO: ESA */
2194 break;
2195 case 0x88: /* HTS -- Horizontal tab stop */
2196 term.tabs[term.c.x] = 1;
2197 break;
2198 case 0x89: /* TODO: HTJ */
2199 case 0x8a: /* TODO: VTS */
2200 case 0x8b: /* TODO: PLD */
2201 case 0x8c: /* TODO: PLU */
2202 case 0x8d: /* TODO: RI */
2203 case 0x8e: /* TODO: SS2 */
2204 case 0x8f: /* TODO: SS3 */
2205 case 0x91: /* TODO: PU1 */
2206 case 0x92: /* TODO: PU2 */
2207 case 0x93: /* TODO: STS */
2208 case 0x94: /* TODO: CCH */
2209 case 0x95: /* TODO: MW */
2210 case 0x96: /* TODO: SPA */
2211 case 0x97: /* TODO: EPA */
2212 case 0x98: /* TODO: SOS */
2213 case 0x99: /* TODO: SGCI */
2214 break;
2215 case 0x9a: /* DECID -- Identify Terminal */
2216 ttywrite(vtiden, strlen(vtiden), 0);
2217 break;
2218 case 0x9b: /* TODO: CSI */
2219 case 0x9c: /* TODO: ST */
2220 break;
2221 case 0x90: /* DCS -- Device Control String */
2222 case 0x9d: /* OSC -- Operating System Command */
2223 case 0x9e: /* PM -- Privacy Message */
2224 case 0x9f: /* APC -- Application Program Command */
2225 tstrsequence(ascii);
2226 return;
2228 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2229 term.esc &= ~(ESC_STR_END|ESC_STR);
2233 * returns 1 when the sequence is finished and it hasn't to read
2234 * more characters for this sequence, otherwise 0
2237 eschandle(uchar ascii)
2239 switch (ascii) {
2240 case '[':
2241 term.esc |= ESC_CSI;
2242 return 0;
2243 case '#':
2244 term.esc |= ESC_TEST;
2245 return 0;
2246 case '%':
2247 term.esc |= ESC_UTF8;
2248 return 0;
2249 case 'P': /* DCS -- Device Control String */
2250 case '_': /* APC -- Application Program Command */
2251 case '^': /* PM -- Privacy Message */
2252 case ']': /* OSC -- Operating System Command */
2253 case 'k': /* old title set compatibility */
2254 tstrsequence(ascii);
2255 return 0;
2256 case 'n': /* LS2 -- Locking shift 2 */
2257 case 'o': /* LS3 -- Locking shift 3 */
2258 term.charset = 2 + (ascii - 'n');
2259 break;
2260 case '(': /* GZD4 -- set primary charset G0 */
2261 case ')': /* G1D4 -- set secondary charset G1 */
2262 case '*': /* G2D4 -- set tertiary charset G2 */
2263 case '+': /* G3D4 -- set quaternary charset G3 */
2264 term.icharset = ascii - '(';
2265 term.esc |= ESC_ALTCHARSET;
2266 return 0;
2267 case 'D': /* IND -- Linefeed */
2268 if (term.c.y == term.bot) {
2269 tscrollup(term.top, 1);
2270 } else {
2271 tmoveto(term.c.x, term.c.y+1);
2273 break;
2274 case 'E': /* NEL -- Next line */
2275 tnewline(1); /* always go to first col */
2276 break;
2277 case 'H': /* HTS -- Horizontal tab stop */
2278 term.tabs[term.c.x] = 1;
2279 break;
2280 case 'M': /* RI -- Reverse index */
2281 if (term.c.y == term.top) {
2282 tscrolldown(term.top, 1);
2283 } else {
2284 tmoveto(term.c.x, term.c.y-1);
2286 break;
2287 case 'Z': /* DECID -- Identify Terminal */
2288 ttywrite(vtiden, strlen(vtiden), 0);
2289 break;
2290 case 'c': /* RIS -- Reset to inital state */
2291 treset();
2292 resettitle();
2293 xloadcols();
2294 break;
2295 case '=': /* DECPAM -- Application keypad */
2296 xsetmode(1, MODE_APPKEYPAD);
2297 break;
2298 case '>': /* DECPNM -- Normal keypad */
2299 xsetmode(0, MODE_APPKEYPAD);
2300 break;
2301 case '7': /* DECSC -- Save Cursor */
2302 tcursor(CURSOR_SAVE);
2303 break;
2304 case '8': /* DECRC -- Restore Cursor */
2305 tcursor(CURSOR_LOAD);
2306 break;
2307 case '\\': /* ST -- String Terminator */
2308 if (term.esc & ESC_STR_END)
2309 strhandle();
2310 break;
2311 default:
2312 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2313 (uchar) ascii, isprint(ascii)? ascii:'.');
2314 break;
2316 return 1;
2319 void
2320 tputc(Rune u)
2322 char c[UTF_SIZ];
2323 int control;
2324 int width, len;
2325 Glyph *gp;
2327 control = ISCONTROL(u);
2328 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2329 c[0] = u;
2330 width = len = 1;
2331 } else {
2332 len = utf8encode(u, c);
2333 if (!control && (width = wcwidth(u)) == -1) {
2334 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2335 width = 1;
2339 if (IS_SET(MODE_PRINT))
2340 tprinter(c, len);
2343 * STR sequence must be checked before anything else
2344 * because it uses all following characters until it
2345 * receives a ESC, a SUB, a ST or any other C1 control
2346 * character.
2348 if (term.esc & ESC_STR) {
2349 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2350 ISCONTROLC1(u)) {
2351 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2352 if (IS_SET(MODE_SIXEL)) {
2353 /* TODO: render sixel */;
2354 term.mode &= ~MODE_SIXEL;
2355 return;
2357 term.esc |= ESC_STR_END;
2358 goto check_control_code;
2362 if (IS_SET(MODE_SIXEL)) {
2363 /* TODO: implement sixel mode */
2364 return;
2366 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2367 term.mode |= MODE_SIXEL;
2369 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2371 * Here is a bug in terminals. If the user never sends
2372 * some code to stop the str or esc command, then st
2373 * will stop responding. But this is better than
2374 * silently failing with unknown characters. At least
2375 * then users will report back.
2377 * In the case users ever get fixed, here is the code:
2380 * term.esc = 0;
2381 * strhandle();
2383 return;
2386 memmove(&strescseq.buf[strescseq.len], c, len);
2387 strescseq.len += len;
2388 return;
2391 check_control_code:
2393 * Actions of control codes must be performed as soon they arrive
2394 * because they can be embedded inside a control sequence, and
2395 * they must not cause conflicts with sequences.
2397 if (control) {
2398 tcontrolcode(u);
2400 * control codes are not shown ever
2402 return;
2403 } else if (term.esc & ESC_START) {
2404 if (term.esc & ESC_CSI) {
2405 csiescseq.buf[csiescseq.len++] = u;
2406 if (BETWEEN(u, 0x40, 0x7E)
2407 || csiescseq.len >= \
2408 sizeof(csiescseq.buf)-1) {
2409 term.esc = 0;
2410 csiparse();
2411 csihandle();
2413 return;
2414 } else if (term.esc & ESC_UTF8) {
2415 tdefutf8(u);
2416 } else if (term.esc & ESC_ALTCHARSET) {
2417 tdeftran(u);
2418 } else if (term.esc & ESC_TEST) {
2419 tdectest(u);
2420 } else {
2421 if (!eschandle(u))
2422 return;
2423 /* sequence already finished */
2425 term.esc = 0;
2427 * All characters which form part of a sequence are not
2428 * printed
2430 return;
2432 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2433 selclear();
2435 gp = &term.line[term.c.y][term.c.x];
2436 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2437 gp->mode |= ATTR_WRAP;
2438 tnewline(1);
2439 gp = &term.line[term.c.y][term.c.x];
2442 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2443 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2445 if (term.c.x+width > term.col) {
2446 tnewline(1);
2447 gp = &term.line[term.c.y][term.c.x];
2450 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2452 if (width == 2) {
2453 gp->mode |= ATTR_WIDE;
2454 if (term.c.x+1 < term.col) {
2455 gp[1].u = '\0';
2456 gp[1].mode = ATTR_WDUMMY;
2459 if (term.c.x+width < term.col) {
2460 tmoveto(term.c.x+width, term.c.y);
2461 } else {
2462 term.c.state |= CURSOR_WRAPNEXT;
2467 twrite(const char *buf, int buflen, int show_ctrl)
2469 int charsize;
2470 Rune u;
2471 int n;
2473 for (n = 0; n < buflen; n += charsize) {
2474 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2475 /* process a complete utf8 char */
2476 charsize = utf8decode(buf + n, &u, buflen - n);
2477 if (charsize == 0)
2478 break;
2479 } else {
2480 u = buf[n] & 0xFF;
2481 charsize = 1;
2483 if (show_ctrl && ISCONTROL(u)) {
2484 if (u & 0x80) {
2485 u &= 0x7f;
2486 tputc('^');
2487 tputc('[');
2488 } else if (u != '\n' && u != '\r' && u != '\t') {
2489 u ^= 0x40;
2490 tputc('^');
2493 tputc(u);
2495 return n;
2498 void
2499 tresize(int col, int row)
2501 int i;
2502 int minrow = MIN(row, term.row);
2503 int mincol = MIN(col, term.col);
2504 int *bp;
2505 TCursor c;
2507 if (col < 1 || row < 1) {
2508 fprintf(stderr,
2509 "tresize: error resizing to %dx%d\n", col, row);
2510 return;
2514 * slide screen to keep cursor where we expect it -
2515 * tscrollup would work here, but we can optimize to
2516 * memmove because we're freeing the earlier lines
2518 for (i = 0; i <= term.c.y - row; i++) {
2519 free(term.line[i]);
2520 free(term.alt[i]);
2522 /* ensure that both src and dst are not NULL */
2523 if (i > 0) {
2524 memmove(term.line, term.line + i, row * sizeof(Line));
2525 memmove(term.alt, term.alt + i, row * sizeof(Line));
2527 for (i += row; i < term.row; i++) {
2528 free(term.line[i]);
2529 free(term.alt[i]);
2532 /* resize to new height */
2533 term.line = xrealloc(term.line, row * sizeof(Line));
2534 term.alt = xrealloc(term.alt, row * sizeof(Line));
2535 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2536 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2538 /* resize each row to new width, zero-pad if needed */
2539 for (i = 0; i < minrow; i++) {
2540 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2541 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2544 /* allocate any new rows */
2545 for (/* i = minrow */; i < row; i++) {
2546 term.line[i] = xmalloc(col * sizeof(Glyph));
2547 term.alt[i] = xmalloc(col * sizeof(Glyph));
2549 if (col > term.col) {
2550 bp = term.tabs + term.col;
2552 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2553 while (--bp > term.tabs && !*bp)
2554 /* nothing */ ;
2555 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2556 *bp = 1;
2558 /* update terminal size */
2559 term.col = col;
2560 term.row = row;
2561 /* reset scrolling region */
2562 tsetscroll(0, row-1);
2563 /* make use of the LIMIT in tmoveto */
2564 tmoveto(term.c.x, term.c.y);
2565 /* Clearing both screens (it makes dirty all lines) */
2566 c = term.c;
2567 for (i = 0; i < 2; i++) {
2568 if (mincol < col && 0 < minrow) {
2569 tclearregion(mincol, 0, col - 1, minrow - 1);
2571 if (0 < col && minrow < row) {
2572 tclearregion(0, minrow, col - 1, row - 1);
2574 tswapscreen();
2575 tcursor(CURSOR_LOAD);
2577 term.c = c;
2580 void
2581 resettitle(void)
2583 xsettitle(NULL);
2586 void
2587 drawregion(int x1, int y1, int x2, int y2)
2589 int y;
2590 for (y = y1; y < y2; y++) {
2591 if (!term.dirty[y])
2592 continue;
2594 term.dirty[y] = 0;
2595 xdrawline(term.line[y], x1, y, x2);
2599 void
2600 draw(void)
2602 int cx = term.c.x;
2604 if (!xstartdraw())
2605 return;
2607 /* adjust cursor position */
2608 LIMIT(term.ocx, 0, term.col-1);
2609 LIMIT(term.ocy, 0, term.row-1);
2610 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2611 term.ocx--;
2612 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2613 cx--;
2615 drawregion(0, 0, term.col, term.row);
2616 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2617 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2618 term.ocx = cx, term.ocy = term.c.y;
2619 xfinishdraw();
2622 void
2623 redraw(void)
2625 tfulldirt();
2626 draw();