Xinqi Bao's Git
dae7b91bce03a1b8dd127c81abcae37cc0d2d734
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
39 #define ESC_BUF_SIZ (128*UTF_SIZ)
40 #define ESC_ARG_SIZ 16
41 #define STR_BUF_SIZ ESC_BUF_SIZ
42 #define STR_ARG_SIZ ESC_ARG_SIZ
45 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
46 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
47 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
48 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
49 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
52 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
54 enum cursor_movement
{
78 ESC_STR
= 4, /* OSC, PM, APC */
80 ESC_STR_END
= 16, /* a final string was encountered */
81 ESC_TEST
= 32, /* Enter in test mode */
86 /* CSI Escape sequence structs */
87 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
89 char buf
[ESC_BUF_SIZ
]; /* raw string */
90 int len
; /* raw string length */
93 int narg
; /* nb of args */
97 /* STR Escape sequence structs */
98 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
100 char type
; /* ESC type ... */
101 char buf
[STR_BUF_SIZ
]; /* raw string */
102 int len
; /* raw string length */
103 char *args
[STR_ARG_SIZ
];
104 int narg
; /* nb of args */
108 static void execsh(char **);
109 static void stty(char **);
110 static void sigchld(int);
112 static void csidump(void);
113 static void csihandle(void);
114 static void csiparse(void);
115 static void csireset(void);
116 static int eschandle(uchar
);
117 static void strdump(void);
118 static void strhandle(void);
119 static void strparse(void);
120 static void strreset(void);
122 static void tprinter(char *, size_t);
123 static void tdumpsel(void);
124 static void tdumpline(int);
125 static void tdump(void);
126 static void tclearregion(int, int, int, int);
127 static void tcursor(int);
128 static void tdeletechar(int);
129 static void tdeleteline(int);
130 static void tinsertblank(int);
131 static void tinsertblankline(int);
132 static int tlinelen(int);
133 static void tmoveto(int, int);
134 static void tmoveato(int, int);
135 static void tnewline(int);
136 static void tputtab(int);
137 static void tputc(Rune
);
138 static void treset(void);
139 static void tscrollup(int, int);
140 static void tscrolldown(int, int);
141 static void tsetattr(int *, int);
142 static void tsetchar(Rune
, Glyph
*, int, int);
143 static void tsetscroll(int, int);
144 static void tswapscreen(void);
145 static void tsetmode(int, int, int *, int);
146 static int twrite(const char *, int, int);
147 static void tfulldirt(void);
148 static void tcontrolcode(uchar
);
149 static void tdectest(char );
150 static void tdefutf8(char);
151 static int32_t tdefcolor(int *, int *, int);
152 static void tdeftran(char);
153 static void tstrsequence(uchar
);
155 static void selscroll(int, int);
156 static void selsnap(int *, int *, int);
158 static Rune
utf8decodebyte(char, size_t *);
159 static char utf8encodebyte(Rune
, size_t);
160 static char *utf8strchr(char *s
, Rune u
);
161 static size_t utf8validate(Rune
*, size_t);
163 static char *base64dec(const char *);
165 static ssize_t
xwrite(int, const char *, size_t);
172 int oldbutton
= 3; /* button event on startup: 3 = release */
174 static CSIEscape csiescseq
;
175 static STREscape strescseq
;
178 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
179 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
180 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
181 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
184 xwrite(int fd
, const char *s
, size_t len
)
190 r
= write(fd
, s
, len
);
203 void *p
= malloc(len
);
206 die("Out of memory\n");
212 xrealloc(void *p
, size_t len
)
214 if ((p
= realloc(p
, len
)) == NULL
)
215 die("Out of memory\n");
223 if ((s
= strdup(s
)) == NULL
)
224 die("Out of memory\n");
230 utf8decode(const char *c
, Rune
*u
, size_t clen
)
232 size_t i
, j
, len
, type
;
238 udecoded
= utf8decodebyte(c
[0], &len
);
239 if (!BETWEEN(len
, 1, UTF_SIZ
))
241 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
242 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
249 utf8validate(u
, len
);
255 utf8decodebyte(char c
, size_t *i
)
257 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
258 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
259 return (uchar
)c
& ~utfmask
[*i
];
265 utf8encode(Rune u
, char *c
)
269 len
= utf8validate(&u
, 0);
273 for (i
= len
- 1; i
!= 0; --i
) {
274 c
[i
] = utf8encodebyte(u
, 0);
277 c
[0] = utf8encodebyte(u
, len
);
283 utf8encodebyte(Rune u
, size_t i
)
285 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
289 utf8strchr(char *s
, Rune u
)
295 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
296 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
306 utf8validate(Rune
*u
, size_t i
)
308 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
310 for (i
= 1; *u
> utfmax
[i
]; ++i
)
316 static const char base64_digits
[] = {
317 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
318 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
319 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
320 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
321 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
322 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
323 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
324 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
325 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
326 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
327 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
328 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
332 base64dec_getc(const char **src
)
334 while (**src
&& !isprint(**src
)) (*src
)++;
339 base64dec(const char *src
)
341 size_t in_len
= strlen(src
);
345 in_len
+= 4 - (in_len
% 4);
346 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
348 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
349 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
350 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
351 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
353 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
356 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
359 *dst
++ = ((c
& 0x03) << 6) | d
;
368 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
369 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
374 sel
.clipboard
= NULL
;
382 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
385 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
396 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
397 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
398 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
400 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
401 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
403 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
404 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
406 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
407 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
409 /* expand selection over line breaks */
410 if (sel
.type
== SEL_RECTANGULAR
)
412 i
= tlinelen(sel
.nb
.y
);
415 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
416 sel
.ne
.x
= term
.col
- 1;
420 selected(int x
, int y
)
422 if (sel
.mode
== SEL_EMPTY
)
425 if (sel
.type
== SEL_RECTANGULAR
)
426 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
427 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
429 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
430 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
431 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
435 selsnap(int *x
, int *y
, int direction
)
437 int newx
, newy
, xt
, yt
;
438 int delim
, prevdelim
;
444 * Snap around if the word wraps around at the end or
445 * beginning of a line.
447 prevgp
= &term
.line
[*y
][*x
];
448 prevdelim
= ISDELIM(prevgp
->u
);
450 newx
= *x
+ direction
;
452 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
454 newx
= (newx
+ term
.col
) % term
.col
;
455 if (!BETWEEN(newy
, 0, term
.row
- 1))
461 yt
= newy
, xt
= newx
;
462 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
466 if (newx
>= tlinelen(newy
))
469 gp
= &term
.line
[newy
][newx
];
470 delim
= ISDELIM(gp
->u
);
471 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
472 || (delim
&& gp
->u
!= prevgp
->u
)))
483 * Snap around if the the previous line or the current one
484 * has set ATTR_WRAP at its end. Then the whole next or
485 * previous line will be selected.
487 *x
= (direction
< 0) ? 0 : term
.col
- 1;
489 for (; *y
> 0; *y
+= direction
) {
490 if (!(term
.line
[*y
-1][term
.col
-1].mode
495 } else if (direction
> 0) {
496 for (; *y
< term
.row
-1; *y
+= direction
) {
497 if (!(term
.line
[*y
][term
.col
-1].mode
511 int y
, bufsize
, lastx
, linelen
;
517 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
518 ptr
= str
= xmalloc(bufsize
);
520 /* append every set & selected glyph to the selection */
521 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
522 if ((linelen
= tlinelen(y
)) == 0) {
527 if (sel
.type
== SEL_RECTANGULAR
) {
528 gp
= &term
.line
[y
][sel
.nb
.x
];
531 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
532 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
534 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
535 while (last
>= gp
&& last
->u
== ' ')
538 for ( ; gp
<= last
; ++gp
) {
539 if (gp
->mode
& ATTR_WDUMMY
)
542 ptr
+= utf8encode(gp
->u
, ptr
);
546 * Copy and pasting of line endings is inconsistent
547 * in the inconsistent terminal and GUI world.
548 * The best solution seems like to produce '\n' when
549 * something is copied from st and convert '\n' to
550 * '\r', when something to be pasted is received by
552 * FIXME: Fix the computer world.
554 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
568 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
572 die(const char *errstr
, ...)
576 va_start(ap
, errstr
);
577 vfprintf(stderr
, errstr
, ap
);
586 const struct passwd
*pw
;
589 if ((pw
= getpwuid(getuid())) == NULL
) {
591 die("getpwuid:%s\n", strerror(errno
));
593 die("who are you?\n");
596 if ((sh
= getenv("SHELL")) == NULL
)
597 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
605 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
610 setenv("LOGNAME", pw
->pw_name
, 1);
611 setenv("USER", pw
->pw_name
, 1);
612 setenv("SHELL", sh
, 1);
613 setenv("HOME", pw
->pw_dir
, 1);
614 setenv("TERM", termname
, 1);
616 signal(SIGCHLD
, SIG_DFL
);
617 signal(SIGHUP
, SIG_DFL
);
618 signal(SIGINT
, SIG_DFL
);
619 signal(SIGQUIT
, SIG_DFL
);
620 signal(SIGTERM
, SIG_DFL
);
621 signal(SIGALRM
, SIG_DFL
);
633 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
634 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
639 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
640 die("child finished with error '%d'\n", stat
);
648 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
651 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
652 die("incorrect stty parameters\n");
653 memcpy(cmd
, stty_args
, n
);
655 siz
= sizeof(cmd
) - n
;
656 for (p
= args
; p
&& (s
= *p
); ++p
) {
657 if ((n
= strlen(s
)) > siz
-1)
658 die("stty parameter length too long\n");
665 if (system(cmd
) != 0)
666 perror("Couldn't call stty");
670 ttynew(char *line
, char *out
, char **args
)
675 term
.mode
|= MODE_PRINT
;
676 iofd
= (!strcmp(out
, "-")) ?
677 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
679 fprintf(stderr
, "Error opening %s:%s\n",
680 out
, strerror(errno
));
685 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
686 die("open line failed: %s\n", strerror(errno
));
692 /* seems to work fine on linux, openbsd and freebsd */
693 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
694 die("openpty failed: %s\n", strerror(errno
));
696 switch (pid
= fork()) {
698 die("fork failed\n");
702 setsid(); /* create a new process group */
706 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
707 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
715 signal(SIGCHLD
, sigchld
);
723 static char buf
[BUFSIZ
];
724 static int buflen
= 0;
728 /* append read bytes to unprocessed bytes */
729 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
730 die("Couldn't read from shell: %s\n", strerror(errno
));
733 written
= twrite(buf
, buflen
, 0);
735 /* keep any uncomplete utf8 char for the next call */
737 memmove(buf
, buf
+ written
, buflen
);
743 ttywrite(const char *s
, size_t n
)
750 * Remember that we are using a pty, which might be a modem line.
751 * Writing too much will clog the line. That's why we are doing this
753 * FIXME: Migrate the world to Plan 9.
761 /* Check if we can write. */
762 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
765 die("select failed: %s\n", strerror(errno
));
767 if (FD_ISSET(cmdfd
, &wfd
)) {
769 * Only write the bytes written by ttywrite() or the
770 * default of 256. This seems to be a reasonable value
771 * for a serial line. Bigger values might clog the I/O.
773 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
777 * We weren't able to write out everything.
778 * This means the buffer is getting full
786 /* All bytes have been written. */
790 if (FD_ISSET(cmdfd
, &rfd
))
796 die("write error on tty: %s\n", strerror(errno
));
800 ttysend(char *s
, size_t n
)
803 if (IS_SET(MODE_ECHO
))
808 ttyresize(int tw
, int th
)
816 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
817 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
825 for (i
= 0; i
< term
.row
-1; i
++) {
826 for (j
= 0; j
< term
.col
-1; j
++) {
827 if (term
.line
[i
][j
].mode
& attr
)
836 tsetdirt(int top
, int bot
)
840 LIMIT(top
, 0, term
.row
-1);
841 LIMIT(bot
, 0, term
.row
-1);
843 for (i
= top
; i
<= bot
; i
++)
848 tsetdirtattr(int attr
)
852 for (i
= 0; i
< term
.row
-1; i
++) {
853 for (j
= 0; j
< term
.col
-1; j
++) {
854 if (term
.line
[i
][j
].mode
& attr
) {
865 tsetdirt(0, term
.row
-1);
872 int alt
= IS_SET(MODE_ALTSCREEN
);
874 if (mode
== CURSOR_SAVE
) {
876 } else if (mode
== CURSOR_LOAD
) {
878 tmoveto(c
[alt
].x
, c
[alt
].y
);
891 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
893 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
894 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
897 term
.bot
= term
.row
- 1;
898 term
.mode
= MODE_WRAP
|MODE_UTF8
;
899 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
902 for (i
= 0; i
< 2; i
++) {
904 tcursor(CURSOR_SAVE
);
905 tclearregion(0, 0, term
.col
-1, term
.row
-1);
911 tnew(int col
, int row
)
913 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
923 Line
*tmp
= term
.line
;
925 term
.line
= term
.alt
;
927 term
.mode
^= MODE_ALTSCREEN
;
932 tscrolldown(int orig
, int n
)
937 LIMIT(n
, 0, term
.bot
-orig
+1);
939 tsetdirt(orig
, term
.bot
-n
);
940 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
942 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
944 term
.line
[i
] = term
.line
[i
-n
];
945 term
.line
[i
-n
] = temp
;
952 tscrollup(int orig
, int n
)
957 LIMIT(n
, 0, term
.bot
-orig
+1);
959 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
960 tsetdirt(orig
+n
, term
.bot
);
962 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
964 term
.line
[i
] = term
.line
[i
+n
];
965 term
.line
[i
+n
] = temp
;
972 selscroll(int orig
, int n
)
977 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
978 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
982 if (sel
.type
== SEL_RECTANGULAR
) {
983 if (sel
.ob
.y
< term
.top
)
985 if (sel
.oe
.y
> term
.bot
)
988 if (sel
.ob
.y
< term
.top
) {
992 if (sel
.oe
.y
> term
.bot
) {
1002 tnewline(int first_col
)
1006 if (y
== term
.bot
) {
1007 tscrollup(term
.top
, 1);
1011 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1017 char *p
= csiescseq
.buf
, *np
;
1026 csiescseq
.buf
[csiescseq
.len
] = '\0';
1027 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1029 v
= strtol(p
, &np
, 10);
1032 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1034 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1036 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1040 csiescseq
.mode
[0] = *p
++;
1041 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1044 /* for absolute user moves, when decom is set */
1046 tmoveato(int x
, int y
)
1048 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1052 tmoveto(int x
, int y
)
1056 if (term
.c
.state
& CURSOR_ORIGIN
) {
1061 maxy
= term
.row
- 1;
1063 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1064 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1065 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1069 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1071 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1072 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1073 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1074 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1075 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1076 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1077 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1078 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1079 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1083 * The table is proudly stolen from rxvt.
1085 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1086 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1087 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1089 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1090 if (x
+1 < term
.col
) {
1091 term
.line
[y
][x
+1].u
= ' ';
1092 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1094 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1095 term
.line
[y
][x
-1].u
= ' ';
1096 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1100 term
.line
[y
][x
] = *attr
;
1101 term
.line
[y
][x
].u
= u
;
1105 tclearregion(int x1
, int y1
, int x2
, int y2
)
1111 temp
= x1
, x1
= x2
, x2
= temp
;
1113 temp
= y1
, y1
= y2
, y2
= temp
;
1115 LIMIT(x1
, 0, term
.col
-1);
1116 LIMIT(x2
, 0, term
.col
-1);
1117 LIMIT(y1
, 0, term
.row
-1);
1118 LIMIT(y2
, 0, term
.row
-1);
1120 for (y
= y1
; y
<= y2
; y
++) {
1122 for (x
= x1
; x
<= x2
; x
++) {
1123 gp
= &term
.line
[y
][x
];
1126 gp
->fg
= term
.c
.attr
.fg
;
1127 gp
->bg
= term
.c
.attr
.bg
;
1140 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1144 size
= term
.col
- src
;
1145 line
= term
.line
[term
.c
.y
];
1147 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1148 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1157 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1161 size
= term
.col
- dst
;
1162 line
= term
.line
[term
.c
.y
];
1164 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1165 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1169 tinsertblankline(int n
)
1171 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1172 tscrolldown(term
.c
.y
, n
);
1178 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1179 tscrollup(term
.c
.y
, n
);
1183 tdefcolor(int *attr
, int *npar
, int l
)
1188 switch (attr
[*npar
+ 1]) {
1189 case 2: /* direct color in RGB space */
1190 if (*npar
+ 4 >= l
) {
1192 "erresc(38): Incorrect number of parameters (%d)\n",
1196 r
= attr
[*npar
+ 2];
1197 g
= attr
[*npar
+ 3];
1198 b
= attr
[*npar
+ 4];
1200 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1201 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1204 idx
= TRUECOLOR(r
, g
, b
);
1206 case 5: /* indexed color */
1207 if (*npar
+ 2 >= l
) {
1209 "erresc(38): Incorrect number of parameters (%d)\n",
1214 if (!BETWEEN(attr
[*npar
], 0, 255))
1215 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1219 case 0: /* implemented defined (only foreground) */
1220 case 1: /* transparent */
1221 case 3: /* direct color in CMY space */
1222 case 4: /* direct color in CMYK space */
1225 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1233 tsetattr(int *attr
, int l
)
1238 for (i
= 0; i
< l
; i
++) {
1241 term
.c
.attr
.mode
&= ~(
1250 term
.c
.attr
.fg
= defaultfg
;
1251 term
.c
.attr
.bg
= defaultbg
;
1254 term
.c
.attr
.mode
|= ATTR_BOLD
;
1257 term
.c
.attr
.mode
|= ATTR_FAINT
;
1260 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1263 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1265 case 5: /* slow blink */
1267 case 6: /* rapid blink */
1268 term
.c
.attr
.mode
|= ATTR_BLINK
;
1271 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1274 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1277 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1280 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1283 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1286 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1289 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1292 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1295 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1298 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1301 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1302 term
.c
.attr
.fg
= idx
;
1305 term
.c
.attr
.fg
= defaultfg
;
1308 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1309 term
.c
.attr
.bg
= idx
;
1312 term
.c
.attr
.bg
= defaultbg
;
1315 if (BETWEEN(attr
[i
], 30, 37)) {
1316 term
.c
.attr
.fg
= attr
[i
] - 30;
1317 } else if (BETWEEN(attr
[i
], 40, 47)) {
1318 term
.c
.attr
.bg
= attr
[i
] - 40;
1319 } else if (BETWEEN(attr
[i
], 90, 97)) {
1320 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1321 } else if (BETWEEN(attr
[i
], 100, 107)) {
1322 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1325 "erresc(default): gfx attr %d unknown\n",
1326 attr
[i
]), csidump();
1334 tsetscroll(int t
, int b
)
1338 LIMIT(t
, 0, term
.row
-1);
1339 LIMIT(b
, 0, term
.row
-1);
1350 tsetmode(int priv
, int set
, int *args
, int narg
)
1355 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1358 case 1: /* DECCKM -- Cursor key */
1359 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1361 case 5: /* DECSCNM -- Reverse video */
1363 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1364 if (mode
!= term
.mode
)
1367 case 6: /* DECOM -- Origin */
1368 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1371 case 7: /* DECAWM -- Auto wrap */
1372 MODBIT(term
.mode
, set
, MODE_WRAP
);
1374 case 0: /* Error (IGNORED) */
1375 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1376 case 3: /* DECCOLM -- Column (IGNORED) */
1377 case 4: /* DECSCLM -- Scroll (IGNORED) */
1378 case 8: /* DECARM -- Auto repeat (IGNORED) */
1379 case 18: /* DECPFF -- Printer feed (IGNORED) */
1380 case 19: /* DECPEX -- Printer extent (IGNORED) */
1381 case 42: /* DECNRCM -- National characters (IGNORED) */
1382 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1384 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1385 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1387 case 9: /* X10 mouse compatibility mode */
1388 xsetpointermotion(0);
1389 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1390 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1392 case 1000: /* 1000: report button press */
1393 xsetpointermotion(0);
1394 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1395 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1397 case 1002: /* 1002: report motion on button press */
1398 xsetpointermotion(0);
1399 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1400 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1402 case 1003: /* 1003: enable all mouse motions */
1403 xsetpointermotion(set
);
1404 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1405 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1407 case 1004: /* 1004: send focus events to tty */
1408 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1410 case 1006: /* 1006: extended reporting mode */
1411 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1414 MODBIT(term
.mode
, set
, MODE_8BIT
);
1416 case 1049: /* swap screen & set/restore cursor as xterm */
1417 if (!allowaltscreen
)
1419 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1421 case 47: /* swap screen */
1423 if (!allowaltscreen
)
1425 alt
= IS_SET(MODE_ALTSCREEN
);
1427 tclearregion(0, 0, term
.col
-1,
1430 if (set
^ alt
) /* set is always 1 or 0 */
1436 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1438 case 2004: /* 2004: bracketed paste mode */
1439 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1441 /* Not implemented mouse modes. See comments there. */
1442 case 1001: /* mouse highlight mode; can hang the
1443 terminal by design when implemented. */
1444 case 1005: /* UTF-8 mouse mode; will confuse
1445 applications not supporting UTF-8
1447 case 1015: /* urxvt mangled mouse mode; incompatible
1448 and can be mistaken for other control
1452 "erresc: unknown private set/reset mode %d\n",
1458 case 0: /* Error (IGNORED) */
1460 case 2: /* KAM -- keyboard action */
1461 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1463 case 4: /* IRM -- Insertion-replacement */
1464 MODBIT(term
.mode
, set
, MODE_INSERT
);
1466 case 12: /* SRM -- Send/Receive */
1467 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1469 case 20: /* LNM -- Linefeed/new line */
1470 MODBIT(term
.mode
, set
, MODE_CRLF
);
1474 "erresc: unknown set/reset mode %d\n",
1488 switch (csiescseq
.mode
[0]) {
1491 fprintf(stderr
, "erresc: unknown csi ");
1495 case '@': /* ICH -- Insert <n> blank char */
1496 DEFAULT(csiescseq
.arg
[0], 1);
1497 tinsertblank(csiescseq
.arg
[0]);
1499 case 'A': /* CUU -- Cursor <n> Up */
1500 DEFAULT(csiescseq
.arg
[0], 1);
1501 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1503 case 'B': /* CUD -- Cursor <n> Down */
1504 case 'e': /* VPR --Cursor <n> Down */
1505 DEFAULT(csiescseq
.arg
[0], 1);
1506 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1508 case 'i': /* MC -- Media Copy */
1509 switch (csiescseq
.arg
[0]) {
1514 tdumpline(term
.c
.y
);
1520 term
.mode
&= ~MODE_PRINT
;
1523 term
.mode
|= MODE_PRINT
;
1527 case 'c': /* DA -- Device Attributes */
1528 if (csiescseq
.arg
[0] == 0)
1529 ttywrite(vtiden
, strlen(vtiden
));
1531 case 'C': /* CUF -- Cursor <n> Forward */
1532 case 'a': /* HPR -- Cursor <n> Forward */
1533 DEFAULT(csiescseq
.arg
[0], 1);
1534 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1536 case 'D': /* CUB -- Cursor <n> Backward */
1537 DEFAULT(csiescseq
.arg
[0], 1);
1538 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1540 case 'E': /* CNL -- Cursor <n> Down and first col */
1541 DEFAULT(csiescseq
.arg
[0], 1);
1542 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1544 case 'F': /* CPL -- Cursor <n> Up and first col */
1545 DEFAULT(csiescseq
.arg
[0], 1);
1546 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1548 case 'g': /* TBC -- Tabulation clear */
1549 switch (csiescseq
.arg
[0]) {
1550 case 0: /* clear current tab stop */
1551 term
.tabs
[term
.c
.x
] = 0;
1553 case 3: /* clear all the tabs */
1554 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1560 case 'G': /* CHA -- Move to <col> */
1562 DEFAULT(csiescseq
.arg
[0], 1);
1563 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1565 case 'H': /* CUP -- Move to <row> <col> */
1567 DEFAULT(csiescseq
.arg
[0], 1);
1568 DEFAULT(csiescseq
.arg
[1], 1);
1569 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1571 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1572 DEFAULT(csiescseq
.arg
[0], 1);
1573 tputtab(csiescseq
.arg
[0]);
1575 case 'J': /* ED -- Clear screen */
1577 switch (csiescseq
.arg
[0]) {
1579 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1580 if (term
.c
.y
< term
.row
-1) {
1581 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1587 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1588 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1591 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1597 case 'K': /* EL -- Clear line */
1598 switch (csiescseq
.arg
[0]) {
1600 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1604 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1607 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1611 case 'S': /* SU -- Scroll <n> line up */
1612 DEFAULT(csiescseq
.arg
[0], 1);
1613 tscrollup(term
.top
, csiescseq
.arg
[0]);
1615 case 'T': /* SD -- Scroll <n> line down */
1616 DEFAULT(csiescseq
.arg
[0], 1);
1617 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1619 case 'L': /* IL -- Insert <n> blank lines */
1620 DEFAULT(csiescseq
.arg
[0], 1);
1621 tinsertblankline(csiescseq
.arg
[0]);
1623 case 'l': /* RM -- Reset Mode */
1624 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1626 case 'M': /* DL -- Delete <n> lines */
1627 DEFAULT(csiescseq
.arg
[0], 1);
1628 tdeleteline(csiescseq
.arg
[0]);
1630 case 'X': /* ECH -- Erase <n> char */
1631 DEFAULT(csiescseq
.arg
[0], 1);
1632 tclearregion(term
.c
.x
, term
.c
.y
,
1633 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1635 case 'P': /* DCH -- Delete <n> char */
1636 DEFAULT(csiescseq
.arg
[0], 1);
1637 tdeletechar(csiescseq
.arg
[0]);
1639 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1640 DEFAULT(csiescseq
.arg
[0], 1);
1641 tputtab(-csiescseq
.arg
[0]);
1643 case 'd': /* VPA -- Move to <row> */
1644 DEFAULT(csiescseq
.arg
[0], 1);
1645 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1647 case 'h': /* SM -- Set terminal mode */
1648 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1650 case 'm': /* SGR -- Terminal attribute (color) */
1651 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1653 case 'n': /* DSR – Device Status Report (cursor position) */
1654 if (csiescseq
.arg
[0] == 6) {
1655 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1656 term
.c
.y
+1, term
.c
.x
+1);
1660 case 'r': /* DECSTBM -- Set Scrolling Region */
1661 if (csiescseq
.priv
) {
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 DEFAULT(csiescseq
.arg
[1], term
.row
);
1666 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1670 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1671 tcursor(CURSOR_SAVE
);
1673 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1674 tcursor(CURSOR_LOAD
);
1677 switch (csiescseq
.mode
[1]) {
1678 case 'q': /* DECSCUSR -- Set Cursor Style */
1679 if (xsetcursor(csiescseq
.arg
[0]))
1695 fprintf(stderr
, "ESC[");
1696 for (i
= 0; i
< csiescseq
.len
; i
++) {
1697 c
= csiescseq
.buf
[i
] & 0xff;
1700 } else if (c
== '\n') {
1701 fprintf(stderr
, "(\\n)");
1702 } else if (c
== '\r') {
1703 fprintf(stderr
, "(\\r)");
1704 } else if (c
== 0x1b) {
1705 fprintf(stderr
, "(\\e)");
1707 fprintf(stderr
, "(%02x)", c
);
1716 memset(&csiescseq
, 0, sizeof(csiescseq
));
1725 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1727 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1729 switch (strescseq
.type
) {
1730 case ']': /* OSC -- Operating System Command */
1736 xsettitle(strescseq
.args
[1]);
1742 dec
= base64dec(strescseq
.args
[2]);
1747 fprintf(stderr
, "erresc: invalid base64\n");
1751 case 4: /* color set */
1754 p
= strescseq
.args
[2];
1756 case 104: /* color reset, here p = NULL */
1757 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1758 if (xsetcolorname(j
, p
)) {
1759 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1762 * TODO if defaultbg color is changed, borders
1770 case 'k': /* old title set compatibility */
1771 xsettitle(strescseq
.args
[0]);
1773 case 'P': /* DCS -- Device Control String */
1774 term
.mode
|= ESC_DCS
;
1775 case '_': /* APC -- Application Program Command */
1776 case '^': /* PM -- Privacy Message */
1780 fprintf(stderr
, "erresc: unknown str ");
1788 char *p
= strescseq
.buf
;
1791 strescseq
.buf
[strescseq
.len
] = '\0';
1796 while (strescseq
.narg
< STR_ARG_SIZ
) {
1797 strescseq
.args
[strescseq
.narg
++] = p
;
1798 while ((c
= *p
) != ';' && c
!= '\0')
1812 fprintf(stderr
, "ESC%c", strescseq
.type
);
1813 for (i
= 0; i
< strescseq
.len
; i
++) {
1814 c
= strescseq
.buf
[i
] & 0xff;
1818 } else if (isprint(c
)) {
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
);
1830 fprintf(stderr
, "ESC\\\n");
1836 memset(&strescseq
, 0, sizeof(strescseq
));
1840 sendbreak(const Arg
*arg
)
1842 if (tcsendbreak(cmdfd
, 0))
1843 perror("Error sending break");
1847 tprinter(char *s
, size_t len
)
1849 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1850 perror("Error writing to output file");
1857 iso14755(const Arg
*arg
)
1860 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1861 unsigned long utf32
;
1863 if (!(p
= popen(ISO14755CMD
, "r")))
1866 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1869 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1871 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1872 (*e
!= '\n' && *e
!= '\0'))
1875 ttysend(uc
, utf8encode(utf32
, uc
));
1879 toggleprinter(const Arg
*arg
)
1881 term
.mode
^= MODE_PRINT
;
1885 printscreen(const Arg
*arg
)
1891 printsel(const Arg
*arg
)
1901 if ((ptr
= getsel())) {
1902 tprinter(ptr
, strlen(ptr
));
1913 bp
= &term
.line
[n
][0];
1914 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1915 if (bp
!= end
|| bp
->u
!= ' ') {
1916 for ( ;bp
<= end
; ++bp
)
1917 tprinter(buf
, utf8encode(bp
->u
, buf
));
1927 for (i
= 0; i
< term
.row
; ++i
)
1937 while (x
< term
.col
&& n
--)
1938 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1941 while (x
> 0 && n
++)
1942 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1945 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1949 tdefutf8(char ascii
)
1952 term
.mode
|= MODE_UTF8
;
1953 else if (ascii
== '@')
1954 term
.mode
&= ~MODE_UTF8
;
1958 tdeftran(char ascii
)
1960 static char cs
[] = "0B";
1961 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
1964 if ((p
= strchr(cs
, ascii
)) == NULL
) {
1965 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
1967 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
1976 if (c
== '8') { /* DEC screen alignment test. */
1977 for (x
= 0; x
< term
.col
; ++x
) {
1978 for (y
= 0; y
< term
.row
; ++y
)
1979 tsetchar('E', &term
.c
.attr
, x
, y
);
1985 tstrsequence(uchar c
)
1990 case 0x90: /* DCS -- Device Control String */
1992 term
.esc
|= ESC_DCS
;
1994 case 0x9f: /* APC -- Application Program Command */
1997 case 0x9e: /* PM -- Privacy Message */
2000 case 0x9d: /* OSC -- Operating System Command */
2005 term
.esc
|= ESC_STR
;
2009 tcontrolcode(uchar ascii
)
2016 tmoveto(term
.c
.x
-1, term
.c
.y
);
2019 tmoveto(0, term
.c
.y
);
2024 /* go to first col if the mode is set */
2025 tnewline(IS_SET(MODE_CRLF
));
2027 case '\a': /* BEL */
2028 if (term
.esc
& ESC_STR_END
) {
2029 /* backwards compatibility to xterm */
2035 case '\033': /* ESC */
2037 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2038 term
.esc
|= ESC_START
;
2040 case '\016': /* SO (LS1 -- Locking shift 1) */
2041 case '\017': /* SI (LS0 -- Locking shift 0) */
2042 term
.charset
= 1 - (ascii
- '\016');
2044 case '\032': /* SUB */
2045 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2046 case '\030': /* CAN */
2049 case '\005': /* ENQ (IGNORED) */
2050 case '\000': /* NUL (IGNORED) */
2051 case '\021': /* XON (IGNORED) */
2052 case '\023': /* XOFF (IGNORED) */
2053 case 0177: /* DEL (IGNORED) */
2055 case 0x80: /* TODO: PAD */
2056 case 0x81: /* TODO: HOP */
2057 case 0x82: /* TODO: BPH */
2058 case 0x83: /* TODO: NBH */
2059 case 0x84: /* TODO: IND */
2061 case 0x85: /* NEL -- Next line */
2062 tnewline(1); /* always go to first col */
2064 case 0x86: /* TODO: SSA */
2065 case 0x87: /* TODO: ESA */
2067 case 0x88: /* HTS -- Horizontal tab stop */
2068 term
.tabs
[term
.c
.x
] = 1;
2070 case 0x89: /* TODO: HTJ */
2071 case 0x8a: /* TODO: VTS */
2072 case 0x8b: /* TODO: PLD */
2073 case 0x8c: /* TODO: PLU */
2074 case 0x8d: /* TODO: RI */
2075 case 0x8e: /* TODO: SS2 */
2076 case 0x8f: /* TODO: SS3 */
2077 case 0x91: /* TODO: PU1 */
2078 case 0x92: /* TODO: PU2 */
2079 case 0x93: /* TODO: STS */
2080 case 0x94: /* TODO: CCH */
2081 case 0x95: /* TODO: MW */
2082 case 0x96: /* TODO: SPA */
2083 case 0x97: /* TODO: EPA */
2084 case 0x98: /* TODO: SOS */
2085 case 0x99: /* TODO: SGCI */
2087 case 0x9a: /* DECID -- Identify Terminal */
2088 ttywrite(vtiden
, strlen(vtiden
));
2090 case 0x9b: /* TODO: CSI */
2091 case 0x9c: /* TODO: ST */
2093 case 0x90: /* DCS -- Device Control String */
2094 case 0x9d: /* OSC -- Operating System Command */
2095 case 0x9e: /* PM -- Privacy Message */
2096 case 0x9f: /* APC -- Application Program Command */
2097 tstrsequence(ascii
);
2100 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2101 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2105 * returns 1 when the sequence is finished and it hasn't to read
2106 * more characters for this sequence, otherwise 0
2109 eschandle(uchar ascii
)
2113 term
.esc
|= ESC_CSI
;
2116 term
.esc
|= ESC_TEST
;
2119 term
.esc
|= ESC_UTF8
;
2121 case 'P': /* DCS -- Device Control String */
2122 case '_': /* APC -- Application Program Command */
2123 case '^': /* PM -- Privacy Message */
2124 case ']': /* OSC -- Operating System Command */
2125 case 'k': /* old title set compatibility */
2126 tstrsequence(ascii
);
2128 case 'n': /* LS2 -- Locking shift 2 */
2129 case 'o': /* LS3 -- Locking shift 3 */
2130 term
.charset
= 2 + (ascii
- 'n');
2132 case '(': /* GZD4 -- set primary charset G0 */
2133 case ')': /* G1D4 -- set secondary charset G1 */
2134 case '*': /* G2D4 -- set tertiary charset G2 */
2135 case '+': /* G3D4 -- set quaternary charset G3 */
2136 term
.icharset
= ascii
- '(';
2137 term
.esc
|= ESC_ALTCHARSET
;
2139 case 'D': /* IND -- Linefeed */
2140 if (term
.c
.y
== term
.bot
) {
2141 tscrollup(term
.top
, 1);
2143 tmoveto(term
.c
.x
, term
.c
.y
+1);
2146 case 'E': /* NEL -- Next line */
2147 tnewline(1); /* always go to first col */
2149 case 'H': /* HTS -- Horizontal tab stop */
2150 term
.tabs
[term
.c
.x
] = 1;
2152 case 'M': /* RI -- Reverse index */
2153 if (term
.c
.y
== term
.top
) {
2154 tscrolldown(term
.top
, 1);
2156 tmoveto(term
.c
.x
, term
.c
.y
-1);
2159 case 'Z': /* DECID -- Identify Terminal */
2160 ttywrite(vtiden
, strlen(vtiden
));
2162 case 'c': /* RIS -- Reset to inital state */
2167 case '=': /* DECPAM -- Application keypad */
2168 term
.mode
|= MODE_APPKEYPAD
;
2170 case '>': /* DECPNM -- Normal keypad */
2171 term
.mode
&= ~MODE_APPKEYPAD
;
2173 case '7': /* DECSC -- Save Cursor */
2174 tcursor(CURSOR_SAVE
);
2176 case '8': /* DECRC -- Restore Cursor */
2177 tcursor(CURSOR_LOAD
);
2179 case '\\': /* ST -- String Terminator */
2180 if (term
.esc
& ESC_STR_END
)
2184 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2185 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2199 control
= ISCONTROL(u
);
2200 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2204 len
= utf8encode(u
, c
);
2205 if (!control
&& (width
= wcwidth(u
)) == -1) {
2206 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2211 if (IS_SET(MODE_PRINT
))
2215 * STR sequence must be checked before anything else
2216 * because it uses all following characters until it
2217 * receives a ESC, a SUB, a ST or any other C1 control
2220 if (term
.esc
& ESC_STR
) {
2221 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2223 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2224 if (IS_SET(MODE_SIXEL
)) {
2225 /* TODO: render sixel */;
2226 term
.mode
&= ~MODE_SIXEL
;
2229 term
.esc
|= ESC_STR_END
;
2230 goto check_control_code
;
2234 if (IS_SET(MODE_SIXEL
)) {
2235 /* TODO: implement sixel mode */
2238 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2239 term
.mode
|= MODE_SIXEL
;
2241 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2243 * Here is a bug in terminals. If the user never sends
2244 * some code to stop the str or esc command, then st
2245 * will stop responding. But this is better than
2246 * silently failing with unknown characters. At least
2247 * then users will report back.
2249 * In the case users ever get fixed, here is the code:
2258 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2259 strescseq
.len
+= len
;
2265 * Actions of control codes must be performed as soon they arrive
2266 * because they can be embedded inside a control sequence, and
2267 * they must not cause conflicts with sequences.
2272 * control codes are not shown ever
2275 } else if (term
.esc
& ESC_START
) {
2276 if (term
.esc
& ESC_CSI
) {
2277 csiescseq
.buf
[csiescseq
.len
++] = u
;
2278 if (BETWEEN(u
, 0x40, 0x7E)
2279 || csiescseq
.len
>= \
2280 sizeof(csiescseq
.buf
)-1) {
2286 } else if (term
.esc
& ESC_UTF8
) {
2288 } else if (term
.esc
& ESC_ALTCHARSET
) {
2290 } else if (term
.esc
& ESC_TEST
) {
2295 /* sequence already finished */
2299 * All characters which form part of a sequence are not
2304 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2307 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2308 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2309 gp
->mode
|= ATTR_WRAP
;
2311 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2314 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2315 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2317 if (term
.c
.x
+width
> term
.col
) {
2319 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2322 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2325 gp
->mode
|= ATTR_WIDE
;
2326 if (term
.c
.x
+1 < term
.col
) {
2328 gp
[1].mode
= ATTR_WDUMMY
;
2331 if (term
.c
.x
+width
< term
.col
) {
2332 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2334 term
.c
.state
|= CURSOR_WRAPNEXT
;
2339 twrite(const char *buf
, int buflen
, int show_ctrl
)
2345 for (n
= 0; n
< buflen
; n
+= charsize
) {
2346 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2347 /* process a complete utf8 char */
2348 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2355 if (show_ctrl
&& ISCONTROL(u
)) {
2360 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2371 tresize(int col
, int row
)
2374 int minrow
= MIN(row
, term
.row
);
2375 int mincol
= MIN(col
, term
.col
);
2379 if (col
< 1 || row
< 1) {
2381 "tresize: error resizing to %dx%d\n", col
, row
);
2386 * slide screen to keep cursor where we expect it -
2387 * tscrollup would work here, but we can optimize to
2388 * memmove because we're freeing the earlier lines
2390 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2394 /* ensure that both src and dst are not NULL */
2396 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2397 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2399 for (i
+= row
; i
< term
.row
; i
++) {
2404 /* resize to new height */
2405 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2406 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2407 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2408 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2410 /* resize each row to new width, zero-pad if needed */
2411 for (i
= 0; i
< minrow
; i
++) {
2412 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2413 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2416 /* allocate any new rows */
2417 for (/* i = minrow */; i
< row
; i
++) {
2418 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2419 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2421 if (col
> term
.col
) {
2422 bp
= term
.tabs
+ term
.col
;
2424 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2425 while (--bp
> term
.tabs
&& !*bp
)
2427 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2430 /* update terminal size */
2433 /* reset scrolling region */
2434 tsetscroll(0, row
-1);
2435 /* make use of the LIMIT in tmoveto */
2436 tmoveto(term
.c
.x
, term
.c
.y
);
2437 /* Clearing both screens (it makes dirty all lines) */
2439 for (i
= 0; i
< 2; i
++) {
2440 if (mincol
< col
&& 0 < minrow
) {
2441 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2443 if (0 < col
&& minrow
< row
) {
2444 tclearregion(0, minrow
, col
- 1, row
- 1);
2447 tcursor(CURSOR_LOAD
);
2466 numlock(const Arg
*dummy
)