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 %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(const char *src
)
392 size_t in_len
= strlen(src
);
397 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
399 int a
= base64_digits
[(unsigned char) *src
++];
400 int b
= base64_digits
[(unsigned char) *src
++];
401 int c
= base64_digits
[(unsigned char) *src
++];
402 int d
= base64_digits
[(unsigned char) *src
++];
404 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
407 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
410 *dst
++ = ((c
& 0x03) << 6) | d
;
419 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
420 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
425 sel
.clipboard
= NULL
;
434 return LIMIT(x
, 0, term
.col
-1);
443 return LIMIT(y
, 0, term
.row
-1);
451 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
454 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
465 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
466 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
467 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
469 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
470 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
472 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
473 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
475 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
476 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
478 /* expand selection over line breaks */
479 if (sel
.type
== SEL_RECTANGULAR
)
481 i
= tlinelen(sel
.nb
.y
);
484 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
485 sel
.ne
.x
= term
.col
- 1;
489 selected(int x
, int y
)
491 if (sel
.mode
== SEL_EMPTY
)
494 if (sel
.type
== SEL_RECTANGULAR
)
495 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
496 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
498 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
499 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
500 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
504 selsnap(int *x
, int *y
, int direction
)
506 int newx
, newy
, xt
, yt
;
507 int delim
, prevdelim
;
513 * Snap around if the word wraps around at the end or
514 * beginning of a line.
516 prevgp
= &term
.line
[*y
][*x
];
517 prevdelim
= ISDELIM(prevgp
->u
);
519 newx
= *x
+ direction
;
521 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
523 newx
= (newx
+ term
.col
) % term
.col
;
524 if (!BETWEEN(newy
, 0, term
.row
- 1))
530 yt
= newy
, xt
= newx
;
531 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
535 if (newx
>= tlinelen(newy
))
538 gp
= &term
.line
[newy
][newx
];
539 delim
= ISDELIM(gp
->u
);
540 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
541 || (delim
&& gp
->u
!= prevgp
->u
)))
552 * Snap around if the the previous line or the current one
553 * has set ATTR_WRAP at its end. Then the whole next or
554 * previous line will be selected.
556 *x
= (direction
< 0) ? 0 : term
.col
- 1;
558 for (; *y
> 0; *y
+= direction
) {
559 if (!(term
.line
[*y
-1][term
.col
-1].mode
564 } else if (direction
> 0) {
565 for (; *y
< term
.row
-1; *y
+= direction
) {
566 if (!(term
.line
[*y
][term
.col
-1].mode
580 int y
, bufsize
, lastx
, linelen
;
586 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
587 ptr
= str
= xmalloc(bufsize
);
589 /* append every set & selected glyph to the selection */
590 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
591 if ((linelen
= tlinelen(y
)) == 0) {
596 if (sel
.type
== SEL_RECTANGULAR
) {
597 gp
= &term
.line
[y
][sel
.nb
.x
];
600 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
601 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
603 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
604 while (last
>= gp
&& last
->u
== ' ')
607 for ( ; gp
<= last
; ++gp
) {
608 if (gp
->mode
& ATTR_WDUMMY
)
611 ptr
+= utf8encode(gp
->u
, ptr
);
615 * Copy and pasting of line endings is inconsistent
616 * in the inconsistent terminal and GUI world.
617 * The best solution seems like to produce '\n' when
618 * something is copied from st and convert '\n' to
619 * '\r', when something to be pasted is received by
621 * FIXME: Fix the computer world.
623 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
631 selpaste(const Arg
*dummy
)
637 clipcopy(const Arg
*dummy
)
643 clippaste(const Arg
*dummy
)
655 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
659 die(const char *errstr
, ...)
663 va_start(ap
, errstr
);
664 vfprintf(stderr
, errstr
, ap
);
672 char **args
, *sh
, *prog
;
673 const struct passwd
*pw
;
676 if ((pw
= getpwuid(getuid())) == NULL
) {
678 die("getpwuid:%s\n", strerror(errno
));
680 die("who are you?\n");
683 if ((sh
= getenv("SHELL")) == NULL
)
684 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
692 args
= (opt_cmd
) ? opt_cmd
: (char *[]) {prog
, NULL
};
697 setenv("LOGNAME", pw
->pw_name
, 1);
698 setenv("USER", pw
->pw_name
, 1);
699 setenv("SHELL", sh
, 1);
700 setenv("HOME", pw
->pw_dir
, 1);
701 setenv("TERM", termname
, 1);
704 signal(SIGCHLD
, SIG_DFL
);
705 signal(SIGHUP
, SIG_DFL
);
706 signal(SIGINT
, SIG_DFL
);
707 signal(SIGQUIT
, SIG_DFL
);
708 signal(SIGTERM
, SIG_DFL
);
709 signal(SIGALRM
, SIG_DFL
);
721 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
722 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
727 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
728 die("child finished with error '%d'\n", stat
);
736 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
739 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
740 die("incorrect stty parameters\n");
741 memcpy(cmd
, stty_args
, n
);
743 siz
= sizeof(cmd
) - n
;
744 for (p
= opt_cmd
; p
&& (s
= *p
); ++p
) {
745 if ((n
= strlen(s
)) > siz
-1)
746 die("stty parameter length too long\n");
753 if (system(cmd
) != 0)
754 perror("Couldn't call stty");
761 struct winsize w
= {term
.row
, term
.col
, 0, 0};
764 term
.mode
|= MODE_PRINT
;
765 iofd
= (!strcmp(opt_io
, "-")) ?
766 1 : open(opt_io
, O_WRONLY
| O_CREAT
, 0666);
768 fprintf(stderr
, "Error opening %s:%s\n",
769 opt_io
, strerror(errno
));
774 if ((cmdfd
= open(opt_line
, O_RDWR
)) < 0)
775 die("open line failed: %s\n", strerror(errno
));
781 /* seems to work fine on linux, openbsd and freebsd */
782 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
783 die("openpty failed: %s\n", strerror(errno
));
785 switch (pid
= fork()) {
787 die("fork failed\n");
791 setsid(); /* create a new process group */
795 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
796 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
804 signal(SIGCHLD
, sigchld
);
812 static char buf
[BUFSIZ
];
813 static int buflen
= 0;
815 int charsize
; /* size of utf8 char in bytes */
819 /* append read bytes to unprocessed bytes */
820 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
821 die("Couldn't read from shell: %s\n", strerror(errno
));
827 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
828 /* process a complete utf8 char */
829 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
839 tputc(*ptr
++ & 0xFF);
843 /* keep any uncomplete utf8 char for the next call */
845 memmove(buf
, ptr
, buflen
);
851 ttywrite(const char *s
, size_t n
)
858 * Remember that we are using a pty, which might be a modem line.
859 * Writing too much will clog the line. That's why we are doing this
861 * FIXME: Migrate the world to Plan 9.
869 /* Check if we can write. */
870 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
873 die("select failed: %s\n", strerror(errno
));
875 if (FD_ISSET(cmdfd
, &wfd
)) {
877 * Only write the bytes written by ttywrite() or the
878 * default of 256. This seems to be a reasonable value
879 * for a serial line. Bigger values might clog the I/O.
881 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
885 * We weren't able to write out everything.
886 * This means the buffer is getting full
894 /* All bytes have been written. */
898 if (FD_ISSET(cmdfd
, &rfd
))
904 die("write error on tty: %s\n", strerror(errno
));
908 ttysend(char *s
, size_t n
)
915 if (!IS_SET(MODE_ECHO
))
919 for (t
= s
; t
< lim
; t
+= len
) {
920 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
921 len
= utf8decode(t
, &u
, n
);
940 w
.ws_xpixel
= win
.tw
;
941 w
.ws_ypixel
= win
.th
;
942 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
943 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
951 for (i
= 0; i
< term
.row
-1; i
++) {
952 for (j
= 0; j
< term
.col
-1; j
++) {
953 if (term
.line
[i
][j
].mode
& attr
)
962 tsetdirt(int top
, int bot
)
966 LIMIT(top
, 0, term
.row
-1);
967 LIMIT(bot
, 0, term
.row
-1);
969 for (i
= top
; i
<= bot
; i
++)
974 tsetdirtattr(int attr
)
978 for (i
= 0; i
< term
.row
-1; i
++) {
979 for (j
= 0; j
< term
.col
-1; j
++) {
980 if (term
.line
[i
][j
].mode
& attr
) {
991 tsetdirt(0, term
.row
-1);
998 int alt
= IS_SET(MODE_ALTSCREEN
);
1000 if (mode
== CURSOR_SAVE
) {
1002 } else if (mode
== CURSOR_LOAD
) {
1004 tmoveto(c
[alt
].x
, c
[alt
].y
);
1013 term
.c
= (TCursor
){{
1017 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1019 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1020 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1023 term
.bot
= term
.row
- 1;
1024 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1025 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1028 for (i
= 0; i
< 2; i
++) {
1030 tcursor(CURSOR_SAVE
);
1031 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1037 tnew(int col
, int row
)
1039 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1049 Line
*tmp
= term
.line
;
1051 term
.line
= term
.alt
;
1053 term
.mode
^= MODE_ALTSCREEN
;
1058 tscrolldown(int orig
, int n
)
1063 LIMIT(n
, 0, term
.bot
-orig
+1);
1065 tsetdirt(orig
, term
.bot
-n
);
1066 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1068 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1069 temp
= term
.line
[i
];
1070 term
.line
[i
] = term
.line
[i
-n
];
1071 term
.line
[i
-n
] = temp
;
1078 tscrollup(int orig
, int n
)
1083 LIMIT(n
, 0, term
.bot
-orig
+1);
1085 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1086 tsetdirt(orig
+n
, term
.bot
);
1088 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1089 temp
= term
.line
[i
];
1090 term
.line
[i
] = term
.line
[i
+n
];
1091 term
.line
[i
+n
] = temp
;
1094 selscroll(orig
, -n
);
1098 selscroll(int orig
, int n
)
1103 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1104 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1108 if (sel
.type
== SEL_RECTANGULAR
) {
1109 if (sel
.ob
.y
< term
.top
)
1110 sel
.ob
.y
= term
.top
;
1111 if (sel
.oe
.y
> term
.bot
)
1112 sel
.oe
.y
= term
.bot
;
1114 if (sel
.ob
.y
< term
.top
) {
1115 sel
.ob
.y
= term
.top
;
1118 if (sel
.oe
.y
> term
.bot
) {
1119 sel
.oe
.y
= term
.bot
;
1120 sel
.oe
.x
= term
.col
;
1128 tnewline(int first_col
)
1132 if (y
== term
.bot
) {
1133 tscrollup(term
.top
, 1);
1137 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1143 char *p
= csiescseq
.buf
, *np
;
1152 csiescseq
.buf
[csiescseq
.len
] = '\0';
1153 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1155 v
= strtol(p
, &np
, 10);
1158 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1160 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1162 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1166 csiescseq
.mode
[0] = *p
++;
1167 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1170 /* for absolute user moves, when decom is set */
1172 tmoveato(int x
, int y
)
1174 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1178 tmoveto(int x
, int y
)
1182 if (term
.c
.state
& CURSOR_ORIGIN
) {
1187 maxy
= term
.row
- 1;
1189 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1190 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1191 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1195 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1197 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1198 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1199 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1200 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1201 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1202 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1203 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1204 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1205 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1209 * The table is proudly stolen from rxvt.
1211 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1212 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1213 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1215 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1216 if (x
+1 < term
.col
) {
1217 term
.line
[y
][x
+1].u
= ' ';
1218 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1220 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1221 term
.line
[y
][x
-1].u
= ' ';
1222 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1226 term
.line
[y
][x
] = *attr
;
1227 term
.line
[y
][x
].u
= u
;
1231 tclearregion(int x1
, int y1
, int x2
, int y2
)
1237 temp
= x1
, x1
= x2
, x2
= temp
;
1239 temp
= y1
, y1
= y2
, y2
= temp
;
1241 LIMIT(x1
, 0, term
.col
-1);
1242 LIMIT(x2
, 0, term
.col
-1);
1243 LIMIT(y1
, 0, term
.row
-1);
1244 LIMIT(y2
, 0, term
.row
-1);
1246 for (y
= y1
; y
<= y2
; y
++) {
1248 for (x
= x1
; x
<= x2
; x
++) {
1249 gp
= &term
.line
[y
][x
];
1252 gp
->fg
= term
.c
.attr
.fg
;
1253 gp
->bg
= term
.c
.attr
.bg
;
1266 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1270 size
= term
.col
- src
;
1271 line
= term
.line
[term
.c
.y
];
1273 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1274 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1283 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1287 size
= term
.col
- dst
;
1288 line
= term
.line
[term
.c
.y
];
1290 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1291 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1295 tinsertblankline(int n
)
1297 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1298 tscrolldown(term
.c
.y
, n
);
1304 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1305 tscrollup(term
.c
.y
, n
);
1309 tdefcolor(int *attr
, int *npar
, int l
)
1314 switch (attr
[*npar
+ 1]) {
1315 case 2: /* direct color in RGB space */
1316 if (*npar
+ 4 >= l
) {
1318 "erresc(38): Incorrect number of parameters (%d)\n",
1322 r
= attr
[*npar
+ 2];
1323 g
= attr
[*npar
+ 3];
1324 b
= attr
[*npar
+ 4];
1326 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1327 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1330 idx
= TRUECOLOR(r
, g
, b
);
1332 case 5: /* indexed color */
1333 if (*npar
+ 2 >= l
) {
1335 "erresc(38): Incorrect number of parameters (%d)\n",
1340 if (!BETWEEN(attr
[*npar
], 0, 255))
1341 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1345 case 0: /* implemented defined (only foreground) */
1346 case 1: /* transparent */
1347 case 3: /* direct color in CMY space */
1348 case 4: /* direct color in CMYK space */
1351 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1359 tsetattr(int *attr
, int l
)
1364 for (i
= 0; i
< l
; i
++) {
1367 term
.c
.attr
.mode
&= ~(
1376 term
.c
.attr
.fg
= defaultfg
;
1377 term
.c
.attr
.bg
= defaultbg
;
1380 term
.c
.attr
.mode
|= ATTR_BOLD
;
1383 term
.c
.attr
.mode
|= ATTR_FAINT
;
1386 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1389 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1391 case 5: /* slow blink */
1393 case 6: /* rapid blink */
1394 term
.c
.attr
.mode
|= ATTR_BLINK
;
1397 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1400 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1403 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1406 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1409 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1412 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1415 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1418 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1421 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1424 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1427 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1428 term
.c
.attr
.fg
= idx
;
1431 term
.c
.attr
.fg
= defaultfg
;
1434 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1435 term
.c
.attr
.bg
= idx
;
1438 term
.c
.attr
.bg
= defaultbg
;
1441 if (BETWEEN(attr
[i
], 30, 37)) {
1442 term
.c
.attr
.fg
= attr
[i
] - 30;
1443 } else if (BETWEEN(attr
[i
], 40, 47)) {
1444 term
.c
.attr
.bg
= attr
[i
] - 40;
1445 } else if (BETWEEN(attr
[i
], 90, 97)) {
1446 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1447 } else if (BETWEEN(attr
[i
], 100, 107)) {
1448 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1451 "erresc(default): gfx attr %d unknown\n",
1452 attr
[i
]), csidump();
1460 tsetscroll(int t
, int b
)
1464 LIMIT(t
, 0, term
.row
-1);
1465 LIMIT(b
, 0, term
.row
-1);
1476 tsetmode(int priv
, int set
, int *args
, int narg
)
1481 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1484 case 1: /* DECCKM -- Cursor key */
1485 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1487 case 5: /* DECSCNM -- Reverse video */
1489 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1490 if (mode
!= term
.mode
)
1493 case 6: /* DECOM -- Origin */
1494 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1497 case 7: /* DECAWM -- Auto wrap */
1498 MODBIT(term
.mode
, set
, MODE_WRAP
);
1500 case 0: /* Error (IGNORED) */
1501 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1502 case 3: /* DECCOLM -- Column (IGNORED) */
1503 case 4: /* DECSCLM -- Scroll (IGNORED) */
1504 case 8: /* DECARM -- Auto repeat (IGNORED) */
1505 case 18: /* DECPFF -- Printer feed (IGNORED) */
1506 case 19: /* DECPEX -- Printer extent (IGNORED) */
1507 case 42: /* DECNRCM -- National characters (IGNORED) */
1508 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1510 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1511 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1513 case 9: /* X10 mouse compatibility mode */
1514 xsetpointermotion(0);
1515 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1516 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1518 case 1000: /* 1000: report button press */
1519 xsetpointermotion(0);
1520 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1521 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1523 case 1002: /* 1002: report motion on button press */
1524 xsetpointermotion(0);
1525 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1526 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1528 case 1003: /* 1003: enable all mouse motions */
1529 xsetpointermotion(set
);
1530 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1531 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1533 case 1004: /* 1004: send focus events to tty */
1534 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1536 case 1006: /* 1006: extended reporting mode */
1537 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1540 MODBIT(term
.mode
, set
, MODE_8BIT
);
1542 case 1049: /* swap screen & set/restore cursor as xterm */
1543 if (!allowaltscreen
)
1545 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1547 case 47: /* swap screen */
1549 if (!allowaltscreen
)
1551 alt
= IS_SET(MODE_ALTSCREEN
);
1553 tclearregion(0, 0, term
.col
-1,
1556 if (set
^ alt
) /* set is always 1 or 0 */
1562 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1564 case 2004: /* 2004: bracketed paste mode */
1565 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1567 /* Not implemented mouse modes. See comments there. */
1568 case 1001: /* mouse highlight mode; can hang the
1569 terminal by design when implemented. */
1570 case 1005: /* UTF-8 mouse mode; will confuse
1571 applications not supporting UTF-8
1573 case 1015: /* urxvt mangled mouse mode; incompatible
1574 and can be mistaken for other control
1578 "erresc: unknown private set/reset mode %d\n",
1584 case 0: /* Error (IGNORED) */
1586 case 2: /* KAM -- keyboard action */
1587 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1589 case 4: /* IRM -- Insertion-replacement */
1590 MODBIT(term
.mode
, set
, MODE_INSERT
);
1592 case 12: /* SRM -- Send/Receive */
1593 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1595 case 20: /* LNM -- Linefeed/new line */
1596 MODBIT(term
.mode
, set
, MODE_CRLF
);
1600 "erresc: unknown set/reset mode %d\n",
1614 switch (csiescseq
.mode
[0]) {
1617 fprintf(stderr
, "erresc: unknown csi ");
1621 case '@': /* ICH -- Insert <n> blank char */
1622 DEFAULT(csiescseq
.arg
[0], 1);
1623 tinsertblank(csiescseq
.arg
[0]);
1625 case 'A': /* CUU -- Cursor <n> Up */
1626 DEFAULT(csiescseq
.arg
[0], 1);
1627 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1629 case 'B': /* CUD -- Cursor <n> Down */
1630 case 'e': /* VPR --Cursor <n> Down */
1631 DEFAULT(csiescseq
.arg
[0], 1);
1632 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1634 case 'i': /* MC -- Media Copy */
1635 switch (csiescseq
.arg
[0]) {
1640 tdumpline(term
.c
.y
);
1646 term
.mode
&= ~MODE_PRINT
;
1649 term
.mode
|= MODE_PRINT
;
1653 case 'c': /* DA -- Device Attributes */
1654 if (csiescseq
.arg
[0] == 0)
1655 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1657 case 'C': /* CUF -- Cursor <n> Forward */
1658 case 'a': /* HPR -- Cursor <n> Forward */
1659 DEFAULT(csiescseq
.arg
[0], 1);
1660 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1662 case 'D': /* CUB -- Cursor <n> Backward */
1663 DEFAULT(csiescseq
.arg
[0], 1);
1664 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1666 case 'E': /* CNL -- Cursor <n> Down and first col */
1667 DEFAULT(csiescseq
.arg
[0], 1);
1668 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1670 case 'F': /* CPL -- Cursor <n> Up and first col */
1671 DEFAULT(csiescseq
.arg
[0], 1);
1672 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1674 case 'g': /* TBC -- Tabulation clear */
1675 switch (csiescseq
.arg
[0]) {
1676 case 0: /* clear current tab stop */
1677 term
.tabs
[term
.c
.x
] = 0;
1679 case 3: /* clear all the tabs */
1680 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1686 case 'G': /* CHA -- Move to <col> */
1688 DEFAULT(csiescseq
.arg
[0], 1);
1689 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1691 case 'H': /* CUP -- Move to <row> <col> */
1693 DEFAULT(csiescseq
.arg
[0], 1);
1694 DEFAULT(csiescseq
.arg
[1], 1);
1695 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1697 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1698 DEFAULT(csiescseq
.arg
[0], 1);
1699 tputtab(csiescseq
.arg
[0]);
1701 case 'J': /* ED -- Clear screen */
1703 switch (csiescseq
.arg
[0]) {
1705 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1706 if (term
.c
.y
< term
.row
-1) {
1707 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1713 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1714 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1717 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1723 case 'K': /* EL -- Clear line */
1724 switch (csiescseq
.arg
[0]) {
1726 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1730 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1733 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1737 case 'S': /* SU -- Scroll <n> line up */
1738 DEFAULT(csiescseq
.arg
[0], 1);
1739 tscrollup(term
.top
, csiescseq
.arg
[0]);
1741 case 'T': /* SD -- Scroll <n> line down */
1742 DEFAULT(csiescseq
.arg
[0], 1);
1743 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1745 case 'L': /* IL -- Insert <n> blank lines */
1746 DEFAULT(csiescseq
.arg
[0], 1);
1747 tinsertblankline(csiescseq
.arg
[0]);
1749 case 'l': /* RM -- Reset Mode */
1750 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1752 case 'M': /* DL -- Delete <n> lines */
1753 DEFAULT(csiescseq
.arg
[0], 1);
1754 tdeleteline(csiescseq
.arg
[0]);
1756 case 'X': /* ECH -- Erase <n> char */
1757 DEFAULT(csiescseq
.arg
[0], 1);
1758 tclearregion(term
.c
.x
, term
.c
.y
,
1759 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1761 case 'P': /* DCH -- Delete <n> char */
1762 DEFAULT(csiescseq
.arg
[0], 1);
1763 tdeletechar(csiescseq
.arg
[0]);
1765 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1766 DEFAULT(csiescseq
.arg
[0], 1);
1767 tputtab(-csiescseq
.arg
[0]);
1769 case 'd': /* VPA -- Move to <row> */
1770 DEFAULT(csiescseq
.arg
[0], 1);
1771 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1773 case 'h': /* SM -- Set terminal mode */
1774 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1776 case 'm': /* SGR -- Terminal attribute (color) */
1777 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1779 case 'n': /* DSR – Device Status Report (cursor position) */
1780 if (csiescseq
.arg
[0] == 6) {
1781 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1782 term
.c
.y
+1, term
.c
.x
+1);
1786 case 'r': /* DECSTBM -- Set Scrolling Region */
1787 if (csiescseq
.priv
) {
1790 DEFAULT(csiescseq
.arg
[0], 1);
1791 DEFAULT(csiescseq
.arg
[1], term
.row
);
1792 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1796 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1797 tcursor(CURSOR_SAVE
);
1799 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1800 tcursor(CURSOR_LOAD
);
1803 switch (csiescseq
.mode
[1]) {
1804 case 'q': /* DECSCUSR -- Set Cursor Style */
1805 DEFAULT(csiescseq
.arg
[0], 1);
1806 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1809 win
.cursor
= csiescseq
.arg
[0];
1824 fprintf(stderr
, "ESC[");
1825 for (i
= 0; i
< csiescseq
.len
; i
++) {
1826 c
= csiescseq
.buf
[i
] & 0xff;
1829 } else if (c
== '\n') {
1830 fprintf(stderr
, "(\\n)");
1831 } else if (c
== '\r') {
1832 fprintf(stderr
, "(\\r)");
1833 } else if (c
== 0x1b) {
1834 fprintf(stderr
, "(\\e)");
1836 fprintf(stderr
, "(%02x)", c
);
1845 memset(&csiescseq
, 0, sizeof(csiescseq
));
1854 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1856 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1858 switch (strescseq
.type
) {
1859 case ']': /* OSC -- Operating System Command */
1865 xsettitle(strescseq
.args
[1]);
1871 dec
= base64dec(strescseq
.args
[2]);
1873 xsetsel(dec
, CurrentTime
);
1876 fprintf(stderr
, "erresc: invalid base64\n");
1880 case 4: /* color set */
1883 p
= strescseq
.args
[2];
1885 case 104: /* color reset, here p = NULL */
1886 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1887 if (xsetcolorname(j
, p
)) {
1888 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1891 * TODO if defaultbg color is changed, borders
1899 case 'k': /* old title set compatibility */
1900 xsettitle(strescseq
.args
[0]);
1902 case 'P': /* DCS -- Device Control String */
1903 term
.mode
|= ESC_DCS
;
1904 case '_': /* APC -- Application Program Command */
1905 case '^': /* PM -- Privacy Message */
1909 fprintf(stderr
, "erresc: unknown str ");
1917 char *p
= strescseq
.buf
;
1920 strescseq
.buf
[strescseq
.len
] = '\0';
1925 while (strescseq
.narg
< STR_ARG_SIZ
) {
1926 strescseq
.args
[strescseq
.narg
++] = p
;
1927 while ((c
= *p
) != ';' && c
!= '\0')
1941 fprintf(stderr
, "ESC%c", strescseq
.type
);
1942 for (i
= 0; i
< strescseq
.len
; i
++) {
1943 c
= strescseq
.buf
[i
] & 0xff;
1947 } else if (isprint(c
)) {
1949 } else if (c
== '\n') {
1950 fprintf(stderr
, "(\\n)");
1951 } else if (c
== '\r') {
1952 fprintf(stderr
, "(\\r)");
1953 } else if (c
== 0x1b) {
1954 fprintf(stderr
, "(\\e)");
1956 fprintf(stderr
, "(%02x)", c
);
1959 fprintf(stderr
, "ESC\\\n");
1965 memset(&strescseq
, 0, sizeof(strescseq
));
1969 sendbreak(const Arg
*arg
)
1971 if (tcsendbreak(cmdfd
, 0))
1972 perror("Error sending break");
1976 tprinter(char *s
, size_t len
)
1978 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1979 fprintf(stderr
, "Error writing in %s:%s\n",
1980 opt_io
, strerror(errno
));
1987 iso14755(const Arg
*arg
)
1989 unsigned long id
= xwinid();
1990 char cmd
[sizeof(ISO14755CMD
) + NUMMAXLEN(id
)];
1992 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1993 unsigned long utf32
;
1995 snprintf(cmd
, sizeof(cmd
), ISO14755CMD
, id
);
1996 if (!(p
= popen(cmd
, "r")))
1999 us
= fgets(codepoint
, sizeof(codepoint
), p
);
2002 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
2004 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
2005 (*e
!= '\n' && *e
!= '\0'))
2008 ttysend(uc
, utf8encode(utf32
, uc
));
2012 toggleprinter(const Arg
*arg
)
2014 term
.mode
^= MODE_PRINT
;
2018 printscreen(const Arg
*arg
)
2024 printsel(const Arg
*arg
)
2034 if ((ptr
= getsel())) {
2035 tprinter(ptr
, strlen(ptr
));
2046 bp
= &term
.line
[n
][0];
2047 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2048 if (bp
!= end
|| bp
->u
!= ' ') {
2049 for ( ;bp
<= end
; ++bp
)
2050 tprinter(buf
, utf8encode(bp
->u
, buf
));
2060 for (i
= 0; i
< term
.row
; ++i
)
2070 while (x
< term
.col
&& n
--)
2071 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2074 while (x
> 0 && n
++)
2075 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2078 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2084 if (ISCONTROL(u
)) { /* control code */
2089 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2098 tdefutf8(char ascii
)
2101 term
.mode
|= MODE_UTF8
;
2102 else if (ascii
== '@')
2103 term
.mode
&= ~MODE_UTF8
;
2107 tdeftran(char ascii
)
2109 static char cs
[] = "0B";
2110 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2113 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2114 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2116 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2125 if (c
== '8') { /* DEC screen alignment test. */
2126 for (x
= 0; x
< term
.col
; ++x
) {
2127 for (y
= 0; y
< term
.row
; ++y
)
2128 tsetchar('E', &term
.c
.attr
, x
, y
);
2134 tstrsequence(uchar c
)
2139 case 0x90: /* DCS -- Device Control String */
2141 term
.esc
|= ESC_DCS
;
2143 case 0x9f: /* APC -- Application Program Command */
2146 case 0x9e: /* PM -- Privacy Message */
2149 case 0x9d: /* OSC -- Operating System Command */
2154 term
.esc
|= ESC_STR
;
2158 tcontrolcode(uchar ascii
)
2165 tmoveto(term
.c
.x
-1, term
.c
.y
);
2168 tmoveto(0, term
.c
.y
);
2173 /* go to first col if the mode is set */
2174 tnewline(IS_SET(MODE_CRLF
));
2176 case '\a': /* BEL */
2177 if (term
.esc
& ESC_STR_END
) {
2178 /* backwards compatibility to xterm */
2181 if (!(win
.state
& WIN_FOCUSED
))
2187 case '\033': /* ESC */
2189 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2190 term
.esc
|= ESC_START
;
2192 case '\016': /* SO (LS1 -- Locking shift 1) */
2193 case '\017': /* SI (LS0 -- Locking shift 0) */
2194 term
.charset
= 1 - (ascii
- '\016');
2196 case '\032': /* SUB */
2197 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2198 case '\030': /* CAN */
2201 case '\005': /* ENQ (IGNORED) */
2202 case '\000': /* NUL (IGNORED) */
2203 case '\021': /* XON (IGNORED) */
2204 case '\023': /* XOFF (IGNORED) */
2205 case 0177: /* DEL (IGNORED) */
2207 case 0x80: /* TODO: PAD */
2208 case 0x81: /* TODO: HOP */
2209 case 0x82: /* TODO: BPH */
2210 case 0x83: /* TODO: NBH */
2211 case 0x84: /* TODO: IND */
2213 case 0x85: /* NEL -- Next line */
2214 tnewline(1); /* always go to first col */
2216 case 0x86: /* TODO: SSA */
2217 case 0x87: /* TODO: ESA */
2219 case 0x88: /* HTS -- Horizontal tab stop */
2220 term
.tabs
[term
.c
.x
] = 1;
2222 case 0x89: /* TODO: HTJ */
2223 case 0x8a: /* TODO: VTS */
2224 case 0x8b: /* TODO: PLD */
2225 case 0x8c: /* TODO: PLU */
2226 case 0x8d: /* TODO: RI */
2227 case 0x8e: /* TODO: SS2 */
2228 case 0x8f: /* TODO: SS3 */
2229 case 0x91: /* TODO: PU1 */
2230 case 0x92: /* TODO: PU2 */
2231 case 0x93: /* TODO: STS */
2232 case 0x94: /* TODO: CCH */
2233 case 0x95: /* TODO: MW */
2234 case 0x96: /* TODO: SPA */
2235 case 0x97: /* TODO: EPA */
2236 case 0x98: /* TODO: SOS */
2237 case 0x99: /* TODO: SGCI */
2239 case 0x9a: /* DECID -- Identify Terminal */
2240 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2242 case 0x9b: /* TODO: CSI */
2243 case 0x9c: /* TODO: ST */
2245 case 0x90: /* DCS -- Device Control String */
2246 case 0x9d: /* OSC -- Operating System Command */
2247 case 0x9e: /* PM -- Privacy Message */
2248 case 0x9f: /* APC -- Application Program Command */
2249 tstrsequence(ascii
);
2252 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2253 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2257 * returns 1 when the sequence is finished and it hasn't to read
2258 * more characters for this sequence, otherwise 0
2261 eschandle(uchar ascii
)
2265 term
.esc
|= ESC_CSI
;
2268 term
.esc
|= ESC_TEST
;
2271 term
.esc
|= ESC_UTF8
;
2273 case 'P': /* DCS -- Device Control String */
2274 case '_': /* APC -- Application Program Command */
2275 case '^': /* PM -- Privacy Message */
2276 case ']': /* OSC -- Operating System Command */
2277 case 'k': /* old title set compatibility */
2278 tstrsequence(ascii
);
2280 case 'n': /* LS2 -- Locking shift 2 */
2281 case 'o': /* LS3 -- Locking shift 3 */
2282 term
.charset
= 2 + (ascii
- 'n');
2284 case '(': /* GZD4 -- set primary charset G0 */
2285 case ')': /* G1D4 -- set secondary charset G1 */
2286 case '*': /* G2D4 -- set tertiary charset G2 */
2287 case '+': /* G3D4 -- set quaternary charset G3 */
2288 term
.icharset
= ascii
- '(';
2289 term
.esc
|= ESC_ALTCHARSET
;
2291 case 'D': /* IND -- Linefeed */
2292 if (term
.c
.y
== term
.bot
) {
2293 tscrollup(term
.top
, 1);
2295 tmoveto(term
.c
.x
, term
.c
.y
+1);
2298 case 'E': /* NEL -- Next line */
2299 tnewline(1); /* always go to first col */
2301 case 'H': /* HTS -- Horizontal tab stop */
2302 term
.tabs
[term
.c
.x
] = 1;
2304 case 'M': /* RI -- Reverse index */
2305 if (term
.c
.y
== term
.top
) {
2306 tscrolldown(term
.top
, 1);
2308 tmoveto(term
.c
.x
, term
.c
.y
-1);
2311 case 'Z': /* DECID -- Identify Terminal */
2312 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2314 case 'c': /* RIS -- Reset to inital state */
2319 case '=': /* DECPAM -- Application keypad */
2320 term
.mode
|= MODE_APPKEYPAD
;
2322 case '>': /* DECPNM -- Normal keypad */
2323 term
.mode
&= ~MODE_APPKEYPAD
;
2325 case '7': /* DECSC -- Save Cursor */
2326 tcursor(CURSOR_SAVE
);
2328 case '8': /* DECRC -- Restore Cursor */
2329 tcursor(CURSOR_LOAD
);
2331 case '\\': /* ST -- String Terminator */
2332 if (term
.esc
& ESC_STR_END
)
2336 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2337 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2351 control
= ISCONTROL(u
);
2352 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2356 len
= utf8encode(u
, c
);
2357 if (!control
&& (width
= wcwidth(u
)) == -1) {
2358 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2363 if (IS_SET(MODE_PRINT
))
2367 * STR sequence must be checked before anything else
2368 * because it uses all following characters until it
2369 * receives a ESC, a SUB, a ST or any other C1 control
2372 if (term
.esc
& ESC_STR
) {
2373 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2375 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2376 if (IS_SET(MODE_SIXEL
)) {
2377 /* TODO: render sixel */;
2378 term
.mode
&= ~MODE_SIXEL
;
2381 term
.esc
|= ESC_STR_END
;
2382 goto check_control_code
;
2386 if (IS_SET(MODE_SIXEL
)) {
2387 /* TODO: implement sixel mode */
2390 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2391 term
.mode
|= MODE_SIXEL
;
2393 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2395 * Here is a bug in terminals. If the user never sends
2396 * some code to stop the str or esc command, then st
2397 * will stop responding. But this is better than
2398 * silently failing with unknown characters. At least
2399 * then users will report back.
2401 * In the case users ever get fixed, here is the code:
2410 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2411 strescseq
.len
+= len
;
2417 * Actions of control codes must be performed as soon they arrive
2418 * because they can be embedded inside a control sequence, and
2419 * they must not cause conflicts with sequences.
2424 * control codes are not shown ever
2427 } else if (term
.esc
& ESC_START
) {
2428 if (term
.esc
& ESC_CSI
) {
2429 csiescseq
.buf
[csiescseq
.len
++] = u
;
2430 if (BETWEEN(u
, 0x40, 0x7E)
2431 || csiescseq
.len
>= \
2432 sizeof(csiescseq
.buf
)-1) {
2438 } else if (term
.esc
& ESC_UTF8
) {
2440 } else if (term
.esc
& ESC_ALTCHARSET
) {
2442 } else if (term
.esc
& ESC_TEST
) {
2447 /* sequence already finished */
2451 * All characters which form part of a sequence are not
2456 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2459 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2460 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2461 gp
->mode
|= ATTR_WRAP
;
2463 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2466 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2467 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2469 if (term
.c
.x
+width
> term
.col
) {
2471 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2474 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2477 gp
->mode
|= ATTR_WIDE
;
2478 if (term
.c
.x
+1 < term
.col
) {
2480 gp
[1].mode
= ATTR_WDUMMY
;
2483 if (term
.c
.x
+width
< term
.col
) {
2484 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2486 term
.c
.state
|= CURSOR_WRAPNEXT
;
2491 tresize(int col
, int row
)
2494 int minrow
= MIN(row
, term
.row
);
2495 int mincol
= MIN(col
, term
.col
);
2499 if (col
< 1 || row
< 1) {
2501 "tresize: error resizing to %dx%d\n", col
, row
);
2506 * slide screen to keep cursor where we expect it -
2507 * tscrollup would work here, but we can optimize to
2508 * memmove because we're freeing the earlier lines
2510 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2514 /* ensure that both src and dst are not NULL */
2516 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2517 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2519 for (i
+= row
; i
< term
.row
; i
++) {
2524 /* resize to new width */
2525 term
.specbuf
= xrealloc(term
.specbuf
, col
* sizeof(GlyphFontSpec
));
2527 /* resize to new height */
2528 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2529 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2530 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2531 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2533 /* resize each row to new width, zero-pad if needed */
2534 for (i
= 0; i
< minrow
; i
++) {
2535 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2536 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2539 /* allocate any new rows */
2540 for (/* i = minrow */; i
< row
; i
++) {
2541 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2542 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2544 if (col
> term
.col
) {
2545 bp
= term
.tabs
+ term
.col
;
2547 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2548 while (--bp
> term
.tabs
&& !*bp
)
2550 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2553 /* update terminal size */
2556 /* reset scrolling region */
2557 tsetscroll(0, row
-1);
2558 /* make use of the LIMIT in tmoveto */
2559 tmoveto(term
.c
.x
, term
.c
.y
);
2560 /* Clearing both screens (it makes dirty all lines) */
2562 for (i
= 0; i
< 2; i
++) {
2563 if (mincol
< col
&& 0 < minrow
) {
2564 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2566 if (0 < col
&& minrow
< row
) {
2567 tclearregion(0, minrow
, col
- 1, row
- 1);
2570 tcursor(CURSOR_LOAD
);
2576 zoom(const Arg
*arg
)
2580 larg
.f
= usedfontsize
+ arg
->f
;
2585 zoomabs(const Arg
*arg
)
2588 xloadfonts(usedfont
, arg
->f
);
2596 zoomreset(const Arg
*arg
)
2600 if (defaultfontsize
> 0) {
2601 larg
.f
= defaultfontsize
;
2609 xsettitle(opt_title
? opt_title
: "st");
2620 match(uint mask
, uint state
)
2622 return mask
== XK_ANY_MOD
|| mask
== (state
& ~ignoremod
);
2626 numlock(const Arg
*dummy
)
2632 kmap(KeySym k
, uint state
)
2637 /* Check for mapped keys out of X11 function keys. */
2638 for (i
= 0; i
< LEN(mappedkeys
); i
++) {
2639 if (mappedkeys
[i
] == k
)
2642 if (i
== LEN(mappedkeys
)) {
2643 if ((k
& 0xFFFF) < 0xFD00)
2647 for (kp
= key
; kp
< key
+ LEN(key
); kp
++) {
2651 if (!match(kp
->mask
, state
))
2654 if (IS_SET(MODE_APPKEYPAD
) ? kp
->appkey
< 0 : kp
->appkey
> 0)
2656 if (term
.numlock
&& kp
->appkey
== 2)
2659 if (IS_SET(MODE_APPCURSOR
) ? kp
->appcursor
< 0 : kp
->appcursor
> 0)
2662 if (IS_SET(MODE_CRLF
) ? kp
->crlf
< 0 : kp
->crlf
> 0)
2672 cresize(int width
, int height
)
2681 col
= (win
.w
- 2 * borderpx
) / win
.cw
;
2682 row
= (win
.h
- 2 * borderpx
) / win
.ch
;
2691 die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
2692 " [-n name] [-o file]\n"
2693 " [-T title] [-t title] [-w windowid]"
2694 " [[-e] command [args ...]]\n"
2695 " %s [-aiv] [-c class] [-f font] [-g geometry]"
2696 " [-n name] [-o file]\n"
2697 " [-T title] [-t title] [-w windowid] -l line"
2698 " [stty_args ...]\n", argv0
, argv0
);