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
41 #define IS_SET(flag) ((term.mode & (flag)) != 0)
42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
45 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
46 #define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
47 term.scr + HISTSIZE + 1) % HISTSIZE] : \
48 term.line[(y) - term.scr])
53 MODE_ALTSCREEN
= 1 << 2,
60 enum cursor_movement
{
84 ESC_STR
= 4, /* DCS, OSC, PM, APC */
86 ESC_STR_END
= 16, /* a final string was encountered */
87 ESC_TEST
= 32, /* Enter in test mode */
92 Glyph attr
; /* current char attributes */
103 * Selection variables:
104 * nb – normalized coordinates of the beginning of the selection
105 * ne – normalized coordinates of the end of the selection
106 * ob – original coordinates of the beginning of the selection
107 * oe – original coordinates of the end of the selection
116 /* Internal representation of the screen */
118 int row
; /* nb row */
119 int col
; /* nb col */
120 Line
*line
; /* screen */
121 Line
*alt
; /* alternate screen */
122 Line hist
[HISTSIZE
]; /* history buffer */
123 int histi
; /* history index */
124 int scr
; /* scroll back */
125 int *dirty
; /* dirtyness of lines */
126 TCursor c
; /* cursor */
127 int ocx
; /* old cursor col */
128 int ocy
; /* old cursor row */
129 int top
; /* top scroll limit */
130 int bot
; /* bottom scroll limit */
131 int mode
; /* terminal mode flags */
132 int esc
; /* escape state flags */
133 char trantbl
[4]; /* charset table translation */
134 int charset
; /* current charset */
135 int icharset
; /* selected charset for sequence */
137 Rune lastc
; /* last printed char outside of sequence, 0 if control */
140 /* CSI Escape sequence structs */
141 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
143 char buf
[ESC_BUF_SIZ
]; /* raw string */
144 size_t len
; /* raw string length */
146 int arg
[ESC_ARG_SIZ
];
147 int narg
; /* nb of args */
151 /* STR Escape sequence structs */
152 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
154 char type
; /* ESC type ... */
155 char *buf
; /* allocated raw string */
156 size_t siz
; /* allocation size */
157 size_t len
; /* raw string length */
158 char *args
[STR_ARG_SIZ
];
159 int narg
; /* nb of args */
162 static void execsh(char *, char **);
163 static void stty(char **);
164 static void sigchld(int);
165 static void ttywriteraw(const char *, size_t);
167 static void csidump(void);
168 static void csihandle(void);
169 static void csiparse(void);
170 static void csireset(void);
171 static int eschandle(uchar
);
172 static void strdump(void);
173 static void strhandle(void);
174 static void strparse(void);
175 static void strreset(void);
177 static void tprinter(char *, size_t);
178 static void tdumpsel(void);
179 static void tdumpline(int);
180 static void tdump(void);
181 static void tclearregion(int, int, int, int);
182 static void tcursor(int);
183 static void tdeletechar(int);
184 static void tdeleteline(int);
185 static void tinsertblank(int);
186 static void tinsertblankline(int);
187 static int tlinelen(int);
188 static void tmoveto(int, int);
189 static void tmoveato(int, int);
190 static void tnewline(int);
191 static void tputtab(int);
192 static void tputc(Rune
);
193 static void treset(void);
194 static void tscrollup(int, int, int);
195 static void tscrolldown(int, int, int);
196 static void tsetattr(const int *, int);
197 static void tsetchar(Rune
, const Glyph
*, int, int);
198 static void tsetdirt(int, int);
199 static void tsetscroll(int, int);
200 static void tswapscreen(void);
201 static void tsetmode(int, int, const int *, int);
202 static int twrite(const char *, int, int);
203 static void tfulldirt(void);
204 static void tcontrolcode(uchar
);
205 static void tdectest(char );
206 static void tdefutf8(char);
207 static int32_t tdefcolor(const int *, int *, int);
208 static void tdeftran(char);
209 static void tstrsequence(uchar
);
211 static void drawregion(int, int, int, int);
213 static void selnormalize(void);
214 static void selscroll(int, int);
215 static void selsnap(int *, int *, int);
217 static size_t utf8decode(const char *, Rune
*, size_t);
218 static Rune
utf8decodebyte(char, size_t *);
219 static char utf8encodebyte(Rune
, size_t);
220 static size_t utf8validate(Rune
*, size_t);
222 static char *base64dec(const char *);
223 static char base64dec_getc(const char **);
225 static ssize_t
xwrite(int, const char *, size_t);
229 static Selection sel
;
230 static CSIEscape csiescseq
;
231 static STREscape strescseq
;
236 static const uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
237 static const uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
238 static const Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
239 static const Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
242 xwrite(int fd
, const char *s
, size_t len
)
248 r
= write(fd
, s
, len
);
263 if (!(p
= malloc(len
)))
264 die("malloc: %s\n", strerror(errno
));
270 xrealloc(void *p
, size_t len
)
272 if ((p
= realloc(p
, len
)) == NULL
)
273 die("realloc: %s\n", strerror(errno
));
279 xstrdup(const char *s
)
283 if ((p
= strdup(s
)) == NULL
)
284 die("strdup: %s\n", strerror(errno
));
290 utf8decode(const char *c
, Rune
*u
, size_t clen
)
292 size_t i
, j
, len
, type
;
298 udecoded
= utf8decodebyte(c
[0], &len
);
299 if (!BETWEEN(len
, 1, UTF_SIZ
))
301 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
302 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
309 utf8validate(u
, len
);
315 utf8decodebyte(char c
, size_t *i
)
317 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
318 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
319 return (uchar
)c
& ~utfmask
[*i
];
325 utf8encode(Rune u
, char *c
)
329 len
= utf8validate(&u
, 0);
333 for (i
= len
- 1; i
!= 0; --i
) {
334 c
[i
] = utf8encodebyte(u
, 0);
337 c
[0] = utf8encodebyte(u
, len
);
343 utf8encodebyte(Rune u
, size_t i
)
345 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
349 utf8validate(Rune
*u
, size_t i
)
351 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
353 for (i
= 1; *u
> utfmax
[i
]; ++i
)
359 static const char base64_digits
[] = {
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, 62, 0, 0, 0,
362 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
363 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
364 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
365 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
366 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
367 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
368 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
369 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
370 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
371 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
375 base64dec_getc(const char **src
)
377 while (**src
&& !isprint(**src
))
379 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
383 base64dec(const char *src
)
385 size_t in_len
= strlen(src
);
389 in_len
+= 4 - (in_len
% 4);
390 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
392 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
393 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
394 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
395 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
397 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
398 if (a
== -1 || b
== -1)
401 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
404 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
407 *dst
++ = ((c
& 0x03) << 6) | d
;
426 if (TLINE(y
)[i
- 1].mode
& ATTR_WRAP
)
429 while (i
> 0 && TLINE(y
)[i
- 1].u
== ' ')
436 selstart(int col
, int row
, int snap
)
439 sel
.mode
= SEL_EMPTY
;
440 sel
.type
= SEL_REGULAR
;
441 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
443 sel
.oe
.x
= sel
.ob
.x
= col
;
444 sel
.oe
.y
= sel
.ob
.y
= row
;
448 sel
.mode
= SEL_READY
;
449 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
453 selextend(int col
, int row
, int type
, int done
)
455 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
457 if (sel
.mode
== SEL_IDLE
)
459 if (done
&& sel
.mode
== SEL_EMPTY
) {
475 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
476 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
478 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
486 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
487 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
488 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
490 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
491 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
493 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
494 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
496 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
497 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
499 /* expand selection over line breaks */
500 if (sel
.type
== SEL_RECTANGULAR
)
502 i
= tlinelen(sel
.nb
.y
);
505 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
506 sel
.ne
.x
= term
.col
- 1;
510 selected(int x
, int y
)
512 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
513 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
516 if (sel
.type
== SEL_RECTANGULAR
)
517 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
518 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
520 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
521 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
522 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
526 selsnap(int *x
, int *y
, int direction
)
528 int newx
, newy
, xt
, yt
;
529 int delim
, prevdelim
;
530 const Glyph
*gp
, *prevgp
;
535 * Snap around if the word wraps around at the end or
536 * beginning of a line.
538 prevgp
= &TLINE(*y
)[*x
];
539 prevdelim
= ISDELIM(prevgp
->u
);
541 newx
= *x
+ direction
;
543 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
545 newx
= (newx
+ term
.col
) % term
.col
;
546 if (!BETWEEN(newy
, 0, term
.row
- 1))
552 yt
= newy
, xt
= newx
;
553 if (!(TLINE(yt
)[xt
].mode
& ATTR_WRAP
))
557 if (newx
>= tlinelen(newy
))
560 gp
= &TLINE(newy
)[newx
];
561 delim
= ISDELIM(gp
->u
);
562 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
563 || (delim
&& gp
->u
!= prevgp
->u
)))
574 * Snap around if the the previous line or the current one
575 * has set ATTR_WRAP at its end. Then the whole next or
576 * previous line will be selected.
578 *x
= (direction
< 0) ? 0 : term
.col
- 1;
580 for (; *y
> 0; *y
+= direction
) {
581 if (!(TLINE(*y
-1)[term
.col
-1].mode
586 } else if (direction
> 0) {
587 for (; *y
< term
.row
-1; *y
+= direction
) {
588 if (!(TLINE(*y
)[term
.col
-1].mode
602 int y
, bufsize
, lastx
, linelen
;
603 const Glyph
*gp
, *last
;
608 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
609 ptr
= str
= xmalloc(bufsize
);
611 /* append every set & selected glyph to the selection */
612 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
613 if ((linelen
= tlinelen(y
)) == 0) {
618 if (sel
.type
== SEL_RECTANGULAR
) {
619 gp
= &TLINE(y
)[sel
.nb
.x
];
622 gp
= &TLINE(y
)[sel
.nb
.y
== y
? sel
.nb
.x
: 0];
623 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
625 last
= &TLINE(y
)[MIN(lastx
, linelen
-1)];
626 while (last
>= gp
&& last
->u
== ' ')
629 for ( ; gp
<= last
; ++gp
) {
630 if (gp
->mode
& ATTR_WDUMMY
)
633 ptr
+= utf8encode(gp
->u
, ptr
);
637 * Copy and pasting of line endings is inconsistent
638 * in the inconsistent terminal and GUI world.
639 * The best solution seems like to produce '\n' when
640 * something is copied from st and convert '\n' to
641 * '\r', when something to be pasted is received by
643 * FIXME: Fix the computer world.
645 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) &&
646 (!(last
->mode
& ATTR_WRAP
) || sel
.type
== SEL_RECTANGULAR
))
660 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
664 die(const char *errstr
, ...)
668 va_start(ap
, errstr
);
669 vfprintf(stderr
, errstr
, ap
);
675 execsh(char *cmd
, char **args
)
677 char *sh
, *prog
, *arg
;
678 const struct passwd
*pw
;
681 if ((pw
= getpwuid(getuid())) == NULL
) {
683 die("getpwuid: %s\n", strerror(errno
));
685 die("who are you?\n");
688 if ((sh
= getenv("SHELL")) == NULL
)
689 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
696 arg
= utmp
? utmp
: sh
;
704 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
709 setenv("LOGNAME", pw
->pw_name
, 1);
710 setenv("USER", pw
->pw_name
, 1);
711 setenv("SHELL", sh
, 1);
712 setenv("HOME", pw
->pw_dir
, 1);
713 setenv("TERM", termname
, 1);
715 signal(SIGCHLD
, SIG_DFL
);
716 signal(SIGHUP
, SIG_DFL
);
717 signal(SIGINT
, SIG_DFL
);
718 signal(SIGQUIT
, SIG_DFL
);
719 signal(SIGTERM
, SIG_DFL
);
720 signal(SIGALRM
, SIG_DFL
);
732 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
733 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
738 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
739 die("child exited with status %d\n", WEXITSTATUS(stat
));
740 else if (WIFSIGNALED(stat
))
741 die("child terminated due to signal %d\n", WTERMSIG(stat
));
748 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
751 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
752 die("incorrect stty parameters\n");
753 memcpy(cmd
, stty_args
, n
);
755 siz
= sizeof(cmd
) - n
;
756 for (p
= args
; p
&& (s
= *p
); ++p
) {
757 if ((n
= strlen(s
)) > siz
-1)
758 die("stty parameter length too long\n");
765 if (system(cmd
) != 0)
766 perror("Couldn't call stty");
770 ttynew(const char *line
, char *cmd
, const char *out
, char **args
)
775 term
.mode
|= MODE_PRINT
;
776 iofd
= (!strcmp(out
, "-")) ?
777 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
779 fprintf(stderr
, "Error opening %s:%s\n",
780 out
, strerror(errno
));
785 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
786 die("open line '%s' failed: %s\n",
787 line
, strerror(errno
));
793 /* seems to work fine on linux, openbsd and freebsd */
794 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
795 die("openpty failed: %s\n", strerror(errno
));
797 switch (pid
= fork()) {
799 die("fork failed: %s\n", strerror(errno
));
804 setsid(); /* create a new process group */
808 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
809 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
813 if (pledge("stdio getpw proc exec", NULL
) == -1)
820 if (pledge("stdio rpath tty proc", NULL
) == -1)
825 signal(SIGCHLD
, sigchld
);
834 static char buf
[BUFSIZ
];
835 static int buflen
= 0;
838 /* append read bytes to unprocessed bytes */
839 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
845 die("couldn't read from shell: %s\n", strerror(errno
));
848 written
= twrite(buf
, buflen
, 0);
850 /* keep any incomplete UTF-8 byte sequence for the next call */
852 memmove(buf
, buf
+ written
, buflen
);
858 ttywrite(const char *s
, size_t n
, int may_echo
)
861 Arg arg
= (Arg
) { .i
= term
.scr
};
865 if (may_echo
&& IS_SET(MODE_ECHO
))
868 if (!IS_SET(MODE_CRLF
)) {
873 /* This is similar to how the kernel handles ONLCR for ttys */
877 ttywriteraw("\r\n", 2);
879 next
= memchr(s
, '\r', n
);
880 DEFAULT(next
, s
+ n
);
881 ttywriteraw(s
, next
- s
);
889 ttywriteraw(const char *s
, size_t n
)
896 * Remember that we are using a pty, which might be a modem line.
897 * Writing too much will clog the line. That's why we are doing this
899 * FIXME: Migrate the world to Plan 9.
907 /* Check if we can write. */
908 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
911 die("select failed: %s\n", strerror(errno
));
913 if (FD_ISSET(cmdfd
, &wfd
)) {
915 * Only write the bytes written by ttywrite() or the
916 * default of 256. This seems to be a reasonable value
917 * for a serial line. Bigger values might clog the I/O.
919 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
923 * We weren't able to write out everything.
924 * This means the buffer is getting full
932 /* All bytes have been written. */
936 if (FD_ISSET(cmdfd
, &rfd
))
942 die("write error on tty: %s\n", strerror(errno
));
946 ttyresize(int tw
, int th
)
954 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
955 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
961 /* Send SIGHUP to shell */
970 for (i
= 0; i
< term
.row
-1; i
++) {
971 for (j
= 0; j
< term
.col
-1; j
++) {
972 if (term
.line
[i
][j
].mode
& attr
)
981 tsetdirt(int top
, int bot
)
985 LIMIT(top
, 0, term
.row
-1);
986 LIMIT(bot
, 0, term
.row
-1);
988 for (i
= top
; i
<= bot
; i
++)
993 tsetdirtattr(int attr
)
997 for (i
= 0; i
< term
.row
-1; i
++) {
998 for (j
= 0; j
< term
.col
-1; j
++) {
999 if (term
.line
[i
][j
].mode
& attr
) {
1010 tsetdirt(0, term
.row
-1);
1016 static TCursor c
[2];
1017 int alt
= IS_SET(MODE_ALTSCREEN
);
1019 if (mode
== CURSOR_SAVE
) {
1021 } else if (mode
== CURSOR_LOAD
) {
1023 tmoveto(c
[alt
].x
, c
[alt
].y
);
1032 term
.c
= (TCursor
){{
1036 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1038 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1039 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1042 term
.bot
= term
.row
- 1;
1043 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1044 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1047 for (i
= 0; i
< 2; i
++) {
1049 tcursor(CURSOR_SAVE
);
1050 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1056 tnew(int col
, int row
)
1058 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1065 return IS_SET(MODE_ALTSCREEN
);
1071 Line
*tmp
= term
.line
;
1073 term
.line
= term
.alt
;
1075 term
.mode
^= MODE_ALTSCREEN
;
1080 kscrolldown(const Arg
* a
)
1085 n
= (term
.row
+ n
) / 2;
1098 kscrollup(const Arg
* a
)
1103 n
= (term
.row
+ n
) / 2;
1105 if (term
.scr
<= HISTSIZE
-n
) {
1113 tscrolldown(int orig
, int n
, int copyhist
)
1118 LIMIT(n
, 0, term
.bot
-orig
+1);
1121 term
.histi
= (term
.histi
- 1 + HISTSIZE
) % HISTSIZE
;
1122 temp
= term
.hist
[term
.histi
];
1123 term
.hist
[term
.histi
] = term
.line
[term
.bot
];
1124 term
.line
[term
.bot
] = temp
;
1127 tsetdirt(orig
, term
.bot
-n
);
1128 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1130 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1131 temp
= term
.line
[i
];
1132 term
.line
[i
] = term
.line
[i
-n
];
1133 term
.line
[i
-n
] = temp
;
1141 tscrollup(int orig
, int n
, int copyhist
)
1146 LIMIT(n
, 0, term
.bot
-orig
+1);
1149 term
.histi
= (term
.histi
+ 1) % HISTSIZE
;
1150 temp
= term
.hist
[term
.histi
];
1151 term
.hist
[term
.histi
] = term
.line
[orig
];
1152 term
.line
[orig
] = temp
;
1155 if (term
.scr
> 0 && term
.scr
< HISTSIZE
)
1156 term
.scr
= MIN(term
.scr
+ n
, HISTSIZE
-1);
1158 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1159 tsetdirt(orig
+n
, term
.bot
);
1161 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1162 temp
= term
.line
[i
];
1163 term
.line
[i
] = term
.line
[i
+n
];
1164 term
.line
[i
+n
] = temp
;
1168 selscroll(orig
, -n
);
1172 selscroll(int orig
, int n
)
1177 if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
) != BETWEEN(sel
.ne
.y
, orig
, term
.bot
)) {
1179 } else if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
)) {
1182 if (sel
.ob
.y
< term
.top
|| sel
.ob
.y
> term
.bot
||
1183 sel
.oe
.y
< term
.top
|| sel
.oe
.y
> term
.bot
) {
1192 tnewline(int first_col
)
1196 if (y
== term
.bot
) {
1197 tscrollup(term
.top
, 1, 1);
1201 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1207 char *p
= csiescseq
.buf
, *np
;
1216 csiescseq
.buf
[csiescseq
.len
] = '\0';
1217 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1219 v
= strtol(p
, &np
, 10);
1222 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1224 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1226 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1230 csiescseq
.mode
[0] = *p
++;
1231 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1234 /* for absolute user moves, when decom is set */
1236 tmoveato(int x
, int y
)
1238 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1242 tmoveto(int x
, int y
)
1246 if (term
.c
.state
& CURSOR_ORIGIN
) {
1251 maxy
= term
.row
- 1;
1253 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1254 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1255 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1259 tsetchar(Rune u
, const Glyph
*attr
, int x
, int y
)
1261 static const char *vt100_0
[62] = { /* 0x41 - 0x7e */
1262 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1263 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1264 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1265 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1266 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1267 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1268 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1269 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1273 * The table is proudly stolen from rxvt.
1275 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1276 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1277 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1279 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1280 if (x
+1 < term
.col
) {
1281 term
.line
[y
][x
+1].u
= ' ';
1282 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1284 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1285 term
.line
[y
][x
-1].u
= ' ';
1286 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1290 term
.line
[y
][x
] = *attr
;
1291 term
.line
[y
][x
].u
= u
;
1295 tclearregion(int x1
, int y1
, int x2
, int y2
)
1301 temp
= x1
, x1
= x2
, x2
= temp
;
1303 temp
= y1
, y1
= y2
, y2
= temp
;
1305 LIMIT(x1
, 0, term
.col
-1);
1306 LIMIT(x2
, 0, term
.col
-1);
1307 LIMIT(y1
, 0, term
.row
-1);
1308 LIMIT(y2
, 0, term
.row
-1);
1310 for (y
= y1
; y
<= y2
; y
++) {
1312 for (x
= x1
; x
<= x2
; x
++) {
1313 gp
= &term
.line
[y
][x
];
1316 gp
->fg
= term
.c
.attr
.fg
;
1317 gp
->bg
= term
.c
.attr
.bg
;
1330 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1334 size
= term
.col
- src
;
1335 line
= term
.line
[term
.c
.y
];
1337 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1338 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1347 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1351 size
= term
.col
- dst
;
1352 line
= term
.line
[term
.c
.y
];
1354 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1355 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1359 tinsertblankline(int n
)
1361 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1362 tscrolldown(term
.c
.y
, n
, 0);
1368 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1369 tscrollup(term
.c
.y
, n
, 0);
1373 tdefcolor(const int *attr
, int *npar
, int l
)
1378 switch (attr
[*npar
+ 1]) {
1379 case 2: /* direct color in RGB space */
1380 if (*npar
+ 4 >= l
) {
1382 "erresc(38): Incorrect number of parameters (%d)\n",
1386 r
= attr
[*npar
+ 2];
1387 g
= attr
[*npar
+ 3];
1388 b
= attr
[*npar
+ 4];
1390 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1391 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1394 idx
= TRUECOLOR(r
, g
, b
);
1396 case 5: /* indexed color */
1397 if (*npar
+ 2 >= l
) {
1399 "erresc(38): Incorrect number of parameters (%d)\n",
1404 if (!BETWEEN(attr
[*npar
], 0, 255))
1405 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1409 case 0: /* implemented defined (only foreground) */
1410 case 1: /* transparent */
1411 case 3: /* direct color in CMY space */
1412 case 4: /* direct color in CMYK space */
1415 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1423 tsetattr(const int *attr
, int l
)
1428 for (i
= 0; i
< l
; i
++) {
1431 term
.c
.attr
.mode
&= ~(
1440 term
.c
.attr
.fg
= defaultfg
;
1441 term
.c
.attr
.bg
= defaultbg
;
1444 term
.c
.attr
.mode
|= ATTR_BOLD
;
1447 term
.c
.attr
.mode
|= ATTR_FAINT
;
1450 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1453 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1455 case 5: /* slow blink */
1457 case 6: /* rapid blink */
1458 term
.c
.attr
.mode
|= ATTR_BLINK
;
1461 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1464 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1467 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1470 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1473 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1476 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1479 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1482 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1485 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1488 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1491 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1492 term
.c
.attr
.fg
= idx
;
1495 term
.c
.attr
.fg
= defaultfg
;
1498 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1499 term
.c
.attr
.bg
= idx
;
1502 term
.c
.attr
.bg
= defaultbg
;
1505 if (BETWEEN(attr
[i
], 30, 37)) {
1506 term
.c
.attr
.fg
= attr
[i
] - 30;
1507 } else if (BETWEEN(attr
[i
], 40, 47)) {
1508 term
.c
.attr
.bg
= attr
[i
] - 40;
1509 } else if (BETWEEN(attr
[i
], 90, 97)) {
1510 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1511 } else if (BETWEEN(attr
[i
], 100, 107)) {
1512 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1515 "erresc(default): gfx attr %d unknown\n",
1525 tsetscroll(int t
, int b
)
1529 LIMIT(t
, 0, term
.row
-1);
1530 LIMIT(b
, 0, term
.row
-1);
1541 tsetmode(int priv
, int set
, const int *args
, int narg
)
1543 int alt
; const int *lim
;
1545 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1548 case 1: /* DECCKM -- Cursor key */
1549 xsetmode(set
, MODE_APPCURSOR
);
1551 case 5: /* DECSCNM -- Reverse video */
1552 xsetmode(set
, MODE_REVERSE
);
1554 case 6: /* DECOM -- Origin */
1555 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1558 case 7: /* DECAWM -- Auto wrap */
1559 MODBIT(term
.mode
, set
, MODE_WRAP
);
1561 case 0: /* Error (IGNORED) */
1562 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1563 case 3: /* DECCOLM -- Column (IGNORED) */
1564 case 4: /* DECSCLM -- Scroll (IGNORED) */
1565 case 8: /* DECARM -- Auto repeat (IGNORED) */
1566 case 18: /* DECPFF -- Printer feed (IGNORED) */
1567 case 19: /* DECPEX -- Printer extent (IGNORED) */
1568 case 42: /* DECNRCM -- National characters (IGNORED) */
1569 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1571 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1572 xsetmode(!set
, MODE_HIDE
);
1574 case 9: /* X10 mouse compatibility mode */
1575 xsetpointermotion(0);
1576 xsetmode(0, MODE_MOUSE
);
1577 xsetmode(set
, MODE_MOUSEX10
);
1579 case 1000: /* 1000: report button press */
1580 xsetpointermotion(0);
1581 xsetmode(0, MODE_MOUSE
);
1582 xsetmode(set
, MODE_MOUSEBTN
);
1584 case 1002: /* 1002: report motion on button press */
1585 xsetpointermotion(0);
1586 xsetmode(0, MODE_MOUSE
);
1587 xsetmode(set
, MODE_MOUSEMOTION
);
1589 case 1003: /* 1003: enable all mouse motions */
1590 xsetpointermotion(set
);
1591 xsetmode(0, MODE_MOUSE
);
1592 xsetmode(set
, MODE_MOUSEMANY
);
1594 case 1004: /* 1004: send focus events to tty */
1595 xsetmode(set
, MODE_FOCUS
);
1597 case 1006: /* 1006: extended reporting mode */
1598 xsetmode(set
, MODE_MOUSESGR
);
1601 xsetmode(set
, MODE_8BIT
);
1603 case 1049: /* swap screen & set/restore cursor as xterm */
1604 if (!allowaltscreen
)
1606 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1608 case 47: /* swap screen */
1610 if (!allowaltscreen
)
1612 alt
= IS_SET(MODE_ALTSCREEN
);
1614 tclearregion(0, 0, term
.col
-1,
1617 if (set
^ alt
) /* set is always 1 or 0 */
1623 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1625 case 2004: /* 2004: bracketed paste mode */
1626 xsetmode(set
, MODE_BRCKTPASTE
);
1628 /* Not implemented mouse modes. See comments there. */
1629 case 1001: /* mouse highlight mode; can hang the
1630 terminal by design when implemented. */
1631 case 1005: /* UTF-8 mouse mode; will confuse
1632 applications not supporting UTF-8
1634 case 1015: /* urxvt mangled mouse mode; incompatible
1635 and can be mistaken for other control
1640 "erresc: unknown private set/reset mode %d\n",
1646 case 0: /* Error (IGNORED) */
1649 xsetmode(set
, MODE_KBDLOCK
);
1651 case 4: /* IRM -- Insertion-replacement */
1652 MODBIT(term
.mode
, set
, MODE_INSERT
);
1654 case 12: /* SRM -- Send/Receive */
1655 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1657 case 20: /* LNM -- Linefeed/new line */
1658 MODBIT(term
.mode
, set
, MODE_CRLF
);
1662 "erresc: unknown set/reset mode %d\n",
1676 switch (csiescseq
.mode
[0]) {
1679 fprintf(stderr
, "erresc: unknown csi ");
1683 case '@': /* ICH -- Insert <n> blank char */
1684 DEFAULT(csiescseq
.arg
[0], 1);
1685 tinsertblank(csiescseq
.arg
[0]);
1687 case 'A': /* CUU -- Cursor <n> Up */
1688 DEFAULT(csiescseq
.arg
[0], 1);
1689 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1691 case 'B': /* CUD -- Cursor <n> Down */
1692 case 'e': /* VPR --Cursor <n> Down */
1693 DEFAULT(csiescseq
.arg
[0], 1);
1694 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1696 case 'i': /* MC -- Media Copy */
1697 switch (csiescseq
.arg
[0]) {
1702 tdumpline(term
.c
.y
);
1708 term
.mode
&= ~MODE_PRINT
;
1711 term
.mode
|= MODE_PRINT
;
1715 case 'c': /* DA -- Device Attributes */
1716 if (csiescseq
.arg
[0] == 0)
1717 ttywrite(vtiden
, strlen(vtiden
), 0);
1719 case 'b': /* REP -- if last char is printable print it <n> more times */
1720 DEFAULT(csiescseq
.arg
[0], 1);
1722 while (csiescseq
.arg
[0]-- > 0)
1725 case 'C': /* CUF -- Cursor <n> Forward */
1726 case 'a': /* HPR -- Cursor <n> Forward */
1727 DEFAULT(csiescseq
.arg
[0], 1);
1728 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1730 case 'D': /* CUB -- Cursor <n> Backward */
1731 DEFAULT(csiescseq
.arg
[0], 1);
1732 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1734 case 'E': /* CNL -- Cursor <n> Down and first col */
1735 DEFAULT(csiescseq
.arg
[0], 1);
1736 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1738 case 'F': /* CPL -- Cursor <n> Up and first col */
1739 DEFAULT(csiescseq
.arg
[0], 1);
1740 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1742 case 'g': /* TBC -- Tabulation clear */
1743 switch (csiescseq
.arg
[0]) {
1744 case 0: /* clear current tab stop */
1745 term
.tabs
[term
.c
.x
] = 0;
1747 case 3: /* clear all the tabs */
1748 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1754 case 'G': /* CHA -- Move to <col> */
1756 DEFAULT(csiescseq
.arg
[0], 1);
1757 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1759 case 'H': /* CUP -- Move to <row> <col> */
1761 DEFAULT(csiescseq
.arg
[0], 1);
1762 DEFAULT(csiescseq
.arg
[1], 1);
1763 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1765 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1766 DEFAULT(csiescseq
.arg
[0], 1);
1767 tputtab(csiescseq
.arg
[0]);
1769 case 'J': /* ED -- Clear screen */
1770 switch (csiescseq
.arg
[0]) {
1772 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1773 if (term
.c
.y
< term
.row
-1) {
1774 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1780 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1781 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1784 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1790 case 'K': /* EL -- Clear line */
1791 switch (csiescseq
.arg
[0]) {
1793 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1797 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1800 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1804 case 'S': /* SU -- Scroll <n> line up */
1805 DEFAULT(csiescseq
.arg
[0], 1);
1806 tscrollup(term
.top
, csiescseq
.arg
[0], 0);
1808 case 'T': /* SD -- Scroll <n> line down */
1809 DEFAULT(csiescseq
.arg
[0], 1);
1810 tscrolldown(term
.top
, csiescseq
.arg
[0], 0);
1812 case 'L': /* IL -- Insert <n> blank lines */
1813 DEFAULT(csiescseq
.arg
[0], 1);
1814 tinsertblankline(csiescseq
.arg
[0]);
1816 case 'l': /* RM -- Reset Mode */
1817 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1819 case 'M': /* DL -- Delete <n> lines */
1820 DEFAULT(csiescseq
.arg
[0], 1);
1821 tdeleteline(csiescseq
.arg
[0]);
1823 case 'X': /* ECH -- Erase <n> char */
1824 DEFAULT(csiescseq
.arg
[0], 1);
1825 tclearregion(term
.c
.x
, term
.c
.y
,
1826 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1828 case 'P': /* DCH -- Delete <n> char */
1829 DEFAULT(csiescseq
.arg
[0], 1);
1830 tdeletechar(csiescseq
.arg
[0]);
1832 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1833 DEFAULT(csiescseq
.arg
[0], 1);
1834 tputtab(-csiescseq
.arg
[0]);
1836 case 'd': /* VPA -- Move to <row> */
1837 DEFAULT(csiescseq
.arg
[0], 1);
1838 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1840 case 'h': /* SM -- Set terminal mode */
1841 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1843 case 'm': /* SGR -- Terminal attribute (color) */
1844 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1846 case 'n': /* DSR – Device Status Report (cursor position) */
1847 if (csiescseq
.arg
[0] == 6) {
1848 len
= snprintf(buf
, sizeof(buf
), "\033[%i;%iR",
1849 term
.c
.y
+1, term
.c
.x
+1);
1850 ttywrite(buf
, len
, 0);
1853 case 'r': /* DECSTBM -- Set Scrolling Region */
1854 if (csiescseq
.priv
) {
1857 DEFAULT(csiescseq
.arg
[0], 1);
1858 DEFAULT(csiescseq
.arg
[1], term
.row
);
1859 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1863 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1864 tcursor(CURSOR_SAVE
);
1866 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1867 tcursor(CURSOR_LOAD
);
1870 switch (csiescseq
.mode
[1]) {
1871 case 'q': /* DECSCUSR -- Set Cursor Style */
1872 if (xsetcursor(csiescseq
.arg
[0]))
1888 fprintf(stderr
, "ESC[");
1889 for (i
= 0; i
< csiescseq
.len
; i
++) {
1890 c
= csiescseq
.buf
[i
] & 0xff;
1893 } else if (c
== '\n') {
1894 fprintf(stderr
, "(\\n)");
1895 } else if (c
== '\r') {
1896 fprintf(stderr
, "(\\r)");
1897 } else if (c
== 0x1b) {
1898 fprintf(stderr
, "(\\e)");
1900 fprintf(stderr
, "(%02x)", c
);
1909 memset(&csiescseq
, 0, sizeof(csiescseq
));
1913 osc4_color_response(int num
)
1917 unsigned char r
, g
, b
;
1919 if (xgetcolor(num
, &r
, &g
, &b
)) {
1920 fprintf(stderr
, "erresc: failed to fetch osc4 color %d\n", num
);
1924 n
= snprintf(buf
, sizeof buf
, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1925 num
, r
, r
, g
, g
, b
, b
);
1927 ttywrite(buf
, n
, 1);
1931 osc_color_response(int index
, int num
)
1935 unsigned char r
, g
, b
;
1937 if (xgetcolor(index
, &r
, &g
, &b
)) {
1938 fprintf(stderr
, "erresc: failed to fetch osc color %d\n", index
);
1942 n
= snprintf(buf
, sizeof buf
, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1943 num
, r
, r
, g
, g
, b
, b
);
1945 ttywrite(buf
, n
, 1);
1951 char *p
= NULL
, *dec
;
1954 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1956 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1958 switch (strescseq
.type
) {
1959 case ']': /* OSC -- Operating System Command */
1963 xsettitle(strescseq
.args
[1]);
1964 xseticontitle(strescseq
.args
[1]);
1969 xseticontitle(strescseq
.args
[1]);
1973 xsettitle(strescseq
.args
[1]);
1976 if (narg
> 2 && allowwindowops
) {
1977 dec
= base64dec(strescseq
.args
[2]);
1982 fprintf(stderr
, "erresc: invalid base64\n");
1990 p
= strescseq
.args
[1];
1992 if (!strcmp(p
, "?"))
1993 osc_color_response(defaultfg
, 10);
1994 else if (xsetcolorname(defaultfg
, p
))
1995 fprintf(stderr
, "erresc: invalid foreground color: %s\n", p
);
2003 p
= strescseq
.args
[1];
2005 if (!strcmp(p
, "?"))
2006 osc_color_response(defaultbg
, 11);
2007 else if (xsetcolorname(defaultbg
, p
))
2008 fprintf(stderr
, "erresc: invalid background color: %s\n", p
);
2016 p
= strescseq
.args
[1];
2018 if (!strcmp(p
, "?"))
2019 osc_color_response(defaultcs
, 12);
2020 else if (xsetcolorname(defaultcs
, p
))
2021 fprintf(stderr
, "erresc: invalid cursor color: %s\n", p
);
2025 case 4: /* color set */
2028 p
= strescseq
.args
[2];
2030 case 104: /* color reset */
2031 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
2033 if (p
&& !strcmp(p
, "?"))
2034 osc4_color_response(j
);
2035 else if (xsetcolorname(j
, p
)) {
2036 if (par
== 104 && narg
<= 1)
2037 return; /* color reset without parameter */
2038 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
2039 j
, p
? p
: "(null)");
2042 * TODO if defaultbg color is changed, borders
2050 case 'k': /* old title set compatibility */
2051 xsettitle(strescseq
.args
[0]);
2053 case 'P': /* DCS -- Device Control String */
2054 case '_': /* APC -- Application Program Command */
2055 case '^': /* PM -- Privacy Message */
2059 fprintf(stderr
, "erresc: unknown str ");
2067 char *p
= strescseq
.buf
;
2070 strescseq
.buf
[strescseq
.len
] = '\0';
2075 while (strescseq
.narg
< STR_ARG_SIZ
) {
2076 strescseq
.args
[strescseq
.narg
++] = p
;
2077 while ((c
= *p
) != ';' && c
!= '\0')
2091 fprintf(stderr
, "ESC%c", strescseq
.type
);
2092 for (i
= 0; i
< strescseq
.len
; i
++) {
2093 c
= strescseq
.buf
[i
] & 0xff;
2097 } else if (isprint(c
)) {
2099 } else if (c
== '\n') {
2100 fprintf(stderr
, "(\\n)");
2101 } else if (c
== '\r') {
2102 fprintf(stderr
, "(\\r)");
2103 } else if (c
== 0x1b) {
2104 fprintf(stderr
, "(\\e)");
2106 fprintf(stderr
, "(%02x)", c
);
2109 fprintf(stderr
, "ESC\\\n");
2115 strescseq
= (STREscape
){
2116 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
2122 sendbreak(const Arg
*arg
)
2124 if (tcsendbreak(cmdfd
, 0))
2125 perror("Error sending break");
2129 tprinter(char *s
, size_t len
)
2131 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
2132 perror("Error writing to output file");
2139 toggleprinter(const Arg
*arg
)
2141 term
.mode
^= MODE_PRINT
;
2145 printscreen(const Arg
*arg
)
2151 printsel(const Arg
*arg
)
2161 if ((ptr
= getsel())) {
2162 tprinter(ptr
, strlen(ptr
));
2171 const Glyph
*bp
, *end
;
2173 bp
= &term
.line
[n
][0];
2174 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2175 if (bp
!= end
|| bp
->u
!= ' ') {
2176 for ( ; bp
<= end
; ++bp
)
2177 tprinter(buf
, utf8encode(bp
->u
, buf
));
2187 for (i
= 0; i
< term
.row
; ++i
)
2197 while (x
< term
.col
&& n
--)
2198 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2201 while (x
> 0 && n
++)
2202 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2205 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2209 tdefutf8(char ascii
)
2212 term
.mode
|= MODE_UTF8
;
2213 else if (ascii
== '@')
2214 term
.mode
&= ~MODE_UTF8
;
2218 tdeftran(char ascii
)
2220 static char cs
[] = "0B";
2221 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2224 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2225 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2227 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2236 if (c
== '8') { /* DEC screen alignment test. */
2237 for (x
= 0; x
< term
.col
; ++x
) {
2238 for (y
= 0; y
< term
.row
; ++y
)
2239 tsetchar('E', &term
.c
.attr
, x
, y
);
2245 tstrsequence(uchar c
)
2248 case 0x90: /* DCS -- Device Control String */
2251 case 0x9f: /* APC -- Application Program Command */
2254 case 0x9e: /* PM -- Privacy Message */
2257 case 0x9d: /* OSC -- Operating System Command */
2263 term
.esc
|= ESC_STR
;
2267 tcontrolcode(uchar ascii
)
2274 tmoveto(term
.c
.x
-1, term
.c
.y
);
2277 tmoveto(0, term
.c
.y
);
2282 /* go to first col if the mode is set */
2283 tnewline(IS_SET(MODE_CRLF
));
2285 case '\a': /* BEL */
2286 if (term
.esc
& ESC_STR_END
) {
2287 /* backwards compatibility to xterm */
2293 case '\033': /* ESC */
2295 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2296 term
.esc
|= ESC_START
;
2298 case '\016': /* SO (LS1 -- Locking shift 1) */
2299 case '\017': /* SI (LS0 -- Locking shift 0) */
2300 term
.charset
= 1 - (ascii
- '\016');
2302 case '\032': /* SUB */
2303 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2305 case '\030': /* CAN */
2308 case '\005': /* ENQ (IGNORED) */
2309 case '\000': /* NUL (IGNORED) */
2310 case '\021': /* XON (IGNORED) */
2311 case '\023': /* XOFF (IGNORED) */
2312 case 0177: /* DEL (IGNORED) */
2314 case 0x80: /* TODO: PAD */
2315 case 0x81: /* TODO: HOP */
2316 case 0x82: /* TODO: BPH */
2317 case 0x83: /* TODO: NBH */
2318 case 0x84: /* TODO: IND */
2320 case 0x85: /* NEL -- Next line */
2321 tnewline(1); /* always go to first col */
2323 case 0x86: /* TODO: SSA */
2324 case 0x87: /* TODO: ESA */
2326 case 0x88: /* HTS -- Horizontal tab stop */
2327 term
.tabs
[term
.c
.x
] = 1;
2329 case 0x89: /* TODO: HTJ */
2330 case 0x8a: /* TODO: VTS */
2331 case 0x8b: /* TODO: PLD */
2332 case 0x8c: /* TODO: PLU */
2333 case 0x8d: /* TODO: RI */
2334 case 0x8e: /* TODO: SS2 */
2335 case 0x8f: /* TODO: SS3 */
2336 case 0x91: /* TODO: PU1 */
2337 case 0x92: /* TODO: PU2 */
2338 case 0x93: /* TODO: STS */
2339 case 0x94: /* TODO: CCH */
2340 case 0x95: /* TODO: MW */
2341 case 0x96: /* TODO: SPA */
2342 case 0x97: /* TODO: EPA */
2343 case 0x98: /* TODO: SOS */
2344 case 0x99: /* TODO: SGCI */
2346 case 0x9a: /* DECID -- Identify Terminal */
2347 ttywrite(vtiden
, strlen(vtiden
), 0);
2349 case 0x9b: /* TODO: CSI */
2350 case 0x9c: /* TODO: ST */
2352 case 0x90: /* DCS -- Device Control String */
2353 case 0x9d: /* OSC -- Operating System Command */
2354 case 0x9e: /* PM -- Privacy Message */
2355 case 0x9f: /* APC -- Application Program Command */
2356 tstrsequence(ascii
);
2359 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2360 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2364 * returns 1 when the sequence is finished and it hasn't to read
2365 * more characters for this sequence, otherwise 0
2368 eschandle(uchar ascii
)
2372 term
.esc
|= ESC_CSI
;
2375 term
.esc
|= ESC_TEST
;
2378 term
.esc
|= ESC_UTF8
;
2380 case 'P': /* DCS -- Device Control String */
2381 case '_': /* APC -- Application Program Command */
2382 case '^': /* PM -- Privacy Message */
2383 case ']': /* OSC -- Operating System Command */
2384 case 'k': /* old title set compatibility */
2385 tstrsequence(ascii
);
2387 case 'n': /* LS2 -- Locking shift 2 */
2388 case 'o': /* LS3 -- Locking shift 3 */
2389 term
.charset
= 2 + (ascii
- 'n');
2391 case '(': /* GZD4 -- set primary charset G0 */
2392 case ')': /* G1D4 -- set secondary charset G1 */
2393 case '*': /* G2D4 -- set tertiary charset G2 */
2394 case '+': /* G3D4 -- set quaternary charset G3 */
2395 term
.icharset
= ascii
- '(';
2396 term
.esc
|= ESC_ALTCHARSET
;
2398 case 'D': /* IND -- Linefeed */
2399 if (term
.c
.y
== term
.bot
) {
2400 tscrollup(term
.top
, 1, 1);
2402 tmoveto(term
.c
.x
, term
.c
.y
+1);
2405 case 'E': /* NEL -- Next line */
2406 tnewline(1); /* always go to first col */
2408 case 'H': /* HTS -- Horizontal tab stop */
2409 term
.tabs
[term
.c
.x
] = 1;
2411 case 'M': /* RI -- Reverse index */
2412 if (term
.c
.y
== term
.top
) {
2413 tscrolldown(term
.top
, 1, 1);
2415 tmoveto(term
.c
.x
, term
.c
.y
-1);
2418 case 'Z': /* DECID -- Identify Terminal */
2419 ttywrite(vtiden
, strlen(vtiden
), 0);
2421 case 'c': /* RIS -- Reset to initial state */
2426 case '=': /* DECPAM -- Application keypad */
2427 xsetmode(1, MODE_APPKEYPAD
);
2429 case '>': /* DECPNM -- Normal keypad */
2430 xsetmode(0, MODE_APPKEYPAD
);
2432 case '7': /* DECSC -- Save Cursor */
2433 tcursor(CURSOR_SAVE
);
2435 case '8': /* DECRC -- Restore Cursor */
2436 tcursor(CURSOR_LOAD
);
2438 case '\\': /* ST -- String Terminator */
2439 if (term
.esc
& ESC_STR_END
)
2443 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2444 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2458 control
= ISCONTROL(u
);
2459 if (u
< 127 || !IS_SET(MODE_UTF8
)) {
2463 len
= utf8encode(u
, c
);
2464 if (!control
&& (width
= wcwidth(u
)) == -1)
2468 if (IS_SET(MODE_PRINT
))
2472 * STR sequence must be checked before anything else
2473 * because it uses all following characters until it
2474 * receives a ESC, a SUB, a ST or any other C1 control
2477 if (term
.esc
& ESC_STR
) {
2478 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2480 term
.esc
&= ~(ESC_START
|ESC_STR
);
2481 term
.esc
|= ESC_STR_END
;
2482 goto check_control_code
;
2485 if (strescseq
.len
+len
>= strescseq
.siz
) {
2487 * Here is a bug in terminals. If the user never sends
2488 * some code to stop the str or esc command, then st
2489 * will stop responding. But this is better than
2490 * silently failing with unknown characters. At least
2491 * then users will report back.
2493 * In the case users ever get fixed, here is the code:
2499 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2502 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2505 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2506 strescseq
.len
+= len
;
2512 * Actions of control codes must be performed as soon they arrive
2513 * because they can be embedded inside a control sequence, and
2514 * they must not cause conflicts with sequences.
2519 * control codes are not shown ever
2524 } else if (term
.esc
& ESC_START
) {
2525 if (term
.esc
& ESC_CSI
) {
2526 csiescseq
.buf
[csiescseq
.len
++] = u
;
2527 if (BETWEEN(u
, 0x40, 0x7E)
2528 || csiescseq
.len
>= \
2529 sizeof(csiescseq
.buf
)-1) {
2535 } else if (term
.esc
& ESC_UTF8
) {
2537 } else if (term
.esc
& ESC_ALTCHARSET
) {
2539 } else if (term
.esc
& ESC_TEST
) {
2544 /* sequence already finished */
2548 * All characters which form part of a sequence are not
2553 if (selected(term
.c
.x
, term
.c
.y
))
2556 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2557 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2558 gp
->mode
|= ATTR_WRAP
;
2560 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2563 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2564 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2566 if (term
.c
.x
+width
> term
.col
) {
2568 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2571 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2575 gp
->mode
|= ATTR_WIDE
;
2576 if (term
.c
.x
+1 < term
.col
) {
2577 if (gp
[1].mode
== ATTR_WIDE
&& term
.c
.x
+2 < term
.col
) {
2579 gp
[2].mode
&= ~ATTR_WDUMMY
;
2582 gp
[1].mode
= ATTR_WDUMMY
;
2585 if (term
.c
.x
+width
< term
.col
) {
2586 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2588 term
.c
.state
|= CURSOR_WRAPNEXT
;
2593 twrite(const char *buf
, int buflen
, int show_ctrl
)
2599 for (n
= 0; n
< buflen
; n
+= charsize
) {
2600 if (IS_SET(MODE_UTF8
)) {
2601 /* process a complete utf8 char */
2602 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2609 if (show_ctrl
&& ISCONTROL(u
)) {
2614 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2625 tresize(int col
, int row
)
2628 int minrow
= MIN(row
, term
.row
);
2629 int mincol
= MIN(col
, term
.col
);
2633 if (col
< 1 || row
< 1) {
2635 "tresize: error resizing to %dx%d\n", col
, row
);
2640 * slide screen to keep cursor where we expect it -
2641 * tscrollup would work here, but we can optimize to
2642 * memmove because we're freeing the earlier lines
2644 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2648 /* ensure that both src and dst are not NULL */
2650 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2651 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2653 for (i
+= row
; i
< term
.row
; i
++) {
2658 /* resize to new height */
2659 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2660 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2661 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2662 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2664 for (i
= 0; i
< HISTSIZE
; i
++) {
2665 term
.hist
[i
] = xrealloc(term
.hist
[i
], col
* sizeof(Glyph
));
2666 for (j
= mincol
; j
< col
; j
++) {
2667 term
.hist
[i
][j
] = term
.c
.attr
;
2668 term
.hist
[i
][j
].u
= ' ';
2672 /* resize each row to new width, zero-pad if needed */
2673 for (i
= 0; i
< minrow
; i
++) {
2674 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2675 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2678 /* allocate any new rows */
2679 for (/* i = minrow */; i
< row
; i
++) {
2680 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2681 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2683 if (col
> term
.col
) {
2684 bp
= term
.tabs
+ term
.col
;
2686 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2687 while (--bp
> term
.tabs
&& !*bp
)
2689 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2692 /* update terminal size */
2695 /* reset scrolling region */
2696 tsetscroll(0, row
-1);
2697 /* make use of the LIMIT in tmoveto */
2698 tmoveto(term
.c
.x
, term
.c
.y
);
2699 /* Clearing both screens (it makes dirty all lines) */
2701 for (i
= 0; i
< 2; i
++) {
2702 if (mincol
< col
&& 0 < minrow
) {
2703 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2705 if (0 < col
&& minrow
< row
) {
2706 tclearregion(0, minrow
, col
- 1, row
- 1);
2709 tcursor(CURSOR_LOAD
);
2721 drawregion(int x1
, int y1
, int x2
, int y2
)
2725 for (y
= y1
; y
< y2
; y
++) {
2730 xdrawline(TLINE(y
), x1
, y
, x2
);
2737 int cx
= term
.c
.x
, ocx
= term
.ocx
, ocy
= term
.ocy
;
2742 /* adjust cursor position */
2743 LIMIT(term
.ocx
, 0, term
.col
-1);
2744 LIMIT(term
.ocy
, 0, term
.row
-1);
2745 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2747 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2750 drawregion(0, 0, term
.col
, term
.row
);
2752 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2753 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2755 term
.ocy
= term
.c
.y
;
2757 if (ocx
!= term
.ocx
|| ocy
!= term
.ocy
)
2758 xximspot(term
.ocx
, term
.ocy
);