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>
24 #include <fontconfig/fontconfig.h>
28 #include <X11/cursorfont.h>
29 #include <X11/Xft/Xft.h>
39 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
41 #elif defined(__FreeBSD__) || defined(__DragonFly__)
46 #define UTF_INVALID 0xFFFD
47 #define ESC_BUF_SIZ (128*UTF_SIZ)
48 #define ESC_ARG_SIZ 16
49 #define STR_BUF_SIZ ESC_BUF_SIZ
50 #define STR_ARG_SIZ ESC_ARG_SIZ
53 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
54 #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
55 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
56 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
57 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
58 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
61 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
63 enum cursor_movement
{
87 ESC_STR
= 4, /* OSC, PM, APC */
89 ESC_STR_END
= 16, /* a final string was encountered */
90 ESC_TEST
= 32, /* Enter in test mode */
95 /* CSI Escape sequence structs */
96 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
98 char buf
[ESC_BUF_SIZ
]; /* raw string */
99 int len
; /* raw string length */
101 int arg
[ESC_ARG_SIZ
];
102 int narg
; /* nb of args */
106 /* STR Escape sequence structs */
107 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
109 char type
; /* ESC type ... */
110 char buf
[STR_BUF_SIZ
]; /* raw string */
111 int len
; /* raw string length */
112 char *args
[STR_ARG_SIZ
];
113 int narg
; /* nb of args */
120 /* three valued logic variables: 0 indifferent, 1 on, -1 off */
121 signed char appkey
; /* application keypad */
122 signed char appcursor
; /* application cursor */
123 signed char crlf
; /* crlf mode */
126 /* function definitions used in config.h */
127 static void clipcopy(const Arg
*);
128 static void clippaste(const Arg
*);
129 static void numlock(const Arg
*);
130 static void selpaste(const Arg
*);
131 static void printsel(const Arg
*);
132 static void printscreen(const Arg
*) ;
133 static void iso14755(const Arg
*);
134 static void toggleprinter(const Arg
*);
135 static void sendbreak(const Arg
*);
137 /* config.h for applying patches and the configuration. */
140 static void execsh(void);
141 static void stty(void);
142 static void sigchld(int);
144 static void csidump(void);
145 static void csihandle(void);
146 static void csiparse(void);
147 static void csireset(void);
148 static int eschandle(uchar
);
149 static void strdump(void);
150 static void strhandle(void);
151 static void strparse(void);
152 static void strreset(void);
154 static void tprinter(char *, size_t);
155 static void tdumpsel(void);
156 static void tdumpline(int);
157 static void tdump(void);
158 static void tclearregion(int, int, int, int);
159 static void tcursor(int);
160 static void tdeletechar(int);
161 static void tdeleteline(int);
162 static void tinsertblank(int);
163 static void tinsertblankline(int);
164 static int tlinelen(int);
165 static void tmoveto(int, int);
166 static void tmoveato(int, int);
167 static void tnewline(int);
168 static void tputtab(int);
169 static void tputc(Rune
);
170 static void treset(void);
171 static void tresize(int, int);
172 static void tscrollup(int, int);
173 static void tscrolldown(int, int);
174 static void tsetattr(int *, int);
175 static void tsetchar(Rune
, Glyph
*, int, int);
176 static void tsetscroll(int, int);
177 static void tswapscreen(void);
178 static void tsetmode(int, int, int *, int);
179 static void tfulldirt(void);
180 static void techo(Rune
);
181 static void tcontrolcode(uchar
);
182 static void tdectest(char );
183 static void tdefutf8(char);
184 static int32_t tdefcolor(int *, int *, int);
185 static void tdeftran(char);
186 static void tstrsequence(uchar
);
188 static void selscroll(int, int);
189 static void selsnap(int *, int *, int);
191 static Rune
utf8decodebyte(char, size_t *);
192 static char utf8encodebyte(Rune
, size_t);
193 static char *utf8strchr(char *s
, Rune u
);
194 static size_t utf8validate(Rune
*, size_t);
196 static char *base64dec(const char *);
198 static ssize_t
xwrite(int, const char *, size_t);
199 static void *xrealloc(void *, size_t);
207 char **opt_cmd
= NULL
;
208 char *opt_class
= NULL
;
209 char *opt_embed
= NULL
;
210 char *opt_font
= NULL
;
212 char *opt_line
= NULL
;
213 char *opt_name
= NULL
;
214 char *opt_title
= NULL
;
215 int oldbutton
= 3; /* button event on startup: 3 = release */
217 static CSIEscape csiescseq
;
218 static STREscape strescseq
;
221 char *usedfont
= NULL
;
222 double usedfontsize
= 0;
223 double defaultfontsize
= 0;
225 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
226 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
227 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
228 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
230 /* config.h array lengths */
231 size_t colornamelen
= LEN(colorname
);
232 size_t mshortcutslen
= LEN(mshortcuts
);
233 size_t shortcutslen
= LEN(shortcuts
);
234 size_t selmaskslen
= LEN(selmasks
);
237 xwrite(int fd
, const char *s
, size_t len
)
243 r
= write(fd
, s
, len
);
256 void *p
= malloc(len
);
259 die("Out of memory\n");
265 xrealloc(void *p
, size_t len
)
267 if ((p
= realloc(p
, len
)) == NULL
)
268 die("Out of memory\n");
276 if ((s
= strdup(s
)) == NULL
)
277 die("Out of memory\n");
283 utf8decode(char *c
, Rune
*u
, size_t clen
)
285 size_t i
, j
, len
, type
;
291 udecoded
= utf8decodebyte(c
[0], &len
);
292 if (!BETWEEN(len
, 1, UTF_SIZ
))
294 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
295 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
302 utf8validate(u
, len
);
308 utf8decodebyte(char c
, size_t *i
)
310 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
311 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
312 return (uchar
)c
& ~utfmask
[*i
];
318 utf8encode(Rune u
, char *c
)
322 len
= utf8validate(&u
, 0);
326 for (i
= len
- 1; i
!= 0; --i
) {
327 c
[i
] = utf8encodebyte(u
, 0);
330 c
[0] = utf8encodebyte(u
, len
);
336 utf8encodebyte(Rune u
, size_t i
)
338 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
342 utf8strchr(char *s
, Rune u
)
348 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
349 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
359 utf8validate(Rune
*u
, size_t i
)
361 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
363 for (i
= 1; *u
> utfmax
[i
]; ++i
)
369 static const char base64_digits
[] = {
370 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
371 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
372 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
373 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
374 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
375 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
376 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
377 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
385 base64dec_getc(const char **src
)
387 while (**src
&& !isprint(**src
)) (*src
)++;
392 base64dec(const char *src
)
394 size_t in_len
= strlen(src
);
398 in_len
+= 4 - (in_len
% 4);
399 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
401 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
402 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
403 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
404 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
406 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
409 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
412 *dst
++ = ((c
& 0x03) << 6) | d
;
421 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
422 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
427 sel
.clipboard
= NULL
;
436 return LIMIT(x
, 0, term
.col
-1);
445 return LIMIT(y
, 0, term
.row
-1);
453 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
456 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
467 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
468 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
469 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
471 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
472 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
474 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
475 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
477 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
478 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
480 /* expand selection over line breaks */
481 if (sel
.type
== SEL_RECTANGULAR
)
483 i
= tlinelen(sel
.nb
.y
);
486 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
487 sel
.ne
.x
= term
.col
- 1;
491 selected(int x
, int y
)
493 if (sel
.mode
== SEL_EMPTY
)
496 if (sel
.type
== SEL_RECTANGULAR
)
497 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
498 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
500 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
501 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
502 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
506 selsnap(int *x
, int *y
, int direction
)
508 int newx
, newy
, xt
, yt
;
509 int delim
, prevdelim
;
515 * Snap around if the word wraps around at the end or
516 * beginning of a line.
518 prevgp
= &term
.line
[*y
][*x
];
519 prevdelim
= ISDELIM(prevgp
->u
);
521 newx
= *x
+ direction
;
523 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
525 newx
= (newx
+ term
.col
) % term
.col
;
526 if (!BETWEEN(newy
, 0, term
.row
- 1))
532 yt
= newy
, xt
= newx
;
533 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
537 if (newx
>= tlinelen(newy
))
540 gp
= &term
.line
[newy
][newx
];
541 delim
= ISDELIM(gp
->u
);
542 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
543 || (delim
&& gp
->u
!= prevgp
->u
)))
554 * Snap around if the the previous line or the current one
555 * has set ATTR_WRAP at its end. Then the whole next or
556 * previous line will be selected.
558 *x
= (direction
< 0) ? 0 : term
.col
- 1;
560 for (; *y
> 0; *y
+= direction
) {
561 if (!(term
.line
[*y
-1][term
.col
-1].mode
566 } else if (direction
> 0) {
567 for (; *y
< term
.row
-1; *y
+= direction
) {
568 if (!(term
.line
[*y
][term
.col
-1].mode
582 int y
, bufsize
, lastx
, linelen
;
588 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
589 ptr
= str
= xmalloc(bufsize
);
591 /* append every set & selected glyph to the selection */
592 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
593 if ((linelen
= tlinelen(y
)) == 0) {
598 if (sel
.type
== SEL_RECTANGULAR
) {
599 gp
= &term
.line
[y
][sel
.nb
.x
];
602 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
603 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
605 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
606 while (last
>= gp
&& last
->u
== ' ')
609 for ( ; gp
<= last
; ++gp
) {
610 if (gp
->mode
& ATTR_WDUMMY
)
613 ptr
+= utf8encode(gp
->u
, ptr
);
617 * Copy and pasting of line endings is inconsistent
618 * in the inconsistent terminal and GUI world.
619 * The best solution seems like to produce '\n' when
620 * something is copied from st and convert '\n' to
621 * '\r', when something to be pasted is received by
623 * FIXME: Fix the computer world.
625 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
633 selpaste(const Arg
*dummy
)
639 clipcopy(const Arg
*dummy
)
645 clippaste(const Arg
*dummy
)
657 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
661 die(const char *errstr
, ...)
665 va_start(ap
, errstr
);
666 vfprintf(stderr
, errstr
, ap
);
674 char **args
, *sh
, *prog
;
675 const struct passwd
*pw
;
678 if ((pw
= getpwuid(getuid())) == NULL
) {
680 die("getpwuid:%s\n", strerror(errno
));
682 die("who are you?\n");
685 if ((sh
= getenv("SHELL")) == NULL
)
686 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
694 args
= (opt_cmd
) ? opt_cmd
: (char *[]) {prog
, NULL
};
699 setenv("LOGNAME", pw
->pw_name
, 1);
700 setenv("USER", pw
->pw_name
, 1);
701 setenv("SHELL", sh
, 1);
702 setenv("HOME", pw
->pw_dir
, 1);
703 setenv("TERM", termname
, 1);
705 signal(SIGCHLD
, SIG_DFL
);
706 signal(SIGHUP
, SIG_DFL
);
707 signal(SIGINT
, SIG_DFL
);
708 signal(SIGQUIT
, SIG_DFL
);
709 signal(SIGTERM
, SIG_DFL
);
710 signal(SIGALRM
, SIG_DFL
);
722 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
723 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
728 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
729 die("child finished with error '%d'\n", stat
);
737 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
740 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
741 die("incorrect stty parameters\n");
742 memcpy(cmd
, stty_args
, n
);
744 siz
= sizeof(cmd
) - n
;
745 for (p
= opt_cmd
; p
&& (s
= *p
); ++p
) {
746 if ((n
= strlen(s
)) > siz
-1)
747 die("stty parameter length too long\n");
754 if (system(cmd
) != 0)
755 perror("Couldn't call stty");
762 struct winsize w
= {term
.row
, term
.col
, 0, 0};
765 term
.mode
|= MODE_PRINT
;
766 iofd
= (!strcmp(opt_io
, "-")) ?
767 1 : open(opt_io
, O_WRONLY
| O_CREAT
, 0666);
769 fprintf(stderr
, "Error opening %s:%s\n",
770 opt_io
, strerror(errno
));
775 if ((cmdfd
= open(opt_line
, O_RDWR
)) < 0)
776 die("open line failed: %s\n", strerror(errno
));
782 /* seems to work fine on linux, openbsd and freebsd */
783 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
784 die("openpty failed: %s\n", strerror(errno
));
786 switch (pid
= fork()) {
788 die("fork failed\n");
792 setsid(); /* create a new process group */
796 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
797 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
805 signal(SIGCHLD
, sigchld
);
813 static char buf
[BUFSIZ
];
814 static int buflen
= 0;
816 int charsize
; /* size of utf8 char in bytes */
820 /* append read bytes to unprocessed bytes */
821 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
822 die("Couldn't read from shell: %s\n", strerror(errno
));
828 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
829 /* process a complete utf8 char */
830 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
840 tputc(*ptr
++ & 0xFF);
844 /* keep any uncomplete utf8 char for the next call */
846 memmove(buf
, ptr
, buflen
);
852 ttywrite(const char *s
, size_t n
)
859 * Remember that we are using a pty, which might be a modem line.
860 * Writing too much will clog the line. That's why we are doing this
862 * FIXME: Migrate the world to Plan 9.
870 /* Check if we can write. */
871 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
874 die("select failed: %s\n", strerror(errno
));
876 if (FD_ISSET(cmdfd
, &wfd
)) {
878 * Only write the bytes written by ttywrite() or the
879 * default of 256. This seems to be a reasonable value
880 * for a serial line. Bigger values might clog the I/O.
882 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
886 * We weren't able to write out everything.
887 * This means the buffer is getting full
895 /* All bytes have been written. */
899 if (FD_ISSET(cmdfd
, &rfd
))
905 die("write error on tty: %s\n", strerror(errno
));
909 ttysend(char *s
, size_t n
)
916 if (!IS_SET(MODE_ECHO
))
920 for (t
= s
; t
< lim
; t
+= len
) {
921 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
922 len
= utf8decode(t
, &u
, n
);
941 w
.ws_xpixel
= win
.tw
;
942 w
.ws_ypixel
= win
.th
;
943 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
944 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
952 for (i
= 0; i
< term
.row
-1; i
++) {
953 for (j
= 0; j
< term
.col
-1; j
++) {
954 if (term
.line
[i
][j
].mode
& attr
)
963 tsetdirt(int top
, int bot
)
967 LIMIT(top
, 0, term
.row
-1);
968 LIMIT(bot
, 0, term
.row
-1);
970 for (i
= top
; i
<= bot
; i
++)
975 tsetdirtattr(int attr
)
979 for (i
= 0; i
< term
.row
-1; i
++) {
980 for (j
= 0; j
< term
.col
-1; j
++) {
981 if (term
.line
[i
][j
].mode
& attr
) {
992 tsetdirt(0, term
.row
-1);
999 int alt
= IS_SET(MODE_ALTSCREEN
);
1001 if (mode
== CURSOR_SAVE
) {
1003 } else if (mode
== CURSOR_LOAD
) {
1005 tmoveto(c
[alt
].x
, c
[alt
].y
);
1014 term
.c
= (TCursor
){{
1018 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1020 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1021 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1024 term
.bot
= term
.row
- 1;
1025 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1026 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1029 for (i
= 0; i
< 2; i
++) {
1031 tcursor(CURSOR_SAVE
);
1032 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1038 tnew(int col
, int row
)
1040 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1050 Line
*tmp
= term
.line
;
1052 term
.line
= term
.alt
;
1054 term
.mode
^= MODE_ALTSCREEN
;
1059 tscrolldown(int orig
, int n
)
1064 LIMIT(n
, 0, term
.bot
-orig
+1);
1066 tsetdirt(orig
, term
.bot
-n
);
1067 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1069 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1070 temp
= term
.line
[i
];
1071 term
.line
[i
] = term
.line
[i
-n
];
1072 term
.line
[i
-n
] = temp
;
1079 tscrollup(int orig
, int n
)
1084 LIMIT(n
, 0, term
.bot
-orig
+1);
1086 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1087 tsetdirt(orig
+n
, term
.bot
);
1089 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1090 temp
= term
.line
[i
];
1091 term
.line
[i
] = term
.line
[i
+n
];
1092 term
.line
[i
+n
] = temp
;
1095 selscroll(orig
, -n
);
1099 selscroll(int orig
, int n
)
1104 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1105 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1109 if (sel
.type
== SEL_RECTANGULAR
) {
1110 if (sel
.ob
.y
< term
.top
)
1111 sel
.ob
.y
= term
.top
;
1112 if (sel
.oe
.y
> term
.bot
)
1113 sel
.oe
.y
= term
.bot
;
1115 if (sel
.ob
.y
< term
.top
) {
1116 sel
.ob
.y
= term
.top
;
1119 if (sel
.oe
.y
> term
.bot
) {
1120 sel
.oe
.y
= term
.bot
;
1121 sel
.oe
.x
= term
.col
;
1129 tnewline(int first_col
)
1133 if (y
== term
.bot
) {
1134 tscrollup(term
.top
, 1);
1138 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1144 char *p
= csiescseq
.buf
, *np
;
1153 csiescseq
.buf
[csiescseq
.len
] = '\0';
1154 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1156 v
= strtol(p
, &np
, 10);
1159 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1161 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1163 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1167 csiescseq
.mode
[0] = *p
++;
1168 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1171 /* for absolute user moves, when decom is set */
1173 tmoveato(int x
, int y
)
1175 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1179 tmoveto(int x
, int y
)
1183 if (term
.c
.state
& CURSOR_ORIGIN
) {
1188 maxy
= term
.row
- 1;
1190 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1191 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1192 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1196 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1198 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1199 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1200 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1201 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1202 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1203 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1204 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1205 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1206 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1210 * The table is proudly stolen from rxvt.
1212 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1213 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1214 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1216 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1217 if (x
+1 < term
.col
) {
1218 term
.line
[y
][x
+1].u
= ' ';
1219 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1221 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1222 term
.line
[y
][x
-1].u
= ' ';
1223 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1227 term
.line
[y
][x
] = *attr
;
1228 term
.line
[y
][x
].u
= u
;
1232 tclearregion(int x1
, int y1
, int x2
, int y2
)
1238 temp
= x1
, x1
= x2
, x2
= temp
;
1240 temp
= y1
, y1
= y2
, y2
= temp
;
1242 LIMIT(x1
, 0, term
.col
-1);
1243 LIMIT(x2
, 0, term
.col
-1);
1244 LIMIT(y1
, 0, term
.row
-1);
1245 LIMIT(y2
, 0, term
.row
-1);
1247 for (y
= y1
; y
<= y2
; y
++) {
1249 for (x
= x1
; x
<= x2
; x
++) {
1250 gp
= &term
.line
[y
][x
];
1253 gp
->fg
= term
.c
.attr
.fg
;
1254 gp
->bg
= term
.c
.attr
.bg
;
1267 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1271 size
= term
.col
- src
;
1272 line
= term
.line
[term
.c
.y
];
1274 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1275 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1284 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1288 size
= term
.col
- dst
;
1289 line
= term
.line
[term
.c
.y
];
1291 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1292 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1296 tinsertblankline(int n
)
1298 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1299 tscrolldown(term
.c
.y
, n
);
1305 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1306 tscrollup(term
.c
.y
, n
);
1310 tdefcolor(int *attr
, int *npar
, int l
)
1315 switch (attr
[*npar
+ 1]) {
1316 case 2: /* direct color in RGB space */
1317 if (*npar
+ 4 >= l
) {
1319 "erresc(38): Incorrect number of parameters (%d)\n",
1323 r
= attr
[*npar
+ 2];
1324 g
= attr
[*npar
+ 3];
1325 b
= attr
[*npar
+ 4];
1327 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1328 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1331 idx
= TRUECOLOR(r
, g
, b
);
1333 case 5: /* indexed color */
1334 if (*npar
+ 2 >= l
) {
1336 "erresc(38): Incorrect number of parameters (%d)\n",
1341 if (!BETWEEN(attr
[*npar
], 0, 255))
1342 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1346 case 0: /* implemented defined (only foreground) */
1347 case 1: /* transparent */
1348 case 3: /* direct color in CMY space */
1349 case 4: /* direct color in CMYK space */
1352 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1360 tsetattr(int *attr
, int l
)
1365 for (i
= 0; i
< l
; i
++) {
1368 term
.c
.attr
.mode
&= ~(
1377 term
.c
.attr
.fg
= defaultfg
;
1378 term
.c
.attr
.bg
= defaultbg
;
1381 term
.c
.attr
.mode
|= ATTR_BOLD
;
1384 term
.c
.attr
.mode
|= ATTR_FAINT
;
1387 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1390 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1392 case 5: /* slow blink */
1394 case 6: /* rapid blink */
1395 term
.c
.attr
.mode
|= ATTR_BLINK
;
1398 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1401 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1404 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1407 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1410 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1413 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1416 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1419 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1422 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1425 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1428 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1429 term
.c
.attr
.fg
= idx
;
1432 term
.c
.attr
.fg
= defaultfg
;
1435 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1436 term
.c
.attr
.bg
= idx
;
1439 term
.c
.attr
.bg
= defaultbg
;
1442 if (BETWEEN(attr
[i
], 30, 37)) {
1443 term
.c
.attr
.fg
= attr
[i
] - 30;
1444 } else if (BETWEEN(attr
[i
], 40, 47)) {
1445 term
.c
.attr
.bg
= attr
[i
] - 40;
1446 } else if (BETWEEN(attr
[i
], 90, 97)) {
1447 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1448 } else if (BETWEEN(attr
[i
], 100, 107)) {
1449 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1452 "erresc(default): gfx attr %d unknown\n",
1453 attr
[i
]), csidump();
1461 tsetscroll(int t
, int b
)
1465 LIMIT(t
, 0, term
.row
-1);
1466 LIMIT(b
, 0, term
.row
-1);
1477 tsetmode(int priv
, int set
, int *args
, int narg
)
1482 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1485 case 1: /* DECCKM -- Cursor key */
1486 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1488 case 5: /* DECSCNM -- Reverse video */
1490 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1491 if (mode
!= term
.mode
)
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 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1514 case 9: /* X10 mouse compatibility mode */
1515 xsetpointermotion(0);
1516 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1517 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1519 case 1000: /* 1000: report button press */
1520 xsetpointermotion(0);
1521 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1522 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1524 case 1002: /* 1002: report motion on button press */
1525 xsetpointermotion(0);
1526 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1527 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1529 case 1003: /* 1003: enable all mouse motions */
1530 xsetpointermotion(set
);
1531 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1532 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1534 case 1004: /* 1004: send focus events to tty */
1535 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1537 case 1006: /* 1006: extended reporting mode */
1538 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1541 MODBIT(term
.mode
, 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 MODBIT(term
.mode
, 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) */
1587 case 2: /* KAM -- keyboard action */
1588 MODBIT(term
.mode
, 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
, sizeof(vtiden
) - 1);
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);
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 DEFAULT(csiescseq
.arg
[0], 1);
1807 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1810 win
.cursor
= csiescseq
.arg
[0];
1825 fprintf(stderr
, "ESC[");
1826 for (i
= 0; i
< csiescseq
.len
; i
++) {
1827 c
= csiescseq
.buf
[i
] & 0xff;
1830 } else if (c
== '\n') {
1831 fprintf(stderr
, "(\\n)");
1832 } else if (c
== '\r') {
1833 fprintf(stderr
, "(\\r)");
1834 } else if (c
== 0x1b) {
1835 fprintf(stderr
, "(\\e)");
1837 fprintf(stderr
, "(%02x)", c
);
1846 memset(&csiescseq
, 0, sizeof(csiescseq
));
1855 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1857 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1859 switch (strescseq
.type
) {
1860 case ']': /* OSC -- Operating System Command */
1866 xsettitle(strescseq
.args
[1]);
1872 dec
= base64dec(strescseq
.args
[2]);
1874 xsetsel(dec
, CurrentTime
);
1877 fprintf(stderr
, "erresc: invalid base64\n");
1881 case 4: /* color set */
1884 p
= strescseq
.args
[2];
1886 case 104: /* color reset, here p = NULL */
1887 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1888 if (xsetcolorname(j
, p
)) {
1889 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1892 * TODO if defaultbg color is changed, borders
1900 case 'k': /* old title set compatibility */
1901 xsettitle(strescseq
.args
[0]);
1903 case 'P': /* DCS -- Device Control String */
1904 term
.mode
|= ESC_DCS
;
1905 case '_': /* APC -- Application Program Command */
1906 case '^': /* PM -- Privacy Message */
1910 fprintf(stderr
, "erresc: unknown str ");
1918 char *p
= strescseq
.buf
;
1921 strescseq
.buf
[strescseq
.len
] = '\0';
1926 while (strescseq
.narg
< STR_ARG_SIZ
) {
1927 strescseq
.args
[strescseq
.narg
++] = p
;
1928 while ((c
= *p
) != ';' && c
!= '\0')
1942 fprintf(stderr
, "ESC%c", strescseq
.type
);
1943 for (i
= 0; i
< strescseq
.len
; i
++) {
1944 c
= strescseq
.buf
[i
] & 0xff;
1948 } else if (isprint(c
)) {
1950 } else if (c
== '\n') {
1951 fprintf(stderr
, "(\\n)");
1952 } else if (c
== '\r') {
1953 fprintf(stderr
, "(\\r)");
1954 } else if (c
== 0x1b) {
1955 fprintf(stderr
, "(\\e)");
1957 fprintf(stderr
, "(%02x)", c
);
1960 fprintf(stderr
, "ESC\\\n");
1966 memset(&strescseq
, 0, sizeof(strescseq
));
1970 sendbreak(const Arg
*arg
)
1972 if (tcsendbreak(cmdfd
, 0))
1973 perror("Error sending break");
1977 tprinter(char *s
, size_t len
)
1979 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1980 fprintf(stderr
, "Error writing in %s:%s\n",
1981 opt_io
, strerror(errno
));
1988 iso14755(const Arg
*arg
)
1991 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1992 unsigned long utf32
;
1994 if (!(p
= popen(ISO14755CMD
, "r")))
1997 us
= fgets(codepoint
, sizeof(codepoint
), p
);
2000 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
2002 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
2003 (*e
!= '\n' && *e
!= '\0'))
2006 ttysend(uc
, utf8encode(utf32
, uc
));
2010 toggleprinter(const Arg
*arg
)
2012 term
.mode
^= MODE_PRINT
;
2016 printscreen(const Arg
*arg
)
2022 printsel(const Arg
*arg
)
2032 if ((ptr
= getsel())) {
2033 tprinter(ptr
, strlen(ptr
));
2044 bp
= &term
.line
[n
][0];
2045 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2046 if (bp
!= end
|| bp
->u
!= ' ') {
2047 for ( ;bp
<= end
; ++bp
)
2048 tprinter(buf
, utf8encode(bp
->u
, buf
));
2058 for (i
= 0; i
< term
.row
; ++i
)
2068 while (x
< term
.col
&& n
--)
2069 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2072 while (x
> 0 && n
++)
2073 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2076 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2082 if (ISCONTROL(u
)) { /* control code */
2087 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2096 tdefutf8(char ascii
)
2099 term
.mode
|= MODE_UTF8
;
2100 else if (ascii
== '@')
2101 term
.mode
&= ~MODE_UTF8
;
2105 tdeftran(char ascii
)
2107 static char cs
[] = "0B";
2108 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2111 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2112 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2114 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2123 if (c
== '8') { /* DEC screen alignment test. */
2124 for (x
= 0; x
< term
.col
; ++x
) {
2125 for (y
= 0; y
< term
.row
; ++y
)
2126 tsetchar('E', &term
.c
.attr
, x
, y
);
2132 tstrsequence(uchar c
)
2137 case 0x90: /* DCS -- Device Control String */
2139 term
.esc
|= ESC_DCS
;
2141 case 0x9f: /* APC -- Application Program Command */
2144 case 0x9e: /* PM -- Privacy Message */
2147 case 0x9d: /* OSC -- Operating System Command */
2152 term
.esc
|= ESC_STR
;
2156 tcontrolcode(uchar ascii
)
2163 tmoveto(term
.c
.x
-1, term
.c
.y
);
2166 tmoveto(0, term
.c
.y
);
2171 /* go to first col if the mode is set */
2172 tnewline(IS_SET(MODE_CRLF
));
2174 case '\a': /* BEL */
2175 if (term
.esc
& ESC_STR_END
) {
2176 /* backwards compatibility to xterm */
2179 if (!(win
.state
& WIN_FOCUSED
))
2185 case '\033': /* ESC */
2187 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2188 term
.esc
|= ESC_START
;
2190 case '\016': /* SO (LS1 -- Locking shift 1) */
2191 case '\017': /* SI (LS0 -- Locking shift 0) */
2192 term
.charset
= 1 - (ascii
- '\016');
2194 case '\032': /* SUB */
2195 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2196 case '\030': /* CAN */
2199 case '\005': /* ENQ (IGNORED) */
2200 case '\000': /* NUL (IGNORED) */
2201 case '\021': /* XON (IGNORED) */
2202 case '\023': /* XOFF (IGNORED) */
2203 case 0177: /* DEL (IGNORED) */
2205 case 0x80: /* TODO: PAD */
2206 case 0x81: /* TODO: HOP */
2207 case 0x82: /* TODO: BPH */
2208 case 0x83: /* TODO: NBH */
2209 case 0x84: /* TODO: IND */
2211 case 0x85: /* NEL -- Next line */
2212 tnewline(1); /* always go to first col */
2214 case 0x86: /* TODO: SSA */
2215 case 0x87: /* TODO: ESA */
2217 case 0x88: /* HTS -- Horizontal tab stop */
2218 term
.tabs
[term
.c
.x
] = 1;
2220 case 0x89: /* TODO: HTJ */
2221 case 0x8a: /* TODO: VTS */
2222 case 0x8b: /* TODO: PLD */
2223 case 0x8c: /* TODO: PLU */
2224 case 0x8d: /* TODO: RI */
2225 case 0x8e: /* TODO: SS2 */
2226 case 0x8f: /* TODO: SS3 */
2227 case 0x91: /* TODO: PU1 */
2228 case 0x92: /* TODO: PU2 */
2229 case 0x93: /* TODO: STS */
2230 case 0x94: /* TODO: CCH */
2231 case 0x95: /* TODO: MW */
2232 case 0x96: /* TODO: SPA */
2233 case 0x97: /* TODO: EPA */
2234 case 0x98: /* TODO: SOS */
2235 case 0x99: /* TODO: SGCI */
2237 case 0x9a: /* DECID -- Identify Terminal */
2238 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2240 case 0x9b: /* TODO: CSI */
2241 case 0x9c: /* TODO: ST */
2243 case 0x90: /* DCS -- Device Control String */
2244 case 0x9d: /* OSC -- Operating System Command */
2245 case 0x9e: /* PM -- Privacy Message */
2246 case 0x9f: /* APC -- Application Program Command */
2247 tstrsequence(ascii
);
2250 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2251 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2255 * returns 1 when the sequence is finished and it hasn't to read
2256 * more characters for this sequence, otherwise 0
2259 eschandle(uchar ascii
)
2263 term
.esc
|= ESC_CSI
;
2266 term
.esc
|= ESC_TEST
;
2269 term
.esc
|= ESC_UTF8
;
2271 case 'P': /* DCS -- Device Control String */
2272 case '_': /* APC -- Application Program Command */
2273 case '^': /* PM -- Privacy Message */
2274 case ']': /* OSC -- Operating System Command */
2275 case 'k': /* old title set compatibility */
2276 tstrsequence(ascii
);
2278 case 'n': /* LS2 -- Locking shift 2 */
2279 case 'o': /* LS3 -- Locking shift 3 */
2280 term
.charset
= 2 + (ascii
- 'n');
2282 case '(': /* GZD4 -- set primary charset G0 */
2283 case ')': /* G1D4 -- set secondary charset G1 */
2284 case '*': /* G2D4 -- set tertiary charset G2 */
2285 case '+': /* G3D4 -- set quaternary charset G3 */
2286 term
.icharset
= ascii
- '(';
2287 term
.esc
|= ESC_ALTCHARSET
;
2289 case 'D': /* IND -- Linefeed */
2290 if (term
.c
.y
== term
.bot
) {
2291 tscrollup(term
.top
, 1);
2293 tmoveto(term
.c
.x
, term
.c
.y
+1);
2296 case 'E': /* NEL -- Next line */
2297 tnewline(1); /* always go to first col */
2299 case 'H': /* HTS -- Horizontal tab stop */
2300 term
.tabs
[term
.c
.x
] = 1;
2302 case 'M': /* RI -- Reverse index */
2303 if (term
.c
.y
== term
.top
) {
2304 tscrolldown(term
.top
, 1);
2306 tmoveto(term
.c
.x
, term
.c
.y
-1);
2309 case 'Z': /* DECID -- Identify Terminal */
2310 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2312 case 'c': /* RIS -- Reset to inital state */
2317 case '=': /* DECPAM -- Application keypad */
2318 term
.mode
|= MODE_APPKEYPAD
;
2320 case '>': /* DECPNM -- Normal keypad */
2321 term
.mode
&= ~MODE_APPKEYPAD
;
2323 case '7': /* DECSC -- Save Cursor */
2324 tcursor(CURSOR_SAVE
);
2326 case '8': /* DECRC -- Restore Cursor */
2327 tcursor(CURSOR_LOAD
);
2329 case '\\': /* ST -- String Terminator */
2330 if (term
.esc
& ESC_STR_END
)
2334 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2335 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2349 control
= ISCONTROL(u
);
2350 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2354 len
= utf8encode(u
, c
);
2355 if (!control
&& (width
= wcwidth(u
)) == -1) {
2356 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2361 if (IS_SET(MODE_PRINT
))
2365 * STR sequence must be checked before anything else
2366 * because it uses all following characters until it
2367 * receives a ESC, a SUB, a ST or any other C1 control
2370 if (term
.esc
& ESC_STR
) {
2371 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2373 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2374 if (IS_SET(MODE_SIXEL
)) {
2375 /* TODO: render sixel */;
2376 term
.mode
&= ~MODE_SIXEL
;
2379 term
.esc
|= ESC_STR_END
;
2380 goto check_control_code
;
2384 if (IS_SET(MODE_SIXEL
)) {
2385 /* TODO: implement sixel mode */
2388 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2389 term
.mode
|= MODE_SIXEL
;
2391 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2393 * Here is a bug in terminals. If the user never sends
2394 * some code to stop the str or esc command, then st
2395 * will stop responding. But this is better than
2396 * silently failing with unknown characters. At least
2397 * then users will report back.
2399 * In the case users ever get fixed, here is the code:
2408 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2409 strescseq
.len
+= len
;
2415 * Actions of control codes must be performed as soon they arrive
2416 * because they can be embedded inside a control sequence, and
2417 * they must not cause conflicts with sequences.
2422 * control codes are not shown ever
2425 } else if (term
.esc
& ESC_START
) {
2426 if (term
.esc
& ESC_CSI
) {
2427 csiescseq
.buf
[csiescseq
.len
++] = u
;
2428 if (BETWEEN(u
, 0x40, 0x7E)
2429 || csiescseq
.len
>= \
2430 sizeof(csiescseq
.buf
)-1) {
2436 } else if (term
.esc
& ESC_UTF8
) {
2438 } else if (term
.esc
& ESC_ALTCHARSET
) {
2440 } else if (term
.esc
& ESC_TEST
) {
2445 /* sequence already finished */
2449 * All characters which form part of a sequence are not
2454 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2457 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2458 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2459 gp
->mode
|= ATTR_WRAP
;
2461 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2464 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2465 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2467 if (term
.c
.x
+width
> term
.col
) {
2469 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2472 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2475 gp
->mode
|= ATTR_WIDE
;
2476 if (term
.c
.x
+1 < term
.col
) {
2478 gp
[1].mode
= ATTR_WDUMMY
;
2481 if (term
.c
.x
+width
< term
.col
) {
2482 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2484 term
.c
.state
|= CURSOR_WRAPNEXT
;
2489 tresize(int col
, int row
)
2492 int minrow
= MIN(row
, term
.row
);
2493 int mincol
= MIN(col
, term
.col
);
2497 if (col
< 1 || row
< 1) {
2499 "tresize: error resizing to %dx%d\n", col
, row
);
2504 * slide screen to keep cursor where we expect it -
2505 * tscrollup would work here, but we can optimize to
2506 * memmove because we're freeing the earlier lines
2508 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2512 /* ensure that both src and dst are not NULL */
2514 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2515 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2517 for (i
+= row
; i
< term
.row
; i
++) {
2522 /* resize to new width */
2523 term
.specbuf
= xrealloc(term
.specbuf
, col
* sizeof(GlyphFontSpec
));
2525 /* resize to new height */
2526 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2527 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2528 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2529 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2531 /* resize each row to new width, zero-pad if needed */
2532 for (i
= 0; i
< minrow
; i
++) {
2533 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2534 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2537 /* allocate any new rows */
2538 for (/* i = minrow */; i
< row
; i
++) {
2539 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2540 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2542 if (col
> term
.col
) {
2543 bp
= term
.tabs
+ term
.col
;
2545 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2546 while (--bp
> term
.tabs
&& !*bp
)
2548 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2551 /* update terminal size */
2554 /* reset scrolling region */
2555 tsetscroll(0, row
-1);
2556 /* make use of the LIMIT in tmoveto */
2557 tmoveto(term
.c
.x
, term
.c
.y
);
2558 /* Clearing both screens (it makes dirty all lines) */
2560 for (i
= 0; i
< 2; i
++) {
2561 if (mincol
< col
&& 0 < minrow
) {
2562 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2564 if (0 < col
&& minrow
< row
) {
2565 tclearregion(0, minrow
, col
- 1, row
- 1);
2568 tcursor(CURSOR_LOAD
);
2576 xsettitle(opt_title
? opt_title
: "st");
2587 match(uint mask
, uint state
)
2589 return mask
== XK_ANY_MOD
|| mask
== (state
& ~ignoremod
);
2593 numlock(const Arg
*dummy
)
2599 kmap(KeySym k
, uint state
)
2604 /* Check for mapped keys out of X11 function keys. */
2605 for (i
= 0; i
< LEN(mappedkeys
); i
++) {
2606 if (mappedkeys
[i
] == k
)
2609 if (i
== LEN(mappedkeys
)) {
2610 if ((k
& 0xFFFF) < 0xFD00)
2614 for (kp
= key
; kp
< key
+ LEN(key
); kp
++) {
2618 if (!match(kp
->mask
, state
))
2621 if (IS_SET(MODE_APPKEYPAD
) ? kp
->appkey
< 0 : kp
->appkey
> 0)
2623 if (term
.numlock
&& kp
->appkey
== 2)
2626 if (IS_SET(MODE_APPCURSOR
) ? kp
->appcursor
< 0 : kp
->appcursor
> 0)
2629 if (IS_SET(MODE_CRLF
) ? kp
->crlf
< 0 : kp
->crlf
> 0)
2639 cresize(int width
, int height
)
2648 col
= (win
.w
- 2 * borderpx
) / win
.cw
;
2649 row
= (win
.h
- 2 * borderpx
) / win
.ch
;