Xinqi Bao's Git
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) == '\177')
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
)) (*src
)++;
370 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
374 base64dec(const char *src
)
376 size_t in_len
= strlen(src
);
380 in_len
+= 4 - (in_len
% 4);
381 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
383 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
384 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
385 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
388 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
389 if (a
== -1 || b
== -1)
392 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
395 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
398 *dst
++ = ((c
& 0x03) << 6) | d
;
417 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
420 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
427 selstart(int col
, int row
, int snap
)
430 sel
.mode
= SEL_EMPTY
;
431 sel
.type
= SEL_REGULAR
;
432 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
434 sel
.oe
.x
= sel
.ob
.x
= col
;
435 sel
.oe
.y
= sel
.ob
.y
= row
;
439 sel
.mode
= SEL_READY
;
440 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
444 selextend(int col
, int row
, int type
, int done
)
446 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
448 if (sel
.mode
== SEL_IDLE
)
450 if (done
&& sel
.mode
== SEL_EMPTY
) {
466 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
467 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
469 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
477 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
478 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
479 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
481 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
482 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
484 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
485 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
487 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
488 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
490 /* expand selection over line breaks */
491 if (sel
.type
== SEL_RECTANGULAR
)
493 i
= tlinelen(sel
.nb
.y
);
496 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
497 sel
.ne
.x
= term
.col
- 1;
501 selected(int x
, int y
)
503 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
504 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
507 if (sel
.type
== SEL_RECTANGULAR
)
508 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
509 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
511 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
512 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
513 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
517 selsnap(int *x
, int *y
, int direction
)
519 int newx
, newy
, xt
, yt
;
520 int delim
, prevdelim
;
526 * Snap around if the word wraps around at the end or
527 * beginning of a line.
529 prevgp
= &term
.line
[*y
][*x
];
530 prevdelim
= ISDELIM(prevgp
->u
);
532 newx
= *x
+ direction
;
534 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
536 newx
= (newx
+ term
.col
) % term
.col
;
537 if (!BETWEEN(newy
, 0, term
.row
- 1))
543 yt
= newy
, xt
= newx
;
544 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
548 if (newx
>= tlinelen(newy
))
551 gp
= &term
.line
[newy
][newx
];
552 delim
= ISDELIM(gp
->u
);
553 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
554 || (delim
&& gp
->u
!= prevgp
->u
)))
565 * Snap around if the the previous line or the current one
566 * has set ATTR_WRAP at its end. Then the whole next or
567 * previous line will be selected.
569 *x
= (direction
< 0) ? 0 : term
.col
- 1;
571 for (; *y
> 0; *y
+= direction
) {
572 if (!(term
.line
[*y
-1][term
.col
-1].mode
577 } else if (direction
> 0) {
578 for (; *y
< term
.row
-1; *y
+= direction
) {
579 if (!(term
.line
[*y
][term
.col
-1].mode
593 int y
, bufsize
, lastx
, linelen
;
599 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
600 ptr
= str
= xmalloc(bufsize
);
602 /* append every set & selected glyph to the selection */
603 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
604 if ((linelen
= tlinelen(y
)) == 0) {
609 if (sel
.type
== SEL_RECTANGULAR
) {
610 gp
= &term
.line
[y
][sel
.nb
.x
];
613 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
614 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
616 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
617 while (last
>= gp
&& last
->u
== ' ')
620 for ( ; gp
<= last
; ++gp
) {
621 if (gp
->mode
& ATTR_WDUMMY
)
624 ptr
+= utf8encode(gp
->u
, ptr
);
628 * Copy and pasting of line endings is inconsistent
629 * in the inconsistent terminal and GUI world.
630 * The best solution seems like to produce '\n' when
631 * something is copied from st and convert '\n' to
632 * '\r', when something to be pasted is received by
634 * FIXME: Fix the computer world.
636 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
650 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
654 die(const char *errstr
, ...)
658 va_start(ap
, errstr
);
659 vfprintf(stderr
, errstr
, ap
);
665 execsh(char *cmd
, char **args
)
667 char *sh
, *prog
, *arg
;
668 const struct passwd
*pw
;
671 if ((pw
= getpwuid(getuid())) == NULL
) {
673 die("getpwuid: %s\n", strerror(errno
));
675 die("who are you?\n");
678 if ((sh
= getenv("SHELL")) == NULL
)
679 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
684 } else if (scroll
|| utmp
) {
685 prog
= scroll
? scroll
: utmp
;
686 arg
= scroll
? utmp
: NULL
;
691 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
696 setenv("LOGNAME", pw
->pw_name
, 1);
697 setenv("USER", pw
->pw_name
, 1);
698 setenv("SHELL", sh
, 1);
699 setenv("HOME", pw
->pw_dir
, 1);
700 setenv("TERM", termname
, 1);
702 signal(SIGCHLD
, SIG_DFL
);
703 signal(SIGHUP
, SIG_DFL
);
704 signal(SIGINT
, SIG_DFL
);
705 signal(SIGQUIT
, SIG_DFL
);
706 signal(SIGTERM
, SIG_DFL
);
707 signal(SIGALRM
, SIG_DFL
);
719 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
720 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
725 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
726 die("child exited with status %d\n", WEXITSTATUS(stat
));
727 else if (WIFSIGNALED(stat
))
728 die("child terminated due to signal %d\n", WTERMSIG(stat
));
735 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
738 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
739 die("incorrect stty parameters\n");
740 memcpy(cmd
, stty_args
, n
);
742 siz
= sizeof(cmd
) - n
;
743 for (p
= args
; p
&& (s
= *p
); ++p
) {
744 if ((n
= strlen(s
)) > siz
-1)
745 die("stty parameter length too long\n");
752 if (system(cmd
) != 0)
753 perror("Couldn't call stty");
757 ttynew(char *line
, char *cmd
, char *out
, char **args
)
762 term
.mode
|= MODE_PRINT
;
763 iofd
= (!strcmp(out
, "-")) ?
764 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
766 fprintf(stderr
, "Error opening %s:%s\n",
767 out
, strerror(errno
));
772 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
773 die("open line '%s' failed: %s\n",
774 line
, strerror(errno
));
780 /* seems to work fine on linux, openbsd and freebsd */
781 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
782 die("openpty failed: %s\n", strerror(errno
));
784 switch (pid
= fork()) {
786 die("fork failed: %s\n", strerror(errno
));
790 setsid(); /* create a new process group */
794 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
795 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
799 if (pledge("stdio getpw proc exec", NULL
) == -1)
806 if (pledge("stdio rpath tty proc", NULL
) == -1)
811 signal(SIGCHLD
, sigchld
);
820 static char buf
[BUFSIZ
];
821 static int buflen
= 0;
825 /* append read bytes to unprocessed bytes */
826 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
830 fputs("Found EOF in input\n", stderr
);
833 die("couldn't read from shell: %s\n", strerror(errno
));
836 written
= twrite(buf
, buflen
, 0);
838 /* keep any uncomplete utf8 char for the next call */
840 memmove(buf
, buf
+ written
, buflen
);
847 ttywrite(const char *s
, size_t n
, int may_echo
)
851 if (may_echo
&& IS_SET(MODE_ECHO
))
854 if (!IS_SET(MODE_CRLF
)) {
859 /* This is similar to how the kernel handles ONLCR for ttys */
863 ttywriteraw("\r\n", 2);
865 next
= memchr(s
, '\r', n
);
866 DEFAULT(next
, s
+ n
);
867 ttywriteraw(s
, next
- s
);
875 ttywriteraw(const char *s
, size_t n
)
882 * Remember that we are using a pty, which might be a modem line.
883 * Writing too much will clog the line. That's why we are doing this
885 * FIXME: Migrate the world to Plan 9.
893 /* Check if we can write. */
894 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
897 die("select failed: %s\n", strerror(errno
));
899 if (FD_ISSET(cmdfd
, &wfd
)) {
901 * Only write the bytes written by ttywrite() or the
902 * default of 256. This seems to be a reasonable value
903 * for a serial line. Bigger values might clog the I/O.
905 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
909 * We weren't able to write out everything.
910 * This means the buffer is getting full
918 /* All bytes have been written. */
922 if (FD_ISSET(cmdfd
, &rfd
))
928 die("write error on tty: %s\n", strerror(errno
));
932 ttyresize(int tw
, int th
)
940 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
941 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
947 /* Send SIGHUP to shell */
956 for (i
= 0; i
< term
.row
-1; i
++) {
957 for (j
= 0; j
< term
.col
-1; j
++) {
958 if (term
.line
[i
][j
].mode
& attr
)
967 tsetdirt(int top
, int bot
)
971 LIMIT(top
, 0, term
.row
-1);
972 LIMIT(bot
, 0, term
.row
-1);
974 for (i
= top
; i
<= bot
; i
++)
979 tsetdirtattr(int attr
)
983 for (i
= 0; i
< term
.row
-1; i
++) {
984 for (j
= 0; j
< term
.col
-1; j
++) {
985 if (term
.line
[i
][j
].mode
& attr
) {
996 tsetdirt(0, term
.row
-1);
1002 static TCursor c
[2];
1003 int alt
= IS_SET(MODE_ALTSCREEN
);
1005 if (mode
== CURSOR_SAVE
) {
1007 } else if (mode
== CURSOR_LOAD
) {
1009 tmoveto(c
[alt
].x
, c
[alt
].y
);
1018 term
.c
= (TCursor
){{
1022 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1024 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1025 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1028 term
.bot
= term
.row
- 1;
1029 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1030 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1033 for (i
= 0; i
< 2; i
++) {
1035 tcursor(CURSOR_SAVE
);
1036 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1042 tnew(int col
, int row
)
1044 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1052 Line
*tmp
= term
.line
;
1054 term
.line
= term
.alt
;
1056 term
.mode
^= MODE_ALTSCREEN
;
1061 tscrolldown(int orig
, int n
)
1066 LIMIT(n
, 0, term
.bot
-orig
+1);
1068 tsetdirt(orig
, term
.bot
-n
);
1069 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1071 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1072 temp
= term
.line
[i
];
1073 term
.line
[i
] = term
.line
[i
-n
];
1074 term
.line
[i
-n
] = temp
;
1081 tscrollup(int orig
, int n
)
1086 LIMIT(n
, 0, term
.bot
-orig
+1);
1088 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1089 tsetdirt(orig
+n
, term
.bot
);
1091 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1092 temp
= term
.line
[i
];
1093 term
.line
[i
] = term
.line
[i
+n
];
1094 term
.line
[i
+n
] = temp
;
1097 selscroll(orig
, -n
);
1101 selscroll(int orig
, int n
)
1106 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1107 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1111 if (sel
.type
== SEL_RECTANGULAR
) {
1112 if (sel
.ob
.y
< term
.top
)
1113 sel
.ob
.y
= term
.top
;
1114 if (sel
.oe
.y
> term
.bot
)
1115 sel
.oe
.y
= term
.bot
;
1117 if (sel
.ob
.y
< term
.top
) {
1118 sel
.ob
.y
= term
.top
;
1121 if (sel
.oe
.y
> term
.bot
) {
1122 sel
.oe
.y
= term
.bot
;
1123 sel
.oe
.x
= term
.col
;
1131 tnewline(int first_col
)
1135 if (y
== term
.bot
) {
1136 tscrollup(term
.top
, 1);
1140 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1146 char *p
= csiescseq
.buf
, *np
;
1155 csiescseq
.buf
[csiescseq
.len
] = '\0';
1156 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1158 v
= strtol(p
, &np
, 10);
1161 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1163 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1165 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1169 csiescseq
.mode
[0] = *p
++;
1170 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1173 /* for absolute user moves, when decom is set */
1175 tmoveato(int x
, int y
)
1177 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1181 tmoveto(int x
, int y
)
1185 if (term
.c
.state
& CURSOR_ORIGIN
) {
1190 maxy
= term
.row
- 1;
1192 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1193 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1194 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1198 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1200 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1201 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1202 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1203 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1204 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1205 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1206 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1207 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1208 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1212 * The table is proudly stolen from rxvt.
1214 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1215 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1216 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1218 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1219 if (x
+1 < term
.col
) {
1220 term
.line
[y
][x
+1].u
= ' ';
1221 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1223 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1224 term
.line
[y
][x
-1].u
= ' ';
1225 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1229 term
.line
[y
][x
] = *attr
;
1230 term
.line
[y
][x
].u
= u
;
1234 tclearregion(int x1
, int y1
, int x2
, int y2
)
1240 temp
= x1
, x1
= x2
, x2
= temp
;
1242 temp
= y1
, y1
= y2
, y2
= temp
;
1244 LIMIT(x1
, 0, term
.col
-1);
1245 LIMIT(x2
, 0, term
.col
-1);
1246 LIMIT(y1
, 0, term
.row
-1);
1247 LIMIT(y2
, 0, term
.row
-1);
1249 for (y
= y1
; y
<= y2
; y
++) {
1251 for (x
= x1
; x
<= x2
; x
++) {
1252 gp
= &term
.line
[y
][x
];
1255 gp
->fg
= term
.c
.attr
.fg
;
1256 gp
->bg
= term
.c
.attr
.bg
;
1269 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1273 size
= term
.col
- src
;
1274 line
= term
.line
[term
.c
.y
];
1276 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1277 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1286 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1290 size
= term
.col
- dst
;
1291 line
= term
.line
[term
.c
.y
];
1293 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1294 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1298 tinsertblankline(int n
)
1300 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1301 tscrolldown(term
.c
.y
, n
);
1307 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1308 tscrollup(term
.c
.y
, n
);
1312 tdefcolor(int *attr
, int *npar
, int l
)
1317 switch (attr
[*npar
+ 1]) {
1318 case 2: /* direct color in RGB space */
1319 if (*npar
+ 4 >= l
) {
1321 "erresc(38): Incorrect number of parameters (%d)\n",
1325 r
= attr
[*npar
+ 2];
1326 g
= attr
[*npar
+ 3];
1327 b
= attr
[*npar
+ 4];
1329 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1330 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1333 idx
= TRUECOLOR(r
, g
, b
);
1335 case 5: /* indexed color */
1336 if (*npar
+ 2 >= l
) {
1338 "erresc(38): Incorrect number of parameters (%d)\n",
1343 if (!BETWEEN(attr
[*npar
], 0, 255))
1344 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1348 case 0: /* implemented defined (only foreground) */
1349 case 1: /* transparent */
1350 case 3: /* direct color in CMY space */
1351 case 4: /* direct color in CMYK space */
1354 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1362 tsetattr(int *attr
, int l
)
1367 for (i
= 0; i
< l
; i
++) {
1370 term
.c
.attr
.mode
&= ~(
1379 term
.c
.attr
.fg
= defaultfg
;
1380 term
.c
.attr
.bg
= defaultbg
;
1383 term
.c
.attr
.mode
|= ATTR_BOLD
;
1386 term
.c
.attr
.mode
|= ATTR_FAINT
;
1389 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1392 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1394 case 5: /* slow blink */
1396 case 6: /* rapid blink */
1397 term
.c
.attr
.mode
|= ATTR_BLINK
;
1400 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1403 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1406 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1409 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1412 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1415 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1418 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1421 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1424 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1427 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1430 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1431 term
.c
.attr
.fg
= idx
;
1434 term
.c
.attr
.fg
= defaultfg
;
1437 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1438 term
.c
.attr
.bg
= idx
;
1441 term
.c
.attr
.bg
= defaultbg
;
1444 if (BETWEEN(attr
[i
], 30, 37)) {
1445 term
.c
.attr
.fg
= attr
[i
] - 30;
1446 } else if (BETWEEN(attr
[i
], 40, 47)) {
1447 term
.c
.attr
.bg
= attr
[i
] - 40;
1448 } else if (BETWEEN(attr
[i
], 90, 97)) {
1449 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1450 } else if (BETWEEN(attr
[i
], 100, 107)) {
1451 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1454 "erresc(default): gfx attr %d unknown\n",
1464 tsetscroll(int t
, int b
)
1468 LIMIT(t
, 0, term
.row
-1);
1469 LIMIT(b
, 0, term
.row
-1);
1480 tsetmode(int priv
, int set
, int *args
, int narg
)
1484 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1487 case 1: /* DECCKM -- Cursor key */
1488 xsetmode(set
, MODE_APPCURSOR
);
1490 case 5: /* DECSCNM -- Reverse video */
1491 xsetmode(set
, MODE_REVERSE
);
1493 case 6: /* DECOM -- Origin */
1494 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1497 case 7: /* DECAWM -- Auto wrap */
1498 MODBIT(term
.mode
, set
, MODE_WRAP
);
1500 case 0: /* Error (IGNORED) */
1501 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1502 case 3: /* DECCOLM -- Column (IGNORED) */
1503 case 4: /* DECSCLM -- Scroll (IGNORED) */
1504 case 8: /* DECARM -- Auto repeat (IGNORED) */
1505 case 18: /* DECPFF -- Printer feed (IGNORED) */
1506 case 19: /* DECPEX -- Printer extent (IGNORED) */
1507 case 42: /* DECNRCM -- National characters (IGNORED) */
1508 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1510 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1511 xsetmode(!set
, MODE_HIDE
);
1513 case 9: /* X10 mouse compatibility mode */
1514 xsetpointermotion(0);
1515 xsetmode(0, MODE_MOUSE
);
1516 xsetmode(set
, MODE_MOUSEX10
);
1518 case 1000: /* 1000: report button press */
1519 xsetpointermotion(0);
1520 xsetmode(0, MODE_MOUSE
);
1521 xsetmode(set
, MODE_MOUSEBTN
);
1523 case 1002: /* 1002: report motion on button press */
1524 xsetpointermotion(0);
1525 xsetmode(0, MODE_MOUSE
);
1526 xsetmode(set
, MODE_MOUSEMOTION
);
1528 case 1003: /* 1003: enable all mouse motions */
1529 xsetpointermotion(set
);
1530 xsetmode(0, MODE_MOUSE
);
1531 xsetmode(set
, MODE_MOUSEMANY
);
1533 case 1004: /* 1004: send focus events to tty */
1534 xsetmode(set
, MODE_FOCUS
);
1536 case 1006: /* 1006: extended reporting mode */
1537 xsetmode(set
, MODE_MOUSESGR
);
1540 xsetmode(set
, MODE_8BIT
);
1542 case 1049: /* swap screen & set/restore cursor as xterm */
1543 if (!allowaltscreen
)
1545 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1547 case 47: /* swap screen */
1549 if (!allowaltscreen
)
1551 alt
= IS_SET(MODE_ALTSCREEN
);
1553 tclearregion(0, 0, term
.col
-1,
1556 if (set
^ alt
) /* set is always 1 or 0 */
1562 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1564 case 2004: /* 2004: bracketed paste mode */
1565 xsetmode(set
, MODE_BRCKTPASTE
);
1567 /* Not implemented mouse modes. See comments there. */
1568 case 1001: /* mouse highlight mode; can hang the
1569 terminal by design when implemented. */
1570 case 1005: /* UTF-8 mouse mode; will confuse
1571 applications not supporting UTF-8
1573 case 1015: /* urxvt mangled mouse mode; incompatible
1574 and can be mistaken for other control
1579 "erresc: unknown private set/reset mode %d\n",
1585 case 0: /* Error (IGNORED) */
1588 xsetmode(set
, MODE_KBDLOCK
);
1590 case 4: /* IRM -- Insertion-replacement */
1591 MODBIT(term
.mode
, set
, MODE_INSERT
);
1593 case 12: /* SRM -- Send/Receive */
1594 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1596 case 20: /* LNM -- Linefeed/new line */
1597 MODBIT(term
.mode
, set
, MODE_CRLF
);
1601 "erresc: unknown set/reset mode %d\n",
1615 switch (csiescseq
.mode
[0]) {
1618 fprintf(stderr
, "erresc: unknown csi ");
1622 case '@': /* ICH -- Insert <n> blank char */
1623 DEFAULT(csiescseq
.arg
[0], 1);
1624 tinsertblank(csiescseq
.arg
[0]);
1626 case 'A': /* CUU -- Cursor <n> Up */
1627 DEFAULT(csiescseq
.arg
[0], 1);
1628 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1630 case 'B': /* CUD -- Cursor <n> Down */
1631 case 'e': /* VPR --Cursor <n> Down */
1632 DEFAULT(csiescseq
.arg
[0], 1);
1633 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1635 case 'i': /* MC -- Media Copy */
1636 switch (csiescseq
.arg
[0]) {
1641 tdumpline(term
.c
.y
);
1647 term
.mode
&= ~MODE_PRINT
;
1650 term
.mode
|= MODE_PRINT
;
1654 case 'c': /* DA -- Device Attributes */
1655 if (csiescseq
.arg
[0] == 0)
1656 ttywrite(vtiden
, strlen(vtiden
), 0);
1658 case 'C': /* CUF -- Cursor <n> Forward */
1659 case 'a': /* HPR -- Cursor <n> Forward */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1663 case 'D': /* CUB -- Cursor <n> Backward */
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1667 case 'E': /* CNL -- Cursor <n> Down and first col */
1668 DEFAULT(csiescseq
.arg
[0], 1);
1669 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1671 case 'F': /* CPL -- Cursor <n> Up and first col */
1672 DEFAULT(csiescseq
.arg
[0], 1);
1673 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1675 case 'g': /* TBC -- Tabulation clear */
1676 switch (csiescseq
.arg
[0]) {
1677 case 0: /* clear current tab stop */
1678 term
.tabs
[term
.c
.x
] = 0;
1680 case 3: /* clear all the tabs */
1681 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1687 case 'G': /* CHA -- Move to <col> */
1689 DEFAULT(csiescseq
.arg
[0], 1);
1690 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1692 case 'H': /* CUP -- Move to <row> <col> */
1694 DEFAULT(csiescseq
.arg
[0], 1);
1695 DEFAULT(csiescseq
.arg
[1], 1);
1696 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1698 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1699 DEFAULT(csiescseq
.arg
[0], 1);
1700 tputtab(csiescseq
.arg
[0]);
1702 case 'J': /* ED -- Clear screen */
1703 switch (csiescseq
.arg
[0]) {
1705 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1706 if (term
.c
.y
< term
.row
-1) {
1707 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1713 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1714 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1717 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1723 case 'K': /* EL -- Clear line */
1724 switch (csiescseq
.arg
[0]) {
1726 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1730 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1733 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1737 case 'S': /* SU -- Scroll <n> line up */
1738 DEFAULT(csiescseq
.arg
[0], 1);
1739 tscrollup(term
.top
, csiescseq
.arg
[0]);
1741 case 'T': /* SD -- Scroll <n> line down */
1742 DEFAULT(csiescseq
.arg
[0], 1);
1743 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1745 case 'L': /* IL -- Insert <n> blank lines */
1746 DEFAULT(csiescseq
.arg
[0], 1);
1747 tinsertblankline(csiescseq
.arg
[0]);
1749 case 'l': /* RM -- Reset Mode */
1750 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1752 case 'M': /* DL -- Delete <n> lines */
1753 DEFAULT(csiescseq
.arg
[0], 1);
1754 tdeleteline(csiescseq
.arg
[0]);
1756 case 'X': /* ECH -- Erase <n> char */
1757 DEFAULT(csiescseq
.arg
[0], 1);
1758 tclearregion(term
.c
.x
, term
.c
.y
,
1759 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1761 case 'P': /* DCH -- Delete <n> char */
1762 DEFAULT(csiescseq
.arg
[0], 1);
1763 tdeletechar(csiescseq
.arg
[0]);
1765 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1766 DEFAULT(csiescseq
.arg
[0], 1);
1767 tputtab(-csiescseq
.arg
[0]);
1769 case 'd': /* VPA -- Move to <row> */
1770 DEFAULT(csiescseq
.arg
[0], 1);
1771 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1773 case 'h': /* SM -- Set terminal mode */
1774 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1776 case 'm': /* SGR -- Terminal attribute (color) */
1777 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1779 case 'n': /* DSR – Device Status Report (cursor position) */
1780 if (csiescseq
.arg
[0] == 6) {
1781 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1782 term
.c
.y
+1, term
.c
.x
+1);
1783 ttywrite(buf
, len
, 0);
1786 case 'r': /* DECSTBM -- Set Scrolling Region */
1787 if (csiescseq
.priv
) {
1790 DEFAULT(csiescseq
.arg
[0], 1);
1791 DEFAULT(csiescseq
.arg
[1], term
.row
);
1792 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1796 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1797 tcursor(CURSOR_SAVE
);
1799 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1800 tcursor(CURSOR_LOAD
);
1803 switch (csiescseq
.mode
[1]) {
1804 case 'q': /* DECSCUSR -- Set Cursor Style */
1805 if (xsetcursor(csiescseq
.arg
[0]))
1821 fprintf(stderr
, "ESC[");
1822 for (i
= 0; i
< csiescseq
.len
; i
++) {
1823 c
= csiescseq
.buf
[i
] & 0xff;
1826 } else if (c
== '\n') {
1827 fprintf(stderr
, "(\\n)");
1828 } else if (c
== '\r') {
1829 fprintf(stderr
, "(\\r)");
1830 } else if (c
== 0x1b) {
1831 fprintf(stderr
, "(\\e)");
1833 fprintf(stderr
, "(%02x)", c
);
1842 memset(&csiescseq
, 0, sizeof(csiescseq
));
1848 char *p
= NULL
, *dec
;
1851 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1853 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1855 switch (strescseq
.type
) {
1856 case ']': /* OSC -- Operating System Command */
1862 xsettitle(strescseq
.args
[1]);
1866 dec
= base64dec(strescseq
.args
[2]);
1871 fprintf(stderr
, "erresc: invalid base64\n");
1875 case 4: /* color set */
1878 p
= strescseq
.args
[2];
1880 case 104: /* color reset, here p = NULL */
1881 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1882 if (xsetcolorname(j
, p
)) {
1883 if (par
== 104 && narg
<= 1)
1884 return; /* color reset without parameter */
1885 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1886 j
, p
? p
: "(null)");
1889 * TODO if defaultbg color is changed, borders
1897 case 'k': /* old title set compatibility */
1898 xsettitle(strescseq
.args
[0]);
1900 case 'P': /* DCS -- Device Control String */
1901 term
.mode
|= ESC_DCS
;
1902 case '_': /* APC -- Application Program Command */
1903 case '^': /* PM -- Privacy Message */
1907 fprintf(stderr
, "erresc: unknown str ");
1915 char *p
= strescseq
.buf
;
1918 strescseq
.buf
[strescseq
.len
] = '\0';
1923 while (strescseq
.narg
< STR_ARG_SIZ
) {
1924 strescseq
.args
[strescseq
.narg
++] = p
;
1925 while ((c
= *p
) != ';' && c
!= '\0')
1939 fprintf(stderr
, "ESC%c", strescseq
.type
);
1940 for (i
= 0; i
< strescseq
.len
; i
++) {
1941 c
= strescseq
.buf
[i
] & 0xff;
1945 } else if (isprint(c
)) {
1947 } else if (c
== '\n') {
1948 fprintf(stderr
, "(\\n)");
1949 } else if (c
== '\r') {
1950 fprintf(stderr
, "(\\r)");
1951 } else if (c
== 0x1b) {
1952 fprintf(stderr
, "(\\e)");
1954 fprintf(stderr
, "(%02x)", c
);
1957 fprintf(stderr
, "ESC\\\n");
1963 strescseq
= (STREscape
){
1964 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
1970 sendbreak(const Arg
*arg
)
1972 if (tcsendbreak(cmdfd
, 0))
1973 perror("Error sending break");
1977 tprinter(char *s
, size_t len
)
1979 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1980 perror("Error writing to output file");
1987 toggleprinter(const Arg
*arg
)
1989 term
.mode
^= MODE_PRINT
;
1993 printscreen(const Arg
*arg
)
1999 printsel(const Arg
*arg
)
2009 if ((ptr
= getsel())) {
2010 tprinter(ptr
, strlen(ptr
));
2021 bp
= &term
.line
[n
][0];
2022 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2023 if (bp
!= end
|| bp
->u
!= ' ') {
2024 for ( ;bp
<= end
; ++bp
)
2025 tprinter(buf
, utf8encode(bp
->u
, buf
));
2035 for (i
= 0; i
< term
.row
; ++i
)
2045 while (x
< term
.col
&& n
--)
2046 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2049 while (x
> 0 && n
++)
2050 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2053 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2057 tdefutf8(char ascii
)
2060 term
.mode
|= MODE_UTF8
;
2061 else if (ascii
== '@')
2062 term
.mode
&= ~MODE_UTF8
;
2066 tdeftran(char ascii
)
2068 static char cs
[] = "0B";
2069 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2072 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2073 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2075 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2084 if (c
== '8') { /* DEC screen alignment test. */
2085 for (x
= 0; x
< term
.col
; ++x
) {
2086 for (y
= 0; y
< term
.row
; ++y
)
2087 tsetchar('E', &term
.c
.attr
, x
, y
);
2093 tstrsequence(uchar c
)
2098 case 0x90: /* DCS -- Device Control String */
2100 term
.esc
|= ESC_DCS
;
2102 case 0x9f: /* APC -- Application Program Command */
2105 case 0x9e: /* PM -- Privacy Message */
2108 case 0x9d: /* OSC -- Operating System Command */
2113 term
.esc
|= ESC_STR
;
2117 tcontrolcode(uchar ascii
)
2124 tmoveto(term
.c
.x
-1, term
.c
.y
);
2127 tmoveto(0, term
.c
.y
);
2132 /* go to first col if the mode is set */
2133 tnewline(IS_SET(MODE_CRLF
));
2135 case '\a': /* BEL */
2136 if (term
.esc
& ESC_STR_END
) {
2137 /* backwards compatibility to xterm */
2143 case '\033': /* ESC */
2145 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2146 term
.esc
|= ESC_START
;
2148 case '\016': /* SO (LS1 -- Locking shift 1) */
2149 case '\017': /* SI (LS0 -- Locking shift 0) */
2150 term
.charset
= 1 - (ascii
- '\016');
2152 case '\032': /* SUB */
2153 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2154 case '\030': /* CAN */
2157 case '\005': /* ENQ (IGNORED) */
2158 case '\000': /* NUL (IGNORED) */
2159 case '\021': /* XON (IGNORED) */
2160 case '\023': /* XOFF (IGNORED) */
2161 case 0177: /* DEL (IGNORED) */
2163 case 0x80: /* TODO: PAD */
2164 case 0x81: /* TODO: HOP */
2165 case 0x82: /* TODO: BPH */
2166 case 0x83: /* TODO: NBH */
2167 case 0x84: /* TODO: IND */
2169 case 0x85: /* NEL -- Next line */
2170 tnewline(1); /* always go to first col */
2172 case 0x86: /* TODO: SSA */
2173 case 0x87: /* TODO: ESA */
2175 case 0x88: /* HTS -- Horizontal tab stop */
2176 term
.tabs
[term
.c
.x
] = 1;
2178 case 0x89: /* TODO: HTJ */
2179 case 0x8a: /* TODO: VTS */
2180 case 0x8b: /* TODO: PLD */
2181 case 0x8c: /* TODO: PLU */
2182 case 0x8d: /* TODO: RI */
2183 case 0x8e: /* TODO: SS2 */
2184 case 0x8f: /* TODO: SS3 */
2185 case 0x91: /* TODO: PU1 */
2186 case 0x92: /* TODO: PU2 */
2187 case 0x93: /* TODO: STS */
2188 case 0x94: /* TODO: CCH */
2189 case 0x95: /* TODO: MW */
2190 case 0x96: /* TODO: SPA */
2191 case 0x97: /* TODO: EPA */
2192 case 0x98: /* TODO: SOS */
2193 case 0x99: /* TODO: SGCI */
2195 case 0x9a: /* DECID -- Identify Terminal */
2196 ttywrite(vtiden
, strlen(vtiden
), 0);
2198 case 0x9b: /* TODO: CSI */
2199 case 0x9c: /* TODO: ST */
2201 case 0x90: /* DCS -- Device Control String */
2202 case 0x9d: /* OSC -- Operating System Command */
2203 case 0x9e: /* PM -- Privacy Message */
2204 case 0x9f: /* APC -- Application Program Command */
2205 tstrsequence(ascii
);
2208 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2209 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2213 * returns 1 when the sequence is finished and it hasn't to read
2214 * more characters for this sequence, otherwise 0
2217 eschandle(uchar ascii
)
2221 term
.esc
|= ESC_CSI
;
2224 term
.esc
|= ESC_TEST
;
2227 term
.esc
|= ESC_UTF8
;
2229 case 'P': /* DCS -- Device Control String */
2230 case '_': /* APC -- Application Program Command */
2231 case '^': /* PM -- Privacy Message */
2232 case ']': /* OSC -- Operating System Command */
2233 case 'k': /* old title set compatibility */
2234 tstrsequence(ascii
);
2236 case 'n': /* LS2 -- Locking shift 2 */
2237 case 'o': /* LS3 -- Locking shift 3 */
2238 term
.charset
= 2 + (ascii
- 'n');
2240 case '(': /* GZD4 -- set primary charset G0 */
2241 case ')': /* G1D4 -- set secondary charset G1 */
2242 case '*': /* G2D4 -- set tertiary charset G2 */
2243 case '+': /* G3D4 -- set quaternary charset G3 */
2244 term
.icharset
= ascii
- '(';
2245 term
.esc
|= ESC_ALTCHARSET
;
2247 case 'D': /* IND -- Linefeed */
2248 if (term
.c
.y
== term
.bot
) {
2249 tscrollup(term
.top
, 1);
2251 tmoveto(term
.c
.x
, term
.c
.y
+1);
2254 case 'E': /* NEL -- Next line */
2255 tnewline(1); /* always go to first col */
2257 case 'H': /* HTS -- Horizontal tab stop */
2258 term
.tabs
[term
.c
.x
] = 1;
2260 case 'M': /* RI -- Reverse index */
2261 if (term
.c
.y
== term
.top
) {
2262 tscrolldown(term
.top
, 1);
2264 tmoveto(term
.c
.x
, term
.c
.y
-1);
2267 case 'Z': /* DECID -- Identify Terminal */
2268 ttywrite(vtiden
, strlen(vtiden
), 0);
2270 case 'c': /* RIS -- Reset to initial state */
2275 case '=': /* DECPAM -- Application keypad */
2276 xsetmode(1, MODE_APPKEYPAD
);
2278 case '>': /* DECPNM -- Normal keypad */
2279 xsetmode(0, MODE_APPKEYPAD
);
2281 case '7': /* DECSC -- Save Cursor */
2282 tcursor(CURSOR_SAVE
);
2284 case '8': /* DECRC -- Restore Cursor */
2285 tcursor(CURSOR_LOAD
);
2287 case '\\': /* ST -- String Terminator */
2288 if (term
.esc
& ESC_STR_END
)
2292 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2293 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2307 control
= ISCONTROL(u
);
2308 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2312 len
= utf8encode(u
, c
);
2313 if (!control
&& (width
= wcwidth(u
)) == -1) {
2314 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2319 if (IS_SET(MODE_PRINT
))
2323 * STR sequence must be checked before anything else
2324 * because it uses all following characters until it
2325 * receives a ESC, a SUB, a ST or any other C1 control
2328 if (term
.esc
& ESC_STR
) {
2329 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2331 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2332 if (IS_SET(MODE_SIXEL
)) {
2333 /* TODO: render sixel */;
2334 term
.mode
&= ~MODE_SIXEL
;
2337 term
.esc
|= ESC_STR_END
;
2338 goto check_control_code
;
2341 if (IS_SET(MODE_SIXEL
)) {
2342 /* TODO: implement sixel mode */
2345 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2346 term
.mode
|= MODE_SIXEL
;
2348 if (strescseq
.len
+len
>= strescseq
.siz
) {
2350 * Here is a bug in terminals. If the user never sends
2351 * some code to stop the str or esc command, then st
2352 * will stop responding. But this is better than
2353 * silently failing with unknown characters. At least
2354 * then users will report back.
2356 * In the case users ever get fixed, here is the code:
2362 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2365 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2368 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2369 strescseq
.len
+= len
;
2375 * Actions of control codes must be performed as soon they arrive
2376 * because they can be embedded inside a control sequence, and
2377 * they must not cause conflicts with sequences.
2382 * control codes are not shown ever
2385 } else if (term
.esc
& ESC_START
) {
2386 if (term
.esc
& ESC_CSI
) {
2387 csiescseq
.buf
[csiescseq
.len
++] = u
;
2388 if (BETWEEN(u
, 0x40, 0x7E)
2389 || csiescseq
.len
>= \
2390 sizeof(csiescseq
.buf
)-1) {
2396 } else if (term
.esc
& ESC_UTF8
) {
2398 } else if (term
.esc
& ESC_ALTCHARSET
) {
2400 } else if (term
.esc
& ESC_TEST
) {
2405 /* sequence already finished */
2409 * All characters which form part of a sequence are not
2414 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2417 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2418 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2419 gp
->mode
|= ATTR_WRAP
;
2421 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2424 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2425 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2427 if (term
.c
.x
+width
> term
.col
) {
2429 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2432 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2435 gp
->mode
|= ATTR_WIDE
;
2436 if (term
.c
.x
+1 < term
.col
) {
2438 gp
[1].mode
= ATTR_WDUMMY
;
2441 if (term
.c
.x
+width
< term
.col
) {
2442 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2444 term
.c
.state
|= CURSOR_WRAPNEXT
;
2449 twrite(const char *buf
, int buflen
, int show_ctrl
)
2455 for (n
= 0; n
< buflen
; n
+= charsize
) {
2456 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2457 /* process a complete utf8 char */
2458 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2465 if (show_ctrl
&& ISCONTROL(u
)) {
2470 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2481 tresize(int col
, int row
)
2484 int minrow
= MIN(row
, term
.row
);
2485 int mincol
= MIN(col
, term
.col
);
2489 if (col
< 1 || row
< 1) {
2491 "tresize: error resizing to %dx%d\n", col
, row
);
2496 * slide screen to keep cursor where we expect it -
2497 * tscrollup would work here, but we can optimize to
2498 * memmove because we're freeing the earlier lines
2500 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2504 /* ensure that both src and dst are not NULL */
2506 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2507 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2509 for (i
+= row
; i
< term
.row
; i
++) {
2514 /* resize to new height */
2515 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2516 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2517 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2518 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2520 /* resize each row to new width, zero-pad if needed */
2521 for (i
= 0; i
< minrow
; i
++) {
2522 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2523 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2526 /* allocate any new rows */
2527 for (/* i = minrow */; i
< row
; i
++) {
2528 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2529 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2531 if (col
> term
.col
) {
2532 bp
= term
.tabs
+ term
.col
;
2534 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2535 while (--bp
> term
.tabs
&& !*bp
)
2537 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2540 /* update terminal size */
2543 /* reset scrolling region */
2544 tsetscroll(0, row
-1);
2545 /* make use of the LIMIT in tmoveto */
2546 tmoveto(term
.c
.x
, term
.c
.y
);
2547 /* Clearing both screens (it makes dirty all lines) */
2549 for (i
= 0; i
< 2; i
++) {
2550 if (mincol
< col
&& 0 < minrow
) {
2551 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2553 if (0 < col
&& minrow
< row
) {
2554 tclearregion(0, minrow
, col
- 1, row
- 1);
2557 tcursor(CURSOR_LOAD
);
2569 drawregion(int x1
, int y1
, int x2
, int y2
)
2572 for (y
= y1
; y
< y2
; y
++) {
2577 xdrawline(term
.line
[y
], x1
, y
, x2
);
2589 /* adjust cursor position */
2590 LIMIT(term
.ocx
, 0, term
.col
-1);
2591 LIMIT(term
.ocy
, 0, term
.row
-1);
2592 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2594 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2597 drawregion(0, 0, term
.col
, term
.row
);
2598 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2599 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2600 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;
2602 xximspot(term
.ocx
, term
.ocy
);