Xinqi Bao's Git
1 /* See LICENSE for license details. */
14 #include <sys/ioctl.h>
15 #include <sys/select.h>
18 #include <sys/types.h>
24 #include <fontconfig/fontconfig.h>
28 #include <X11/cursorfont.h>
29 #include <X11/Xft/Xft.h>
39 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
41 #elif defined(__FreeBSD__) || defined(__DragonFly__)
46 #define UTF_INVALID 0xFFFD
47 #define ESC_BUF_SIZ (128*UTF_SIZ)
48 #define ESC_ARG_SIZ 16
49 #define STR_BUF_SIZ ESC_BUF_SIZ
50 #define STR_ARG_SIZ ESC_ARG_SIZ
53 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
54 #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
55 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
56 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
57 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
58 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
61 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
63 enum cursor_movement
{
87 ESC_STR
= 4, /* OSC, PM, APC */
89 ESC_STR_END
= 16, /* a final string was encountered */
90 ESC_TEST
= 32, /* Enter in test mode */
95 /* CSI Escape sequence structs */
96 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
98 char buf
[ESC_BUF_SIZ
]; /* raw string */
99 int len
; /* raw string length */
101 int arg
[ESC_ARG_SIZ
];
102 int narg
; /* nb of args */
106 /* STR Escape sequence structs */
107 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
109 char type
; /* ESC type ... */
110 char buf
[STR_BUF_SIZ
]; /* raw string */
111 int len
; /* raw string length */
112 char *args
[STR_ARG_SIZ
];
113 int narg
; /* nb of args */
120 /* three valued logic variables: 0 indifferent, 1 on, -1 off */
121 signed char appkey
; /* application keypad */
122 signed char appcursor
; /* application cursor */
123 signed char crlf
; /* crlf mode */
126 /* function definitions used in config.h */
127 static void clipcopy(const Arg
*);
128 static void clippaste(const Arg
*);
129 static void numlock(const Arg
*);
130 static void selpaste(const Arg
*);
131 static void zoom(const Arg
*);
132 static void zoomabs(const Arg
*);
133 static void zoomreset(const Arg
*);
134 static void printsel(const Arg
*);
135 static void printscreen(const Arg
*) ;
136 static void iso14755(const Arg
*);
137 static void toggleprinter(const Arg
*);
138 static void sendbreak(const Arg
*);
140 /* config.h for applying patches and the configuration. */
143 static void execsh(void);
144 static void stty(void);
145 static void sigchld(int);
147 static void csidump(void);
148 static void csihandle(void);
149 static void csiparse(void);
150 static void csireset(void);
151 static int eschandle(uchar
);
152 static void strdump(void);
153 static void strhandle(void);
154 static void strparse(void);
155 static void strreset(void);
157 static void tprinter(char *, size_t);
158 static void tdumpsel(void);
159 static void tdumpline(int);
160 static void tdump(void);
161 static void tclearregion(int, int, int, int);
162 static void tcursor(int);
163 static void tdeletechar(int);
164 static void tdeleteline(int);
165 static void tinsertblank(int);
166 static void tinsertblankline(int);
167 static int tlinelen(int);
168 static void tmoveto(int, int);
169 static void tmoveato(int, int);
170 static void tnewline(int);
171 static void tputtab(int);
172 static void tputc(Rune
);
173 static void treset(void);
174 static void tresize(int, int);
175 static void tscrollup(int, int);
176 static void tscrolldown(int, int);
177 static void tsetattr(int *, int);
178 static void tsetchar(Rune
, Glyph
*, int, int);
179 static void tsetscroll(int, int);
180 static void tswapscreen(void);
181 static void tsetmode(int, int, int *, int);
182 static void tfulldirt(void);
183 static void techo(Rune
);
184 static void tcontrolcode(uchar
);
185 static void tdectest(char );
186 static void tdefutf8(char);
187 static int32_t tdefcolor(int *, int *, int);
188 static void tdeftran(char);
189 static void tstrsequence(uchar
);
191 static void selscroll(int, int);
192 static void selsnap(int *, int *, int);
194 static Rune
utf8decodebyte(char, size_t *);
195 static char utf8encodebyte(Rune
, size_t);
196 static char *utf8strchr(char *s
, Rune u
);
197 static size_t utf8validate(Rune
*, size_t);
199 static char *base64dec(const char *);
201 static ssize_t
xwrite(int, const char *, size_t);
202 static void *xrealloc(void *, size_t);
210 char **opt_cmd
= NULL
;
211 char *opt_class
= NULL
;
212 char *opt_embed
= NULL
;
213 char *opt_font
= NULL
;
215 char *opt_line
= NULL
;
216 char *opt_name
= NULL
;
217 char *opt_title
= NULL
;
218 int oldbutton
= 3; /* button event on startup: 3 = release */
220 static CSIEscape csiescseq
;
221 static STREscape strescseq
;
224 char *usedfont
= NULL
;
225 double usedfontsize
= 0;
226 double defaultfontsize
= 0;
228 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
229 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
230 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
231 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
233 /* config.h array lengths */
234 size_t colornamelen
= LEN(colorname
);
235 size_t mshortcutslen
= LEN(mshortcuts
);
236 size_t shortcutslen
= LEN(shortcuts
);
237 size_t selmaskslen
= LEN(selmasks
);
240 xwrite(int fd
, const char *s
, size_t len
)
246 r
= write(fd
, s
, len
);
259 void *p
= malloc(len
);
262 die("Out of memory\n");
268 xrealloc(void *p
, size_t len
)
270 if ((p
= realloc(p
, len
)) == NULL
)
271 die("Out of memory\n");
279 if ((s
= strdup(s
)) == NULL
)
280 die("Out of memory\n");
286 utf8decode(char *c
, Rune
*u
, size_t clen
)
288 size_t i
, j
, len
, type
;
294 udecoded
= utf8decodebyte(c
[0], &len
);
295 if (!BETWEEN(len
, 1, UTF_SIZ
))
297 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
298 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
305 utf8validate(u
, len
);
311 utf8decodebyte(char c
, size_t *i
)
313 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
314 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
315 return (uchar
)c
& ~utfmask
[*i
];
321 utf8encode(Rune u
, char *c
)
325 len
= utf8validate(&u
, 0);
329 for (i
= len
- 1; i
!= 0; --i
) {
330 c
[i
] = utf8encodebyte(u
, 0);
333 c
[0] = utf8encodebyte(u
, len
);
339 utf8encodebyte(Rune u
, size_t i
)
341 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
345 utf8strchr(char *s
, Rune u
)
351 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
352 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
362 utf8validate(Rune
*u
, size_t i
)
364 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
366 for (i
= 1; *u
> utfmax
[i
]; ++i
)
372 static const char base64_digits
[] = {
373 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
374 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
375 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
376 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
377 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
378 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
379 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
381 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
388 base64dec_getc(const char **src
)
390 while (**src
&& !isprint(**src
)) (*src
)++;
395 base64dec(const char *src
)
397 size_t in_len
= strlen(src
);
401 in_len
+= 4 - (in_len
% 4);
402 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
404 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
405 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
406 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
407 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
409 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
412 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
415 *dst
++ = ((c
& 0x03) << 6) | d
;
424 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
425 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
430 sel
.clipboard
= NULL
;
439 return LIMIT(x
, 0, term
.col
-1);
448 return LIMIT(y
, 0, term
.row
-1);
456 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
459 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
470 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
471 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
472 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
474 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
475 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
477 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
478 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
480 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
481 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
483 /* expand selection over line breaks */
484 if (sel
.type
== SEL_RECTANGULAR
)
486 i
= tlinelen(sel
.nb
.y
);
489 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
490 sel
.ne
.x
= term
.col
- 1;
494 selected(int x
, int y
)
496 if (sel
.mode
== SEL_EMPTY
)
499 if (sel
.type
== SEL_RECTANGULAR
)
500 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
501 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
503 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
504 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
505 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
509 selsnap(int *x
, int *y
, int direction
)
511 int newx
, newy
, xt
, yt
;
512 int delim
, prevdelim
;
518 * Snap around if the word wraps around at the end or
519 * beginning of a line.
521 prevgp
= &term
.line
[*y
][*x
];
522 prevdelim
= ISDELIM(prevgp
->u
);
524 newx
= *x
+ direction
;
526 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
528 newx
= (newx
+ term
.col
) % term
.col
;
529 if (!BETWEEN(newy
, 0, term
.row
- 1))
535 yt
= newy
, xt
= newx
;
536 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
540 if (newx
>= tlinelen(newy
))
543 gp
= &term
.line
[newy
][newx
];
544 delim
= ISDELIM(gp
->u
);
545 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
546 || (delim
&& gp
->u
!= prevgp
->u
)))
557 * Snap around if the the previous line or the current one
558 * has set ATTR_WRAP at its end. Then the whole next or
559 * previous line will be selected.
561 *x
= (direction
< 0) ? 0 : term
.col
- 1;
563 for (; *y
> 0; *y
+= direction
) {
564 if (!(term
.line
[*y
-1][term
.col
-1].mode
569 } else if (direction
> 0) {
570 for (; *y
< term
.row
-1; *y
+= direction
) {
571 if (!(term
.line
[*y
][term
.col
-1].mode
585 int y
, bufsize
, lastx
, linelen
;
591 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
592 ptr
= str
= xmalloc(bufsize
);
594 /* append every set & selected glyph to the selection */
595 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
596 if ((linelen
= tlinelen(y
)) == 0) {
601 if (sel
.type
== SEL_RECTANGULAR
) {
602 gp
= &term
.line
[y
][sel
.nb
.x
];
605 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
606 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
608 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
609 while (last
>= gp
&& last
->u
== ' ')
612 for ( ; gp
<= last
; ++gp
) {
613 if (gp
->mode
& ATTR_WDUMMY
)
616 ptr
+= utf8encode(gp
->u
, ptr
);
620 * Copy and pasting of line endings is inconsistent
621 * in the inconsistent terminal and GUI world.
622 * The best solution seems like to produce '\n' when
623 * something is copied from st and convert '\n' to
624 * '\r', when something to be pasted is received by
626 * FIXME: Fix the computer world.
628 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
636 selpaste(const Arg
*dummy
)
642 clipcopy(const Arg
*dummy
)
648 clippaste(const Arg
*dummy
)
660 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
664 die(const char *errstr
, ...)
668 va_start(ap
, errstr
);
669 vfprintf(stderr
, errstr
, ap
);
677 char **args
, *sh
, *prog
;
678 const struct passwd
*pw
;
681 if ((pw
= getpwuid(getuid())) == NULL
) {
683 die("getpwuid:%s\n", strerror(errno
));
685 die("who are you?\n");
688 if ((sh
= getenv("SHELL")) == NULL
)
689 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
697 args
= (opt_cmd
) ? opt_cmd
: (char *[]) {prog
, NULL
};
702 setenv("LOGNAME", pw
->pw_name
, 1);
703 setenv("USER", pw
->pw_name
, 1);
704 setenv("SHELL", sh
, 1);
705 setenv("HOME", pw
->pw_dir
, 1);
706 setenv("TERM", termname
, 1);
708 signal(SIGCHLD
, SIG_DFL
);
709 signal(SIGHUP
, SIG_DFL
);
710 signal(SIGINT
, SIG_DFL
);
711 signal(SIGQUIT
, SIG_DFL
);
712 signal(SIGTERM
, SIG_DFL
);
713 signal(SIGALRM
, SIG_DFL
);
725 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
726 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
731 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
732 die("child finished with error '%d'\n", stat
);
740 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
743 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
744 die("incorrect stty parameters\n");
745 memcpy(cmd
, stty_args
, n
);
747 siz
= sizeof(cmd
) - n
;
748 for (p
= opt_cmd
; p
&& (s
= *p
); ++p
) {
749 if ((n
= strlen(s
)) > siz
-1)
750 die("stty parameter length too long\n");
757 if (system(cmd
) != 0)
758 perror("Couldn't call stty");
765 struct winsize w
= {term
.row
, term
.col
, 0, 0};
768 term
.mode
|= MODE_PRINT
;
769 iofd
= (!strcmp(opt_io
, "-")) ?
770 1 : open(opt_io
, O_WRONLY
| O_CREAT
, 0666);
772 fprintf(stderr
, "Error opening %s:%s\n",
773 opt_io
, strerror(errno
));
778 if ((cmdfd
= open(opt_line
, O_RDWR
)) < 0)
779 die("open line failed: %s\n", strerror(errno
));
785 /* seems to work fine on linux, openbsd and freebsd */
786 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
787 die("openpty failed: %s\n", strerror(errno
));
789 switch (pid
= fork()) {
791 die("fork failed\n");
795 setsid(); /* create a new process group */
799 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
800 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
808 signal(SIGCHLD
, sigchld
);
816 static char buf
[BUFSIZ
];
817 static int buflen
= 0;
819 int charsize
; /* size of utf8 char in bytes */
823 /* append read bytes to unprocessed bytes */
824 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
825 die("Couldn't read from shell: %s\n", strerror(errno
));
831 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
832 /* process a complete utf8 char */
833 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
843 tputc(*ptr
++ & 0xFF);
847 /* keep any uncomplete utf8 char for the next call */
849 memmove(buf
, ptr
, buflen
);
855 ttywrite(const char *s
, size_t n
)
862 * Remember that we are using a pty, which might be a modem line.
863 * Writing too much will clog the line. That's why we are doing this
865 * FIXME: Migrate the world to Plan 9.
873 /* Check if we can write. */
874 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
877 die("select failed: %s\n", strerror(errno
));
879 if (FD_ISSET(cmdfd
, &wfd
)) {
881 * Only write the bytes written by ttywrite() or the
882 * default of 256. This seems to be a reasonable value
883 * for a serial line. Bigger values might clog the I/O.
885 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
889 * We weren't able to write out everything.
890 * This means the buffer is getting full
898 /* All bytes have been written. */
902 if (FD_ISSET(cmdfd
, &rfd
))
908 die("write error on tty: %s\n", strerror(errno
));
912 ttysend(char *s
, size_t n
)
919 if (!IS_SET(MODE_ECHO
))
923 for (t
= s
; t
< lim
; t
+= len
) {
924 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
925 len
= utf8decode(t
, &u
, n
);
944 w
.ws_xpixel
= win
.tw
;
945 w
.ws_ypixel
= win
.th
;
946 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
947 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
955 for (i
= 0; i
< term
.row
-1; i
++) {
956 for (j
= 0; j
< term
.col
-1; j
++) {
957 if (term
.line
[i
][j
].mode
& attr
)
966 tsetdirt(int top
, int bot
)
970 LIMIT(top
, 0, term
.row
-1);
971 LIMIT(bot
, 0, term
.row
-1);
973 for (i
= top
; i
<= bot
; i
++)
978 tsetdirtattr(int attr
)
982 for (i
= 0; i
< term
.row
-1; i
++) {
983 for (j
= 0; j
< term
.col
-1; j
++) {
984 if (term
.line
[i
][j
].mode
& attr
) {
995 tsetdirt(0, term
.row
-1);
1001 static TCursor c
[2];
1002 int alt
= IS_SET(MODE_ALTSCREEN
);
1004 if (mode
== CURSOR_SAVE
) {
1006 } else if (mode
== CURSOR_LOAD
) {
1008 tmoveto(c
[alt
].x
, c
[alt
].y
);
1017 term
.c
= (TCursor
){{
1021 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
1023 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1024 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1027 term
.bot
= term
.row
- 1;
1028 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1029 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1032 for (i
= 0; i
< 2; i
++) {
1034 tcursor(CURSOR_SAVE
);
1035 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1041 tnew(int col
, int row
)
1043 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1053 Line
*tmp
= term
.line
;
1055 term
.line
= term
.alt
;
1057 term
.mode
^= MODE_ALTSCREEN
;
1062 tscrolldown(int orig
, int n
)
1067 LIMIT(n
, 0, term
.bot
-orig
+1);
1069 tsetdirt(orig
, term
.bot
-n
);
1070 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1072 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1073 temp
= term
.line
[i
];
1074 term
.line
[i
] = term
.line
[i
-n
];
1075 term
.line
[i
-n
] = temp
;
1082 tscrollup(int orig
, int n
)
1087 LIMIT(n
, 0, term
.bot
-orig
+1);
1089 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1090 tsetdirt(orig
+n
, term
.bot
);
1092 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1093 temp
= term
.line
[i
];
1094 term
.line
[i
] = term
.line
[i
+n
];
1095 term
.line
[i
+n
] = temp
;
1098 selscroll(orig
, -n
);
1102 selscroll(int orig
, int n
)
1107 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1108 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1112 if (sel
.type
== SEL_RECTANGULAR
) {
1113 if (sel
.ob
.y
< term
.top
)
1114 sel
.ob
.y
= term
.top
;
1115 if (sel
.oe
.y
> term
.bot
)
1116 sel
.oe
.y
= term
.bot
;
1118 if (sel
.ob
.y
< term
.top
) {
1119 sel
.ob
.y
= term
.top
;
1122 if (sel
.oe
.y
> term
.bot
) {
1123 sel
.oe
.y
= term
.bot
;
1124 sel
.oe
.x
= term
.col
;
1132 tnewline(int first_col
)
1136 if (y
== term
.bot
) {
1137 tscrollup(term
.top
, 1);
1141 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1147 char *p
= csiescseq
.buf
, *np
;
1156 csiescseq
.buf
[csiescseq
.len
] = '\0';
1157 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1159 v
= strtol(p
, &np
, 10);
1162 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1164 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1166 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1170 csiescseq
.mode
[0] = *p
++;
1171 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1174 /* for absolute user moves, when decom is set */
1176 tmoveato(int x
, int y
)
1178 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1182 tmoveto(int x
, int y
)
1186 if (term
.c
.state
& CURSOR_ORIGIN
) {
1191 maxy
= term
.row
- 1;
1193 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1194 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1195 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1199 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1201 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1202 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1203 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1204 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1205 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1206 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1207 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1208 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1209 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1213 * The table is proudly stolen from rxvt.
1215 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1216 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1217 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1219 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1220 if (x
+1 < term
.col
) {
1221 term
.line
[y
][x
+1].u
= ' ';
1222 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1224 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1225 term
.line
[y
][x
-1].u
= ' ';
1226 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1230 term
.line
[y
][x
] = *attr
;
1231 term
.line
[y
][x
].u
= u
;
1235 tclearregion(int x1
, int y1
, int x2
, int y2
)
1241 temp
= x1
, x1
= x2
, x2
= temp
;
1243 temp
= y1
, y1
= y2
, y2
= temp
;
1245 LIMIT(x1
, 0, term
.col
-1);
1246 LIMIT(x2
, 0, term
.col
-1);
1247 LIMIT(y1
, 0, term
.row
-1);
1248 LIMIT(y2
, 0, term
.row
-1);
1250 for (y
= y1
; y
<= y2
; y
++) {
1252 for (x
= x1
; x
<= x2
; x
++) {
1253 gp
= &term
.line
[y
][x
];
1256 gp
->fg
= term
.c
.attr
.fg
;
1257 gp
->bg
= term
.c
.attr
.bg
;
1270 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1274 size
= term
.col
- src
;
1275 line
= term
.line
[term
.c
.y
];
1277 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1278 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1287 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1291 size
= term
.col
- dst
;
1292 line
= term
.line
[term
.c
.y
];
1294 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1295 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1299 tinsertblankline(int n
)
1301 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1302 tscrolldown(term
.c
.y
, n
);
1308 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1309 tscrollup(term
.c
.y
, n
);
1313 tdefcolor(int *attr
, int *npar
, int l
)
1318 switch (attr
[*npar
+ 1]) {
1319 case 2: /* direct color in RGB space */
1320 if (*npar
+ 4 >= l
) {
1322 "erresc(38): Incorrect number of parameters (%d)\n",
1326 r
= attr
[*npar
+ 2];
1327 g
= attr
[*npar
+ 3];
1328 b
= attr
[*npar
+ 4];
1330 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1331 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1334 idx
= TRUECOLOR(r
, g
, b
);
1336 case 5: /* indexed color */
1337 if (*npar
+ 2 >= l
) {
1339 "erresc(38): Incorrect number of parameters (%d)\n",
1344 if (!BETWEEN(attr
[*npar
], 0, 255))
1345 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1349 case 0: /* implemented defined (only foreground) */
1350 case 1: /* transparent */
1351 case 3: /* direct color in CMY space */
1352 case 4: /* direct color in CMYK space */
1355 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1363 tsetattr(int *attr
, int l
)
1368 for (i
= 0; i
< l
; i
++) {
1371 term
.c
.attr
.mode
&= ~(
1380 term
.c
.attr
.fg
= defaultfg
;
1381 term
.c
.attr
.bg
= defaultbg
;
1384 term
.c
.attr
.mode
|= ATTR_BOLD
;
1387 term
.c
.attr
.mode
|= ATTR_FAINT
;
1390 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1393 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1395 case 5: /* slow blink */
1397 case 6: /* rapid blink */
1398 term
.c
.attr
.mode
|= ATTR_BLINK
;
1401 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1404 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1407 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1410 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1413 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1416 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1419 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1422 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1425 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1428 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1431 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1432 term
.c
.attr
.fg
= idx
;
1435 term
.c
.attr
.fg
= defaultfg
;
1438 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1439 term
.c
.attr
.bg
= idx
;
1442 term
.c
.attr
.bg
= defaultbg
;
1445 if (BETWEEN(attr
[i
], 30, 37)) {
1446 term
.c
.attr
.fg
= attr
[i
] - 30;
1447 } else if (BETWEEN(attr
[i
], 40, 47)) {
1448 term
.c
.attr
.bg
= attr
[i
] - 40;
1449 } else if (BETWEEN(attr
[i
], 90, 97)) {
1450 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1451 } else if (BETWEEN(attr
[i
], 100, 107)) {
1452 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1455 "erresc(default): gfx attr %d unknown\n",
1456 attr
[i
]), csidump();
1464 tsetscroll(int t
, int b
)
1468 LIMIT(t
, 0, term
.row
-1);
1469 LIMIT(b
, 0, term
.row
-1);
1480 tsetmode(int priv
, int set
, int *args
, int narg
)
1485 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1488 case 1: /* DECCKM -- Cursor key */
1489 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1491 case 5: /* DECSCNM -- Reverse video */
1493 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1494 if (mode
!= term
.mode
)
1497 case 6: /* DECOM -- Origin */
1498 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1501 case 7: /* DECAWM -- Auto wrap */
1502 MODBIT(term
.mode
, set
, MODE_WRAP
);
1504 case 0: /* Error (IGNORED) */
1505 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1506 case 3: /* DECCOLM -- Column (IGNORED) */
1507 case 4: /* DECSCLM -- Scroll (IGNORED) */
1508 case 8: /* DECARM -- Auto repeat (IGNORED) */
1509 case 18: /* DECPFF -- Printer feed (IGNORED) */
1510 case 19: /* DECPEX -- Printer extent (IGNORED) */
1511 case 42: /* DECNRCM -- National characters (IGNORED) */
1512 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1514 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1515 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1517 case 9: /* X10 mouse compatibility mode */
1518 xsetpointermotion(0);
1519 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1520 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1522 case 1000: /* 1000: report button press */
1523 xsetpointermotion(0);
1524 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1525 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1527 case 1002: /* 1002: report motion on button press */
1528 xsetpointermotion(0);
1529 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1530 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1532 case 1003: /* 1003: enable all mouse motions */
1533 xsetpointermotion(set
);
1534 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1535 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1537 case 1004: /* 1004: send focus events to tty */
1538 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1540 case 1006: /* 1006: extended reporting mode */
1541 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1544 MODBIT(term
.mode
, set
, MODE_8BIT
);
1546 case 1049: /* swap screen & set/restore cursor as xterm */
1547 if (!allowaltscreen
)
1549 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1551 case 47: /* swap screen */
1553 if (!allowaltscreen
)
1555 alt
= IS_SET(MODE_ALTSCREEN
);
1557 tclearregion(0, 0, term
.col
-1,
1560 if (set
^ alt
) /* set is always 1 or 0 */
1566 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1568 case 2004: /* 2004: bracketed paste mode */
1569 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1571 /* Not implemented mouse modes. See comments there. */
1572 case 1001: /* mouse highlight mode; can hang the
1573 terminal by design when implemented. */
1574 case 1005: /* UTF-8 mouse mode; will confuse
1575 applications not supporting UTF-8
1577 case 1015: /* urxvt mangled mouse mode; incompatible
1578 and can be mistaken for other control
1582 "erresc: unknown private set/reset mode %d\n",
1588 case 0: /* Error (IGNORED) */
1590 case 2: /* KAM -- keyboard action */
1591 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1593 case 4: /* IRM -- Insertion-replacement */
1594 MODBIT(term
.mode
, set
, MODE_INSERT
);
1596 case 12: /* SRM -- Send/Receive */
1597 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1599 case 20: /* LNM -- Linefeed/new line */
1600 MODBIT(term
.mode
, set
, MODE_CRLF
);
1604 "erresc: unknown set/reset mode %d\n",
1618 switch (csiescseq
.mode
[0]) {
1621 fprintf(stderr
, "erresc: unknown csi ");
1625 case '@': /* ICH -- Insert <n> blank char */
1626 DEFAULT(csiescseq
.arg
[0], 1);
1627 tinsertblank(csiescseq
.arg
[0]);
1629 case 'A': /* CUU -- Cursor <n> Up */
1630 DEFAULT(csiescseq
.arg
[0], 1);
1631 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1633 case 'B': /* CUD -- Cursor <n> Down */
1634 case 'e': /* VPR --Cursor <n> Down */
1635 DEFAULT(csiescseq
.arg
[0], 1);
1636 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1638 case 'i': /* MC -- Media Copy */
1639 switch (csiescseq
.arg
[0]) {
1644 tdumpline(term
.c
.y
);
1650 term
.mode
&= ~MODE_PRINT
;
1653 term
.mode
|= MODE_PRINT
;
1657 case 'c': /* DA -- Device Attributes */
1658 if (csiescseq
.arg
[0] == 0)
1659 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1661 case 'C': /* CUF -- Cursor <n> Forward */
1662 case 'a': /* HPR -- Cursor <n> Forward */
1663 DEFAULT(csiescseq
.arg
[0], 1);
1664 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1666 case 'D': /* CUB -- Cursor <n> Backward */
1667 DEFAULT(csiescseq
.arg
[0], 1);
1668 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1670 case 'E': /* CNL -- Cursor <n> Down and first col */
1671 DEFAULT(csiescseq
.arg
[0], 1);
1672 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1674 case 'F': /* CPL -- Cursor <n> Up and first col */
1675 DEFAULT(csiescseq
.arg
[0], 1);
1676 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1678 case 'g': /* TBC -- Tabulation clear */
1679 switch (csiescseq
.arg
[0]) {
1680 case 0: /* clear current tab stop */
1681 term
.tabs
[term
.c
.x
] = 0;
1683 case 3: /* clear all the tabs */
1684 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1690 case 'G': /* CHA -- Move to <col> */
1692 DEFAULT(csiescseq
.arg
[0], 1);
1693 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1695 case 'H': /* CUP -- Move to <row> <col> */
1697 DEFAULT(csiescseq
.arg
[0], 1);
1698 DEFAULT(csiescseq
.arg
[1], 1);
1699 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1701 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1702 DEFAULT(csiescseq
.arg
[0], 1);
1703 tputtab(csiescseq
.arg
[0]);
1705 case 'J': /* ED -- Clear screen */
1707 switch (csiescseq
.arg
[0]) {
1709 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1710 if (term
.c
.y
< term
.row
-1) {
1711 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1717 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1718 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1721 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1727 case 'K': /* EL -- Clear line */
1728 switch (csiescseq
.arg
[0]) {
1730 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1734 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1737 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1741 case 'S': /* SU -- Scroll <n> line up */
1742 DEFAULT(csiescseq
.arg
[0], 1);
1743 tscrollup(term
.top
, csiescseq
.arg
[0]);
1745 case 'T': /* SD -- Scroll <n> line down */
1746 DEFAULT(csiescseq
.arg
[0], 1);
1747 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1749 case 'L': /* IL -- Insert <n> blank lines */
1750 DEFAULT(csiescseq
.arg
[0], 1);
1751 tinsertblankline(csiescseq
.arg
[0]);
1753 case 'l': /* RM -- Reset Mode */
1754 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1756 case 'M': /* DL -- Delete <n> lines */
1757 DEFAULT(csiescseq
.arg
[0], 1);
1758 tdeleteline(csiescseq
.arg
[0]);
1760 case 'X': /* ECH -- Erase <n> char */
1761 DEFAULT(csiescseq
.arg
[0], 1);
1762 tclearregion(term
.c
.x
, term
.c
.y
,
1763 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1765 case 'P': /* DCH -- Delete <n> char */
1766 DEFAULT(csiescseq
.arg
[0], 1);
1767 tdeletechar(csiescseq
.arg
[0]);
1769 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1770 DEFAULT(csiescseq
.arg
[0], 1);
1771 tputtab(-csiescseq
.arg
[0]);
1773 case 'd': /* VPA -- Move to <row> */
1774 DEFAULT(csiescseq
.arg
[0], 1);
1775 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1777 case 'h': /* SM -- Set terminal mode */
1778 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1780 case 'm': /* SGR -- Terminal attribute (color) */
1781 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1783 case 'n': /* DSR – Device Status Report (cursor position) */
1784 if (csiescseq
.arg
[0] == 6) {
1785 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1786 term
.c
.y
+1, term
.c
.x
+1);
1790 case 'r': /* DECSTBM -- Set Scrolling Region */
1791 if (csiescseq
.priv
) {
1794 DEFAULT(csiescseq
.arg
[0], 1);
1795 DEFAULT(csiescseq
.arg
[1], term
.row
);
1796 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1800 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1801 tcursor(CURSOR_SAVE
);
1803 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1804 tcursor(CURSOR_LOAD
);
1807 switch (csiescseq
.mode
[1]) {
1808 case 'q': /* DECSCUSR -- Set Cursor Style */
1809 DEFAULT(csiescseq
.arg
[0], 1);
1810 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1813 win
.cursor
= csiescseq
.arg
[0];
1828 fprintf(stderr
, "ESC[");
1829 for (i
= 0; i
< csiescseq
.len
; i
++) {
1830 c
= csiescseq
.buf
[i
] & 0xff;
1833 } else if (c
== '\n') {
1834 fprintf(stderr
, "(\\n)");
1835 } else if (c
== '\r') {
1836 fprintf(stderr
, "(\\r)");
1837 } else if (c
== 0x1b) {
1838 fprintf(stderr
, "(\\e)");
1840 fprintf(stderr
, "(%02x)", c
);
1849 memset(&csiescseq
, 0, sizeof(csiescseq
));
1858 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1860 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1862 switch (strescseq
.type
) {
1863 case ']': /* OSC -- Operating System Command */
1869 xsettitle(strescseq
.args
[1]);
1875 dec
= base64dec(strescseq
.args
[2]);
1877 xsetsel(dec
, CurrentTime
);
1880 fprintf(stderr
, "erresc: invalid base64\n");
1884 case 4: /* color set */
1887 p
= strescseq
.args
[2];
1889 case 104: /* color reset, here p = NULL */
1890 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1891 if (xsetcolorname(j
, p
)) {
1892 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1895 * TODO if defaultbg color is changed, borders
1903 case 'k': /* old title set compatibility */
1904 xsettitle(strescseq
.args
[0]);
1906 case 'P': /* DCS -- Device Control String */
1907 term
.mode
|= ESC_DCS
;
1908 case '_': /* APC -- Application Program Command */
1909 case '^': /* PM -- Privacy Message */
1913 fprintf(stderr
, "erresc: unknown str ");
1921 char *p
= strescseq
.buf
;
1924 strescseq
.buf
[strescseq
.len
] = '\0';
1929 while (strescseq
.narg
< STR_ARG_SIZ
) {
1930 strescseq
.args
[strescseq
.narg
++] = p
;
1931 while ((c
= *p
) != ';' && c
!= '\0')
1945 fprintf(stderr
, "ESC%c", strescseq
.type
);
1946 for (i
= 0; i
< strescseq
.len
; i
++) {
1947 c
= strescseq
.buf
[i
] & 0xff;
1951 } else if (isprint(c
)) {
1953 } else if (c
== '\n') {
1954 fprintf(stderr
, "(\\n)");
1955 } else if (c
== '\r') {
1956 fprintf(stderr
, "(\\r)");
1957 } else if (c
== 0x1b) {
1958 fprintf(stderr
, "(\\e)");
1960 fprintf(stderr
, "(%02x)", c
);
1963 fprintf(stderr
, "ESC\\\n");
1969 memset(&strescseq
, 0, sizeof(strescseq
));
1973 sendbreak(const Arg
*arg
)
1975 if (tcsendbreak(cmdfd
, 0))
1976 perror("Error sending break");
1980 tprinter(char *s
, size_t len
)
1982 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1983 fprintf(stderr
, "Error writing in %s:%s\n",
1984 opt_io
, strerror(errno
));
1991 iso14755(const Arg
*arg
)
1994 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1995 unsigned long utf32
;
1997 if (!(p
= popen(ISO14755CMD
, "r")))
2000 us
= fgets(codepoint
, sizeof(codepoint
), p
);
2003 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
2005 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
2006 (*e
!= '\n' && *e
!= '\0'))
2009 ttysend(uc
, utf8encode(utf32
, uc
));
2013 toggleprinter(const Arg
*arg
)
2015 term
.mode
^= MODE_PRINT
;
2019 printscreen(const Arg
*arg
)
2025 printsel(const Arg
*arg
)
2035 if ((ptr
= getsel())) {
2036 tprinter(ptr
, strlen(ptr
));
2047 bp
= &term
.line
[n
][0];
2048 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2049 if (bp
!= end
|| bp
->u
!= ' ') {
2050 for ( ;bp
<= end
; ++bp
)
2051 tprinter(buf
, utf8encode(bp
->u
, buf
));
2061 for (i
= 0; i
< term
.row
; ++i
)
2071 while (x
< term
.col
&& n
--)
2072 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2075 while (x
> 0 && n
++)
2076 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2079 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2085 if (ISCONTROL(u
)) { /* control code */
2090 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2099 tdefutf8(char ascii
)
2102 term
.mode
|= MODE_UTF8
;
2103 else if (ascii
== '@')
2104 term
.mode
&= ~MODE_UTF8
;
2108 tdeftran(char ascii
)
2110 static char cs
[] = "0B";
2111 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2114 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2115 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2117 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2126 if (c
== '8') { /* DEC screen alignment test. */
2127 for (x
= 0; x
< term
.col
; ++x
) {
2128 for (y
= 0; y
< term
.row
; ++y
)
2129 tsetchar('E', &term
.c
.attr
, x
, y
);
2135 tstrsequence(uchar c
)
2140 case 0x90: /* DCS -- Device Control String */
2142 term
.esc
|= ESC_DCS
;
2144 case 0x9f: /* APC -- Application Program Command */
2147 case 0x9e: /* PM -- Privacy Message */
2150 case 0x9d: /* OSC -- Operating System Command */
2155 term
.esc
|= ESC_STR
;
2159 tcontrolcode(uchar ascii
)
2166 tmoveto(term
.c
.x
-1, term
.c
.y
);
2169 tmoveto(0, term
.c
.y
);
2174 /* go to first col if the mode is set */
2175 tnewline(IS_SET(MODE_CRLF
));
2177 case '\a': /* BEL */
2178 if (term
.esc
& ESC_STR_END
) {
2179 /* backwards compatibility to xterm */
2182 if (!(win
.state
& WIN_FOCUSED
))
2188 case '\033': /* ESC */
2190 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2191 term
.esc
|= ESC_START
;
2193 case '\016': /* SO (LS1 -- Locking shift 1) */
2194 case '\017': /* SI (LS0 -- Locking shift 0) */
2195 term
.charset
= 1 - (ascii
- '\016');
2197 case '\032': /* SUB */
2198 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2199 case '\030': /* CAN */
2202 case '\005': /* ENQ (IGNORED) */
2203 case '\000': /* NUL (IGNORED) */
2204 case '\021': /* XON (IGNORED) */
2205 case '\023': /* XOFF (IGNORED) */
2206 case 0177: /* DEL (IGNORED) */
2208 case 0x80: /* TODO: PAD */
2209 case 0x81: /* TODO: HOP */
2210 case 0x82: /* TODO: BPH */
2211 case 0x83: /* TODO: NBH */
2212 case 0x84: /* TODO: IND */
2214 case 0x85: /* NEL -- Next line */
2215 tnewline(1); /* always go to first col */
2217 case 0x86: /* TODO: SSA */
2218 case 0x87: /* TODO: ESA */
2220 case 0x88: /* HTS -- Horizontal tab stop */
2221 term
.tabs
[term
.c
.x
] = 1;
2223 case 0x89: /* TODO: HTJ */
2224 case 0x8a: /* TODO: VTS */
2225 case 0x8b: /* TODO: PLD */
2226 case 0x8c: /* TODO: PLU */
2227 case 0x8d: /* TODO: RI */
2228 case 0x8e: /* TODO: SS2 */
2229 case 0x8f: /* TODO: SS3 */
2230 case 0x91: /* TODO: PU1 */
2231 case 0x92: /* TODO: PU2 */
2232 case 0x93: /* TODO: STS */
2233 case 0x94: /* TODO: CCH */
2234 case 0x95: /* TODO: MW */
2235 case 0x96: /* TODO: SPA */
2236 case 0x97: /* TODO: EPA */
2237 case 0x98: /* TODO: SOS */
2238 case 0x99: /* TODO: SGCI */
2240 case 0x9a: /* DECID -- Identify Terminal */
2241 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2243 case 0x9b: /* TODO: CSI */
2244 case 0x9c: /* TODO: ST */
2246 case 0x90: /* DCS -- Device Control String */
2247 case 0x9d: /* OSC -- Operating System Command */
2248 case 0x9e: /* PM -- Privacy Message */
2249 case 0x9f: /* APC -- Application Program Command */
2250 tstrsequence(ascii
);
2253 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2254 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2258 * returns 1 when the sequence is finished and it hasn't to read
2259 * more characters for this sequence, otherwise 0
2262 eschandle(uchar ascii
)
2266 term
.esc
|= ESC_CSI
;
2269 term
.esc
|= ESC_TEST
;
2272 term
.esc
|= ESC_UTF8
;
2274 case 'P': /* DCS -- Device Control String */
2275 case '_': /* APC -- Application Program Command */
2276 case '^': /* PM -- Privacy Message */
2277 case ']': /* OSC -- Operating System Command */
2278 case 'k': /* old title set compatibility */
2279 tstrsequence(ascii
);
2281 case 'n': /* LS2 -- Locking shift 2 */
2282 case 'o': /* LS3 -- Locking shift 3 */
2283 term
.charset
= 2 + (ascii
- 'n');
2285 case '(': /* GZD4 -- set primary charset G0 */
2286 case ')': /* G1D4 -- set secondary charset G1 */
2287 case '*': /* G2D4 -- set tertiary charset G2 */
2288 case '+': /* G3D4 -- set quaternary charset G3 */
2289 term
.icharset
= ascii
- '(';
2290 term
.esc
|= ESC_ALTCHARSET
;
2292 case 'D': /* IND -- Linefeed */
2293 if (term
.c
.y
== term
.bot
) {
2294 tscrollup(term
.top
, 1);
2296 tmoveto(term
.c
.x
, term
.c
.y
+1);
2299 case 'E': /* NEL -- Next line */
2300 tnewline(1); /* always go to first col */
2302 case 'H': /* HTS -- Horizontal tab stop */
2303 term
.tabs
[term
.c
.x
] = 1;
2305 case 'M': /* RI -- Reverse index */
2306 if (term
.c
.y
== term
.top
) {
2307 tscrolldown(term
.top
, 1);
2309 tmoveto(term
.c
.x
, term
.c
.y
-1);
2312 case 'Z': /* DECID -- Identify Terminal */
2313 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2315 case 'c': /* RIS -- Reset to inital state */
2320 case '=': /* DECPAM -- Application keypad */
2321 term
.mode
|= MODE_APPKEYPAD
;
2323 case '>': /* DECPNM -- Normal keypad */
2324 term
.mode
&= ~MODE_APPKEYPAD
;
2326 case '7': /* DECSC -- Save Cursor */
2327 tcursor(CURSOR_SAVE
);
2329 case '8': /* DECRC -- Restore Cursor */
2330 tcursor(CURSOR_LOAD
);
2332 case '\\': /* ST -- String Terminator */
2333 if (term
.esc
& ESC_STR_END
)
2337 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2338 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2352 control
= ISCONTROL(u
);
2353 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2357 len
= utf8encode(u
, c
);
2358 if (!control
&& (width
= wcwidth(u
)) == -1) {
2359 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2364 if (IS_SET(MODE_PRINT
))
2368 * STR sequence must be checked before anything else
2369 * because it uses all following characters until it
2370 * receives a ESC, a SUB, a ST or any other C1 control
2373 if (term
.esc
& ESC_STR
) {
2374 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2376 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2377 if (IS_SET(MODE_SIXEL
)) {
2378 /* TODO: render sixel */;
2379 term
.mode
&= ~MODE_SIXEL
;
2382 term
.esc
|= ESC_STR_END
;
2383 goto check_control_code
;
2387 if (IS_SET(MODE_SIXEL
)) {
2388 /* TODO: implement sixel mode */
2391 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2392 term
.mode
|= MODE_SIXEL
;
2394 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2396 * Here is a bug in terminals. If the user never sends
2397 * some code to stop the str or esc command, then st
2398 * will stop responding. But this is better than
2399 * silently failing with unknown characters. At least
2400 * then users will report back.
2402 * In the case users ever get fixed, here is the code:
2411 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2412 strescseq
.len
+= len
;
2418 * Actions of control codes must be performed as soon they arrive
2419 * because they can be embedded inside a control sequence, and
2420 * they must not cause conflicts with sequences.
2425 * control codes are not shown ever
2428 } else if (term
.esc
& ESC_START
) {
2429 if (term
.esc
& ESC_CSI
) {
2430 csiescseq
.buf
[csiescseq
.len
++] = u
;
2431 if (BETWEEN(u
, 0x40, 0x7E)
2432 || csiescseq
.len
>= \
2433 sizeof(csiescseq
.buf
)-1) {
2439 } else if (term
.esc
& ESC_UTF8
) {
2441 } else if (term
.esc
& ESC_ALTCHARSET
) {
2443 } else if (term
.esc
& ESC_TEST
) {
2448 /* sequence already finished */
2452 * All characters which form part of a sequence are not
2457 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2460 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2461 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2462 gp
->mode
|= ATTR_WRAP
;
2464 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2467 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2468 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2470 if (term
.c
.x
+width
> term
.col
) {
2472 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2475 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2478 gp
->mode
|= ATTR_WIDE
;
2479 if (term
.c
.x
+1 < term
.col
) {
2481 gp
[1].mode
= ATTR_WDUMMY
;
2484 if (term
.c
.x
+width
< term
.col
) {
2485 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2487 term
.c
.state
|= CURSOR_WRAPNEXT
;
2492 tresize(int col
, int row
)
2495 int minrow
= MIN(row
, term
.row
);
2496 int mincol
= MIN(col
, term
.col
);
2500 if (col
< 1 || row
< 1) {
2502 "tresize: error resizing to %dx%d\n", col
, row
);
2507 * slide screen to keep cursor where we expect it -
2508 * tscrollup would work here, but we can optimize to
2509 * memmove because we're freeing the earlier lines
2511 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2515 /* ensure that both src and dst are not NULL */
2517 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2518 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2520 for (i
+= row
; i
< term
.row
; i
++) {
2525 /* resize to new width */
2526 term
.specbuf
= xrealloc(term
.specbuf
, col
* sizeof(GlyphFontSpec
));
2528 /* resize to new height */
2529 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2530 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2531 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2532 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2534 /* resize each row to new width, zero-pad if needed */
2535 for (i
= 0; i
< minrow
; i
++) {
2536 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2537 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2540 /* allocate any new rows */
2541 for (/* i = minrow */; i
< row
; i
++) {
2542 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2543 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2545 if (col
> term
.col
) {
2546 bp
= term
.tabs
+ term
.col
;
2548 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2549 while (--bp
> term
.tabs
&& !*bp
)
2551 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2554 /* update terminal size */
2557 /* reset scrolling region */
2558 tsetscroll(0, row
-1);
2559 /* make use of the LIMIT in tmoveto */
2560 tmoveto(term
.c
.x
, term
.c
.y
);
2561 /* Clearing both screens (it makes dirty all lines) */
2563 for (i
= 0; i
< 2; i
++) {
2564 if (mincol
< col
&& 0 < minrow
) {
2565 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2567 if (0 < col
&& minrow
< row
) {
2568 tclearregion(0, minrow
, col
- 1, row
- 1);
2571 tcursor(CURSOR_LOAD
);
2577 zoom(const Arg
*arg
)
2581 larg
.f
= usedfontsize
+ arg
->f
;
2586 zoomabs(const Arg
*arg
)
2589 xloadfonts(usedfont
, arg
->f
);
2597 zoomreset(const Arg
*arg
)
2601 if (defaultfontsize
> 0) {
2602 larg
.f
= defaultfontsize
;
2610 xsettitle(opt_title
? opt_title
: "st");
2621 match(uint mask
, uint state
)
2623 return mask
== XK_ANY_MOD
|| mask
== (state
& ~ignoremod
);
2627 numlock(const Arg
*dummy
)
2633 kmap(KeySym k
, uint state
)
2638 /* Check for mapped keys out of X11 function keys. */
2639 for (i
= 0; i
< LEN(mappedkeys
); i
++) {
2640 if (mappedkeys
[i
] == k
)
2643 if (i
== LEN(mappedkeys
)) {
2644 if ((k
& 0xFFFF) < 0xFD00)
2648 for (kp
= key
; kp
< key
+ LEN(key
); kp
++) {
2652 if (!match(kp
->mask
, state
))
2655 if (IS_SET(MODE_APPKEYPAD
) ? kp
->appkey
< 0 : kp
->appkey
> 0)
2657 if (term
.numlock
&& kp
->appkey
== 2)
2660 if (IS_SET(MODE_APPCURSOR
) ? kp
->appcursor
< 0 : kp
->appcursor
> 0)
2663 if (IS_SET(MODE_CRLF
) ? kp
->crlf
< 0 : kp
->crlf
> 0)
2673 cresize(int width
, int height
)
2682 col
= (win
.w
- 2 * borderpx
) / win
.cw
;
2683 row
= (win
.h
- 2 * borderpx
) / win
.ch
;