1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
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
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 MODE_ALTSCREEN
= 1 << 2,
57 enum cursor_movement
{
81 ESC_STR
= 4, /* OSC, PM, APC */
83 ESC_STR_END
= 16, /* a final string was encountered */
84 ESC_TEST
= 32, /* Enter in test mode */
90 Glyph attr
; /* current char attributes */
101 * Selection variables:
102 * nb – normalized coordinates of the beginning of the selection
103 * ne – normalized coordinates of the end of the selection
104 * ob – original coordinates of the beginning of the selection
105 * oe – original coordinates of the end of the selection
114 /* Internal representation of the screen */
116 int row
; /* nb row */
117 int col
; /* nb col */
118 Line
*line
; /* screen */
119 Line
*alt
; /* alternate screen */
120 int *dirty
; /* dirtyness of lines */
121 TCursor c
; /* cursor */
122 int ocx
; /* old cursor col */
123 int ocy
; /* old cursor row */
124 int top
; /* top scroll limit */
125 int bot
; /* bottom scroll limit */
126 int mode
; /* terminal mode flags */
127 int esc
; /* escape state flags */
128 char trantbl
[4]; /* charset table translation */
129 int charset
; /* current charset */
130 int icharset
; /* selected charset for sequence */
134 /* CSI Escape sequence structs */
135 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
137 char buf
[ESC_BUF_SIZ
]; /* raw string */
138 size_t len
; /* raw string length */
140 int arg
[ESC_ARG_SIZ
];
141 int narg
; /* nb of args */
145 /* STR Escape sequence structs */
146 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
148 char type
; /* ESC type ... */
149 char *buf
; /* allocated raw string */
150 size_t siz
; /* allocation size */
151 size_t len
; /* raw string length */
152 char *args
[STR_ARG_SIZ
];
153 int narg
; /* nb of args */
156 static void execsh(char *, char **);
157 static void stty(char **);
158 static void sigchld(int);
159 static void ttywriteraw(const char *, size_t);
161 static void csidump(void);
162 static void csihandle(void);
163 static void csiparse(void);
164 static void csireset(void);
165 static int eschandle(uchar
);
166 static void strdump(void);
167 static void strhandle(void);
168 static void strparse(void);
169 static void strreset(void);
171 static void tprinter(char *, size_t);
172 static void tdumpsel(void);
173 static void tdumpline(int);
174 static void tdump(void);
175 static void tclearregion(int, int, int, int);
176 static void tcursor(int);
177 static void tdeletechar(int);
178 static void tdeleteline(int);
179 static void tinsertblank(int);
180 static void tinsertblankline(int);
181 static int tlinelen(int);
182 static void tmoveto(int, int);
183 static void tmoveato(int, int);
184 static void tnewline(int);
185 static void tputtab(int);
186 static void tputc(Rune
);
187 static void treset(void);
188 static void tscrollup(int, int);
189 static void tscrolldown(int, int);
190 static void tsetattr(int *, int);
191 static void tsetchar(Rune
, Glyph
*, int, int);
192 static void tsetdirt(int, int);
193 static void tsetscroll(int, int);
194 static void tswapscreen(void);
195 static void tsetmode(int, int, int *, int);
196 static int twrite(const char *, int, int);
197 static void tfulldirt(void);
198 static void tcontrolcode(uchar
);
199 static void tdectest(char );
200 static void tdefutf8(char);
201 static int32_t tdefcolor(int *, int *, int);
202 static void tdeftran(char);
203 static void tstrsequence(uchar
);
205 static void drawregion(int, int, int, int);
207 static void selnormalize(void);
208 static void selscroll(int, int);
209 static void selsnap(int *, int *, int);
211 static size_t utf8decode(const char *, Rune
*, size_t);
212 static Rune
utf8decodebyte(char, size_t *);
213 static char utf8encodebyte(Rune
, size_t);
214 static size_t utf8validate(Rune
*, size_t);
216 static char *base64dec(const char *);
217 static char base64dec_getc(const char **);
219 static ssize_t
xwrite(int, const char *, size_t);
223 static Selection sel
;
224 static CSIEscape csiescseq
;
225 static STREscape strescseq
;
230 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
231 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
232 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
233 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
236 xwrite(int fd
, const char *s
, size_t len
)
242 r
= write(fd
, s
, len
);
257 if (!(p
= malloc(len
)))
258 die("malloc: %s\n", strerror(errno
));
264 xrealloc(void *p
, size_t len
)
266 if ((p
= realloc(p
, len
)) == NULL
)
267 die("realloc: %s\n", strerror(errno
));
275 if ((s
= strdup(s
)) == NULL
)
276 die("strdup: %s\n", strerror(errno
));
282 utf8decode(const char *c
, Rune
*u
, size_t clen
)
284 size_t i
, j
, len
, type
;
290 udecoded
= utf8decodebyte(c
[0], &len
);
291 if (!BETWEEN(len
, 1, UTF_SIZ
))
293 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
294 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
301 utf8validate(u
, len
);
307 utf8decodebyte(char c
, size_t *i
)
309 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
310 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
311 return (uchar
)c
& ~utfmask
[*i
];
317 utf8encode(Rune u
, char *c
)
321 len
= utf8validate(&u
, 0);
325 for (i
= len
- 1; i
!= 0; --i
) {
326 c
[i
] = utf8encodebyte(u
, 0);
329 c
[0] = utf8encodebyte(u
, len
);
335 utf8encodebyte(Rune u
, size_t i
)
337 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
341 utf8validate(Rune
*u
, size_t i
)
343 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
345 for (i
= 1; *u
> utfmax
[i
]; ++i
)
351 static const char base64_digits
[] = {
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
354 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
355 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
356 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
357 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
361 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
362 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
367 base64dec_getc(const char **src
)
369 while (**src
&& !isprint(**src
))
371 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
375 base64dec(const char *src
)
377 size_t in_len
= strlen(src
);
381 in_len
+= 4 - (in_len
% 4);
382 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
384 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
385 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
387 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
389 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
390 if (a
== -1 || b
== -1)
393 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
396 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
399 *dst
++ = ((c
& 0x03) << 6) | d
;
418 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
421 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
428 selstart(int col
, int row
, int snap
)
431 sel
.mode
= SEL_EMPTY
;
432 sel
.type
= SEL_REGULAR
;
433 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
435 sel
.oe
.x
= sel
.ob
.x
= col
;
436 sel
.oe
.y
= sel
.ob
.y
= row
;
440 sel
.mode
= SEL_READY
;
441 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
445 selextend(int col
, int row
, int type
, int done
)
447 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
449 if (sel
.mode
== SEL_IDLE
)
451 if (done
&& sel
.mode
== SEL_EMPTY
) {
467 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
468 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
470 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
478 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
479 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
480 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
482 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
483 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
485 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
486 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
488 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
489 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
491 /* expand selection over line breaks */
492 if (sel
.type
== SEL_RECTANGULAR
)
494 i
= tlinelen(sel
.nb
.y
);
497 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
498 sel
.ne
.x
= term
.col
- 1;
502 selected(int x
, int y
)
504 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
505 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
508 if (sel
.type
== SEL_RECTANGULAR
)
509 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
510 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
512 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
513 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
514 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
518 selsnap(int *x
, int *y
, int direction
)
520 int newx
, newy
, xt
, yt
;
521 int delim
, prevdelim
;
527 * Snap around if the word wraps around at the end or
528 * beginning of a line.
530 prevgp
= &term
.line
[*y
][*x
];
531 prevdelim
= ISDELIM(prevgp
->u
);
533 newx
= *x
+ direction
;
535 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
537 newx
= (newx
+ term
.col
) % term
.col
;
538 if (!BETWEEN(newy
, 0, term
.row
- 1))
544 yt
= newy
, xt
= newx
;
545 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
549 if (newx
>= tlinelen(newy
))
552 gp
= &term
.line
[newy
][newx
];
553 delim
= ISDELIM(gp
->u
);
554 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
555 || (delim
&& gp
->u
!= prevgp
->u
)))
566 * Snap around if the the previous line or the current one
567 * has set ATTR_WRAP at its end. Then the whole next or
568 * previous line will be selected.
570 *x
= (direction
< 0) ? 0 : term
.col
- 1;
572 for (; *y
> 0; *y
+= direction
) {
573 if (!(term
.line
[*y
-1][term
.col
-1].mode
578 } else if (direction
> 0) {
579 for (; *y
< term
.row
-1; *y
+= direction
) {
580 if (!(term
.line
[*y
][term
.col
-1].mode
594 int y
, bufsize
, lastx
, linelen
;
600 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
601 ptr
= str
= xmalloc(bufsize
);
603 /* append every set & selected glyph to the selection */
604 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
605 if ((linelen
= tlinelen(y
)) == 0) {
610 if (sel
.type
== SEL_RECTANGULAR
) {
611 gp
= &term
.line
[y
][sel
.nb
.x
];
614 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
615 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
617 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
618 while (last
>= gp
&& last
->u
== ' ')
621 for ( ; gp
<= last
; ++gp
) {
622 if (gp
->mode
& ATTR_WDUMMY
)
625 ptr
+= utf8encode(gp
->u
, ptr
);
629 * Copy and pasting of line endings is inconsistent
630 * in the inconsistent terminal and GUI world.
631 * The best solution seems like to produce '\n' when
632 * something is copied from st and convert '\n' to
633 * '\r', when something to be pasted is received by
635 * FIXME: Fix the computer world.
637 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) &&
638 (!(last
->mode
& ATTR_WRAP
) || sel
.type
== SEL_RECTANGULAR
))
652 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
656 die(const char *errstr
, ...)
660 va_start(ap
, errstr
);
661 vfprintf(stderr
, errstr
, ap
);
667 execsh(char *cmd
, char **args
)
669 char *sh
, *prog
, *arg
;
670 const struct passwd
*pw
;
673 if ((pw
= getpwuid(getuid())) == NULL
) {
675 die("getpwuid: %s\n", strerror(errno
));
677 die("who are you?\n");
680 if ((sh
= getenv("SHELL")) == NULL
)
681 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
688 arg
= utmp
? utmp
: sh
;
696 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
701 setenv("LOGNAME", pw
->pw_name
, 1);
702 setenv("USER", pw
->pw_name
, 1);
703 setenv("SHELL", sh
, 1);
704 setenv("HOME", pw
->pw_dir
, 1);
705 setenv("TERM", termname
, 1);
707 signal(SIGCHLD
, SIG_DFL
);
708 signal(SIGHUP
, SIG_DFL
);
709 signal(SIGINT
, SIG_DFL
);
710 signal(SIGQUIT
, SIG_DFL
);
711 signal(SIGTERM
, SIG_DFL
);
712 signal(SIGALRM
, SIG_DFL
);
724 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
725 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
730 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
731 die("child exited with status %d\n", WEXITSTATUS(stat
));
732 else if (WIFSIGNALED(stat
))
733 die("child terminated due to signal %d\n", WTERMSIG(stat
));
740 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
743 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
744 die("incorrect stty parameters\n");
745 memcpy(cmd
, stty_args
, n
);
747 siz
= sizeof(cmd
) - n
;
748 for (p
= args
; p
&& (s
= *p
); ++p
) {
749 if ((n
= strlen(s
)) > siz
-1)
750 die("stty parameter length too long\n");
757 if (system(cmd
) != 0)
758 perror("Couldn't call stty");
762 ttynew(char *line
, char *cmd
, char *out
, char **args
)
767 term
.mode
|= MODE_PRINT
;
768 iofd
= (!strcmp(out
, "-")) ?
769 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
771 fprintf(stderr
, "Error opening %s:%s\n",
772 out
, strerror(errno
));
777 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
778 die("open line '%s' failed: %s\n",
779 line
, strerror(errno
));
785 /* seems to work fine on linux, openbsd and freebsd */
786 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
787 die("openpty failed: %s\n", strerror(errno
));
789 switch (pid
= fork()) {
791 die("fork failed: %s\n", strerror(errno
));
795 setsid(); /* create a new process group */
799 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
800 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
804 if (pledge("stdio getpw proc exec", NULL
) == -1)
811 if (pledge("stdio rpath tty proc", NULL
) == -1)
816 signal(SIGCHLD
, sigchld
);
825 static char buf
[BUFSIZ
];
826 static int buflen
= 0;
829 /* append read bytes to unprocessed bytes */
830 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
836 die("couldn't read from shell: %s\n", strerror(errno
));
839 written
= twrite(buf
, buflen
, 0);
841 /* keep any incomplete UTF-8 byte sequence for the next call */
843 memmove(buf
, buf
+ written
, buflen
);
850 ttywrite(const char *s
, size_t n
, int may_echo
)
854 if (may_echo
&& IS_SET(MODE_ECHO
))
857 if (!IS_SET(MODE_CRLF
)) {
862 /* This is similar to how the kernel handles ONLCR for ttys */
866 ttywriteraw("\r\n", 2);
868 next
= memchr(s
, '\r', n
);
869 DEFAULT(next
, s
+ n
);
870 ttywriteraw(s
, next
- s
);
878 ttywriteraw(const char *s
, size_t n
)
885 * Remember that we are using a pty, which might be a modem line.
886 * Writing too much will clog the line. That's why we are doing this
888 * FIXME: Migrate the world to Plan 9.
896 /* Check if we can write. */
897 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
900 die("select failed: %s\n", strerror(errno
));
902 if (FD_ISSET(cmdfd
, &wfd
)) {
904 * Only write the bytes written by ttywrite() or the
905 * default of 256. This seems to be a reasonable value
906 * for a serial line. Bigger values might clog the I/O.
908 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
912 * We weren't able to write out everything.
913 * This means the buffer is getting full
921 /* All bytes have been written. */
925 if (FD_ISSET(cmdfd
, &rfd
))
931 die("write error on tty: %s\n", strerror(errno
));
935 ttyresize(int tw
, int th
)
943 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
944 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
950 /* Send SIGHUP to shell */
959 for (i
= 0; i
< term
.row
-1; i
++) {
960 for (j
= 0; j
< term
.col
-1; j
++) {
961 if (term
.line
[i
][j
].mode
& attr
)
970 tsetdirt(int top
, int bot
)
974 LIMIT(top
, 0, term
.row
-1);
975 LIMIT(bot
, 0, term
.row
-1);
977 for (i
= top
; i
<= bot
; i
++)
982 tsetdirtattr(int attr
)
986 for (i
= 0; i
< term
.row
-1; i
++) {
987 for (j
= 0; j
< term
.col
-1; j
++) {
988 if (term
.line
[i
][j
].mode
& attr
) {
999 tsetdirt(0, term
.row
-1);
1005 static TCursor c
[2];
1006 int alt
= IS_SET(MODE_ALTSCREEN
);
1008 if (mode
== CURSOR_SAVE
) {
1010 } else if (mode
== CURSOR_LOAD
) {
1012 tmoveto(c
[alt
].x
, c
[alt
].y
);
1021 term
.c
= (TCursor
){{
1025 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1027 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1028 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1031 term
.bot
= term
.row
- 1;
1032 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1033 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1036 for (i
= 0; i
< 2; i
++) {
1038 tcursor(CURSOR_SAVE
);
1039 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1045 tnew(int col
, int row
)
1047 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1055 Line
*tmp
= term
.line
;
1057 term
.line
= term
.alt
;
1059 term
.mode
^= MODE_ALTSCREEN
;
1064 tscrolldown(int orig
, int n
)
1069 LIMIT(n
, 0, term
.bot
-orig
+1);
1071 tsetdirt(orig
, term
.bot
-n
);
1072 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1074 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1075 temp
= term
.line
[i
];
1076 term
.line
[i
] = term
.line
[i
-n
];
1077 term
.line
[i
-n
] = temp
;
1084 tscrollup(int orig
, int n
)
1089 LIMIT(n
, 0, term
.bot
-orig
+1);
1091 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1092 tsetdirt(orig
+n
, term
.bot
);
1094 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1095 temp
= term
.line
[i
];
1096 term
.line
[i
] = term
.line
[i
+n
];
1097 term
.line
[i
+n
] = temp
;
1100 selscroll(orig
, -n
);
1104 selscroll(int orig
, int n
)
1109 if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
) != BETWEEN(sel
.ne
.y
, orig
, term
.bot
)) {
1111 } else if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
)) {
1114 if (sel
.ob
.y
< term
.top
|| sel
.ob
.y
> term
.bot
||
1115 sel
.oe
.y
< term
.top
|| sel
.oe
.y
> term
.bot
) {
1124 tnewline(int first_col
)
1128 if (y
== term
.bot
) {
1129 tscrollup(term
.top
, 1);
1133 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1139 char *p
= csiescseq
.buf
, *np
;
1148 csiescseq
.buf
[csiescseq
.len
] = '\0';
1149 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1151 v
= strtol(p
, &np
, 10);
1154 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1156 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1158 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1162 csiescseq
.mode
[0] = *p
++;
1163 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1166 /* for absolute user moves, when decom is set */
1168 tmoveato(int x
, int y
)
1170 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1174 tmoveto(int x
, int y
)
1178 if (term
.c
.state
& CURSOR_ORIGIN
) {
1183 maxy
= term
.row
- 1;
1185 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1186 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1187 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1191 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1193 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1194 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1195 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1196 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1197 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1198 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1199 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1200 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1201 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1205 * The table is proudly stolen from rxvt.
1207 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1208 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1209 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1211 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1212 if (x
+1 < term
.col
) {
1213 term
.line
[y
][x
+1].u
= ' ';
1214 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1216 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1217 term
.line
[y
][x
-1].u
= ' ';
1218 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1222 term
.line
[y
][x
] = *attr
;
1223 term
.line
[y
][x
].u
= u
;
1227 tclearregion(int x1
, int y1
, int x2
, int y2
)
1233 temp
= x1
, x1
= x2
, x2
= temp
;
1235 temp
= y1
, y1
= y2
, y2
= temp
;
1237 LIMIT(x1
, 0, term
.col
-1);
1238 LIMIT(x2
, 0, term
.col
-1);
1239 LIMIT(y1
, 0, term
.row
-1);
1240 LIMIT(y2
, 0, term
.row
-1);
1242 for (y
= y1
; y
<= y2
; y
++) {
1244 for (x
= x1
; x
<= x2
; x
++) {
1245 gp
= &term
.line
[y
][x
];
1248 gp
->fg
= term
.c
.attr
.fg
;
1249 gp
->bg
= term
.c
.attr
.bg
;
1262 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1266 size
= term
.col
- src
;
1267 line
= term
.line
[term
.c
.y
];
1269 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1270 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1279 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1283 size
= term
.col
- dst
;
1284 line
= term
.line
[term
.c
.y
];
1286 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1287 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1291 tinsertblankline(int n
)
1293 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1294 tscrolldown(term
.c
.y
, n
);
1300 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1301 tscrollup(term
.c
.y
, n
);
1305 tdefcolor(int *attr
, int *npar
, int l
)
1310 switch (attr
[*npar
+ 1]) {
1311 case 2: /* direct color in RGB space */
1312 if (*npar
+ 4 >= l
) {
1314 "erresc(38): Incorrect number of parameters (%d)\n",
1318 r
= attr
[*npar
+ 2];
1319 g
= attr
[*npar
+ 3];
1320 b
= attr
[*npar
+ 4];
1322 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1323 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1326 idx
= TRUECOLOR(r
, g
, b
);
1328 case 5: /* indexed color */
1329 if (*npar
+ 2 >= l
) {
1331 "erresc(38): Incorrect number of parameters (%d)\n",
1336 if (!BETWEEN(attr
[*npar
], 0, 255))
1337 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1341 case 0: /* implemented defined (only foreground) */
1342 case 1: /* transparent */
1343 case 3: /* direct color in CMY space */
1344 case 4: /* direct color in CMYK space */
1347 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1355 tsetattr(int *attr
, int l
)
1360 for (i
= 0; i
< l
; i
++) {
1363 term
.c
.attr
.mode
&= ~(
1372 term
.c
.attr
.fg
= defaultfg
;
1373 term
.c
.attr
.bg
= defaultbg
;
1376 term
.c
.attr
.mode
|= ATTR_BOLD
;
1379 term
.c
.attr
.mode
|= ATTR_FAINT
;
1382 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1385 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1387 case 5: /* slow blink */
1389 case 6: /* rapid blink */
1390 term
.c
.attr
.mode
|= ATTR_BLINK
;
1393 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1396 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1399 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1402 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1405 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1408 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1411 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1414 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1417 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1420 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1423 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1424 term
.c
.attr
.fg
= idx
;
1427 term
.c
.attr
.fg
= defaultfg
;
1430 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1431 term
.c
.attr
.bg
= idx
;
1434 term
.c
.attr
.bg
= defaultbg
;
1437 if (BETWEEN(attr
[i
], 30, 37)) {
1438 term
.c
.attr
.fg
= attr
[i
] - 30;
1439 } else if (BETWEEN(attr
[i
], 40, 47)) {
1440 term
.c
.attr
.bg
= attr
[i
] - 40;
1441 } else if (BETWEEN(attr
[i
], 90, 97)) {
1442 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1443 } else if (BETWEEN(attr
[i
], 100, 107)) {
1444 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1447 "erresc(default): gfx attr %d unknown\n",
1457 tsetscroll(int t
, int b
)
1461 LIMIT(t
, 0, term
.row
-1);
1462 LIMIT(b
, 0, term
.row
-1);
1473 tsetmode(int priv
, int set
, int *args
, int narg
)
1477 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1480 case 1: /* DECCKM -- Cursor key */
1481 xsetmode(set
, MODE_APPCURSOR
);
1483 case 5: /* DECSCNM -- Reverse video */
1484 xsetmode(set
, MODE_REVERSE
);
1486 case 6: /* DECOM -- Origin */
1487 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1490 case 7: /* DECAWM -- Auto wrap */
1491 MODBIT(term
.mode
, set
, MODE_WRAP
);
1493 case 0: /* Error (IGNORED) */
1494 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1495 case 3: /* DECCOLM -- Column (IGNORED) */
1496 case 4: /* DECSCLM -- Scroll (IGNORED) */
1497 case 8: /* DECARM -- Auto repeat (IGNORED) */
1498 case 18: /* DECPFF -- Printer feed (IGNORED) */
1499 case 19: /* DECPEX -- Printer extent (IGNORED) */
1500 case 42: /* DECNRCM -- National characters (IGNORED) */
1501 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1503 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1504 xsetmode(!set
, MODE_HIDE
);
1506 case 9: /* X10 mouse compatibility mode */
1507 xsetpointermotion(0);
1508 xsetmode(0, MODE_MOUSE
);
1509 xsetmode(set
, MODE_MOUSEX10
);
1511 case 1000: /* 1000: report button press */
1512 xsetpointermotion(0);
1513 xsetmode(0, MODE_MOUSE
);
1514 xsetmode(set
, MODE_MOUSEBTN
);
1516 case 1002: /* 1002: report motion on button press */
1517 xsetpointermotion(0);
1518 xsetmode(0, MODE_MOUSE
);
1519 xsetmode(set
, MODE_MOUSEMOTION
);
1521 case 1003: /* 1003: enable all mouse motions */
1522 xsetpointermotion(set
);
1523 xsetmode(0, MODE_MOUSE
);
1524 xsetmode(set
, MODE_MOUSEMANY
);
1526 case 1004: /* 1004: send focus events to tty */
1527 xsetmode(set
, MODE_FOCUS
);
1529 case 1006: /* 1006: extended reporting mode */
1530 xsetmode(set
, MODE_MOUSESGR
);
1533 xsetmode(set
, MODE_8BIT
);
1535 case 1049: /* swap screen & set/restore cursor as xterm */
1536 if (!allowaltscreen
)
1538 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1540 case 47: /* swap screen */
1542 if (!allowaltscreen
)
1544 alt
= IS_SET(MODE_ALTSCREEN
);
1546 tclearregion(0, 0, term
.col
-1,
1549 if (set
^ alt
) /* set is always 1 or 0 */
1555 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1557 case 2004: /* 2004: bracketed paste mode */
1558 xsetmode(set
, MODE_BRCKTPASTE
);
1560 /* Not implemented mouse modes. See comments there. */
1561 case 1001: /* mouse highlight mode; can hang the
1562 terminal by design when implemented. */
1563 case 1005: /* UTF-8 mouse mode; will confuse
1564 applications not supporting UTF-8
1566 case 1015: /* urxvt mangled mouse mode; incompatible
1567 and can be mistaken for other control
1572 "erresc: unknown private set/reset mode %d\n",
1578 case 0: /* Error (IGNORED) */
1581 xsetmode(set
, MODE_KBDLOCK
);
1583 case 4: /* IRM -- Insertion-replacement */
1584 MODBIT(term
.mode
, set
, MODE_INSERT
);
1586 case 12: /* SRM -- Send/Receive */
1587 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1589 case 20: /* LNM -- Linefeed/new line */
1590 MODBIT(term
.mode
, set
, MODE_CRLF
);
1594 "erresc: unknown set/reset mode %d\n",
1608 switch (csiescseq
.mode
[0]) {
1611 fprintf(stderr
, "erresc: unknown csi ");
1615 case '@': /* ICH -- Insert <n> blank char */
1616 DEFAULT(csiescseq
.arg
[0], 1);
1617 tinsertblank(csiescseq
.arg
[0]);
1619 case 'A': /* CUU -- Cursor <n> Up */
1620 DEFAULT(csiescseq
.arg
[0], 1);
1621 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
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]);
1628 case 'i': /* MC -- Media Copy */
1629 switch (csiescseq
.arg
[0]) {
1634 tdumpline(term
.c
.y
);
1640 term
.mode
&= ~MODE_PRINT
;
1643 term
.mode
|= MODE_PRINT
;
1647 case 'c': /* DA -- Device Attributes */
1648 if (csiescseq
.arg
[0] == 0)
1649 ttywrite(vtiden
, strlen(vtiden
), 0);
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
);
1656 case 'D': /* CUB -- Cursor <n> Backward */
1657 DEFAULT(csiescseq
.arg
[0], 1);
1658 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
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]);
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]);
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;
1673 case 3: /* clear all the tabs */
1674 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1680 case 'G': /* CHA -- Move to <col> */
1682 DEFAULT(csiescseq
.arg
[0], 1);
1683 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1685 case 'H': /* CUP -- Move to <row> <col> */
1687 DEFAULT(csiescseq
.arg
[0], 1);
1688 DEFAULT(csiescseq
.arg
[1], 1);
1689 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1691 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1692 DEFAULT(csiescseq
.arg
[0], 1);
1693 tputtab(csiescseq
.arg
[0]);
1695 case 'J': /* ED -- Clear screen */
1696 switch (csiescseq
.arg
[0]) {
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,
1706 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1707 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1710 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1716 case 'K': /* EL -- Clear line */
1717 switch (csiescseq
.arg
[0]) {
1719 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1723 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1726 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1730 case 'S': /* SU -- Scroll <n> line up */
1731 DEFAULT(csiescseq
.arg
[0], 1);
1732 tscrollup(term
.top
, csiescseq
.arg
[0]);
1734 case 'T': /* SD -- Scroll <n> line down */
1735 DEFAULT(csiescseq
.arg
[0], 1);
1736 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1738 case 'L': /* IL -- Insert <n> blank lines */
1739 DEFAULT(csiescseq
.arg
[0], 1);
1740 tinsertblankline(csiescseq
.arg
[0]);
1742 case 'l': /* RM -- Reset Mode */
1743 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1745 case 'M': /* DL -- Delete <n> lines */
1746 DEFAULT(csiescseq
.arg
[0], 1);
1747 tdeleteline(csiescseq
.arg
[0]);
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
);
1754 case 'P': /* DCH -- Delete <n> char */
1755 DEFAULT(csiescseq
.arg
[0], 1);
1756 tdeletechar(csiescseq
.arg
[0]);
1758 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1759 DEFAULT(csiescseq
.arg
[0], 1);
1760 tputtab(-csiescseq
.arg
[0]);
1762 case 'd': /* VPA -- Move to <row> */
1763 DEFAULT(csiescseq
.arg
[0], 1);
1764 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1766 case 'h': /* SM -- Set terminal mode */
1767 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1769 case 'm': /* SGR -- Terminal attribute (color) */
1770 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
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);
1779 case 'r': /* DECSTBM -- Set Scrolling Region */
1780 if (csiescseq
.priv
) {
1783 DEFAULT(csiescseq
.arg
[0], 1);
1784 DEFAULT(csiescseq
.arg
[1], term
.row
);
1785 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1789 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1790 tcursor(CURSOR_SAVE
);
1792 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1793 tcursor(CURSOR_LOAD
);
1796 switch (csiescseq
.mode
[1]) {
1797 case 'q': /* DECSCUSR -- Set Cursor Style */
1798 if (xsetcursor(csiescseq
.arg
[0]))
1814 fprintf(stderr
, "ESC[");
1815 for (i
= 0; i
< csiescseq
.len
; i
++) {
1816 c
= csiescseq
.buf
[i
] & 0xff;
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)");
1826 fprintf(stderr
, "(%02x)", c
);
1835 memset(&csiescseq
, 0, sizeof(csiescseq
));
1841 char *p
= NULL
, *dec
;
1844 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1846 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1848 switch (strescseq
.type
) {
1849 case ']': /* OSC -- Operating System Command */
1855 xsettitle(strescseq
.args
[1]);
1859 dec
= base64dec(strescseq
.args
[2]);
1864 fprintf(stderr
, "erresc: invalid base64\n");
1868 case 4: /* color set */
1871 p
= strescseq
.args
[2];
1873 case 104: /* color reset, here p = NULL */
1874 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1875 if (xsetcolorname(j
, p
)) {
1876 if (par
== 104 && narg
<= 1)
1877 return; /* color reset without parameter */
1878 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1879 j
, p
? p
: "(null)");
1882 * TODO if defaultbg color is changed, borders
1890 case 'k': /* old title set compatibility */
1891 xsettitle(strescseq
.args
[0]);
1893 case 'P': /* DCS -- Device Control String */
1894 term
.mode
|= ESC_DCS
;
1895 case '_': /* APC -- Application Program Command */
1896 case '^': /* PM -- Privacy Message */
1900 fprintf(stderr
, "erresc: unknown str ");
1908 char *p
= strescseq
.buf
;
1911 strescseq
.buf
[strescseq
.len
] = '\0';
1916 while (strescseq
.narg
< STR_ARG_SIZ
) {
1917 strescseq
.args
[strescseq
.narg
++] = p
;
1918 while ((c
= *p
) != ';' && c
!= '\0')
1932 fprintf(stderr
, "ESC%c", strescseq
.type
);
1933 for (i
= 0; i
< strescseq
.len
; i
++) {
1934 c
= strescseq
.buf
[i
] & 0xff;
1938 } else if (isprint(c
)) {
1940 } else if (c
== '\n') {
1941 fprintf(stderr
, "(\\n)");
1942 } else if (c
== '\r') {
1943 fprintf(stderr
, "(\\r)");
1944 } else if (c
== 0x1b) {
1945 fprintf(stderr
, "(\\e)");
1947 fprintf(stderr
, "(%02x)", c
);
1950 fprintf(stderr
, "ESC\\\n");
1956 strescseq
= (STREscape
){
1957 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
1963 sendbreak(const Arg
*arg
)
1965 if (tcsendbreak(cmdfd
, 0))
1966 perror("Error sending break");
1970 tprinter(char *s
, size_t len
)
1972 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1973 perror("Error writing to output file");
1980 toggleprinter(const Arg
*arg
)
1982 term
.mode
^= MODE_PRINT
;
1986 printscreen(const Arg
*arg
)
1992 printsel(const Arg
*arg
)
2002 if ((ptr
= getsel())) {
2003 tprinter(ptr
, strlen(ptr
));
2014 bp
= &term
.line
[n
][0];
2015 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2016 if (bp
!= end
|| bp
->u
!= ' ') {
2017 for ( ; bp
<= end
; ++bp
)
2018 tprinter(buf
, utf8encode(bp
->u
, buf
));
2028 for (i
= 0; i
< term
.row
; ++i
)
2038 while (x
< term
.col
&& n
--)
2039 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2042 while (x
> 0 && n
++)
2043 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2046 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2050 tdefutf8(char ascii
)
2053 term
.mode
|= MODE_UTF8
;
2054 else if (ascii
== '@')
2055 term
.mode
&= ~MODE_UTF8
;
2059 tdeftran(char ascii
)
2061 static char cs
[] = "0B";
2062 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2065 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2066 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2068 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2077 if (c
== '8') { /* DEC screen alignment test. */
2078 for (x
= 0; x
< term
.col
; ++x
) {
2079 for (y
= 0; y
< term
.row
; ++y
)
2080 tsetchar('E', &term
.c
.attr
, x
, y
);
2086 tstrsequence(uchar c
)
2091 case 0x90: /* DCS -- Device Control String */
2093 term
.esc
|= ESC_DCS
;
2095 case 0x9f: /* APC -- Application Program Command */
2098 case 0x9e: /* PM -- Privacy Message */
2101 case 0x9d: /* OSC -- Operating System Command */
2106 term
.esc
|= ESC_STR
;
2110 tcontrolcode(uchar ascii
)
2117 tmoveto(term
.c
.x
-1, term
.c
.y
);
2120 tmoveto(0, term
.c
.y
);
2125 /* go to first col if the mode is set */
2126 tnewline(IS_SET(MODE_CRLF
));
2128 case '\a': /* BEL */
2129 if (term
.esc
& ESC_STR_END
) {
2130 /* backwards compatibility to xterm */
2136 case '\033': /* ESC */
2138 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2139 term
.esc
|= ESC_START
;
2141 case '\016': /* SO (LS1 -- Locking shift 1) */
2142 case '\017': /* SI (LS0 -- Locking shift 0) */
2143 term
.charset
= 1 - (ascii
- '\016');
2145 case '\032': /* SUB */
2146 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2148 case '\030': /* CAN */
2151 case '\005': /* ENQ (IGNORED) */
2152 case '\000': /* NUL (IGNORED) */
2153 case '\021': /* XON (IGNORED) */
2154 case '\023': /* XOFF (IGNORED) */
2155 case 0177: /* DEL (IGNORED) */
2157 case 0x80: /* TODO: PAD */
2158 case 0x81: /* TODO: HOP */
2159 case 0x82: /* TODO: BPH */
2160 case 0x83: /* TODO: NBH */
2161 case 0x84: /* TODO: IND */
2163 case 0x85: /* NEL -- Next line */
2164 tnewline(1); /* always go to first col */
2166 case 0x86: /* TODO: SSA */
2167 case 0x87: /* TODO: ESA */
2169 case 0x88: /* HTS -- Horizontal tab stop */
2170 term
.tabs
[term
.c
.x
] = 1;
2172 case 0x89: /* TODO: HTJ */
2173 case 0x8a: /* TODO: VTS */
2174 case 0x8b: /* TODO: PLD */
2175 case 0x8c: /* TODO: PLU */
2176 case 0x8d: /* TODO: RI */
2177 case 0x8e: /* TODO: SS2 */
2178 case 0x8f: /* TODO: SS3 */
2179 case 0x91: /* TODO: PU1 */
2180 case 0x92: /* TODO: PU2 */
2181 case 0x93: /* TODO: STS */
2182 case 0x94: /* TODO: CCH */
2183 case 0x95: /* TODO: MW */
2184 case 0x96: /* TODO: SPA */
2185 case 0x97: /* TODO: EPA */
2186 case 0x98: /* TODO: SOS */
2187 case 0x99: /* TODO: SGCI */
2189 case 0x9a: /* DECID -- Identify Terminal */
2190 ttywrite(vtiden
, strlen(vtiden
), 0);
2192 case 0x9b: /* TODO: CSI */
2193 case 0x9c: /* TODO: ST */
2195 case 0x90: /* DCS -- Device Control String */
2196 case 0x9d: /* OSC -- Operating System Command */
2197 case 0x9e: /* PM -- Privacy Message */
2198 case 0x9f: /* APC -- Application Program Command */
2199 tstrsequence(ascii
);
2202 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2203 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2207 * returns 1 when the sequence is finished and it hasn't to read
2208 * more characters for this sequence, otherwise 0
2211 eschandle(uchar ascii
)
2215 term
.esc
|= ESC_CSI
;
2218 term
.esc
|= ESC_TEST
;
2221 term
.esc
|= ESC_UTF8
;
2223 case 'P': /* DCS -- Device Control String */
2224 case '_': /* APC -- Application Program Command */
2225 case '^': /* PM -- Privacy Message */
2226 case ']': /* OSC -- Operating System Command */
2227 case 'k': /* old title set compatibility */
2228 tstrsequence(ascii
);
2230 case 'n': /* LS2 -- Locking shift 2 */
2231 case 'o': /* LS3 -- Locking shift 3 */
2232 term
.charset
= 2 + (ascii
- 'n');
2234 case '(': /* GZD4 -- set primary charset G0 */
2235 case ')': /* G1D4 -- set secondary charset G1 */
2236 case '*': /* G2D4 -- set tertiary charset G2 */
2237 case '+': /* G3D4 -- set quaternary charset G3 */
2238 term
.icharset
= ascii
- '(';
2239 term
.esc
|= ESC_ALTCHARSET
;
2241 case 'D': /* IND -- Linefeed */
2242 if (term
.c
.y
== term
.bot
) {
2243 tscrollup(term
.top
, 1);
2245 tmoveto(term
.c
.x
, term
.c
.y
+1);
2248 case 'E': /* NEL -- Next line */
2249 tnewline(1); /* always go to first col */
2251 case 'H': /* HTS -- Horizontal tab stop */
2252 term
.tabs
[term
.c
.x
] = 1;
2254 case 'M': /* RI -- Reverse index */
2255 if (term
.c
.y
== term
.top
) {
2256 tscrolldown(term
.top
, 1);
2258 tmoveto(term
.c
.x
, term
.c
.y
-1);
2261 case 'Z': /* DECID -- Identify Terminal */
2262 ttywrite(vtiden
, strlen(vtiden
), 0);
2264 case 'c': /* RIS -- Reset to initial state */
2269 case '=': /* DECPAM -- Application keypad */
2270 xsetmode(1, MODE_APPKEYPAD
);
2272 case '>': /* DECPNM -- Normal keypad */
2273 xsetmode(0, MODE_APPKEYPAD
);
2275 case '7': /* DECSC -- Save Cursor */
2276 tcursor(CURSOR_SAVE
);
2278 case '8': /* DECRC -- Restore Cursor */
2279 tcursor(CURSOR_LOAD
);
2281 case '\\': /* ST -- String Terminator */
2282 if (term
.esc
& ESC_STR_END
)
2286 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2287 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2301 control
= ISCONTROL(u
);
2302 if (u
< 127 || !IS_SET(MODE_UTF8
| MODE_SIXEL
)) {
2306 len
= utf8encode(u
, c
);
2307 if (!control
&& (width
= wcwidth(u
)) == -1)
2311 if (IS_SET(MODE_PRINT
))
2315 * STR sequence must be checked before anything else
2316 * because it uses all following characters until it
2317 * receives a ESC, a SUB, a ST or any other C1 control
2320 if (term
.esc
& ESC_STR
) {
2321 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2323 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2324 if (IS_SET(MODE_SIXEL
)) {
2325 /* TODO: render sixel */;
2326 term
.mode
&= ~MODE_SIXEL
;
2329 term
.esc
|= ESC_STR_END
;
2330 goto check_control_code
;
2333 if (IS_SET(MODE_SIXEL
)) {
2334 /* TODO: implement sixel mode */
2337 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2338 term
.mode
|= MODE_SIXEL
;
2340 if (strescseq
.len
+len
>= strescseq
.siz
) {
2342 * Here is a bug in terminals. If the user never sends
2343 * some code to stop the str or esc command, then st
2344 * will stop responding. But this is better than
2345 * silently failing with unknown characters. At least
2346 * then users will report back.
2348 * In the case users ever get fixed, here is the code:
2354 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2357 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2360 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2361 strescseq
.len
+= len
;
2367 * Actions of control codes must be performed as soon they arrive
2368 * because they can be embedded inside a control sequence, and
2369 * they must not cause conflicts with sequences.
2374 * control codes are not shown ever
2377 } else if (term
.esc
& ESC_START
) {
2378 if (term
.esc
& ESC_CSI
) {
2379 csiescseq
.buf
[csiescseq
.len
++] = u
;
2380 if (BETWEEN(u
, 0x40, 0x7E)
2381 || csiescseq
.len
>= \
2382 sizeof(csiescseq
.buf
)-1) {
2388 } else if (term
.esc
& ESC_UTF8
) {
2390 } else if (term
.esc
& ESC_ALTCHARSET
) {
2392 } else if (term
.esc
& ESC_TEST
) {
2397 /* sequence already finished */
2401 * All characters which form part of a sequence are not
2406 if (selected(term
.c
.x
, term
.c
.y
))
2409 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2410 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2411 gp
->mode
|= ATTR_WRAP
;
2413 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2416 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2417 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2419 if (term
.c
.x
+width
> term
.col
) {
2421 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2424 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2427 gp
->mode
|= ATTR_WIDE
;
2428 if (term
.c
.x
+1 < term
.col
) {
2430 gp
[1].mode
= ATTR_WDUMMY
;
2433 if (term
.c
.x
+width
< term
.col
) {
2434 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2436 term
.c
.state
|= CURSOR_WRAPNEXT
;
2441 twrite(const char *buf
, int buflen
, int show_ctrl
)
2447 for (n
= 0; n
< buflen
; n
+= charsize
) {
2448 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2449 /* process a complete utf8 char */
2450 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2457 if (show_ctrl
&& ISCONTROL(u
)) {
2462 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2473 tresize(int col
, int row
)
2476 int minrow
= MIN(row
, term
.row
);
2477 int mincol
= MIN(col
, term
.col
);
2481 if (col
< 1 || row
< 1) {
2483 "tresize: error resizing to %dx%d\n", col
, row
);
2488 * slide screen to keep cursor where we expect it -
2489 * tscrollup would work here, but we can optimize to
2490 * memmove because we're freeing the earlier lines
2492 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2496 /* ensure that both src and dst are not NULL */
2498 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2499 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2501 for (i
+= row
; i
< term
.row
; i
++) {
2506 /* resize to new height */
2507 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2508 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2509 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2510 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2512 /* resize each row to new width, zero-pad if needed */
2513 for (i
= 0; i
< minrow
; i
++) {
2514 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2515 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2518 /* allocate any new rows */
2519 for (/* i = minrow */; i
< row
; i
++) {
2520 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2521 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2523 if (col
> term
.col
) {
2524 bp
= term
.tabs
+ term
.col
;
2526 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2527 while (--bp
> term
.tabs
&& !*bp
)
2529 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2532 /* update terminal size */
2535 /* reset scrolling region */
2536 tsetscroll(0, row
-1);
2537 /* make use of the LIMIT in tmoveto */
2538 tmoveto(term
.c
.x
, term
.c
.y
);
2539 /* Clearing both screens (it makes dirty all lines) */
2541 for (i
= 0; i
< 2; i
++) {
2542 if (mincol
< col
&& 0 < minrow
) {
2543 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2545 if (0 < col
&& minrow
< row
) {
2546 tclearregion(0, minrow
, col
- 1, row
- 1);
2549 tcursor(CURSOR_LOAD
);
2561 drawregion(int x1
, int y1
, int x2
, int y2
)
2565 for (y
= y1
; y
< y2
; y
++) {
2570 xdrawline(term
.line
[y
], x1
, y
, x2
);
2577 int cx
= term
.c
.x
, ocx
= term
.ocx
, ocy
= term
.ocy
;
2582 /* adjust cursor position */
2583 LIMIT(term
.ocx
, 0, term
.col
-1);
2584 LIMIT(term
.ocy
, 0, term
.row
-1);
2585 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2587 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2590 drawregion(0, 0, term
.col
, term
.row
);
2591 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2592 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2594 term
.ocy
= term
.c
.y
;
2596 if (ocx
!= term
.ocx
|| ocy
!= term
.ocy
)
2597 xximspot(term
.ocx
, term
.ocy
);