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 %lu -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);
711 signal(SIGCHLD
, SIG_DFL
);
712 signal(SIGHUP
, SIG_DFL
);
713 signal(SIGINT
, SIG_DFL
);
714 signal(SIGQUIT
, SIG_DFL
);
715 signal(SIGTERM
, SIG_DFL
);
716 signal(SIGALRM
, SIG_DFL
);
728 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
729 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
734 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
735 die("child finished with error '%d'\n", stat
);
743 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
746 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
747 die("incorrect stty parameters\n");
748 memcpy(cmd
, stty_args
, n
);
750 siz
= sizeof(cmd
) - n
;
751 for (p
= opt_cmd
; p
&& (s
= *p
); ++p
) {
752 if ((n
= strlen(s
)) > siz
-1)
753 die("stty parameter length too long\n");
760 if (system(cmd
) != 0)
761 perror("Couldn't call stty");
768 struct winsize w
= {term
.row
, term
.col
, 0, 0};
771 term
.mode
|= MODE_PRINT
;
772 iofd
= (!strcmp(opt_io
, "-")) ?
773 1 : open(opt_io
, O_WRONLY
| O_CREAT
, 0666);
775 fprintf(stderr
, "Error opening %s:%s\n",
776 opt_io
, strerror(errno
));
781 if ((cmdfd
= open(opt_line
, O_RDWR
)) < 0)
782 die("open line failed: %s\n", strerror(errno
));
788 /* seems to work fine on linux, openbsd and freebsd */
789 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
790 die("openpty failed: %s\n", strerror(errno
));
792 switch (pid
= fork()) {
794 die("fork failed\n");
798 setsid(); /* create a new process group */
802 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
803 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
811 signal(SIGCHLD
, sigchld
);
819 static char buf
[BUFSIZ
];
820 static int buflen
= 0;
822 int charsize
; /* size of utf8 char in bytes */
826 /* append read bytes to unprocessed bytes */
827 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
828 die("Couldn't read from shell: %s\n", strerror(errno
));
834 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
835 /* process a complete utf8 char */
836 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
846 tputc(*ptr
++ & 0xFF);
850 /* keep any uncomplete utf8 char for the next call */
852 memmove(buf
, ptr
, buflen
);
858 ttywrite(const char *s
, size_t n
)
865 * Remember that we are using a pty, which might be a modem line.
866 * Writing too much will clog the line. That's why we are doing this
868 * FIXME: Migrate the world to Plan 9.
876 /* Check if we can write. */
877 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
880 die("select failed: %s\n", strerror(errno
));
882 if (FD_ISSET(cmdfd
, &wfd
)) {
884 * Only write the bytes written by ttywrite() or the
885 * default of 256. This seems to be a reasonable value
886 * for a serial line. Bigger values might clog the I/O.
888 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
892 * We weren't able to write out everything.
893 * This means the buffer is getting full
901 /* All bytes have been written. */
905 if (FD_ISSET(cmdfd
, &rfd
))
911 die("write error on tty: %s\n", strerror(errno
));
915 ttysend(char *s
, size_t n
)
922 if (!IS_SET(MODE_ECHO
))
926 for (t
= s
; t
< lim
; t
+= len
) {
927 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
928 len
= utf8decode(t
, &u
, n
);
947 w
.ws_xpixel
= win
.tw
;
948 w
.ws_ypixel
= win
.th
;
949 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
950 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
958 for (i
= 0; i
< term
.row
-1; i
++) {
959 for (j
= 0; j
< term
.col
-1; j
++) {
960 if (term
.line
[i
][j
].mode
& attr
)
969 tsetdirt(int top
, int bot
)
973 LIMIT(top
, 0, term
.row
-1);
974 LIMIT(bot
, 0, term
.row
-1);
976 for (i
= top
; i
<= bot
; i
++)
981 tsetdirtattr(int attr
)
985 for (i
= 0; i
< term
.row
-1; i
++) {
986 for (j
= 0; j
< term
.col
-1; j
++) {
987 if (term
.line
[i
][j
].mode
& attr
) {
998 tsetdirt(0, term
.row
-1);
1004 static TCursor c
[2];
1005 int alt
= IS_SET(MODE_ALTSCREEN
);
1007 if (mode
== CURSOR_SAVE
) {
1009 } else if (mode
== CURSOR_LOAD
) {
1011 tmoveto(c
[alt
].x
, c
[alt
].y
);
1020 term
.c
= (TCursor
){{
1024 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1026 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1027 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1030 term
.bot
= term
.row
- 1;
1031 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1032 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1035 for (i
= 0; i
< 2; i
++) {
1037 tcursor(CURSOR_SAVE
);
1038 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1044 tnew(int col
, int row
)
1046 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1056 Line
*tmp
= term
.line
;
1058 term
.line
= term
.alt
;
1060 term
.mode
^= MODE_ALTSCREEN
;
1065 tscrolldown(int orig
, int n
)
1070 LIMIT(n
, 0, term
.bot
-orig
+1);
1072 tsetdirt(orig
, term
.bot
-n
);
1073 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1075 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1076 temp
= term
.line
[i
];
1077 term
.line
[i
] = term
.line
[i
-n
];
1078 term
.line
[i
-n
] = temp
;
1085 tscrollup(int orig
, int n
)
1090 LIMIT(n
, 0, term
.bot
-orig
+1);
1092 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1093 tsetdirt(orig
+n
, term
.bot
);
1095 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1096 temp
= term
.line
[i
];
1097 term
.line
[i
] = term
.line
[i
+n
];
1098 term
.line
[i
+n
] = temp
;
1101 selscroll(orig
, -n
);
1105 selscroll(int orig
, int n
)
1110 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1111 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1115 if (sel
.type
== SEL_RECTANGULAR
) {
1116 if (sel
.ob
.y
< term
.top
)
1117 sel
.ob
.y
= term
.top
;
1118 if (sel
.oe
.y
> term
.bot
)
1119 sel
.oe
.y
= term
.bot
;
1121 if (sel
.ob
.y
< term
.top
) {
1122 sel
.ob
.y
= term
.top
;
1125 if (sel
.oe
.y
> term
.bot
) {
1126 sel
.oe
.y
= term
.bot
;
1127 sel
.oe
.x
= term
.col
;
1135 tnewline(int first_col
)
1139 if (y
== term
.bot
) {
1140 tscrollup(term
.top
, 1);
1144 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1150 char *p
= csiescseq
.buf
, *np
;
1159 csiescseq
.buf
[csiescseq
.len
] = '\0';
1160 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1162 v
= strtol(p
, &np
, 10);
1165 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1167 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1169 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1173 csiescseq
.mode
[0] = *p
++;
1174 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1177 /* for absolute user moves, when decom is set */
1179 tmoveato(int x
, int y
)
1181 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1185 tmoveto(int x
, int y
)
1189 if (term
.c
.state
& CURSOR_ORIGIN
) {
1194 maxy
= term
.row
- 1;
1196 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1197 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1198 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1202 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1204 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1205 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1206 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1207 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1208 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1209 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1210 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1211 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1212 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1216 * The table is proudly stolen from rxvt.
1218 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1219 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1220 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1222 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1223 if (x
+1 < term
.col
) {
1224 term
.line
[y
][x
+1].u
= ' ';
1225 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1227 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1228 term
.line
[y
][x
-1].u
= ' ';
1229 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1233 term
.line
[y
][x
] = *attr
;
1234 term
.line
[y
][x
].u
= u
;
1238 tclearregion(int x1
, int y1
, int x2
, int y2
)
1244 temp
= x1
, x1
= x2
, x2
= temp
;
1246 temp
= y1
, y1
= y2
, y2
= temp
;
1248 LIMIT(x1
, 0, term
.col
-1);
1249 LIMIT(x2
, 0, term
.col
-1);
1250 LIMIT(y1
, 0, term
.row
-1);
1251 LIMIT(y2
, 0, term
.row
-1);
1253 for (y
= y1
; y
<= y2
; y
++) {
1255 for (x
= x1
; x
<= x2
; x
++) {
1256 gp
= &term
.line
[y
][x
];
1259 gp
->fg
= term
.c
.attr
.fg
;
1260 gp
->bg
= term
.c
.attr
.bg
;
1273 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1277 size
= term
.col
- src
;
1278 line
= term
.line
[term
.c
.y
];
1280 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1281 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1290 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1294 size
= term
.col
- dst
;
1295 line
= term
.line
[term
.c
.y
];
1297 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1298 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1302 tinsertblankline(int n
)
1304 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1305 tscrolldown(term
.c
.y
, n
);
1311 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1312 tscrollup(term
.c
.y
, n
);
1316 tdefcolor(int *attr
, int *npar
, int l
)
1321 switch (attr
[*npar
+ 1]) {
1322 case 2: /* direct color in RGB space */
1323 if (*npar
+ 4 >= l
) {
1325 "erresc(38): Incorrect number of parameters (%d)\n",
1329 r
= attr
[*npar
+ 2];
1330 g
= attr
[*npar
+ 3];
1331 b
= attr
[*npar
+ 4];
1333 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1334 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1337 idx
= TRUECOLOR(r
, g
, b
);
1339 case 5: /* indexed color */
1340 if (*npar
+ 2 >= l
) {
1342 "erresc(38): Incorrect number of parameters (%d)\n",
1347 if (!BETWEEN(attr
[*npar
], 0, 255))
1348 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1352 case 0: /* implemented defined (only foreground) */
1353 case 1: /* transparent */
1354 case 3: /* direct color in CMY space */
1355 case 4: /* direct color in CMYK space */
1358 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1366 tsetattr(int *attr
, int l
)
1371 for (i
= 0; i
< l
; i
++) {
1374 term
.c
.attr
.mode
&= ~(
1383 term
.c
.attr
.fg
= defaultfg
;
1384 term
.c
.attr
.bg
= defaultbg
;
1387 term
.c
.attr
.mode
|= ATTR_BOLD
;
1390 term
.c
.attr
.mode
|= ATTR_FAINT
;
1393 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1396 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1398 case 5: /* slow blink */
1400 case 6: /* rapid blink */
1401 term
.c
.attr
.mode
|= ATTR_BLINK
;
1404 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1407 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1410 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1413 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1416 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1419 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1422 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1425 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1428 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1431 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1434 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1435 term
.c
.attr
.fg
= idx
;
1438 term
.c
.attr
.fg
= defaultfg
;
1441 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1442 term
.c
.attr
.bg
= idx
;
1445 term
.c
.attr
.bg
= defaultbg
;
1448 if (BETWEEN(attr
[i
], 30, 37)) {
1449 term
.c
.attr
.fg
= attr
[i
] - 30;
1450 } else if (BETWEEN(attr
[i
], 40, 47)) {
1451 term
.c
.attr
.bg
= attr
[i
] - 40;
1452 } else if (BETWEEN(attr
[i
], 90, 97)) {
1453 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1454 } else if (BETWEEN(attr
[i
], 100, 107)) {
1455 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1458 "erresc(default): gfx attr %d unknown\n",
1459 attr
[i
]), csidump();
1467 tsetscroll(int t
, int b
)
1471 LIMIT(t
, 0, term
.row
-1);
1472 LIMIT(b
, 0, term
.row
-1);
1483 tsetmode(int priv
, int set
, int *args
, int narg
)
1488 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1491 case 1: /* DECCKM -- Cursor key */
1492 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1494 case 5: /* DECSCNM -- Reverse video */
1496 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1497 if (mode
!= term
.mode
)
1500 case 6: /* DECOM -- Origin */
1501 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1504 case 7: /* DECAWM -- Auto wrap */
1505 MODBIT(term
.mode
, set
, MODE_WRAP
);
1507 case 0: /* Error (IGNORED) */
1508 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1509 case 3: /* DECCOLM -- Column (IGNORED) */
1510 case 4: /* DECSCLM -- Scroll (IGNORED) */
1511 case 8: /* DECARM -- Auto repeat (IGNORED) */
1512 case 18: /* DECPFF -- Printer feed (IGNORED) */
1513 case 19: /* DECPEX -- Printer extent (IGNORED) */
1514 case 42: /* DECNRCM -- National characters (IGNORED) */
1515 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1517 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1518 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1520 case 9: /* X10 mouse compatibility mode */
1521 xsetpointermotion(0);
1522 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1523 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1525 case 1000: /* 1000: report button press */
1526 xsetpointermotion(0);
1527 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1528 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1530 case 1002: /* 1002: report motion on button press */
1531 xsetpointermotion(0);
1532 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1533 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1535 case 1003: /* 1003: enable all mouse motions */
1536 xsetpointermotion(set
);
1537 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1538 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1540 case 1004: /* 1004: send focus events to tty */
1541 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1543 case 1006: /* 1006: extended reporting mode */
1544 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1547 MODBIT(term
.mode
, set
, MODE_8BIT
);
1549 case 1049: /* swap screen & set/restore cursor as xterm */
1550 if (!allowaltscreen
)
1552 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1554 case 47: /* swap screen */
1556 if (!allowaltscreen
)
1558 alt
= IS_SET(MODE_ALTSCREEN
);
1560 tclearregion(0, 0, term
.col
-1,
1563 if (set
^ alt
) /* set is always 1 or 0 */
1569 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1571 case 2004: /* 2004: bracketed paste mode */
1572 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1574 /* Not implemented mouse modes. See comments there. */
1575 case 1001: /* mouse highlight mode; can hang the
1576 terminal by design when implemented. */
1577 case 1005: /* UTF-8 mouse mode; will confuse
1578 applications not supporting UTF-8
1580 case 1015: /* urxvt mangled mouse mode; incompatible
1581 and can be mistaken for other control
1585 "erresc: unknown private set/reset mode %d\n",
1591 case 0: /* Error (IGNORED) */
1593 case 2: /* KAM -- keyboard action */
1594 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1596 case 4: /* IRM -- Insertion-replacement */
1597 MODBIT(term
.mode
, set
, MODE_INSERT
);
1599 case 12: /* SRM -- Send/Receive */
1600 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1602 case 20: /* LNM -- Linefeed/new line */
1603 MODBIT(term
.mode
, set
, MODE_CRLF
);
1607 "erresc: unknown set/reset mode %d\n",
1621 switch (csiescseq
.mode
[0]) {
1624 fprintf(stderr
, "erresc: unknown csi ");
1628 case '@': /* ICH -- Insert <n> blank char */
1629 DEFAULT(csiescseq
.arg
[0], 1);
1630 tinsertblank(csiescseq
.arg
[0]);
1632 case 'A': /* CUU -- Cursor <n> Up */
1633 DEFAULT(csiescseq
.arg
[0], 1);
1634 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1636 case 'B': /* CUD -- Cursor <n> Down */
1637 case 'e': /* VPR --Cursor <n> Down */
1638 DEFAULT(csiescseq
.arg
[0], 1);
1639 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1641 case 'i': /* MC -- Media Copy */
1642 switch (csiescseq
.arg
[0]) {
1647 tdumpline(term
.c
.y
);
1653 term
.mode
&= ~MODE_PRINT
;
1656 term
.mode
|= MODE_PRINT
;
1660 case 'c': /* DA -- Device Attributes */
1661 if (csiescseq
.arg
[0] == 0)
1662 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1664 case 'C': /* CUF -- Cursor <n> Forward */
1665 case 'a': /* HPR -- Cursor <n> Forward */
1666 DEFAULT(csiescseq
.arg
[0], 1);
1667 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1669 case 'D': /* CUB -- Cursor <n> Backward */
1670 DEFAULT(csiescseq
.arg
[0], 1);
1671 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1673 case 'E': /* CNL -- Cursor <n> Down and first col */
1674 DEFAULT(csiescseq
.arg
[0], 1);
1675 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1677 case 'F': /* CPL -- Cursor <n> Up and first col */
1678 DEFAULT(csiescseq
.arg
[0], 1);
1679 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1681 case 'g': /* TBC -- Tabulation clear */
1682 switch (csiescseq
.arg
[0]) {
1683 case 0: /* clear current tab stop */
1684 term
.tabs
[term
.c
.x
] = 0;
1686 case 3: /* clear all the tabs */
1687 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1693 case 'G': /* CHA -- Move to <col> */
1695 DEFAULT(csiescseq
.arg
[0], 1);
1696 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1698 case 'H': /* CUP -- Move to <row> <col> */
1700 DEFAULT(csiescseq
.arg
[0], 1);
1701 DEFAULT(csiescseq
.arg
[1], 1);
1702 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1704 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1705 DEFAULT(csiescseq
.arg
[0], 1);
1706 tputtab(csiescseq
.arg
[0]);
1708 case 'J': /* ED -- Clear screen */
1710 switch (csiescseq
.arg
[0]) {
1712 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1713 if (term
.c
.y
< term
.row
-1) {
1714 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1720 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1721 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1724 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1730 case 'K': /* EL -- Clear line */
1731 switch (csiescseq
.arg
[0]) {
1733 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1737 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1740 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1744 case 'S': /* SU -- Scroll <n> line up */
1745 DEFAULT(csiescseq
.arg
[0], 1);
1746 tscrollup(term
.top
, csiescseq
.arg
[0]);
1748 case 'T': /* SD -- Scroll <n> line down */
1749 DEFAULT(csiescseq
.arg
[0], 1);
1750 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1752 case 'L': /* IL -- Insert <n> blank lines */
1753 DEFAULT(csiescseq
.arg
[0], 1);
1754 tinsertblankline(csiescseq
.arg
[0]);
1756 case 'l': /* RM -- Reset Mode */
1757 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1759 case 'M': /* DL -- Delete <n> lines */
1760 DEFAULT(csiescseq
.arg
[0], 1);
1761 tdeleteline(csiescseq
.arg
[0]);
1763 case 'X': /* ECH -- Erase <n> char */
1764 DEFAULT(csiescseq
.arg
[0], 1);
1765 tclearregion(term
.c
.x
, term
.c
.y
,
1766 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1768 case 'P': /* DCH -- Delete <n> char */
1769 DEFAULT(csiescseq
.arg
[0], 1);
1770 tdeletechar(csiescseq
.arg
[0]);
1772 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1773 DEFAULT(csiescseq
.arg
[0], 1);
1774 tputtab(-csiescseq
.arg
[0]);
1776 case 'd': /* VPA -- Move to <row> */
1777 DEFAULT(csiescseq
.arg
[0], 1);
1778 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1780 case 'h': /* SM -- Set terminal mode */
1781 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1783 case 'm': /* SGR -- Terminal attribute (color) */
1784 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1786 case 'n': /* DSR – Device Status Report (cursor position) */
1787 if (csiescseq
.arg
[0] == 6) {
1788 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1789 term
.c
.y
+1, term
.c
.x
+1);
1793 case 'r': /* DECSTBM -- Set Scrolling Region */
1794 if (csiescseq
.priv
) {
1797 DEFAULT(csiescseq
.arg
[0], 1);
1798 DEFAULT(csiescseq
.arg
[1], term
.row
);
1799 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1803 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1804 tcursor(CURSOR_SAVE
);
1806 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1807 tcursor(CURSOR_LOAD
);
1810 switch (csiescseq
.mode
[1]) {
1811 case 'q': /* DECSCUSR -- Set Cursor Style */
1812 DEFAULT(csiescseq
.arg
[0], 1);
1813 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1816 win
.cursor
= csiescseq
.arg
[0];
1831 fprintf(stderr
, "ESC[");
1832 for (i
= 0; i
< csiescseq
.len
; i
++) {
1833 c
= csiescseq
.buf
[i
] & 0xff;
1836 } else if (c
== '\n') {
1837 fprintf(stderr
, "(\\n)");
1838 } else if (c
== '\r') {
1839 fprintf(stderr
, "(\\r)");
1840 } else if (c
== 0x1b) {
1841 fprintf(stderr
, "(\\e)");
1843 fprintf(stderr
, "(%02x)", c
);
1852 memset(&csiescseq
, 0, sizeof(csiescseq
));
1861 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1863 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1865 switch (strescseq
.type
) {
1866 case ']': /* OSC -- Operating System Command */
1872 xsettitle(strescseq
.args
[1]);
1878 dec
= base64dec(strescseq
.args
[2]);
1880 xsetsel(dec
, CurrentTime
);
1883 fprintf(stderr
, "erresc: invalid base64\n");
1887 case 4: /* color set */
1890 p
= strescseq
.args
[2];
1892 case 104: /* color reset, here p = NULL */
1893 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1894 if (xsetcolorname(j
, p
)) {
1895 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1898 * TODO if defaultbg color is changed, borders
1906 case 'k': /* old title set compatibility */
1907 xsettitle(strescseq
.args
[0]);
1909 case 'P': /* DCS -- Device Control String */
1910 term
.mode
|= ESC_DCS
;
1911 case '_': /* APC -- Application Program Command */
1912 case '^': /* PM -- Privacy Message */
1916 fprintf(stderr
, "erresc: unknown str ");
1924 char *p
= strescseq
.buf
;
1927 strescseq
.buf
[strescseq
.len
] = '\0';
1932 while (strescseq
.narg
< STR_ARG_SIZ
) {
1933 strescseq
.args
[strescseq
.narg
++] = p
;
1934 while ((c
= *p
) != ';' && c
!= '\0')
1948 fprintf(stderr
, "ESC%c", strescseq
.type
);
1949 for (i
= 0; i
< strescseq
.len
; i
++) {
1950 c
= strescseq
.buf
[i
] & 0xff;
1954 } else if (isprint(c
)) {
1956 } else if (c
== '\n') {
1957 fprintf(stderr
, "(\\n)");
1958 } else if (c
== '\r') {
1959 fprintf(stderr
, "(\\r)");
1960 } else if (c
== 0x1b) {
1961 fprintf(stderr
, "(\\e)");
1963 fprintf(stderr
, "(%02x)", c
);
1966 fprintf(stderr
, "ESC\\\n");
1972 memset(&strescseq
, 0, sizeof(strescseq
));
1976 sendbreak(const Arg
*arg
)
1978 if (tcsendbreak(cmdfd
, 0))
1979 perror("Error sending break");
1983 tprinter(char *s
, size_t len
)
1985 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1986 fprintf(stderr
, "Error writing in %s:%s\n",
1987 opt_io
, strerror(errno
));
1994 iso14755(const Arg
*arg
)
1996 unsigned long id
= xwinid();
1997 char cmd
[sizeof(ISO14755CMD
) + NUMMAXLEN(id
)];
1999 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
2000 unsigned long utf32
;
2002 snprintf(cmd
, sizeof(cmd
), ISO14755CMD
, id
);
2003 if (!(p
= popen(cmd
, "r")))
2006 us
= fgets(codepoint
, sizeof(codepoint
), p
);
2009 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
2011 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
2012 (*e
!= '\n' && *e
!= '\0'))
2015 ttysend(uc
, utf8encode(utf32
, uc
));
2019 toggleprinter(const Arg
*arg
)
2021 term
.mode
^= MODE_PRINT
;
2025 printscreen(const Arg
*arg
)
2031 printsel(const Arg
*arg
)
2041 if ((ptr
= getsel())) {
2042 tprinter(ptr
, strlen(ptr
));
2053 bp
= &term
.line
[n
][0];
2054 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2055 if (bp
!= end
|| bp
->u
!= ' ') {
2056 for ( ;bp
<= end
; ++bp
)
2057 tprinter(buf
, utf8encode(bp
->u
, buf
));
2067 for (i
= 0; i
< term
.row
; ++i
)
2077 while (x
< term
.col
&& n
--)
2078 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2081 while (x
> 0 && n
++)
2082 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2085 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2091 if (ISCONTROL(u
)) { /* control code */
2096 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2105 tdefutf8(char ascii
)
2108 term
.mode
|= MODE_UTF8
;
2109 else if (ascii
== '@')
2110 term
.mode
&= ~MODE_UTF8
;
2114 tdeftran(char ascii
)
2116 static char cs
[] = "0B";
2117 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2120 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2121 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2123 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2132 if (c
== '8') { /* DEC screen alignment test. */
2133 for (x
= 0; x
< term
.col
; ++x
) {
2134 for (y
= 0; y
< term
.row
; ++y
)
2135 tsetchar('E', &term
.c
.attr
, x
, y
);
2141 tstrsequence(uchar c
)
2146 case 0x90: /* DCS -- Device Control String */
2148 term
.esc
|= ESC_DCS
;
2150 case 0x9f: /* APC -- Application Program Command */
2153 case 0x9e: /* PM -- Privacy Message */
2156 case 0x9d: /* OSC -- Operating System Command */
2161 term
.esc
|= ESC_STR
;
2165 tcontrolcode(uchar ascii
)
2172 tmoveto(term
.c
.x
-1, term
.c
.y
);
2175 tmoveto(0, term
.c
.y
);
2180 /* go to first col if the mode is set */
2181 tnewline(IS_SET(MODE_CRLF
));
2183 case '\a': /* BEL */
2184 if (term
.esc
& ESC_STR_END
) {
2185 /* backwards compatibility to xterm */
2188 if (!(win
.state
& WIN_FOCUSED
))
2194 case '\033': /* ESC */
2196 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2197 term
.esc
|= ESC_START
;
2199 case '\016': /* SO (LS1 -- Locking shift 1) */
2200 case '\017': /* SI (LS0 -- Locking shift 0) */
2201 term
.charset
= 1 - (ascii
- '\016');
2203 case '\032': /* SUB */
2204 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2205 case '\030': /* CAN */
2208 case '\005': /* ENQ (IGNORED) */
2209 case '\000': /* NUL (IGNORED) */
2210 case '\021': /* XON (IGNORED) */
2211 case '\023': /* XOFF (IGNORED) */
2212 case 0177: /* DEL (IGNORED) */
2214 case 0x80: /* TODO: PAD */
2215 case 0x81: /* TODO: HOP */
2216 case 0x82: /* TODO: BPH */
2217 case 0x83: /* TODO: NBH */
2218 case 0x84: /* TODO: IND */
2220 case 0x85: /* NEL -- Next line */
2221 tnewline(1); /* always go to first col */
2223 case 0x86: /* TODO: SSA */
2224 case 0x87: /* TODO: ESA */
2226 case 0x88: /* HTS -- Horizontal tab stop */
2227 term
.tabs
[term
.c
.x
] = 1;
2229 case 0x89: /* TODO: HTJ */
2230 case 0x8a: /* TODO: VTS */
2231 case 0x8b: /* TODO: PLD */
2232 case 0x8c: /* TODO: PLU */
2233 case 0x8d: /* TODO: RI */
2234 case 0x8e: /* TODO: SS2 */
2235 case 0x8f: /* TODO: SS3 */
2236 case 0x91: /* TODO: PU1 */
2237 case 0x92: /* TODO: PU2 */
2238 case 0x93: /* TODO: STS */
2239 case 0x94: /* TODO: CCH */
2240 case 0x95: /* TODO: MW */
2241 case 0x96: /* TODO: SPA */
2242 case 0x97: /* TODO: EPA */
2243 case 0x98: /* TODO: SOS */
2244 case 0x99: /* TODO: SGCI */
2246 case 0x9a: /* DECID -- Identify Terminal */
2247 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2249 case 0x9b: /* TODO: CSI */
2250 case 0x9c: /* TODO: ST */
2252 case 0x90: /* DCS -- Device Control String */
2253 case 0x9d: /* OSC -- Operating System Command */
2254 case 0x9e: /* PM -- Privacy Message */
2255 case 0x9f: /* APC -- Application Program Command */
2256 tstrsequence(ascii
);
2259 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2260 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2264 * returns 1 when the sequence is finished and it hasn't to read
2265 * more characters for this sequence, otherwise 0
2268 eschandle(uchar ascii
)
2272 term
.esc
|= ESC_CSI
;
2275 term
.esc
|= ESC_TEST
;
2278 term
.esc
|= ESC_UTF8
;
2280 case 'P': /* DCS -- Device Control String */
2281 case '_': /* APC -- Application Program Command */
2282 case '^': /* PM -- Privacy Message */
2283 case ']': /* OSC -- Operating System Command */
2284 case 'k': /* old title set compatibility */
2285 tstrsequence(ascii
);
2287 case 'n': /* LS2 -- Locking shift 2 */
2288 case 'o': /* LS3 -- Locking shift 3 */
2289 term
.charset
= 2 + (ascii
- 'n');
2291 case '(': /* GZD4 -- set primary charset G0 */
2292 case ')': /* G1D4 -- set secondary charset G1 */
2293 case '*': /* G2D4 -- set tertiary charset G2 */
2294 case '+': /* G3D4 -- set quaternary charset G3 */
2295 term
.icharset
= ascii
- '(';
2296 term
.esc
|= ESC_ALTCHARSET
;
2298 case 'D': /* IND -- Linefeed */
2299 if (term
.c
.y
== term
.bot
) {
2300 tscrollup(term
.top
, 1);
2302 tmoveto(term
.c
.x
, term
.c
.y
+1);
2305 case 'E': /* NEL -- Next line */
2306 tnewline(1); /* always go to first col */
2308 case 'H': /* HTS -- Horizontal tab stop */
2309 term
.tabs
[term
.c
.x
] = 1;
2311 case 'M': /* RI -- Reverse index */
2312 if (term
.c
.y
== term
.top
) {
2313 tscrolldown(term
.top
, 1);
2315 tmoveto(term
.c
.x
, term
.c
.y
-1);
2318 case 'Z': /* DECID -- Identify Terminal */
2319 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2321 case 'c': /* RIS -- Reset to inital state */
2326 case '=': /* DECPAM -- Application keypad */
2327 term
.mode
|= MODE_APPKEYPAD
;
2329 case '>': /* DECPNM -- Normal keypad */
2330 term
.mode
&= ~MODE_APPKEYPAD
;
2332 case '7': /* DECSC -- Save Cursor */
2333 tcursor(CURSOR_SAVE
);
2335 case '8': /* DECRC -- Restore Cursor */
2336 tcursor(CURSOR_LOAD
);
2338 case '\\': /* ST -- String Terminator */
2339 if (term
.esc
& ESC_STR_END
)
2343 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2344 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2358 control
= ISCONTROL(u
);
2359 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2363 len
= utf8encode(u
, c
);
2364 if (!control
&& (width
= wcwidth(u
)) == -1) {
2365 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2370 if (IS_SET(MODE_PRINT
))
2374 * STR sequence must be checked before anything else
2375 * because it uses all following characters until it
2376 * receives a ESC, a SUB, a ST or any other C1 control
2379 if (term
.esc
& ESC_STR
) {
2380 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2382 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2383 if (IS_SET(MODE_SIXEL
)) {
2384 /* TODO: render sixel */;
2385 term
.mode
&= ~MODE_SIXEL
;
2388 term
.esc
|= ESC_STR_END
;
2389 goto check_control_code
;
2393 if (IS_SET(MODE_SIXEL
)) {
2394 /* TODO: implement sixel mode */
2397 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2398 term
.mode
|= MODE_SIXEL
;
2400 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2402 * Here is a bug in terminals. If the user never sends
2403 * some code to stop the str or esc command, then st
2404 * will stop responding. But this is better than
2405 * silently failing with unknown characters. At least
2406 * then users will report back.
2408 * In the case users ever get fixed, here is the code:
2417 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2418 strescseq
.len
+= len
;
2424 * Actions of control codes must be performed as soon they arrive
2425 * because they can be embedded inside a control sequence, and
2426 * they must not cause conflicts with sequences.
2431 * control codes are not shown ever
2434 } else if (term
.esc
& ESC_START
) {
2435 if (term
.esc
& ESC_CSI
) {
2436 csiescseq
.buf
[csiescseq
.len
++] = u
;
2437 if (BETWEEN(u
, 0x40, 0x7E)
2438 || csiescseq
.len
>= \
2439 sizeof(csiescseq
.buf
)-1) {
2445 } else if (term
.esc
& ESC_UTF8
) {
2447 } else if (term
.esc
& ESC_ALTCHARSET
) {
2449 } else if (term
.esc
& ESC_TEST
) {
2454 /* sequence already finished */
2458 * All characters which form part of a sequence are not
2463 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2466 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2467 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2468 gp
->mode
|= ATTR_WRAP
;
2470 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2473 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2474 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2476 if (term
.c
.x
+width
> term
.col
) {
2478 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2481 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2484 gp
->mode
|= ATTR_WIDE
;
2485 if (term
.c
.x
+1 < term
.col
) {
2487 gp
[1].mode
= ATTR_WDUMMY
;
2490 if (term
.c
.x
+width
< term
.col
) {
2491 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2493 term
.c
.state
|= CURSOR_WRAPNEXT
;
2498 tresize(int col
, int row
)
2501 int minrow
= MIN(row
, term
.row
);
2502 int mincol
= MIN(col
, term
.col
);
2506 if (col
< 1 || row
< 1) {
2508 "tresize: error resizing to %dx%d\n", col
, row
);
2513 * slide screen to keep cursor where we expect it -
2514 * tscrollup would work here, but we can optimize to
2515 * memmove because we're freeing the earlier lines
2517 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2521 /* ensure that both src and dst are not NULL */
2523 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2524 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2526 for (i
+= row
; i
< term
.row
; i
++) {
2531 /* resize to new width */
2532 term
.specbuf
= xrealloc(term
.specbuf
, col
* sizeof(GlyphFontSpec
));
2534 /* resize to new height */
2535 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2536 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2537 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2538 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2540 /* resize each row to new width, zero-pad if needed */
2541 for (i
= 0; i
< minrow
; i
++) {
2542 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2543 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2546 /* allocate any new rows */
2547 for (/* i = minrow */; i
< row
; i
++) {
2548 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2549 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2551 if (col
> term
.col
) {
2552 bp
= term
.tabs
+ term
.col
;
2554 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2555 while (--bp
> term
.tabs
&& !*bp
)
2557 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2560 /* update terminal size */
2563 /* reset scrolling region */
2564 tsetscroll(0, row
-1);
2565 /* make use of the LIMIT in tmoveto */
2566 tmoveto(term
.c
.x
, term
.c
.y
);
2567 /* Clearing both screens (it makes dirty all lines) */
2569 for (i
= 0; i
< 2; i
++) {
2570 if (mincol
< col
&& 0 < minrow
) {
2571 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2573 if (0 < col
&& minrow
< row
) {
2574 tclearregion(0, minrow
, col
- 1, row
- 1);
2577 tcursor(CURSOR_LOAD
);
2583 zoom(const Arg
*arg
)
2587 larg
.f
= usedfontsize
+ arg
->f
;
2592 zoomabs(const Arg
*arg
)
2595 xloadfonts(usedfont
, arg
->f
);
2603 zoomreset(const Arg
*arg
)
2607 if (defaultfontsize
> 0) {
2608 larg
.f
= defaultfontsize
;
2616 xsettitle(opt_title
? opt_title
: "st");
2627 match(uint mask
, uint state
)
2629 return mask
== XK_ANY_MOD
|| mask
== (state
& ~ignoremod
);
2633 numlock(const Arg
*dummy
)
2639 kmap(KeySym k
, uint state
)
2644 /* Check for mapped keys out of X11 function keys. */
2645 for (i
= 0; i
< LEN(mappedkeys
); i
++) {
2646 if (mappedkeys
[i
] == k
)
2649 if (i
== LEN(mappedkeys
)) {
2650 if ((k
& 0xFFFF) < 0xFD00)
2654 for (kp
= key
; kp
< key
+ LEN(key
); kp
++) {
2658 if (!match(kp
->mask
, state
))
2661 if (IS_SET(MODE_APPKEYPAD
) ? kp
->appkey
< 0 : kp
->appkey
> 0)
2663 if (term
.numlock
&& kp
->appkey
== 2)
2666 if (IS_SET(MODE_APPCURSOR
) ? kp
->appcursor
< 0 : kp
->appcursor
> 0)
2669 if (IS_SET(MODE_CRLF
) ? kp
->crlf
< 0 : kp
->crlf
> 0)
2679 cresize(int width
, int height
)
2688 col
= (win
.w
- 2 * borderpx
) / win
.cw
;
2689 row
= (win
.h
- 2 * borderpx
) / win
.ch
;
2698 die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
2699 " [-n name] [-o file]\n"
2700 " [-T title] [-t title] [-w windowid]"
2701 " [[-e] command [args ...]]\n"
2702 " %s [-aiv] [-c class] [-f font] [-g geometry]"
2703 " [-n name] [-o file]\n"
2704 " [-T title] [-t title] [-w windowid] -l line"
2705 " [stty_args ...]\n", argv0
, argv0
);