Xinqi Bao's Git
1 /* See LICENSE for license details. */
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
18 #include <sys/types.h>
31 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
33 #elif defined(__FreeBSD__) || defined(__DragonFly__)
38 #define UTF_INVALID 0xFFFD
40 #define ESC_BUF_SIZ (128*UTF_SIZ)
41 #define ESC_ARG_SIZ 16
42 #define STR_BUF_SIZ ESC_BUF_SIZ
43 #define STR_ARG_SIZ ESC_ARG_SIZ
46 #define IS_SET(flag) ((term.mode & (flag)) != 0)
47 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
48 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
49 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
50 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
51 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
54 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
59 MODE_ALTSCREEN
= 1 << 2,
67 enum cursor_movement
{
91 ESC_STR
= 4, /* OSC, PM, APC */
93 ESC_STR_END
= 16, /* a final string was encountered */
94 ESC_TEST
= 32, /* Enter in test mode */
100 Glyph attr
; /* current char attributes */
111 * Selection variables:
112 * nb – normalized coordinates of the beginning of the selection
113 * ne – normalized coordinates of the end of the selection
114 * ob – original coordinates of the beginning of the selection
115 * oe – original coordinates of the end of the selection
124 /* Internal representation of the screen */
126 int row
; /* nb row */
127 int col
; /* nb col */
128 Line
*line
; /* screen */
129 Line
*alt
; /* alternate screen */
130 int *dirty
; /* dirtyness of lines */
131 TCursor c
; /* cursor */
132 int ocx
; /* old cursor col */
133 int ocy
; /* old cursor row */
134 int top
; /* top scroll limit */
135 int bot
; /* bottom scroll limit */
136 int mode
; /* terminal mode flags */
137 int esc
; /* escape state flags */
138 char trantbl
[4]; /* charset table translation */
139 int charset
; /* current charset */
140 int icharset
; /* selected charset for sequence */
144 /* CSI Escape sequence structs */
145 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
147 char buf
[ESC_BUF_SIZ
]; /* raw string */
148 int len
; /* raw string length */
150 int arg
[ESC_ARG_SIZ
];
151 int narg
; /* nb of args */
155 /* STR Escape sequence structs */
156 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
158 char type
; /* ESC type ... */
159 char buf
[STR_BUF_SIZ
]; /* raw string */
160 int len
; /* raw string length */
161 char *args
[STR_ARG_SIZ
];
162 int narg
; /* nb of args */
165 static void execsh(char *, char **);
166 static void stty(char **);
167 static void sigchld(int);
168 static void ttywriteraw(const char *, size_t);
170 static void csidump(void);
171 static void csihandle(void);
172 static void csiparse(void);
173 static void csireset(void);
174 static int eschandle(uchar
);
175 static void strdump(void);
176 static void strhandle(void);
177 static void strparse(void);
178 static void strreset(void);
180 static void tprinter(char *, size_t);
181 static void tdumpsel(void);
182 static void tdumpline(int);
183 static void tdump(void);
184 static void tclearregion(int, int, int, int);
185 static void tcursor(int);
186 static void tdeletechar(int);
187 static void tdeleteline(int);
188 static void tinsertblank(int);
189 static void tinsertblankline(int);
190 static int tlinelen(int);
191 static void tmoveto(int, int);
192 static void tmoveato(int, int);
193 static void tnewline(int);
194 static void tputtab(int);
195 static void tputc(Rune
);
196 static void treset(void);
197 static void tscrollup(int, int);
198 static void tscrolldown(int, int);
199 static void tsetattr(int *, int);
200 static void tsetchar(Rune
, Glyph
*, int, int);
201 static void tsetdirt(int, int);
202 static void tsetscroll(int, int);
203 static void tswapscreen(void);
204 static void tsetmode(int, int, int *, int);
205 static int twrite(const char *, int, int);
206 static void tfulldirt(void);
207 static void tcontrolcode(uchar
);
208 static void tdectest(char );
209 static void tdefutf8(char);
210 static int32_t tdefcolor(int *, int *, int);
211 static void tdeftran(char);
212 static void tstrsequence(uchar
);
214 static void drawregion(int, int, int, int);
216 static void selnormalize(void);
217 static void selscroll(int, int);
218 static void selsnap(int *, int *, int);
220 static size_t utf8decode(const char *, Rune
*, size_t);
221 static Rune
utf8decodebyte(char, size_t *);
222 static char utf8encodebyte(Rune
, size_t);
223 static char *utf8strchr(char *, Rune
);
224 static size_t utf8validate(Rune
*, size_t);
226 static char *base64dec(const char *);
227 static char base64dec_getc(const char **);
229 static ssize_t
xwrite(int, const char *, size_t);
233 static Selection sel
;
234 static CSIEscape csiescseq
;
235 static STREscape strescseq
;
240 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
241 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
242 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
243 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
246 xwrite(int fd
, const char *s
, size_t len
)
252 r
= write(fd
, s
, len
);
265 void *p
= malloc(len
);
268 die("Out of memory\n");
274 xrealloc(void *p
, size_t len
)
276 if ((p
= realloc(p
, len
)) == NULL
)
277 die("Out of memory\n");
285 if ((s
= strdup(s
)) == NULL
)
286 die("Out of memory\n");
292 utf8decode(const char *c
, Rune
*u
, size_t clen
)
294 size_t i
, j
, len
, type
;
300 udecoded
= utf8decodebyte(c
[0], &len
);
301 if (!BETWEEN(len
, 1, UTF_SIZ
))
303 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
304 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
311 utf8validate(u
, len
);
317 utf8decodebyte(char c
, size_t *i
)
319 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
320 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
321 return (uchar
)c
& ~utfmask
[*i
];
327 utf8encode(Rune u
, char *c
)
331 len
= utf8validate(&u
, 0);
335 for (i
= len
- 1; i
!= 0; --i
) {
336 c
[i
] = utf8encodebyte(u
, 0);
339 c
[0] = utf8encodebyte(u
, len
);
345 utf8encodebyte(Rune u
, size_t i
)
347 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
351 utf8strchr(char *s
, Rune u
)
357 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
358 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
368 utf8validate(Rune
*u
, size_t i
)
370 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
372 for (i
= 1; *u
> utfmax
[i
]; ++i
)
378 static const char base64_digits
[] = {
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, 62, 0, 0, 0,
381 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
382 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
383 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
384 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
385 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
386 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
387 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
388 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
389 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
390 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
394 base64dec_getc(const char **src
)
396 while (**src
&& !isprint(**src
)) (*src
)++;
401 base64dec(const char *src
)
403 size_t in_len
= strlen(src
);
407 in_len
+= 4 - (in_len
% 4);
408 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
410 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
411 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
412 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
413 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
415 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
418 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
421 *dst
++ = ((c
& 0x03) << 6) | d
;
440 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
443 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
450 selstart(int col
, int row
, int snap
)
453 sel
.mode
= SEL_EMPTY
;
454 sel
.type
= SEL_REGULAR
;
456 sel
.oe
.x
= sel
.ob
.x
= col
;
457 sel
.oe
.y
= sel
.ob
.y
= row
;
461 sel
.mode
= SEL_READY
;
462 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
466 selextend(int col
, int row
, int type
, int done
)
468 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
472 if (done
&& sel
.mode
== SEL_EMPTY
) {
483 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
489 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
490 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
492 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
500 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
501 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
502 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
504 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
505 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
507 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
508 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
510 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
511 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
513 /* expand selection over line breaks */
514 if (sel
.type
== SEL_RECTANGULAR
)
516 i
= tlinelen(sel
.nb
.y
);
519 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
520 sel
.ne
.x
= term
.col
- 1;
524 selected(int x
, int y
)
526 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
527 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
530 if (sel
.type
== SEL_RECTANGULAR
)
531 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
532 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
534 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
535 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
536 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
540 selsnap(int *x
, int *y
, int direction
)
542 int newx
, newy
, xt
, yt
;
543 int delim
, prevdelim
;
549 * Snap around if the word wraps around at the end or
550 * beginning of a line.
552 prevgp
= &term
.line
[*y
][*x
];
553 prevdelim
= ISDELIM(prevgp
->u
);
555 newx
= *x
+ direction
;
557 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
559 newx
= (newx
+ term
.col
) % term
.col
;
560 if (!BETWEEN(newy
, 0, term
.row
- 1))
566 yt
= newy
, xt
= newx
;
567 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
571 if (newx
>= tlinelen(newy
))
574 gp
= &term
.line
[newy
][newx
];
575 delim
= ISDELIM(gp
->u
);
576 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
577 || (delim
&& gp
->u
!= prevgp
->u
)))
588 * Snap around if the the previous line or the current one
589 * has set ATTR_WRAP at its end. Then the whole next or
590 * previous line will be selected.
592 *x
= (direction
< 0) ? 0 : term
.col
- 1;
594 for (; *y
> 0; *y
+= direction
) {
595 if (!(term
.line
[*y
-1][term
.col
-1].mode
600 } else if (direction
> 0) {
601 for (; *y
< term
.row
-1; *y
+= direction
) {
602 if (!(term
.line
[*y
][term
.col
-1].mode
616 int y
, bufsize
, lastx
, linelen
;
622 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
623 ptr
= str
= xmalloc(bufsize
);
625 /* append every set & selected glyph to the selection */
626 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
627 if ((linelen
= tlinelen(y
)) == 0) {
632 if (sel
.type
== SEL_RECTANGULAR
) {
633 gp
= &term
.line
[y
][sel
.nb
.x
];
636 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
637 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
639 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
640 while (last
>= gp
&& last
->u
== ' ')
643 for ( ; gp
<= last
; ++gp
) {
644 if (gp
->mode
& ATTR_WDUMMY
)
647 ptr
+= utf8encode(gp
->u
, ptr
);
651 * Copy and pasting of line endings is inconsistent
652 * in the inconsistent terminal and GUI world.
653 * The best solution seems like to produce '\n' when
654 * something is copied from st and convert '\n' to
655 * '\r', when something to be pasted is received by
657 * FIXME: Fix the computer world.
659 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
673 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
677 die(const char *errstr
, ...)
681 va_start(ap
, errstr
);
682 vfprintf(stderr
, errstr
, ap
);
688 execsh(char *cmd
, char **args
)
691 const struct passwd
*pw
;
694 if ((pw
= getpwuid(getuid())) == NULL
) {
696 die("getpwuid:%s\n", strerror(errno
));
698 die("who are you?\n");
701 if ((sh
= getenv("SHELL")) == NULL
)
702 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
710 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
715 setenv("LOGNAME", pw
->pw_name
, 1);
716 setenv("USER", pw
->pw_name
, 1);
717 setenv("SHELL", sh
, 1);
718 setenv("HOME", pw
->pw_dir
, 1);
719 setenv("TERM", termname
, 1);
721 signal(SIGCHLD
, SIG_DFL
);
722 signal(SIGHUP
, SIG_DFL
);
723 signal(SIGINT
, SIG_DFL
);
724 signal(SIGQUIT
, SIG_DFL
);
725 signal(SIGTERM
, SIG_DFL
);
726 signal(SIGALRM
, SIG_DFL
);
738 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
739 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
744 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
745 die("child finished with error '%d'\n", stat
);
753 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
756 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
757 die("incorrect stty parameters\n");
758 memcpy(cmd
, stty_args
, n
);
760 siz
= sizeof(cmd
) - n
;
761 for (p
= args
; p
&& (s
= *p
); ++p
) {
762 if ((n
= strlen(s
)) > siz
-1)
763 die("stty parameter length too long\n");
770 if (system(cmd
) != 0)
771 perror("Couldn't call stty");
775 ttynew(char *line
, char *cmd
, char *out
, char **args
)
780 term
.mode
|= MODE_PRINT
;
781 iofd
= (!strcmp(out
, "-")) ?
782 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
784 fprintf(stderr
, "Error opening %s:%s\n",
785 out
, strerror(errno
));
790 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
791 die("open line failed: %s\n", strerror(errno
));
797 /* seems to work fine on linux, openbsd and freebsd */
798 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
799 die("openpty failed: %s\n", strerror(errno
));
801 switch (pid
= fork()) {
803 die("fork failed\n");
807 setsid(); /* create a new process group */
811 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
812 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
820 signal(SIGCHLD
, sigchld
);
829 static char buf
[BUFSIZ
];
830 static int buflen
= 0;
834 /* append read bytes to unprocessed bytes */
835 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
836 die("Couldn't read from shell: %s\n", strerror(errno
));
839 written
= twrite(buf
, buflen
, 0);
841 /* keep any uncomplete utf8 char for the next call */
843 memmove(buf
, buf
+ written
, buflen
);
849 ttywrite(const char *s
, size_t n
, int may_echo
)
853 if (may_echo
&& IS_SET(MODE_ECHO
))
856 if (!IS_SET(MODE_CRLF
)) {
861 /* This is similar to how the kernel handles ONLCR for ttys */
865 ttywriteraw("\r\n", 2);
867 next
= memchr(s
, '\r', n
);
868 DEFAULT(next
, s
+ n
);
869 ttywriteraw(s
, next
- s
);
877 ttywriteraw(const char *s
, size_t n
)
884 * Remember that we are using a pty, which might be a modem line.
885 * Writing too much will clog the line. That's why we are doing this
887 * FIXME: Migrate the world to Plan 9.
895 /* Check if we can write. */
896 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
899 die("select failed: %s\n", strerror(errno
));
901 if (FD_ISSET(cmdfd
, &wfd
)) {
903 * Only write the bytes written by ttywrite() or the
904 * default of 256. This seems to be a reasonable value
905 * for a serial line. Bigger values might clog the I/O.
907 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
911 * We weren't able to write out everything.
912 * This means the buffer is getting full
920 /* All bytes have been written. */
924 if (FD_ISSET(cmdfd
, &rfd
))
930 die("write error on tty: %s\n", strerror(errno
));
934 ttyresize(int tw
, int th
)
942 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
943 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
949 /* Send SIGHUP to shell */
958 for (i
= 0; i
< term
.row
-1; i
++) {
959 for (j
= 0; j
< term
.col
-1; j
++) {
960 if (term
.line
[i
][j
].mode
& attr
)
969 tsetdirt(int top
, int bot
)
973 LIMIT(top
, 0, term
.row
-1);
974 LIMIT(bot
, 0, term
.row
-1);
976 for (i
= top
; i
<= bot
; i
++)
981 tsetdirtattr(int attr
)
985 for (i
= 0; i
< term
.row
-1; i
++) {
986 for (j
= 0; j
< term
.col
-1; j
++) {
987 if (term
.line
[i
][j
].mode
& attr
) {
998 tsetdirt(0, term
.row
-1);
1004 static TCursor c
[2];
1005 int alt
= IS_SET(MODE_ALTSCREEN
);
1007 if (mode
== CURSOR_SAVE
) {
1009 } else if (mode
== CURSOR_LOAD
) {
1011 tmoveto(c
[alt
].x
, c
[alt
].y
);
1020 term
.c
= (TCursor
){{
1024 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1026 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1027 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1030 term
.bot
= term
.row
- 1;
1031 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1032 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1035 for (i
= 0; i
< 2; i
++) {
1037 tcursor(CURSOR_SAVE
);
1038 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1044 tnew(int col
, int row
)
1046 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1054 Line
*tmp
= term
.line
;
1056 term
.line
= term
.alt
;
1058 term
.mode
^= MODE_ALTSCREEN
;
1063 tscrolldown(int orig
, int n
)
1068 LIMIT(n
, 0, term
.bot
-orig
+1);
1070 tsetdirt(orig
, term
.bot
-n
);
1071 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1073 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1074 temp
= term
.line
[i
];
1075 term
.line
[i
] = term
.line
[i
-n
];
1076 term
.line
[i
-n
] = temp
;
1083 tscrollup(int orig
, int n
)
1088 LIMIT(n
, 0, term
.bot
-orig
+1);
1090 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1091 tsetdirt(orig
+n
, term
.bot
);
1093 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1094 temp
= term
.line
[i
];
1095 term
.line
[i
] = term
.line
[i
+n
];
1096 term
.line
[i
+n
] = temp
;
1099 selscroll(orig
, -n
);
1103 selscroll(int orig
, int n
)
1108 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1109 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1113 if (sel
.type
== SEL_RECTANGULAR
) {
1114 if (sel
.ob
.y
< term
.top
)
1115 sel
.ob
.y
= term
.top
;
1116 if (sel
.oe
.y
> term
.bot
)
1117 sel
.oe
.y
= term
.bot
;
1119 if (sel
.ob
.y
< term
.top
) {
1120 sel
.ob
.y
= term
.top
;
1123 if (sel
.oe
.y
> term
.bot
) {
1124 sel
.oe
.y
= term
.bot
;
1125 sel
.oe
.x
= term
.col
;
1133 tnewline(int first_col
)
1137 if (y
== term
.bot
) {
1138 tscrollup(term
.top
, 1);
1142 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1148 char *p
= csiescseq
.buf
, *np
;
1157 csiescseq
.buf
[csiescseq
.len
] = '\0';
1158 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1160 v
= strtol(p
, &np
, 10);
1163 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1165 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1167 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1171 csiescseq
.mode
[0] = *p
++;
1172 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1175 /* for absolute user moves, when decom is set */
1177 tmoveato(int x
, int y
)
1179 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1183 tmoveto(int x
, int y
)
1187 if (term
.c
.state
& CURSOR_ORIGIN
) {
1192 maxy
= term
.row
- 1;
1194 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1195 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1196 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1200 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1202 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1203 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1204 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1205 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1206 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1207 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1208 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1209 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1210 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1214 * The table is proudly stolen from rxvt.
1216 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1217 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1218 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1220 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1221 if (x
+1 < term
.col
) {
1222 term
.line
[y
][x
+1].u
= ' ';
1223 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1225 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1226 term
.line
[y
][x
-1].u
= ' ';
1227 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1231 term
.line
[y
][x
] = *attr
;
1232 term
.line
[y
][x
].u
= u
;
1236 tclearregion(int x1
, int y1
, int x2
, int y2
)
1242 temp
= x1
, x1
= x2
, x2
= temp
;
1244 temp
= y1
, y1
= y2
, y2
= temp
;
1246 LIMIT(x1
, 0, term
.col
-1);
1247 LIMIT(x2
, 0, term
.col
-1);
1248 LIMIT(y1
, 0, term
.row
-1);
1249 LIMIT(y2
, 0, term
.row
-1);
1251 for (y
= y1
; y
<= y2
; y
++) {
1253 for (x
= x1
; x
<= x2
; x
++) {
1254 gp
= &term
.line
[y
][x
];
1257 gp
->fg
= term
.c
.attr
.fg
;
1258 gp
->bg
= term
.c
.attr
.bg
;
1271 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1275 size
= term
.col
- src
;
1276 line
= term
.line
[term
.c
.y
];
1278 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1279 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1288 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1292 size
= term
.col
- dst
;
1293 line
= term
.line
[term
.c
.y
];
1295 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1296 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1300 tinsertblankline(int n
)
1302 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1303 tscrolldown(term
.c
.y
, n
);
1309 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1310 tscrollup(term
.c
.y
, n
);
1314 tdefcolor(int *attr
, int *npar
, int l
)
1319 switch (attr
[*npar
+ 1]) {
1320 case 2: /* direct color in RGB space */
1321 if (*npar
+ 4 >= l
) {
1323 "erresc(38): Incorrect number of parameters (%d)\n",
1327 r
= attr
[*npar
+ 2];
1328 g
= attr
[*npar
+ 3];
1329 b
= attr
[*npar
+ 4];
1331 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1332 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1335 idx
= TRUECOLOR(r
, g
, b
);
1337 case 5: /* indexed color */
1338 if (*npar
+ 2 >= l
) {
1340 "erresc(38): Incorrect number of parameters (%d)\n",
1345 if (!BETWEEN(attr
[*npar
], 0, 255))
1346 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1350 case 0: /* implemented defined (only foreground) */
1351 case 1: /* transparent */
1352 case 3: /* direct color in CMY space */
1353 case 4: /* direct color in CMYK space */
1356 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1364 tsetattr(int *attr
, int l
)
1369 for (i
= 0; i
< l
; i
++) {
1372 term
.c
.attr
.mode
&= ~(
1381 term
.c
.attr
.fg
= defaultfg
;
1382 term
.c
.attr
.bg
= defaultbg
;
1385 term
.c
.attr
.mode
|= ATTR_BOLD
;
1388 term
.c
.attr
.mode
|= ATTR_FAINT
;
1391 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1394 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1396 case 5: /* slow blink */
1398 case 6: /* rapid blink */
1399 term
.c
.attr
.mode
|= ATTR_BLINK
;
1402 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1405 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1408 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1411 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1414 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1417 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1420 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1423 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1426 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1429 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1432 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1433 term
.c
.attr
.fg
= idx
;
1436 term
.c
.attr
.fg
= defaultfg
;
1439 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1440 term
.c
.attr
.bg
= idx
;
1443 term
.c
.attr
.bg
= defaultbg
;
1446 if (BETWEEN(attr
[i
], 30, 37)) {
1447 term
.c
.attr
.fg
= attr
[i
] - 30;
1448 } else if (BETWEEN(attr
[i
], 40, 47)) {
1449 term
.c
.attr
.bg
= attr
[i
] - 40;
1450 } else if (BETWEEN(attr
[i
], 90, 97)) {
1451 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1452 } else if (BETWEEN(attr
[i
], 100, 107)) {
1453 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1456 "erresc(default): gfx attr %d unknown\n",
1457 attr
[i
]), csidump();
1465 tsetscroll(int t
, int b
)
1469 LIMIT(t
, 0, term
.row
-1);
1470 LIMIT(b
, 0, term
.row
-1);
1481 tsetmode(int priv
, int set
, int *args
, int narg
)
1485 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1488 case 1: /* DECCKM -- Cursor key */
1489 xsetmode(set
, MODE_APPCURSOR
);
1491 case 5: /* DECSCNM -- Reverse video */
1492 xsetmode(set
, MODE_REVERSE
);
1494 case 6: /* DECOM -- Origin */
1495 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1498 case 7: /* DECAWM -- Auto wrap */
1499 MODBIT(term
.mode
, set
, MODE_WRAP
);
1501 case 0: /* Error (IGNORED) */
1502 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1503 case 3: /* DECCOLM -- Column (IGNORED) */
1504 case 4: /* DECSCLM -- Scroll (IGNORED) */
1505 case 8: /* DECARM -- Auto repeat (IGNORED) */
1506 case 18: /* DECPFF -- Printer feed (IGNORED) */
1507 case 19: /* DECPEX -- Printer extent (IGNORED) */
1508 case 42: /* DECNRCM -- National characters (IGNORED) */
1509 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1511 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1512 xsetmode(!set
, MODE_HIDE
);
1514 case 9: /* X10 mouse compatibility mode */
1515 xsetpointermotion(0);
1516 xsetmode(0, MODE_MOUSE
);
1517 xsetmode(set
, MODE_MOUSEX10
);
1519 case 1000: /* 1000: report button press */
1520 xsetpointermotion(0);
1521 xsetmode(0, MODE_MOUSE
);
1522 xsetmode(set
, MODE_MOUSEBTN
);
1524 case 1002: /* 1002: report motion on button press */
1525 xsetpointermotion(0);
1526 xsetmode(0, MODE_MOUSE
);
1527 xsetmode(set
, MODE_MOUSEMOTION
);
1529 case 1003: /* 1003: enable all mouse motions */
1530 xsetpointermotion(set
);
1531 xsetmode(0, MODE_MOUSE
);
1532 xsetmode(set
, MODE_MOUSEMANY
);
1534 case 1004: /* 1004: send focus events to tty */
1535 xsetmode(set
, MODE_FOCUS
);
1537 case 1006: /* 1006: extended reporting mode */
1538 xsetmode(set
, MODE_MOUSESGR
);
1541 xsetmode(set
, MODE_8BIT
);
1543 case 1049: /* swap screen & set/restore cursor as xterm */
1544 if (!allowaltscreen
)
1546 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1548 case 47: /* swap screen */
1550 if (!allowaltscreen
)
1552 alt
= IS_SET(MODE_ALTSCREEN
);
1554 tclearregion(0, 0, term
.col
-1,
1557 if (set
^ alt
) /* set is always 1 or 0 */
1563 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1565 case 2004: /* 2004: bracketed paste mode */
1566 xsetmode(set
, MODE_BRCKTPASTE
);
1568 /* Not implemented mouse modes. See comments there. */
1569 case 1001: /* mouse highlight mode; can hang the
1570 terminal by design when implemented. */
1571 case 1005: /* UTF-8 mouse mode; will confuse
1572 applications not supporting UTF-8
1574 case 1015: /* urxvt mangled mouse mode; incompatible
1575 and can be mistaken for other control
1579 "erresc: unknown private set/reset mode %d\n",
1585 case 0: /* Error (IGNORED) */
1588 xsetmode(set
, MODE_KBDLOCK
);
1590 case 4: /* IRM -- Insertion-replacement */
1591 MODBIT(term
.mode
, set
, MODE_INSERT
);
1593 case 12: /* SRM -- Send/Receive */
1594 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1596 case 20: /* LNM -- Linefeed/new line */
1597 MODBIT(term
.mode
, set
, MODE_CRLF
);
1601 "erresc: unknown set/reset mode %d\n",
1615 switch (csiescseq
.mode
[0]) {
1618 fprintf(stderr
, "erresc: unknown csi ");
1622 case '@': /* ICH -- Insert <n> blank char */
1623 DEFAULT(csiescseq
.arg
[0], 1);
1624 tinsertblank(csiescseq
.arg
[0]);
1626 case 'A': /* CUU -- Cursor <n> Up */
1627 DEFAULT(csiescseq
.arg
[0], 1);
1628 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1630 case 'B': /* CUD -- Cursor <n> Down */
1631 case 'e': /* VPR --Cursor <n> Down */
1632 DEFAULT(csiescseq
.arg
[0], 1);
1633 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1635 case 'i': /* MC -- Media Copy */
1636 switch (csiescseq
.arg
[0]) {
1641 tdumpline(term
.c
.y
);
1647 term
.mode
&= ~MODE_PRINT
;
1650 term
.mode
|= MODE_PRINT
;
1654 case 'c': /* DA -- Device Attributes */
1655 if (csiescseq
.arg
[0] == 0)
1656 ttywrite(vtiden
, strlen(vtiden
), 0);
1658 case 'C': /* CUF -- Cursor <n> Forward */
1659 case 'a': /* HPR -- Cursor <n> Forward */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1663 case 'D': /* CUB -- Cursor <n> Backward */
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1667 case 'E': /* CNL -- Cursor <n> Down and first col */
1668 DEFAULT(csiescseq
.arg
[0], 1);
1669 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1671 case 'F': /* CPL -- Cursor <n> Up and first col */
1672 DEFAULT(csiescseq
.arg
[0], 1);
1673 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1675 case 'g': /* TBC -- Tabulation clear */
1676 switch (csiescseq
.arg
[0]) {
1677 case 0: /* clear current tab stop */
1678 term
.tabs
[term
.c
.x
] = 0;
1680 case 3: /* clear all the tabs */
1681 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1687 case 'G': /* CHA -- Move to <col> */
1689 DEFAULT(csiescseq
.arg
[0], 1);
1690 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1692 case 'H': /* CUP -- Move to <row> <col> */
1694 DEFAULT(csiescseq
.arg
[0], 1);
1695 DEFAULT(csiescseq
.arg
[1], 1);
1696 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1698 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1699 DEFAULT(csiescseq
.arg
[0], 1);
1700 tputtab(csiescseq
.arg
[0]);
1702 case 'J': /* ED -- Clear screen */
1704 switch (csiescseq
.arg
[0]) {
1706 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1707 if (term
.c
.y
< term
.row
-1) {
1708 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1714 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1715 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1718 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1724 case 'K': /* EL -- Clear line */
1725 switch (csiescseq
.arg
[0]) {
1727 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1731 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1734 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1738 case 'S': /* SU -- Scroll <n> line up */
1739 DEFAULT(csiescseq
.arg
[0], 1);
1740 tscrollup(term
.top
, csiescseq
.arg
[0]);
1742 case 'T': /* SD -- Scroll <n> line down */
1743 DEFAULT(csiescseq
.arg
[0], 1);
1744 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1746 case 'L': /* IL -- Insert <n> blank lines */
1747 DEFAULT(csiescseq
.arg
[0], 1);
1748 tinsertblankline(csiescseq
.arg
[0]);
1750 case 'l': /* RM -- Reset Mode */
1751 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1753 case 'M': /* DL -- Delete <n> lines */
1754 DEFAULT(csiescseq
.arg
[0], 1);
1755 tdeleteline(csiescseq
.arg
[0]);
1757 case 'X': /* ECH -- Erase <n> char */
1758 DEFAULT(csiescseq
.arg
[0], 1);
1759 tclearregion(term
.c
.x
, term
.c
.y
,
1760 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1762 case 'P': /* DCH -- Delete <n> char */
1763 DEFAULT(csiescseq
.arg
[0], 1);
1764 tdeletechar(csiescseq
.arg
[0]);
1766 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1767 DEFAULT(csiescseq
.arg
[0], 1);
1768 tputtab(-csiescseq
.arg
[0]);
1770 case 'd': /* VPA -- Move to <row> */
1771 DEFAULT(csiescseq
.arg
[0], 1);
1772 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1774 case 'h': /* SM -- Set terminal mode */
1775 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1777 case 'm': /* SGR -- Terminal attribute (color) */
1778 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1780 case 'n': /* DSR – Device Status Report (cursor position) */
1781 if (csiescseq
.arg
[0] == 6) {
1782 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1783 term
.c
.y
+1, term
.c
.x
+1);
1784 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
));
1852 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1854 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1856 switch (strescseq
.type
) {
1857 case ']': /* OSC -- Operating System Command */
1863 xsettitle(strescseq
.args
[1]);
1869 dec
= base64dec(strescseq
.args
[2]);
1874 fprintf(stderr
, "erresc: invalid base64\n");
1878 case 4: /* color set */
1881 p
= strescseq
.args
[2];
1883 case 104: /* color reset, here p = NULL */
1884 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1885 if (xsetcolorname(j
, p
)) {
1886 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1889 * TODO if defaultbg color is changed, borders
1897 case 'k': /* old title set compatibility */
1898 xsettitle(strescseq
.args
[0]);
1900 case 'P': /* DCS -- Device Control String */
1901 term
.mode
|= ESC_DCS
;
1902 case '_': /* APC -- Application Program Command */
1903 case '^': /* PM -- Privacy Message */
1907 fprintf(stderr
, "erresc: unknown str ");
1915 char *p
= strescseq
.buf
;
1918 strescseq
.buf
[strescseq
.len
] = '\0';
1923 while (strescseq
.narg
< STR_ARG_SIZ
) {
1924 strescseq
.args
[strescseq
.narg
++] = p
;
1925 while ((c
= *p
) != ';' && c
!= '\0')
1939 fprintf(stderr
, "ESC%c", strescseq
.type
);
1940 for (i
= 0; i
< strescseq
.len
; i
++) {
1941 c
= strescseq
.buf
[i
] & 0xff;
1945 } else if (isprint(c
)) {
1947 } else if (c
== '\n') {
1948 fprintf(stderr
, "(\\n)");
1949 } else if (c
== '\r') {
1950 fprintf(stderr
, "(\\r)");
1951 } else if (c
== 0x1b) {
1952 fprintf(stderr
, "(\\e)");
1954 fprintf(stderr
, "(%02x)", c
);
1957 fprintf(stderr
, "ESC\\\n");
1963 memset(&strescseq
, 0, sizeof(strescseq
));
1967 sendbreak(const Arg
*arg
)
1969 if (tcsendbreak(cmdfd
, 0))
1970 perror("Error sending break");
1974 tprinter(char *s
, size_t len
)
1976 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1977 perror("Error writing to output file");
1984 iso14755(const Arg
*arg
)
1987 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1988 unsigned long utf32
;
1990 if (!(p
= popen(ISO14755CMD
, "r")))
1993 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1996 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1998 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1999 (*e
!= '\n' && *e
!= '\0'))
2002 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
2006 toggleprinter(const Arg
*arg
)
2008 term
.mode
^= MODE_PRINT
;
2012 printscreen(const Arg
*arg
)
2018 printsel(const Arg
*arg
)
2028 if ((ptr
= getsel())) {
2029 tprinter(ptr
, strlen(ptr
));
2040 bp
= &term
.line
[n
][0];
2041 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2042 if (bp
!= end
|| bp
->u
!= ' ') {
2043 for ( ;bp
<= end
; ++bp
)
2044 tprinter(buf
, utf8encode(bp
->u
, buf
));
2054 for (i
= 0; i
< term
.row
; ++i
)
2064 while (x
< term
.col
&& n
--)
2065 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2068 while (x
> 0 && n
++)
2069 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2072 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2076 tdefutf8(char ascii
)
2079 term
.mode
|= MODE_UTF8
;
2080 else if (ascii
== '@')
2081 term
.mode
&= ~MODE_UTF8
;
2085 tdeftran(char ascii
)
2087 static char cs
[] = "0B";
2088 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2091 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2092 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2094 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2103 if (c
== '8') { /* DEC screen alignment test. */
2104 for (x
= 0; x
< term
.col
; ++x
) {
2105 for (y
= 0; y
< term
.row
; ++y
)
2106 tsetchar('E', &term
.c
.attr
, x
, y
);
2112 tstrsequence(uchar c
)
2117 case 0x90: /* DCS -- Device Control String */
2119 term
.esc
|= ESC_DCS
;
2121 case 0x9f: /* APC -- Application Program Command */
2124 case 0x9e: /* PM -- Privacy Message */
2127 case 0x9d: /* OSC -- Operating System Command */
2132 term
.esc
|= ESC_STR
;
2136 tcontrolcode(uchar ascii
)
2143 tmoveto(term
.c
.x
-1, term
.c
.y
);
2146 tmoveto(0, term
.c
.y
);
2151 /* go to first col if the mode is set */
2152 tnewline(IS_SET(MODE_CRLF
));
2154 case '\a': /* BEL */
2155 if (term
.esc
& ESC_STR_END
) {
2156 /* backwards compatibility to xterm */
2162 case '\033': /* ESC */
2164 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2165 term
.esc
|= ESC_START
;
2167 case '\016': /* SO (LS1 -- Locking shift 1) */
2168 case '\017': /* SI (LS0 -- Locking shift 0) */
2169 term
.charset
= 1 - (ascii
- '\016');
2171 case '\032': /* SUB */
2172 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2173 case '\030': /* CAN */
2176 case '\005': /* ENQ (IGNORED) */
2177 case '\000': /* NUL (IGNORED) */
2178 case '\021': /* XON (IGNORED) */
2179 case '\023': /* XOFF (IGNORED) */
2180 case 0177: /* DEL (IGNORED) */
2182 case 0x80: /* TODO: PAD */
2183 case 0x81: /* TODO: HOP */
2184 case 0x82: /* TODO: BPH */
2185 case 0x83: /* TODO: NBH */
2186 case 0x84: /* TODO: IND */
2188 case 0x85: /* NEL -- Next line */
2189 tnewline(1); /* always go to first col */
2191 case 0x86: /* TODO: SSA */
2192 case 0x87: /* TODO: ESA */
2194 case 0x88: /* HTS -- Horizontal tab stop */
2195 term
.tabs
[term
.c
.x
] = 1;
2197 case 0x89: /* TODO: HTJ */
2198 case 0x8a: /* TODO: VTS */
2199 case 0x8b: /* TODO: PLD */
2200 case 0x8c: /* TODO: PLU */
2201 case 0x8d: /* TODO: RI */
2202 case 0x8e: /* TODO: SS2 */
2203 case 0x8f: /* TODO: SS3 */
2204 case 0x91: /* TODO: PU1 */
2205 case 0x92: /* TODO: PU2 */
2206 case 0x93: /* TODO: STS */
2207 case 0x94: /* TODO: CCH */
2208 case 0x95: /* TODO: MW */
2209 case 0x96: /* TODO: SPA */
2210 case 0x97: /* TODO: EPA */
2211 case 0x98: /* TODO: SOS */
2212 case 0x99: /* TODO: SGCI */
2214 case 0x9a: /* DECID -- Identify Terminal */
2215 ttywrite(vtiden
, strlen(vtiden
), 0);
2217 case 0x9b: /* TODO: CSI */
2218 case 0x9c: /* TODO: ST */
2220 case 0x90: /* DCS -- Device Control String */
2221 case 0x9d: /* OSC -- Operating System Command */
2222 case 0x9e: /* PM -- Privacy Message */
2223 case 0x9f: /* APC -- Application Program Command */
2224 tstrsequence(ascii
);
2227 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2228 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2232 * returns 1 when the sequence is finished and it hasn't to read
2233 * more characters for this sequence, otherwise 0
2236 eschandle(uchar ascii
)
2240 term
.esc
|= ESC_CSI
;
2243 term
.esc
|= ESC_TEST
;
2246 term
.esc
|= ESC_UTF8
;
2248 case 'P': /* DCS -- Device Control String */
2249 case '_': /* APC -- Application Program Command */
2250 case '^': /* PM -- Privacy Message */
2251 case ']': /* OSC -- Operating System Command */
2252 case 'k': /* old title set compatibility */
2253 tstrsequence(ascii
);
2255 case 'n': /* LS2 -- Locking shift 2 */
2256 case 'o': /* LS3 -- Locking shift 3 */
2257 term
.charset
= 2 + (ascii
- 'n');
2259 case '(': /* GZD4 -- set primary charset G0 */
2260 case ')': /* G1D4 -- set secondary charset G1 */
2261 case '*': /* G2D4 -- set tertiary charset G2 */
2262 case '+': /* G3D4 -- set quaternary charset G3 */
2263 term
.icharset
= ascii
- '(';
2264 term
.esc
|= ESC_ALTCHARSET
;
2266 case 'D': /* IND -- Linefeed */
2267 if (term
.c
.y
== term
.bot
) {
2268 tscrollup(term
.top
, 1);
2270 tmoveto(term
.c
.x
, term
.c
.y
+1);
2273 case 'E': /* NEL -- Next line */
2274 tnewline(1); /* always go to first col */
2276 case 'H': /* HTS -- Horizontal tab stop */
2277 term
.tabs
[term
.c
.x
] = 1;
2279 case 'M': /* RI -- Reverse index */
2280 if (term
.c
.y
== term
.top
) {
2281 tscrolldown(term
.top
, 1);
2283 tmoveto(term
.c
.x
, term
.c
.y
-1);
2286 case 'Z': /* DECID -- Identify Terminal */
2287 ttywrite(vtiden
, strlen(vtiden
), 0);
2289 case 'c': /* RIS -- Reset to inital state */
2294 case '=': /* DECPAM -- Application keypad */
2295 xsetmode(1, MODE_APPKEYPAD
);
2297 case '>': /* DECPNM -- Normal keypad */
2298 xsetmode(0, MODE_APPKEYPAD
);
2300 case '7': /* DECSC -- Save Cursor */
2301 tcursor(CURSOR_SAVE
);
2303 case '8': /* DECRC -- Restore Cursor */
2304 tcursor(CURSOR_LOAD
);
2306 case '\\': /* ST -- String Terminator */
2307 if (term
.esc
& ESC_STR_END
)
2311 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2312 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2326 control
= ISCONTROL(u
);
2327 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2331 len
= utf8encode(u
, c
);
2332 if (!control
&& (width
= wcwidth(u
)) == -1) {
2333 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2338 if (IS_SET(MODE_PRINT
))
2342 * STR sequence must be checked before anything else
2343 * because it uses all following characters until it
2344 * receives a ESC, a SUB, a ST or any other C1 control
2347 if (term
.esc
& ESC_STR
) {
2348 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2350 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2351 if (IS_SET(MODE_SIXEL
)) {
2352 /* TODO: render sixel */;
2353 term
.mode
&= ~MODE_SIXEL
;
2356 term
.esc
|= ESC_STR_END
;
2357 goto check_control_code
;
2361 if (IS_SET(MODE_SIXEL
)) {
2362 /* TODO: implement sixel mode */
2365 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2366 term
.mode
|= MODE_SIXEL
;
2368 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2370 * Here is a bug in terminals. If the user never sends
2371 * some code to stop the str or esc command, then st
2372 * will stop responding. But this is better than
2373 * silently failing with unknown characters. At least
2374 * then users will report back.
2376 * In the case users ever get fixed, here is the code:
2385 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2386 strescseq
.len
+= len
;
2392 * Actions of control codes must be performed as soon they arrive
2393 * because they can be embedded inside a control sequence, and
2394 * they must not cause conflicts with sequences.
2399 * control codes are not shown ever
2402 } else if (term
.esc
& ESC_START
) {
2403 if (term
.esc
& ESC_CSI
) {
2404 csiescseq
.buf
[csiescseq
.len
++] = u
;
2405 if (BETWEEN(u
, 0x40, 0x7E)
2406 || csiescseq
.len
>= \
2407 sizeof(csiescseq
.buf
)-1) {
2413 } else if (term
.esc
& ESC_UTF8
) {
2415 } else if (term
.esc
& ESC_ALTCHARSET
) {
2417 } else if (term
.esc
& ESC_TEST
) {
2422 /* sequence already finished */
2426 * All characters which form part of a sequence are not
2431 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2434 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2435 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2436 gp
->mode
|= ATTR_WRAP
;
2438 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2441 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2442 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2444 if (term
.c
.x
+width
> term
.col
) {
2446 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2449 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2452 gp
->mode
|= ATTR_WIDE
;
2453 if (term
.c
.x
+1 < term
.col
) {
2455 gp
[1].mode
= ATTR_WDUMMY
;
2458 if (term
.c
.x
+width
< term
.col
) {
2459 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2461 term
.c
.state
|= CURSOR_WRAPNEXT
;
2466 twrite(const char *buf
, int buflen
, int show_ctrl
)
2472 for (n
= 0; n
< buflen
; n
+= charsize
) {
2473 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2474 /* process a complete utf8 char */
2475 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2482 if (show_ctrl
&& ISCONTROL(u
)) {
2487 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2498 tresize(int col
, int row
)
2501 int minrow
= MIN(row
, term
.row
);
2502 int mincol
= MIN(col
, term
.col
);
2506 if (col
< 1 || row
< 1) {
2508 "tresize: error resizing to %dx%d\n", col
, row
);
2513 * slide screen to keep cursor where we expect it -
2514 * tscrollup would work here, but we can optimize to
2515 * memmove because we're freeing the earlier lines
2517 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2521 /* ensure that both src and dst are not NULL */
2523 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2524 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2526 for (i
+= row
; i
< term
.row
; i
++) {
2531 /* resize to new height */
2532 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2533 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2534 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2535 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2537 /* resize each row to new width, zero-pad if needed */
2538 for (i
= 0; i
< minrow
; i
++) {
2539 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2540 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2543 /* allocate any new rows */
2544 for (/* i = minrow */; i
< row
; i
++) {
2545 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2546 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2548 if (col
> term
.col
) {
2549 bp
= term
.tabs
+ term
.col
;
2551 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2552 while (--bp
> term
.tabs
&& !*bp
)
2554 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2557 /* update terminal size */
2560 /* reset scrolling region */
2561 tsetscroll(0, row
-1);
2562 /* make use of the LIMIT in tmoveto */
2563 tmoveto(term
.c
.x
, term
.c
.y
);
2564 /* Clearing both screens (it makes dirty all lines) */
2566 for (i
= 0; i
< 2; i
++) {
2567 if (mincol
< col
&& 0 < minrow
) {
2568 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2570 if (0 < col
&& minrow
< row
) {
2571 tclearregion(0, minrow
, col
- 1, row
- 1);
2574 tcursor(CURSOR_LOAD
);
2586 drawregion(int x1
, int y1
, int x2
, int y2
)
2589 for (y
= y1
; y
< y2
; y
++) {
2594 xdrawline(term
.line
[y
], x1
, y
, x2
);
2606 /* adjust cursor position */
2607 LIMIT(term
.ocx
, 0, term
.col
-1);
2608 LIMIT(term
.ocy
, 0, term
.row
-1);
2609 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2611 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2614 drawregion(0, 0, term
.col
, term
.row
);
2615 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2616 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2617 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;