Xinqi Bao's Git
58f79411839d948b46ccc6e55dfc60e7f99a72f0
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 ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
52 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
53 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
54 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
57 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
59 enum cursor_movement
{
83 ESC_STR
= 4, /* OSC, PM, APC */
85 ESC_STR_END
= 16, /* a final string was encountered */
86 ESC_TEST
= 32, /* Enter in test mode */
91 /* CSI Escape sequence structs */
92 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
94 char buf
[ESC_BUF_SIZ
]; /* raw string */
95 int len
; /* raw string length */
98 int narg
; /* nb of args */
102 /* STR Escape sequence structs */
103 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
105 char type
; /* ESC type ... */
106 char buf
[STR_BUF_SIZ
]; /* raw string */
107 int len
; /* raw string length */
108 char *args
[STR_ARG_SIZ
];
109 int narg
; /* nb of args */
112 /* function definitions used in config.h */
113 static void clipcopy(const Arg
*);
114 static void clippaste(const Arg
*);
115 static void numlock(const Arg
*);
116 static void selpaste(const Arg
*);
117 static void printsel(const Arg
*);
118 static void printscreen(const Arg
*) ;
119 static void iso14755(const Arg
*);
120 static void toggleprinter(const Arg
*);
121 static void sendbreak(const Arg
*);
123 /* config.h for applying patches and the configuration. */
126 static void execsh(char **);
127 static void stty(char **);
128 static void sigchld(int);
130 static void csidump(void);
131 static void csihandle(void);
132 static void csiparse(void);
133 static void csireset(void);
134 static int eschandle(uchar
);
135 static void strdump(void);
136 static void strhandle(void);
137 static void strparse(void);
138 static void strreset(void);
140 static void tprinter(char *, size_t);
141 static void tdumpsel(void);
142 static void tdumpline(int);
143 static void tdump(void);
144 static void tclearregion(int, int, int, int);
145 static void tcursor(int);
146 static void tdeletechar(int);
147 static void tdeleteline(int);
148 static void tinsertblank(int);
149 static void tinsertblankline(int);
150 static int tlinelen(int);
151 static void tmoveto(int, int);
152 static void tmoveato(int, int);
153 static void tnewline(int);
154 static void tputtab(int);
155 static void tputc(Rune
);
156 static void treset(void);
157 static void tscrollup(int, int);
158 static void tscrolldown(int, int);
159 static void tsetattr(int *, int);
160 static void tsetchar(Rune
, Glyph
*, int, int);
161 static void tsetscroll(int, int);
162 static void tswapscreen(void);
163 static void tsetmode(int, int, int *, int);
164 static void tfulldirt(void);
165 static void techo(Rune
);
166 static void tcontrolcode(uchar
);
167 static void tdectest(char );
168 static void tdefutf8(char);
169 static int32_t tdefcolor(int *, int *, int);
170 static void tdeftran(char);
171 static void tstrsequence(uchar
);
173 static void selscroll(int, int);
174 static void selsnap(int *, int *, int);
176 static Rune
utf8decodebyte(char, size_t *);
177 static char utf8encodebyte(Rune
, size_t);
178 static char *utf8strchr(char *s
, Rune u
);
179 static size_t utf8validate(Rune
*, size_t);
181 static char *base64dec(const char *);
183 static ssize_t
xwrite(int, const char *, size_t);
191 int oldbutton
= 3; /* button event on startup: 3 = release */
193 static CSIEscape csiescseq
;
194 static STREscape strescseq
;
197 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
198 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
199 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
200 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
202 /* config.h array lengths */
203 size_t colornamelen
= LEN(colorname
);
204 size_t mshortcutslen
= LEN(mshortcuts
);
205 size_t shortcutslen
= LEN(shortcuts
);
206 size_t selmaskslen
= LEN(selmasks
);
207 size_t keyslen
= LEN(key
);
208 size_t mappedkeyslen
= LEN(mappedkeys
);
211 xwrite(int fd
, const char *s
, size_t len
)
217 r
= write(fd
, s
, len
);
230 void *p
= malloc(len
);
233 die("Out of memory\n");
239 xrealloc(void *p
, size_t len
)
241 if ((p
= realloc(p
, len
)) == NULL
)
242 die("Out of memory\n");
250 if ((s
= strdup(s
)) == NULL
)
251 die("Out of memory\n");
257 utf8decode(char *c
, Rune
*u
, size_t clen
)
259 size_t i
, j
, len
, type
;
265 udecoded
= utf8decodebyte(c
[0], &len
);
266 if (!BETWEEN(len
, 1, UTF_SIZ
))
268 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
269 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
276 utf8validate(u
, len
);
282 utf8decodebyte(char c
, size_t *i
)
284 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
285 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
286 return (uchar
)c
& ~utfmask
[*i
];
292 utf8encode(Rune u
, char *c
)
296 len
= utf8validate(&u
, 0);
300 for (i
= len
- 1; i
!= 0; --i
) {
301 c
[i
] = utf8encodebyte(u
, 0);
304 c
[0] = utf8encodebyte(u
, len
);
310 utf8encodebyte(Rune u
, size_t i
)
312 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
316 utf8strchr(char *s
, Rune u
)
322 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
323 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
333 utf8validate(Rune
*u
, size_t i
)
335 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
337 for (i
= 1; *u
> utfmax
[i
]; ++i
)
343 static const char base64_digits
[] = {
344 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
345 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
346 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
347 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
348 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
349 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
350 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
351 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
352 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
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, 0, 0, 0, 0, 0,
355 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
359 base64dec_getc(const char **src
)
361 while (**src
&& !isprint(**src
)) (*src
)++;
366 base64dec(const char *src
)
368 size_t in_len
= strlen(src
);
372 in_len
+= 4 - (in_len
% 4);
373 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
375 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
376 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
377 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
378 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
380 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
383 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
386 *dst
++ = ((c
& 0x03) << 6) | d
;
395 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick1
);
396 clock_gettime(CLOCK_MONOTONIC
, &sel
.tclick2
);
401 sel
.clipboard
= NULL
;
409 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
412 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
423 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
424 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
425 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
427 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
428 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
430 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
431 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
433 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
434 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
436 /* expand selection over line breaks */
437 if (sel
.type
== SEL_RECTANGULAR
)
439 i
= tlinelen(sel
.nb
.y
);
442 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
443 sel
.ne
.x
= term
.col
- 1;
447 selected(int x
, int y
)
449 if (sel
.mode
== SEL_EMPTY
)
452 if (sel
.type
== SEL_RECTANGULAR
)
453 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
454 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
456 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
457 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
458 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
462 selsnap(int *x
, int *y
, int direction
)
464 int newx
, newy
, xt
, yt
;
465 int delim
, prevdelim
;
471 * Snap around if the word wraps around at the end or
472 * beginning of a line.
474 prevgp
= &term
.line
[*y
][*x
];
475 prevdelim
= ISDELIM(prevgp
->u
);
477 newx
= *x
+ direction
;
479 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
481 newx
= (newx
+ term
.col
) % term
.col
;
482 if (!BETWEEN(newy
, 0, term
.row
- 1))
488 yt
= newy
, xt
= newx
;
489 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
493 if (newx
>= tlinelen(newy
))
496 gp
= &term
.line
[newy
][newx
];
497 delim
= ISDELIM(gp
->u
);
498 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
499 || (delim
&& gp
->u
!= prevgp
->u
)))
510 * Snap around if the the previous line or the current one
511 * has set ATTR_WRAP at its end. Then the whole next or
512 * previous line will be selected.
514 *x
= (direction
< 0) ? 0 : term
.col
- 1;
516 for (; *y
> 0; *y
+= direction
) {
517 if (!(term
.line
[*y
-1][term
.col
-1].mode
522 } else if (direction
> 0) {
523 for (; *y
< term
.row
-1; *y
+= direction
) {
524 if (!(term
.line
[*y
][term
.col
-1].mode
538 int y
, bufsize
, lastx
, linelen
;
544 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
545 ptr
= str
= xmalloc(bufsize
);
547 /* append every set & selected glyph to the selection */
548 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
549 if ((linelen
= tlinelen(y
)) == 0) {
554 if (sel
.type
== SEL_RECTANGULAR
) {
555 gp
= &term
.line
[y
][sel
.nb
.x
];
558 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
559 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
561 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
562 while (last
>= gp
&& last
->u
== ' ')
565 for ( ; gp
<= last
; ++gp
) {
566 if (gp
->mode
& ATTR_WDUMMY
)
569 ptr
+= utf8encode(gp
->u
, ptr
);
573 * Copy and pasting of line endings is inconsistent
574 * in the inconsistent terminal and GUI world.
575 * The best solution seems like to produce '\n' when
576 * something is copied from st and convert '\n' to
577 * '\r', when something to be pasted is received by
579 * FIXME: Fix the computer world.
581 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
589 selpaste(const Arg
*dummy
)
595 clipcopy(const Arg
*dummy
)
601 clippaste(const Arg
*dummy
)
613 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
617 die(const char *errstr
, ...)
621 va_start(ap
, errstr
);
622 vfprintf(stderr
, errstr
, ap
);
631 const struct passwd
*pw
;
634 if ((pw
= getpwuid(getuid())) == NULL
) {
636 die("getpwuid:%s\n", strerror(errno
));
638 die("who are you?\n");
641 if ((sh
= getenv("SHELL")) == NULL
)
642 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
650 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
655 setenv("LOGNAME", pw
->pw_name
, 1);
656 setenv("USER", pw
->pw_name
, 1);
657 setenv("SHELL", sh
, 1);
658 setenv("HOME", pw
->pw_dir
, 1);
659 setenv("TERM", termname
, 1);
661 signal(SIGCHLD
, SIG_DFL
);
662 signal(SIGHUP
, SIG_DFL
);
663 signal(SIGINT
, SIG_DFL
);
664 signal(SIGQUIT
, SIG_DFL
);
665 signal(SIGTERM
, SIG_DFL
);
666 signal(SIGALRM
, SIG_DFL
);
678 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
679 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
684 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
685 die("child finished with error '%d'\n", stat
);
693 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
696 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
697 die("incorrect stty parameters\n");
698 memcpy(cmd
, stty_args
, n
);
700 siz
= sizeof(cmd
) - n
;
701 for (p
= args
; p
&& (s
= *p
); ++p
) {
702 if ((n
= strlen(s
)) > siz
-1)
703 die("stty parameter length too long\n");
710 if (system(cmd
) != 0)
711 perror("Couldn't call stty");
715 ttynew(char *line
, char *out
, char **args
)
718 struct winsize w
= {term
.row
, term
.col
, 0, 0};
721 term
.mode
|= MODE_PRINT
;
722 iofd
= (!strcmp(out
, "-")) ?
723 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
725 fprintf(stderr
, "Error opening %s:%s\n",
726 out
, strerror(errno
));
731 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
732 die("open line failed: %s\n", strerror(errno
));
738 /* seems to work fine on linux, openbsd and freebsd */
739 if (openpty(&m
, &s
, NULL
, NULL
, &w
) < 0)
740 die("openpty failed: %s\n", strerror(errno
));
742 switch (pid
= fork()) {
744 die("fork failed\n");
748 setsid(); /* create a new process group */
752 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
753 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
761 signal(SIGCHLD
, sigchld
);
769 static char buf
[BUFSIZ
];
770 static int buflen
= 0;
772 int charsize
; /* size of utf8 char in bytes */
776 /* append read bytes to unprocessed bytes */
777 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
778 die("Couldn't read from shell: %s\n", strerror(errno
));
784 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
785 /* process a complete utf8 char */
786 charsize
= utf8decode(ptr
, &unicodep
, buflen
);
796 tputc(*ptr
++ & 0xFF);
800 /* keep any uncomplete utf8 char for the next call */
802 memmove(buf
, ptr
, buflen
);
808 ttywrite(const char *s
, size_t n
)
815 * Remember that we are using a pty, which might be a modem line.
816 * Writing too much will clog the line. That's why we are doing this
818 * FIXME: Migrate the world to Plan 9.
826 /* Check if we can write. */
827 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
830 die("select failed: %s\n", strerror(errno
));
832 if (FD_ISSET(cmdfd
, &wfd
)) {
834 * Only write the bytes written by ttywrite() or the
835 * default of 256. This seems to be a reasonable value
836 * for a serial line. Bigger values might clog the I/O.
838 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
842 * We weren't able to write out everything.
843 * This means the buffer is getting full
851 /* All bytes have been written. */
855 if (FD_ISSET(cmdfd
, &rfd
))
861 die("write error on tty: %s\n", strerror(errno
));
865 ttysend(char *s
, size_t n
)
872 if (!IS_SET(MODE_ECHO
))
876 for (t
= s
; t
< lim
; t
+= len
) {
877 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
878 len
= utf8decode(t
, &u
, n
);
891 ttyresize(int tw
, int th
)
899 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
900 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
908 for (i
= 0; i
< term
.row
-1; i
++) {
909 for (j
= 0; j
< term
.col
-1; j
++) {
910 if (term
.line
[i
][j
].mode
& attr
)
919 tsetdirt(int top
, int bot
)
923 LIMIT(top
, 0, term
.row
-1);
924 LIMIT(bot
, 0, term
.row
-1);
926 for (i
= top
; i
<= bot
; i
++)
931 tsetdirtattr(int attr
)
935 for (i
= 0; i
< term
.row
-1; i
++) {
936 for (j
= 0; j
< term
.col
-1; j
++) {
937 if (term
.line
[i
][j
].mode
& attr
) {
948 tsetdirt(0, term
.row
-1);
955 int alt
= IS_SET(MODE_ALTSCREEN
);
957 if (mode
== CURSOR_SAVE
) {
959 } else if (mode
== CURSOR_LOAD
) {
961 tmoveto(c
[alt
].x
, c
[alt
].y
);
974 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
976 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
977 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
980 term
.bot
= term
.row
- 1;
981 term
.mode
= MODE_WRAP
|MODE_UTF8
;
982 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
985 for (i
= 0; i
< 2; i
++) {
987 tcursor(CURSOR_SAVE
);
988 tclearregion(0, 0, term
.col
-1, term
.row
-1);
994 tnew(int col
, int row
)
996 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1006 Line
*tmp
= term
.line
;
1008 term
.line
= term
.alt
;
1010 term
.mode
^= MODE_ALTSCREEN
;
1015 tscrolldown(int orig
, int n
)
1020 LIMIT(n
, 0, term
.bot
-orig
+1);
1022 tsetdirt(orig
, term
.bot
-n
);
1023 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1025 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1026 temp
= term
.line
[i
];
1027 term
.line
[i
] = term
.line
[i
-n
];
1028 term
.line
[i
-n
] = temp
;
1035 tscrollup(int orig
, int n
)
1040 LIMIT(n
, 0, term
.bot
-orig
+1);
1042 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1043 tsetdirt(orig
+n
, term
.bot
);
1045 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1046 temp
= term
.line
[i
];
1047 term
.line
[i
] = term
.line
[i
+n
];
1048 term
.line
[i
+n
] = temp
;
1051 selscroll(orig
, -n
);
1055 selscroll(int orig
, int n
)
1060 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1061 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1065 if (sel
.type
== SEL_RECTANGULAR
) {
1066 if (sel
.ob
.y
< term
.top
)
1067 sel
.ob
.y
= term
.top
;
1068 if (sel
.oe
.y
> term
.bot
)
1069 sel
.oe
.y
= term
.bot
;
1071 if (sel
.ob
.y
< term
.top
) {
1072 sel
.ob
.y
= term
.top
;
1075 if (sel
.oe
.y
> term
.bot
) {
1076 sel
.oe
.y
= term
.bot
;
1077 sel
.oe
.x
= term
.col
;
1085 tnewline(int first_col
)
1089 if (y
== term
.bot
) {
1090 tscrollup(term
.top
, 1);
1094 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1100 char *p
= csiescseq
.buf
, *np
;
1109 csiescseq
.buf
[csiescseq
.len
] = '\0';
1110 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1112 v
= strtol(p
, &np
, 10);
1115 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1117 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1119 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1123 csiescseq
.mode
[0] = *p
++;
1124 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1127 /* for absolute user moves, when decom is set */
1129 tmoveato(int x
, int y
)
1131 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1135 tmoveto(int x
, int y
)
1139 if (term
.c
.state
& CURSOR_ORIGIN
) {
1144 maxy
= term
.row
- 1;
1146 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1147 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1148 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1152 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1154 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1155 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1156 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1157 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1158 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1159 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1160 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1161 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1162 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1166 * The table is proudly stolen from rxvt.
1168 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1169 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1170 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1172 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1173 if (x
+1 < term
.col
) {
1174 term
.line
[y
][x
+1].u
= ' ';
1175 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1177 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1178 term
.line
[y
][x
-1].u
= ' ';
1179 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1183 term
.line
[y
][x
] = *attr
;
1184 term
.line
[y
][x
].u
= u
;
1188 tclearregion(int x1
, int y1
, int x2
, int y2
)
1194 temp
= x1
, x1
= x2
, x2
= temp
;
1196 temp
= y1
, y1
= y2
, y2
= temp
;
1198 LIMIT(x1
, 0, term
.col
-1);
1199 LIMIT(x2
, 0, term
.col
-1);
1200 LIMIT(y1
, 0, term
.row
-1);
1201 LIMIT(y2
, 0, term
.row
-1);
1203 for (y
= y1
; y
<= y2
; y
++) {
1205 for (x
= x1
; x
<= x2
; x
++) {
1206 gp
= &term
.line
[y
][x
];
1209 gp
->fg
= term
.c
.attr
.fg
;
1210 gp
->bg
= term
.c
.attr
.bg
;
1223 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1227 size
= term
.col
- src
;
1228 line
= term
.line
[term
.c
.y
];
1230 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1231 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1240 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1244 size
= term
.col
- dst
;
1245 line
= term
.line
[term
.c
.y
];
1247 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1248 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1252 tinsertblankline(int n
)
1254 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1255 tscrolldown(term
.c
.y
, n
);
1261 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1262 tscrollup(term
.c
.y
, n
);
1266 tdefcolor(int *attr
, int *npar
, int l
)
1271 switch (attr
[*npar
+ 1]) {
1272 case 2: /* direct color in RGB space */
1273 if (*npar
+ 4 >= l
) {
1275 "erresc(38): Incorrect number of parameters (%d)\n",
1279 r
= attr
[*npar
+ 2];
1280 g
= attr
[*npar
+ 3];
1281 b
= attr
[*npar
+ 4];
1283 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1284 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1287 idx
= TRUECOLOR(r
, g
, b
);
1289 case 5: /* indexed color */
1290 if (*npar
+ 2 >= l
) {
1292 "erresc(38): Incorrect number of parameters (%d)\n",
1297 if (!BETWEEN(attr
[*npar
], 0, 255))
1298 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1302 case 0: /* implemented defined (only foreground) */
1303 case 1: /* transparent */
1304 case 3: /* direct color in CMY space */
1305 case 4: /* direct color in CMYK space */
1308 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1316 tsetattr(int *attr
, int l
)
1321 for (i
= 0; i
< l
; i
++) {
1324 term
.c
.attr
.mode
&= ~(
1333 term
.c
.attr
.fg
= defaultfg
;
1334 term
.c
.attr
.bg
= defaultbg
;
1337 term
.c
.attr
.mode
|= ATTR_BOLD
;
1340 term
.c
.attr
.mode
|= ATTR_FAINT
;
1343 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1346 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1348 case 5: /* slow blink */
1350 case 6: /* rapid blink */
1351 term
.c
.attr
.mode
|= ATTR_BLINK
;
1354 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1357 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1360 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1363 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1366 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1369 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1372 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1375 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1378 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1381 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1384 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1385 term
.c
.attr
.fg
= idx
;
1388 term
.c
.attr
.fg
= defaultfg
;
1391 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1392 term
.c
.attr
.bg
= idx
;
1395 term
.c
.attr
.bg
= defaultbg
;
1398 if (BETWEEN(attr
[i
], 30, 37)) {
1399 term
.c
.attr
.fg
= attr
[i
] - 30;
1400 } else if (BETWEEN(attr
[i
], 40, 47)) {
1401 term
.c
.attr
.bg
= attr
[i
] - 40;
1402 } else if (BETWEEN(attr
[i
], 90, 97)) {
1403 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1404 } else if (BETWEEN(attr
[i
], 100, 107)) {
1405 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1408 "erresc(default): gfx attr %d unknown\n",
1409 attr
[i
]), csidump();
1417 tsetscroll(int t
, int b
)
1421 LIMIT(t
, 0, term
.row
-1);
1422 LIMIT(b
, 0, term
.row
-1);
1433 tsetmode(int priv
, int set
, int *args
, int narg
)
1438 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1441 case 1: /* DECCKM -- Cursor key */
1442 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1444 case 5: /* DECSCNM -- Reverse video */
1446 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1447 if (mode
!= term
.mode
)
1450 case 6: /* DECOM -- Origin */
1451 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1454 case 7: /* DECAWM -- Auto wrap */
1455 MODBIT(term
.mode
, set
, MODE_WRAP
);
1457 case 0: /* Error (IGNORED) */
1458 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1459 case 3: /* DECCOLM -- Column (IGNORED) */
1460 case 4: /* DECSCLM -- Scroll (IGNORED) */
1461 case 8: /* DECARM -- Auto repeat (IGNORED) */
1462 case 18: /* DECPFF -- Printer feed (IGNORED) */
1463 case 19: /* DECPEX -- Printer extent (IGNORED) */
1464 case 42: /* DECNRCM -- National characters (IGNORED) */
1465 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1467 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1468 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1470 case 9: /* X10 mouse compatibility mode */
1471 xsetpointermotion(0);
1472 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1473 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1475 case 1000: /* 1000: report button press */
1476 xsetpointermotion(0);
1477 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1478 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1480 case 1002: /* 1002: report motion on button press */
1481 xsetpointermotion(0);
1482 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1483 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1485 case 1003: /* 1003: enable all mouse motions */
1486 xsetpointermotion(set
);
1487 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1488 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1490 case 1004: /* 1004: send focus events to tty */
1491 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1493 case 1006: /* 1006: extended reporting mode */
1494 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1497 MODBIT(term
.mode
, set
, MODE_8BIT
);
1499 case 1049: /* swap screen & set/restore cursor as xterm */
1500 if (!allowaltscreen
)
1502 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1504 case 47: /* swap screen */
1506 if (!allowaltscreen
)
1508 alt
= IS_SET(MODE_ALTSCREEN
);
1510 tclearregion(0, 0, term
.col
-1,
1513 if (set
^ alt
) /* set is always 1 or 0 */
1519 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1521 case 2004: /* 2004: bracketed paste mode */
1522 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1524 /* Not implemented mouse modes. See comments there. */
1525 case 1001: /* mouse highlight mode; can hang the
1526 terminal by design when implemented. */
1527 case 1005: /* UTF-8 mouse mode; will confuse
1528 applications not supporting UTF-8
1530 case 1015: /* urxvt mangled mouse mode; incompatible
1531 and can be mistaken for other control
1535 "erresc: unknown private set/reset mode %d\n",
1541 case 0: /* Error (IGNORED) */
1543 case 2: /* KAM -- keyboard action */
1544 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1546 case 4: /* IRM -- Insertion-replacement */
1547 MODBIT(term
.mode
, set
, MODE_INSERT
);
1549 case 12: /* SRM -- Send/Receive */
1550 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1552 case 20: /* LNM -- Linefeed/new line */
1553 MODBIT(term
.mode
, set
, MODE_CRLF
);
1557 "erresc: unknown set/reset mode %d\n",
1571 switch (csiescseq
.mode
[0]) {
1574 fprintf(stderr
, "erresc: unknown csi ");
1578 case '@': /* ICH -- Insert <n> blank char */
1579 DEFAULT(csiescseq
.arg
[0], 1);
1580 tinsertblank(csiescseq
.arg
[0]);
1582 case 'A': /* CUU -- Cursor <n> Up */
1583 DEFAULT(csiescseq
.arg
[0], 1);
1584 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1586 case 'B': /* CUD -- Cursor <n> Down */
1587 case 'e': /* VPR --Cursor <n> Down */
1588 DEFAULT(csiescseq
.arg
[0], 1);
1589 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1591 case 'i': /* MC -- Media Copy */
1592 switch (csiescseq
.arg
[0]) {
1597 tdumpline(term
.c
.y
);
1603 term
.mode
&= ~MODE_PRINT
;
1606 term
.mode
|= MODE_PRINT
;
1610 case 'c': /* DA -- Device Attributes */
1611 if (csiescseq
.arg
[0] == 0)
1612 ttywrite(vtiden
, sizeof(vtiden
) - 1);
1614 case 'C': /* CUF -- Cursor <n> Forward */
1615 case 'a': /* HPR -- Cursor <n> Forward */
1616 DEFAULT(csiescseq
.arg
[0], 1);
1617 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1619 case 'D': /* CUB -- Cursor <n> Backward */
1620 DEFAULT(csiescseq
.arg
[0], 1);
1621 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1623 case 'E': /* CNL -- Cursor <n> Down and first col */
1624 DEFAULT(csiescseq
.arg
[0], 1);
1625 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1627 case 'F': /* CPL -- Cursor <n> Up and first col */
1628 DEFAULT(csiescseq
.arg
[0], 1);
1629 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1631 case 'g': /* TBC -- Tabulation clear */
1632 switch (csiescseq
.arg
[0]) {
1633 case 0: /* clear current tab stop */
1634 term
.tabs
[term
.c
.x
] = 0;
1636 case 3: /* clear all the tabs */
1637 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1643 case 'G': /* CHA -- Move to <col> */
1645 DEFAULT(csiescseq
.arg
[0], 1);
1646 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1648 case 'H': /* CUP -- Move to <row> <col> */
1650 DEFAULT(csiescseq
.arg
[0], 1);
1651 DEFAULT(csiescseq
.arg
[1], 1);
1652 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1654 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1655 DEFAULT(csiescseq
.arg
[0], 1);
1656 tputtab(csiescseq
.arg
[0]);
1658 case 'J': /* ED -- Clear screen */
1660 switch (csiescseq
.arg
[0]) {
1662 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1663 if (term
.c
.y
< term
.row
-1) {
1664 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1670 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1671 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1674 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1680 case 'K': /* EL -- Clear line */
1681 switch (csiescseq
.arg
[0]) {
1683 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1687 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1690 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1694 case 'S': /* SU -- Scroll <n> line up */
1695 DEFAULT(csiescseq
.arg
[0], 1);
1696 tscrollup(term
.top
, csiescseq
.arg
[0]);
1698 case 'T': /* SD -- Scroll <n> line down */
1699 DEFAULT(csiescseq
.arg
[0], 1);
1700 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1702 case 'L': /* IL -- Insert <n> blank lines */
1703 DEFAULT(csiescseq
.arg
[0], 1);
1704 tinsertblankline(csiescseq
.arg
[0]);
1706 case 'l': /* RM -- Reset Mode */
1707 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1709 case 'M': /* DL -- Delete <n> lines */
1710 DEFAULT(csiescseq
.arg
[0], 1);
1711 tdeleteline(csiescseq
.arg
[0]);
1713 case 'X': /* ECH -- Erase <n> char */
1714 DEFAULT(csiescseq
.arg
[0], 1);
1715 tclearregion(term
.c
.x
, term
.c
.y
,
1716 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1718 case 'P': /* DCH -- Delete <n> char */
1719 DEFAULT(csiescseq
.arg
[0], 1);
1720 tdeletechar(csiescseq
.arg
[0]);
1722 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1723 DEFAULT(csiescseq
.arg
[0], 1);
1724 tputtab(-csiescseq
.arg
[0]);
1726 case 'd': /* VPA -- Move to <row> */
1727 DEFAULT(csiescseq
.arg
[0], 1);
1728 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1730 case 'h': /* SM -- Set terminal mode */
1731 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1733 case 'm': /* SGR -- Terminal attribute (color) */
1734 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1736 case 'n': /* DSR – Device Status Report (cursor position) */
1737 if (csiescseq
.arg
[0] == 6) {
1738 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1739 term
.c
.y
+1, term
.c
.x
+1);
1743 case 'r': /* DECSTBM -- Set Scrolling Region */
1744 if (csiescseq
.priv
) {
1747 DEFAULT(csiescseq
.arg
[0], 1);
1748 DEFAULT(csiescseq
.arg
[1], term
.row
);
1749 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1753 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1754 tcursor(CURSOR_SAVE
);
1756 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1757 tcursor(CURSOR_LOAD
);
1760 switch (csiescseq
.mode
[1]) {
1761 case 'q': /* DECSCUSR -- Set Cursor Style */
1762 DEFAULT(csiescseq
.arg
[0], 1);
1763 if (!BETWEEN(csiescseq
.arg
[0], 0, 6)) {
1766 win
.cursor
= csiescseq
.arg
[0];
1781 fprintf(stderr
, "ESC[");
1782 for (i
= 0; i
< csiescseq
.len
; i
++) {
1783 c
= csiescseq
.buf
[i
] & 0xff;
1786 } else if (c
== '\n') {
1787 fprintf(stderr
, "(\\n)");
1788 } else if (c
== '\r') {
1789 fprintf(stderr
, "(\\r)");
1790 } else if (c
== 0x1b) {
1791 fprintf(stderr
, "(\\e)");
1793 fprintf(stderr
, "(%02x)", c
);
1802 memset(&csiescseq
, 0, sizeof(csiescseq
));
1811 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1813 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1815 switch (strescseq
.type
) {
1816 case ']': /* OSC -- Operating System Command */
1822 xsettitle(strescseq
.args
[1]);
1828 dec
= base64dec(strescseq
.args
[2]);
1830 xsetsel(dec
, CurrentTime
);
1833 fprintf(stderr
, "erresc: invalid base64\n");
1837 case 4: /* color set */
1840 p
= strescseq
.args
[2];
1842 case 104: /* color reset, here p = NULL */
1843 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1844 if (xsetcolorname(j
, p
)) {
1845 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1848 * TODO if defaultbg color is changed, borders
1856 case 'k': /* old title set compatibility */
1857 xsettitle(strescseq
.args
[0]);
1859 case 'P': /* DCS -- Device Control String */
1860 term
.mode
|= ESC_DCS
;
1861 case '_': /* APC -- Application Program Command */
1862 case '^': /* PM -- Privacy Message */
1866 fprintf(stderr
, "erresc: unknown str ");
1874 char *p
= strescseq
.buf
;
1877 strescseq
.buf
[strescseq
.len
] = '\0';
1882 while (strescseq
.narg
< STR_ARG_SIZ
) {
1883 strescseq
.args
[strescseq
.narg
++] = p
;
1884 while ((c
= *p
) != ';' && c
!= '\0')
1898 fprintf(stderr
, "ESC%c", strescseq
.type
);
1899 for (i
= 0; i
< strescseq
.len
; i
++) {
1900 c
= strescseq
.buf
[i
] & 0xff;
1904 } else if (isprint(c
)) {
1906 } else if (c
== '\n') {
1907 fprintf(stderr
, "(\\n)");
1908 } else if (c
== '\r') {
1909 fprintf(stderr
, "(\\r)");
1910 } else if (c
== 0x1b) {
1911 fprintf(stderr
, "(\\e)");
1913 fprintf(stderr
, "(%02x)", c
);
1916 fprintf(stderr
, "ESC\\\n");
1922 memset(&strescseq
, 0, sizeof(strescseq
));
1926 sendbreak(const Arg
*arg
)
1928 if (tcsendbreak(cmdfd
, 0))
1929 perror("Error sending break");
1933 tprinter(char *s
, size_t len
)
1935 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1936 perror("Error writing to output file");
1943 iso14755(const Arg
*arg
)
1946 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1947 unsigned long utf32
;
1949 if (!(p
= popen(ISO14755CMD
, "r")))
1952 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1955 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1957 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1958 (*e
!= '\n' && *e
!= '\0'))
1961 ttysend(uc
, utf8encode(utf32
, uc
));
1965 toggleprinter(const Arg
*arg
)
1967 term
.mode
^= MODE_PRINT
;
1971 printscreen(const Arg
*arg
)
1977 printsel(const Arg
*arg
)
1987 if ((ptr
= getsel())) {
1988 tprinter(ptr
, strlen(ptr
));
1999 bp
= &term
.line
[n
][0];
2000 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2001 if (bp
!= end
|| bp
->u
!= ' ') {
2002 for ( ;bp
<= end
; ++bp
)
2003 tprinter(buf
, utf8encode(bp
->u
, buf
));
2013 for (i
= 0; i
< term
.row
; ++i
)
2023 while (x
< term
.col
&& n
--)
2024 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2027 while (x
> 0 && n
++)
2028 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2031 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2037 if (ISCONTROL(u
)) { /* control code */
2042 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2051 tdefutf8(char ascii
)
2054 term
.mode
|= MODE_UTF8
;
2055 else if (ascii
== '@')
2056 term
.mode
&= ~MODE_UTF8
;
2060 tdeftran(char ascii
)
2062 static char cs
[] = "0B";
2063 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2066 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2067 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2069 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2078 if (c
== '8') { /* DEC screen alignment test. */
2079 for (x
= 0; x
< term
.col
; ++x
) {
2080 for (y
= 0; y
< term
.row
; ++y
)
2081 tsetchar('E', &term
.c
.attr
, x
, y
);
2087 tstrsequence(uchar c
)
2092 case 0x90: /* DCS -- Device Control String */
2094 term
.esc
|= ESC_DCS
;
2096 case 0x9f: /* APC -- Application Program Command */
2099 case 0x9e: /* PM -- Privacy Message */
2102 case 0x9d: /* OSC -- Operating System Command */
2107 term
.esc
|= ESC_STR
;
2111 tcontrolcode(uchar ascii
)
2118 tmoveto(term
.c
.x
-1, term
.c
.y
);
2121 tmoveto(0, term
.c
.y
);
2126 /* go to first col if the mode is set */
2127 tnewline(IS_SET(MODE_CRLF
));
2129 case '\a': /* BEL */
2130 if (term
.esc
& ESC_STR_END
) {
2131 /* backwards compatibility to xterm */
2137 case '\033': /* ESC */
2139 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2140 term
.esc
|= ESC_START
;
2142 case '\016': /* SO (LS1 -- Locking shift 1) */
2143 case '\017': /* SI (LS0 -- Locking shift 0) */
2144 term
.charset
= 1 - (ascii
- '\016');
2146 case '\032': /* SUB */
2147 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2148 case '\030': /* CAN */
2151 case '\005': /* ENQ (IGNORED) */
2152 case '\000': /* NUL (IGNORED) */
2153 case '\021': /* XON (IGNORED) */
2154 case '\023': /* XOFF (IGNORED) */
2155 case 0177: /* DEL (IGNORED) */
2157 case 0x80: /* TODO: PAD */
2158 case 0x81: /* TODO: HOP */
2159 case 0x82: /* TODO: BPH */
2160 case 0x83: /* TODO: NBH */
2161 case 0x84: /* TODO: IND */
2163 case 0x85: /* NEL -- Next line */
2164 tnewline(1); /* always go to first col */
2166 case 0x86: /* TODO: SSA */
2167 case 0x87: /* TODO: ESA */
2169 case 0x88: /* HTS -- Horizontal tab stop */
2170 term
.tabs
[term
.c
.x
] = 1;
2172 case 0x89: /* TODO: HTJ */
2173 case 0x8a: /* TODO: VTS */
2174 case 0x8b: /* TODO: PLD */
2175 case 0x8c: /* TODO: PLU */
2176 case 0x8d: /* TODO: RI */
2177 case 0x8e: /* TODO: SS2 */
2178 case 0x8f: /* TODO: SS3 */
2179 case 0x91: /* TODO: PU1 */
2180 case 0x92: /* TODO: PU2 */
2181 case 0x93: /* TODO: STS */
2182 case 0x94: /* TODO: CCH */
2183 case 0x95: /* TODO: MW */
2184 case 0x96: /* TODO: SPA */
2185 case 0x97: /* TODO: EPA */
2186 case 0x98: /* TODO: SOS */
2187 case 0x99: /* TODO: SGCI */
2189 case 0x9a: /* DECID -- Identify Terminal */
2190 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2192 case 0x9b: /* TODO: CSI */
2193 case 0x9c: /* TODO: ST */
2195 case 0x90: /* DCS -- Device Control String */
2196 case 0x9d: /* OSC -- Operating System Command */
2197 case 0x9e: /* PM -- Privacy Message */
2198 case 0x9f: /* APC -- Application Program Command */
2199 tstrsequence(ascii
);
2202 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2203 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2207 * returns 1 when the sequence is finished and it hasn't to read
2208 * more characters for this sequence, otherwise 0
2211 eschandle(uchar ascii
)
2215 term
.esc
|= ESC_CSI
;
2218 term
.esc
|= ESC_TEST
;
2221 term
.esc
|= ESC_UTF8
;
2223 case 'P': /* DCS -- Device Control String */
2224 case '_': /* APC -- Application Program Command */
2225 case '^': /* PM -- Privacy Message */
2226 case ']': /* OSC -- Operating System Command */
2227 case 'k': /* old title set compatibility */
2228 tstrsequence(ascii
);
2230 case 'n': /* LS2 -- Locking shift 2 */
2231 case 'o': /* LS3 -- Locking shift 3 */
2232 term
.charset
= 2 + (ascii
- 'n');
2234 case '(': /* GZD4 -- set primary charset G0 */
2235 case ')': /* G1D4 -- set secondary charset G1 */
2236 case '*': /* G2D4 -- set tertiary charset G2 */
2237 case '+': /* G3D4 -- set quaternary charset G3 */
2238 term
.icharset
= ascii
- '(';
2239 term
.esc
|= ESC_ALTCHARSET
;
2241 case 'D': /* IND -- Linefeed */
2242 if (term
.c
.y
== term
.bot
) {
2243 tscrollup(term
.top
, 1);
2245 tmoveto(term
.c
.x
, term
.c
.y
+1);
2248 case 'E': /* NEL -- Next line */
2249 tnewline(1); /* always go to first col */
2251 case 'H': /* HTS -- Horizontal tab stop */
2252 term
.tabs
[term
.c
.x
] = 1;
2254 case 'M': /* RI -- Reverse index */
2255 if (term
.c
.y
== term
.top
) {
2256 tscrolldown(term
.top
, 1);
2258 tmoveto(term
.c
.x
, term
.c
.y
-1);
2261 case 'Z': /* DECID -- Identify Terminal */
2262 ttywrite(vtiden
, sizeof(vtiden
) - 1);
2264 case 'c': /* RIS -- Reset to inital state */
2269 case '=': /* DECPAM -- Application keypad */
2270 term
.mode
|= MODE_APPKEYPAD
;
2272 case '>': /* DECPNM -- Normal keypad */
2273 term
.mode
&= ~MODE_APPKEYPAD
;
2275 case '7': /* DECSC -- Save Cursor */
2276 tcursor(CURSOR_SAVE
);
2278 case '8': /* DECRC -- Restore Cursor */
2279 tcursor(CURSOR_LOAD
);
2281 case '\\': /* ST -- String Terminator */
2282 if (term
.esc
& ESC_STR_END
)
2286 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2287 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2301 control
= ISCONTROL(u
);
2302 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2306 len
= utf8encode(u
, c
);
2307 if (!control
&& (width
= wcwidth(u
)) == -1) {
2308 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2313 if (IS_SET(MODE_PRINT
))
2317 * STR sequence must be checked before anything else
2318 * because it uses all following characters until it
2319 * receives a ESC, a SUB, a ST or any other C1 control
2322 if (term
.esc
& ESC_STR
) {
2323 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2325 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2326 if (IS_SET(MODE_SIXEL
)) {
2327 /* TODO: render sixel */;
2328 term
.mode
&= ~MODE_SIXEL
;
2331 term
.esc
|= ESC_STR_END
;
2332 goto check_control_code
;
2336 if (IS_SET(MODE_SIXEL
)) {
2337 /* TODO: implement sixel mode */
2340 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2341 term
.mode
|= MODE_SIXEL
;
2343 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2345 * Here is a bug in terminals. If the user never sends
2346 * some code to stop the str or esc command, then st
2347 * will stop responding. But this is better than
2348 * silently failing with unknown characters. At least
2349 * then users will report back.
2351 * In the case users ever get fixed, here is the code:
2360 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2361 strescseq
.len
+= len
;
2367 * Actions of control codes must be performed as soon they arrive
2368 * because they can be embedded inside a control sequence, and
2369 * they must not cause conflicts with sequences.
2374 * control codes are not shown ever
2377 } else if (term
.esc
& ESC_START
) {
2378 if (term
.esc
& ESC_CSI
) {
2379 csiescseq
.buf
[csiescseq
.len
++] = u
;
2380 if (BETWEEN(u
, 0x40, 0x7E)
2381 || csiescseq
.len
>= \
2382 sizeof(csiescseq
.buf
)-1) {
2388 } else if (term
.esc
& ESC_UTF8
) {
2390 } else if (term
.esc
& ESC_ALTCHARSET
) {
2392 } else if (term
.esc
& ESC_TEST
) {
2397 /* sequence already finished */
2401 * All characters which form part of a sequence are not
2406 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2409 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2410 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2411 gp
->mode
|= ATTR_WRAP
;
2413 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2416 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2417 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2419 if (term
.c
.x
+width
> term
.col
) {
2421 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2424 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2427 gp
->mode
|= ATTR_WIDE
;
2428 if (term
.c
.x
+1 < term
.col
) {
2430 gp
[1].mode
= ATTR_WDUMMY
;
2433 if (term
.c
.x
+width
< term
.col
) {
2434 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2436 term
.c
.state
|= CURSOR_WRAPNEXT
;
2441 tresize(int col
, int row
)
2444 int minrow
= MIN(row
, term
.row
);
2445 int mincol
= MIN(col
, term
.col
);
2449 if (col
< 1 || row
< 1) {
2451 "tresize: error resizing to %dx%d\n", col
, row
);
2456 * slide screen to keep cursor where we expect it -
2457 * tscrollup would work here, but we can optimize to
2458 * memmove because we're freeing the earlier lines
2460 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2464 /* ensure that both src and dst are not NULL */
2466 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2467 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2469 for (i
+= row
; i
< term
.row
; i
++) {
2474 /* resize to new height */
2475 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2476 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2477 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2478 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2480 /* resize each row to new width, zero-pad if needed */
2481 for (i
= 0; i
< minrow
; i
++) {
2482 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2483 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2486 /* allocate any new rows */
2487 for (/* i = minrow */; i
< row
; i
++) {
2488 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2489 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2491 if (col
> term
.col
) {
2492 bp
= term
.tabs
+ term
.col
;
2494 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2495 while (--bp
> term
.tabs
&& !*bp
)
2497 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2500 /* update terminal size */
2503 /* reset scrolling region */
2504 tsetscroll(0, row
-1);
2505 /* make use of the LIMIT in tmoveto */
2506 tmoveto(term
.c
.x
, term
.c
.y
);
2507 /* Clearing both screens (it makes dirty all lines) */
2509 for (i
= 0; i
< 2; i
++) {
2510 if (mincol
< col
&& 0 < minrow
) {
2511 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2513 if (0 < col
&& minrow
< row
) {
2514 tclearregion(0, minrow
, col
- 1, row
- 1);
2517 tcursor(CURSOR_LOAD
);
2536 numlock(const Arg
*dummy
)