1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
32 #define UTF_INVALID 0xFFFD
34 #define ESC_BUF_SIZ (128*UTF_SIZ)
35 #define ESC_ARG_SIZ 16
36 #define STR_BUF_SIZ ESC_BUF_SIZ
37 #define STR_ARG_SIZ ESC_ARG_SIZ
40 #define IS_SET(flag) ((term.mode & (flag)) != 0)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (u && wcschr(worddelimiters, u))
49 MODE_ALTSCREEN
= 1 << 2,
56 enum cursor_movement
{
80 ESC_STR
= 4, /* DCS, OSC, PM, APC */
82 ESC_STR_END
= 16, /* a final string was encountered */
83 ESC_TEST
= 32, /* Enter in test mode */
88 Glyph attr
; /* current char attributes */
99 * Selection variables:
100 * nb – normalized coordinates of the beginning of the selection
101 * ne – normalized coordinates of the end of the selection
102 * ob – original coordinates of the beginning of the selection
103 * oe – original coordinates of the end of the selection
112 /* Internal representation of the screen */
114 int row
; /* nb row */
115 int col
; /* nb col */
116 Line
*line
; /* screen */
117 Line
*alt
; /* alternate screen */
118 int *dirty
; /* dirtyness of lines */
119 TCursor c
; /* cursor */
120 int ocx
; /* old cursor col */
121 int ocy
; /* old cursor row */
122 int top
; /* top scroll limit */
123 int bot
; /* bottom scroll limit */
124 int mode
; /* terminal mode flags */
125 int esc
; /* escape state flags */
126 char trantbl
[4]; /* charset table translation */
127 int charset
; /* current charset */
128 int icharset
; /* selected charset for sequence */
130 Rune lastc
; /* last printed char outside of sequence, 0 if control */
133 /* CSI Escape sequence structs */
134 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
136 char buf
[ESC_BUF_SIZ
]; /* raw string */
137 size_t len
; /* raw string length */
139 int arg
[ESC_ARG_SIZ
];
140 int narg
; /* nb of args */
144 /* STR Escape sequence structs */
145 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
147 char type
; /* ESC type ... */
148 char *buf
; /* allocated raw string */
149 size_t siz
; /* allocation size */
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 void osc_color_response(int, int, int);
165 static int eschandle(uchar
);
166 static void strdump(void);
167 static void strhandle(void);
168 static void strparse(void);
169 static void strreset(void);
171 static void tprinter(char *, size_t);
172 static void tdumpsel(void);
173 static void tdumpline(int);
174 static void tdump(void);
175 static void tclearregion(int, int, int, int);
176 static void tcursor(int);
177 static void tdeletechar(int);
178 static void tdeleteline(int);
179 static void tinsertblank(int);
180 static void tinsertblankline(int);
181 static int tlinelen(int);
182 static void tmoveto(int, int);
183 static void tmoveato(int, int);
184 static void tnewline(int);
185 static void tputtab(int);
186 static void tputc(Rune
);
187 static void treset(void);
188 static void tscrollup(int, int);
189 static void tscrolldown(int, int);
190 static void tsetattr(const int *, int);
191 static void tsetchar(Rune
, const Glyph
*, int, int);
192 static void tsetdirt(int, int);
193 static void tsetscroll(int, int);
194 static void tswapscreen(void);
195 static void tsetmode(int, int, const int *, int);
196 static int twrite(const char *, int, int);
197 static void tfulldirt(void);
198 static void tcontrolcode(uchar
);
199 static void tdectest(char );
200 static void tdefutf8(char);
201 static int32_t tdefcolor(const int *, int *, int);
202 static void tdeftran(char);
203 static void tstrsequence(uchar
);
205 static void drawregion(int, int, int, int);
207 static void selnormalize(void);
208 static void selscroll(int, int);
209 static void selsnap(int *, int *, int);
211 static size_t utf8decode(const char *, Rune
*, size_t);
212 static Rune
utf8decodebyte(char, size_t *);
213 static char utf8encodebyte(Rune
, size_t);
214 static size_t utf8validate(Rune
*, size_t);
216 static char *base64dec(const char *);
217 static char base64dec_getc(const char **);
219 static ssize_t
xwrite(int, const char *, size_t);
223 static Selection sel
;
224 static CSIEscape csiescseq
;
225 static STREscape strescseq
;
230 static const uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
231 static const uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
232 static const Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
233 static const 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
));
273 xstrdup(const char *s
)
277 if ((p
= strdup(s
)) == NULL
)
278 die("strdup: %s\n", strerror(errno
));
284 utf8decode(const char *c
, Rune
*u
, size_t clen
)
286 size_t i
, j
, len
, type
;
292 udecoded
= utf8decodebyte(c
[0], &len
);
293 if (!BETWEEN(len
, 1, UTF_SIZ
))
295 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
296 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
303 utf8validate(u
, len
);
309 utf8decodebyte(char c
, size_t *i
)
311 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
312 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
313 return (uchar
)c
& ~utfmask
[*i
];
319 utf8encode(Rune u
, char *c
)
323 len
= utf8validate(&u
, 0);
327 for (i
= len
- 1; i
!= 0; --i
) {
328 c
[i
] = utf8encodebyte(u
, 0);
331 c
[0] = utf8encodebyte(u
, len
);
337 utf8encodebyte(Rune u
, size_t i
)
339 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
343 utf8validate(Rune
*u
, size_t i
)
345 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
347 for (i
= 1; *u
> utfmax
[i
]; ++i
)
354 base64dec_getc(const char **src
)
356 while (**src
&& !isprint((unsigned char)**src
))
358 return **src
? *((*src
)++) : '='; /* emulate padding if string ends */
362 base64dec(const char *src
)
364 size_t in_len
= strlen(src
);
366 static const char base64_digits
[256] = {
367 [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
368 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
369 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
370 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
371 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
375 in_len
+= 4 - (in_len
% 4);
376 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
378 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
379 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
380 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
381 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
383 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
384 if (a
== -1 || b
== -1)
387 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
390 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
393 *dst
++ = ((c
& 0x03) << 6) | d
;
412 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
415 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
422 selstart(int col
, int row
, int snap
)
425 sel
.mode
= SEL_EMPTY
;
426 sel
.type
= SEL_REGULAR
;
427 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
429 sel
.oe
.x
= sel
.ob
.x
= col
;
430 sel
.oe
.y
= sel
.ob
.y
= row
;
434 sel
.mode
= SEL_READY
;
435 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
439 selextend(int col
, int row
, int type
, int done
)
441 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
443 if (sel
.mode
== SEL_IDLE
)
445 if (done
&& sel
.mode
== SEL_EMPTY
) {
461 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
|| sel
.mode
== SEL_EMPTY
)
462 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
464 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
472 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
473 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
474 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
476 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
477 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
479 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
480 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
482 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
483 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
485 /* expand selection over line breaks */
486 if (sel
.type
== SEL_RECTANGULAR
)
488 i
= tlinelen(sel
.nb
.y
);
491 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
492 sel
.ne
.x
= term
.col
- 1;
496 selected(int x
, int y
)
498 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
499 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
502 if (sel
.type
== SEL_RECTANGULAR
)
503 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
504 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
506 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
507 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
508 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
512 selsnap(int *x
, int *y
, int direction
)
514 int newx
, newy
, xt
, yt
;
515 int delim
, prevdelim
;
516 const Glyph
*gp
, *prevgp
;
521 * Snap around if the word wraps around at the end or
522 * beginning of a line.
524 prevgp
= &term
.line
[*y
][*x
];
525 prevdelim
= ISDELIM(prevgp
->u
);
527 newx
= *x
+ direction
;
529 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
531 newx
= (newx
+ term
.col
) % term
.col
;
532 if (!BETWEEN(newy
, 0, term
.row
- 1))
538 yt
= newy
, xt
= newx
;
539 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
543 if (newx
>= tlinelen(newy
))
546 gp
= &term
.line
[newy
][newx
];
547 delim
= ISDELIM(gp
->u
);
548 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
549 || (delim
&& gp
->u
!= prevgp
->u
)))
560 * Snap around if the the previous line or the current one
561 * has set ATTR_WRAP at its end. Then the whole next or
562 * previous line will be selected.
564 *x
= (direction
< 0) ? 0 : term
.col
- 1;
566 for (; *y
> 0; *y
+= direction
) {
567 if (!(term
.line
[*y
-1][term
.col
-1].mode
572 } else if (direction
> 0) {
573 for (; *y
< term
.row
-1; *y
+= direction
) {
574 if (!(term
.line
[*y
][term
.col
-1].mode
588 int y
, bufsize
, lastx
, linelen
;
589 const Glyph
*gp
, *last
;
594 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
595 ptr
= str
= xmalloc(bufsize
);
597 /* append every set & selected glyph to the selection */
598 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
599 if ((linelen
= tlinelen(y
)) == 0) {
604 if (sel
.type
== SEL_RECTANGULAR
) {
605 gp
= &term
.line
[y
][sel
.nb
.x
];
608 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
609 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
611 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
612 while (last
>= gp
&& last
->u
== ' ')
615 for ( ; gp
<= last
; ++gp
) {
616 if (gp
->mode
& ATTR_WDUMMY
)
619 ptr
+= utf8encode(gp
->u
, ptr
);
623 * Copy and pasting of line endings is inconsistent
624 * in the inconsistent terminal and GUI world.
625 * The best solution seems like to produce '\n' when
626 * something is copied from st and convert '\n' to
627 * '\r', when something to be pasted is received by
629 * FIXME: Fix the computer world.
631 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) &&
632 (!(last
->mode
& ATTR_WRAP
) || sel
.type
== SEL_RECTANGULAR
))
646 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
650 die(const char *errstr
, ...)
654 va_start(ap
, errstr
);
655 vfprintf(stderr
, errstr
, ap
);
661 execsh(char *cmd
, char **args
)
663 char *sh
, *prog
, *arg
;
664 const struct passwd
*pw
;
667 if ((pw
= getpwuid(getuid())) == NULL
) {
669 die("getpwuid: %s\n", strerror(errno
));
671 die("who are you?\n");
674 if ((sh
= getenv("SHELL")) == NULL
)
675 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
682 arg
= utmp
? utmp
: sh
;
690 DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
}));
695 setenv("LOGNAME", pw
->pw_name
, 1);
696 setenv("USER", pw
->pw_name
, 1);
697 setenv("SHELL", sh
, 1);
698 setenv("HOME", pw
->pw_dir
, 1);
699 setenv("TERM", termname
, 1);
701 signal(SIGCHLD
, SIG_DFL
);
702 signal(SIGHUP
, SIG_DFL
);
703 signal(SIGINT
, SIG_DFL
);
704 signal(SIGQUIT
, SIG_DFL
);
705 signal(SIGTERM
, SIG_DFL
);
706 signal(SIGALRM
, SIG_DFL
);
718 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
719 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
724 if (WIFEXITED(stat
) && WEXITSTATUS(stat
))
725 die("child exited with status %d\n", WEXITSTATUS(stat
));
726 else if (WIFSIGNALED(stat
))
727 die("child terminated due to signal %d\n", WTERMSIG(stat
));
734 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
737 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
738 die("incorrect stty parameters\n");
739 memcpy(cmd
, stty_args
, n
);
741 siz
= sizeof(cmd
) - n
;
742 for (p
= args
; p
&& (s
= *p
); ++p
) {
743 if ((n
= strlen(s
)) > siz
-1)
744 die("stty parameter length too long\n");
751 if (system(cmd
) != 0)
752 perror("Couldn't call stty");
756 ttynew(const char *line
, char *cmd
, const char *out
, char **args
)
761 term
.mode
|= MODE_PRINT
;
762 iofd
= (!strcmp(out
, "-")) ?
763 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
765 fprintf(stderr
, "Error opening %s:%s\n",
766 out
, strerror(errno
));
771 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
772 die("open line '%s' failed: %s\n",
773 line
, strerror(errno
));
779 /* seems to work fine on linux, openbsd and freebsd */
780 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
781 die("openpty failed: %s\n", strerror(errno
));
783 switch (pid
= fork()) {
785 die("fork failed: %s\n", strerror(errno
));
790 setsid(); /* create a new process group */
794 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
795 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
799 if (pledge("stdio getpw proc exec", NULL
) == -1)
806 if (pledge("stdio rpath tty proc", NULL
) == -1)
811 signal(SIGCHLD
, sigchld
);
820 static char buf
[BUFSIZ
];
821 static int buflen
= 0;
824 /* append read bytes to unprocessed bytes */
825 ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
);
831 die("couldn't read from shell: %s\n", strerror(errno
));
834 written
= twrite(buf
, buflen
, 0);
836 /* keep any incomplete UTF-8 byte sequence for the next call */
838 memmove(buf
, buf
+ written
, buflen
);
844 ttywrite(const char *s
, size_t n
, int may_echo
)
848 if (may_echo
&& IS_SET(MODE_ECHO
))
851 if (!IS_SET(MODE_CRLF
)) {
856 /* This is similar to how the kernel handles ONLCR for ttys */
860 ttywriteraw("\r\n", 2);
862 next
= memchr(s
, '\r', n
);
863 DEFAULT(next
, s
+ n
);
864 ttywriteraw(s
, next
- s
);
872 ttywriteraw(const char *s
, size_t n
)
879 * Remember that we are using a pty, which might be a modem line.
880 * Writing too much will clog the line. That's why we are doing this
882 * FIXME: Migrate the world to Plan 9.
890 /* Check if we can write. */
891 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
894 die("select failed: %s\n", strerror(errno
));
896 if (FD_ISSET(cmdfd
, &wfd
)) {
898 * Only write the bytes written by ttywrite() or the
899 * default of 256. This seems to be a reasonable value
900 * for a serial line. Bigger values might clog the I/O.
902 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
906 * We weren't able to write out everything.
907 * This means the buffer is getting full
915 /* All bytes have been written. */
919 if (FD_ISSET(cmdfd
, &rfd
))
925 die("write error on tty: %s\n", strerror(errno
));
929 ttyresize(int tw
, int th
)
937 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
938 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
944 /* Send SIGHUP to shell */
953 for (i
= 0; i
< term
.row
-1; i
++) {
954 for (j
= 0; j
< term
.col
-1; j
++) {
955 if (term
.line
[i
][j
].mode
& attr
)
964 tsetdirt(int top
, int bot
)
968 LIMIT(top
, 0, term
.row
-1);
969 LIMIT(bot
, 0, term
.row
-1);
971 for (i
= top
; i
<= bot
; i
++)
976 tsetdirtattr(int attr
)
980 for (i
= 0; i
< term
.row
-1; i
++) {
981 for (j
= 0; j
< term
.col
-1; j
++) {
982 if (term
.line
[i
][j
].mode
& attr
) {
993 tsetdirt(0, term
.row
-1);
1000 int alt
= IS_SET(MODE_ALTSCREEN
);
1002 if (mode
== CURSOR_SAVE
) {
1004 } else if (mode
== CURSOR_LOAD
) {
1006 tmoveto(c
[alt
].x
, c
[alt
].y
);
1015 term
.c
= (TCursor
){{
1019 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1021 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1022 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1025 term
.bot
= term
.row
- 1;
1026 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1027 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1030 for (i
= 0; i
< 2; i
++) {
1032 tcursor(CURSOR_SAVE
);
1033 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1039 tnew(int col
, int row
)
1041 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1049 Line
*tmp
= term
.line
;
1051 term
.line
= term
.alt
;
1053 term
.mode
^= MODE_ALTSCREEN
;
1058 tscrolldown(int orig
, int n
)
1063 LIMIT(n
, 0, term
.bot
-orig
+1);
1065 tsetdirt(orig
, term
.bot
-n
);
1066 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1068 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1069 temp
= term
.line
[i
];
1070 term
.line
[i
] = term
.line
[i
-n
];
1071 term
.line
[i
-n
] = temp
;
1078 tscrollup(int orig
, int n
)
1083 LIMIT(n
, 0, term
.bot
-orig
+1);
1085 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1086 tsetdirt(orig
+n
, term
.bot
);
1088 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1089 temp
= term
.line
[i
];
1090 term
.line
[i
] = term
.line
[i
+n
];
1091 term
.line
[i
+n
] = temp
;
1094 selscroll(orig
, -n
);
1098 selscroll(int orig
, int n
)
1100 if (sel
.ob
.x
== -1 || sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
1103 if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
) != BETWEEN(sel
.ne
.y
, orig
, term
.bot
)) {
1105 } else if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
)) {
1108 if (sel
.ob
.y
< term
.top
|| sel
.ob
.y
> term
.bot
||
1109 sel
.oe
.y
< term
.top
|| sel
.oe
.y
> term
.bot
) {
1118 tnewline(int first_col
)
1122 if (y
== term
.bot
) {
1123 tscrollup(term
.top
, 1);
1127 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1133 char *p
= csiescseq
.buf
, *np
;
1135 int sep
= ';'; /* colon or semi-colon, but not both */
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 (sep
== ';' && *p
== ':')
1154 sep
= ':'; /* allow override to colon once */
1155 if (*p
!= sep
|| csiescseq
.narg
== ESC_ARG_SIZ
)
1159 csiescseq
.mode
[0] = *p
++;
1160 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1163 /* for absolute user moves, when decom is set */
1165 tmoveato(int x
, int y
)
1167 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1171 tmoveto(int x
, int y
)
1175 if (term
.c
.state
& CURSOR_ORIGIN
) {
1180 maxy
= term
.row
- 1;
1182 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1183 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1184 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1188 tsetchar(Rune u
, const Glyph
*attr
, int x
, int y
)
1190 static const char *vt100_0
[62] = { /* 0x41 - 0x7e */
1191 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1192 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1193 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1194 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1195 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1196 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1197 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1198 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1202 * The table is proudly stolen from rxvt.
1204 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1205 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1206 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1208 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1209 if (x
+1 < term
.col
) {
1210 term
.line
[y
][x
+1].u
= ' ';
1211 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1213 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1214 term
.line
[y
][x
-1].u
= ' ';
1215 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1219 term
.line
[y
][x
] = *attr
;
1220 term
.line
[y
][x
].u
= u
;
1224 tclearregion(int x1
, int y1
, int x2
, int y2
)
1230 temp
= x1
, x1
= x2
, x2
= temp
;
1232 temp
= y1
, y1
= y2
, y2
= temp
;
1234 LIMIT(x1
, 0, term
.col
-1);
1235 LIMIT(x2
, 0, term
.col
-1);
1236 LIMIT(y1
, 0, term
.row
-1);
1237 LIMIT(y2
, 0, term
.row
-1);
1239 for (y
= y1
; y
<= y2
; y
++) {
1241 for (x
= x1
; x
<= x2
; x
++) {
1242 gp
= &term
.line
[y
][x
];
1245 gp
->fg
= term
.c
.attr
.fg
;
1246 gp
->bg
= term
.c
.attr
.bg
;
1259 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1263 size
= term
.col
- src
;
1264 line
= term
.line
[term
.c
.y
];
1266 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1267 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1276 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1280 size
= term
.col
- dst
;
1281 line
= term
.line
[term
.c
.y
];
1283 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1284 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1288 tinsertblankline(int n
)
1290 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1291 tscrolldown(term
.c
.y
, n
);
1297 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1298 tscrollup(term
.c
.y
, n
);
1302 tdefcolor(const int *attr
, int *npar
, int l
)
1307 switch (attr
[*npar
+ 1]) {
1308 case 2: /* direct color in RGB space */
1309 if (*npar
+ 4 >= l
) {
1311 "erresc(38): Incorrect number of parameters (%d)\n",
1315 r
= attr
[*npar
+ 2];
1316 g
= attr
[*npar
+ 3];
1317 b
= attr
[*npar
+ 4];
1319 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1320 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1323 idx
= TRUECOLOR(r
, g
, b
);
1325 case 5: /* indexed color */
1326 if (*npar
+ 2 >= l
) {
1328 "erresc(38): Incorrect number of parameters (%d)\n",
1333 if (!BETWEEN(attr
[*npar
], 0, 255))
1334 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1338 case 0: /* implemented defined (only foreground) */
1339 case 1: /* transparent */
1340 case 3: /* direct color in CMY space */
1341 case 4: /* direct color in CMYK space */
1344 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1352 tsetattr(const int *attr
, int l
)
1357 for (i
= 0; i
< l
; i
++) {
1360 term
.c
.attr
.mode
&= ~(
1369 term
.c
.attr
.fg
= defaultfg
;
1370 term
.c
.attr
.bg
= defaultbg
;
1373 term
.c
.attr
.mode
|= ATTR_BOLD
;
1376 term
.c
.attr
.mode
|= ATTR_FAINT
;
1379 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1382 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1384 case 5: /* slow blink */
1386 case 6: /* rapid blink */
1387 term
.c
.attr
.mode
|= ATTR_BLINK
;
1390 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1393 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1396 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1399 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1402 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1405 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1408 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1411 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1414 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1417 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1420 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1421 term
.c
.attr
.fg
= idx
;
1424 term
.c
.attr
.fg
= defaultfg
;
1427 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1428 term
.c
.attr
.bg
= idx
;
1431 term
.c
.attr
.bg
= defaultbg
;
1434 if (BETWEEN(attr
[i
], 30, 37)) {
1435 term
.c
.attr
.fg
= attr
[i
] - 30;
1436 } else if (BETWEEN(attr
[i
], 40, 47)) {
1437 term
.c
.attr
.bg
= attr
[i
] - 40;
1438 } else if (BETWEEN(attr
[i
], 90, 97)) {
1439 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1440 } else if (BETWEEN(attr
[i
], 100, 107)) {
1441 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1444 "erresc(default): gfx attr %d unknown\n",
1454 tsetscroll(int t
, int b
)
1458 LIMIT(t
, 0, term
.row
-1);
1459 LIMIT(b
, 0, term
.row
-1);
1470 tsetmode(int priv
, int set
, const int *args
, int narg
)
1472 int alt
; const int *lim
;
1474 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1477 case 1: /* DECCKM -- Cursor key */
1478 xsetmode(set
, MODE_APPCURSOR
);
1480 case 5: /* DECSCNM -- Reverse video */
1481 xsetmode(set
, MODE_REVERSE
);
1483 case 6: /* DECOM -- Origin */
1484 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1487 case 7: /* DECAWM -- Auto wrap */
1488 MODBIT(term
.mode
, set
, MODE_WRAP
);
1490 case 0: /* Error (IGNORED) */
1491 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1492 case 3: /* DECCOLM -- Column (IGNORED) */
1493 case 4: /* DECSCLM -- Scroll (IGNORED) */
1494 case 8: /* DECARM -- Auto repeat (IGNORED) */
1495 case 18: /* DECPFF -- Printer feed (IGNORED) */
1496 case 19: /* DECPEX -- Printer extent (IGNORED) */
1497 case 42: /* DECNRCM -- National characters (IGNORED) */
1498 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1500 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1501 xsetmode(!set
, MODE_HIDE
);
1503 case 9: /* X10 mouse compatibility mode */
1504 xsetpointermotion(0);
1505 xsetmode(0, MODE_MOUSE
);
1506 xsetmode(set
, MODE_MOUSEX10
);
1508 case 1000: /* 1000: report button press */
1509 xsetpointermotion(0);
1510 xsetmode(0, MODE_MOUSE
);
1511 xsetmode(set
, MODE_MOUSEBTN
);
1513 case 1002: /* 1002: report motion on button press */
1514 xsetpointermotion(0);
1515 xsetmode(0, MODE_MOUSE
);
1516 xsetmode(set
, MODE_MOUSEMOTION
);
1518 case 1003: /* 1003: enable all mouse motions */
1519 xsetpointermotion(set
);
1520 xsetmode(0, MODE_MOUSE
);
1521 xsetmode(set
, MODE_MOUSEMANY
);
1523 case 1004: /* 1004: send focus events to tty */
1524 xsetmode(set
, MODE_FOCUS
);
1526 case 1006: /* 1006: extended reporting mode */
1527 xsetmode(set
, MODE_MOUSESGR
);
1530 xsetmode(set
, MODE_8BIT
);
1532 case 1049: /* swap screen & set/restore cursor as xterm */
1533 if (!allowaltscreen
)
1535 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1537 case 47: /* swap screen */
1539 if (!allowaltscreen
)
1541 alt
= IS_SET(MODE_ALTSCREEN
);
1543 tclearregion(0, 0, term
.col
-1,
1546 if (set
^ alt
) /* set is always 1 or 0 */
1552 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1554 case 2004: /* 2004: bracketed paste mode */
1555 xsetmode(set
, MODE_BRCKTPASTE
);
1557 /* Not implemented mouse modes. See comments there. */
1558 case 1001: /* mouse highlight mode; can hang the
1559 terminal by design when implemented. */
1560 case 1005: /* UTF-8 mouse mode; will confuse
1561 applications not supporting UTF-8
1563 case 1015: /* urxvt mangled mouse mode; incompatible
1564 and can be mistaken for other control
1569 "erresc: unknown private set/reset mode %d\n",
1575 case 0: /* Error (IGNORED) */
1578 xsetmode(set
, MODE_KBDLOCK
);
1580 case 4: /* IRM -- Insertion-replacement */
1581 MODBIT(term
.mode
, set
, MODE_INSERT
);
1583 case 12: /* SRM -- Send/Receive */
1584 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1586 case 20: /* LNM -- Linefeed/new line */
1587 MODBIT(term
.mode
, set
, MODE_CRLF
);
1591 "erresc: unknown set/reset mode %d\n",
1605 switch (csiescseq
.mode
[0]) {
1608 fprintf(stderr
, "erresc: unknown csi ");
1612 case '@': /* ICH -- Insert <n> blank char */
1613 DEFAULT(csiescseq
.arg
[0], 1);
1614 tinsertblank(csiescseq
.arg
[0]);
1616 case 'A': /* CUU -- Cursor <n> Up */
1617 DEFAULT(csiescseq
.arg
[0], 1);
1618 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1620 case 'B': /* CUD -- Cursor <n> Down */
1621 case 'e': /* VPR --Cursor <n> Down */
1622 DEFAULT(csiescseq
.arg
[0], 1);
1623 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1625 case 'i': /* MC -- Media Copy */
1626 switch (csiescseq
.arg
[0]) {
1631 tdumpline(term
.c
.y
);
1637 term
.mode
&= ~MODE_PRINT
;
1640 term
.mode
|= MODE_PRINT
;
1644 case 'c': /* DA -- Device Attributes */
1645 if (csiescseq
.arg
[0] == 0)
1646 ttywrite(vtiden
, strlen(vtiden
), 0);
1648 case 'b': /* REP -- if last char is printable print it <n> more times */
1649 LIMIT(csiescseq
.arg
[0], 1, 65535);
1651 while (csiescseq
.arg
[0]-- > 0)
1654 case 'C': /* CUF -- Cursor <n> Forward */
1655 case 'a': /* HPR -- Cursor <n> Forward */
1656 DEFAULT(csiescseq
.arg
[0], 1);
1657 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1659 case 'D': /* CUB -- Cursor <n> Backward */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1663 case 'E': /* CNL -- Cursor <n> Down and first col */
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1667 case 'F': /* CPL -- Cursor <n> Up and first col */
1668 DEFAULT(csiescseq
.arg
[0], 1);
1669 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1671 case 'g': /* TBC -- Tabulation clear */
1672 switch (csiescseq
.arg
[0]) {
1673 case 0: /* clear current tab stop */
1674 term
.tabs
[term
.c
.x
] = 0;
1676 case 3: /* clear all the tabs */
1677 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1683 case 'G': /* CHA -- Move to <col> */
1685 DEFAULT(csiescseq
.arg
[0], 1);
1686 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1688 case 'H': /* CUP -- Move to <row> <col> */
1690 DEFAULT(csiescseq
.arg
[0], 1);
1691 DEFAULT(csiescseq
.arg
[1], 1);
1692 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1694 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1695 DEFAULT(csiescseq
.arg
[0], 1);
1696 tputtab(csiescseq
.arg
[0]);
1698 case 'J': /* ED -- Clear screen */
1699 switch (csiescseq
.arg
[0]) {
1701 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1702 if (term
.c
.y
< term
.row
-1) {
1703 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1709 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1710 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1713 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1719 case 'K': /* EL -- Clear line */
1720 switch (csiescseq
.arg
[0]) {
1722 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1726 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1729 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1733 case 'S': /* SU -- Scroll <n> line up */
1734 if (csiescseq
.priv
) break;
1735 DEFAULT(csiescseq
.arg
[0], 1);
1736 tscrollup(term
.top
, csiescseq
.arg
[0]);
1738 case 'T': /* SD -- Scroll <n> line down */
1739 DEFAULT(csiescseq
.arg
[0], 1);
1740 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1742 case 'L': /* IL -- Insert <n> blank lines */
1743 DEFAULT(csiescseq
.arg
[0], 1);
1744 tinsertblankline(csiescseq
.arg
[0]);
1746 case 'l': /* RM -- Reset Mode */
1747 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1749 case 'M': /* DL -- Delete <n> lines */
1750 DEFAULT(csiescseq
.arg
[0], 1);
1751 tdeleteline(csiescseq
.arg
[0]);
1753 case 'X': /* ECH -- Erase <n> char */
1754 DEFAULT(csiescseq
.arg
[0], 1);
1755 tclearregion(term
.c
.x
, term
.c
.y
,
1756 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1758 case 'P': /* DCH -- Delete <n> char */
1759 DEFAULT(csiescseq
.arg
[0], 1);
1760 tdeletechar(csiescseq
.arg
[0]);
1762 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1763 DEFAULT(csiescseq
.arg
[0], 1);
1764 tputtab(-csiescseq
.arg
[0]);
1766 case 'd': /* VPA -- Move to <row> */
1767 DEFAULT(csiescseq
.arg
[0], 1);
1768 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1770 case 'h': /* SM -- Set terminal mode */
1771 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1773 case 'm': /* SGR -- Terminal attribute (color) */
1774 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1776 case 'n': /* DSR -- Device Status Report */
1777 switch (csiescseq
.arg
[0]) {
1778 case 5: /* Status Report "OK" `0n` */
1779 ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
1781 case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
1782 len
= snprintf(buf
, sizeof(buf
), "\033[%i;%iR",
1783 term
.c
.y
+1, term
.c
.x
+1);
1784 ttywrite(buf
, len
, 0);
1790 case 'r': /* DECSTBM -- Set Scrolling Region */
1791 if (csiescseq
.priv
) {
1794 DEFAULT(csiescseq
.arg
[0], 1);
1795 DEFAULT(csiescseq
.arg
[1], term
.row
);
1796 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1800 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1801 tcursor(CURSOR_SAVE
);
1803 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1804 tcursor(CURSOR_LOAD
);
1807 switch (csiescseq
.mode
[1]) {
1808 case 'q': /* DECSCUSR -- Set Cursor Style */
1809 if (xsetcursor(csiescseq
.arg
[0]))
1825 fprintf(stderr
, "ESC[");
1826 for (i
= 0; i
< csiescseq
.len
; i
++) {
1827 c
= csiescseq
.buf
[i
] & 0xff;
1830 } else if (c
== '\n') {
1831 fprintf(stderr
, "(\\n)");
1832 } else if (c
== '\r') {
1833 fprintf(stderr
, "(\\r)");
1834 } else if (c
== 0x1b) {
1835 fprintf(stderr
, "(\\e)");
1837 fprintf(stderr
, "(%02x)", c
);
1846 memset(&csiescseq
, 0, sizeof(csiescseq
));
1850 osc_color_response(int num
, int index
, int is_osc4
)
1854 unsigned char r
, g
, b
;
1856 if (xgetcolor(is_osc4
? num
: index
, &r
, &g
, &b
)) {
1857 fprintf(stderr
, "erresc: failed to fetch %s color %d\n",
1858 is_osc4
? "osc4" : "osc",
1859 is_osc4
? num
: index
);
1863 n
= snprintf(buf
, sizeof buf
, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1864 is_osc4
? "4;" : "", num
, r
, r
, g
, g
, b
, b
);
1865 if (n
< 0 || n
>= sizeof(buf
)) {
1866 fprintf(stderr
, "error: %s while printing %s response\n",
1867 n
< 0 ? "snprintf failed" : "truncation occurred",
1868 is_osc4
? "osc4" : "osc");
1870 ttywrite(buf
, n
, 1);
1877 char *p
= NULL
, *dec
;
1879 const struct { int idx
; char *str
; } osc_table
[] = {
1880 { defaultfg
, "foreground" },
1881 { defaultbg
, "background" },
1882 { defaultcs
, "cursor" }
1885 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1887 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1889 switch (strescseq
.type
) {
1890 case ']': /* OSC -- Operating System Command */
1894 xsettitle(strescseq
.args
[1]);
1895 xseticontitle(strescseq
.args
[1]);
1900 xseticontitle(strescseq
.args
[1]);
1904 xsettitle(strescseq
.args
[1]);
1907 if (narg
> 2 && allowwindowops
) {
1908 dec
= base64dec(strescseq
.args
[2]);
1913 fprintf(stderr
, "erresc: invalid base64\n");
1922 p
= strescseq
.args
[1];
1923 if ((j
= par
- 10) < 0 || j
>= LEN(osc_table
))
1924 break; /* shouldn't be possible */
1926 if (!strcmp(p
, "?")) {
1927 osc_color_response(par
, osc_table
[j
].idx
, 0);
1928 } else if (xsetcolorname(osc_table
[j
].idx
, p
)) {
1929 fprintf(stderr
, "erresc: invalid %s color: %s\n",
1930 osc_table
[j
].str
, p
);
1935 case 4: /* color set */
1938 p
= strescseq
.args
[2];
1940 case 104: /* color reset */
1941 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1943 if (p
&& !strcmp(p
, "?")) {
1944 osc_color_response(j
, 0, 1);
1945 } else if (xsetcolorname(j
, p
)) {
1946 if (par
== 104 && narg
<= 1) {
1948 return; /* color reset without parameter */
1950 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1951 j
, p
? p
: "(null)");
1954 * TODO if defaultbg color is changed, borders
1962 case 'k': /* old title set compatibility */
1963 xsettitle(strescseq
.args
[0]);
1965 case 'P': /* DCS -- Device Control String */
1966 case '_': /* APC -- Application Program Command */
1967 case '^': /* PM -- Privacy Message */
1971 fprintf(stderr
, "erresc: unknown str ");
1979 char *p
= strescseq
.buf
;
1982 strescseq
.buf
[strescseq
.len
] = '\0';
1987 while (strescseq
.narg
< STR_ARG_SIZ
) {
1988 strescseq
.args
[strescseq
.narg
++] = p
;
1989 while ((c
= *p
) != ';' && c
!= '\0')
2003 fprintf(stderr
, "ESC%c", strescseq
.type
);
2004 for (i
= 0; i
< strescseq
.len
; i
++) {
2005 c
= strescseq
.buf
[i
] & 0xff;
2009 } else if (isprint(c
)) {
2011 } else if (c
== '\n') {
2012 fprintf(stderr
, "(\\n)");
2013 } else if (c
== '\r') {
2014 fprintf(stderr
, "(\\r)");
2015 } else if (c
== 0x1b) {
2016 fprintf(stderr
, "(\\e)");
2018 fprintf(stderr
, "(%02x)", c
);
2021 fprintf(stderr
, "ESC\\\n");
2027 strescseq
= (STREscape
){
2028 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
2034 sendbreak(const Arg
*arg
)
2036 if (tcsendbreak(cmdfd
, 0))
2037 perror("Error sending break");
2041 tprinter(char *s
, size_t len
)
2043 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
2044 perror("Error writing to output file");
2051 toggleprinter(const Arg
*arg
)
2053 term
.mode
^= MODE_PRINT
;
2057 printscreen(const Arg
*arg
)
2063 printsel(const Arg
*arg
)
2073 if ((ptr
= getsel())) {
2074 tprinter(ptr
, strlen(ptr
));
2083 const Glyph
*bp
, *end
;
2085 bp
= &term
.line
[n
][0];
2086 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2087 if (bp
!= end
|| bp
->u
!= ' ') {
2088 for ( ; bp
<= end
; ++bp
)
2089 tprinter(buf
, utf8encode(bp
->u
, buf
));
2099 for (i
= 0; i
< term
.row
; ++i
)
2109 while (x
< term
.col
&& n
--)
2110 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2113 while (x
> 0 && n
++)
2114 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2117 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2121 tdefutf8(char ascii
)
2124 term
.mode
|= MODE_UTF8
;
2125 else if (ascii
== '@')
2126 term
.mode
&= ~MODE_UTF8
;
2130 tdeftran(char ascii
)
2132 static char cs
[] = "0B";
2133 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2136 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2137 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2139 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2148 if (c
== '8') { /* DEC screen alignment test. */
2149 for (x
= 0; x
< term
.col
; ++x
) {
2150 for (y
= 0; y
< term
.row
; ++y
)
2151 tsetchar('E', &term
.c
.attr
, x
, y
);
2157 tstrsequence(uchar c
)
2160 case 0x90: /* DCS -- Device Control String */
2163 case 0x9f: /* APC -- Application Program Command */
2166 case 0x9e: /* PM -- Privacy Message */
2169 case 0x9d: /* OSC -- Operating System Command */
2175 term
.esc
|= ESC_STR
;
2179 tcontrolcode(uchar ascii
)
2186 tmoveto(term
.c
.x
-1, term
.c
.y
);
2189 tmoveto(0, term
.c
.y
);
2194 /* go to first col if the mode is set */
2195 tnewline(IS_SET(MODE_CRLF
));
2197 case '\a': /* BEL */
2198 if (term
.esc
& ESC_STR_END
) {
2199 /* backwards compatibility to xterm */
2205 case '\033': /* ESC */
2207 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2208 term
.esc
|= ESC_START
;
2210 case '\016': /* SO (LS1 -- Locking shift 1) */
2211 case '\017': /* SI (LS0 -- Locking shift 0) */
2212 term
.charset
= 1 - (ascii
- '\016');
2214 case '\032': /* SUB */
2215 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2217 case '\030': /* CAN */
2220 case '\005': /* ENQ (IGNORED) */
2221 case '\000': /* NUL (IGNORED) */
2222 case '\021': /* XON (IGNORED) */
2223 case '\023': /* XOFF (IGNORED) */
2224 case 0177: /* DEL (IGNORED) */
2226 case 0x80: /* TODO: PAD */
2227 case 0x81: /* TODO: HOP */
2228 case 0x82: /* TODO: BPH */
2229 case 0x83: /* TODO: NBH */
2230 case 0x84: /* TODO: IND */
2232 case 0x85: /* NEL -- Next line */
2233 tnewline(1); /* always go to first col */
2235 case 0x86: /* TODO: SSA */
2236 case 0x87: /* TODO: ESA */
2238 case 0x88: /* HTS -- Horizontal tab stop */
2239 term
.tabs
[term
.c
.x
] = 1;
2241 case 0x89: /* TODO: HTJ */
2242 case 0x8a: /* TODO: VTS */
2243 case 0x8b: /* TODO: PLD */
2244 case 0x8c: /* TODO: PLU */
2245 case 0x8d: /* TODO: RI */
2246 case 0x8e: /* TODO: SS2 */
2247 case 0x8f: /* TODO: SS3 */
2248 case 0x91: /* TODO: PU1 */
2249 case 0x92: /* TODO: PU2 */
2250 case 0x93: /* TODO: STS */
2251 case 0x94: /* TODO: CCH */
2252 case 0x95: /* TODO: MW */
2253 case 0x96: /* TODO: SPA */
2254 case 0x97: /* TODO: EPA */
2255 case 0x98: /* TODO: SOS */
2256 case 0x99: /* TODO: SGCI */
2258 case 0x9a: /* DECID -- Identify Terminal */
2259 ttywrite(vtiden
, strlen(vtiden
), 0);
2261 case 0x9b: /* TODO: CSI */
2262 case 0x9c: /* TODO: ST */
2264 case 0x90: /* DCS -- Device Control String */
2265 case 0x9d: /* OSC -- Operating System Command */
2266 case 0x9e: /* PM -- Privacy Message */
2267 case 0x9f: /* APC -- Application Program Command */
2268 tstrsequence(ascii
);
2271 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2272 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2276 * returns 1 when the sequence is finished and it hasn't to read
2277 * more characters for this sequence, otherwise 0
2280 eschandle(uchar ascii
)
2284 term
.esc
|= ESC_CSI
;
2287 term
.esc
|= ESC_TEST
;
2290 term
.esc
|= ESC_UTF8
;
2292 case 'P': /* DCS -- Device Control String */
2293 case '_': /* APC -- Application Program Command */
2294 case '^': /* PM -- Privacy Message */
2295 case ']': /* OSC -- Operating System Command */
2296 case 'k': /* old title set compatibility */
2297 tstrsequence(ascii
);
2299 case 'n': /* LS2 -- Locking shift 2 */
2300 case 'o': /* LS3 -- Locking shift 3 */
2301 term
.charset
= 2 + (ascii
- 'n');
2303 case '(': /* GZD4 -- set primary charset G0 */
2304 case ')': /* G1D4 -- set secondary charset G1 */
2305 case '*': /* G2D4 -- set tertiary charset G2 */
2306 case '+': /* G3D4 -- set quaternary charset G3 */
2307 term
.icharset
= ascii
- '(';
2308 term
.esc
|= ESC_ALTCHARSET
;
2310 case 'D': /* IND -- Linefeed */
2311 if (term
.c
.y
== term
.bot
) {
2312 tscrollup(term
.top
, 1);
2314 tmoveto(term
.c
.x
, term
.c
.y
+1);
2317 case 'E': /* NEL -- Next line */
2318 tnewline(1); /* always go to first col */
2320 case 'H': /* HTS -- Horizontal tab stop */
2321 term
.tabs
[term
.c
.x
] = 1;
2323 case 'M': /* RI -- Reverse index */
2324 if (term
.c
.y
== term
.top
) {
2325 tscrolldown(term
.top
, 1);
2327 tmoveto(term
.c
.x
, term
.c
.y
-1);
2330 case 'Z': /* DECID -- Identify Terminal */
2331 ttywrite(vtiden
, strlen(vtiden
), 0);
2333 case 'c': /* RIS -- Reset to initial state */
2337 xsetmode(0, MODE_HIDE
);
2339 case '=': /* DECPAM -- Application keypad */
2340 xsetmode(1, MODE_APPKEYPAD
);
2342 case '>': /* DECPNM -- Normal keypad */
2343 xsetmode(0, MODE_APPKEYPAD
);
2345 case '7': /* DECSC -- Save Cursor */
2346 tcursor(CURSOR_SAVE
);
2348 case '8': /* DECRC -- Restore Cursor */
2349 tcursor(CURSOR_LOAD
);
2351 case '\\': /* ST -- String Terminator */
2352 if (term
.esc
& ESC_STR_END
)
2356 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2357 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2371 control
= ISCONTROL(u
);
2372 if (u
< 127 || !IS_SET(MODE_UTF8
)) {
2376 len
= utf8encode(u
, c
);
2377 if (!control
&& (width
= wcwidth(u
)) == -1)
2381 if (IS_SET(MODE_PRINT
))
2385 * STR sequence must be checked before anything else
2386 * because it uses all following characters until it
2387 * receives a ESC, a SUB, a ST or any other C1 control
2390 if (term
.esc
& ESC_STR
) {
2391 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2393 term
.esc
&= ~(ESC_START
|ESC_STR
);
2394 term
.esc
|= ESC_STR_END
;
2395 goto check_control_code
;
2398 if (strescseq
.len
+len
>= strescseq
.siz
) {
2400 * Here is a bug in terminals. If the user never sends
2401 * some code to stop the str or esc command, then st
2402 * will stop responding. But this is better than
2403 * silently failing with unknown characters. At least
2404 * then users will report back.
2406 * In the case users ever get fixed, here is the code:
2412 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2415 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2418 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2419 strescseq
.len
+= len
;
2425 * Actions of control codes must be performed as soon they arrive
2426 * because they can be embedded inside a control sequence, and
2427 * they must not cause conflicts with sequences.
2430 /* in UTF-8 mode ignore handling C1 control characters */
2431 if (IS_SET(MODE_UTF8
) && ISCONTROLC1(u
))
2435 * control codes are not shown ever
2440 } else if (term
.esc
& ESC_START
) {
2441 if (term
.esc
& ESC_CSI
) {
2442 csiescseq
.buf
[csiescseq
.len
++] = u
;
2443 if (BETWEEN(u
, 0x40, 0x7E)
2444 || csiescseq
.len
>= \
2445 sizeof(csiescseq
.buf
)-1) {
2451 } else if (term
.esc
& ESC_UTF8
) {
2453 } else if (term
.esc
& ESC_ALTCHARSET
) {
2455 } else if (term
.esc
& ESC_TEST
) {
2460 /* sequence already finished */
2464 * All characters which form part of a sequence are not
2469 if (selected(term
.c
.x
, term
.c
.y
))
2472 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2473 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2474 gp
->mode
|= ATTR_WRAP
;
2476 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2479 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
) {
2480 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2481 gp
->mode
&= ~ATTR_WIDE
;
2484 if (term
.c
.x
+width
> term
.col
) {
2485 if (IS_SET(MODE_WRAP
))
2488 tmoveto(term
.col
- width
, term
.c
.y
);
2489 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2492 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2496 gp
->mode
|= ATTR_WIDE
;
2497 if (term
.c
.x
+1 < term
.col
) {
2498 if (gp
[1].mode
== ATTR_WIDE
&& term
.c
.x
+2 < term
.col
) {
2500 gp
[2].mode
&= ~ATTR_WDUMMY
;
2503 gp
[1].mode
= ATTR_WDUMMY
;
2506 if (term
.c
.x
+width
< term
.col
) {
2507 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2509 term
.c
.state
|= CURSOR_WRAPNEXT
;
2514 twrite(const char *buf
, int buflen
, int show_ctrl
)
2520 for (n
= 0; n
< buflen
; n
+= charsize
) {
2521 if (IS_SET(MODE_UTF8
)) {
2522 /* process a complete utf8 char */
2523 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2530 if (show_ctrl
&& ISCONTROL(u
)) {
2535 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2546 tresize(int col
, int row
)
2549 int minrow
= MIN(row
, term
.row
);
2550 int mincol
= MIN(col
, term
.col
);
2554 if (col
< 1 || row
< 1) {
2556 "tresize: error resizing to %dx%d\n", col
, row
);
2561 * slide screen to keep cursor where we expect it -
2562 * tscrollup would work here, but we can optimize to
2563 * memmove because we're freeing the earlier lines
2565 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2569 /* ensure that both src and dst are not NULL */
2571 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2572 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2574 for (i
+= row
; i
< term
.row
; i
++) {
2579 /* resize to new height */
2580 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2581 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2582 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2583 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2585 /* resize each row to new width, zero-pad if needed */
2586 for (i
= 0; i
< minrow
; i
++) {
2587 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2588 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2591 /* allocate any new rows */
2592 for (/* i = minrow */; i
< row
; i
++) {
2593 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2594 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2596 if (col
> term
.col
) {
2597 bp
= term
.tabs
+ term
.col
;
2599 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2600 while (--bp
> term
.tabs
&& !*bp
)
2602 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2605 /* update terminal size */
2608 /* reset scrolling region */
2609 tsetscroll(0, row
-1);
2610 /* make use of the LIMIT in tmoveto */
2611 tmoveto(term
.c
.x
, term
.c
.y
);
2612 /* Clearing both screens (it makes dirty all lines) */
2614 for (i
= 0; i
< 2; i
++) {
2615 if (mincol
< col
&& 0 < minrow
) {
2616 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2618 if (0 < col
&& minrow
< row
) {
2619 tclearregion(0, minrow
, col
- 1, row
- 1);
2622 tcursor(CURSOR_LOAD
);
2634 drawregion(int x1
, int y1
, int x2
, int y2
)
2638 for (y
= y1
; y
< y2
; y
++) {
2643 xdrawline(term
.line
[y
], x1
, y
, x2
);
2650 int cx
= term
.c
.x
, ocx
= term
.ocx
, ocy
= term
.ocy
;
2655 /* adjust cursor position */
2656 LIMIT(term
.ocx
, 0, term
.col
-1);
2657 LIMIT(term
.ocy
, 0, term
.row
-1);
2658 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2660 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2663 drawregion(0, 0, term
.col
, term
.row
);
2664 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2665 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2667 term
.ocy
= term
.c
.y
;
2669 if (ocx
!= term
.ocx
|| ocy
!= term
.ocy
)
2670 xximspot(term
.ocx
, term
.ocy
);