Xinqi Bao's Git
543c6150a6aeb5229d0995371166f367bd7a362d
1 /* See LICENSE for license details. */
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
24 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
26 #elif defined(__FreeBSD__) || defined(__DragonFly__)
31 #define UTF_INVALID 0xFFFD
33 #define ESC_BUF_SIZ (128*UTF_SIZ)
34 #define ESC_ARG_SIZ 16
35 #define STR_BUF_SIZ ESC_BUF_SIZ
36 #define STR_ARG_SIZ ESC_ARG_SIZ
39 #define IS_SET(flag) ((term.mode & (flag)) != 0)
40 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
41 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
42 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
47 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
52 MODE_ALTSCREEN
= 1 << 2,
60 enum cursor_movement
{
84 ESC_STR
= 4, /* OSC, PM, APC */
86 ESC_STR_END
= 16, /* a final string was encountered */
87 ESC_TEST
= 32, /* Enter in test mode */
93 Glyph attr
; /* current char attributes */
104 * Selection variables:
105 * nb – normalized coordinates of the beginning of the selection
106 * ne – normalized coordinates of the end of the selection
107 * ob – original coordinates of the beginning of the selection
108 * oe – original coordinates of the end of the selection
117 /* Internal representation of the screen */
119 int row
; /* nb row */
120 int col
; /* nb col */
121 Line
*line
; /* screen */
122 Line
*alt
; /* alternate screen */
123 int *dirty
; /* dirtyness of lines */
124 TCursor c
; /* cursor */
125 int ocx
; /* old cursor col */
126 int ocy
; /* old cursor row */
127 int top
; /* top scroll limit */
128 int bot
; /* bottom scroll limit */
129 int mode
; /* terminal mode flags */
130 int esc
; /* escape state flags */
131 char trantbl
[4]; /* charset table translation */
132 int charset
; /* current charset */
133 int icharset
; /* selected charset for sequence */
137 /* CSI Escape sequence structs */
138 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
140 char buf
[ESC_BUF_SIZ
]; /* raw string */
141 int len
; /* raw string length */
143 int arg
[ESC_ARG_SIZ
];
144 int narg
; /* nb of args */
148 /* STR Escape sequence structs */
149 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
151 char type
; /* ESC type ... */
152 char buf
[STR_BUF_SIZ
]; /* raw string */
153 int len
; /* raw string length */
154 char *args
[STR_ARG_SIZ
];
155 int narg
; /* nb of args */
158 static void execsh(char *, char **);
159 static void stty(char **);
160 static void sigchld(int);
161 static void ttywriteraw(const char *, size_t);
163 static void csidump(void);
164 static void csihandle(void);
165 static void csiparse(void);
166 static void csireset(void);
167 static int eschandle(uchar
);
168 static void strdump(void);
169 static void strhandle(void);
170 static void strparse(void);
171 static void strreset(void);
173 static void tprinter(char *, size_t);
174 static void tdumpsel(void);
175 static void tdumpline(int);
176 static void tdump(void);
177 static void tclearregion(int, int, int, int);
178 static void tcursor(int);
179 static void tdeletechar(int);
180 static void tdeleteline(int);
181 static void tinsertblank(int);
182 static void tinsertblankline(int);
183 static int tlinelen(int);
184 static void tmoveto(int, int);
185 static void tmoveato(int, int);
186 static void tnewline(int);
187 static void tputtab(int);
188 static void tputc(Rune
);
189 static void treset(void);
190 static void tscrollup(int, int);
191 static void tscrolldown(int, int);
192 static void tsetattr(int *, int);
193 static void tsetchar(Rune
, Glyph
*, int, int);
194 static void tsetdirt(int, int);
195 static void tsetscroll(int, int);
196 static void tswapscreen(void);
197 static void tsetmode(int, int, int *, int);
198 static int twrite(const char *, int, int);
199 static void tfulldirt(void);
200 static void tcontrolcode(uchar
);
201 static void tdectest(char );
202 static void tdefutf8(char);
203 static int32_t tdefcolor(int *, int *, int);
204 static void tdeftran(char);
205 static void tstrsequence(uchar
);
207 static void drawregion(int, int, int, int);
209 static void selnormalize(void);
210 static void selscroll(int, int);
211 static void selsnap(int *, int *, int);
213 static size_t utf8decode(const char *, Rune
*, size_t);
214 static Rune
utf8decodebyte(char, size_t *);
215 static char utf8encodebyte(Rune
, size_t);
216 static char *utf8strchr(char *, Rune
);
217 static size_t utf8validate(Rune
*, size_t);
219 static char *base64dec(const char *);
220 static char base64dec_getc(const char **);
222 static ssize_t
xwrite(int, const char *, size_t);
226 static Selection sel
;
227 static CSIEscape csiescseq
;
228 static STREscape strescseq
;
233 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
234 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
235 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
236 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
239 xwrite(int fd
, const char *s
, size_t len
)
245 r
= write(fd
, s
, len
);
258 void *p
= malloc(len
);
261 die("Out of memory\n");
267 xrealloc(void *p
, size_t len
)
269 if ((p
= realloc(p
, len
)) == NULL
)
270 die("Out of memory\n");
278 if ((s
= strdup(s
)) == NULL
)
279 die("Out of memory\n");
285 utf8decode(const char *c
, Rune
*u
, size_t clen
)
287 size_t i
, j
, len
, type
;
293 udecoded
= utf8decodebyte(c
[0], &len
);
294 if (!BETWEEN(len
, 1, UTF_SIZ
))
296 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
297 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
304 utf8validate(u
, len
);
310 utf8decodebyte(char c
, size_t *i
)
312 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
313 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
314 return (uchar
)c
& ~utfmask
[*i
];
320 utf8encode(Rune u
, char *c
)
324 len
= utf8validate(&u
, 0);
328 for (i
= len
- 1; i
!= 0; --i
) {
329 c
[i
] = utf8encodebyte(u
, 0);
332 c
[0] = utf8encodebyte(u
, len
);
338 utf8encodebyte(Rune u
, size_t i
)
340 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
344 utf8strchr(char *s
, Rune u
)
350 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
351 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
361 utf8validate(Rune
*u
, size_t i
)
363 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
365 for (i
= 1; *u
> utfmax
[i
]; ++i
)
371 static const char base64_digits
[] = {
372 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
373 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
374 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
375 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
376 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
377 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
378 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
379 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
381 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
382 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
383 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
387 base64dec_getc(const char **src
)
389 while (**src
&& !isprint(**src
)) (*src
)++;
394 base64dec(const char *src
)
396 size_t in_len
= strlen(src
);
400 in_len
+= 4 - (in_len
% 4);
401 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
403 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
404 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
405 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
406 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
408 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
411 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
414 *dst
++ = ((c
& 0x03) << 6) | d
;
433 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
436 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
443 selstart(int col
, int row
, int snap
)
446 sel
.mode
= SEL_EMPTY
;
447 sel
.type
= SEL_REGULAR
;
449 sel
.oe
.x
= sel
.ob
.x
= col
;
450 sel
.oe
.y
= sel
.ob
.y
= row
;
454 sel
.mode
= SEL_READY
;
455 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
459 selextend(int col
, int row
, int type
, int done
)
461 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
465 if (done
&& sel
.mode
== SEL_EMPTY
) {
476 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
482 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
483 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
485 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
493 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
494 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
495 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
497 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
498 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
500 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
501 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
503 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
504 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
506 /* expand selection over line breaks */
507 if (sel
.type
== SEL_RECTANGULAR
)
509 i
= tlinelen(sel
.nb
.y
);
512 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
513 sel
.ne
.x
= term
.col
- 1;
517 selected(int x
, int y
)
519 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
520 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
523 if (sel
.type
== SEL_RECTANGULAR
)
524 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
525 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
527 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
528 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
529 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
533 selsnap(int *x
, int *y
, int direction
)
535 int newx
, newy
, xt
, yt
;
536 int delim
, prevdelim
;
542 * Snap around if the word wraps around at the end or
543 * beginning of a line.
545 prevgp
= &term
.line
[*y
][*x
];
546 prevdelim
= ISDELIM(prevgp
->u
);
548 newx
= *x
+ direction
;
550 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
552 newx
= (newx
+ term
.col
) % term
.col
;
553 if (!BETWEEN(newy
, 0, term
.row
- 1))
559 yt
= newy
, xt
= newx
;
560 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
564 if (newx
>= tlinelen(newy
))
567 gp
= &term
.line
[newy
][newx
];
568 delim
= ISDELIM(gp
->u
);
569 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
570 || (delim
&& gp
->u
!= prevgp
->u
)))
581 * Snap around if the the previous line or the current one
582 * has set ATTR_WRAP at its end. Then the whole next or
583 * previous line will be selected.
585 *x
= (direction
< 0) ? 0 : term
.col
- 1;
587 for (; *y
> 0; *y
+= direction
) {
588 if (!(term
.line
[*y
-1][term
.col
-1].mode
593 } else if (direction
> 0) {
594 for (; *y
< term
.row
-1; *y
+= direction
) {
595 if (!(term
.line
[*y
][term
.col
-1].mode
609 int y
, bufsize
, lastx
, linelen
;
615 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
616 ptr
= str
= xmalloc(bufsize
);
618 /* append every set & selected glyph to the selection */
619 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
620 if ((linelen
= tlinelen(y
)) == 0) {
625 if (sel
.type
== SEL_RECTANGULAR
) {
626 gp
= &term
.line
[y
][sel
.nb
.x
];
629 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
630 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
632 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
633 while (last
>= gp
&& last
->u
== ' ')
636 for ( ; gp
<= last
; ++gp
) {
637 if (gp
->mode
& ATTR_WDUMMY
)
640 ptr
+= utf8encode(gp
->u
, ptr
);
644 * Copy and pasting of line endings is inconsistent
645 * in the inconsistent terminal and GUI world.
646 * The best solution seems like to produce '\n' when
647 * something is copied from st and convert '\n' to
648 * '\r', when something to be pasted is received by
650 * FIXME: Fix the computer world.
652 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
666 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
670 die(const char *errstr
, ...)
674 va_start(ap
, errstr
);
675 vfprintf(stderr
, errstr
, ap
);
681 execsh(char *cmd
, char **args
)
684 const struct passwd
*pw
;
687 if ((pw
= getpwuid(getuid())) == NULL
) {
689 die("getpwuid:%s\n", strerror(errno
));
691 die("who are you?\n");
694 if ((sh
= getenv("SHELL")) == NULL
)
695 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
703 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
708 setenv("LOGNAME", pw
->pw_name
, 1);
709 setenv("USER", pw
->pw_name
, 1);
710 setenv("SHELL", sh
, 1);
711 setenv("HOME", pw
->pw_dir
, 1);
712 setenv("TERM", termname
, 1);
714 signal(SIGCHLD
, SIG_DFL
);
715 signal(SIGHUP
, SIG_DFL
);
716 signal(SIGINT
, SIG_DFL
);
717 signal(SIGQUIT
, SIG_DFL
);
718 signal(SIGTERM
, SIG_DFL
);
719 signal(SIGALRM
, SIG_DFL
);
731 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
732 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
737 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
738 die("child finished with error '%d'\n", stat
);
746 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
749 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
750 die("incorrect stty parameters\n");
751 memcpy(cmd
, stty_args
, n
);
753 siz
= sizeof(cmd
) - n
;
754 for (p
= args
; p
&& (s
= *p
); ++p
) {
755 if ((n
= strlen(s
)) > siz
-1)
756 die("stty parameter length too long\n");
763 if (system(cmd
) != 0)
764 perror("Couldn't call stty");
768 ttynew(char *line
, char *cmd
, char *out
, char **args
)
773 term
.mode
|= MODE_PRINT
;
774 iofd
= (!strcmp(out
, "-")) ?
775 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
777 fprintf(stderr
, "Error opening %s:%s\n",
778 out
, strerror(errno
));
783 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
784 die("open line failed: %s\n", strerror(errno
));
790 /* seems to work fine on linux, openbsd and freebsd */
791 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
792 die("openpty failed: %s\n", strerror(errno
));
794 switch (pid
= fork()) {
796 die("fork failed\n");
800 setsid(); /* create a new process group */
804 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
805 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
813 signal(SIGCHLD
, sigchld
);
822 static char buf
[BUFSIZ
];
823 static int buflen
= 0;
827 /* append read bytes to unprocessed bytes */
828 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
829 die("Couldn't read from shell: %s\n", strerror(errno
));
832 written
= twrite(buf
, buflen
, 0);
834 /* keep any uncomplete utf8 char for the next call */
836 memmove(buf
, buf
+ written
, buflen
);
842 ttywrite(const char *s
, size_t n
, int may_echo
)
846 if (may_echo
&& IS_SET(MODE_ECHO
))
849 if (!IS_SET(MODE_CRLF
)) {
854 /* This is similar to how the kernel handles ONLCR for ttys */
858 ttywriteraw("\r\n", 2);
860 next
= memchr(s
, '\r', n
);
861 DEFAULT(next
, s
+ n
);
862 ttywriteraw(s
, next
- s
);
870 ttywriteraw(const char *s
, size_t n
)
877 * Remember that we are using a pty, which might be a modem line.
878 * Writing too much will clog the line. That's why we are doing this
880 * FIXME: Migrate the world to Plan 9.
888 /* Check if we can write. */
889 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
892 die("select failed: %s\n", strerror(errno
));
894 if (FD_ISSET(cmdfd
, &wfd
)) {
896 * Only write the bytes written by ttywrite() or the
897 * default of 256. This seems to be a reasonable value
898 * for a serial line. Bigger values might clog the I/O.
900 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
904 * We weren't able to write out everything.
905 * This means the buffer is getting full
913 /* All bytes have been written. */
917 if (FD_ISSET(cmdfd
, &rfd
))
923 die("write error on tty: %s\n", strerror(errno
));
927 ttyresize(int tw
, int th
)
935 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
936 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
942 /* Send SIGHUP to shell */
951 for (i
= 0; i
< term
.row
-1; i
++) {
952 for (j
= 0; j
< term
.col
-1; j
++) {
953 if (term
.line
[i
][j
].mode
& attr
)
962 tsetdirt(int top
, int bot
)
966 LIMIT(top
, 0, term
.row
-1);
967 LIMIT(bot
, 0, term
.row
-1);
969 for (i
= top
; i
<= bot
; i
++)
974 tsetdirtattr(int attr
)
978 for (i
= 0; i
< term
.row
-1; i
++) {
979 for (j
= 0; j
< term
.col
-1; j
++) {
980 if (term
.line
[i
][j
].mode
& attr
) {
991 tsetdirt(0, term
.row
-1);
998 int alt
= IS_SET(MODE_ALTSCREEN
);
1000 if (mode
== CURSOR_SAVE
) {
1002 } else if (mode
== CURSOR_LOAD
) {
1004 tmoveto(c
[alt
].x
, c
[alt
].y
);
1013 term
.c
= (TCursor
){{
1017 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1019 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1020 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1023 term
.bot
= term
.row
- 1;
1024 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1025 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1028 for (i
= 0; i
< 2; i
++) {
1030 tcursor(CURSOR_SAVE
);
1031 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1037 tnew(int col
, int row
)
1039 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1047 Line
*tmp
= term
.line
;
1049 term
.line
= term
.alt
;
1051 term
.mode
^= MODE_ALTSCREEN
;
1056 tscrolldown(int orig
, int n
)
1061 LIMIT(n
, 0, term
.bot
-orig
+1);
1063 tsetdirt(orig
, term
.bot
-n
);
1064 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1066 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1067 temp
= term
.line
[i
];
1068 term
.line
[i
] = term
.line
[i
-n
];
1069 term
.line
[i
-n
] = temp
;
1076 tscrollup(int orig
, int n
)
1081 LIMIT(n
, 0, term
.bot
-orig
+1);
1083 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1084 tsetdirt(orig
+n
, term
.bot
);
1086 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1087 temp
= term
.line
[i
];
1088 term
.line
[i
] = term
.line
[i
+n
];
1089 term
.line
[i
+n
] = temp
;
1092 selscroll(orig
, -n
);
1096 selscroll(int orig
, int n
)
1101 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1102 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1106 if (sel
.type
== SEL_RECTANGULAR
) {
1107 if (sel
.ob
.y
< term
.top
)
1108 sel
.ob
.y
= term
.top
;
1109 if (sel
.oe
.y
> term
.bot
)
1110 sel
.oe
.y
= term
.bot
;
1112 if (sel
.ob
.y
< term
.top
) {
1113 sel
.ob
.y
= term
.top
;
1116 if (sel
.oe
.y
> term
.bot
) {
1117 sel
.oe
.y
= term
.bot
;
1118 sel
.oe
.x
= term
.col
;
1126 tnewline(int first_col
)
1130 if (y
== term
.bot
) {
1131 tscrollup(term
.top
, 1);
1135 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1141 char *p
= csiescseq
.buf
, *np
;
1150 csiescseq
.buf
[csiescseq
.len
] = '\0';
1151 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1153 v
= strtol(p
, &np
, 10);
1156 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1158 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1160 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1164 csiescseq
.mode
[0] = *p
++;
1165 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1168 /* for absolute user moves, when decom is set */
1170 tmoveato(int x
, int y
)
1172 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1176 tmoveto(int x
, int y
)
1180 if (term
.c
.state
& CURSOR_ORIGIN
) {
1185 maxy
= term
.row
- 1;
1187 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1188 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1189 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1193 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1195 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1196 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1197 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1198 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1199 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1200 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1201 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1202 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1203 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1207 * The table is proudly stolen from rxvt.
1209 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1210 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1211 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1213 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1214 if (x
+1 < term
.col
) {
1215 term
.line
[y
][x
+1].u
= ' ';
1216 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1218 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1219 term
.line
[y
][x
-1].u
= ' ';
1220 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1224 term
.line
[y
][x
] = *attr
;
1225 term
.line
[y
][x
].u
= u
;
1229 tclearregion(int x1
, int y1
, int x2
, int y2
)
1235 temp
= x1
, x1
= x2
, x2
= temp
;
1237 temp
= y1
, y1
= y2
, y2
= temp
;
1239 LIMIT(x1
, 0, term
.col
-1);
1240 LIMIT(x2
, 0, term
.col
-1);
1241 LIMIT(y1
, 0, term
.row
-1);
1242 LIMIT(y2
, 0, term
.row
-1);
1244 for (y
= y1
; y
<= y2
; y
++) {
1246 for (x
= x1
; x
<= x2
; x
++) {
1247 gp
= &term
.line
[y
][x
];
1250 gp
->fg
= term
.c
.attr
.fg
;
1251 gp
->bg
= term
.c
.attr
.bg
;
1264 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1268 size
= term
.col
- src
;
1269 line
= term
.line
[term
.c
.y
];
1271 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1272 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1281 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1285 size
= term
.col
- dst
;
1286 line
= term
.line
[term
.c
.y
];
1288 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1289 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1293 tinsertblankline(int n
)
1295 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1296 tscrolldown(term
.c
.y
, n
);
1302 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1303 tscrollup(term
.c
.y
, n
);
1307 tdefcolor(int *attr
, int *npar
, int l
)
1312 switch (attr
[*npar
+ 1]) {
1313 case 2: /* direct color in RGB space */
1314 if (*npar
+ 4 >= l
) {
1316 "erresc(38): Incorrect number of parameters (%d)\n",
1320 r
= attr
[*npar
+ 2];
1321 g
= attr
[*npar
+ 3];
1322 b
= attr
[*npar
+ 4];
1324 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1325 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1328 idx
= TRUECOLOR(r
, g
, b
);
1330 case 5: /* indexed color */
1331 if (*npar
+ 2 >= l
) {
1333 "erresc(38): Incorrect number of parameters (%d)\n",
1338 if (!BETWEEN(attr
[*npar
], 0, 255))
1339 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1343 case 0: /* implemented defined (only foreground) */
1344 case 1: /* transparent */
1345 case 3: /* direct color in CMY space */
1346 case 4: /* direct color in CMYK space */
1349 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1357 tsetattr(int *attr
, int l
)
1362 for (i
= 0; i
< l
; i
++) {
1365 term
.c
.attr
.mode
&= ~(
1374 term
.c
.attr
.fg
= defaultfg
;
1375 term
.c
.attr
.bg
= defaultbg
;
1378 term
.c
.attr
.mode
|= ATTR_BOLD
;
1381 term
.c
.attr
.mode
|= ATTR_FAINT
;
1384 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1387 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1389 case 5: /* slow blink */
1391 case 6: /* rapid blink */
1392 term
.c
.attr
.mode
|= ATTR_BLINK
;
1395 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1398 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1401 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1404 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1407 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1410 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1413 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1416 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1419 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1422 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1425 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1426 term
.c
.attr
.fg
= idx
;
1429 term
.c
.attr
.fg
= defaultfg
;
1432 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1433 term
.c
.attr
.bg
= idx
;
1436 term
.c
.attr
.bg
= defaultbg
;
1439 if (BETWEEN(attr
[i
], 30, 37)) {
1440 term
.c
.attr
.fg
= attr
[i
] - 30;
1441 } else if (BETWEEN(attr
[i
], 40, 47)) {
1442 term
.c
.attr
.bg
= attr
[i
] - 40;
1443 } else if (BETWEEN(attr
[i
], 90, 97)) {
1444 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1445 } else if (BETWEEN(attr
[i
], 100, 107)) {
1446 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1449 "erresc(default): gfx attr %d unknown\n",
1450 attr
[i
]), csidump();
1458 tsetscroll(int t
, int b
)
1462 LIMIT(t
, 0, term
.row
-1);
1463 LIMIT(b
, 0, term
.row
-1);
1474 tsetmode(int priv
, int set
, int *args
, int narg
)
1478 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1481 case 1: /* DECCKM -- Cursor key */
1482 xsetmode(set
, MODE_APPCURSOR
);
1484 case 5: /* DECSCNM -- Reverse video */
1485 xsetmode(set
, MODE_REVERSE
);
1487 case 6: /* DECOM -- Origin */
1488 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1491 case 7: /* DECAWM -- Auto wrap */
1492 MODBIT(term
.mode
, set
, MODE_WRAP
);
1494 case 0: /* Error (IGNORED) */
1495 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1496 case 3: /* DECCOLM -- Column (IGNORED) */
1497 case 4: /* DECSCLM -- Scroll (IGNORED) */
1498 case 8: /* DECARM -- Auto repeat (IGNORED) */
1499 case 18: /* DECPFF -- Printer feed (IGNORED) */
1500 case 19: /* DECPEX -- Printer extent (IGNORED) */
1501 case 42: /* DECNRCM -- National characters (IGNORED) */
1502 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1504 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1505 xsetmode(!set
, MODE_HIDE
);
1507 case 9: /* X10 mouse compatibility mode */
1508 xsetpointermotion(0);
1509 xsetmode(0, MODE_MOUSE
);
1510 xsetmode(set
, MODE_MOUSEX10
);
1512 case 1000: /* 1000: report button press */
1513 xsetpointermotion(0);
1514 xsetmode(0, MODE_MOUSE
);
1515 xsetmode(set
, MODE_MOUSEBTN
);
1517 case 1002: /* 1002: report motion on button press */
1518 xsetpointermotion(0);
1519 xsetmode(0, MODE_MOUSE
);
1520 xsetmode(set
, MODE_MOUSEMOTION
);
1522 case 1003: /* 1003: enable all mouse motions */
1523 xsetpointermotion(set
);
1524 xsetmode(0, MODE_MOUSE
);
1525 xsetmode(set
, MODE_MOUSEMANY
);
1527 case 1004: /* 1004: send focus events to tty */
1528 xsetmode(set
, MODE_FOCUS
);
1530 case 1006: /* 1006: extended reporting mode */
1531 xsetmode(set
, MODE_MOUSESGR
);
1534 xsetmode(set
, MODE_8BIT
);
1536 case 1049: /* swap screen & set/restore cursor as xterm */
1537 if (!allowaltscreen
)
1539 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1541 case 47: /* swap screen */
1543 if (!allowaltscreen
)
1545 alt
= IS_SET(MODE_ALTSCREEN
);
1547 tclearregion(0, 0, term
.col
-1,
1550 if (set
^ alt
) /* set is always 1 or 0 */
1556 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1558 case 2004: /* 2004: bracketed paste mode */
1559 xsetmode(set
, MODE_BRCKTPASTE
);
1561 /* Not implemented mouse modes. See comments there. */
1562 case 1001: /* mouse highlight mode; can hang the
1563 terminal by design when implemented. */
1564 case 1005: /* UTF-8 mouse mode; will confuse
1565 applications not supporting UTF-8
1567 case 1015: /* urxvt mangled mouse mode; incompatible
1568 and can be mistaken for other control
1572 "erresc: unknown private set/reset mode %d\n",
1578 case 0: /* Error (IGNORED) */
1581 xsetmode(set
, MODE_KBDLOCK
);
1583 case 4: /* IRM -- Insertion-replacement */
1584 MODBIT(term
.mode
, set
, MODE_INSERT
);
1586 case 12: /* SRM -- Send/Receive */
1587 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1589 case 20: /* LNM -- Linefeed/new line */
1590 MODBIT(term
.mode
, set
, MODE_CRLF
);
1594 "erresc: unknown set/reset mode %d\n",
1608 switch (csiescseq
.mode
[0]) {
1611 fprintf(stderr
, "erresc: unknown csi ");
1615 case '@': /* ICH -- Insert <n> blank char */
1616 DEFAULT(csiescseq
.arg
[0], 1);
1617 tinsertblank(csiescseq
.arg
[0]);
1619 case 'A': /* CUU -- Cursor <n> Up */
1620 DEFAULT(csiescseq
.arg
[0], 1);
1621 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1623 case 'B': /* CUD -- Cursor <n> Down */
1624 case 'e': /* VPR --Cursor <n> Down */
1625 DEFAULT(csiescseq
.arg
[0], 1);
1626 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1628 case 'i': /* MC -- Media Copy */
1629 switch (csiescseq
.arg
[0]) {
1634 tdumpline(term
.c
.y
);
1640 term
.mode
&= ~MODE_PRINT
;
1643 term
.mode
|= MODE_PRINT
;
1647 case 'c': /* DA -- Device Attributes */
1648 if (csiescseq
.arg
[0] == 0)
1649 ttywrite(vtiden
, strlen(vtiden
), 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 */
1697 switch (csiescseq
.arg
[0]) {
1699 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1700 if (term
.c
.y
< term
.row
-1) {
1701 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1707 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1708 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1711 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1717 case 'K': /* EL -- Clear line */
1718 switch (csiescseq
.arg
[0]) {
1720 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1724 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1727 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1731 case 'S': /* SU -- Scroll <n> line up */
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 (cursor position) */
1774 if (csiescseq
.arg
[0] == 6) {
1775 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1776 term
.c
.y
+1, term
.c
.x
+1);
1777 ttywrite(buf
, len
, 0);
1780 case 'r': /* DECSTBM -- Set Scrolling Region */
1781 if (csiescseq
.priv
) {
1784 DEFAULT(csiescseq
.arg
[0], 1);
1785 DEFAULT(csiescseq
.arg
[1], term
.row
);
1786 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1790 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1791 tcursor(CURSOR_SAVE
);
1793 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1794 tcursor(CURSOR_LOAD
);
1797 switch (csiescseq
.mode
[1]) {
1798 case 'q': /* DECSCUSR -- Set Cursor Style */
1799 if (xsetcursor(csiescseq
.arg
[0]))
1815 fprintf(stderr
, "ESC[");
1816 for (i
= 0; i
< csiescseq
.len
; i
++) {
1817 c
= csiescseq
.buf
[i
] & 0xff;
1820 } else if (c
== '\n') {
1821 fprintf(stderr
, "(\\n)");
1822 } else if (c
== '\r') {
1823 fprintf(stderr
, "(\\r)");
1824 } else if (c
== 0x1b) {
1825 fprintf(stderr
, "(\\e)");
1827 fprintf(stderr
, "(%02x)", c
);
1836 memset(&csiescseq
, 0, sizeof(csiescseq
));
1845 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1847 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1849 switch (strescseq
.type
) {
1850 case ']': /* OSC -- Operating System Command */
1856 xsettitle(strescseq
.args
[1]);
1862 dec
= base64dec(strescseq
.args
[2]);
1867 fprintf(stderr
, "erresc: invalid base64\n");
1871 case 4: /* color set */
1874 p
= strescseq
.args
[2];
1876 case 104: /* color reset, here p = NULL */
1877 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1878 if (xsetcolorname(j
, p
)) {
1879 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1882 * TODO if defaultbg color is changed, borders
1890 case 'k': /* old title set compatibility */
1891 xsettitle(strescseq
.args
[0]);
1893 case 'P': /* DCS -- Device Control String */
1894 term
.mode
|= ESC_DCS
;
1895 case '_': /* APC -- Application Program Command */
1896 case '^': /* PM -- Privacy Message */
1900 fprintf(stderr
, "erresc: unknown str ");
1908 char *p
= strescseq
.buf
;
1911 strescseq
.buf
[strescseq
.len
] = '\0';
1916 while (strescseq
.narg
< STR_ARG_SIZ
) {
1917 strescseq
.args
[strescseq
.narg
++] = p
;
1918 while ((c
= *p
) != ';' && c
!= '\0')
1932 fprintf(stderr
, "ESC%c", strescseq
.type
);
1933 for (i
= 0; i
< strescseq
.len
; i
++) {
1934 c
= strescseq
.buf
[i
] & 0xff;
1938 } else if (isprint(c
)) {
1940 } else if (c
== '\n') {
1941 fprintf(stderr
, "(\\n)");
1942 } else if (c
== '\r') {
1943 fprintf(stderr
, "(\\r)");
1944 } else if (c
== 0x1b) {
1945 fprintf(stderr
, "(\\e)");
1947 fprintf(stderr
, "(%02x)", c
);
1950 fprintf(stderr
, "ESC\\\n");
1956 memset(&strescseq
, 0, sizeof(strescseq
));
1960 sendbreak(const Arg
*arg
)
1962 if (tcsendbreak(cmdfd
, 0))
1963 perror("Error sending break");
1967 tprinter(char *s
, size_t len
)
1969 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1970 perror("Error writing to output file");
1977 iso14755(const Arg
*arg
)
1980 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1981 unsigned long utf32
;
1983 if (!(p
= popen(ISO14755CMD
, "r")))
1986 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1989 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1991 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1992 (*e
!= '\n' && *e
!= '\0'))
1995 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
1999 toggleprinter(const Arg
*arg
)
2001 term
.mode
^= MODE_PRINT
;
2005 printscreen(const Arg
*arg
)
2011 printsel(const Arg
*arg
)
2021 if ((ptr
= getsel())) {
2022 tprinter(ptr
, strlen(ptr
));
2033 bp
= &term
.line
[n
][0];
2034 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2035 if (bp
!= end
|| bp
->u
!= ' ') {
2036 for ( ;bp
<= end
; ++bp
)
2037 tprinter(buf
, utf8encode(bp
->u
, buf
));
2047 for (i
= 0; i
< term
.row
; ++i
)
2057 while (x
< term
.col
&& n
--)
2058 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2061 while (x
> 0 && n
++)
2062 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2065 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2069 tdefutf8(char ascii
)
2072 term
.mode
|= MODE_UTF8
;
2073 else if (ascii
== '@')
2074 term
.mode
&= ~MODE_UTF8
;
2078 tdeftran(char ascii
)
2080 static char cs
[] = "0B";
2081 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2084 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2085 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2087 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2096 if (c
== '8') { /* DEC screen alignment test. */
2097 for (x
= 0; x
< term
.col
; ++x
) {
2098 for (y
= 0; y
< term
.row
; ++y
)
2099 tsetchar('E', &term
.c
.attr
, x
, y
);
2105 tstrsequence(uchar c
)
2110 case 0x90: /* DCS -- Device Control String */
2112 term
.esc
|= ESC_DCS
;
2114 case 0x9f: /* APC -- Application Program Command */
2117 case 0x9e: /* PM -- Privacy Message */
2120 case 0x9d: /* OSC -- Operating System Command */
2125 term
.esc
|= ESC_STR
;
2129 tcontrolcode(uchar ascii
)
2136 tmoveto(term
.c
.x
-1, term
.c
.y
);
2139 tmoveto(0, term
.c
.y
);
2144 /* go to first col if the mode is set */
2145 tnewline(IS_SET(MODE_CRLF
));
2147 case '\a': /* BEL */
2148 if (term
.esc
& ESC_STR_END
) {
2149 /* backwards compatibility to xterm */
2155 case '\033': /* ESC */
2157 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2158 term
.esc
|= ESC_START
;
2160 case '\016': /* SO (LS1 -- Locking shift 1) */
2161 case '\017': /* SI (LS0 -- Locking shift 0) */
2162 term
.charset
= 1 - (ascii
- '\016');
2164 case '\032': /* SUB */
2165 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2166 case '\030': /* CAN */
2169 case '\005': /* ENQ (IGNORED) */
2170 case '\000': /* NUL (IGNORED) */
2171 case '\021': /* XON (IGNORED) */
2172 case '\023': /* XOFF (IGNORED) */
2173 case 0177: /* DEL (IGNORED) */
2175 case 0x80: /* TODO: PAD */
2176 case 0x81: /* TODO: HOP */
2177 case 0x82: /* TODO: BPH */
2178 case 0x83: /* TODO: NBH */
2179 case 0x84: /* TODO: IND */
2181 case 0x85: /* NEL -- Next line */
2182 tnewline(1); /* always go to first col */
2184 case 0x86: /* TODO: SSA */
2185 case 0x87: /* TODO: ESA */
2187 case 0x88: /* HTS -- Horizontal tab stop */
2188 term
.tabs
[term
.c
.x
] = 1;
2190 case 0x89: /* TODO: HTJ */
2191 case 0x8a: /* TODO: VTS */
2192 case 0x8b: /* TODO: PLD */
2193 case 0x8c: /* TODO: PLU */
2194 case 0x8d: /* TODO: RI */
2195 case 0x8e: /* TODO: SS2 */
2196 case 0x8f: /* TODO: SS3 */
2197 case 0x91: /* TODO: PU1 */
2198 case 0x92: /* TODO: PU2 */
2199 case 0x93: /* TODO: STS */
2200 case 0x94: /* TODO: CCH */
2201 case 0x95: /* TODO: MW */
2202 case 0x96: /* TODO: SPA */
2203 case 0x97: /* TODO: EPA */
2204 case 0x98: /* TODO: SOS */
2205 case 0x99: /* TODO: SGCI */
2207 case 0x9a: /* DECID -- Identify Terminal */
2208 ttywrite(vtiden
, strlen(vtiden
), 0);
2210 case 0x9b: /* TODO: CSI */
2211 case 0x9c: /* TODO: ST */
2213 case 0x90: /* DCS -- Device Control String */
2214 case 0x9d: /* OSC -- Operating System Command */
2215 case 0x9e: /* PM -- Privacy Message */
2216 case 0x9f: /* APC -- Application Program Command */
2217 tstrsequence(ascii
);
2220 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2221 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2225 * returns 1 when the sequence is finished and it hasn't to read
2226 * more characters for this sequence, otherwise 0
2229 eschandle(uchar ascii
)
2233 term
.esc
|= ESC_CSI
;
2236 term
.esc
|= ESC_TEST
;
2239 term
.esc
|= ESC_UTF8
;
2241 case 'P': /* DCS -- Device Control String */
2242 case '_': /* APC -- Application Program Command */
2243 case '^': /* PM -- Privacy Message */
2244 case ']': /* OSC -- Operating System Command */
2245 case 'k': /* old title set compatibility */
2246 tstrsequence(ascii
);
2248 case 'n': /* LS2 -- Locking shift 2 */
2249 case 'o': /* LS3 -- Locking shift 3 */
2250 term
.charset
= 2 + (ascii
- 'n');
2252 case '(': /* GZD4 -- set primary charset G0 */
2253 case ')': /* G1D4 -- set secondary charset G1 */
2254 case '*': /* G2D4 -- set tertiary charset G2 */
2255 case '+': /* G3D4 -- set quaternary charset G3 */
2256 term
.icharset
= ascii
- '(';
2257 term
.esc
|= ESC_ALTCHARSET
;
2259 case 'D': /* IND -- Linefeed */
2260 if (term
.c
.y
== term
.bot
) {
2261 tscrollup(term
.top
, 1);
2263 tmoveto(term
.c
.x
, term
.c
.y
+1);
2266 case 'E': /* NEL -- Next line */
2267 tnewline(1); /* always go to first col */
2269 case 'H': /* HTS -- Horizontal tab stop */
2270 term
.tabs
[term
.c
.x
] = 1;
2272 case 'M': /* RI -- Reverse index */
2273 if (term
.c
.y
== term
.top
) {
2274 tscrolldown(term
.top
, 1);
2276 tmoveto(term
.c
.x
, term
.c
.y
-1);
2279 case 'Z': /* DECID -- Identify Terminal */
2280 ttywrite(vtiden
, strlen(vtiden
), 0);
2282 case 'c': /* RIS -- Reset to inital state */
2287 case '=': /* DECPAM -- Application keypad */
2288 xsetmode(1, MODE_APPKEYPAD
);
2290 case '>': /* DECPNM -- Normal keypad */
2291 xsetmode(0, MODE_APPKEYPAD
);
2293 case '7': /* DECSC -- Save Cursor */
2294 tcursor(CURSOR_SAVE
);
2296 case '8': /* DECRC -- Restore Cursor */
2297 tcursor(CURSOR_LOAD
);
2299 case '\\': /* ST -- String Terminator */
2300 if (term
.esc
& ESC_STR_END
)
2304 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2305 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2319 control
= ISCONTROL(u
);
2320 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2324 len
= utf8encode(u
, c
);
2325 if (!control
&& (width
= wcwidth(u
)) == -1) {
2326 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2331 if (IS_SET(MODE_PRINT
))
2335 * STR sequence must be checked before anything else
2336 * because it uses all following characters until it
2337 * receives a ESC, a SUB, a ST or any other C1 control
2340 if (term
.esc
& ESC_STR
) {
2341 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2343 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2344 if (IS_SET(MODE_SIXEL
)) {
2345 /* TODO: render sixel */;
2346 term
.mode
&= ~MODE_SIXEL
;
2349 term
.esc
|= ESC_STR_END
;
2350 goto check_control_code
;
2354 if (IS_SET(MODE_SIXEL
)) {
2355 /* TODO: implement sixel mode */
2358 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2359 term
.mode
|= MODE_SIXEL
;
2361 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2363 * Here is a bug in terminals. If the user never sends
2364 * some code to stop the str or esc command, then st
2365 * will stop responding. But this is better than
2366 * silently failing with unknown characters. At least
2367 * then users will report back.
2369 * In the case users ever get fixed, here is the code:
2378 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2379 strescseq
.len
+= len
;
2385 * Actions of control codes must be performed as soon they arrive
2386 * because they can be embedded inside a control sequence, and
2387 * they must not cause conflicts with sequences.
2392 * control codes are not shown ever
2395 } else if (term
.esc
& ESC_START
) {
2396 if (term
.esc
& ESC_CSI
) {
2397 csiescseq
.buf
[csiescseq
.len
++] = u
;
2398 if (BETWEEN(u
, 0x40, 0x7E)
2399 || csiescseq
.len
>= \
2400 sizeof(csiescseq
.buf
)-1) {
2406 } else if (term
.esc
& ESC_UTF8
) {
2408 } else if (term
.esc
& ESC_ALTCHARSET
) {
2410 } else if (term
.esc
& ESC_TEST
) {
2415 /* sequence already finished */
2419 * All characters which form part of a sequence are not
2424 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2427 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2428 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2429 gp
->mode
|= ATTR_WRAP
;
2431 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2434 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2435 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2437 if (term
.c
.x
+width
> term
.col
) {
2439 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2442 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2445 gp
->mode
|= ATTR_WIDE
;
2446 if (term
.c
.x
+1 < term
.col
) {
2448 gp
[1].mode
= ATTR_WDUMMY
;
2451 if (term
.c
.x
+width
< term
.col
) {
2452 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2454 term
.c
.state
|= CURSOR_WRAPNEXT
;
2459 twrite(const char *buf
, int buflen
, int show_ctrl
)
2465 for (n
= 0; n
< buflen
; n
+= charsize
) {
2466 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2467 /* process a complete utf8 char */
2468 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2475 if (show_ctrl
&& ISCONTROL(u
)) {
2480 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2491 tresize(int col
, int row
)
2494 int minrow
= MIN(row
, term
.row
);
2495 int mincol
= MIN(col
, term
.col
);
2499 if (col
< 1 || row
< 1) {
2501 "tresize: error resizing to %dx%d\n", col
, row
);
2506 * slide screen to keep cursor where we expect it -
2507 * tscrollup would work here, but we can optimize to
2508 * memmove because we're freeing the earlier lines
2510 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2514 /* ensure that both src and dst are not NULL */
2516 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2517 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2519 for (i
+= row
; i
< term
.row
; i
++) {
2524 /* resize to new height */
2525 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2526 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2527 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2528 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2530 /* resize each row to new width, zero-pad if needed */
2531 for (i
= 0; i
< minrow
; i
++) {
2532 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2533 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2536 /* allocate any new rows */
2537 for (/* i = minrow */; i
< row
; i
++) {
2538 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2539 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2541 if (col
> term
.col
) {
2542 bp
= term
.tabs
+ term
.col
;
2544 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2545 while (--bp
> term
.tabs
&& !*bp
)
2547 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2550 /* update terminal size */
2553 /* reset scrolling region */
2554 tsetscroll(0, row
-1);
2555 /* make use of the LIMIT in tmoveto */
2556 tmoveto(term
.c
.x
, term
.c
.y
);
2557 /* Clearing both screens (it makes dirty all lines) */
2559 for (i
= 0; i
< 2; i
++) {
2560 if (mincol
< col
&& 0 < minrow
) {
2561 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2563 if (0 < col
&& minrow
< row
) {
2564 tclearregion(0, minrow
, col
- 1, row
- 1);
2567 tcursor(CURSOR_LOAD
);
2579 drawregion(int x1
, int y1
, int x2
, int y2
)
2582 for (y
= y1
; y
< y2
; y
++) {
2587 xdrawline(term
.line
[y
], x1
, y
, x2
);
2599 /* adjust cursor position */
2600 LIMIT(term
.ocx
, 0, term
.col
-1);
2601 LIMIT(term
.ocy
, 0, term
.row
-1);
2602 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2604 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2607 drawregion(0, 0, term
.col
, term
.row
);
2608 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2609 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2610 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;