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>
41 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
43 #elif defined(__FreeBSD__) || defined(__DragonFly__)
48 #define UTF_INVALID 0xFFFD
49 #define ESC_BUF_SIZ (128*UTF_SIZ)
50 #define ESC_ARG_SIZ 16
51 #define STR_BUF_SIZ ESC_BUF_SIZ
52 #define STR_ARG_SIZ ESC_ARG_SIZ
55 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
56 #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
57 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
58 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
59 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
60 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
63 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
65 enum cursor_movement
{
89 ESC_STR
= 4, /* OSC, PM, APC */
91 ESC_STR_END
= 16, /* a final string was encountered */
92 ESC_TEST
= 32, /* Enter in test mode */
97 /* CSI Escape sequence structs */
98 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
100 char buf
[ESC_BUF_SIZ
]; /* raw string */
101 int len
; /* raw string length */
103 int arg
[ESC_ARG_SIZ
];
104 int narg
; /* nb of args */
108 /* STR Escape sequence structs */
109 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
111 char type
; /* ESC type ... */
112 char buf
[STR_BUF_SIZ
]; /* raw string */
113 int len
; /* raw string length */
114 char *args
[STR_ARG_SIZ
];
115 int narg
; /* nb of args */
122 /* three valued logic variables: 0 indifferent, 1 on, -1 off */
123 signed char appkey
; /* application keypad */
124 signed char appcursor
; /* application cursor */
125 signed char crlf
; /* crlf mode */
128 /* function definitions used in config.h */
129 static void clipcopy(const Arg
*);
130 static void clippaste(const Arg
*);
131 static void numlock(const Arg
*);
132 static void selpaste(const Arg
*);
133 static void zoom(const Arg
*);
134 static void zoomabs(const Arg
*);
135 static void zoomreset(const Arg
*);
136 static void printsel(const Arg
*);
137 static void printscreen(const Arg
*) ;
138 static void iso14755(const Arg
*);
139 static void toggleprinter(const Arg
*);
140 static void sendbreak(const Arg
*);
142 /* config.h for applying patches and the configuration. */
145 static void execsh(void);
146 static void stty(void);
147 static void sigchld(int);
149 static void csidump(void);
150 static void csihandle(void);
151 static void csiparse(void);
152 static void csireset(void);
153 static int eschandle(uchar
);
154 static void strdump(void);
155 static void strhandle(void);
156 static void strparse(void);
157 static void strreset(void);
159 static void tprinter(char *, size_t);
160 static void tdumpsel(void);
161 static void tdumpline(int);
162 static void tdump(void);
163 static void tclearregion(int, int, int, int);
164 static void tcursor(int);
165 static void tdeletechar(int);
166 static void tdeleteline(int);
167 static void tinsertblank(int);
168 static void tinsertblankline(int);
169 static int tlinelen(int);
170 static void tmoveto(int, int);
171 static void tmoveato(int, int);
172 static void tnewline(int);
173 static void tputtab(int);
174 static void tputc(Rune
);
175 static void treset(void);
176 static void tresize(int, int);
177 static void tscrollup(int, int);
178 static void tscrolldown(int, int);
179 static void tsetattr(int *, int);
180 static void tsetchar(Rune
, Glyph
*, int, int);
181 static void tsetscroll(int, int);
182 static void tswapscreen(void);
183 static void tsetmode(int, int, int *, int);
184 static void tfulldirt(void);
185 static void techo(Rune
);
186 static void tcontrolcode(uchar
);
187 static void tdectest(char );
188 static void tdefutf8(char);
189 static int32_t tdefcolor(int *, int *, int);
190 static void tdeftran(char);
191 static void tstrsequence(uchar
);
193 static void selscroll(int, int);
194 static void selsnap(int *, int *, int);
196 static Rune
utf8decodebyte(char, size_t *);
197 static char utf8encodebyte(Rune
, size_t);
198 static char *utf8strchr(char *s
, Rune u
);
199 static size_t utf8validate(Rune
*, size_t);
201 static char *base64dec(const char *);
203 static ssize_t
xwrite(int, const char *, size_t);
204 static void *xrealloc(void *, size_t);
212 char **opt_cmd
= NULL
;
213 char *opt_class
= NULL
;
214 char *opt_embed
= NULL
;
215 char *opt_font
= NULL
;
217 char *opt_line
= NULL
;
218 char *opt_name
= NULL
;
219 char *opt_title
= NULL
;
220 int oldbutton
= 3; /* button event on startup: 3 = release */
222 static CSIEscape csiescseq
;
223 static STREscape strescseq
;
226 char *usedfont
= NULL
;
227 double usedfontsize
= 0;
228 double defaultfontsize
= 0;
230 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
231 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
232 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
233 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
235 /* config.h array lengths */
236 size_t colornamelen
= LEN(colorname
);
237 size_t mshortcutslen
= LEN(mshortcuts
);
238 size_t shortcutslen
= LEN(shortcuts
);
239 size_t selmaskslen
= LEN(selmasks
);
242 xwrite(int fd
, const char *s
, size_t len
)
248 r
= write(fd
, s
, len
);
261 void *p
= malloc(len
);
264 die("Out of memory\n");
270 xrealloc(void *p
, size_t len
)
272 if ((p
= realloc(p
, len
)) == NULL
)
273 die("Out of memory\n");
281 if ((s
= strdup(s
)) == NULL
)
282 die("Out of memory\n");
288 utf8decode(char *c
, Rune
*u
, size_t clen
)
290 size_t i
, j
, len
, type
;
296 udecoded
= utf8decodebyte(c
[0], &len
);
297 if (!BETWEEN(len
, 1, UTF_SIZ
))
299 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
300 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
307 utf8validate(u
, len
);
313 utf8decodebyte(char c
, size_t *i
)
315 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
316 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
317 return (uchar
)c
& ~utfmask
[*i
];
323 utf8encode(Rune u
, char *c
)
327 len
= utf8validate(&u
, 0);
331 for (i
= len
- 1; i
!= 0; --i
) {
332 c
[i
] = utf8encodebyte(u
, 0);
335 c
[0] = utf8encodebyte(u
, len
);
341 utf8encodebyte(Rune u
, size_t i
)
343 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
347 utf8strchr(char *s
, Rune u
)
353 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
354 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
364 utf8validate(Rune
*u
, size_t i
)
366 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
368 for (i
= 1; *u
> utfmax
[i
]; ++i
)
374 static const char base64_digits
[] = {
375 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
376 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
377 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
378 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
379 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
380 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
381 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
382 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
383 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
384 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
390 base64dec_getc(const char **src
)
392 while (**src
&& !isprint(**src
)) (*src
)++;
397 base64dec(const char *src
)
399 size_t in_len
= strlen(src
);
403 in_len
+= 4 - (in_len
% 4);
404 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
406 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
407 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
408 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
409 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
411 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
414 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
417 *dst
++ = ((c
& 0x03) << 6) | d
;
426 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
427 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
432 sel
.clipboard
= NULL
;
441 return LIMIT(x
, 0, term
.col
-1);
450 return LIMIT(y
, 0, term
.row
-1);
458 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
461 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
472 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
473 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
474 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
476 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
477 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
479 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
480 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
482 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
483 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
485 /* expand selection over line breaks */
486 if (sel
.type
== SEL_RECTANGULAR
)
488 i
= tlinelen(sel
.nb
.y
);
491 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
492 sel
.ne
.x
= term
.col
- 1;
496 selected(int x
, int y
)
498 if (sel
.mode
== SEL_EMPTY
)
501 if (sel
.type
== SEL_RECTANGULAR
)
502 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
503 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
505 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
506 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
507 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
511 selsnap(int *x
, int *y
, int direction
)
513 int newx
, newy
, xt
, yt
;
514 int delim
, prevdelim
;
520 * Snap around if the word wraps around at the end or
521 * beginning of a line.
523 prevgp
= &term
.line
[*y
][*x
];
524 prevdelim
= ISDELIM(prevgp
->u
);
526 newx
= *x
+ direction
;
528 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
530 newx
= (newx
+ term
.col
) % term
.col
;
531 if (!BETWEEN(newy
, 0, term
.row
- 1))
537 yt
= newy
, xt
= newx
;
538 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
542 if (newx
>= tlinelen(newy
))
545 gp
= &term
.line
[newy
][newx
];
546 delim
= ISDELIM(gp
->u
);
547 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
548 || (delim
&& gp
->u
!= prevgp
->u
)))
559 * Snap around if the the previous line or the current one
560 * has set ATTR_WRAP at its end. Then the whole next or
561 * previous line will be selected.
563 *x
= (direction
< 0) ? 0 : term
.col
- 1;
565 for (; *y
> 0; *y
+= direction
) {
566 if (!(term
.line
[*y
-1][term
.col
-1].mode
571 } else if (direction
> 0) {
572 for (; *y
< term
.row
-1; *y
+= direction
) {
573 if (!(term
.line
[*y
][term
.col
-1].mode
587 int y
, bufsize
, lastx
, linelen
;
593 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
594 ptr
= str
= xmalloc(bufsize
);
596 /* append every set & selected glyph to the selection */
597 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
598 if ((linelen
= tlinelen(y
)) == 0) {
603 if (sel
.type
== SEL_RECTANGULAR
) {
604 gp
= &term
.line
[y
][sel
.nb
.x
];
607 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
608 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
610 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
611 while (last
>= gp
&& last
->u
== ' ')
614 for ( ; gp
<= last
; ++gp
) {
615 if (gp
->mode
& ATTR_WDUMMY
)
618 ptr
+= utf8encode(gp
->u
, ptr
);
622 * Copy and pasting of line endings is inconsistent
623 * in the inconsistent terminal and GUI world.
624 * The best solution seems like to produce '\n' when
625 * something is copied from st and convert '\n' to
626 * '\r', when something to be pasted is received by
628 * FIXME: Fix the computer world.
630 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
638 selpaste(const Arg
*dummy
)
644 clipcopy(const Arg
*dummy
)
650 clippaste(const Arg
*dummy
)
662 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
666 die(const char *errstr
, ...)
670 va_start(ap
, errstr
);
671 vfprintf(stderr
, errstr
, ap
);
679 char **args
, *sh
, *prog
;
680 const struct passwd
*pw
;
683 if ((pw
= getpwuid(getuid())) == NULL
) {
685 die("getpwuid:%s\n", strerror(errno
));
687 die("who are you?\n");
690 if ((sh
= getenv("SHELL")) == NULL
)
691 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
699 args
= (opt_cmd
) ? opt_cmd
: (char *[]) {prog
, NULL
};
704 setenv("LOGNAME", pw
->pw_name
, 1);
705 setenv("USER", pw
->pw_name
, 1);
706 setenv("SHELL", sh
, 1);
707 setenv("HOME", pw
->pw_dir
, 1);
708 setenv("TERM", termname
, 1);
710 signal(SIGCHLD
, SIG_DFL
);
711 signal(SIGHUP
, SIG_DFL
);
712 signal(SIGINT
, SIG_DFL
);
713 signal(SIGQUIT
, SIG_DFL
);
714 signal(SIGTERM
, SIG_DFL
);
715 signal(SIGALRM
, SIG_DFL
);
727 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
728 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
733 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
734 die("child finished with error '%d'\n", stat
);
742 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
745 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
746 die("incorrect stty parameters\n");
747 memcpy(cmd
, stty_args
, n
);
749 siz
= sizeof(cmd
) - n
;
750 for (p
= opt_cmd
; p
&& (s
= *p
); ++p
) {
751 if ((n
= strlen(s
)) > siz
-1)
752 die("stty parameter length too long\n");
759 if (system(cmd
) != 0)
760 perror("Couldn't call stty");
767 struct winsize w
= {term
.row
, term
.col
, 0, 0};
770 term
.mode
|= MODE_PRINT
;
771 iofd
= (!strcmp(opt_io
, "-")) ?
772 1 : open(opt_io
, O_WRONLY
| O_CREAT
, 0666);
774 fprintf(stderr
, "Error opening %s:%s\n",
775 opt_io
, strerror(errno
));
780 if ((cmdfd
= open(opt_line
, O_RDWR
)) < 0)
781 die("open line failed: %s\n", strerror(errno
));
787 /* seems to work fine on linux, openbsd and freebsd */
788 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
789 die("openpty failed: %s\n", strerror(errno
));
791 switch (pid
= fork()) {
793 die("fork failed\n");
797 setsid(); /* create a new process group */
801 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
802 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
810 signal(SIGCHLD
, sigchld
);
818 static char buf
[BUFSIZ
];
819 static int buflen
= 0;
821 int charsize
; /* size of utf8 char in bytes */
825 /* append read bytes to unprocessed bytes */
826 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
827 die("Couldn't read from shell: %s\n", strerror(errno
));
833 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
834 /* process a complete utf8 char */
835 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
845 tputc(*ptr
++ & 0xFF);
849 /* keep any uncomplete utf8 char for the next call */
851 memmove(buf
, ptr
, buflen
);
857 ttywrite(const char *s
, size_t n
)
864 * Remember that we are using a pty, which might be a modem line.
865 * Writing too much will clog the line. That's why we are doing this
867 * FIXME: Migrate the world to Plan 9.
875 /* Check if we can write. */
876 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
879 die("select failed: %s\n", strerror(errno
));
881 if (FD_ISSET(cmdfd
, &wfd
)) {
883 * Only write the bytes written by ttywrite() or the
884 * default of 256. This seems to be a reasonable value
885 * for a serial line. Bigger values might clog the I/O.
887 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
891 * We weren't able to write out everything.
892 * This means the buffer is getting full
900 /* All bytes have been written. */
904 if (FD_ISSET(cmdfd
, &rfd
))
910 die("write error on tty: %s\n", strerror(errno
));
914 ttysend(char *s
, size_t n
)
921 if (!IS_SET(MODE_ECHO
))
925 for (t
= s
; t
< lim
; t
+= len
) {
926 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
927 len
= utf8decode(t
, &u
, n
);
946 w
.ws_xpixel
= win
.tw
;
947 w
.ws_ypixel
= win
.th
;
948 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
949 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
957 for (i
= 0; i
< term
.row
-1; i
++) {
958 for (j
= 0; j
< term
.col
-1; j
++) {
959 if (term
.line
[i
][j
].mode
& attr
)
968 tsetdirt(int top
, int bot
)
972 LIMIT(top
, 0, term
.row
-1);
973 LIMIT(bot
, 0, term
.row
-1);
975 for (i
= top
; i
<= bot
; i
++)
980 tsetdirtattr(int attr
)
984 for (i
= 0; i
< term
.row
-1; i
++) {
985 for (j
= 0; j
< term
.col
-1; j
++) {
986 if (term
.line
[i
][j
].mode
& attr
) {
997 tsetdirt(0, term
.row
-1);
1003 static TCursor c
[2];
1004 int alt
= IS_SET(MODE_ALTSCREEN
);
1006 if (mode
== CURSOR_SAVE
) {
1008 } else if (mode
== CURSOR_LOAD
) {
1010 tmoveto(c
[alt
].x
, c
[alt
].y
);
1019 term
.c
= (TCursor
){{
1023 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1025 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1026 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1029 term
.bot
= term
.row
- 1;
1030 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1031 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1034 for (i
= 0; i
< 2; i
++) {
1036 tcursor(CURSOR_SAVE
);
1037 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1043 tnew(int col
, int row
)
1045 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1055 Line
*tmp
= term
.line
;
1057 term
.line
= term
.alt
;
1059 term
.mode
^= MODE_ALTSCREEN
;
1064 tscrolldown(int orig
, int n
)
1069 LIMIT(n
, 0, term
.bot
-orig
+1);
1071 tsetdirt(orig
, term
.bot
-n
);
1072 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1074 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1075 temp
= term
.line
[i
];
1076 term
.line
[i
] = term
.line
[i
-n
];
1077 term
.line
[i
-n
] = temp
;
1084 tscrollup(int orig
, int n
)
1089 LIMIT(n
, 0, term
.bot
-orig
+1);
1091 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1092 tsetdirt(orig
+n
, term
.bot
);
1094 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1095 temp
= term
.line
[i
];
1096 term
.line
[i
] = term
.line
[i
+n
];
1097 term
.line
[i
+n
] = temp
;
1100 selscroll(orig
, -n
);
1104 selscroll(int orig
, int n
)
1109 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1110 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1114 if (sel
.type
== SEL_RECTANGULAR
) {
1115 if (sel
.ob
.y
< term
.top
)
1116 sel
.ob
.y
= term
.top
;
1117 if (sel
.oe
.y
> term
.bot
)
1118 sel
.oe
.y
= term
.bot
;
1120 if (sel
.ob
.y
< term
.top
) {
1121 sel
.ob
.y
= term
.top
;
1124 if (sel
.oe
.y
> term
.bot
) {
1125 sel
.oe
.y
= term
.bot
;
1126 sel
.oe
.x
= term
.col
;
1134 tnewline(int first_col
)
1138 if (y
== term
.bot
) {
1139 tscrollup(term
.top
, 1);
1143 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1149 char *p
= csiescseq
.buf
, *np
;
1158 csiescseq
.buf
[csiescseq
.len
] = '\0';
1159 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1161 v
= strtol(p
, &np
, 10);
1164 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1166 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1168 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1172 csiescseq
.mode
[0] = *p
++;
1173 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1176 /* for absolute user moves, when decom is set */
1178 tmoveato(int x
, int y
)
1180 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1184 tmoveto(int x
, int y
)
1188 if (term
.c
.state
& CURSOR_ORIGIN
) {
1193 maxy
= term
.row
- 1;
1195 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1196 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1197 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1201 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1203 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1204 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1205 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1206 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1207 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1208 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1209 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1210 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1211 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1215 * The table is proudly stolen from rxvt.
1217 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1218 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1219 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1221 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1222 if (x
+1 < term
.col
) {
1223 term
.line
[y
][x
+1].u
= ' ';
1224 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1226 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1227 term
.line
[y
][x
-1].u
= ' ';
1228 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1232 term
.line
[y
][x
] = *attr
;
1233 term
.line
[y
][x
].u
= u
;
1237 tclearregion(int x1
, int y1
, int x2
, int y2
)
1243 temp
= x1
, x1
= x2
, x2
= temp
;
1245 temp
= y1
, y1
= y2
, y2
= temp
;
1247 LIMIT(x1
, 0, term
.col
-1);
1248 LIMIT(x2
, 0, term
.col
-1);
1249 LIMIT(y1
, 0, term
.row
-1);
1250 LIMIT(y2
, 0, term
.row
-1);
1252 for (y
= y1
; y
<= y2
; y
++) {
1254 for (x
= x1
; x
<= x2
; x
++) {
1255 gp
= &term
.line
[y
][x
];
1258 gp
->fg
= term
.c
.attr
.fg
;
1259 gp
->bg
= term
.c
.attr
.bg
;
1272 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1276 size
= term
.col
- src
;
1277 line
= term
.line
[term
.c
.y
];
1279 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1280 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1289 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1293 size
= term
.col
- dst
;
1294 line
= term
.line
[term
.c
.y
];
1296 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1297 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1301 tinsertblankline(int n
)
1303 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1304 tscrolldown(term
.c
.y
, n
);
1310 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1311 tscrollup(term
.c
.y
, n
);
1315 tdefcolor(int *attr
, int *npar
, int l
)
1320 switch (attr
[*npar
+ 1]) {
1321 case 2: /* direct color in RGB space */
1322 if (*npar
+ 4 >= l
) {
1324 "erresc(38): Incorrect number of parameters (%d)\n",
1328 r
= attr
[*npar
+ 2];
1329 g
= attr
[*npar
+ 3];
1330 b
= attr
[*npar
+ 4];
1332 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1333 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1336 idx
= TRUECOLOR(r
, g
, b
);
1338 case 5: /* indexed color */
1339 if (*npar
+ 2 >= l
) {
1341 "erresc(38): Incorrect number of parameters (%d)\n",
1346 if (!BETWEEN(attr
[*npar
], 0, 255))
1347 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1351 case 0: /* implemented defined (only foreground) */
1352 case 1: /* transparent */
1353 case 3: /* direct color in CMY space */
1354 case 4: /* direct color in CMYK space */
1357 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1365 tsetattr(int *attr
, int l
)
1370 for (i
= 0; i
< l
; i
++) {
1373 term
.c
.attr
.mode
&= ~(
1382 term
.c
.attr
.fg
= defaultfg
;
1383 term
.c
.attr
.bg
= defaultbg
;
1386 term
.c
.attr
.mode
|= ATTR_BOLD
;
1389 term
.c
.attr
.mode
|= ATTR_FAINT
;
1392 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1395 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1397 case 5: /* slow blink */
1399 case 6: /* rapid blink */
1400 term
.c
.attr
.mode
|= ATTR_BLINK
;
1403 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1406 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1409 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1412 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1415 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1418 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1421 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1424 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1427 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1430 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1433 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1434 term
.c
.attr
.fg
= idx
;
1437 term
.c
.attr
.fg
= defaultfg
;
1440 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1441 term
.c
.attr
.bg
= idx
;
1444 term
.c
.attr
.bg
= defaultbg
;
1447 if (BETWEEN(attr
[i
], 30, 37)) {
1448 term
.c
.attr
.fg
= attr
[i
] - 30;
1449 } else if (BETWEEN(attr
[i
], 40, 47)) {
1450 term
.c
.attr
.bg
= attr
[i
] - 40;
1451 } else if (BETWEEN(attr
[i
], 90, 97)) {
1452 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1453 } else if (BETWEEN(attr
[i
], 100, 107)) {
1454 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1457 "erresc(default): gfx attr %d unknown\n",
1458 attr
[i
]), csidump();
1466 tsetscroll(int t
, int b
)
1470 LIMIT(t
, 0, term
.row
-1);
1471 LIMIT(b
, 0, term
.row
-1);
1482 tsetmode(int priv
, int set
, int *args
, int narg
)
1487 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1490 case 1: /* DECCKM -- Cursor key */
1491 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1493 case 5: /* DECSCNM -- Reverse video */
1495 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1496 if (mode
!= term
.mode
)
1499 case 6: /* DECOM -- Origin */
1500 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1503 case 7: /* DECAWM -- Auto wrap */
1504 MODBIT(term
.mode
, set
, MODE_WRAP
);
1506 case 0: /* Error (IGNORED) */
1507 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1508 case 3: /* DECCOLM -- Column (IGNORED) */
1509 case 4: /* DECSCLM -- Scroll (IGNORED) */
1510 case 8: /* DECARM -- Auto repeat (IGNORED) */
1511 case 18: /* DECPFF -- Printer feed (IGNORED) */
1512 case 19: /* DECPEX -- Printer extent (IGNORED) */
1513 case 42: /* DECNRCM -- National characters (IGNORED) */
1514 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1516 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1517 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1519 case 9: /* X10 mouse compatibility mode */
1520 xsetpointermotion(0);
1521 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1522 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1524 case 1000: /* 1000: report button press */
1525 xsetpointermotion(0);
1526 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1527 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1529 case 1002: /* 1002: report motion on button press */
1530 xsetpointermotion(0);
1531 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1532 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1534 case 1003: /* 1003: enable all mouse motions */
1535 xsetpointermotion(set
);
1536 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1537 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1539 case 1004: /* 1004: send focus events to tty */
1540 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1542 case 1006: /* 1006: extended reporting mode */
1543 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1546 MODBIT(term
.mode
, set
, MODE_8BIT
);
1548 case 1049: /* swap screen & set/restore cursor as xterm */
1549 if (!allowaltscreen
)
1551 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1553 case 47: /* swap screen */
1555 if (!allowaltscreen
)
1557 alt
= IS_SET(MODE_ALTSCREEN
);
1559 tclearregion(0, 0, term
.col
-1,
1562 if (set
^ alt
) /* set is always 1 or 0 */
1568 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1570 case 2004: /* 2004: bracketed paste mode */
1571 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1573 /* Not implemented mouse modes. See comments there. */
1574 case 1001: /* mouse highlight mode; can hang the
1575 terminal by design when implemented. */
1576 case 1005: /* UTF-8 mouse mode; will confuse
1577 applications not supporting UTF-8
1579 case 1015: /* urxvt mangled mouse mode; incompatible
1580 and can be mistaken for other control
1584 "erresc: unknown private set/reset mode %d\n",
1590 case 0: /* Error (IGNORED) */
1592 case 2: /* KAM -- keyboard action */
1593 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1595 case 4: /* IRM -- Insertion-replacement */
1596 MODBIT(term
.mode
, set
, MODE_INSERT
);
1598 case 12: /* SRM -- Send/Receive */
1599 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1601 case 20: /* LNM -- Linefeed/new line */
1602 MODBIT(term
.mode
, set
, MODE_CRLF
);
1606 "erresc: unknown set/reset mode %d\n",
1620 switch (csiescseq
.mode
[0]) {
1623 fprintf(stderr
, "erresc: unknown csi ");
1627 case '@': /* ICH -- Insert <n> blank char */
1628 DEFAULT(csiescseq
.arg
[0], 1);
1629 tinsertblank(csiescseq
.arg
[0]);
1631 case 'A': /* CUU -- Cursor <n> Up */
1632 DEFAULT(csiescseq
.arg
[0], 1);
1633 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1635 case 'B': /* CUD -- Cursor <n> Down */
1636 case 'e': /* VPR --Cursor <n> Down */
1637 DEFAULT(csiescseq
.arg
[0], 1);
1638 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1640 case 'i': /* MC -- Media Copy */
1641 switch (csiescseq
.arg
[0]) {
1646 tdumpline(term
.c
.y
);
1652 term
.mode
&= ~MODE_PRINT
;
1655 term
.mode
|= MODE_PRINT
;
1659 case 'c': /* DA -- Device Attributes */
1660 if (csiescseq
.arg
[0] == 0)
1661 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1663 case 'C': /* CUF -- Cursor <n> Forward */
1664 case 'a': /* HPR -- Cursor <n> Forward */
1665 DEFAULT(csiescseq
.arg
[0], 1);
1666 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1668 case 'D': /* CUB -- Cursor <n> Backward */
1669 DEFAULT(csiescseq
.arg
[0], 1);
1670 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1672 case 'E': /* CNL -- Cursor <n> Down and first col */
1673 DEFAULT(csiescseq
.arg
[0], 1);
1674 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1676 case 'F': /* CPL -- Cursor <n> Up and first col */
1677 DEFAULT(csiescseq
.arg
[0], 1);
1678 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1680 case 'g': /* TBC -- Tabulation clear */
1681 switch (csiescseq
.arg
[0]) {
1682 case 0: /* clear current tab stop */
1683 term
.tabs
[term
.c
.x
] = 0;
1685 case 3: /* clear all the tabs */
1686 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1692 case 'G': /* CHA -- Move to <col> */
1694 DEFAULT(csiescseq
.arg
[0], 1);
1695 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1697 case 'H': /* CUP -- Move to <row> <col> */
1699 DEFAULT(csiescseq
.arg
[0], 1);
1700 DEFAULT(csiescseq
.arg
[1], 1);
1701 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1703 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1704 DEFAULT(csiescseq
.arg
[0], 1);
1705 tputtab(csiescseq
.arg
[0]);
1707 case 'J': /* ED -- Clear screen */
1709 switch (csiescseq
.arg
[0]) {
1711 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1712 if (term
.c
.y
< term
.row
-1) {
1713 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1719 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1720 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1723 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1729 case 'K': /* EL -- Clear line */
1730 switch (csiescseq
.arg
[0]) {
1732 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1736 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1739 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1743 case 'S': /* SU -- Scroll <n> line up */
1744 DEFAULT(csiescseq
.arg
[0], 1);
1745 tscrollup(term
.top
, csiescseq
.arg
[0]);
1747 case 'T': /* SD -- Scroll <n> line down */
1748 DEFAULT(csiescseq
.arg
[0], 1);
1749 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1751 case 'L': /* IL -- Insert <n> blank lines */
1752 DEFAULT(csiescseq
.arg
[0], 1);
1753 tinsertblankline(csiescseq
.arg
[0]);
1755 case 'l': /* RM -- Reset Mode */
1756 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1758 case 'M': /* DL -- Delete <n> lines */
1759 DEFAULT(csiescseq
.arg
[0], 1);
1760 tdeleteline(csiescseq
.arg
[0]);
1762 case 'X': /* ECH -- Erase <n> char */
1763 DEFAULT(csiescseq
.arg
[0], 1);
1764 tclearregion(term
.c
.x
, term
.c
.y
,
1765 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1767 case 'P': /* DCH -- Delete <n> char */
1768 DEFAULT(csiescseq
.arg
[0], 1);
1769 tdeletechar(csiescseq
.arg
[0]);
1771 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1772 DEFAULT(csiescseq
.arg
[0], 1);
1773 tputtab(-csiescseq
.arg
[0]);
1775 case 'd': /* VPA -- Move to <row> */
1776 DEFAULT(csiescseq
.arg
[0], 1);
1777 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1779 case 'h': /* SM -- Set terminal mode */
1780 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1782 case 'm': /* SGR -- Terminal attribute (color) */
1783 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1785 case 'n': /* DSR – Device Status Report (cursor position) */
1786 if (csiescseq
.arg
[0] == 6) {
1787 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1788 term
.c
.y
+1, term
.c
.x
+1);
1792 case 'r': /* DECSTBM -- Set Scrolling Region */
1793 if (csiescseq
.priv
) {
1796 DEFAULT(csiescseq
.arg
[0], 1);
1797 DEFAULT(csiescseq
.arg
[1], term
.row
);
1798 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1802 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1803 tcursor(CURSOR_SAVE
);
1805 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1806 tcursor(CURSOR_LOAD
);
1809 switch (csiescseq
.mode
[1]) {
1810 case 'q': /* DECSCUSR -- Set Cursor Style */
1811 DEFAULT(csiescseq
.arg
[0], 1);
1812 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1815 win
.cursor
= csiescseq
.arg
[0];
1830 fprintf(stderr
, "ESC[");
1831 for (i
= 0; i
< csiescseq
.len
; i
++) {
1832 c
= csiescseq
.buf
[i
] & 0xff;
1835 } else if (c
== '\n') {
1836 fprintf(stderr
, "(\\n)");
1837 } else if (c
== '\r') {
1838 fprintf(stderr
, "(\\r)");
1839 } else if (c
== 0x1b) {
1840 fprintf(stderr
, "(\\e)");
1842 fprintf(stderr
, "(%02x)", c
);
1851 memset(&csiescseq
, 0, sizeof(csiescseq
));
1860 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1862 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1864 switch (strescseq
.type
) {
1865 case ']': /* OSC -- Operating System Command */
1871 xsettitle(strescseq
.args
[1]);
1877 dec
= base64dec(strescseq
.args
[2]);
1879 xsetsel(dec
, CurrentTime
);
1882 fprintf(stderr
, "erresc: invalid base64\n");
1886 case 4: /* color set */
1889 p
= strescseq
.args
[2];
1891 case 104: /* color reset, here p = NULL */
1892 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1893 if (xsetcolorname(j
, p
)) {
1894 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1897 * TODO if defaultbg color is changed, borders
1905 case 'k': /* old title set compatibility */
1906 xsettitle(strescseq
.args
[0]);
1908 case 'P': /* DCS -- Device Control String */
1909 term
.mode
|= ESC_DCS
;
1910 case '_': /* APC -- Application Program Command */
1911 case '^': /* PM -- Privacy Message */
1915 fprintf(stderr
, "erresc: unknown str ");
1923 char *p
= strescseq
.buf
;
1926 strescseq
.buf
[strescseq
.len
] = '\0';
1931 while (strescseq
.narg
< STR_ARG_SIZ
) {
1932 strescseq
.args
[strescseq
.narg
++] = p
;
1933 while ((c
= *p
) != ';' && c
!= '\0')
1947 fprintf(stderr
, "ESC%c", strescseq
.type
);
1948 for (i
= 0; i
< strescseq
.len
; i
++) {
1949 c
= strescseq
.buf
[i
] & 0xff;
1953 } else if (isprint(c
)) {
1955 } else if (c
== '\n') {
1956 fprintf(stderr
, "(\\n)");
1957 } else if (c
== '\r') {
1958 fprintf(stderr
, "(\\r)");
1959 } else if (c
== 0x1b) {
1960 fprintf(stderr
, "(\\e)");
1962 fprintf(stderr
, "(%02x)", c
);
1965 fprintf(stderr
, "ESC\\\n");
1971 memset(&strescseq
, 0, sizeof(strescseq
));
1975 sendbreak(const Arg
*arg
)
1977 if (tcsendbreak(cmdfd
, 0))
1978 perror("Error sending break");
1982 tprinter(char *s
, size_t len
)
1984 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1985 fprintf(stderr
, "Error writing in %s:%s\n",
1986 opt_io
, strerror(errno
));
1993 iso14755(const Arg
*arg
)
1996 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1997 unsigned long utf32
;
1999 if (!(p
= popen(ISO14755CMD
, "r")))
2002 us
= fgets(codepoint
, sizeof(codepoint
), p
);
2005 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
2007 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
2008 (*e
!= '\n' && *e
!= '\0'))
2011 ttysend(uc
, utf8encode(utf32
, uc
));
2015 toggleprinter(const Arg
*arg
)
2017 term
.mode
^= MODE_PRINT
;
2021 printscreen(const Arg
*arg
)
2027 printsel(const Arg
*arg
)
2037 if ((ptr
= getsel())) {
2038 tprinter(ptr
, strlen(ptr
));
2049 bp
= &term
.line
[n
][0];
2050 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2051 if (bp
!= end
|| bp
->u
!= ' ') {
2052 for ( ;bp
<= end
; ++bp
)
2053 tprinter(buf
, utf8encode(bp
->u
, buf
));
2063 for (i
= 0; i
< term
.row
; ++i
)
2073 while (x
< term
.col
&& n
--)
2074 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2077 while (x
> 0 && n
++)
2078 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2081 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2087 if (ISCONTROL(u
)) { /* control code */
2092 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2101 tdefutf8(char ascii
)
2104 term
.mode
|= MODE_UTF8
;
2105 else if (ascii
== '@')
2106 term
.mode
&= ~MODE_UTF8
;
2110 tdeftran(char ascii
)
2112 static char cs
[] = "0B";
2113 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2116 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2117 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2119 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2128 if (c
== '8') { /* DEC screen alignment test. */
2129 for (x
= 0; x
< term
.col
; ++x
) {
2130 for (y
= 0; y
< term
.row
; ++y
)
2131 tsetchar('E', &term
.c
.attr
, x
, y
);
2137 tstrsequence(uchar c
)
2142 case 0x90: /* DCS -- Device Control String */
2144 term
.esc
|= ESC_DCS
;
2146 case 0x9f: /* APC -- Application Program Command */
2149 case 0x9e: /* PM -- Privacy Message */
2152 case 0x9d: /* OSC -- Operating System Command */
2157 term
.esc
|= ESC_STR
;
2161 tcontrolcode(uchar ascii
)
2168 tmoveto(term
.c
.x
-1, term
.c
.y
);
2171 tmoveto(0, term
.c
.y
);
2176 /* go to first col if the mode is set */
2177 tnewline(IS_SET(MODE_CRLF
));
2179 case '\a': /* BEL */
2180 if (term
.esc
& ESC_STR_END
) {
2181 /* backwards compatibility to xterm */
2184 if (!(win
.state
& WIN_FOCUSED
))
2190 case '\033': /* ESC */
2192 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2193 term
.esc
|= ESC_START
;
2195 case '\016': /* SO (LS1 -- Locking shift 1) */
2196 case '\017': /* SI (LS0 -- Locking shift 0) */
2197 term
.charset
= 1 - (ascii
- '\016');
2199 case '\032': /* SUB */
2200 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2201 case '\030': /* CAN */
2204 case '\005': /* ENQ (IGNORED) */
2205 case '\000': /* NUL (IGNORED) */
2206 case '\021': /* XON (IGNORED) */
2207 case '\023': /* XOFF (IGNORED) */
2208 case 0177: /* DEL (IGNORED) */
2210 case 0x80: /* TODO: PAD */
2211 case 0x81: /* TODO: HOP */
2212 case 0x82: /* TODO: BPH */
2213 case 0x83: /* TODO: NBH */
2214 case 0x84: /* TODO: IND */
2216 case 0x85: /* NEL -- Next line */
2217 tnewline(1); /* always go to first col */
2219 case 0x86: /* TODO: SSA */
2220 case 0x87: /* TODO: ESA */
2222 case 0x88: /* HTS -- Horizontal tab stop */
2223 term
.tabs
[term
.c
.x
] = 1;
2225 case 0x89: /* TODO: HTJ */
2226 case 0x8a: /* TODO: VTS */
2227 case 0x8b: /* TODO: PLD */
2228 case 0x8c: /* TODO: PLU */
2229 case 0x8d: /* TODO: RI */
2230 case 0x8e: /* TODO: SS2 */
2231 case 0x8f: /* TODO: SS3 */
2232 case 0x91: /* TODO: PU1 */
2233 case 0x92: /* TODO: PU2 */
2234 case 0x93: /* TODO: STS */
2235 case 0x94: /* TODO: CCH */
2236 case 0x95: /* TODO: MW */
2237 case 0x96: /* TODO: SPA */
2238 case 0x97: /* TODO: EPA */
2239 case 0x98: /* TODO: SOS */
2240 case 0x99: /* TODO: SGCI */
2242 case 0x9a: /* DECID -- Identify Terminal */
2243 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2245 case 0x9b: /* TODO: CSI */
2246 case 0x9c: /* TODO: ST */
2248 case 0x90: /* DCS -- Device Control String */
2249 case 0x9d: /* OSC -- Operating System Command */
2250 case 0x9e: /* PM -- Privacy Message */
2251 case 0x9f: /* APC -- Application Program Command */
2252 tstrsequence(ascii
);
2255 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2256 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2260 * returns 1 when the sequence is finished and it hasn't to read
2261 * more characters for this sequence, otherwise 0
2264 eschandle(uchar ascii
)
2268 term
.esc
|= ESC_CSI
;
2271 term
.esc
|= ESC_TEST
;
2274 term
.esc
|= ESC_UTF8
;
2276 case 'P': /* DCS -- Device Control String */
2277 case '_': /* APC -- Application Program Command */
2278 case '^': /* PM -- Privacy Message */
2279 case ']': /* OSC -- Operating System Command */
2280 case 'k': /* old title set compatibility */
2281 tstrsequence(ascii
);
2283 case 'n': /* LS2 -- Locking shift 2 */
2284 case 'o': /* LS3 -- Locking shift 3 */
2285 term
.charset
= 2 + (ascii
- 'n');
2287 case '(': /* GZD4 -- set primary charset G0 */
2288 case ')': /* G1D4 -- set secondary charset G1 */
2289 case '*': /* G2D4 -- set tertiary charset G2 */
2290 case '+': /* G3D4 -- set quaternary charset G3 */
2291 term
.icharset
= ascii
- '(';
2292 term
.esc
|= ESC_ALTCHARSET
;
2294 case 'D': /* IND -- Linefeed */
2295 if (term
.c
.y
== term
.bot
) {
2296 tscrollup(term
.top
, 1);
2298 tmoveto(term
.c
.x
, term
.c
.y
+1);
2301 case 'E': /* NEL -- Next line */
2302 tnewline(1); /* always go to first col */
2304 case 'H': /* HTS -- Horizontal tab stop */
2305 term
.tabs
[term
.c
.x
] = 1;
2307 case 'M': /* RI -- Reverse index */
2308 if (term
.c
.y
== term
.top
) {
2309 tscrolldown(term
.top
, 1);
2311 tmoveto(term
.c
.x
, term
.c
.y
-1);
2314 case 'Z': /* DECID -- Identify Terminal */
2315 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2317 case 'c': /* RIS -- Reset to inital state */
2322 case '=': /* DECPAM -- Application keypad */
2323 term
.mode
|= MODE_APPKEYPAD
;
2325 case '>': /* DECPNM -- Normal keypad */
2326 term
.mode
&= ~MODE_APPKEYPAD
;
2328 case '7': /* DECSC -- Save Cursor */
2329 tcursor(CURSOR_SAVE
);
2331 case '8': /* DECRC -- Restore Cursor */
2332 tcursor(CURSOR_LOAD
);
2334 case '\\': /* ST -- String Terminator */
2335 if (term
.esc
& ESC_STR_END
)
2339 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2340 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2354 control
= ISCONTROL(u
);
2355 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2359 len
= utf8encode(u
, c
);
2360 if (!control
&& (width
= wcwidth(u
)) == -1) {
2361 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2366 if (IS_SET(MODE_PRINT
))
2370 * STR sequence must be checked before anything else
2371 * because it uses all following characters until it
2372 * receives a ESC, a SUB, a ST or any other C1 control
2375 if (term
.esc
& ESC_STR
) {
2376 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2378 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2379 if (IS_SET(MODE_SIXEL
)) {
2380 /* TODO: render sixel */;
2381 term
.mode
&= ~MODE_SIXEL
;
2384 term
.esc
|= ESC_STR_END
;
2385 goto check_control_code
;
2389 if (IS_SET(MODE_SIXEL
)) {
2390 /* TODO: implement sixel mode */
2393 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2394 term
.mode
|= MODE_SIXEL
;
2396 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2398 * Here is a bug in terminals. If the user never sends
2399 * some code to stop the str or esc command, then st
2400 * will stop responding. But this is better than
2401 * silently failing with unknown characters. At least
2402 * then users will report back.
2404 * In the case users ever get fixed, here is the code:
2413 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2414 strescseq
.len
+= len
;
2420 * Actions of control codes must be performed as soon they arrive
2421 * because they can be embedded inside a control sequence, and
2422 * they must not cause conflicts with sequences.
2427 * control codes are not shown ever
2430 } else if (term
.esc
& ESC_START
) {
2431 if (term
.esc
& ESC_CSI
) {
2432 csiescseq
.buf
[csiescseq
.len
++] = u
;
2433 if (BETWEEN(u
, 0x40, 0x7E)
2434 || csiescseq
.len
>= \
2435 sizeof(csiescseq
.buf
)-1) {
2441 } else if (term
.esc
& ESC_UTF8
) {
2443 } else if (term
.esc
& ESC_ALTCHARSET
) {
2445 } else if (term
.esc
& ESC_TEST
) {
2450 /* sequence already finished */
2454 * All characters which form part of a sequence are not
2459 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2462 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2463 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2464 gp
->mode
|= ATTR_WRAP
;
2466 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2469 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2470 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2472 if (term
.c
.x
+width
> term
.col
) {
2474 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2477 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2480 gp
->mode
|= ATTR_WIDE
;
2481 if (term
.c
.x
+1 < term
.col
) {
2483 gp
[1].mode
= ATTR_WDUMMY
;
2486 if (term
.c
.x
+width
< term
.col
) {
2487 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2489 term
.c
.state
|= CURSOR_WRAPNEXT
;
2494 tresize(int col
, int row
)
2497 int minrow
= MIN(row
, term
.row
);
2498 int mincol
= MIN(col
, term
.col
);
2502 if (col
< 1 || row
< 1) {
2504 "tresize: error resizing to %dx%d\n", col
, row
);
2509 * slide screen to keep cursor where we expect it -
2510 * tscrollup would work here, but we can optimize to
2511 * memmove because we're freeing the earlier lines
2513 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2517 /* ensure that both src and dst are not NULL */
2519 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2520 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2522 for (i
+= row
; i
< term
.row
; i
++) {
2527 /* resize to new width */
2528 term
.specbuf
= xrealloc(term
.specbuf
, col
* sizeof(GlyphFontSpec
));
2530 /* resize to new height */
2531 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2532 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2533 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2534 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2536 /* resize each row to new width, zero-pad if needed */
2537 for (i
= 0; i
< minrow
; i
++) {
2538 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2539 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2542 /* allocate any new rows */
2543 for (/* i = minrow */; i
< row
; i
++) {
2544 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2545 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2547 if (col
> term
.col
) {
2548 bp
= term
.tabs
+ term
.col
;
2550 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2551 while (--bp
> term
.tabs
&& !*bp
)
2553 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2556 /* update terminal size */
2559 /* reset scrolling region */
2560 tsetscroll(0, row
-1);
2561 /* make use of the LIMIT in tmoveto */
2562 tmoveto(term
.c
.x
, term
.c
.y
);
2563 /* Clearing both screens (it makes dirty all lines) */
2565 for (i
= 0; i
< 2; i
++) {
2566 if (mincol
< col
&& 0 < minrow
) {
2567 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2569 if (0 < col
&& minrow
< row
) {
2570 tclearregion(0, minrow
, col
- 1, row
- 1);
2573 tcursor(CURSOR_LOAD
);
2579 zoom(const Arg
*arg
)
2583 larg
.f
= usedfontsize
+ arg
->f
;
2588 zoomabs(const Arg
*arg
)
2591 xloadfonts(usedfont
, arg
->f
);
2599 zoomreset(const Arg
*arg
)
2603 if (defaultfontsize
> 0) {
2604 larg
.f
= defaultfontsize
;
2612 xsettitle(opt_title
? opt_title
: "st");
2623 match(uint mask
, uint state
)
2625 return mask
== XK_ANY_MOD
|| mask
== (state
& ~ignoremod
);
2629 numlock(const Arg
*dummy
)
2635 kmap(KeySym k
, uint state
)
2640 /* Check for mapped keys out of X11 function keys. */
2641 for (i
= 0; i
< LEN(mappedkeys
); i
++) {
2642 if (mappedkeys
[i
] == k
)
2645 if (i
== LEN(mappedkeys
)) {
2646 if ((k
& 0xFFFF) < 0xFD00)
2650 for (kp
= key
; kp
< key
+ LEN(key
); kp
++) {
2654 if (!match(kp
->mask
, state
))
2657 if (IS_SET(MODE_APPKEYPAD
) ? kp
->appkey
< 0 : kp
->appkey
> 0)
2659 if (term
.numlock
&& kp
->appkey
== 2)
2662 if (IS_SET(MODE_APPCURSOR
) ? kp
->appcursor
< 0 : kp
->appcursor
> 0)
2665 if (IS_SET(MODE_CRLF
) ? kp
->crlf
< 0 : kp
->crlf
> 0)
2675 cresize(int width
, int height
)
2684 col
= (win
.w
- 2 * borderpx
) / win
.cw
;
2685 row
= (win
.h
- 2 * borderpx
) / win
.ch
;
2694 die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
2695 " [-n name] [-o file]\n"
2696 " [-T title] [-t title] [-w windowid]"
2697 " [[-e] command [args ...]]\n"
2698 " %s [-aiv] [-c class] [-f font] [-g geometry]"
2699 " [-n name] [-o file]\n"
2700 " [-T title] [-t title] [-w windowid] -l line"
2701 " [stty_args ...]\n", argv0
, argv0
);