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 */
89 int x
; /* terminal column */
90 int y
; /* terminal row */
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
;
1142 csiescseq
.buf
[csiescseq
.len
] = '\0';
1143 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1145 v
= strtol(p
, &np
, 10);
1148 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1150 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1152 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1156 csiescseq
.mode
[0] = *p
++;
1157 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1160 /* for absolute user moves, when decom is set */
1162 tmoveato(int x
, int y
)
1164 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1168 tmoveto(int x
, int y
)
1172 if (term
.c
.state
& CURSOR_ORIGIN
) {
1177 maxy
= term
.row
- 1;
1179 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1180 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1181 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1185 tsetchar(Rune u
, const Glyph
*attr
, int x
, int y
)
1187 static const char *vt100_0
[62] = { /* 0x41 - 0x7e */
1188 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1189 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1190 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1191 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1192 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1193 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1194 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1195 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1199 * The table is proudly stolen from rxvt.
1201 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1202 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1203 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1205 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1206 if (x
+1 < term
.col
) {
1207 term
.line
[y
][x
+1].u
= ' ';
1208 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1210 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1211 term
.line
[y
][x
-1].u
= ' ';
1212 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1216 term
.line
[y
][x
] = *attr
;
1217 term
.line
[y
][x
].u
= u
;
1221 tclearregion(int x1
, int y1
, int x2
, int y2
)
1227 temp
= x1
, x1
= x2
, x2
= temp
;
1229 temp
= y1
, y1
= y2
, y2
= temp
;
1231 LIMIT(x1
, 0, term
.col
-1);
1232 LIMIT(x2
, 0, term
.col
-1);
1233 LIMIT(y1
, 0, term
.row
-1);
1234 LIMIT(y2
, 0, term
.row
-1);
1236 for (y
= y1
; y
<= y2
; y
++) {
1238 for (x
= x1
; x
<= x2
; x
++) {
1239 gp
= &term
.line
[y
][x
];
1242 gp
->fg
= term
.c
.attr
.fg
;
1243 gp
->bg
= term
.c
.attr
.bg
;
1256 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1260 size
= term
.col
- src
;
1261 line
= term
.line
[term
.c
.y
];
1263 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1264 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1273 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1277 size
= term
.col
- dst
;
1278 line
= term
.line
[term
.c
.y
];
1280 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1281 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1285 tinsertblankline(int n
)
1287 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1288 tscrolldown(term
.c
.y
, n
);
1294 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1295 tscrollup(term
.c
.y
, n
);
1299 tdefcolor(const int *attr
, int *npar
, int l
)
1304 switch (attr
[*npar
+ 1]) {
1305 case 2: /* direct color in RGB space */
1306 if (*npar
+ 4 >= l
) {
1308 "erresc(38): Incorrect number of parameters (%d)\n",
1312 r
= attr
[*npar
+ 2];
1313 g
= attr
[*npar
+ 3];
1314 b
= attr
[*npar
+ 4];
1316 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1317 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1320 idx
= TRUECOLOR(r
, g
, b
);
1322 case 5: /* indexed color */
1323 if (*npar
+ 2 >= l
) {
1325 "erresc(38): Incorrect number of parameters (%d)\n",
1330 if (!BETWEEN(attr
[*npar
], 0, 255))
1331 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1335 case 0: /* implemented defined (only foreground) */
1336 case 1: /* transparent */
1337 case 3: /* direct color in CMY space */
1338 case 4: /* direct color in CMYK space */
1341 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1349 tsetattr(const int *attr
, int l
)
1354 for (i
= 0; i
< l
; i
++) {
1357 term
.c
.attr
.mode
&= ~(
1366 term
.c
.attr
.fg
= defaultfg
;
1367 term
.c
.attr
.bg
= defaultbg
;
1370 term
.c
.attr
.mode
|= ATTR_BOLD
;
1373 term
.c
.attr
.mode
|= ATTR_FAINT
;
1376 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1379 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1381 case 5: /* slow blink */
1383 case 6: /* rapid blink */
1384 term
.c
.attr
.mode
|= ATTR_BLINK
;
1387 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1390 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1393 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1396 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1399 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1402 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1405 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1408 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1411 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1414 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1417 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1418 term
.c
.attr
.fg
= idx
;
1421 term
.c
.attr
.fg
= defaultfg
;
1424 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1425 term
.c
.attr
.bg
= idx
;
1428 term
.c
.attr
.bg
= defaultbg
;
1431 if (BETWEEN(attr
[i
], 30, 37)) {
1432 term
.c
.attr
.fg
= attr
[i
] - 30;
1433 } else if (BETWEEN(attr
[i
], 40, 47)) {
1434 term
.c
.attr
.bg
= attr
[i
] - 40;
1435 } else if (BETWEEN(attr
[i
], 90, 97)) {
1436 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1437 } else if (BETWEEN(attr
[i
], 100, 107)) {
1438 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1441 "erresc(default): gfx attr %d unknown\n",
1451 tsetscroll(int t
, int b
)
1455 LIMIT(t
, 0, term
.row
-1);
1456 LIMIT(b
, 0, term
.row
-1);
1467 tsetmode(int priv
, int set
, const int *args
, int narg
)
1469 int alt
; const int *lim
;
1471 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1474 case 1: /* DECCKM -- Cursor key */
1475 xsetmode(set
, MODE_APPCURSOR
);
1477 case 5: /* DECSCNM -- Reverse video */
1478 xsetmode(set
, MODE_REVERSE
);
1480 case 6: /* DECOM -- Origin */
1481 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1484 case 7: /* DECAWM -- Auto wrap */
1485 MODBIT(term
.mode
, set
, MODE_WRAP
);
1487 case 0: /* Error (IGNORED) */
1488 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1489 case 3: /* DECCOLM -- Column (IGNORED) */
1490 case 4: /* DECSCLM -- Scroll (IGNORED) */
1491 case 8: /* DECARM -- Auto repeat (IGNORED) */
1492 case 18: /* DECPFF -- Printer feed (IGNORED) */
1493 case 19: /* DECPEX -- Printer extent (IGNORED) */
1494 case 42: /* DECNRCM -- National characters (IGNORED) */
1495 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1497 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1498 xsetmode(!set
, MODE_HIDE
);
1500 case 9: /* X10 mouse compatibility mode */
1501 xsetpointermotion(0);
1502 xsetmode(0, MODE_MOUSE
);
1503 xsetmode(set
, MODE_MOUSEX10
);
1505 case 1000: /* 1000: report button press */
1506 xsetpointermotion(0);
1507 xsetmode(0, MODE_MOUSE
);
1508 xsetmode(set
, MODE_MOUSEBTN
);
1510 case 1002: /* 1002: report motion on button press */
1511 xsetpointermotion(0);
1512 xsetmode(0, MODE_MOUSE
);
1513 xsetmode(set
, MODE_MOUSEMOTION
);
1515 case 1003: /* 1003: enable all mouse motions */
1516 xsetpointermotion(set
);
1517 xsetmode(0, MODE_MOUSE
);
1518 xsetmode(set
, MODE_MOUSEMANY
);
1520 case 1004: /* 1004: send focus events to tty */
1521 xsetmode(set
, MODE_FOCUS
);
1523 case 1006: /* 1006: extended reporting mode */
1524 xsetmode(set
, MODE_MOUSESGR
);
1527 xsetmode(set
, MODE_8BIT
);
1529 case 1049: /* swap screen & set/restore cursor as xterm */
1530 if (!allowaltscreen
)
1532 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1534 case 47: /* swap screen */
1536 if (!allowaltscreen
)
1538 alt
= IS_SET(MODE_ALTSCREEN
);
1540 tclearregion(0, 0, term
.col
-1,
1543 if (set
^ alt
) /* set is always 1 or 0 */
1549 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1551 case 2004: /* 2004: bracketed paste mode */
1552 xsetmode(set
, MODE_BRCKTPASTE
);
1554 /* Not implemented mouse modes. See comments there. */
1555 case 1001: /* mouse highlight mode; can hang the
1556 terminal by design when implemented. */
1557 case 1005: /* UTF-8 mouse mode; will confuse
1558 applications not supporting UTF-8
1560 case 1015: /* urxvt mangled mouse mode; incompatible
1561 and can be mistaken for other control
1566 "erresc: unknown private set/reset mode %d\n",
1572 case 0: /* Error (IGNORED) */
1575 xsetmode(set
, MODE_KBDLOCK
);
1577 case 4: /* IRM -- Insertion-replacement */
1578 MODBIT(term
.mode
, set
, MODE_INSERT
);
1580 case 12: /* SRM -- Send/Receive */
1581 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1583 case 20: /* LNM -- Linefeed/new line */
1584 MODBIT(term
.mode
, set
, MODE_CRLF
);
1588 "erresc: unknown set/reset mode %d\n",
1602 switch (csiescseq
.mode
[0]) {
1605 fprintf(stderr
, "erresc: unknown csi ");
1609 case '@': /* ICH -- Insert <n> blank char */
1610 DEFAULT(csiescseq
.arg
[0], 1);
1611 tinsertblank(csiescseq
.arg
[0]);
1613 case 'A': /* CUU -- Cursor <n> Up */
1614 DEFAULT(csiescseq
.arg
[0], 1);
1615 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1617 case 'B': /* CUD -- Cursor <n> Down */
1618 case 'e': /* VPR --Cursor <n> Down */
1619 DEFAULT(csiescseq
.arg
[0], 1);
1620 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1622 case 'i': /* MC -- Media Copy */
1623 switch (csiescseq
.arg
[0]) {
1628 tdumpline(term
.c
.y
);
1634 term
.mode
&= ~MODE_PRINT
;
1637 term
.mode
|= MODE_PRINT
;
1641 case 'c': /* DA -- Device Attributes */
1642 if (csiescseq
.arg
[0] == 0)
1643 ttywrite(vtiden
, strlen(vtiden
), 0);
1645 case 'b': /* REP -- if last char is printable print it <n> more times */
1646 DEFAULT(csiescseq
.arg
[0], 1);
1648 while (csiescseq
.arg
[0]-- > 0)
1651 case 'C': /* CUF -- Cursor <n> Forward */
1652 case 'a': /* HPR -- Cursor <n> Forward */
1653 DEFAULT(csiescseq
.arg
[0], 1);
1654 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1656 case 'D': /* CUB -- Cursor <n> Backward */
1657 DEFAULT(csiescseq
.arg
[0], 1);
1658 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1660 case 'E': /* CNL -- Cursor <n> Down and first col */
1661 DEFAULT(csiescseq
.arg
[0], 1);
1662 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1664 case 'F': /* CPL -- Cursor <n> Up and first col */
1665 DEFAULT(csiescseq
.arg
[0], 1);
1666 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1668 case 'g': /* TBC -- Tabulation clear */
1669 switch (csiescseq
.arg
[0]) {
1670 case 0: /* clear current tab stop */
1671 term
.tabs
[term
.c
.x
] = 0;
1673 case 3: /* clear all the tabs */
1674 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1680 case 'G': /* CHA -- Move to <col> */
1682 DEFAULT(csiescseq
.arg
[0], 1);
1683 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1685 case 'H': /* CUP -- Move to <row> <col> */
1687 DEFAULT(csiescseq
.arg
[0], 1);
1688 DEFAULT(csiescseq
.arg
[1], 1);
1689 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1691 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1692 DEFAULT(csiescseq
.arg
[0], 1);
1693 tputtab(csiescseq
.arg
[0]);
1695 case 'J': /* ED -- Clear screen */
1696 switch (csiescseq
.arg
[0]) {
1698 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1699 if (term
.c
.y
< term
.row
-1) {
1700 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1706 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1707 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1710 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1716 case 'K': /* EL -- Clear line */
1717 switch (csiescseq
.arg
[0]) {
1719 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1723 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1726 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1730 case 'S': /* SU -- Scroll <n> line up */
1731 if (csiescseq
.priv
) break;
1732 DEFAULT(csiescseq
.arg
[0], 1);
1733 tscrollup(term
.top
, csiescseq
.arg
[0]);
1735 case 'T': /* SD -- Scroll <n> line down */
1736 DEFAULT(csiescseq
.arg
[0], 1);
1737 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1739 case 'L': /* IL -- Insert <n> blank lines */
1740 DEFAULT(csiescseq
.arg
[0], 1);
1741 tinsertblankline(csiescseq
.arg
[0]);
1743 case 'l': /* RM -- Reset Mode */
1744 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1746 case 'M': /* DL -- Delete <n> lines */
1747 DEFAULT(csiescseq
.arg
[0], 1);
1748 tdeleteline(csiescseq
.arg
[0]);
1750 case 'X': /* ECH -- Erase <n> char */
1751 DEFAULT(csiescseq
.arg
[0], 1);
1752 tclearregion(term
.c
.x
, term
.c
.y
,
1753 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1755 case 'P': /* DCH -- Delete <n> char */
1756 DEFAULT(csiescseq
.arg
[0], 1);
1757 tdeletechar(csiescseq
.arg
[0]);
1759 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1760 DEFAULT(csiescseq
.arg
[0], 1);
1761 tputtab(-csiescseq
.arg
[0]);
1763 case 'd': /* VPA -- Move to <row> */
1764 DEFAULT(csiescseq
.arg
[0], 1);
1765 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1767 case 'h': /* SM -- Set terminal mode */
1768 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1770 case 'm': /* SGR -- Terminal attribute (color) */
1771 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1773 case 'n': /* DSR -- Device Status Report */
1774 switch (csiescseq
.arg
[0]) {
1775 case 5: /* Status Report "OK" `0n` */
1776 ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
1778 case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
1779 len
= snprintf(buf
, sizeof(buf
), "\033[%i;%iR",
1780 term
.c
.y
+1, term
.c
.x
+1);
1781 ttywrite(buf
, len
, 0);
1787 case 'r': /* DECSTBM -- Set Scrolling Region */
1788 if (csiescseq
.priv
) {
1791 DEFAULT(csiescseq
.arg
[0], 1);
1792 DEFAULT(csiescseq
.arg
[1], term
.row
);
1793 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1797 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1798 tcursor(CURSOR_SAVE
);
1800 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1801 tcursor(CURSOR_LOAD
);
1804 switch (csiescseq
.mode
[1]) {
1805 case 'q': /* DECSCUSR -- Set Cursor Style */
1806 if (xsetcursor(csiescseq
.arg
[0]))
1822 fprintf(stderr
, "ESC[");
1823 for (i
= 0; i
< csiescseq
.len
; i
++) {
1824 c
= csiescseq
.buf
[i
] & 0xff;
1827 } else if (c
== '\n') {
1828 fprintf(stderr
, "(\\n)");
1829 } else if (c
== '\r') {
1830 fprintf(stderr
, "(\\r)");
1831 } else if (c
== 0x1b) {
1832 fprintf(stderr
, "(\\e)");
1834 fprintf(stderr
, "(%02x)", c
);
1843 memset(&csiescseq
, 0, sizeof(csiescseq
));
1847 osc_color_response(int num
, int index
, int is_osc4
)
1851 unsigned char r
, g
, b
;
1853 if (xgetcolor(is_osc4
? num
: index
, &r
, &g
, &b
)) {
1854 fprintf(stderr
, "erresc: failed to fetch %s color %d\n",
1855 is_osc4
? "osc4" : "osc",
1856 is_osc4
? num
: index
);
1860 n
= snprintf(buf
, sizeof buf
, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1861 is_osc4
? "4;" : "", num
, r
, r
, g
, g
, b
, b
);
1862 if (n
< 0 || n
>= sizeof(buf
)) {
1863 fprintf(stderr
, "error: %s while printing %s response\n",
1864 n
< 0 ? "snprintf failed" : "truncation occurred",
1865 is_osc4
? "osc4" : "osc");
1867 ttywrite(buf
, n
, 1);
1874 char *p
= NULL
, *dec
;
1876 const struct { int idx
; char *str
; } osc_table
[] = {
1877 { defaultfg
, "foreground" },
1878 { defaultbg
, "background" },
1879 { defaultcs
, "cursor" }
1882 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1884 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1886 switch (strescseq
.type
) {
1887 case ']': /* OSC -- Operating System Command */
1891 xsettitle(strescseq
.args
[1]);
1892 xseticontitle(strescseq
.args
[1]);
1897 xseticontitle(strescseq
.args
[1]);
1901 xsettitle(strescseq
.args
[1]);
1904 if (narg
> 2 && allowwindowops
) {
1905 dec
= base64dec(strescseq
.args
[2]);
1910 fprintf(stderr
, "erresc: invalid base64\n");
1919 p
= strescseq
.args
[1];
1920 if ((j
= par
- 10) < 0 || j
>= LEN(osc_table
))
1921 break; /* shouldn't be possible */
1923 if (!strcmp(p
, "?")) {
1924 osc_color_response(par
, osc_table
[j
].idx
, 0);
1925 } else if (xsetcolorname(osc_table
[j
].idx
, p
)) {
1926 fprintf(stderr
, "erresc: invalid %s color: %s\n",
1927 osc_table
[j
].str
, p
);
1932 case 4: /* color set */
1935 p
= strescseq
.args
[2];
1937 case 104: /* color reset */
1938 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1940 if (p
&& !strcmp(p
, "?")) {
1941 osc_color_response(j
, 0, 1);
1942 } else if (xsetcolorname(j
, p
)) {
1943 if (par
== 104 && narg
<= 1) {
1945 return; /* color reset without parameter */
1947 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n",
1948 j
, p
? p
: "(null)");
1951 * TODO if defaultbg color is changed, borders
1959 case 'k': /* old title set compatibility */
1960 xsettitle(strescseq
.args
[0]);
1962 case 'P': /* DCS -- Device Control String */
1963 case '_': /* APC -- Application Program Command */
1964 case '^': /* PM -- Privacy Message */
1968 fprintf(stderr
, "erresc: unknown str ");
1976 char *p
= strescseq
.buf
;
1979 strescseq
.buf
[strescseq
.len
] = '\0';
1984 while (strescseq
.narg
< STR_ARG_SIZ
) {
1985 strescseq
.args
[strescseq
.narg
++] = p
;
1986 while ((c
= *p
) != ';' && c
!= '\0')
2000 fprintf(stderr
, "ESC%c", strescseq
.type
);
2001 for (i
= 0; i
< strescseq
.len
; i
++) {
2002 c
= strescseq
.buf
[i
] & 0xff;
2006 } else if (isprint(c
)) {
2008 } else if (c
== '\n') {
2009 fprintf(stderr
, "(\\n)");
2010 } else if (c
== '\r') {
2011 fprintf(stderr
, "(\\r)");
2012 } else if (c
== 0x1b) {
2013 fprintf(stderr
, "(\\e)");
2015 fprintf(stderr
, "(%02x)", c
);
2018 fprintf(stderr
, "ESC\\\n");
2024 strescseq
= (STREscape
){
2025 .buf
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
),
2031 sendbreak(const Arg
*arg
)
2033 if (tcsendbreak(cmdfd
, 0))
2034 perror("Error sending break");
2038 tprinter(char *s
, size_t len
)
2040 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
2041 perror("Error writing to output file");
2048 toggleprinter(const Arg
*arg
)
2050 term
.mode
^= MODE_PRINT
;
2054 printscreen(const Arg
*arg
)
2060 printsel(const Arg
*arg
)
2070 if ((ptr
= getsel())) {
2071 tprinter(ptr
, strlen(ptr
));
2080 const Glyph
*bp
, *end
;
2082 bp
= &term
.line
[n
][0];
2083 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2084 if (bp
!= end
|| bp
->u
!= ' ') {
2085 for ( ; bp
<= end
; ++bp
)
2086 tprinter(buf
, utf8encode(bp
->u
, buf
));
2096 for (i
= 0; i
< term
.row
; ++i
)
2106 while (x
< term
.col
&& n
--)
2107 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2110 while (x
> 0 && n
++)
2111 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2114 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2118 tdefutf8(char ascii
)
2121 term
.mode
|= MODE_UTF8
;
2122 else if (ascii
== '@')
2123 term
.mode
&= ~MODE_UTF8
;
2127 tdeftran(char ascii
)
2129 static char cs
[] = "0B";
2130 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2133 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2134 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2136 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2145 if (c
== '8') { /* DEC screen alignment test. */
2146 for (x
= 0; x
< term
.col
; ++x
) {
2147 for (y
= 0; y
< term
.row
; ++y
)
2148 tsetchar('E', &term
.c
.attr
, x
, y
);
2154 tstrsequence(uchar c
)
2157 case 0x90: /* DCS -- Device Control String */
2160 case 0x9f: /* APC -- Application Program Command */
2163 case 0x9e: /* PM -- Privacy Message */
2166 case 0x9d: /* OSC -- Operating System Command */
2172 term
.esc
|= ESC_STR
;
2176 tcontrolcode(uchar ascii
)
2185 for (i
= 1; term
.c
.x
&& term
.line
[term
.c
.y
][term
.c
.x
- i
].u
== 0; ++i
)
2187 tmoveto(term
.c
.x
- i
, term
.c
.y
);
2190 tmoveto(0, term
.c
.y
);
2195 /* go to first col if the mode is set */
2196 tnewline(IS_SET(MODE_CRLF
));
2198 case '\a': /* BEL */
2199 if (term
.esc
& ESC_STR_END
) {
2200 /* backwards compatibility to xterm */
2206 case '\033': /* ESC */
2208 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2209 term
.esc
|= ESC_START
;
2211 case '\016': /* SO (LS1 -- Locking shift 1) */
2212 case '\017': /* SI (LS0 -- Locking shift 0) */
2213 term
.charset
= 1 - (ascii
- '\016');
2215 case '\032': /* SUB */
2216 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2218 case '\030': /* CAN */
2221 case '\005': /* ENQ (IGNORED) */
2222 case '\000': /* NUL (IGNORED) */
2223 case '\021': /* XON (IGNORED) */
2224 case '\023': /* XOFF (IGNORED) */
2225 case 0177: /* DEL (IGNORED) */
2227 case 0x80: /* TODO: PAD */
2228 case 0x81: /* TODO: HOP */
2229 case 0x82: /* TODO: BPH */
2230 case 0x83: /* TODO: NBH */
2231 case 0x84: /* TODO: IND */
2233 case 0x85: /* NEL -- Next line */
2234 tnewline(1); /* always go to first col */
2236 case 0x86: /* TODO: SSA */
2237 case 0x87: /* TODO: ESA */
2239 case 0x88: /* HTS -- Horizontal tab stop */
2240 term
.tabs
[term
.c
.x
] = 1;
2242 case 0x89: /* TODO: HTJ */
2243 case 0x8a: /* TODO: VTS */
2244 case 0x8b: /* TODO: PLD */
2245 case 0x8c: /* TODO: PLU */
2246 case 0x8d: /* TODO: RI */
2247 case 0x8e: /* TODO: SS2 */
2248 case 0x8f: /* TODO: SS3 */
2249 case 0x91: /* TODO: PU1 */
2250 case 0x92: /* TODO: PU2 */
2251 case 0x93: /* TODO: STS */
2252 case 0x94: /* TODO: CCH */
2253 case 0x95: /* TODO: MW */
2254 case 0x96: /* TODO: SPA */
2255 case 0x97: /* TODO: EPA */
2256 case 0x98: /* TODO: SOS */
2257 case 0x99: /* TODO: SGCI */
2259 case 0x9a: /* DECID -- Identify Terminal */
2260 ttywrite(vtiden
, strlen(vtiden
), 0);
2262 case 0x9b: /* TODO: CSI */
2263 case 0x9c: /* TODO: ST */
2265 case 0x90: /* DCS -- Device Control String */
2266 case 0x9d: /* OSC -- Operating System Command */
2267 case 0x9e: /* PM -- Privacy Message */
2268 case 0x9f: /* APC -- Application Program Command */
2269 tstrsequence(ascii
);
2272 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2273 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2277 * returns 1 when the sequence is finished and it hasn't to read
2278 * more characters for this sequence, otherwise 0
2281 eschandle(uchar ascii
)
2285 term
.esc
|= ESC_CSI
;
2288 term
.esc
|= ESC_TEST
;
2291 term
.esc
|= ESC_UTF8
;
2293 case 'P': /* DCS -- Device Control String */
2294 case '_': /* APC -- Application Program Command */
2295 case '^': /* PM -- Privacy Message */
2296 case ']': /* OSC -- Operating System Command */
2297 case 'k': /* old title set compatibility */
2298 tstrsequence(ascii
);
2300 case 'n': /* LS2 -- Locking shift 2 */
2301 case 'o': /* LS3 -- Locking shift 3 */
2302 term
.charset
= 2 + (ascii
- 'n');
2304 case '(': /* GZD4 -- set primary charset G0 */
2305 case ')': /* G1D4 -- set secondary charset G1 */
2306 case '*': /* G2D4 -- set tertiary charset G2 */
2307 case '+': /* G3D4 -- set quaternary charset G3 */
2308 term
.icharset
= ascii
- '(';
2309 term
.esc
|= ESC_ALTCHARSET
;
2311 case 'D': /* IND -- Linefeed */
2312 if (term
.c
.y
== term
.bot
) {
2313 tscrollup(term
.top
, 1);
2315 tmoveto(term
.c
.x
, term
.c
.y
+1);
2318 case 'E': /* NEL -- Next line */
2319 tnewline(1); /* always go to first col */
2321 case 'H': /* HTS -- Horizontal tab stop */
2322 term
.tabs
[term
.c
.x
] = 1;
2324 case 'M': /* RI -- Reverse index */
2325 if (term
.c
.y
== term
.top
) {
2326 tscrolldown(term
.top
, 1);
2328 tmoveto(term
.c
.x
, term
.c
.y
-1);
2331 case 'Z': /* DECID -- Identify Terminal */
2332 ttywrite(vtiden
, strlen(vtiden
), 0);
2334 case 'c': /* RIS -- Reset to initial state */
2338 xsetmode(0, MODE_HIDE
);
2340 case '=': /* DECPAM -- Application keypad */
2341 xsetmode(1, MODE_APPKEYPAD
);
2343 case '>': /* DECPNM -- Normal keypad */
2344 xsetmode(0, MODE_APPKEYPAD
);
2346 case '7': /* DECSC -- Save Cursor */
2347 tcursor(CURSOR_SAVE
);
2349 case '8': /* DECRC -- Restore Cursor */
2350 tcursor(CURSOR_LOAD
);
2352 case '\\': /* ST -- String Terminator */
2353 if (term
.esc
& ESC_STR_END
)
2357 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2358 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2372 control
= ISCONTROL(u
);
2373 if (u
< 127 || !IS_SET(MODE_UTF8
)) {
2377 len
= utf8encode(u
, c
);
2378 if (!control
&& (width
= wcwidth(u
)) == -1)
2382 if (IS_SET(MODE_PRINT
))
2386 * STR sequence must be checked before anything else
2387 * because it uses all following characters until it
2388 * receives a ESC, a SUB, a ST or any other C1 control
2391 if (term
.esc
& ESC_STR
) {
2392 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2394 term
.esc
&= ~(ESC_START
|ESC_STR
);
2395 term
.esc
|= ESC_STR_END
;
2396 goto check_control_code
;
2399 if (strescseq
.len
+len
>= strescseq
.siz
) {
2401 * Here is a bug in terminals. If the user never sends
2402 * some code to stop the str or esc command, then st
2403 * will stop responding. But this is better than
2404 * silently failing with unknown characters. At least
2405 * then users will report back.
2407 * In the case users ever get fixed, here is the code:
2413 if (strescseq
.siz
> (SIZE_MAX
- UTF_SIZ
) / 2)
2416 strescseq
.buf
= xrealloc(strescseq
.buf
, strescseq
.siz
);
2419 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2420 strescseq
.len
+= len
;
2426 * Actions of control codes must be performed as soon they arrive
2427 * because they can be embedded inside a control sequence, and
2428 * they must not cause conflicts with sequences.
2431 /* in UTF-8 mode ignore handling C1 control characters */
2432 if (IS_SET(MODE_UTF8
) && ISCONTROLC1(u
))
2436 * control codes are not shown ever
2441 } else if (term
.esc
& ESC_START
) {
2442 if (term
.esc
& ESC_CSI
) {
2443 csiescseq
.buf
[csiescseq
.len
++] = u
;
2444 if (BETWEEN(u
, 0x40, 0x7E)
2445 || csiescseq
.len
>= \
2446 sizeof(csiescseq
.buf
)-1) {
2452 } else if (term
.esc
& ESC_UTF8
) {
2454 } else if (term
.esc
& ESC_ALTCHARSET
) {
2456 } else if (term
.esc
& ESC_TEST
) {
2461 /* sequence already finished */
2465 * All characters which form part of a sequence are not
2470 if (selected(term
.c
.x
, term
.c
.y
))
2473 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2474 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2475 gp
->mode
|= ATTR_WRAP
;
2477 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2480 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
) {
2481 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2482 gp
->mode
&= ~ATTR_WIDE
;
2485 if (term
.c
.x
+width
> term
.col
) {
2486 if (IS_SET(MODE_WRAP
))
2489 tmoveto(term
.col
- width
, term
.c
.y
);
2490 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2493 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2497 gp
->mode
|= ATTR_WIDE
;
2498 if (term
.c
.x
+1 < term
.col
) {
2499 if (gp
[1].mode
== ATTR_WIDE
&& term
.c
.x
+2 < term
.col
) {
2501 gp
[2].mode
&= ~ATTR_WDUMMY
;
2504 gp
[1].mode
= ATTR_WDUMMY
;
2507 if (term
.c
.x
+width
< term
.col
) {
2508 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2510 term
.c
.state
|= CURSOR_WRAPNEXT
;
2515 twrite(const char *buf
, int buflen
, int show_ctrl
)
2521 for (n
= 0; n
< buflen
; n
+= charsize
) {
2522 if (IS_SET(MODE_UTF8
)) {
2523 /* process a complete utf8 char */
2524 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2531 if (show_ctrl
&& ISCONTROL(u
)) {
2536 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2547 tresize(int col
, int row
)
2550 int minrow
= MIN(row
, term
.row
);
2551 int mincol
= MIN(col
, term
.col
);
2555 if (col
< 1 || row
< 1) {
2557 "tresize: error resizing to %dx%d\n", col
, row
);
2562 * slide screen to keep cursor where we expect it -
2563 * tscrollup would work here, but we can optimize to
2564 * memmove because we're freeing the earlier lines
2566 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2570 /* ensure that both src and dst are not NULL */
2572 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2573 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2575 for (i
+= row
; i
< term
.row
; i
++) {
2580 /* resize to new height */
2581 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2582 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2583 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2584 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2586 /* resize each row to new width, zero-pad if needed */
2587 for (i
= 0; i
< minrow
; i
++) {
2588 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2589 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2592 /* allocate any new rows */
2593 for (/* i = minrow */; i
< row
; i
++) {
2594 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2595 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2597 if (col
> term
.col
) {
2598 bp
= term
.tabs
+ term
.col
;
2600 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2601 while (--bp
> term
.tabs
&& !*bp
)
2603 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2606 /* update terminal size */
2609 /* reset scrolling region */
2610 tsetscroll(0, row
-1);
2611 /* make use of the LIMIT in tmoveto */
2612 tmoveto(term
.c
.x
, term
.c
.y
);
2613 /* Clearing both screens (it makes dirty all lines) */
2615 for (i
= 0; i
< 2; i
++) {
2616 if (mincol
< col
&& 0 < minrow
) {
2617 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2619 if (0 < col
&& minrow
< row
) {
2620 tclearregion(0, minrow
, col
- 1, row
- 1);
2623 tcursor(CURSOR_LOAD
);
2635 drawregion(int x1
, int y1
, int x2
, int y2
)
2639 for (y
= y1
; y
< y2
; y
++) {
2644 xdrawline(term
.line
[y
], x1
, y
, x2
);
2651 int cx
= term
.c
.x
, ocx
= term
.ocx
, ocy
= term
.ocy
;
2656 /* adjust cursor position */
2657 LIMIT(term
.ocx
, 0, term
.col
-1);
2658 LIMIT(term
.ocy
, 0, term
.row
-1);
2659 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2661 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2664 drawregion(0, 0, term
.col
, term
.row
);
2665 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2666 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2668 term
.ocy
= term
.c
.y
;
2670 if (ocx
!= term
.ocx
|| ocy
!= term
.ocy
)
2671 xximspot(term
.ocx
, term
.ocy
);