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) (utf8strchr(worddelimiters, u) != NULL)
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 int 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
[STR_BUF_SIZ
]; /* raw string */
150 int len
; /* raw string length */
151 char *args
[STR_ARG_SIZ
];
152 int narg
; /* nb of args */
155 static void execsh(char *, char **);
156 static void stty(char **);
157 static void sigchld(int);
158 static void ttywriteraw(const char *, size_t);
160 static void csidump(void);
161 static void csihandle(void);
162 static void csiparse(void);
163 static void csireset(void);
164 static int eschandle(uchar
);
165 static void strdump(void);
166 static void strhandle(void);
167 static void strparse(void);
168 static void strreset(void);
170 static void tprinter(char *, size_t);
171 static void tdumpsel(void);
172 static void tdumpline(int);
173 static void tdump(void);
174 static void tclearregion(int, int, int, int);
175 static void tcursor(int);
176 static void tdeletechar(int);
177 static void tdeleteline(int);
178 static void tinsertblank(int);
179 static void tinsertblankline(int);
180 static int tlinelen(int);
181 static void tmoveto(int, int);
182 static void tmoveato(int, int);
183 static void tnewline(int);
184 static void tputtab(int);
185 static void tputc(Rune
);
186 static void treset(void);
187 static void tscrollup(int, int);
188 static void tscrolldown(int, int);
189 static void tsetattr(int *, int);
190 static void tsetchar(Rune
, Glyph
*, int, int);
191 static void tsetdirt(int, int);
192 static void tsetscroll(int, int);
193 static void tswapscreen(void);
194 static void tsetmode(int, int, int *, int);
195 static int twrite(const char *, int, int);
196 static void tfulldirt(void);
197 static void tcontrolcode(uchar
);
198 static void tdectest(char );
199 static void tdefutf8(char);
200 static int32_t tdefcolor(int *, int *, int);
201 static void tdeftran(char);
202 static void tstrsequence(uchar
);
204 static void drawregion(int, int, int, int);
206 static void selnormalize(void);
207 static void selscroll(int, int);
208 static void selsnap(int *, int *, int);
210 static size_t utf8decode(const char *, Rune
*, size_t);
211 static Rune
utf8decodebyte(char, size_t *);
212 static char utf8encodebyte(Rune
, size_t);
213 static char *utf8strchr(char *, Rune
);
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 utf8strchr(char *s
, Rune u
)
347 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
348 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
358 utf8validate(Rune
*u
, size_t i
)
360 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
362 for (i
= 1; *u
> utfmax
[i
]; ++i
)
368 static const char base64_digits
[] = {
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, 62, 0, 0, 0,
371 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
372 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
373 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
374 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
375 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
376 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
377 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
378 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
379 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
384 base64dec_getc(const char **src
)
386 while (**src
&& !isprint(**src
)) (*src
)++;
391 base64dec(const char *src
)
393 size_t in_len
= strlen(src
);
397 in_len
+= 4 - (in_len
% 4);
398 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
400 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
401 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
402 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
403 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
405 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
408 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
411 *dst
++ = ((c
& 0x03) << 6) | d
;
430 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
433 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
440 selstart(int col
, int row
, int snap
)
443 sel
.mode
= SEL_EMPTY
;
444 sel
.type
= SEL_REGULAR
;
445 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
447 sel
.oe
.x
= sel
.ob
.x
= col
;
448 sel
.oe
.y
= sel
.ob
.y
= row
;
452 sel
.mode
= SEL_READY
;
453 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
457 selextend(int col
, int row
, int type
, int done
)
459 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
461 if (sel
.mode
== SEL_IDLE
)
463 if (done
&& sel
.mode
== SEL_EMPTY
) {
479 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
480 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
482 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
490 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
491 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
492 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
494 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
495 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
497 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
498 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
500 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
501 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
503 /* expand selection over line breaks */
504 if (sel
.type
== SEL_RECTANGULAR
)
506 i
= tlinelen(sel
.nb
.y
);
509 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
510 sel
.ne
.x
= term
.col
- 1;
514 selected(int x
, int y
)
516 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
517 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
520 if (sel
.type
== SEL_RECTANGULAR
)
521 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
522 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
524 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
525 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
526 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
530 selsnap(int *x
, int *y
, int direction
)
532 int newx
, newy
, xt
, yt
;
533 int delim
, prevdelim
;
539 * Snap around if the word wraps around at the end or
540 * beginning of a line.
542 prevgp
= &term
.line
[*y
][*x
];
543 prevdelim
= ISDELIM(prevgp
->u
);
545 newx
= *x
+ direction
;
547 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
549 newx
= (newx
+ term
.col
) % term
.col
;
550 if (!BETWEEN(newy
, 0, term
.row
- 1))
556 yt
= newy
, xt
= newx
;
557 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
561 if (newx
>= tlinelen(newy
))
564 gp
= &term
.line
[newy
][newx
];
565 delim
= ISDELIM(gp
->u
);
566 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
567 || (delim
&& gp
->u
!= prevgp
->u
)))
578 * Snap around if the the previous line or the current one
579 * has set ATTR_WRAP at its end. Then the whole next or
580 * previous line will be selected.
582 *x
= (direction
< 0) ? 0 : term
.col
- 1;
584 for (; *y
> 0; *y
+= direction
) {
585 if (!(term
.line
[*y
-1][term
.col
-1].mode
590 } else if (direction
> 0) {
591 for (; *y
< term
.row
-1; *y
+= direction
) {
592 if (!(term
.line
[*y
][term
.col
-1].mode
606 int y
, bufsize
, lastx
, linelen
;
612 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
613 ptr
= str
= xmalloc(bufsize
);
615 /* append every set & selected glyph to the selection */
616 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
617 if ((linelen
= tlinelen(y
)) == 0) {
622 if (sel
.type
== SEL_RECTANGULAR
) {
623 gp
= &term
.line
[y
][sel
.nb
.x
];
626 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
627 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
629 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
630 while (last
>= gp
&& last
->u
== ' ')
633 for ( ; gp
<= last
; ++gp
) {
634 if (gp
->mode
& ATTR_WDUMMY
)
637 ptr
+= utf8encode(gp
->u
, ptr
);
641 * Copy and pasting of line endings is inconsistent
642 * in the inconsistent terminal and GUI world.
643 * The best solution seems like to produce '\n' when
644 * something is copied from st and convert '\n' to
645 * '\r', when something to be pasted is received by
647 * FIXME: Fix the computer world.
649 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
663 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
667 die(const char *errstr
, ...)
671 va_start(ap
, errstr
);
672 vfprintf(stderr
, errstr
, ap
);
678 execsh(char *cmd
, char **args
)
681 const struct passwd
*pw
;
684 if ((pw
= getpwuid(getuid())) == NULL
) {
686 die("getpwuid: %s\n", strerror(errno
));
688 die("who are you?\n");
691 if ((sh
= getenv("SHELL")) == NULL
)
692 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
700 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
705 setenv("LOGNAME", pw
->pw_name
, 1);
706 setenv("USER", pw
->pw_name
, 1);
707 setenv("SHELL", sh
, 1);
708 setenv("HOME", pw
->pw_dir
, 1);
709 setenv("TERM", termname
, 1);
711 signal(SIGCHLD
, SIG_DFL
);
712 signal(SIGHUP
, SIG_DFL
);
713 signal(SIGINT
, SIG_DFL
);
714 signal(SIGQUIT
, SIG_DFL
);
715 signal(SIGTERM
, SIG_DFL
);
716 signal(SIGALRM
, SIG_DFL
);
728 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
729 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
734 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
735 die("child finished with error '%d'\n", stat
);
742 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
745 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
746 die("incorrect stty parameters\n");
747 memcpy(cmd
, stty_args
, n
);
749 siz
= sizeof(cmd
) - n
;
750 for (p
= args
; p
&& (s
= *p
); ++p
) {
751 if ((n
= strlen(s
)) > siz
-1)
752 die("stty parameter length too long\n");
759 if (system(cmd
) != 0)
760 perror("Couldn't call stty");
764 ttynew(char *line
, char *cmd
, char *out
, char **args
)
769 term
.mode
|= MODE_PRINT
;
770 iofd
= (!strcmp(out
, "-")) ?
771 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
773 fprintf(stderr
, "Error opening %s:%s\n",
774 out
, strerror(errno
));
779 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
780 die("open line '%s' failed: %s\n",
781 line
, strerror(errno
));
787 /* seems to work fine on linux, openbsd and freebsd */
788 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
789 die("openpty failed: %s\n", strerror(errno
));
791 switch (pid
= fork()) {
793 die("fork failed: %s\n", strerror(errno
));
797 setsid(); /* create a new process group */
801 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
802 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
806 if (pledge("stdio getpw proc exec", NULL
) == -1)
813 if (pledge("stdio rpath tty proc", NULL
) == -1)
818 signal(SIGCHLD
, sigchld
);
827 static char buf
[BUFSIZ
];
828 static int buflen
= 0;
832 /* append read bytes to unprocessed bytes */
833 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
834 die("couldn't read from shell: %s\n", strerror(errno
));
837 written
= twrite(buf
, buflen
, 0);
839 /* keep any uncomplete utf8 char for the next call */
841 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",
1455 attr
[i
]), csidump();
1463 tsetscroll(int t
, int b
)
1467 LIMIT(t
, 0, term
.row
-1);
1468 LIMIT(b
, 0, term
.row
-1);
1479 tsetmode(int priv
, int set
, int *args
, int narg
)
1483 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1486 case 1: /* DECCKM -- Cursor key */
1487 xsetmode(set
, MODE_APPCURSOR
);
1489 case 5: /* DECSCNM -- Reverse video */
1490 xsetmode(set
, MODE_REVERSE
);
1492 case 6: /* DECOM -- Origin */
1493 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1496 case 7: /* DECAWM -- Auto wrap */
1497 MODBIT(term
.mode
, set
, MODE_WRAP
);
1499 case 0: /* Error (IGNORED) */
1500 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1501 case 3: /* DECCOLM -- Column (IGNORED) */
1502 case 4: /* DECSCLM -- Scroll (IGNORED) */
1503 case 8: /* DECARM -- Auto repeat (IGNORED) */
1504 case 18: /* DECPFF -- Printer feed (IGNORED) */
1505 case 19: /* DECPEX -- Printer extent (IGNORED) */
1506 case 42: /* DECNRCM -- National characters (IGNORED) */
1507 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1509 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1510 xsetmode(!set
, MODE_HIDE
);
1512 case 9: /* X10 mouse compatibility mode */
1513 xsetpointermotion(0);
1514 xsetmode(0, MODE_MOUSE
);
1515 xsetmode(set
, MODE_MOUSEX10
);
1517 case 1000: /* 1000: report button press */
1518 xsetpointermotion(0);
1519 xsetmode(0, MODE_MOUSE
);
1520 xsetmode(set
, MODE_MOUSEBTN
);
1522 case 1002: /* 1002: report motion on button press */
1523 xsetpointermotion(0);
1524 xsetmode(0, MODE_MOUSE
);
1525 xsetmode(set
, MODE_MOUSEMOTION
);
1527 case 1003: /* 1003: enable all mouse motions */
1528 xsetpointermotion(set
);
1529 xsetmode(0, MODE_MOUSE
);
1530 xsetmode(set
, MODE_MOUSEMANY
);
1532 case 1004: /* 1004: send focus events to tty */
1533 xsetmode(set
, MODE_FOCUS
);
1535 case 1006: /* 1006: extended reporting mode */
1536 xsetmode(set
, MODE_MOUSESGR
);
1539 xsetmode(set
, MODE_8BIT
);
1541 case 1049: /* swap screen & set/restore cursor as xterm */
1542 if (!allowaltscreen
)
1544 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1546 case 47: /* swap screen */
1548 if (!allowaltscreen
)
1550 alt
= IS_SET(MODE_ALTSCREEN
);
1552 tclearregion(0, 0, term
.col
-1,
1555 if (set
^ alt
) /* set is always 1 or 0 */
1561 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1563 case 2004: /* 2004: bracketed paste mode */
1564 xsetmode(set
, MODE_BRCKTPASTE
);
1566 /* Not implemented mouse modes. See comments there. */
1567 case 1001: /* mouse highlight mode; can hang the
1568 terminal by design when implemented. */
1569 case 1005: /* UTF-8 mouse mode; will confuse
1570 applications not supporting UTF-8
1572 case 1015: /* urxvt mangled mouse mode; incompatible
1573 and can be mistaken for other control
1577 "erresc: unknown private set/reset mode %d\n",
1583 case 0: /* Error (IGNORED) */
1586 xsetmode(set
, MODE_KBDLOCK
);
1588 case 4: /* IRM -- Insertion-replacement */
1589 MODBIT(term
.mode
, set
, MODE_INSERT
);
1591 case 12: /* SRM -- Send/Receive */
1592 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1594 case 20: /* LNM -- Linefeed/new line */
1595 MODBIT(term
.mode
, set
, MODE_CRLF
);
1599 "erresc: unknown set/reset mode %d\n",
1613 switch (csiescseq
.mode
[0]) {
1616 fprintf(stderr
, "erresc: unknown csi ");
1620 case '@': /* ICH -- Insert <n> blank char */
1621 DEFAULT(csiescseq
.arg
[0], 1);
1622 tinsertblank(csiescseq
.arg
[0]);
1624 case 'A': /* CUU -- Cursor <n> Up */
1625 DEFAULT(csiescseq
.arg
[0], 1);
1626 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1628 case 'B': /* CUD -- Cursor <n> Down */
1629 case 'e': /* VPR --Cursor <n> Down */
1630 DEFAULT(csiescseq
.arg
[0], 1);
1631 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1633 case 'i': /* MC -- Media Copy */
1634 switch (csiescseq
.arg
[0]) {
1639 tdumpline(term
.c
.y
);
1645 term
.mode
&= ~MODE_PRINT
;
1648 term
.mode
|= MODE_PRINT
;
1652 case 'c': /* DA -- Device Attributes */
1653 if (csiescseq
.arg
[0] == 0)
1654 ttywrite(vtiden
, strlen(vtiden
), 0);
1656 case 'C': /* CUF -- Cursor <n> Forward */
1657 case 'a': /* HPR -- Cursor <n> Forward */
1658 DEFAULT(csiescseq
.arg
[0], 1);
1659 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1661 case 'D': /* CUB -- Cursor <n> Backward */
1662 DEFAULT(csiescseq
.arg
[0], 1);
1663 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1665 case 'E': /* CNL -- Cursor <n> Down and first col */
1666 DEFAULT(csiescseq
.arg
[0], 1);
1667 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1669 case 'F': /* CPL -- Cursor <n> Up and first col */
1670 DEFAULT(csiescseq
.arg
[0], 1);
1671 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1673 case 'g': /* TBC -- Tabulation clear */
1674 switch (csiescseq
.arg
[0]) {
1675 case 0: /* clear current tab stop */
1676 term
.tabs
[term
.c
.x
] = 0;
1678 case 3: /* clear all the tabs */
1679 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1685 case 'G': /* CHA -- Move to <col> */
1687 DEFAULT(csiescseq
.arg
[0], 1);
1688 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1690 case 'H': /* CUP -- Move to <row> <col> */
1692 DEFAULT(csiescseq
.arg
[0], 1);
1693 DEFAULT(csiescseq
.arg
[1], 1);
1694 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1696 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1697 DEFAULT(csiescseq
.arg
[0], 1);
1698 tputtab(csiescseq
.arg
[0]);
1700 case 'J': /* ED -- Clear screen */
1701 switch (csiescseq
.arg
[0]) {
1703 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1704 if (term
.c
.y
< term
.row
-1) {
1705 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1711 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1712 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1715 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1721 case 'K': /* EL -- Clear line */
1722 switch (csiescseq
.arg
[0]) {
1724 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1728 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1731 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1735 case 'S': /* SU -- Scroll <n> line up */
1736 DEFAULT(csiescseq
.arg
[0], 1);
1737 tscrollup(term
.top
, csiescseq
.arg
[0]);
1739 case 'T': /* SD -- Scroll <n> line down */
1740 DEFAULT(csiescseq
.arg
[0], 1);
1741 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1743 case 'L': /* IL -- Insert <n> blank lines */
1744 DEFAULT(csiescseq
.arg
[0], 1);
1745 tinsertblankline(csiescseq
.arg
[0]);
1747 case 'l': /* RM -- Reset Mode */
1748 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1750 case 'M': /* DL -- Delete <n> lines */
1751 DEFAULT(csiescseq
.arg
[0], 1);
1752 tdeleteline(csiescseq
.arg
[0]);
1754 case 'X': /* ECH -- Erase <n> char */
1755 DEFAULT(csiescseq
.arg
[0], 1);
1756 tclearregion(term
.c
.x
, term
.c
.y
,
1757 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1759 case 'P': /* DCH -- Delete <n> char */
1760 DEFAULT(csiescseq
.arg
[0], 1);
1761 tdeletechar(csiescseq
.arg
[0]);
1763 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1764 DEFAULT(csiescseq
.arg
[0], 1);
1765 tputtab(-csiescseq
.arg
[0]);
1767 case 'd': /* VPA -- Move to <row> */
1768 DEFAULT(csiescseq
.arg
[0], 1);
1769 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1771 case 'h': /* SM -- Set terminal mode */
1772 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1774 case 'm': /* SGR -- Terminal attribute (color) */
1775 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1777 case 'n': /* DSR – Device Status Report (cursor position) */
1778 if (csiescseq
.arg
[0] == 6) {
1779 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1780 term
.c
.y
+1, term
.c
.x
+1);
1781 ttywrite(buf
, len
, 0);
1784 case 'r': /* DECSTBM -- Set Scrolling Region */
1785 if (csiescseq
.priv
) {
1788 DEFAULT(csiescseq
.arg
[0], 1);
1789 DEFAULT(csiescseq
.arg
[1], term
.row
);
1790 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1794 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1795 tcursor(CURSOR_SAVE
);
1797 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1798 tcursor(CURSOR_LOAD
);
1801 switch (csiescseq
.mode
[1]) {
1802 case 'q': /* DECSCUSR -- Set Cursor Style */
1803 if (xsetcursor(csiescseq
.arg
[0]))
1819 fprintf(stderr
, "ESC[");
1820 for (i
= 0; i
< csiescseq
.len
; i
++) {
1821 c
= csiescseq
.buf
[i
] & 0xff;
1824 } else if (c
== '\n') {
1825 fprintf(stderr
, "(\\n)");
1826 } else if (c
== '\r') {
1827 fprintf(stderr
, "(\\r)");
1828 } else if (c
== 0x1b) {
1829 fprintf(stderr
, "(\\e)");
1831 fprintf(stderr
, "(%02x)", c
);
1840 memset(&csiescseq
, 0, sizeof(csiescseq
));
1849 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1851 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1853 switch (strescseq
.type
) {
1854 case ']': /* OSC -- Operating System Command */
1860 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 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1886 * TODO if defaultbg color is changed, borders
1894 case 'k': /* old title set compatibility */
1895 xsettitle(strescseq
.args
[0]);
1897 case 'P': /* DCS -- Device Control String */
1898 term
.mode
|= ESC_DCS
;
1899 case '_': /* APC -- Application Program Command */
1900 case '^': /* PM -- Privacy Message */
1904 fprintf(stderr
, "erresc: unknown str ");
1912 char *p
= strescseq
.buf
;
1915 strescseq
.buf
[strescseq
.len
] = '\0';
1920 while (strescseq
.narg
< STR_ARG_SIZ
) {
1921 strescseq
.args
[strescseq
.narg
++] = p
;
1922 while ((c
= *p
) != ';' && c
!= '\0')
1936 fprintf(stderr
, "ESC%c", strescseq
.type
);
1937 for (i
= 0; i
< strescseq
.len
; i
++) {
1938 c
= strescseq
.buf
[i
] & 0xff;
1942 } else if (isprint(c
)) {
1944 } else if (c
== '\n') {
1945 fprintf(stderr
, "(\\n)");
1946 } else if (c
== '\r') {
1947 fprintf(stderr
, "(\\r)");
1948 } else if (c
== 0x1b) {
1949 fprintf(stderr
, "(\\e)");
1951 fprintf(stderr
, "(%02x)", c
);
1954 fprintf(stderr
, "ESC\\\n");
1960 memset(&strescseq
, 0, sizeof(strescseq
));
1964 sendbreak(const Arg
*arg
)
1966 if (tcsendbreak(cmdfd
, 0))
1967 perror("Error sending break");
1971 tprinter(char *s
, size_t len
)
1973 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1974 perror("Error writing to output file");
1981 toggleprinter(const Arg
*arg
)
1983 term
.mode
^= MODE_PRINT
;
1987 printscreen(const Arg
*arg
)
1993 printsel(const Arg
*arg
)
2003 if ((ptr
= getsel())) {
2004 tprinter(ptr
, strlen(ptr
));
2015 bp
= &term
.line
[n
][0];
2016 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2017 if (bp
!= end
|| bp
->u
!= ' ') {
2018 for ( ;bp
<= end
; ++bp
)
2019 tprinter(buf
, utf8encode(bp
->u
, buf
));
2029 for (i
= 0; i
< term
.row
; ++i
)
2039 while (x
< term
.col
&& n
--)
2040 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2043 while (x
> 0 && n
++)
2044 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2047 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2051 tdefutf8(char ascii
)
2054 term
.mode
|= MODE_UTF8
;
2055 else if (ascii
== '@')
2056 term
.mode
&= ~MODE_UTF8
;
2060 tdeftran(char ascii
)
2062 static char cs
[] = "0B";
2063 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2066 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2067 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2069 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2078 if (c
== '8') { /* DEC screen alignment test. */
2079 for (x
= 0; x
< term
.col
; ++x
) {
2080 for (y
= 0; y
< term
.row
; ++y
)
2081 tsetchar('E', &term
.c
.attr
, x
, y
);
2087 tstrsequence(uchar c
)
2092 case 0x90: /* DCS -- Device Control String */
2094 term
.esc
|= ESC_DCS
;
2096 case 0x9f: /* APC -- Application Program Command */
2099 case 0x9e: /* PM -- Privacy Message */
2102 case 0x9d: /* OSC -- Operating System Command */
2107 term
.esc
|= ESC_STR
;
2111 tcontrolcode(uchar ascii
)
2118 tmoveto(term
.c
.x
-1, term
.c
.y
);
2121 tmoveto(0, term
.c
.y
);
2126 /* go to first col if the mode is set */
2127 tnewline(IS_SET(MODE_CRLF
));
2129 case '\a': /* BEL */
2130 if (term
.esc
& ESC_STR_END
) {
2131 /* backwards compatibility to xterm */
2137 case '\033': /* ESC */
2139 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2140 term
.esc
|= ESC_START
;
2142 case '\016': /* SO (LS1 -- Locking shift 1) */
2143 case '\017': /* SI (LS0 -- Locking shift 0) */
2144 term
.charset
= 1 - (ascii
- '\016');
2146 case '\032': /* SUB */
2147 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2148 case '\030': /* CAN */
2151 case '\005': /* ENQ (IGNORED) */
2152 case '\000': /* NUL (IGNORED) */
2153 case '\021': /* XON (IGNORED) */
2154 case '\023': /* XOFF (IGNORED) */
2155 case 0177: /* DEL (IGNORED) */
2157 case 0x80: /* TODO: PAD */
2158 case 0x81: /* TODO: HOP */
2159 case 0x82: /* TODO: BPH */
2160 case 0x83: /* TODO: NBH */
2161 case 0x84: /* TODO: IND */
2163 case 0x85: /* NEL -- Next line */
2164 tnewline(1); /* always go to first col */
2166 case 0x86: /* TODO: SSA */
2167 case 0x87: /* TODO: ESA */
2169 case 0x88: /* HTS -- Horizontal tab stop */
2170 term
.tabs
[term
.c
.x
] = 1;
2172 case 0x89: /* TODO: HTJ */
2173 case 0x8a: /* TODO: VTS */
2174 case 0x8b: /* TODO: PLD */
2175 case 0x8c: /* TODO: PLU */
2176 case 0x8d: /* TODO: RI */
2177 case 0x8e: /* TODO: SS2 */
2178 case 0x8f: /* TODO: SS3 */
2179 case 0x91: /* TODO: PU1 */
2180 case 0x92: /* TODO: PU2 */
2181 case 0x93: /* TODO: STS */
2182 case 0x94: /* TODO: CCH */
2183 case 0x95: /* TODO: MW */
2184 case 0x96: /* TODO: SPA */
2185 case 0x97: /* TODO: EPA */
2186 case 0x98: /* TODO: SOS */
2187 case 0x99: /* TODO: SGCI */
2189 case 0x9a: /* DECID -- Identify Terminal */
2190 ttywrite(vtiden
, strlen(vtiden
), 0);
2192 case 0x9b: /* TODO: CSI */
2193 case 0x9c: /* TODO: ST */
2195 case 0x90: /* DCS -- Device Control String */
2196 case 0x9d: /* OSC -- Operating System Command */
2197 case 0x9e: /* PM -- Privacy Message */
2198 case 0x9f: /* APC -- Application Program Command */
2199 tstrsequence(ascii
);
2202 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2203 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2207 * returns 1 when the sequence is finished and it hasn't to read
2208 * more characters for this sequence, otherwise 0
2211 eschandle(uchar ascii
)
2215 term
.esc
|= ESC_CSI
;
2218 term
.esc
|= ESC_TEST
;
2221 term
.esc
|= ESC_UTF8
;
2223 case 'P': /* DCS -- Device Control String */
2224 case '_': /* APC -- Application Program Command */
2225 case '^': /* PM -- Privacy Message */
2226 case ']': /* OSC -- Operating System Command */
2227 case 'k': /* old title set compatibility */
2228 tstrsequence(ascii
);
2230 case 'n': /* LS2 -- Locking shift 2 */
2231 case 'o': /* LS3 -- Locking shift 3 */
2232 term
.charset
= 2 + (ascii
- 'n');
2234 case '(': /* GZD4 -- set primary charset G0 */
2235 case ')': /* G1D4 -- set secondary charset G1 */
2236 case '*': /* G2D4 -- set tertiary charset G2 */
2237 case '+': /* G3D4 -- set quaternary charset G3 */
2238 term
.icharset
= ascii
- '(';
2239 term
.esc
|= ESC_ALTCHARSET
;
2241 case 'D': /* IND -- Linefeed */
2242 if (term
.c
.y
== term
.bot
) {
2243 tscrollup(term
.top
, 1);
2245 tmoveto(term
.c
.x
, term
.c
.y
+1);
2248 case 'E': /* NEL -- Next line */
2249 tnewline(1); /* always go to first col */
2251 case 'H': /* HTS -- Horizontal tab stop */
2252 term
.tabs
[term
.c
.x
] = 1;
2254 case 'M': /* RI -- Reverse index */
2255 if (term
.c
.y
== term
.top
) {
2256 tscrolldown(term
.top
, 1);
2258 tmoveto(term
.c
.x
, term
.c
.y
-1);
2261 case 'Z': /* DECID -- Identify Terminal */
2262 ttywrite(vtiden
, strlen(vtiden
), 0);
2264 case 'c': /* RIS -- Reset to inital state */
2269 case '=': /* DECPAM -- Application keypad */
2270 xsetmode(1, MODE_APPKEYPAD
);
2272 case '>': /* DECPNM -- Normal keypad */
2273 xsetmode(0, MODE_APPKEYPAD
);
2275 case '7': /* DECSC -- Save Cursor */
2276 tcursor(CURSOR_SAVE
);
2278 case '8': /* DECRC -- Restore Cursor */
2279 tcursor(CURSOR_LOAD
);
2281 case '\\': /* ST -- String Terminator */
2282 if (term
.esc
& ESC_STR_END
)
2286 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2287 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2301 control
= ISCONTROL(u
);
2302 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2306 len
= utf8encode(u
, c
);
2307 if (!control
&& (width
= wcwidth(u
)) == -1) {
2308 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2313 if (IS_SET(MODE_PRINT
))
2317 * STR sequence must be checked before anything else
2318 * because it uses all following characters until it
2319 * receives a ESC, a SUB, a ST or any other C1 control
2322 if (term
.esc
& ESC_STR
) {
2323 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2325 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2326 if (IS_SET(MODE_SIXEL
)) {
2327 /* TODO: render sixel */;
2328 term
.mode
&= ~MODE_SIXEL
;
2331 term
.esc
|= ESC_STR_END
;
2332 goto check_control_code
;
2336 if (IS_SET(MODE_SIXEL
)) {
2337 /* TODO: implement sixel mode */
2340 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2341 term
.mode
|= MODE_SIXEL
;
2343 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2345 * Here is a bug in terminals. If the user never sends
2346 * some code to stop the str or esc command, then st
2347 * will stop responding. But this is better than
2348 * silently failing with unknown characters. At least
2349 * then users will report back.
2351 * In the case users ever get fixed, here is the code:
2360 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2361 strescseq
.len
+= len
;
2367 * Actions of control codes must be performed as soon they arrive
2368 * because they can be embedded inside a control sequence, and
2369 * they must not cause conflicts with sequences.
2374 * control codes are not shown ever
2377 } else if (term
.esc
& ESC_START
) {
2378 if (term
.esc
& ESC_CSI
) {
2379 csiescseq
.buf
[csiescseq
.len
++] = u
;
2380 if (BETWEEN(u
, 0x40, 0x7E)
2381 || csiescseq
.len
>= \
2382 sizeof(csiescseq
.buf
)-1) {
2388 } else if (term
.esc
& ESC_UTF8
) {
2390 } else if (term
.esc
& ESC_ALTCHARSET
) {
2392 } else if (term
.esc
& ESC_TEST
) {
2397 /* sequence already finished */
2401 * All characters which form part of a sequence are not
2406 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2409 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2410 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2411 gp
->mode
|= ATTR_WRAP
;
2413 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2416 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2417 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2419 if (term
.c
.x
+width
> term
.col
) {
2421 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2424 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2427 gp
->mode
|= ATTR_WIDE
;
2428 if (term
.c
.x
+1 < term
.col
) {
2430 gp
[1].mode
= ATTR_WDUMMY
;
2433 if (term
.c
.x
+width
< term
.col
) {
2434 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2436 term
.c
.state
|= CURSOR_WRAPNEXT
;
2441 twrite(const char *buf
, int buflen
, int show_ctrl
)
2447 for (n
= 0; n
< buflen
; n
+= charsize
) {
2448 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2449 /* process a complete utf8 char */
2450 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2457 if (show_ctrl
&& ISCONTROL(u
)) {
2462 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2473 tresize(int col
, int row
)
2476 int minrow
= MIN(row
, term
.row
);
2477 int mincol
= MIN(col
, term
.col
);
2481 if (col
< 1 || row
< 1) {
2483 "tresize: error resizing to %dx%d\n", col
, row
);
2488 * slide screen to keep cursor where we expect it -
2489 * tscrollup would work here, but we can optimize to
2490 * memmove because we're freeing the earlier lines
2492 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2496 /* ensure that both src and dst are not NULL */
2498 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2499 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2501 for (i
+= row
; i
< term
.row
; i
++) {
2506 /* resize to new height */
2507 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2508 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2509 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2510 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2512 /* resize each row to new width, zero-pad if needed */
2513 for (i
= 0; i
< minrow
; i
++) {
2514 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2515 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2518 /* allocate any new rows */
2519 for (/* i = minrow */; i
< row
; i
++) {
2520 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2521 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2523 if (col
> term
.col
) {
2524 bp
= term
.tabs
+ term
.col
;
2526 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2527 while (--bp
> term
.tabs
&& !*bp
)
2529 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2532 /* update terminal size */
2535 /* reset scrolling region */
2536 tsetscroll(0, row
-1);
2537 /* make use of the LIMIT in tmoveto */
2538 tmoveto(term
.c
.x
, term
.c
.y
);
2539 /* Clearing both screens (it makes dirty all lines) */
2541 for (i
= 0; i
< 2; i
++) {
2542 if (mincol
< col
&& 0 < minrow
) {
2543 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2545 if (0 < col
&& minrow
< row
) {
2546 tclearregion(0, minrow
, col
- 1, row
- 1);
2549 tcursor(CURSOR_LOAD
);
2561 drawregion(int x1
, int y1
, int x2
, int y2
)
2564 for (y
= y1
; y
< y2
; y
++) {
2569 xdrawline(term
.line
[y
], x1
, y
, x2
);
2581 /* adjust cursor position */
2582 LIMIT(term
.ocx
, 0, term
.col
-1);
2583 LIMIT(term
.ocy
, 0, term
.row
-1);
2584 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2586 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2589 drawregion(0, 0, term
.col
, term
.row
);
2590 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2591 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2592 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;