selextend: clarify: !sel.mode == SEL_IDLE
[azarus-st.git] / st.c
blob46c954ba8bc7276168cfd0129b8e6526b1591715
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 = malloc(len);
261 if (!p)
262 die("Out of memory\n");
264 return p;
267 void *
268 xrealloc(void *p, size_t len)
270 if ((p = realloc(p, len)) == NULL)
271 die("Out of memory\n");
273 return p;
276 char *
277 xstrdup(char *s)
279 if ((s = strdup(s)) == NULL)
280 die("Out of memory\n");
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.snap = snap;
450 sel.oe.x = sel.ob.x = col;
451 sel.oe.y = sel.ob.y = row;
452 selnormalize();
454 if (sel.snap != 0)
455 sel.mode = SEL_READY;
456 tsetdirt(sel.nb.y, sel.ne.y);
459 void
460 selextend(int col, int row, int type, int done)
462 int oldey, oldex, oldsby, oldsey, oldtype;
464 if (sel.mode == SEL_IDLE)
465 return;
466 if (done && sel.mode == SEL_EMPTY) {
467 selclear();
468 return;
471 oldey = sel.oe.y;
472 oldex = sel.oe.x;
473 oldsby = sel.nb.y;
474 oldsey = sel.ne.y;
475 oldtype = sel.type;
477 sel.alt = IS_SET(MODE_ALTSCREEN);
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 failed: %s\n", strerror(errno));
785 dup2(cmdfd, 0);
786 stty(args);
787 return cmdfd;
790 /* seems to work fine on linux, openbsd and freebsd */
791 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
792 die("openpty failed: %s\n", strerror(errno));
794 switch (pid = fork()) {
795 case -1:
796 die("fork failed\n");
797 break;
798 case 0:
799 close(iofd);
800 setsid(); /* create a new process group */
801 dup2(s, 0);
802 dup2(s, 1);
803 dup2(s, 2);
804 if (ioctl(s, TIOCSCTTY, NULL) < 0)
805 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
806 close(s);
807 close(m);
808 execsh(cmd, args);
809 break;
810 default:
811 close(s);
812 cmdfd = m;
813 signal(SIGCHLD, sigchld);
814 break;
816 return cmdfd;
819 size_t
820 ttyread(void)
822 static char buf[BUFSIZ];
823 static int buflen = 0;
824 int written;
825 int ret;
827 /* append read bytes to unprocessed bytes */
828 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
829 die("Couldn't read from shell: %s\n", strerror(errno));
830 buflen += ret;
832 written = twrite(buf, buflen, 0);
833 buflen -= written;
834 /* keep any uncomplete utf8 char for the next call */
835 if (buflen > 0)
836 memmove(buf, buf + written, buflen);
838 return ret;
841 void
842 ttywrite(const char *s, size_t n, int may_echo)
844 const char *next;
846 if (may_echo && IS_SET(MODE_ECHO))
847 twrite(s, n, 1);
849 if (!IS_SET(MODE_CRLF)) {
850 ttywriteraw(s, n);
851 return;
854 /* This is similar to how the kernel handles ONLCR for ttys */
855 while (n > 0) {
856 if (*s == '\r') {
857 next = s + 1;
858 ttywriteraw("\r\n", 2);
859 } else {
860 next = memchr(s, '\r', n);
861 DEFAULT(next, s + n);
862 ttywriteraw(s, next - s);
864 n -= next - s;
865 s = next;
869 void
870 ttywriteraw(const char *s, size_t n)
872 fd_set wfd, rfd;
873 ssize_t r;
874 size_t lim = 256;
877 * Remember that we are using a pty, which might be a modem line.
878 * Writing too much will clog the line. That's why we are doing this
879 * dance.
880 * FIXME: Migrate the world to Plan 9.
882 while (n > 0) {
883 FD_ZERO(&wfd);
884 FD_ZERO(&rfd);
885 FD_SET(cmdfd, &wfd);
886 FD_SET(cmdfd, &rfd);
888 /* Check if we can write. */
889 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
890 if (errno == EINTR)
891 continue;
892 die("select failed: %s\n", strerror(errno));
894 if (FD_ISSET(cmdfd, &wfd)) {
896 * Only write the bytes written by ttywrite() or the
897 * default of 256. This seems to be a reasonable value
898 * for a serial line. Bigger values might clog the I/O.
900 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
901 goto write_error;
902 if (r < n) {
904 * We weren't able to write out everything.
905 * This means the buffer is getting full
906 * again. Empty it.
908 if (n < lim)
909 lim = ttyread();
910 n -= r;
911 s += r;
912 } else {
913 /* All bytes have been written. */
914 break;
917 if (FD_ISSET(cmdfd, &rfd))
918 lim = ttyread();
920 return;
922 write_error:
923 die("write error on tty: %s\n", strerror(errno));
926 void
927 ttyresize(int tw, int th)
929 struct winsize w;
931 w.ws_row = term.row;
932 w.ws_col = term.col;
933 w.ws_xpixel = tw;
934 w.ws_ypixel = th;
935 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
936 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
939 void
940 ttyhangup()
942 /* Send SIGHUP to shell */
943 kill(pid, SIGHUP);
947 tattrset(int attr)
949 int i, j;
951 for (i = 0; i < term.row-1; i++) {
952 for (j = 0; j < term.col-1; j++) {
953 if (term.line[i][j].mode & attr)
954 return 1;
958 return 0;
961 void
962 tsetdirt(int top, int bot)
964 int i;
966 LIMIT(top, 0, term.row-1);
967 LIMIT(bot, 0, term.row-1);
969 for (i = top; i <= bot; i++)
970 term.dirty[i] = 1;
973 void
974 tsetdirtattr(int attr)
976 int i, j;
978 for (i = 0; i < term.row-1; i++) {
979 for (j = 0; j < term.col-1; j++) {
980 if (term.line[i][j].mode & attr) {
981 tsetdirt(i, i);
982 break;
988 void
989 tfulldirt(void)
991 tsetdirt(0, term.row-1);
994 void
995 tcursor(int mode)
997 static TCursor c[2];
998 int alt = IS_SET(MODE_ALTSCREEN);
1000 if (mode == CURSOR_SAVE) {
1001 c[alt] = term.c;
1002 } else if (mode == CURSOR_LOAD) {
1003 term.c = c[alt];
1004 tmoveto(c[alt].x, c[alt].y);
1008 void
1009 treset(void)
1011 uint i;
1013 term.c = (TCursor){{
1014 .mode = ATTR_NULL,
1015 .fg = defaultfg,
1016 .bg = defaultbg
1017 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1019 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1020 for (i = tabspaces; i < term.col; i += tabspaces)
1021 term.tabs[i] = 1;
1022 term.top = 0;
1023 term.bot = term.row - 1;
1024 term.mode = MODE_WRAP|MODE_UTF8;
1025 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1026 term.charset = 0;
1028 for (i = 0; i < 2; i++) {
1029 tmoveto(0, 0);
1030 tcursor(CURSOR_SAVE);
1031 tclearregion(0, 0, term.col-1, term.row-1);
1032 tswapscreen();
1036 void
1037 tnew(int col, int row)
1039 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1040 tresize(col, row);
1041 treset();
1044 void
1045 tswapscreen(void)
1047 Line *tmp = term.line;
1049 term.line = term.alt;
1050 term.alt = tmp;
1051 term.mode ^= MODE_ALTSCREEN;
1052 tfulldirt();
1055 void
1056 tscrolldown(int orig, int n)
1058 int i;
1059 Line temp;
1061 LIMIT(n, 0, term.bot-orig+1);
1063 tsetdirt(orig, term.bot-n);
1064 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1066 for (i = term.bot; i >= orig+n; i--) {
1067 temp = term.line[i];
1068 term.line[i] = term.line[i-n];
1069 term.line[i-n] = temp;
1072 selscroll(orig, n);
1075 void
1076 tscrollup(int orig, int n)
1078 int i;
1079 Line temp;
1081 LIMIT(n, 0, term.bot-orig+1);
1083 tclearregion(0, orig, term.col-1, orig+n-1);
1084 tsetdirt(orig+n, term.bot);
1086 for (i = orig; i <= term.bot-n; i++) {
1087 temp = term.line[i];
1088 term.line[i] = term.line[i+n];
1089 term.line[i+n] = temp;
1092 selscroll(orig, -n);
1095 void
1096 selscroll(int orig, int n)
1098 if (sel.ob.x == -1)
1099 return;
1101 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1102 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1103 selclear();
1104 return;
1106 if (sel.type == SEL_RECTANGULAR) {
1107 if (sel.ob.y < term.top)
1108 sel.ob.y = term.top;
1109 if (sel.oe.y > term.bot)
1110 sel.oe.y = term.bot;
1111 } else {
1112 if (sel.ob.y < term.top) {
1113 sel.ob.y = term.top;
1114 sel.ob.x = 0;
1116 if (sel.oe.y > term.bot) {
1117 sel.oe.y = term.bot;
1118 sel.oe.x = term.col;
1121 selnormalize();
1125 void
1126 tnewline(int first_col)
1128 int y = term.c.y;
1130 if (y == term.bot) {
1131 tscrollup(term.top, 1);
1132 } else {
1133 y++;
1135 tmoveto(first_col ? 0 : term.c.x, y);
1138 void
1139 csiparse(void)
1141 char *p = csiescseq.buf, *np;
1142 long int v;
1144 csiescseq.narg = 0;
1145 if (*p == '?') {
1146 csiescseq.priv = 1;
1147 p++;
1150 csiescseq.buf[csiescseq.len] = '\0';
1151 while (p < csiescseq.buf+csiescseq.len) {
1152 np = NULL;
1153 v = strtol(p, &np, 10);
1154 if (np == p)
1155 v = 0;
1156 if (v == LONG_MAX || v == LONG_MIN)
1157 v = -1;
1158 csiescseq.arg[csiescseq.narg++] = v;
1159 p = np;
1160 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1161 break;
1162 p++;
1164 csiescseq.mode[0] = *p++;
1165 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1168 /* for absolute user moves, when decom is set */
1169 void
1170 tmoveato(int x, int y)
1172 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1175 void
1176 tmoveto(int x, int y)
1178 int miny, maxy;
1180 if (term.c.state & CURSOR_ORIGIN) {
1181 miny = term.top;
1182 maxy = term.bot;
1183 } else {
1184 miny = 0;
1185 maxy = term.row - 1;
1187 term.c.state &= ~CURSOR_WRAPNEXT;
1188 term.c.x = LIMIT(x, 0, term.col-1);
1189 term.c.y = LIMIT(y, miny, maxy);
1192 void
1193 tsetchar(Rune u, Glyph *attr, int x, int y)
1195 static char *vt100_0[62] = { /* 0x41 - 0x7e */
1196 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1197 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1198 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1199 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1200 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1201 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1202 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1203 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1207 * The table is proudly stolen from rxvt.
1209 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1210 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1211 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1213 if (term.line[y][x].mode & ATTR_WIDE) {
1214 if (x+1 < term.col) {
1215 term.line[y][x+1].u = ' ';
1216 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1218 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1219 term.line[y][x-1].u = ' ';
1220 term.line[y][x-1].mode &= ~ATTR_WIDE;
1223 term.dirty[y] = 1;
1224 term.line[y][x] = *attr;
1225 term.line[y][x].u = u;
1228 void
1229 tclearregion(int x1, int y1, int x2, int y2)
1231 int x, y, temp;
1232 Glyph *gp;
1234 if (x1 > x2)
1235 temp = x1, x1 = x2, x2 = temp;
1236 if (y1 > y2)
1237 temp = y1, y1 = y2, y2 = temp;
1239 LIMIT(x1, 0, term.col-1);
1240 LIMIT(x2, 0, term.col-1);
1241 LIMIT(y1, 0, term.row-1);
1242 LIMIT(y2, 0, term.row-1);
1244 for (y = y1; y <= y2; y++) {
1245 term.dirty[y] = 1;
1246 for (x = x1; x <= x2; x++) {
1247 gp = &term.line[y][x];
1248 if (selected(x, y))
1249 selclear();
1250 gp->fg = term.c.attr.fg;
1251 gp->bg = term.c.attr.bg;
1252 gp->mode = 0;
1253 gp->u = ' ';
1258 void
1259 tdeletechar(int n)
1261 int dst, src, size;
1262 Glyph *line;
1264 LIMIT(n, 0, term.col - term.c.x);
1266 dst = term.c.x;
1267 src = term.c.x + n;
1268 size = term.col - src;
1269 line = term.line[term.c.y];
1271 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1272 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1275 void
1276 tinsertblank(int n)
1278 int dst, src, size;
1279 Glyph *line;
1281 LIMIT(n, 0, term.col - term.c.x);
1283 dst = term.c.x + n;
1284 src = term.c.x;
1285 size = term.col - dst;
1286 line = term.line[term.c.y];
1288 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1289 tclearregion(src, term.c.y, dst - 1, term.c.y);
1292 void
1293 tinsertblankline(int n)
1295 if (BETWEEN(term.c.y, term.top, term.bot))
1296 tscrolldown(term.c.y, n);
1299 void
1300 tdeleteline(int n)
1302 if (BETWEEN(term.c.y, term.top, term.bot))
1303 tscrollup(term.c.y, n);
1306 int32_t
1307 tdefcolor(int *attr, int *npar, int l)
1309 int32_t idx = -1;
1310 uint r, g, b;
1312 switch (attr[*npar + 1]) {
1313 case 2: /* direct color in RGB space */
1314 if (*npar + 4 >= l) {
1315 fprintf(stderr,
1316 "erresc(38): Incorrect number of parameters (%d)\n",
1317 *npar);
1318 break;
1320 r = attr[*npar + 2];
1321 g = attr[*npar + 3];
1322 b = attr[*npar + 4];
1323 *npar += 4;
1324 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1325 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1326 r, g, b);
1327 else
1328 idx = TRUECOLOR(r, g, b);
1329 break;
1330 case 5: /* indexed color */
1331 if (*npar + 2 >= l) {
1332 fprintf(stderr,
1333 "erresc(38): Incorrect number of parameters (%d)\n",
1334 *npar);
1335 break;
1337 *npar += 2;
1338 if (!BETWEEN(attr[*npar], 0, 255))
1339 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1340 else
1341 idx = attr[*npar];
1342 break;
1343 case 0: /* implemented defined (only foreground) */
1344 case 1: /* transparent */
1345 case 3: /* direct color in CMY space */
1346 case 4: /* direct color in CMYK space */
1347 default:
1348 fprintf(stderr,
1349 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1350 break;
1353 return idx;
1356 void
1357 tsetattr(int *attr, int l)
1359 int i;
1360 int32_t idx;
1362 for (i = 0; i < l; i++) {
1363 switch (attr[i]) {
1364 case 0:
1365 term.c.attr.mode &= ~(
1366 ATTR_BOLD |
1367 ATTR_FAINT |
1368 ATTR_ITALIC |
1369 ATTR_UNDERLINE |
1370 ATTR_BLINK |
1371 ATTR_REVERSE |
1372 ATTR_INVISIBLE |
1373 ATTR_STRUCK );
1374 term.c.attr.fg = defaultfg;
1375 term.c.attr.bg = defaultbg;
1376 break;
1377 case 1:
1378 term.c.attr.mode |= ATTR_BOLD;
1379 break;
1380 case 2:
1381 term.c.attr.mode |= ATTR_FAINT;
1382 break;
1383 case 3:
1384 term.c.attr.mode |= ATTR_ITALIC;
1385 break;
1386 case 4:
1387 term.c.attr.mode |= ATTR_UNDERLINE;
1388 break;
1389 case 5: /* slow blink */
1390 /* FALLTHROUGH */
1391 case 6: /* rapid blink */
1392 term.c.attr.mode |= ATTR_BLINK;
1393 break;
1394 case 7:
1395 term.c.attr.mode |= ATTR_REVERSE;
1396 break;
1397 case 8:
1398 term.c.attr.mode |= ATTR_INVISIBLE;
1399 break;
1400 case 9:
1401 term.c.attr.mode |= ATTR_STRUCK;
1402 break;
1403 case 22:
1404 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1405 break;
1406 case 23:
1407 term.c.attr.mode &= ~ATTR_ITALIC;
1408 break;
1409 case 24:
1410 term.c.attr.mode &= ~ATTR_UNDERLINE;
1411 break;
1412 case 25:
1413 term.c.attr.mode &= ~ATTR_BLINK;
1414 break;
1415 case 27:
1416 term.c.attr.mode &= ~ATTR_REVERSE;
1417 break;
1418 case 28:
1419 term.c.attr.mode &= ~ATTR_INVISIBLE;
1420 break;
1421 case 29:
1422 term.c.attr.mode &= ~ATTR_STRUCK;
1423 break;
1424 case 38:
1425 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1426 term.c.attr.fg = idx;
1427 break;
1428 case 39:
1429 term.c.attr.fg = defaultfg;
1430 break;
1431 case 48:
1432 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1433 term.c.attr.bg = idx;
1434 break;
1435 case 49:
1436 term.c.attr.bg = defaultbg;
1437 break;
1438 default:
1439 if (BETWEEN(attr[i], 30, 37)) {
1440 term.c.attr.fg = attr[i] - 30;
1441 } else if (BETWEEN(attr[i], 40, 47)) {
1442 term.c.attr.bg = attr[i] - 40;
1443 } else if (BETWEEN(attr[i], 90, 97)) {
1444 term.c.attr.fg = attr[i] - 90 + 8;
1445 } else if (BETWEEN(attr[i], 100, 107)) {
1446 term.c.attr.bg = attr[i] - 100 + 8;
1447 } else {
1448 fprintf(stderr,
1449 "erresc(default): gfx attr %d unknown\n",
1450 attr[i]), csidump();
1452 break;
1457 void
1458 tsetscroll(int t, int b)
1460 int temp;
1462 LIMIT(t, 0, term.row-1);
1463 LIMIT(b, 0, term.row-1);
1464 if (t > b) {
1465 temp = t;
1466 t = b;
1467 b = temp;
1469 term.top = t;
1470 term.bot = b;
1473 void
1474 tsetmode(int priv, int set, int *args, int narg)
1476 int alt, *lim;
1478 for (lim = args + narg; args < lim; ++args) {
1479 if (priv) {
1480 switch (*args) {
1481 case 1: /* DECCKM -- Cursor key */
1482 xsetmode(set, MODE_APPCURSOR);
1483 break;
1484 case 5: /* DECSCNM -- Reverse video */
1485 xsetmode(set, MODE_REVERSE);
1486 break;
1487 case 6: /* DECOM -- Origin */
1488 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1489 tmoveato(0, 0);
1490 break;
1491 case 7: /* DECAWM -- Auto wrap */
1492 MODBIT(term.mode, set, MODE_WRAP);
1493 break;
1494 case 0: /* Error (IGNORED) */
1495 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1496 case 3: /* DECCOLM -- Column (IGNORED) */
1497 case 4: /* DECSCLM -- Scroll (IGNORED) */
1498 case 8: /* DECARM -- Auto repeat (IGNORED) */
1499 case 18: /* DECPFF -- Printer feed (IGNORED) */
1500 case 19: /* DECPEX -- Printer extent (IGNORED) */
1501 case 42: /* DECNRCM -- National characters (IGNORED) */
1502 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1503 break;
1504 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1505 xsetmode(!set, MODE_HIDE);
1506 break;
1507 case 9: /* X10 mouse compatibility mode */
1508 xsetpointermotion(0);
1509 xsetmode(0, MODE_MOUSE);
1510 xsetmode(set, MODE_MOUSEX10);
1511 break;
1512 case 1000: /* 1000: report button press */
1513 xsetpointermotion(0);
1514 xsetmode(0, MODE_MOUSE);
1515 xsetmode(set, MODE_MOUSEBTN);
1516 break;
1517 case 1002: /* 1002: report motion on button press */
1518 xsetpointermotion(0);
1519 xsetmode(0, MODE_MOUSE);
1520 xsetmode(set, MODE_MOUSEMOTION);
1521 break;
1522 case 1003: /* 1003: enable all mouse motions */
1523 xsetpointermotion(set);
1524 xsetmode(0, MODE_MOUSE);
1525 xsetmode(set, MODE_MOUSEMANY);
1526 break;
1527 case 1004: /* 1004: send focus events to tty */
1528 xsetmode(set, MODE_FOCUS);
1529 break;
1530 case 1006: /* 1006: extended reporting mode */
1531 xsetmode(set, MODE_MOUSESGR);
1532 break;
1533 case 1034:
1534 xsetmode(set, MODE_8BIT);
1535 break;
1536 case 1049: /* swap screen & set/restore cursor as xterm */
1537 if (!allowaltscreen)
1538 break;
1539 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1540 /* FALLTHROUGH */
1541 case 47: /* swap screen */
1542 case 1047:
1543 if (!allowaltscreen)
1544 break;
1545 alt = IS_SET(MODE_ALTSCREEN);
1546 if (alt) {
1547 tclearregion(0, 0, term.col-1,
1548 term.row-1);
1550 if (set ^ alt) /* set is always 1 or 0 */
1551 tswapscreen();
1552 if (*args != 1049)
1553 break;
1554 /* FALLTHROUGH */
1555 case 1048:
1556 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1557 break;
1558 case 2004: /* 2004: bracketed paste mode */
1559 xsetmode(set, MODE_BRCKTPASTE);
1560 break;
1561 /* Not implemented mouse modes. See comments there. */
1562 case 1001: /* mouse highlight mode; can hang the
1563 terminal by design when implemented. */
1564 case 1005: /* UTF-8 mouse mode; will confuse
1565 applications not supporting UTF-8
1566 and luit. */
1567 case 1015: /* urxvt mangled mouse mode; incompatible
1568 and can be mistaken for other control
1569 codes. */
1570 default:
1571 fprintf(stderr,
1572 "erresc: unknown private set/reset mode %d\n",
1573 *args);
1574 break;
1576 } else {
1577 switch (*args) {
1578 case 0: /* Error (IGNORED) */
1579 break;
1580 case 2:
1581 xsetmode(set, MODE_KBDLOCK);
1582 break;
1583 case 4: /* IRM -- Insertion-replacement */
1584 MODBIT(term.mode, set, MODE_INSERT);
1585 break;
1586 case 12: /* SRM -- Send/Receive */
1587 MODBIT(term.mode, !set, MODE_ECHO);
1588 break;
1589 case 20: /* LNM -- Linefeed/new line */
1590 MODBIT(term.mode, set, MODE_CRLF);
1591 break;
1592 default:
1593 fprintf(stderr,
1594 "erresc: unknown set/reset mode %d\n",
1595 *args);
1596 break;
1602 void
1603 csihandle(void)
1605 char buf[40];
1606 int len;
1608 switch (csiescseq.mode[0]) {
1609 default:
1610 unknown:
1611 fprintf(stderr, "erresc: unknown csi ");
1612 csidump();
1613 /* die(""); */
1614 break;
1615 case '@': /* ICH -- Insert <n> blank char */
1616 DEFAULT(csiescseq.arg[0], 1);
1617 tinsertblank(csiescseq.arg[0]);
1618 break;
1619 case 'A': /* CUU -- Cursor <n> Up */
1620 DEFAULT(csiescseq.arg[0], 1);
1621 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1622 break;
1623 case 'B': /* CUD -- Cursor <n> Down */
1624 case 'e': /* VPR --Cursor <n> Down */
1625 DEFAULT(csiescseq.arg[0], 1);
1626 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1627 break;
1628 case 'i': /* MC -- Media Copy */
1629 switch (csiescseq.arg[0]) {
1630 case 0:
1631 tdump();
1632 break;
1633 case 1:
1634 tdumpline(term.c.y);
1635 break;
1636 case 2:
1637 tdumpsel();
1638 break;
1639 case 4:
1640 term.mode &= ~MODE_PRINT;
1641 break;
1642 case 5:
1643 term.mode |= MODE_PRINT;
1644 break;
1646 break;
1647 case 'c': /* DA -- Device Attributes */
1648 if (csiescseq.arg[0] == 0)
1649 ttywrite(vtiden, strlen(vtiden), 0);
1650 break;
1651 case 'C': /* CUF -- Cursor <n> Forward */
1652 case 'a': /* HPR -- Cursor <n> Forward */
1653 DEFAULT(csiescseq.arg[0], 1);
1654 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1655 break;
1656 case 'D': /* CUB -- Cursor <n> Backward */
1657 DEFAULT(csiescseq.arg[0], 1);
1658 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1659 break;
1660 case 'E': /* CNL -- Cursor <n> Down and first col */
1661 DEFAULT(csiescseq.arg[0], 1);
1662 tmoveto(0, term.c.y+csiescseq.arg[0]);
1663 break;
1664 case 'F': /* CPL -- Cursor <n> Up and first col */
1665 DEFAULT(csiescseq.arg[0], 1);
1666 tmoveto(0, term.c.y-csiescseq.arg[0]);
1667 break;
1668 case 'g': /* TBC -- Tabulation clear */
1669 switch (csiescseq.arg[0]) {
1670 case 0: /* clear current tab stop */
1671 term.tabs[term.c.x] = 0;
1672 break;
1673 case 3: /* clear all the tabs */
1674 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1675 break;
1676 default:
1677 goto unknown;
1679 break;
1680 case 'G': /* CHA -- Move to <col> */
1681 case '`': /* HPA */
1682 DEFAULT(csiescseq.arg[0], 1);
1683 tmoveto(csiescseq.arg[0]-1, term.c.y);
1684 break;
1685 case 'H': /* CUP -- Move to <row> <col> */
1686 case 'f': /* HVP */
1687 DEFAULT(csiescseq.arg[0], 1);
1688 DEFAULT(csiescseq.arg[1], 1);
1689 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1690 break;
1691 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1692 DEFAULT(csiescseq.arg[0], 1);
1693 tputtab(csiescseq.arg[0]);
1694 break;
1695 case 'J': /* ED -- Clear screen */
1696 switch (csiescseq.arg[0]) {
1697 case 0: /* below */
1698 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1699 if (term.c.y < term.row-1) {
1700 tclearregion(0, term.c.y+1, term.col-1,
1701 term.row-1);
1703 break;
1704 case 1: /* above */
1705 if (term.c.y > 1)
1706 tclearregion(0, 0, term.col-1, term.c.y-1);
1707 tclearregion(0, term.c.y, term.c.x, term.c.y);
1708 break;
1709 case 2: /* all */
1710 tclearregion(0, 0, term.col-1, term.row-1);
1711 break;
1712 default:
1713 goto unknown;
1715 break;
1716 case 'K': /* EL -- Clear line */
1717 switch (csiescseq.arg[0]) {
1718 case 0: /* right */
1719 tclearregion(term.c.x, term.c.y, term.col-1,
1720 term.c.y);
1721 break;
1722 case 1: /* left */
1723 tclearregion(0, term.c.y, term.c.x, term.c.y);
1724 break;
1725 case 2: /* all */
1726 tclearregion(0, term.c.y, term.col-1, term.c.y);
1727 break;
1729 break;
1730 case 'S': /* SU -- Scroll <n> line up */
1731 DEFAULT(csiescseq.arg[0], 1);
1732 tscrollup(term.top, csiescseq.arg[0]);
1733 break;
1734 case 'T': /* SD -- Scroll <n> line down */
1735 DEFAULT(csiescseq.arg[0], 1);
1736 tscrolldown(term.top, csiescseq.arg[0]);
1737 break;
1738 case 'L': /* IL -- Insert <n> blank lines */
1739 DEFAULT(csiescseq.arg[0], 1);
1740 tinsertblankline(csiescseq.arg[0]);
1741 break;
1742 case 'l': /* RM -- Reset Mode */
1743 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1744 break;
1745 case 'M': /* DL -- Delete <n> lines */
1746 DEFAULT(csiescseq.arg[0], 1);
1747 tdeleteline(csiescseq.arg[0]);
1748 break;
1749 case 'X': /* ECH -- Erase <n> char */
1750 DEFAULT(csiescseq.arg[0], 1);
1751 tclearregion(term.c.x, term.c.y,
1752 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1753 break;
1754 case 'P': /* DCH -- Delete <n> char */
1755 DEFAULT(csiescseq.arg[0], 1);
1756 tdeletechar(csiescseq.arg[0]);
1757 break;
1758 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1759 DEFAULT(csiescseq.arg[0], 1);
1760 tputtab(-csiescseq.arg[0]);
1761 break;
1762 case 'd': /* VPA -- Move to <row> */
1763 DEFAULT(csiescseq.arg[0], 1);
1764 tmoveato(term.c.x, csiescseq.arg[0]-1);
1765 break;
1766 case 'h': /* SM -- Set terminal mode */
1767 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1768 break;
1769 case 'm': /* SGR -- Terminal attribute (color) */
1770 tsetattr(csiescseq.arg, csiescseq.narg);
1771 break;
1772 case 'n': /* DSR – Device Status Report (cursor position) */
1773 if (csiescseq.arg[0] == 6) {
1774 len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1775 term.c.y+1, term.c.x+1);
1776 ttywrite(buf, len, 0);
1778 break;
1779 case 'r': /* DECSTBM -- Set Scrolling Region */
1780 if (csiescseq.priv) {
1781 goto unknown;
1782 } else {
1783 DEFAULT(csiescseq.arg[0], 1);
1784 DEFAULT(csiescseq.arg[1], term.row);
1785 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1786 tmoveato(0, 0);
1788 break;
1789 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1790 tcursor(CURSOR_SAVE);
1791 break;
1792 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1793 tcursor(CURSOR_LOAD);
1794 break;
1795 case ' ':
1796 switch (csiescseq.mode[1]) {
1797 case 'q': /* DECSCUSR -- Set Cursor Style */
1798 if (xsetcursor(csiescseq.arg[0]))
1799 goto unknown;
1800 break;
1801 default:
1802 goto unknown;
1804 break;
1808 void
1809 csidump(void)
1811 int i;
1812 uint c;
1814 fprintf(stderr, "ESC[");
1815 for (i = 0; i < csiescseq.len; i++) {
1816 c = csiescseq.buf[i] & 0xff;
1817 if (isprint(c)) {
1818 putc(c, stderr);
1819 } else if (c == '\n') {
1820 fprintf(stderr, "(\\n)");
1821 } else if (c == '\r') {
1822 fprintf(stderr, "(\\r)");
1823 } else if (c == 0x1b) {
1824 fprintf(stderr, "(\\e)");
1825 } else {
1826 fprintf(stderr, "(%02x)", c);
1829 putc('\n', stderr);
1832 void
1833 csireset(void)
1835 memset(&csiescseq, 0, sizeof(csiescseq));
1838 void
1839 strhandle(void)
1841 char *p = NULL;
1842 int j, narg, par;
1844 term.esc &= ~(ESC_STR_END|ESC_STR);
1845 strparse();
1846 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1848 switch (strescseq.type) {
1849 case ']': /* OSC -- Operating System Command */
1850 switch (par) {
1851 case 0:
1852 case 1:
1853 case 2:
1854 if (narg > 1)
1855 xsettitle(strescseq.args[1]);
1856 return;
1857 case 52:
1858 if (narg > 2) {
1859 char *dec;
1861 dec = base64dec(strescseq.args[2]);
1862 if (dec) {
1863 xsetsel(dec);
1864 xclipcopy();
1865 } else {
1866 fprintf(stderr, "erresc: invalid base64\n");
1869 return;
1870 case 4: /* color set */
1871 if (narg < 3)
1872 break;
1873 p = strescseq.args[2];
1874 /* FALLTHROUGH */
1875 case 104: /* color reset, here p = NULL */
1876 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1877 if (xsetcolorname(j, p)) {
1878 fprintf(stderr, "erresc: invalid color %s\n", p);
1879 } else {
1881 * TODO if defaultbg color is changed, borders
1882 * are dirty
1884 redraw();
1886 return;
1888 break;
1889 case 'k': /* old title set compatibility */
1890 xsettitle(strescseq.args[0]);
1891 return;
1892 case 'P': /* DCS -- Device Control String */
1893 term.mode |= ESC_DCS;
1894 case '_': /* APC -- Application Program Command */
1895 case '^': /* PM -- Privacy Message */
1896 return;
1899 fprintf(stderr, "erresc: unknown str ");
1900 strdump();
1903 void
1904 strparse(void)
1906 int c;
1907 char *p = strescseq.buf;
1909 strescseq.narg = 0;
1910 strescseq.buf[strescseq.len] = '\0';
1912 if (*p == '\0')
1913 return;
1915 while (strescseq.narg < STR_ARG_SIZ) {
1916 strescseq.args[strescseq.narg++] = p;
1917 while ((c = *p) != ';' && c != '\0')
1918 ++p;
1919 if (c == '\0')
1920 return;
1921 *p++ = '\0';
1925 void
1926 strdump(void)
1928 int i;
1929 uint c;
1931 fprintf(stderr, "ESC%c", strescseq.type);
1932 for (i = 0; i < strescseq.len; i++) {
1933 c = strescseq.buf[i] & 0xff;
1934 if (c == '\0') {
1935 putc('\n', stderr);
1936 return;
1937 } else if (isprint(c)) {
1938 putc(c, stderr);
1939 } else if (c == '\n') {
1940 fprintf(stderr, "(\\n)");
1941 } else if (c == '\r') {
1942 fprintf(stderr, "(\\r)");
1943 } else if (c == 0x1b) {
1944 fprintf(stderr, "(\\e)");
1945 } else {
1946 fprintf(stderr, "(%02x)", c);
1949 fprintf(stderr, "ESC\\\n");
1952 void
1953 strreset(void)
1955 memset(&strescseq, 0, sizeof(strescseq));
1958 void
1959 sendbreak(const Arg *arg)
1961 if (tcsendbreak(cmdfd, 0))
1962 perror("Error sending break");
1965 void
1966 tprinter(char *s, size_t len)
1968 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1969 perror("Error writing to output file");
1970 close(iofd);
1971 iofd = -1;
1975 void
1976 iso14755(const Arg *arg)
1978 FILE *p;
1979 char *us, *e, codepoint[9], uc[UTF_SIZ];
1980 unsigned long utf32;
1982 if (!(p = popen(ISO14755CMD, "r")))
1983 return;
1985 us = fgets(codepoint, sizeof(codepoint), p);
1986 pclose(p);
1988 if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
1989 return;
1990 if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
1991 (*e != '\n' && *e != '\0'))
1992 return;
1994 ttywrite(uc, utf8encode(utf32, uc), 1);
1997 void
1998 toggleprinter(const Arg *arg)
2000 term.mode ^= MODE_PRINT;
2003 void
2004 printscreen(const Arg *arg)
2006 tdump();
2009 void
2010 printsel(const Arg *arg)
2012 tdumpsel();
2015 void
2016 tdumpsel(void)
2018 char *ptr;
2020 if ((ptr = getsel())) {
2021 tprinter(ptr, strlen(ptr));
2022 free(ptr);
2026 void
2027 tdumpline(int n)
2029 char buf[UTF_SIZ];
2030 Glyph *bp, *end;
2032 bp = &term.line[n][0];
2033 end = &bp[MIN(tlinelen(n), term.col) - 1];
2034 if (bp != end || bp->u != ' ') {
2035 for ( ;bp <= end; ++bp)
2036 tprinter(buf, utf8encode(bp->u, buf));
2038 tprinter("\n", 1);
2041 void
2042 tdump(void)
2044 int i;
2046 for (i = 0; i < term.row; ++i)
2047 tdumpline(i);
2050 void
2051 tputtab(int n)
2053 uint x = term.c.x;
2055 if (n > 0) {
2056 while (x < term.col && n--)
2057 for (++x; x < term.col && !term.tabs[x]; ++x)
2058 /* nothing */ ;
2059 } else if (n < 0) {
2060 while (x > 0 && n++)
2061 for (--x; x > 0 && !term.tabs[x]; --x)
2062 /* nothing */ ;
2064 term.c.x = LIMIT(x, 0, term.col-1);
2067 void
2068 tdefutf8(char ascii)
2070 if (ascii == 'G')
2071 term.mode |= MODE_UTF8;
2072 else if (ascii == '@')
2073 term.mode &= ~MODE_UTF8;
2076 void
2077 tdeftran(char ascii)
2079 static char cs[] = "0B";
2080 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2081 char *p;
2083 if ((p = strchr(cs, ascii)) == NULL) {
2084 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2085 } else {
2086 term.trantbl[term.icharset] = vcs[p - cs];
2090 void
2091 tdectest(char c)
2093 int x, y;
2095 if (c == '8') { /* DEC screen alignment test. */
2096 for (x = 0; x < term.col; ++x) {
2097 for (y = 0; y < term.row; ++y)
2098 tsetchar('E', &term.c.attr, x, y);
2103 void
2104 tstrsequence(uchar c)
2106 strreset();
2108 switch (c) {
2109 case 0x90: /* DCS -- Device Control String */
2110 c = 'P';
2111 term.esc |= ESC_DCS;
2112 break;
2113 case 0x9f: /* APC -- Application Program Command */
2114 c = '_';
2115 break;
2116 case 0x9e: /* PM -- Privacy Message */
2117 c = '^';
2118 break;
2119 case 0x9d: /* OSC -- Operating System Command */
2120 c = ']';
2121 break;
2123 strescseq.type = c;
2124 term.esc |= ESC_STR;
2127 void
2128 tcontrolcode(uchar ascii)
2130 switch (ascii) {
2131 case '\t': /* HT */
2132 tputtab(1);
2133 return;
2134 case '\b': /* BS */
2135 tmoveto(term.c.x-1, term.c.y);
2136 return;
2137 case '\r': /* CR */
2138 tmoveto(0, term.c.y);
2139 return;
2140 case '\f': /* LF */
2141 case '\v': /* VT */
2142 case '\n': /* LF */
2143 /* go to first col if the mode is set */
2144 tnewline(IS_SET(MODE_CRLF));
2145 return;
2146 case '\a': /* BEL */
2147 if (term.esc & ESC_STR_END) {
2148 /* backwards compatibility to xterm */
2149 strhandle();
2150 } else {
2151 xbell();
2153 break;
2154 case '\033': /* ESC */
2155 csireset();
2156 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2157 term.esc |= ESC_START;
2158 return;
2159 case '\016': /* SO (LS1 -- Locking shift 1) */
2160 case '\017': /* SI (LS0 -- Locking shift 0) */
2161 term.charset = 1 - (ascii - '\016');
2162 return;
2163 case '\032': /* SUB */
2164 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2165 case '\030': /* CAN */
2166 csireset();
2167 break;
2168 case '\005': /* ENQ (IGNORED) */
2169 case '\000': /* NUL (IGNORED) */
2170 case '\021': /* XON (IGNORED) */
2171 case '\023': /* XOFF (IGNORED) */
2172 case 0177: /* DEL (IGNORED) */
2173 return;
2174 case 0x80: /* TODO: PAD */
2175 case 0x81: /* TODO: HOP */
2176 case 0x82: /* TODO: BPH */
2177 case 0x83: /* TODO: NBH */
2178 case 0x84: /* TODO: IND */
2179 break;
2180 case 0x85: /* NEL -- Next line */
2181 tnewline(1); /* always go to first col */
2182 break;
2183 case 0x86: /* TODO: SSA */
2184 case 0x87: /* TODO: ESA */
2185 break;
2186 case 0x88: /* HTS -- Horizontal tab stop */
2187 term.tabs[term.c.x] = 1;
2188 break;
2189 case 0x89: /* TODO: HTJ */
2190 case 0x8a: /* TODO: VTS */
2191 case 0x8b: /* TODO: PLD */
2192 case 0x8c: /* TODO: PLU */
2193 case 0x8d: /* TODO: RI */
2194 case 0x8e: /* TODO: SS2 */
2195 case 0x8f: /* TODO: SS3 */
2196 case 0x91: /* TODO: PU1 */
2197 case 0x92: /* TODO: PU2 */
2198 case 0x93: /* TODO: STS */
2199 case 0x94: /* TODO: CCH */
2200 case 0x95: /* TODO: MW */
2201 case 0x96: /* TODO: SPA */
2202 case 0x97: /* TODO: EPA */
2203 case 0x98: /* TODO: SOS */
2204 case 0x99: /* TODO: SGCI */
2205 break;
2206 case 0x9a: /* DECID -- Identify Terminal */
2207 ttywrite(vtiden, strlen(vtiden), 0);
2208 break;
2209 case 0x9b: /* TODO: CSI */
2210 case 0x9c: /* TODO: ST */
2211 break;
2212 case 0x90: /* DCS -- Device Control String */
2213 case 0x9d: /* OSC -- Operating System Command */
2214 case 0x9e: /* PM -- Privacy Message */
2215 case 0x9f: /* APC -- Application Program Command */
2216 tstrsequence(ascii);
2217 return;
2219 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2220 term.esc &= ~(ESC_STR_END|ESC_STR);
2224 * returns 1 when the sequence is finished and it hasn't to read
2225 * more characters for this sequence, otherwise 0
2228 eschandle(uchar ascii)
2230 switch (ascii) {
2231 case '[':
2232 term.esc |= ESC_CSI;
2233 return 0;
2234 case '#':
2235 term.esc |= ESC_TEST;
2236 return 0;
2237 case '%':
2238 term.esc |= ESC_UTF8;
2239 return 0;
2240 case 'P': /* DCS -- Device Control String */
2241 case '_': /* APC -- Application Program Command */
2242 case '^': /* PM -- Privacy Message */
2243 case ']': /* OSC -- Operating System Command */
2244 case 'k': /* old title set compatibility */
2245 tstrsequence(ascii);
2246 return 0;
2247 case 'n': /* LS2 -- Locking shift 2 */
2248 case 'o': /* LS3 -- Locking shift 3 */
2249 term.charset = 2 + (ascii - 'n');
2250 break;
2251 case '(': /* GZD4 -- set primary charset G0 */
2252 case ')': /* G1D4 -- set secondary charset G1 */
2253 case '*': /* G2D4 -- set tertiary charset G2 */
2254 case '+': /* G3D4 -- set quaternary charset G3 */
2255 term.icharset = ascii - '(';
2256 term.esc |= ESC_ALTCHARSET;
2257 return 0;
2258 case 'D': /* IND -- Linefeed */
2259 if (term.c.y == term.bot) {
2260 tscrollup(term.top, 1);
2261 } else {
2262 tmoveto(term.c.x, term.c.y+1);
2264 break;
2265 case 'E': /* NEL -- Next line */
2266 tnewline(1); /* always go to first col */
2267 break;
2268 case 'H': /* HTS -- Horizontal tab stop */
2269 term.tabs[term.c.x] = 1;
2270 break;
2271 case 'M': /* RI -- Reverse index */
2272 if (term.c.y == term.top) {
2273 tscrolldown(term.top, 1);
2274 } else {
2275 tmoveto(term.c.x, term.c.y-1);
2277 break;
2278 case 'Z': /* DECID -- Identify Terminal */
2279 ttywrite(vtiden, strlen(vtiden), 0);
2280 break;
2281 case 'c': /* RIS -- Reset to inital state */
2282 treset();
2283 resettitle();
2284 xloadcols();
2285 break;
2286 case '=': /* DECPAM -- Application keypad */
2287 xsetmode(1, MODE_APPKEYPAD);
2288 break;
2289 case '>': /* DECPNM -- Normal keypad */
2290 xsetmode(0, MODE_APPKEYPAD);
2291 break;
2292 case '7': /* DECSC -- Save Cursor */
2293 tcursor(CURSOR_SAVE);
2294 break;
2295 case '8': /* DECRC -- Restore Cursor */
2296 tcursor(CURSOR_LOAD);
2297 break;
2298 case '\\': /* ST -- String Terminator */
2299 if (term.esc & ESC_STR_END)
2300 strhandle();
2301 break;
2302 default:
2303 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2304 (uchar) ascii, isprint(ascii)? ascii:'.');
2305 break;
2307 return 1;
2310 void
2311 tputc(Rune u)
2313 char c[UTF_SIZ];
2314 int control;
2315 int width, len;
2316 Glyph *gp;
2318 control = ISCONTROL(u);
2319 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2320 c[0] = u;
2321 width = len = 1;
2322 } else {
2323 len = utf8encode(u, c);
2324 if (!control && (width = wcwidth(u)) == -1) {
2325 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2326 width = 1;
2330 if (IS_SET(MODE_PRINT))
2331 tprinter(c, len);
2334 * STR sequence must be checked before anything else
2335 * because it uses all following characters until it
2336 * receives a ESC, a SUB, a ST or any other C1 control
2337 * character.
2339 if (term.esc & ESC_STR) {
2340 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2341 ISCONTROLC1(u)) {
2342 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2343 if (IS_SET(MODE_SIXEL)) {
2344 /* TODO: render sixel */;
2345 term.mode &= ~MODE_SIXEL;
2346 return;
2348 term.esc |= ESC_STR_END;
2349 goto check_control_code;
2353 if (IS_SET(MODE_SIXEL)) {
2354 /* TODO: implement sixel mode */
2355 return;
2357 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2358 term.mode |= MODE_SIXEL;
2360 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2362 * Here is a bug in terminals. If the user never sends
2363 * some code to stop the str or esc command, then st
2364 * will stop responding. But this is better than
2365 * silently failing with unknown characters. At least
2366 * then users will report back.
2368 * In the case users ever get fixed, here is the code:
2371 * term.esc = 0;
2372 * strhandle();
2374 return;
2377 memmove(&strescseq.buf[strescseq.len], c, len);
2378 strescseq.len += len;
2379 return;
2382 check_control_code:
2384 * Actions of control codes must be performed as soon they arrive
2385 * because they can be embedded inside a control sequence, and
2386 * they must not cause conflicts with sequences.
2388 if (control) {
2389 tcontrolcode(u);
2391 * control codes are not shown ever
2393 return;
2394 } else if (term.esc & ESC_START) {
2395 if (term.esc & ESC_CSI) {
2396 csiescseq.buf[csiescseq.len++] = u;
2397 if (BETWEEN(u, 0x40, 0x7E)
2398 || csiescseq.len >= \
2399 sizeof(csiescseq.buf)-1) {
2400 term.esc = 0;
2401 csiparse();
2402 csihandle();
2404 return;
2405 } else if (term.esc & ESC_UTF8) {
2406 tdefutf8(u);
2407 } else if (term.esc & ESC_ALTCHARSET) {
2408 tdeftran(u);
2409 } else if (term.esc & ESC_TEST) {
2410 tdectest(u);
2411 } else {
2412 if (!eschandle(u))
2413 return;
2414 /* sequence already finished */
2416 term.esc = 0;
2418 * All characters which form part of a sequence are not
2419 * printed
2421 return;
2423 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2424 selclear();
2426 gp = &term.line[term.c.y][term.c.x];
2427 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2428 gp->mode |= ATTR_WRAP;
2429 tnewline(1);
2430 gp = &term.line[term.c.y][term.c.x];
2433 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2434 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2436 if (term.c.x+width > term.col) {
2437 tnewline(1);
2438 gp = &term.line[term.c.y][term.c.x];
2441 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2443 if (width == 2) {
2444 gp->mode |= ATTR_WIDE;
2445 if (term.c.x+1 < term.col) {
2446 gp[1].u = '\0';
2447 gp[1].mode = ATTR_WDUMMY;
2450 if (term.c.x+width < term.col) {
2451 tmoveto(term.c.x+width, term.c.y);
2452 } else {
2453 term.c.state |= CURSOR_WRAPNEXT;
2458 twrite(const char *buf, int buflen, int show_ctrl)
2460 int charsize;
2461 Rune u;
2462 int n;
2464 for (n = 0; n < buflen; n += charsize) {
2465 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2466 /* process a complete utf8 char */
2467 charsize = utf8decode(buf + n, &u, buflen - n);
2468 if (charsize == 0)
2469 break;
2470 } else {
2471 u = buf[n] & 0xFF;
2472 charsize = 1;
2474 if (show_ctrl && ISCONTROL(u)) {
2475 if (u & 0x80) {
2476 u &= 0x7f;
2477 tputc('^');
2478 tputc('[');
2479 } else if (u != '\n' && u != '\r' && u != '\t') {
2480 u ^= 0x40;
2481 tputc('^');
2484 tputc(u);
2486 return n;
2489 void
2490 tresize(int col, int row)
2492 int i;
2493 int minrow = MIN(row, term.row);
2494 int mincol = MIN(col, term.col);
2495 int *bp;
2496 TCursor c;
2498 if (col < 1 || row < 1) {
2499 fprintf(stderr,
2500 "tresize: error resizing to %dx%d\n", col, row);
2501 return;
2505 * slide screen to keep cursor where we expect it -
2506 * tscrollup would work here, but we can optimize to
2507 * memmove because we're freeing the earlier lines
2509 for (i = 0; i <= term.c.y - row; i++) {
2510 free(term.line[i]);
2511 free(term.alt[i]);
2513 /* ensure that both src and dst are not NULL */
2514 if (i > 0) {
2515 memmove(term.line, term.line + i, row * sizeof(Line));
2516 memmove(term.alt, term.alt + i, row * sizeof(Line));
2518 for (i += row; i < term.row; i++) {
2519 free(term.line[i]);
2520 free(term.alt[i]);
2523 /* resize to new height */
2524 term.line = xrealloc(term.line, row * sizeof(Line));
2525 term.alt = xrealloc(term.alt, row * sizeof(Line));
2526 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2527 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2529 /* resize each row to new width, zero-pad if needed */
2530 for (i = 0; i < minrow; i++) {
2531 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2532 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2535 /* allocate any new rows */
2536 for (/* i = minrow */; i < row; i++) {
2537 term.line[i] = xmalloc(col * sizeof(Glyph));
2538 term.alt[i] = xmalloc(col * sizeof(Glyph));
2540 if (col > term.col) {
2541 bp = term.tabs + term.col;
2543 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2544 while (--bp > term.tabs && !*bp)
2545 /* nothing */ ;
2546 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2547 *bp = 1;
2549 /* update terminal size */
2550 term.col = col;
2551 term.row = row;
2552 /* reset scrolling region */
2553 tsetscroll(0, row-1);
2554 /* make use of the LIMIT in tmoveto */
2555 tmoveto(term.c.x, term.c.y);
2556 /* Clearing both screens (it makes dirty all lines) */
2557 c = term.c;
2558 for (i = 0; i < 2; i++) {
2559 if (mincol < col && 0 < minrow) {
2560 tclearregion(mincol, 0, col - 1, minrow - 1);
2562 if (0 < col && minrow < row) {
2563 tclearregion(0, minrow, col - 1, row - 1);
2565 tswapscreen();
2566 tcursor(CURSOR_LOAD);
2568 term.c = c;
2571 void
2572 resettitle(void)
2574 xsettitle(NULL);
2577 void
2578 drawregion(int x1, int y1, int x2, int y2)
2580 int y;
2581 for (y = y1; y < y2; y++) {
2582 if (!term.dirty[y])
2583 continue;
2585 term.dirty[y] = 0;
2586 xdrawline(term.line[y], x1, y, x2);
2590 void
2591 draw(void)
2593 int cx = term.c.x;
2595 if (!xstartdraw())
2596 return;
2598 /* adjust cursor position */
2599 LIMIT(term.ocx, 0, term.col-1);
2600 LIMIT(term.ocy, 0, term.row-1);
2601 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2602 term.ocx--;
2603 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2604 cx--;
2606 drawregion(0, 0, term.col, term.row);
2607 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2608 term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2609 term.ocx = cx, term.ocy = term.c.y;
2610 xfinishdraw();
2613 void
2614 redraw(void)
2616 tfulldirt();
2617 draw();