Xinqi Bao's Git
1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 MODE_ALTSCREEN
= 1 << 2,
57 enum cursor_movement
{
81 ESC_STR
= 4, /* OSC, PM, APC */
83 ESC_STR_END
= 16, /* a final string was encountered */
84 ESC_TEST
= 32, /* Enter in test mode */
90 Glyph attr
; /* current char attributes */
101 * Selection variables:
102 * nb – normalized coordinates of the beginning of the selection
103 * ne – normalized coordinates of the end of the selection
104 * ob – original coordinates of the beginning of the selection
105 * oe – original coordinates of the end of the selection
114 /* Internal representation of the screen */
116 int row
; /* nb row */
117 int col
; /* nb col */
118 Line
*line
; /* screen */
119 Line
*alt
; /* alternate screen */
120 int *dirty
; /* dirtyness of lines */
121 TCursor c
; /* cursor */
122 int ocx
; /* old cursor col */
123 int ocy
; /* old cursor row */
124 int top
; /* top scroll limit */
125 int bot
; /* bottom scroll limit */
126 int mode
; /* terminal mode flags */
127 int esc
; /* escape state flags */
128 char trantbl
[4]; /* charset table translation */
129 int charset
; /* current charset */
130 int icharset
; /* selected charset for sequence */
134 /* CSI Escape sequence structs */
135 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
137 char buf
[ESC_BUF_SIZ
]; /* raw string */
138 size_t len
; /* raw string length */
140 int arg
[ESC_ARG_SIZ
];
141 int narg
; /* nb of args */
145 /* STR Escape sequence structs */
146 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
148 char type
; /* ESC type ... */
149 char buf
[STR_BUF_SIZ
]; /* raw string */
150 size_t 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 size_t utf8validate(Rune
*, size_t);
215 static char *base64dec(const char *);
216 static char base64dec_getc(const char **);
218 static ssize_t
xwrite(int, const char *, size_t);
222 static Selection sel
;
223 static CSIEscape csiescseq
;
224 static STREscape strescseq
;
229 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
230 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
231 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
232 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
235 xwrite(int fd
, const char *s
, size_t len
)
241 r
= write(fd
, s
, len
);
256 if (!(p
= malloc(len
)))
257 die("malloc: %s\n", strerror(errno
));
263 xrealloc(void *p
, size_t len
)
265 if ((p
= realloc(p
, len
)) == NULL
)
266 die("realloc: %s\n", strerror(errno
));
274 if ((s
= strdup(s
)) == NULL
)
275 die("strdup: %s\n", strerror(errno
));
281 utf8decode(const char *c
, Rune
*u
, size_t clen
)
283 size_t i
, j
, len
, type
;
289 udecoded
= utf8decodebyte(c
[0], &len
);
290 if (!BETWEEN(len
, 1, UTF_SIZ
))
292 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
293 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
300 utf8validate(u
, len
);
306 utf8decodebyte(char c
, size_t *i
)
308 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
309 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
310 return (uchar
)c
& ~utfmask
[*i
];
316 utf8encode(Rune u
, char *c
)
320 len
= utf8validate(&u
, 0);
324 for (i
= len
- 1; i
!= 0; --i
) {
325 c
[i
] = utf8encodebyte(u
, 0);
328 c
[0] = utf8encodebyte(u
, len
);
334 utf8encodebyte(Rune u
, size_t i
)
336 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
340 utf8validate(Rune
*u
, size_t i
)
342 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
344 for (i
= 1; *u
> utfmax
[i
]; ++i
)
350 static const char base64_digits
[] = {
351 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
353 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
354 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
355 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
356 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
357 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
361 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
362 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
366 base64dec_getc(const char **src
)
368 while (**src
&& !isprint(**src
)) (*src
)++;
369 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
373 base64dec(const char *src
)
375 size_t in_len
= strlen(src
);
379 in_len
+= 4 - (in_len
% 4);
380 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
382 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
383 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
384 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
385 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
387 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
388 if (a
== -1 || b
== -1)
391 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
394 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
397 *dst
++ = ((c
& 0x03) << 6) | d
;
416 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
419 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
426 selstart(int col
, int row
, int snap
)
429 sel
.mode
= SEL_EMPTY
;
430 sel
.type
= SEL_REGULAR
;
431 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
433 sel
.oe
.x
= sel
.ob
.x
= col
;
434 sel
.oe
.y
= sel
.ob
.y
= row
;
438 sel
.mode
= SEL_READY
;
439 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
443 selextend(int col
, int row
, int type
, int done
)
445 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
447 if (sel
.mode
== SEL_IDLE
)
449 if (done
&& sel
.mode
== SEL_EMPTY
) {
465 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
466 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
468 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
476 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
477 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
478 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
480 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
481 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
483 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
484 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
486 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
487 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
489 /* expand selection over line breaks */
490 if (sel
.type
== SEL_RECTANGULAR
)
492 i
= tlinelen(sel
.nb
.y
);
495 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
496 sel
.ne
.x
= term
.col
- 1;
500 selected(int x
, int y
)
502 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
503 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
506 if (sel
.type
== SEL_RECTANGULAR
)
507 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
508 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
510 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
511 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
512 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
516 selsnap(int *x
, int *y
, int direction
)
518 int newx
, newy
, xt
, yt
;
519 int delim
, prevdelim
;
525 * Snap around if the word wraps around at the end or
526 * beginning of a line.
528 prevgp
= &term
.line
[*y
][*x
];
529 prevdelim
= ISDELIM(prevgp
->u
);
531 newx
= *x
+ direction
;
533 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
535 newx
= (newx
+ term
.col
) % term
.col
;
536 if (!BETWEEN(newy
, 0, term
.row
- 1))
542 yt
= newy
, xt
= newx
;
543 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
547 if (newx
>= tlinelen(newy
))
550 gp
= &term
.line
[newy
][newx
];
551 delim
= ISDELIM(gp
->u
);
552 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
553 || (delim
&& gp
->u
!= prevgp
->u
)))
564 * Snap around if the the previous line or the current one
565 * has set ATTR_WRAP at its end. Then the whole next or
566 * previous line will be selected.
568 *x
= (direction
< 0) ? 0 : term
.col
- 1;
570 for (; *y
> 0; *y
+= direction
) {
571 if (!(term
.line
[*y
-1][term
.col
-1].mode
576 } else if (direction
> 0) {
577 for (; *y
< term
.row
-1; *y
+= direction
) {
578 if (!(term
.line
[*y
][term
.col
-1].mode
592 int y
, bufsize
, lastx
, linelen
;
598 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
599 ptr
= str
= xmalloc(bufsize
);
601 /* append every set & selected glyph to the selection */
602 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
603 if ((linelen
= tlinelen(y
)) == 0) {
608 if (sel
.type
== SEL_RECTANGULAR
) {
609 gp
= &term
.line
[y
][sel
.nb
.x
];
612 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
613 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
615 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
616 while (last
>= gp
&& last
->u
== ' ')
619 for ( ; gp
<= last
; ++gp
) {
620 if (gp
->mode
& ATTR_WDUMMY
)
623 ptr
+= utf8encode(gp
->u
, ptr
);
627 * Copy and pasting of line endings is inconsistent
628 * in the inconsistent terminal and GUI world.
629 * The best solution seems like to produce '\n' when
630 * something is copied from st and convert '\n' to
631 * '\r', when something to be pasted is received by
633 * FIXME: Fix the computer world.
635 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
649 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
653 die(const char *errstr
, ...)
657 va_start(ap
, errstr
);
658 vfprintf(stderr
, errstr
, ap
);
664 execsh(char *cmd
, char **args
)
667 const struct passwd
*pw
;
670 if ((pw
= getpwuid(getuid())) == NULL
) {
672 die("getpwuid: %s\n", strerror(errno
));
674 die("who are you?\n");
677 if ((sh
= getenv("SHELL")) == NULL
)
678 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
686 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
691 setenv("LOGNAME", pw
->pw_name
, 1);
692 setenv("USER", pw
->pw_name
, 1);
693 setenv("SHELL", sh
, 1);
694 setenv("HOME", pw
->pw_dir
, 1);
695 setenv("TERM", termname
, 1);
697 signal(SIGCHLD
, SIG_DFL
);
698 signal(SIGHUP
, SIG_DFL
);
699 signal(SIGINT
, SIG_DFL
);
700 signal(SIGQUIT
, SIG_DFL
);
701 signal(SIGTERM
, SIG_DFL
);
702 signal(SIGALRM
, SIG_DFL
);
714 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
715 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
720 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
721 die("child exited with status %d\n", WEXITSTATUS(stat
));
722 else if (WIFSIGNALED(stat
))
723 die("child terminated due to signal %d\n", WTERMSIG(stat
));
730 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
733 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
734 die("incorrect stty parameters\n");
735 memcpy(cmd
, stty_args
, n
);
737 siz
= sizeof(cmd
) - n
;
738 for (p
= args
; p
&& (s
= *p
); ++p
) {
739 if ((n
= strlen(s
)) > siz
-1)
740 die("stty parameter length too long\n");
747 if (system(cmd
) != 0)
748 perror("Couldn't call stty");
752 ttynew(char *line
, char *cmd
, char *out
, char **args
)
757 term
.mode
|= MODE_PRINT
;
758 iofd
= (!strcmp(out
, "-")) ?
759 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
761 fprintf(stderr
, "Error opening %s:%s\n",
762 out
, strerror(errno
));
767 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
768 die("open line '%s' failed: %s\n",
769 line
, strerror(errno
));
775 /* seems to work fine on linux, openbsd and freebsd */
776 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
777 die("openpty failed: %s\n", strerror(errno
));
779 switch (pid
= fork()) {
781 die("fork failed: %s\n", strerror(errno
));
785 setsid(); /* create a new process group */
789 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
790 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
794 if (pledge("stdio getpw proc exec", NULL
) == -1)
801 if (pledge("stdio rpath tty proc", NULL
) == -1)
806 signal(SIGCHLD
, sigchld
);
815 static char buf
[BUFSIZ
];
816 static int buflen
= 0;
820 /* append read bytes to unprocessed bytes */
821 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
822 die("couldn't read from shell: %s\n", strerror(errno
));
825 written
= twrite(buf
, buflen
, 0);
827 /* keep any uncomplete utf8 char for the next call */
829 memmove(buf
, buf
+ written
, buflen
);
835 ttywrite(const char *s
, size_t n
, int may_echo
)
839 if (may_echo
&& IS_SET(MODE_ECHO
))
842 if (!IS_SET(MODE_CRLF
)) {
847 /* This is similar to how the kernel handles ONLCR for ttys */
851 ttywriteraw("\r\n", 2);
853 next
= memchr(s
, '\r', n
);
854 DEFAULT(next
, s
+ n
);
855 ttywriteraw(s
, next
- s
);
863 ttywriteraw(const char *s
, size_t n
)
870 * Remember that we are using a pty, which might be a modem line.
871 * Writing too much will clog the line. That's why we are doing this
873 * FIXME: Migrate the world to Plan 9.
881 /* Check if we can write. */
882 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
885 die("select failed: %s\n", strerror(errno
));
887 if (FD_ISSET(cmdfd
, &wfd
)) {
889 * Only write the bytes written by ttywrite() or the
890 * default of 256. This seems to be a reasonable value
891 * for a serial line. Bigger values might clog the I/O.
893 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
897 * We weren't able to write out everything.
898 * This means the buffer is getting full
906 /* All bytes have been written. */
910 if (FD_ISSET(cmdfd
, &rfd
))
916 die("write error on tty: %s\n", strerror(errno
));
920 ttyresize(int tw
, int th
)
928 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
929 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
935 /* Send SIGHUP to shell */
944 for (i
= 0; i
< term
.row
-1; i
++) {
945 for (j
= 0; j
< term
.col
-1; j
++) {
946 if (term
.line
[i
][j
].mode
& attr
)
955 tsetdirt(int top
, int bot
)
959 LIMIT(top
, 0, term
.row
-1);
960 LIMIT(bot
, 0, term
.row
-1);
962 for (i
= top
; i
<= bot
; i
++)
967 tsetdirtattr(int attr
)
971 for (i
= 0; i
< term
.row
-1; i
++) {
972 for (j
= 0; j
< term
.col
-1; j
++) {
973 if (term
.line
[i
][j
].mode
& attr
) {
984 tsetdirt(0, term
.row
-1);
991 int alt
= IS_SET(MODE_ALTSCREEN
);
993 if (mode
== CURSOR_SAVE
) {
995 } else if (mode
== CURSOR_LOAD
) {
997 tmoveto(c
[alt
].x
, c
[alt
].y
);
1006 term
.c
= (TCursor
){{
1010 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1012 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1013 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1016 term
.bot
= term
.row
- 1;
1017 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1018 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1021 for (i
= 0; i
< 2; i
++) {
1023 tcursor(CURSOR_SAVE
);
1024 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1030 tnew(int col
, int row
)
1032 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1040 Line
*tmp
= term
.line
;
1042 term
.line
= term
.alt
;
1044 term
.mode
^= MODE_ALTSCREEN
;
1049 tscrolldown(int orig
, int n
)
1054 LIMIT(n
, 0, term
.bot
-orig
+1);
1056 tsetdirt(orig
, term
.bot
-n
);
1057 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1059 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1060 temp
= term
.line
[i
];
1061 term
.line
[i
] = term
.line
[i
-n
];
1062 term
.line
[i
-n
] = temp
;
1069 tscrollup(int orig
, int n
)
1074 LIMIT(n
, 0, term
.bot
-orig
+1);
1076 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1077 tsetdirt(orig
+n
, term
.bot
);
1079 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1080 temp
= term
.line
[i
];
1081 term
.line
[i
] = term
.line
[i
+n
];
1082 term
.line
[i
+n
] = temp
;
1085 selscroll(orig
, -n
);
1089 selscroll(int orig
, int n
)
1094 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1095 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1099 if (sel
.type
== SEL_RECTANGULAR
) {
1100 if (sel
.ob
.y
< term
.top
)
1101 sel
.ob
.y
= term
.top
;
1102 if (sel
.oe
.y
> term
.bot
)
1103 sel
.oe
.y
= term
.bot
;
1105 if (sel
.ob
.y
< term
.top
) {
1106 sel
.ob
.y
= term
.top
;
1109 if (sel
.oe
.y
> term
.bot
) {
1110 sel
.oe
.y
= term
.bot
;
1111 sel
.oe
.x
= term
.col
;
1119 tnewline(int first_col
)
1123 if (y
== term
.bot
) {
1124 tscrollup(term
.top
, 1);
1128 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1134 char *p
= csiescseq
.buf
, *np
;
1143 csiescseq
.buf
[csiescseq
.len
] = '\0';
1144 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1146 v
= strtol(p
, &np
, 10);
1149 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1151 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1153 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1157 csiescseq
.mode
[0] = *p
++;
1158 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1161 /* for absolute user moves, when decom is set */
1163 tmoveato(int x
, int y
)
1165 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1169 tmoveto(int x
, int y
)
1173 if (term
.c
.state
& CURSOR_ORIGIN
) {
1178 maxy
= term
.row
- 1;
1180 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1181 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1182 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1186 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1188 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1189 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1190 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1191 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1192 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1193 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1194 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1195 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1196 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1200 * The table is proudly stolen from rxvt.
1202 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1203 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1204 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1206 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1207 if (x
+1 < term
.col
) {
1208 term
.line
[y
][x
+1].u
= ' ';
1209 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1211 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1212 term
.line
[y
][x
-1].u
= ' ';
1213 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1217 term
.line
[y
][x
] = *attr
;
1218 term
.line
[y
][x
].u
= u
;
1222 tclearregion(int x1
, int y1
, int x2
, int y2
)
1228 temp
= x1
, x1
= x2
, x2
= temp
;
1230 temp
= y1
, y1
= y2
, y2
= temp
;
1232 LIMIT(x1
, 0, term
.col
-1);
1233 LIMIT(x2
, 0, term
.col
-1);
1234 LIMIT(y1
, 0, term
.row
-1);
1235 LIMIT(y2
, 0, term
.row
-1);
1237 for (y
= y1
; y
<= y2
; y
++) {
1239 for (x
= x1
; x
<= x2
; x
++) {
1240 gp
= &term
.line
[y
][x
];
1243 gp
->fg
= term
.c
.attr
.fg
;
1244 gp
->bg
= term
.c
.attr
.bg
;
1257 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1261 size
= term
.col
- src
;
1262 line
= term
.line
[term
.c
.y
];
1264 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1265 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1274 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1278 size
= term
.col
- dst
;
1279 line
= term
.line
[term
.c
.y
];
1281 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1282 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1286 tinsertblankline(int n
)
1288 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1289 tscrolldown(term
.c
.y
, n
);
1295 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1296 tscrollup(term
.c
.y
, n
);
1300 tdefcolor(int *attr
, int *npar
, int l
)
1305 switch (attr
[*npar
+ 1]) {
1306 case 2: /* direct color in RGB space */
1307 if (*npar
+ 4 >= l
) {
1309 "erresc(38): Incorrect number of parameters (%d)\n",
1313 r
= attr
[*npar
+ 2];
1314 g
= attr
[*npar
+ 3];
1315 b
= attr
[*npar
+ 4];
1317 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1318 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1321 idx
= TRUECOLOR(r
, g
, b
);
1323 case 5: /* indexed color */
1324 if (*npar
+ 2 >= l
) {
1326 "erresc(38): Incorrect number of parameters (%d)\n",
1331 if (!BETWEEN(attr
[*npar
], 0, 255))
1332 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1336 case 0: /* implemented defined (only foreground) */
1337 case 1: /* transparent */
1338 case 3: /* direct color in CMY space */
1339 case 4: /* direct color in CMYK space */
1342 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1350 tsetattr(int *attr
, int l
)
1355 for (i
= 0; i
< l
; i
++) {
1358 term
.c
.attr
.mode
&= ~(
1367 term
.c
.attr
.fg
= defaultfg
;
1368 term
.c
.attr
.bg
= defaultbg
;
1371 term
.c
.attr
.mode
|= ATTR_BOLD
;
1374 term
.c
.attr
.mode
|= ATTR_FAINT
;
1377 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1380 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1382 case 5: /* slow blink */
1384 case 6: /* rapid blink */
1385 term
.c
.attr
.mode
|= ATTR_BLINK
;
1388 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1391 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1394 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1397 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1400 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1403 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1406 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1409 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1412 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1415 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1418 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1419 term
.c
.attr
.fg
= idx
;
1422 term
.c
.attr
.fg
= defaultfg
;
1425 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1426 term
.c
.attr
.bg
= idx
;
1429 term
.c
.attr
.bg
= defaultbg
;
1432 if (BETWEEN(attr
[i
], 30, 37)) {
1433 term
.c
.attr
.fg
= attr
[i
] - 30;
1434 } else if (BETWEEN(attr
[i
], 40, 47)) {
1435 term
.c
.attr
.bg
= attr
[i
] - 40;
1436 } else if (BETWEEN(attr
[i
], 90, 97)) {
1437 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1438 } else if (BETWEEN(attr
[i
], 100, 107)) {
1439 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1442 "erresc(default): gfx attr %d unknown\n",
1452 tsetscroll(int t
, int b
)
1456 LIMIT(t
, 0, term
.row
-1);
1457 LIMIT(b
, 0, term
.row
-1);
1468 tsetmode(int priv
, int set
, int *args
, int narg
)
1472 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1475 case 1: /* DECCKM -- Cursor key */
1476 xsetmode(set
, MODE_APPCURSOR
);
1478 case 5: /* DECSCNM -- Reverse video */
1479 xsetmode(set
, MODE_REVERSE
);
1481 case 6: /* DECOM -- Origin */
1482 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1485 case 7: /* DECAWM -- Auto wrap */
1486 MODBIT(term
.mode
, set
, MODE_WRAP
);
1488 case 0: /* Error (IGNORED) */
1489 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1490 case 3: /* DECCOLM -- Column (IGNORED) */
1491 case 4: /* DECSCLM -- Scroll (IGNORED) */
1492 case 8: /* DECARM -- Auto repeat (IGNORED) */
1493 case 18: /* DECPFF -- Printer feed (IGNORED) */
1494 case 19: /* DECPEX -- Printer extent (IGNORED) */
1495 case 42: /* DECNRCM -- National characters (IGNORED) */
1496 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1498 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1499 xsetmode(!set
, MODE_HIDE
);
1501 case 9: /* X10 mouse compatibility mode */
1502 xsetpointermotion(0);
1503 xsetmode(0, MODE_MOUSE
);
1504 xsetmode(set
, MODE_MOUSEX10
);
1506 case 1000: /* 1000: report button press */
1507 xsetpointermotion(0);
1508 xsetmode(0, MODE_MOUSE
);
1509 xsetmode(set
, MODE_MOUSEBTN
);
1511 case 1002: /* 1002: report motion on button press */
1512 xsetpointermotion(0);
1513 xsetmode(0, MODE_MOUSE
);
1514 xsetmode(set
, MODE_MOUSEMOTION
);
1516 case 1003: /* 1003: enable all mouse motions */
1517 xsetpointermotion(set
);
1518 xsetmode(0, MODE_MOUSE
);
1519 xsetmode(set
, MODE_MOUSEMANY
);
1521 case 1004: /* 1004: send focus events to tty */
1522 xsetmode(set
, MODE_FOCUS
);
1524 case 1006: /* 1006: extended reporting mode */
1525 xsetmode(set
, MODE_MOUSESGR
);
1528 xsetmode(set
, MODE_8BIT
);
1530 case 1049: /* swap screen & set/restore cursor as xterm */
1531 if (!allowaltscreen
)
1533 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1535 case 47: /* swap screen */
1537 if (!allowaltscreen
)
1539 alt
= IS_SET(MODE_ALTSCREEN
);
1541 tclearregion(0, 0, term
.col
-1,
1544 if (set
^ alt
) /* set is always 1 or 0 */
1550 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1552 case 2004: /* 2004: bracketed paste mode */
1553 xsetmode(set
, MODE_BRCKTPASTE
);
1555 /* Not implemented mouse modes. See comments there. */
1556 case 1001: /* mouse highlight mode; can hang the
1557 terminal by design when implemented. */
1558 case 1005: /* UTF-8 mouse mode; will confuse
1559 applications not supporting UTF-8
1561 case 1015: /* urxvt mangled mouse mode; incompatible
1562 and can be mistaken for other control
1567 "erresc: unknown private set/reset mode %d\n",
1573 case 0: /* Error (IGNORED) */
1576 xsetmode(set
, MODE_KBDLOCK
);
1578 case 4: /* IRM -- Insertion-replacement */
1579 MODBIT(term
.mode
, set
, MODE_INSERT
);
1581 case 12: /* SRM -- Send/Receive */
1582 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1584 case 20: /* LNM -- Linefeed/new line */
1585 MODBIT(term
.mode
, set
, MODE_CRLF
);
1589 "erresc: unknown set/reset mode %d\n",
1603 switch (csiescseq
.mode
[0]) {
1606 fprintf(stderr
, "erresc: unknown csi ");
1610 case '@': /* ICH -- Insert <n> blank char */
1611 DEFAULT(csiescseq
.arg
[0], 1);
1612 tinsertblank(csiescseq
.arg
[0]);
1614 case 'A': /* CUU -- Cursor <n> Up */
1615 DEFAULT(csiescseq
.arg
[0], 1);
1616 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1618 case 'B': /* CUD -- Cursor <n> Down */
1619 case 'e': /* VPR --Cursor <n> Down */
1620 DEFAULT(csiescseq
.arg
[0], 1);
1621 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1623 case 'i': /* MC -- Media Copy */
1624 switch (csiescseq
.arg
[0]) {
1629 tdumpline(term
.c
.y
);
1635 term
.mode
&= ~MODE_PRINT
;
1638 term
.mode
|= MODE_PRINT
;
1642 case 'c': /* DA -- Device Attributes */
1643 if (csiescseq
.arg
[0] == 0)
1644 ttywrite(vtiden
, strlen(vtiden
), 0);
1646 case 'C': /* CUF -- Cursor <n> Forward */
1647 case 'a': /* HPR -- Cursor <n> Forward */
1648 DEFAULT(csiescseq
.arg
[0], 1);
1649 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1651 case 'D': /* CUB -- Cursor <n> Backward */
1652 DEFAULT(csiescseq
.arg
[0], 1);
1653 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1655 case 'E': /* CNL -- Cursor <n> Down and first col */
1656 DEFAULT(csiescseq
.arg
[0], 1);
1657 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1659 case 'F': /* CPL -- Cursor <n> Up and first col */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1663 case 'g': /* TBC -- Tabulation clear */
1664 switch (csiescseq
.arg
[0]) {
1665 case 0: /* clear current tab stop */
1666 term
.tabs
[term
.c
.x
] = 0;
1668 case 3: /* clear all the tabs */
1669 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1675 case 'G': /* CHA -- Move to <col> */
1677 DEFAULT(csiescseq
.arg
[0], 1);
1678 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1680 case 'H': /* CUP -- Move to <row> <col> */
1682 DEFAULT(csiescseq
.arg
[0], 1);
1683 DEFAULT(csiescseq
.arg
[1], 1);
1684 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1686 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1687 DEFAULT(csiescseq
.arg
[0], 1);
1688 tputtab(csiescseq
.arg
[0]);
1690 case 'J': /* ED -- Clear screen */
1691 switch (csiescseq
.arg
[0]) {
1693 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1694 if (term
.c
.y
< term
.row
-1) {
1695 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1701 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1702 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1705 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1711 case 'K': /* EL -- Clear line */
1712 switch (csiescseq
.arg
[0]) {
1714 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1718 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1721 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1725 case 'S': /* SU -- Scroll <n> line up */
1726 DEFAULT(csiescseq
.arg
[0], 1);
1727 tscrollup(term
.top
, csiescseq
.arg
[0]);
1729 case 'T': /* SD -- Scroll <n> line down */
1730 DEFAULT(csiescseq
.arg
[0], 1);
1731 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1733 case 'L': /* IL -- Insert <n> blank lines */
1734 DEFAULT(csiescseq
.arg
[0], 1);
1735 tinsertblankline(csiescseq
.arg
[0]);
1737 case 'l': /* RM -- Reset Mode */
1738 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1740 case 'M': /* DL -- Delete <n> lines */
1741 DEFAULT(csiescseq
.arg
[0], 1);
1742 tdeleteline(csiescseq
.arg
[0]);
1744 case 'X': /* ECH -- Erase <n> char */
1745 DEFAULT(csiescseq
.arg
[0], 1);
1746 tclearregion(term
.c
.x
, term
.c
.y
,
1747 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1749 case 'P': /* DCH -- Delete <n> char */
1750 DEFAULT(csiescseq
.arg
[0], 1);
1751 tdeletechar(csiescseq
.arg
[0]);
1753 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1754 DEFAULT(csiescseq
.arg
[0], 1);
1755 tputtab(-csiescseq
.arg
[0]);
1757 case 'd': /* VPA -- Move to <row> */
1758 DEFAULT(csiescseq
.arg
[0], 1);
1759 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1761 case 'h': /* SM -- Set terminal mode */
1762 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1764 case 'm': /* SGR -- Terminal attribute (color) */
1765 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1767 case 'n': /* DSR – Device Status Report (cursor position) */
1768 if (csiescseq
.arg
[0] == 6) {
1769 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1770 term
.c
.y
+1, term
.c
.x
+1);
1771 ttywrite(buf
, len
, 0);
1774 case 'r': /* DECSTBM -- Set Scrolling Region */
1775 if (csiescseq
.priv
) {
1778 DEFAULT(csiescseq
.arg
[0], 1);
1779 DEFAULT(csiescseq
.arg
[1], term
.row
);
1780 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1784 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1785 tcursor(CURSOR_SAVE
);
1787 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1788 tcursor(CURSOR_LOAD
);
1791 switch (csiescseq
.mode
[1]) {
1792 case 'q': /* DECSCUSR -- Set Cursor Style */
1793 if (xsetcursor(csiescseq
.arg
[0]))
1809 fprintf(stderr
, "ESC[");
1810 for (i
= 0; i
< csiescseq
.len
; i
++) {
1811 c
= csiescseq
.buf
[i
] & 0xff;
1814 } else if (c
== '\n') {
1815 fprintf(stderr
, "(\\n)");
1816 } else if (c
== '\r') {
1817 fprintf(stderr
, "(\\r)");
1818 } else if (c
== 0x1b) {
1819 fprintf(stderr
, "(\\e)");
1821 fprintf(stderr
, "(%02x)", c
);
1830 memset(&csiescseq
, 0, sizeof(csiescseq
));
1836 char *p
= NULL
, *dec
;
1839 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1841 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1843 switch (strescseq
.type
) {
1844 case ']': /* OSC -- Operating System Command */
1850 xsettitle(strescseq
.args
[1]);
1854 dec
= base64dec(strescseq
.args
[2]);
1859 fprintf(stderr
, "erresc: invalid base64\n");
1863 case 4: /* color set */
1866 p
= strescseq
.args
[2];
1868 case 104: /* color reset, here p = NULL */
1869 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1870 if (xsetcolorname(j
, p
)) {
1871 if (par
== 104 && narg
<= 1)
1872 return; /* color reset without parameter */
1873 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1874 j
, p
? p
: "(null)");
1877 * TODO if defaultbg color is changed, borders
1885 case 'k': /* old title set compatibility */
1886 xsettitle(strescseq
.args
[0]);
1888 case 'P': /* DCS -- Device Control String */
1889 term
.mode
|= ESC_DCS
;
1890 case '_': /* APC -- Application Program Command */
1891 case '^': /* PM -- Privacy Message */
1895 fprintf(stderr
, "erresc: unknown str ");
1903 char *p
= strescseq
.buf
;
1906 strescseq
.buf
[strescseq
.len
] = '\0';
1911 while (strescseq
.narg
< STR_ARG_SIZ
) {
1912 strescseq
.args
[strescseq
.narg
++] = p
;
1913 while ((c
= *p
) != ';' && c
!= '\0')
1927 fprintf(stderr
, "ESC%c", strescseq
.type
);
1928 for (i
= 0; i
< strescseq
.len
; i
++) {
1929 c
= strescseq
.buf
[i
] & 0xff;
1933 } else if (isprint(c
)) {
1935 } else if (c
== '\n') {
1936 fprintf(stderr
, "(\\n)");
1937 } else if (c
== '\r') {
1938 fprintf(stderr
, "(\\r)");
1939 } else if (c
== 0x1b) {
1940 fprintf(stderr
, "(\\e)");
1942 fprintf(stderr
, "(%02x)", c
);
1945 fprintf(stderr
, "ESC\\\n");
1951 memset(&strescseq
, 0, sizeof(strescseq
));
1955 sendbreak(const Arg
*arg
)
1957 if (tcsendbreak(cmdfd
, 0))
1958 perror("Error sending break");
1962 tprinter(char *s
, size_t len
)
1964 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1965 perror("Error writing to output file");
1972 toggleprinter(const Arg
*arg
)
1974 term
.mode
^= MODE_PRINT
;
1978 printscreen(const Arg
*arg
)
1984 printsel(const Arg
*arg
)
1994 if ((ptr
= getsel())) {
1995 tprinter(ptr
, strlen(ptr
));
2006 bp
= &term
.line
[n
][0];
2007 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2008 if (bp
!= end
|| bp
->u
!= ' ') {
2009 for ( ;bp
<= end
; ++bp
)
2010 tprinter(buf
, utf8encode(bp
->u
, buf
));
2020 for (i
= 0; i
< term
.row
; ++i
)
2030 while (x
< term
.col
&& n
--)
2031 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2034 while (x
> 0 && n
++)
2035 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2038 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2042 tdefutf8(char ascii
)
2045 term
.mode
|= MODE_UTF8
;
2046 else if (ascii
== '@')
2047 term
.mode
&= ~MODE_UTF8
;
2051 tdeftran(char ascii
)
2053 static char cs
[] = "0B";
2054 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2057 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2058 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2060 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2069 if (c
== '8') { /* DEC screen alignment test. */
2070 for (x
= 0; x
< term
.col
; ++x
) {
2071 for (y
= 0; y
< term
.row
; ++y
)
2072 tsetchar('E', &term
.c
.attr
, x
, y
);
2078 tstrsequence(uchar c
)
2083 case 0x90: /* DCS -- Device Control String */
2085 term
.esc
|= ESC_DCS
;
2087 case 0x9f: /* APC -- Application Program Command */
2090 case 0x9e: /* PM -- Privacy Message */
2093 case 0x9d: /* OSC -- Operating System Command */
2098 term
.esc
|= ESC_STR
;
2102 tcontrolcode(uchar ascii
)
2109 tmoveto(term
.c
.x
-1, term
.c
.y
);
2112 tmoveto(0, term
.c
.y
);
2117 /* go to first col if the mode is set */
2118 tnewline(IS_SET(MODE_CRLF
));
2120 case '\a': /* BEL */
2121 if (term
.esc
& ESC_STR_END
) {
2122 /* backwards compatibility to xterm */
2128 case '\033': /* ESC */
2130 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2131 term
.esc
|= ESC_START
;
2133 case '\016': /* SO (LS1 -- Locking shift 1) */
2134 case '\017': /* SI (LS0 -- Locking shift 0) */
2135 term
.charset
= 1 - (ascii
- '\016');
2137 case '\032': /* SUB */
2138 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2139 case '\030': /* CAN */
2142 case '\005': /* ENQ (IGNORED) */
2143 case '\000': /* NUL (IGNORED) */
2144 case '\021': /* XON (IGNORED) */
2145 case '\023': /* XOFF (IGNORED) */
2146 case 0177: /* DEL (IGNORED) */
2148 case 0x80: /* TODO: PAD */
2149 case 0x81: /* TODO: HOP */
2150 case 0x82: /* TODO: BPH */
2151 case 0x83: /* TODO: NBH */
2152 case 0x84: /* TODO: IND */
2154 case 0x85: /* NEL -- Next line */
2155 tnewline(1); /* always go to first col */
2157 case 0x86: /* TODO: SSA */
2158 case 0x87: /* TODO: ESA */
2160 case 0x88: /* HTS -- Horizontal tab stop */
2161 term
.tabs
[term
.c
.x
] = 1;
2163 case 0x89: /* TODO: HTJ */
2164 case 0x8a: /* TODO: VTS */
2165 case 0x8b: /* TODO: PLD */
2166 case 0x8c: /* TODO: PLU */
2167 case 0x8d: /* TODO: RI */
2168 case 0x8e: /* TODO: SS2 */
2169 case 0x8f: /* TODO: SS3 */
2170 case 0x91: /* TODO: PU1 */
2171 case 0x92: /* TODO: PU2 */
2172 case 0x93: /* TODO: STS */
2173 case 0x94: /* TODO: CCH */
2174 case 0x95: /* TODO: MW */
2175 case 0x96: /* TODO: SPA */
2176 case 0x97: /* TODO: EPA */
2177 case 0x98: /* TODO: SOS */
2178 case 0x99: /* TODO: SGCI */
2180 case 0x9a: /* DECID -- Identify Terminal */
2181 ttywrite(vtiden
, strlen(vtiden
), 0);
2183 case 0x9b: /* TODO: CSI */
2184 case 0x9c: /* TODO: ST */
2186 case 0x90: /* DCS -- Device Control String */
2187 case 0x9d: /* OSC -- Operating System Command */
2188 case 0x9e: /* PM -- Privacy Message */
2189 case 0x9f: /* APC -- Application Program Command */
2190 tstrsequence(ascii
);
2193 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2194 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2198 * returns 1 when the sequence is finished and it hasn't to read
2199 * more characters for this sequence, otherwise 0
2202 eschandle(uchar ascii
)
2206 term
.esc
|= ESC_CSI
;
2209 term
.esc
|= ESC_TEST
;
2212 term
.esc
|= ESC_UTF8
;
2214 case 'P': /* DCS -- Device Control String */
2215 case '_': /* APC -- Application Program Command */
2216 case '^': /* PM -- Privacy Message */
2217 case ']': /* OSC -- Operating System Command */
2218 case 'k': /* old title set compatibility */
2219 tstrsequence(ascii
);
2221 case 'n': /* LS2 -- Locking shift 2 */
2222 case 'o': /* LS3 -- Locking shift 3 */
2223 term
.charset
= 2 + (ascii
- 'n');
2225 case '(': /* GZD4 -- set primary charset G0 */
2226 case ')': /* G1D4 -- set secondary charset G1 */
2227 case '*': /* G2D4 -- set tertiary charset G2 */
2228 case '+': /* G3D4 -- set quaternary charset G3 */
2229 term
.icharset
= ascii
- '(';
2230 term
.esc
|= ESC_ALTCHARSET
;
2232 case 'D': /* IND -- Linefeed */
2233 if (term
.c
.y
== term
.bot
) {
2234 tscrollup(term
.top
, 1);
2236 tmoveto(term
.c
.x
, term
.c
.y
+1);
2239 case 'E': /* NEL -- Next line */
2240 tnewline(1); /* always go to first col */
2242 case 'H': /* HTS -- Horizontal tab stop */
2243 term
.tabs
[term
.c
.x
] = 1;
2245 case 'M': /* RI -- Reverse index */
2246 if (term
.c
.y
== term
.top
) {
2247 tscrolldown(term
.top
, 1);
2249 tmoveto(term
.c
.x
, term
.c
.y
-1);
2252 case 'Z': /* DECID -- Identify Terminal */
2253 ttywrite(vtiden
, strlen(vtiden
), 0);
2255 case 'c': /* RIS -- Reset to initial state */
2260 case '=': /* DECPAM -- Application keypad */
2261 xsetmode(1, MODE_APPKEYPAD
);
2263 case '>': /* DECPNM -- Normal keypad */
2264 xsetmode(0, MODE_APPKEYPAD
);
2266 case '7': /* DECSC -- Save Cursor */
2267 tcursor(CURSOR_SAVE
);
2269 case '8': /* DECRC -- Restore Cursor */
2270 tcursor(CURSOR_LOAD
);
2272 case '\\': /* ST -- String Terminator */
2273 if (term
.esc
& ESC_STR_END
)
2277 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2278 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2292 control
= ISCONTROL(u
);
2293 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2297 len
= utf8encode(u
, c
);
2298 if (!control
&& (width
= wcwidth(u
)) == -1) {
2299 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2304 if (IS_SET(MODE_PRINT
))
2308 * STR sequence must be checked before anything else
2309 * because it uses all following characters until it
2310 * receives a ESC, a SUB, a ST or any other C1 control
2313 if (term
.esc
& ESC_STR
) {
2314 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2316 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2317 if (IS_SET(MODE_SIXEL
)) {
2318 /* TODO: render sixel */;
2319 term
.mode
&= ~MODE_SIXEL
;
2322 term
.esc
|= ESC_STR_END
;
2323 goto check_control_code
;
2326 if (IS_SET(MODE_SIXEL
)) {
2327 /* TODO: implement sixel mode */
2330 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2331 term
.mode
|= MODE_SIXEL
;
2333 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)) {
2335 * Here is a bug in terminals. If the user never sends
2336 * some code to stop the str or esc command, then st
2337 * will stop responding. But this is better than
2338 * silently failing with unknown characters. At least
2339 * then users will report back.
2341 * In the case users ever get fixed, here is the code:
2350 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2351 strescseq
.len
+= len
;
2357 * Actions of control codes must be performed as soon they arrive
2358 * because they can be embedded inside a control sequence, and
2359 * they must not cause conflicts with sequences.
2364 * control codes are not shown ever
2367 } else if (term
.esc
& ESC_START
) {
2368 if (term
.esc
& ESC_CSI
) {
2369 csiescseq
.buf
[csiescseq
.len
++] = u
;
2370 if (BETWEEN(u
, 0x40, 0x7E)
2371 || csiescseq
.len
>= \
2372 sizeof(csiescseq
.buf
)-1) {
2378 } else if (term
.esc
& ESC_UTF8
) {
2380 } else if (term
.esc
& ESC_ALTCHARSET
) {
2382 } else if (term
.esc
& ESC_TEST
) {
2387 /* sequence already finished */
2391 * All characters which form part of a sequence are not
2396 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2399 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2400 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2401 gp
->mode
|= ATTR_WRAP
;
2403 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2406 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2407 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2409 if (term
.c
.x
+width
> term
.col
) {
2411 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2414 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2417 gp
->mode
|= ATTR_WIDE
;
2418 if (term
.c
.x
+1 < term
.col
) {
2420 gp
[1].mode
= ATTR_WDUMMY
;
2423 if (term
.c
.x
+width
< term
.col
) {
2424 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2426 term
.c
.state
|= CURSOR_WRAPNEXT
;
2431 twrite(const char *buf
, int buflen
, int show_ctrl
)
2437 for (n
= 0; n
< buflen
; n
+= charsize
) {
2438 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2439 /* process a complete utf8 char */
2440 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2447 if (show_ctrl
&& ISCONTROL(u
)) {
2452 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2463 tresize(int col
, int row
)
2466 int minrow
= MIN(row
, term
.row
);
2467 int mincol
= MIN(col
, term
.col
);
2471 if (col
< 1 || row
< 1) {
2473 "tresize: error resizing to %dx%d\n", col
, row
);
2478 * slide screen to keep cursor where we expect it -
2479 * tscrollup would work here, but we can optimize to
2480 * memmove because we're freeing the earlier lines
2482 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2486 /* ensure that both src and dst are not NULL */
2488 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2489 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2491 for (i
+= row
; i
< term
.row
; i
++) {
2496 /* resize to new height */
2497 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2498 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2499 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2500 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2502 /* resize each row to new width, zero-pad if needed */
2503 for (i
= 0; i
< minrow
; i
++) {
2504 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2505 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2508 /* allocate any new rows */
2509 for (/* i = minrow */; i
< row
; i
++) {
2510 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2511 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2513 if (col
> term
.col
) {
2514 bp
= term
.tabs
+ term
.col
;
2516 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2517 while (--bp
> term
.tabs
&& !*bp
)
2519 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2522 /* update terminal size */
2525 /* reset scrolling region */
2526 tsetscroll(0, row
-1);
2527 /* make use of the LIMIT in tmoveto */
2528 tmoveto(term
.c
.x
, term
.c
.y
);
2529 /* Clearing both screens (it makes dirty all lines) */
2531 for (i
= 0; i
< 2; i
++) {
2532 if (mincol
< col
&& 0 < minrow
) {
2533 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2535 if (0 < col
&& minrow
< row
) {
2536 tclearregion(0, minrow
, col
- 1, row
- 1);
2539 tcursor(CURSOR_LOAD
);
2551 drawregion(int x1
, int y1
, int x2
, int y2
)
2554 for (y
= y1
; y
< y2
; y
++) {
2559 xdrawline(term
.line
[y
], x1
, y
, x2
);
2571 /* adjust cursor position */
2572 LIMIT(term
.ocx
, 0, term
.col
-1);
2573 LIMIT(term
.ocy
, 0, term
.row
-1);
2574 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2576 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2579 drawregion(0, 0, term
.col
, term
.row
);
2580 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2581 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2582 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;
2584 xximspot(term
.ocx
, term
.ocy
);