Xinqi Bao's Git
1a8fa1f52148d9c3c608807074fc261574b87be5
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>
36 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
38 #elif defined(__FreeBSD__) || defined(__DragonFly__)
43 #define UTF_INVALID 0xFFFD
44 #define ESC_BUF_SIZ (128*UTF_SIZ)
45 #define ESC_ARG_SIZ 16
46 #define STR_BUF_SIZ ESC_BUF_SIZ
47 #define STR_ARG_SIZ ESC_ARG_SIZ
50 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
51 #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
52 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
53 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
54 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
55 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
58 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
60 enum cursor_movement
{
84 ESC_STR
= 4, /* OSC, PM, APC */
86 ESC_STR_END
= 16, /* a final string was encountered */
87 ESC_TEST
= 32, /* Enter in test mode */
92 /* CSI Escape sequence structs */
93 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
95 char buf
[ESC_BUF_SIZ
]; /* raw string */
96 int len
; /* raw string length */
99 int narg
; /* nb of args */
103 /* STR Escape sequence structs */
104 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
106 char type
; /* ESC type ... */
107 char buf
[STR_BUF_SIZ
]; /* raw string */
108 int len
; /* raw string length */
109 char *args
[STR_ARG_SIZ
];
110 int narg
; /* nb of args */
113 /* function definitions used in config.h */
114 static void clipcopy(const Arg
*);
115 static void clippaste(const Arg
*);
116 static void numlock(const Arg
*);
117 static void selpaste(const Arg
*);
118 static void printsel(const Arg
*);
119 static void printscreen(const Arg
*) ;
120 static void iso14755(const Arg
*);
121 static void toggleprinter(const Arg
*);
122 static void sendbreak(const Arg
*);
124 /* config.h for applying patches and the configuration. */
127 static void execsh(void);
128 static void stty(void);
129 static void sigchld(int);
131 static void csidump(void);
132 static void csihandle(void);
133 static void csiparse(void);
134 static void csireset(void);
135 static int eschandle(uchar
);
136 static void strdump(void);
137 static void strhandle(void);
138 static void strparse(void);
139 static void strreset(void);
141 static void tprinter(char *, size_t);
142 static void tdumpsel(void);
143 static void tdumpline(int);
144 static void tdump(void);
145 static void tclearregion(int, int, int, int);
146 static void tcursor(int);
147 static void tdeletechar(int);
148 static void tdeleteline(int);
149 static void tinsertblank(int);
150 static void tinsertblankline(int);
151 static int tlinelen(int);
152 static void tmoveto(int, int);
153 static void tmoveato(int, int);
154 static void tnewline(int);
155 static void tputtab(int);
156 static void tputc(Rune
);
157 static void treset(void);
158 static void tscrollup(int, int);
159 static void tscrolldown(int, int);
160 static void tsetattr(int *, int);
161 static void tsetchar(Rune
, Glyph
*, int, int);
162 static void tsetscroll(int, int);
163 static void tswapscreen(void);
164 static void tsetmode(int, int, int *, int);
165 static void tfulldirt(void);
166 static void techo(Rune
);
167 static void tcontrolcode(uchar
);
168 static void tdectest(char );
169 static void tdefutf8(char);
170 static int32_t tdefcolor(int *, int *, int);
171 static void tdeftran(char);
172 static void tstrsequence(uchar
);
174 static void selscroll(int, int);
175 static void selsnap(int *, int *, int);
177 static Rune
utf8decodebyte(char, size_t *);
178 static char utf8encodebyte(Rune
, size_t);
179 static char *utf8strchr(char *s
, Rune u
);
180 static size_t utf8validate(Rune
*, size_t);
182 static char *base64dec(const char *);
184 static ssize_t
xwrite(int, const char *, size_t);
192 char **opt_cmd
= NULL
;
193 char *opt_class
= NULL
;
194 char *opt_embed
= NULL
;
195 char *opt_font
= NULL
;
197 char *opt_line
= NULL
;
198 char *opt_name
= NULL
;
199 char *opt_title
= NULL
;
200 int oldbutton
= 3; /* button event on startup: 3 = release */
202 static CSIEscape csiescseq
;
203 static STREscape strescseq
;
206 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
207 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
208 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
209 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
211 /* config.h array lengths */
212 size_t colornamelen
= LEN(colorname
);
213 size_t mshortcutslen
= LEN(mshortcuts
);
214 size_t shortcutslen
= LEN(shortcuts
);
215 size_t selmaskslen
= LEN(selmasks
);
216 size_t keyslen
= LEN(key
);
217 size_t mappedkeyslen
= LEN(mappedkeys
);
220 xwrite(int fd
, const char *s
, size_t len
)
226 r
= write(fd
, s
, len
);
239 void *p
= malloc(len
);
242 die("Out of memory\n");
248 xrealloc(void *p
, size_t len
)
250 if ((p
= realloc(p
, len
)) == NULL
)
251 die("Out of memory\n");
259 if ((s
= strdup(s
)) == NULL
)
260 die("Out of memory\n");
266 utf8decode(char *c
, Rune
*u
, size_t clen
)
268 size_t i
, j
, len
, type
;
274 udecoded
= utf8decodebyte(c
[0], &len
);
275 if (!BETWEEN(len
, 1, UTF_SIZ
))
277 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
278 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
285 utf8validate(u
, len
);
291 utf8decodebyte(char c
, size_t *i
)
293 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
294 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
295 return (uchar
)c
& ~utfmask
[*i
];
301 utf8encode(Rune u
, char *c
)
305 len
= utf8validate(&u
, 0);
309 for (i
= len
- 1; i
!= 0; --i
) {
310 c
[i
] = utf8encodebyte(u
, 0);
313 c
[0] = utf8encodebyte(u
, len
);
319 utf8encodebyte(Rune u
, size_t i
)
321 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
325 utf8strchr(char *s
, Rune u
)
331 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
332 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
342 utf8validate(Rune
*u
, size_t i
)
344 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
346 for (i
= 1; *u
> utfmax
[i
]; ++i
)
352 static const char base64_digits
[] = {
353 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
354 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
355 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
356 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
357 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
358 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
359 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
361 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
362 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
364 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
368 base64dec_getc(const char **src
)
370 while (**src
&& !isprint(**src
)) (*src
)++;
375 base64dec(const char *src
)
377 size_t in_len
= strlen(src
);
381 in_len
+= 4 - (in_len
% 4);
382 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
384 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
385 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
387 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
389 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
392 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
395 *dst
++ = ((c
& 0x03) << 6) | d
;
404 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
405 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
410 sel
.clipboard
= NULL
;
418 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
421 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
432 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
433 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
434 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
436 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
437 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
439 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
440 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
442 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
443 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
445 /* expand selection over line breaks */
446 if (sel
.type
== SEL_RECTANGULAR
)
448 i
= tlinelen(sel
.nb
.y
);
451 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
452 sel
.ne
.x
= term
.col
- 1;
456 selected(int x
, int y
)
458 if (sel
.mode
== SEL_EMPTY
)
461 if (sel
.type
== SEL_RECTANGULAR
)
462 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
463 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
465 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
466 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
467 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
471 selsnap(int *x
, int *y
, int direction
)
473 int newx
, newy
, xt
, yt
;
474 int delim
, prevdelim
;
480 * Snap around if the word wraps around at the end or
481 * beginning of a line.
483 prevgp
= &term
.line
[*y
][*x
];
484 prevdelim
= ISDELIM(prevgp
->u
);
486 newx
= *x
+ direction
;
488 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
490 newx
= (newx
+ term
.col
) % term
.col
;
491 if (!BETWEEN(newy
, 0, term
.row
- 1))
497 yt
= newy
, xt
= newx
;
498 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
502 if (newx
>= tlinelen(newy
))
505 gp
= &term
.line
[newy
][newx
];
506 delim
= ISDELIM(gp
->u
);
507 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
508 || (delim
&& gp
->u
!= prevgp
->u
)))
519 * Snap around if the the previous line or the current one
520 * has set ATTR_WRAP at its end. Then the whole next or
521 * previous line will be selected.
523 *x
= (direction
< 0) ? 0 : term
.col
- 1;
525 for (; *y
> 0; *y
+= direction
) {
526 if (!(term
.line
[*y
-1][term
.col
-1].mode
531 } else if (direction
> 0) {
532 for (; *y
< term
.row
-1; *y
+= direction
) {
533 if (!(term
.line
[*y
][term
.col
-1].mode
547 int y
, bufsize
, lastx
, linelen
;
553 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
554 ptr
= str
= xmalloc(bufsize
);
556 /* append every set & selected glyph to the selection */
557 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
558 if ((linelen
= tlinelen(y
)) == 0) {
563 if (sel
.type
== SEL_RECTANGULAR
) {
564 gp
= &term
.line
[y
][sel
.nb
.x
];
567 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
568 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
570 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
571 while (last
>= gp
&& last
->u
== ' ')
574 for ( ; gp
<= last
; ++gp
) {
575 if (gp
->mode
& ATTR_WDUMMY
)
578 ptr
+= utf8encode(gp
->u
, ptr
);
582 * Copy and pasting of line endings is inconsistent
583 * in the inconsistent terminal and GUI world.
584 * The best solution seems like to produce '\n' when
585 * something is copied from st and convert '\n' to
586 * '\r', when something to be pasted is received by
588 * FIXME: Fix the computer world.
590 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
598 selpaste(const Arg
*dummy
)
604 clipcopy(const Arg
*dummy
)
610 clippaste(const Arg
*dummy
)
622 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
626 die(const char *errstr
, ...)
630 va_start(ap
, errstr
);
631 vfprintf(stderr
, errstr
, ap
);
639 char **args
, *sh
, *prog
;
640 const struct passwd
*pw
;
643 if ((pw
= getpwuid(getuid())) == NULL
) {
645 die("getpwuid:%s\n", strerror(errno
));
647 die("who are you?\n");
650 if ((sh
= getenv("SHELL")) == NULL
)
651 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
659 args
= (opt_cmd
) ? opt_cmd
: (char *[]) {prog
, NULL
};
664 setenv("LOGNAME", pw
->pw_name
, 1);
665 setenv("USER", pw
->pw_name
, 1);
666 setenv("SHELL", sh
, 1);
667 setenv("HOME", pw
->pw_dir
, 1);
668 setenv("TERM", termname
, 1);
670 signal(SIGCHLD
, SIG_DFL
);
671 signal(SIGHUP
, SIG_DFL
);
672 signal(SIGINT
, SIG_DFL
);
673 signal(SIGQUIT
, SIG_DFL
);
674 signal(SIGTERM
, SIG_DFL
);
675 signal(SIGALRM
, SIG_DFL
);
687 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
688 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
693 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
694 die("child finished with error '%d'\n", stat
);
702 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
705 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
706 die("incorrect stty parameters\n");
707 memcpy(cmd
, stty_args
, n
);
709 siz
= sizeof(cmd
) - n
;
710 for (p
= opt_cmd
; p
&& (s
= *p
); ++p
) {
711 if ((n
= strlen(s
)) > siz
-1)
712 die("stty parameter length too long\n");
719 if (system(cmd
) != 0)
720 perror("Couldn't call stty");
727 struct winsize w
= {term
.row
, term
.col
, 0, 0};
730 term
.mode
|= MODE_PRINT
;
731 iofd
= (!strcmp(opt_io
, "-")) ?
732 1 : open(opt_io
, O_WRONLY
| O_CREAT
, 0666);
734 fprintf(stderr
, "Error opening %s:%s\n",
735 opt_io
, strerror(errno
));
740 if ((cmdfd
= open(opt_line
, O_RDWR
)) < 0)
741 die("open line failed: %s\n", strerror(errno
));
747 /* seems to work fine on linux, openbsd and freebsd */
748 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
749 die("openpty failed: %s\n", strerror(errno
));
751 switch (pid
= fork()) {
753 die("fork failed\n");
757 setsid(); /* create a new process group */
761 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
762 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
770 signal(SIGCHLD
, sigchld
);
778 static char buf
[BUFSIZ
];
779 static int buflen
= 0;
781 int charsize
; /* size of utf8 char in bytes */
785 /* append read bytes to unprocessed bytes */
786 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
787 die("Couldn't read from shell: %s\n", strerror(errno
));
793 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
794 /* process a complete utf8 char */
795 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
805 tputc(*ptr
++ & 0xFF);
809 /* keep any uncomplete utf8 char for the next call */
811 memmove(buf
, ptr
, buflen
);
817 ttywrite(const char *s
, size_t n
)
824 * Remember that we are using a pty, which might be a modem line.
825 * Writing too much will clog the line. That's why we are doing this
827 * FIXME: Migrate the world to Plan 9.
835 /* Check if we can write. */
836 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
839 die("select failed: %s\n", strerror(errno
));
841 if (FD_ISSET(cmdfd
, &wfd
)) {
843 * Only write the bytes written by ttywrite() or the
844 * default of 256. This seems to be a reasonable value
845 * for a serial line. Bigger values might clog the I/O.
847 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
851 * We weren't able to write out everything.
852 * This means the buffer is getting full
860 /* All bytes have been written. */
864 if (FD_ISSET(cmdfd
, &rfd
))
870 die("write error on tty: %s\n", strerror(errno
));
874 ttysend(char *s
, size_t n
)
881 if (!IS_SET(MODE_ECHO
))
885 for (t
= s
; t
< lim
; t
+= len
) {
886 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
887 len
= utf8decode(t
, &u
, n
);
900 ttyresize(int tw
, int th
)
908 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
909 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
917 for (i
= 0; i
< term
.row
-1; i
++) {
918 for (j
= 0; j
< term
.col
-1; j
++) {
919 if (term
.line
[i
][j
].mode
& attr
)
928 tsetdirt(int top
, int bot
)
932 LIMIT(top
, 0, term
.row
-1);
933 LIMIT(bot
, 0, term
.row
-1);
935 for (i
= top
; i
<= bot
; i
++)
940 tsetdirtattr(int attr
)
944 for (i
= 0; i
< term
.row
-1; i
++) {
945 for (j
= 0; j
< term
.col
-1; j
++) {
946 if (term
.line
[i
][j
].mode
& attr
) {
957 tsetdirt(0, term
.row
-1);
964 int alt
= IS_SET(MODE_ALTSCREEN
);
966 if (mode
== CURSOR_SAVE
) {
968 } else if (mode
== CURSOR_LOAD
) {
970 tmoveto(c
[alt
].x
, c
[alt
].y
);
983 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
985 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
986 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
989 term
.bot
= term
.row
- 1;
990 term
.mode
= MODE_WRAP
|MODE_UTF8
;
991 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
994 for (i
= 0; i
< 2; i
++) {
996 tcursor(CURSOR_SAVE
);
997 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1003 tnew(int col
, int row
)
1005 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1015 Line
*tmp
= term
.line
;
1017 term
.line
= term
.alt
;
1019 term
.mode
^= MODE_ALTSCREEN
;
1024 tscrolldown(int orig
, int n
)
1029 LIMIT(n
, 0, term
.bot
-orig
+1);
1031 tsetdirt(orig
, term
.bot
-n
);
1032 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1034 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1035 temp
= term
.line
[i
];
1036 term
.line
[i
] = term
.line
[i
-n
];
1037 term
.line
[i
-n
] = temp
;
1044 tscrollup(int orig
, int n
)
1049 LIMIT(n
, 0, term
.bot
-orig
+1);
1051 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1052 tsetdirt(orig
+n
, term
.bot
);
1054 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1055 temp
= term
.line
[i
];
1056 term
.line
[i
] = term
.line
[i
+n
];
1057 term
.line
[i
+n
] = temp
;
1060 selscroll(orig
, -n
);
1064 selscroll(int orig
, int n
)
1069 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1070 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1074 if (sel
.type
== SEL_RECTANGULAR
) {
1075 if (sel
.ob
.y
< term
.top
)
1076 sel
.ob
.y
= term
.top
;
1077 if (sel
.oe
.y
> term
.bot
)
1078 sel
.oe
.y
= term
.bot
;
1080 if (sel
.ob
.y
< term
.top
) {
1081 sel
.ob
.y
= term
.top
;
1084 if (sel
.oe
.y
> term
.bot
) {
1085 sel
.oe
.y
= term
.bot
;
1086 sel
.oe
.x
= term
.col
;
1094 tnewline(int first_col
)
1098 if (y
== term
.bot
) {
1099 tscrollup(term
.top
, 1);
1103 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1109 char *p
= csiescseq
.buf
, *np
;
1118 csiescseq
.buf
[csiescseq
.len
] = '\0';
1119 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1121 v
= strtol(p
, &np
, 10);
1124 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1126 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1128 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1132 csiescseq
.mode
[0] = *p
++;
1133 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1136 /* for absolute user moves, when decom is set */
1138 tmoveato(int x
, int y
)
1140 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1144 tmoveto(int x
, int y
)
1148 if (term
.c
.state
& CURSOR_ORIGIN
) {
1153 maxy
= term
.row
- 1;
1155 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1156 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1157 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1161 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1163 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1164 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1165 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1166 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1167 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1168 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1169 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1170 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1171 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1175 * The table is proudly stolen from rxvt.
1177 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1178 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1179 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1181 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1182 if (x
+1 < term
.col
) {
1183 term
.line
[y
][x
+1].u
= ' ';
1184 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1186 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1187 term
.line
[y
][x
-1].u
= ' ';
1188 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1192 term
.line
[y
][x
] = *attr
;
1193 term
.line
[y
][x
].u
= u
;
1197 tclearregion(int x1
, int y1
, int x2
, int y2
)
1203 temp
= x1
, x1
= x2
, x2
= temp
;
1205 temp
= y1
, y1
= y2
, y2
= temp
;
1207 LIMIT(x1
, 0, term
.col
-1);
1208 LIMIT(x2
, 0, term
.col
-1);
1209 LIMIT(y1
, 0, term
.row
-1);
1210 LIMIT(y2
, 0, term
.row
-1);
1212 for (y
= y1
; y
<= y2
; y
++) {
1214 for (x
= x1
; x
<= x2
; x
++) {
1215 gp
= &term
.line
[y
][x
];
1218 gp
->fg
= term
.c
.attr
.fg
;
1219 gp
->bg
= term
.c
.attr
.bg
;
1232 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1236 size
= term
.col
- src
;
1237 line
= term
.line
[term
.c
.y
];
1239 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1240 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1249 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1253 size
= term
.col
- dst
;
1254 line
= term
.line
[term
.c
.y
];
1256 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1257 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1261 tinsertblankline(int n
)
1263 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1264 tscrolldown(term
.c
.y
, n
);
1270 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1271 tscrollup(term
.c
.y
, n
);
1275 tdefcolor(int *attr
, int *npar
, int l
)
1280 switch (attr
[*npar
+ 1]) {
1281 case 2: /* direct color in RGB space */
1282 if (*npar
+ 4 >= l
) {
1284 "erresc(38): Incorrect number of parameters (%d)\n",
1288 r
= attr
[*npar
+ 2];
1289 g
= attr
[*npar
+ 3];
1290 b
= attr
[*npar
+ 4];
1292 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1293 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1296 idx
= TRUECOLOR(r
, g
, b
);
1298 case 5: /* indexed color */
1299 if (*npar
+ 2 >= l
) {
1301 "erresc(38): Incorrect number of parameters (%d)\n",
1306 if (!BETWEEN(attr
[*npar
], 0, 255))
1307 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1311 case 0: /* implemented defined (only foreground) */
1312 case 1: /* transparent */
1313 case 3: /* direct color in CMY space */
1314 case 4: /* direct color in CMYK space */
1317 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1325 tsetattr(int *attr
, int l
)
1330 for (i
= 0; i
< l
; i
++) {
1333 term
.c
.attr
.mode
&= ~(
1342 term
.c
.attr
.fg
= defaultfg
;
1343 term
.c
.attr
.bg
= defaultbg
;
1346 term
.c
.attr
.mode
|= ATTR_BOLD
;
1349 term
.c
.attr
.mode
|= ATTR_FAINT
;
1352 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1355 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1357 case 5: /* slow blink */
1359 case 6: /* rapid blink */
1360 term
.c
.attr
.mode
|= ATTR_BLINK
;
1363 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1366 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1369 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1372 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1375 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1378 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1381 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1384 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1387 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1390 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1393 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1394 term
.c
.attr
.fg
= idx
;
1397 term
.c
.attr
.fg
= defaultfg
;
1400 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1401 term
.c
.attr
.bg
= idx
;
1404 term
.c
.attr
.bg
= defaultbg
;
1407 if (BETWEEN(attr
[i
], 30, 37)) {
1408 term
.c
.attr
.fg
= attr
[i
] - 30;
1409 } else if (BETWEEN(attr
[i
], 40, 47)) {
1410 term
.c
.attr
.bg
= attr
[i
] - 40;
1411 } else if (BETWEEN(attr
[i
], 90, 97)) {
1412 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1413 } else if (BETWEEN(attr
[i
], 100, 107)) {
1414 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1417 "erresc(default): gfx attr %d unknown\n",
1418 attr
[i
]), csidump();
1426 tsetscroll(int t
, int b
)
1430 LIMIT(t
, 0, term
.row
-1);
1431 LIMIT(b
, 0, term
.row
-1);
1442 tsetmode(int priv
, int set
, int *args
, int narg
)
1447 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1450 case 1: /* DECCKM -- Cursor key */
1451 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1453 case 5: /* DECSCNM -- Reverse video */
1455 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1456 if (mode
!= term
.mode
)
1459 case 6: /* DECOM -- Origin */
1460 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1463 case 7: /* DECAWM -- Auto wrap */
1464 MODBIT(term
.mode
, set
, MODE_WRAP
);
1466 case 0: /* Error (IGNORED) */
1467 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1468 case 3: /* DECCOLM -- Column (IGNORED) */
1469 case 4: /* DECSCLM -- Scroll (IGNORED) */
1470 case 8: /* DECARM -- Auto repeat (IGNORED) */
1471 case 18: /* DECPFF -- Printer feed (IGNORED) */
1472 case 19: /* DECPEX -- Printer extent (IGNORED) */
1473 case 42: /* DECNRCM -- National characters (IGNORED) */
1474 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1476 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1477 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1479 case 9: /* X10 mouse compatibility mode */
1480 xsetpointermotion(0);
1481 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1482 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1484 case 1000: /* 1000: report button press */
1485 xsetpointermotion(0);
1486 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1487 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1489 case 1002: /* 1002: report motion on button press */
1490 xsetpointermotion(0);
1491 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1492 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1494 case 1003: /* 1003: enable all mouse motions */
1495 xsetpointermotion(set
);
1496 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1497 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1499 case 1004: /* 1004: send focus events to tty */
1500 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1502 case 1006: /* 1006: extended reporting mode */
1503 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1506 MODBIT(term
.mode
, set
, MODE_8BIT
);
1508 case 1049: /* swap screen & set/restore cursor as xterm */
1509 if (!allowaltscreen
)
1511 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1513 case 47: /* swap screen */
1515 if (!allowaltscreen
)
1517 alt
= IS_SET(MODE_ALTSCREEN
);
1519 tclearregion(0, 0, term
.col
-1,
1522 if (set
^ alt
) /* set is always 1 or 0 */
1528 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1530 case 2004: /* 2004: bracketed paste mode */
1531 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1533 /* Not implemented mouse modes. See comments there. */
1534 case 1001: /* mouse highlight mode; can hang the
1535 terminal by design when implemented. */
1536 case 1005: /* UTF-8 mouse mode; will confuse
1537 applications not supporting UTF-8
1539 case 1015: /* urxvt mangled mouse mode; incompatible
1540 and can be mistaken for other control
1544 "erresc: unknown private set/reset mode %d\n",
1550 case 0: /* Error (IGNORED) */
1552 case 2: /* KAM -- keyboard action */
1553 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1555 case 4: /* IRM -- Insertion-replacement */
1556 MODBIT(term
.mode
, set
, MODE_INSERT
);
1558 case 12: /* SRM -- Send/Receive */
1559 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1561 case 20: /* LNM -- Linefeed/new line */
1562 MODBIT(term
.mode
, set
, MODE_CRLF
);
1566 "erresc: unknown set/reset mode %d\n",
1580 switch (csiescseq
.mode
[0]) {
1583 fprintf(stderr
, "erresc: unknown csi ");
1587 case '@': /* ICH -- Insert <n> blank char */
1588 DEFAULT(csiescseq
.arg
[0], 1);
1589 tinsertblank(csiescseq
.arg
[0]);
1591 case 'A': /* CUU -- Cursor <n> Up */
1592 DEFAULT(csiescseq
.arg
[0], 1);
1593 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1595 case 'B': /* CUD -- Cursor <n> Down */
1596 case 'e': /* VPR --Cursor <n> Down */
1597 DEFAULT(csiescseq
.arg
[0], 1);
1598 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1600 case 'i': /* MC -- Media Copy */
1601 switch (csiescseq
.arg
[0]) {
1606 tdumpline(term
.c
.y
);
1612 term
.mode
&= ~MODE_PRINT
;
1615 term
.mode
|= MODE_PRINT
;
1619 case 'c': /* DA -- Device Attributes */
1620 if (csiescseq
.arg
[0] == 0)
1621 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1623 case 'C': /* CUF -- Cursor <n> Forward */
1624 case 'a': /* HPR -- Cursor <n> Forward */
1625 DEFAULT(csiescseq
.arg
[0], 1);
1626 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1628 case 'D': /* CUB -- Cursor <n> Backward */
1629 DEFAULT(csiescseq
.arg
[0], 1);
1630 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1632 case 'E': /* CNL -- Cursor <n> Down and first col */
1633 DEFAULT(csiescseq
.arg
[0], 1);
1634 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1636 case 'F': /* CPL -- Cursor <n> Up and first col */
1637 DEFAULT(csiescseq
.arg
[0], 1);
1638 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1640 case 'g': /* TBC -- Tabulation clear */
1641 switch (csiescseq
.arg
[0]) {
1642 case 0: /* clear current tab stop */
1643 term
.tabs
[term
.c
.x
] = 0;
1645 case 3: /* clear all the tabs */
1646 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1652 case 'G': /* CHA -- Move to <col> */
1654 DEFAULT(csiescseq
.arg
[0], 1);
1655 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1657 case 'H': /* CUP -- Move to <row> <col> */
1659 DEFAULT(csiescseq
.arg
[0], 1);
1660 DEFAULT(csiescseq
.arg
[1], 1);
1661 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1663 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1664 DEFAULT(csiescseq
.arg
[0], 1);
1665 tputtab(csiescseq
.arg
[0]);
1667 case 'J': /* ED -- Clear screen */
1669 switch (csiescseq
.arg
[0]) {
1671 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1672 if (term
.c
.y
< term
.row
-1) {
1673 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1679 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1680 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1683 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1689 case 'K': /* EL -- Clear line */
1690 switch (csiescseq
.arg
[0]) {
1692 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1696 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1699 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1703 case 'S': /* SU -- Scroll <n> line up */
1704 DEFAULT(csiescseq
.arg
[0], 1);
1705 tscrollup(term
.top
, csiescseq
.arg
[0]);
1707 case 'T': /* SD -- Scroll <n> line down */
1708 DEFAULT(csiescseq
.arg
[0], 1);
1709 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1711 case 'L': /* IL -- Insert <n> blank lines */
1712 DEFAULT(csiescseq
.arg
[0], 1);
1713 tinsertblankline(csiescseq
.arg
[0]);
1715 case 'l': /* RM -- Reset Mode */
1716 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1718 case 'M': /* DL -- Delete <n> lines */
1719 DEFAULT(csiescseq
.arg
[0], 1);
1720 tdeleteline(csiescseq
.arg
[0]);
1722 case 'X': /* ECH -- Erase <n> char */
1723 DEFAULT(csiescseq
.arg
[0], 1);
1724 tclearregion(term
.c
.x
, term
.c
.y
,
1725 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1727 case 'P': /* DCH -- Delete <n> char */
1728 DEFAULT(csiescseq
.arg
[0], 1);
1729 tdeletechar(csiescseq
.arg
[0]);
1731 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1732 DEFAULT(csiescseq
.arg
[0], 1);
1733 tputtab(-csiescseq
.arg
[0]);
1735 case 'd': /* VPA -- Move to <row> */
1736 DEFAULT(csiescseq
.arg
[0], 1);
1737 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1739 case 'h': /* SM -- Set terminal mode */
1740 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1742 case 'm': /* SGR -- Terminal attribute (color) */
1743 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1745 case 'n': /* DSR – Device Status Report (cursor position) */
1746 if (csiescseq
.arg
[0] == 6) {
1747 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1748 term
.c
.y
+1, term
.c
.x
+1);
1752 case 'r': /* DECSTBM -- Set Scrolling Region */
1753 if (csiescseq
.priv
) {
1756 DEFAULT(csiescseq
.arg
[0], 1);
1757 DEFAULT(csiescseq
.arg
[1], term
.row
);
1758 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1762 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1763 tcursor(CURSOR_SAVE
);
1765 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1766 tcursor(CURSOR_LOAD
);
1769 switch (csiescseq
.mode
[1]) {
1770 case 'q': /* DECSCUSR -- Set Cursor Style */
1771 DEFAULT(csiescseq
.arg
[0], 1);
1772 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1775 win
.cursor
= csiescseq
.arg
[0];
1790 fprintf(stderr
, "ESC[");
1791 for (i
= 0; i
< csiescseq
.len
; i
++) {
1792 c
= csiescseq
.buf
[i
] & 0xff;
1795 } else if (c
== '\n') {
1796 fprintf(stderr
, "(\\n)");
1797 } else if (c
== '\r') {
1798 fprintf(stderr
, "(\\r)");
1799 } else if (c
== 0x1b) {
1800 fprintf(stderr
, "(\\e)");
1802 fprintf(stderr
, "(%02x)", c
);
1811 memset(&csiescseq
, 0, sizeof(csiescseq
));
1820 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1822 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1824 switch (strescseq
.type
) {
1825 case ']': /* OSC -- Operating System Command */
1831 xsettitle(strescseq
.args
[1]);
1837 dec
= base64dec(strescseq
.args
[2]);
1839 xsetsel(dec
, CurrentTime
);
1842 fprintf(stderr
, "erresc: invalid base64\n");
1846 case 4: /* color set */
1849 p
= strescseq
.args
[2];
1851 case 104: /* color reset, here p = NULL */
1852 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1853 if (xsetcolorname(j
, p
)) {
1854 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1857 * TODO if defaultbg color is changed, borders
1865 case 'k': /* old title set compatibility */
1866 xsettitle(strescseq
.args
[0]);
1868 case 'P': /* DCS -- Device Control String */
1869 term
.mode
|= ESC_DCS
;
1870 case '_': /* APC -- Application Program Command */
1871 case '^': /* PM -- Privacy Message */
1875 fprintf(stderr
, "erresc: unknown str ");
1883 char *p
= strescseq
.buf
;
1886 strescseq
.buf
[strescseq
.len
] = '\0';
1891 while (strescseq
.narg
< STR_ARG_SIZ
) {
1892 strescseq
.args
[strescseq
.narg
++] = p
;
1893 while ((c
= *p
) != ';' && c
!= '\0')
1907 fprintf(stderr
, "ESC%c", strescseq
.type
);
1908 for (i
= 0; i
< strescseq
.len
; i
++) {
1909 c
= strescseq
.buf
[i
] & 0xff;
1913 } else if (isprint(c
)) {
1915 } else if (c
== '\n') {
1916 fprintf(stderr
, "(\\n)");
1917 } else if (c
== '\r') {
1918 fprintf(stderr
, "(\\r)");
1919 } else if (c
== 0x1b) {
1920 fprintf(stderr
, "(\\e)");
1922 fprintf(stderr
, "(%02x)", c
);
1925 fprintf(stderr
, "ESC\\\n");
1931 memset(&strescseq
, 0, sizeof(strescseq
));
1935 sendbreak(const Arg
*arg
)
1937 if (tcsendbreak(cmdfd
, 0))
1938 perror("Error sending break");
1942 tprinter(char *s
, size_t len
)
1944 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1945 fprintf(stderr
, "Error writing in %s:%s\n",
1946 opt_io
, strerror(errno
));
1953 iso14755(const Arg
*arg
)
1956 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1957 unsigned long utf32
;
1959 if (!(p
= popen(ISO14755CMD
, "r")))
1962 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1965 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1967 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1968 (*e
!= '\n' && *e
!= '\0'))
1971 ttysend(uc
, utf8encode(utf32
, uc
));
1975 toggleprinter(const Arg
*arg
)
1977 term
.mode
^= MODE_PRINT
;
1981 printscreen(const Arg
*arg
)
1987 printsel(const Arg
*arg
)
1997 if ((ptr
= getsel())) {
1998 tprinter(ptr
, strlen(ptr
));
2009 bp
= &term
.line
[n
][0];
2010 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2011 if (bp
!= end
|| bp
->u
!= ' ') {
2012 for ( ;bp
<= end
; ++bp
)
2013 tprinter(buf
, utf8encode(bp
->u
, buf
));
2023 for (i
= 0; i
< term
.row
; ++i
)
2033 while (x
< term
.col
&& n
--)
2034 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2037 while (x
> 0 && n
++)
2038 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2041 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2047 if (ISCONTROL(u
)) { /* control code */
2052 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2061 tdefutf8(char ascii
)
2064 term
.mode
|= MODE_UTF8
;
2065 else if (ascii
== '@')
2066 term
.mode
&= ~MODE_UTF8
;
2070 tdeftran(char ascii
)
2072 static char cs
[] = "0B";
2073 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2076 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2077 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2079 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2088 if (c
== '8') { /* DEC screen alignment test. */
2089 for (x
= 0; x
< term
.col
; ++x
) {
2090 for (y
= 0; y
< term
.row
; ++y
)
2091 tsetchar('E', &term
.c
.attr
, x
, y
);
2097 tstrsequence(uchar c
)
2102 case 0x90: /* DCS -- Device Control String */
2104 term
.esc
|= ESC_DCS
;
2106 case 0x9f: /* APC -- Application Program Command */
2109 case 0x9e: /* PM -- Privacy Message */
2112 case 0x9d: /* OSC -- Operating System Command */
2117 term
.esc
|= ESC_STR
;
2121 tcontrolcode(uchar ascii
)
2128 tmoveto(term
.c
.x
-1, term
.c
.y
);
2131 tmoveto(0, term
.c
.y
);
2136 /* go to first col if the mode is set */
2137 tnewline(IS_SET(MODE_CRLF
));
2139 case '\a': /* BEL */
2140 if (term
.esc
& ESC_STR_END
) {
2141 /* backwards compatibility to xterm */
2147 case '\033': /* ESC */
2149 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2150 term
.esc
|= ESC_START
;
2152 case '\016': /* SO (LS1 -- Locking shift 1) */
2153 case '\017': /* SI (LS0 -- Locking shift 0) */
2154 term
.charset
= 1 - (ascii
- '\016');
2156 case '\032': /* SUB */
2157 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2158 case '\030': /* CAN */
2161 case '\005': /* ENQ (IGNORED) */
2162 case '\000': /* NUL (IGNORED) */
2163 case '\021': /* XON (IGNORED) */
2164 case '\023': /* XOFF (IGNORED) */
2165 case 0177: /* DEL (IGNORED) */
2167 case 0x80: /* TODO: PAD */
2168 case 0x81: /* TODO: HOP */
2169 case 0x82: /* TODO: BPH */
2170 case 0x83: /* TODO: NBH */
2171 case 0x84: /* TODO: IND */
2173 case 0x85: /* NEL -- Next line */
2174 tnewline(1); /* always go to first col */
2176 case 0x86: /* TODO: SSA */
2177 case 0x87: /* TODO: ESA */
2179 case 0x88: /* HTS -- Horizontal tab stop */
2180 term
.tabs
[term
.c
.x
] = 1;
2182 case 0x89: /* TODO: HTJ */
2183 case 0x8a: /* TODO: VTS */
2184 case 0x8b: /* TODO: PLD */
2185 case 0x8c: /* TODO: PLU */
2186 case 0x8d: /* TODO: RI */
2187 case 0x8e: /* TODO: SS2 */
2188 case 0x8f: /* TODO: SS3 */
2189 case 0x91: /* TODO: PU1 */
2190 case 0x92: /* TODO: PU2 */
2191 case 0x93: /* TODO: STS */
2192 case 0x94: /* TODO: CCH */
2193 case 0x95: /* TODO: MW */
2194 case 0x96: /* TODO: SPA */
2195 case 0x97: /* TODO: EPA */
2196 case 0x98: /* TODO: SOS */
2197 case 0x99: /* TODO: SGCI */
2199 case 0x9a: /* DECID -- Identify Terminal */
2200 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2202 case 0x9b: /* TODO: CSI */
2203 case 0x9c: /* TODO: ST */
2205 case 0x90: /* DCS -- Device Control String */
2206 case 0x9d: /* OSC -- Operating System Command */
2207 case 0x9e: /* PM -- Privacy Message */
2208 case 0x9f: /* APC -- Application Program Command */
2209 tstrsequence(ascii
);
2212 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2213 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2217 * returns 1 when the sequence is finished and it hasn't to read
2218 * more characters for this sequence, otherwise 0
2221 eschandle(uchar ascii
)
2225 term
.esc
|= ESC_CSI
;
2228 term
.esc
|= ESC_TEST
;
2231 term
.esc
|= ESC_UTF8
;
2233 case 'P': /* DCS -- Device Control String */
2234 case '_': /* APC -- Application Program Command */
2235 case '^': /* PM -- Privacy Message */
2236 case ']': /* OSC -- Operating System Command */
2237 case 'k': /* old title set compatibility */
2238 tstrsequence(ascii
);
2240 case 'n': /* LS2 -- Locking shift 2 */
2241 case 'o': /* LS3 -- Locking shift 3 */
2242 term
.charset
= 2 + (ascii
- 'n');
2244 case '(': /* GZD4 -- set primary charset G0 */
2245 case ')': /* G1D4 -- set secondary charset G1 */
2246 case '*': /* G2D4 -- set tertiary charset G2 */
2247 case '+': /* G3D4 -- set quaternary charset G3 */
2248 term
.icharset
= ascii
- '(';
2249 term
.esc
|= ESC_ALTCHARSET
;
2251 case 'D': /* IND -- Linefeed */
2252 if (term
.c
.y
== term
.bot
) {
2253 tscrollup(term
.top
, 1);
2255 tmoveto(term
.c
.x
, term
.c
.y
+1);
2258 case 'E': /* NEL -- Next line */
2259 tnewline(1); /* always go to first col */
2261 case 'H': /* HTS -- Horizontal tab stop */
2262 term
.tabs
[term
.c
.x
] = 1;
2264 case 'M': /* RI -- Reverse index */
2265 if (term
.c
.y
== term
.top
) {
2266 tscrolldown(term
.top
, 1);
2268 tmoveto(term
.c
.x
, term
.c
.y
-1);
2271 case 'Z': /* DECID -- Identify Terminal */
2272 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2274 case 'c': /* RIS -- Reset to inital state */
2279 case '=': /* DECPAM -- Application keypad */
2280 term
.mode
|= MODE_APPKEYPAD
;
2282 case '>': /* DECPNM -- Normal keypad */
2283 term
.mode
&= ~MODE_APPKEYPAD
;
2285 case '7': /* DECSC -- Save Cursor */
2286 tcursor(CURSOR_SAVE
);
2288 case '8': /* DECRC -- Restore Cursor */
2289 tcursor(CURSOR_LOAD
);
2291 case '\\': /* ST -- String Terminator */
2292 if (term
.esc
& ESC_STR_END
)
2296 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2297 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2311 control
= ISCONTROL(u
);
2312 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2316 len
= utf8encode(u
, c
);
2317 if (!control
&& (width
= wcwidth(u
)) == -1) {
2318 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2323 if (IS_SET(MODE_PRINT
))
2327 * STR sequence must be checked before anything else
2328 * because it uses all following characters until it
2329 * receives a ESC, a SUB, a ST or any other C1 control
2332 if (term
.esc
& ESC_STR
) {
2333 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2335 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2336 if (IS_SET(MODE_SIXEL
)) {
2337 /* TODO: render sixel */;
2338 term
.mode
&= ~MODE_SIXEL
;
2341 term
.esc
|= ESC_STR_END
;
2342 goto check_control_code
;
2346 if (IS_SET(MODE_SIXEL
)) {
2347 /* TODO: implement sixel mode */
2350 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2351 term
.mode
|= MODE_SIXEL
;
2353 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2355 * Here is a bug in terminals. If the user never sends
2356 * some code to stop the str or esc command, then st
2357 * will stop responding. But this is better than
2358 * silently failing with unknown characters. At least
2359 * then users will report back.
2361 * In the case users ever get fixed, here is the code:
2370 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2371 strescseq
.len
+= len
;
2377 * Actions of control codes must be performed as soon they arrive
2378 * because they can be embedded inside a control sequence, and
2379 * they must not cause conflicts with sequences.
2384 * control codes are not shown ever
2387 } else if (term
.esc
& ESC_START
) {
2388 if (term
.esc
& ESC_CSI
) {
2389 csiescseq
.buf
[csiescseq
.len
++] = u
;
2390 if (BETWEEN(u
, 0x40, 0x7E)
2391 || csiescseq
.len
>= \
2392 sizeof(csiescseq
.buf
)-1) {
2398 } else if (term
.esc
& ESC_UTF8
) {
2400 } else if (term
.esc
& ESC_ALTCHARSET
) {
2402 } else if (term
.esc
& ESC_TEST
) {
2407 /* sequence already finished */
2411 * All characters which form part of a sequence are not
2416 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2419 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2420 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2421 gp
->mode
|= ATTR_WRAP
;
2423 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2426 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2427 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2429 if (term
.c
.x
+width
> term
.col
) {
2431 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2434 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2437 gp
->mode
|= ATTR_WIDE
;
2438 if (term
.c
.x
+1 < term
.col
) {
2440 gp
[1].mode
= ATTR_WDUMMY
;
2443 if (term
.c
.x
+width
< term
.col
) {
2444 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2446 term
.c
.state
|= CURSOR_WRAPNEXT
;
2451 tresize(int col
, int row
)
2454 int minrow
= MIN(row
, term
.row
);
2455 int mincol
= MIN(col
, term
.col
);
2459 if (col
< 1 || row
< 1) {
2461 "tresize: error resizing to %dx%d\n", col
, row
);
2466 * slide screen to keep cursor where we expect it -
2467 * tscrollup would work here, but we can optimize to
2468 * memmove because we're freeing the earlier lines
2470 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2474 /* ensure that both src and dst are not NULL */
2476 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2477 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2479 for (i
+= row
; i
< term
.row
; i
++) {
2484 /* resize to new height */
2485 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2486 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2487 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2488 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2490 /* resize each row to new width, zero-pad if needed */
2491 for (i
= 0; i
< minrow
; i
++) {
2492 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2493 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2496 /* allocate any new rows */
2497 for (/* i = minrow */; i
< row
; i
++) {
2498 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2499 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2501 if (col
> term
.col
) {
2502 bp
= term
.tabs
+ term
.col
;
2504 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2505 while (--bp
> term
.tabs
&& !*bp
)
2507 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2510 /* update terminal size */
2513 /* reset scrolling region */
2514 tsetscroll(0, row
-1);
2515 /* make use of the LIMIT in tmoveto */
2516 tmoveto(term
.c
.x
, term
.c
.y
);
2517 /* Clearing both screens (it makes dirty all lines) */
2519 for (i
= 0; i
< 2; i
++) {
2520 if (mincol
< col
&& 0 < minrow
) {
2521 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2523 if (0 < col
&& minrow
< row
) {
2524 tclearregion(0, minrow
, col
- 1, row
- 1);
2527 tcursor(CURSOR_LOAD
);
2535 xsettitle(opt_title
? opt_title
: "st");
2546 numlock(const Arg
*dummy
)