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>
31 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
33 #elif defined(__FreeBSD__) || defined(__DragonFly__)
38 #define UTF_INVALID 0xFFFD
39 #define ESC_BUF_SIZ (128*UTF_SIZ)
40 #define ESC_ARG_SIZ 16
41 #define STR_BUF_SIZ ESC_BUF_SIZ
42 #define STR_ARG_SIZ ESC_ARG_SIZ
45 #define IS_SET(flag) ((term.mode & (flag)) != 0)
46 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
47 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
48 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
49 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
50 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
53 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
58 MODE_ALTSCREEN
= 1 << 2,
66 enum cursor_movement
{
90 ESC_STR
= 4, /* OSC, PM, APC */
92 ESC_STR_END
= 16, /* a final string was encountered */
93 ESC_TEST
= 32, /* Enter in test mode */
98 /* Internal representation of the screen */
100 int row
; /* nb row */
101 int col
; /* nb col */
102 Line
*line
; /* screen */
103 Line
*alt
; /* alternate screen */
104 int *dirty
; /* dirtyness of lines */
105 TCursor c
; /* cursor */
106 int ocx
; /* old cursor col */
107 int ocy
; /* old cursor row */
108 int top
; /* top scroll limit */
109 int bot
; /* bottom scroll limit */
110 int mode
; /* terminal mode flags */
111 int esc
; /* escape state flags */
112 char trantbl
[4]; /* charset table translation */
113 int charset
; /* current charset */
114 int icharset
; /* selected charset for sequence */
118 /* CSI Escape sequence structs */
119 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
121 char buf
[ESC_BUF_SIZ
]; /* raw string */
122 int len
; /* raw string length */
124 int arg
[ESC_ARG_SIZ
];
125 int narg
; /* nb of args */
129 /* STR Escape sequence structs */
130 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
132 char type
; /* ESC type ... */
133 char buf
[STR_BUF_SIZ
]; /* raw string */
134 int len
; /* raw string length */
135 char *args
[STR_ARG_SIZ
];
136 int narg
; /* nb of args */
139 static void execsh(char *, char **);
140 static void stty(char **);
141 static void sigchld(int);
142 static void ttywriteraw(const char *, size_t);
144 static void csidump(void);
145 static void csihandle(void);
146 static void csiparse(void);
147 static void csireset(void);
148 static int eschandle(uchar
);
149 static void strdump(void);
150 static void strhandle(void);
151 static void strparse(void);
152 static void strreset(void);
154 static void tprinter(char *, size_t);
155 static void tdumpsel(void);
156 static void tdumpline(int);
157 static void tdump(void);
158 static void tclearregion(int, int, int, int);
159 static void tcursor(int);
160 static void tdeletechar(int);
161 static void tdeleteline(int);
162 static void tinsertblank(int);
163 static void tinsertblankline(int);
164 static int tlinelen(int);
165 static void tmoveto(int, int);
166 static void tmoveato(int, int);
167 static void tnewline(int);
168 static void tputtab(int);
169 static void tputc(Rune
);
170 static void treset(void);
171 static void tscrollup(int, int);
172 static void tscrolldown(int, int);
173 static void tsetattr(int *, int);
174 static void tsetchar(Rune
, Glyph
*, int, int);
175 static void tsetdirt(int, int);
176 static void tsetscroll(int, int);
177 static void tswapscreen(void);
178 static void tsetmode(int, int, int *, int);
179 static int twrite(const char *, int, int);
180 static void tfulldirt(void);
181 static void tcontrolcode(uchar
);
182 static void tdectest(char );
183 static void tdefutf8(char);
184 static int32_t tdefcolor(int *, int *, int);
185 static void tdeftran(char);
186 static void tstrsequence(uchar
);
188 static void drawregion(int, int, int, int);
190 static void selscroll(int, int);
191 static void selsnap(int *, int *, int);
193 static Rune
utf8decodebyte(char, size_t *);
194 static char utf8encodebyte(Rune
, size_t);
195 static char *utf8strchr(char *s
, Rune u
);
196 static size_t utf8validate(Rune
*, size_t);
198 static char *base64dec(const char *);
200 static ssize_t
xwrite(int, const char *, size_t);
204 static Selection sel
;
205 static CSIEscape csiescseq
;
206 static STREscape strescseq
;
211 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
212 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
213 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
214 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
217 xwrite(int fd
, const char *s
, size_t len
)
223 r
= write(fd
, s
, len
);
236 void *p
= malloc(len
);
239 die("Out of memory\n");
245 xrealloc(void *p
, size_t len
)
247 if ((p
= realloc(p
, len
)) == NULL
)
248 die("Out of memory\n");
256 if ((s
= strdup(s
)) == NULL
)
257 die("Out of memory\n");
263 utf8decode(const char *c
, Rune
*u
, size_t clen
)
265 size_t i
, j
, len
, type
;
271 udecoded
= utf8decodebyte(c
[0], &len
);
272 if (!BETWEEN(len
, 1, UTF_SIZ
))
274 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
275 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
282 utf8validate(u
, len
);
288 utf8decodebyte(char c
, size_t *i
)
290 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
291 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
292 return (uchar
)c
& ~utfmask
[*i
];
298 utf8encode(Rune u
, char *c
)
302 len
= utf8validate(&u
, 0);
306 for (i
= len
- 1; i
!= 0; --i
) {
307 c
[i
] = utf8encodebyte(u
, 0);
310 c
[0] = utf8encodebyte(u
, len
);
316 utf8encodebyte(Rune u
, size_t i
)
318 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
322 utf8strchr(char *s
, Rune u
)
328 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
329 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
339 utf8validate(Rune
*u
, size_t i
)
341 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
343 for (i
= 1; *u
> utfmax
[i
]; ++i
)
349 static const char base64_digits
[] = {
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, 62, 0, 0, 0,
352 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
353 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
354 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
355 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
356 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
357 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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
365 base64dec_getc(const char **src
)
367 while (**src
&& !isprint(**src
)) (*src
)++;
372 base64dec(const char *src
)
374 size_t in_len
= strlen(src
);
378 in_len
+= 4 - (in_len
% 4);
379 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
381 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
382 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
383 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
384 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
386 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
389 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
392 *dst
++ = ((c
& 0x03) << 6) | d
;
411 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
414 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
421 selstart(int col
, int row
, int snap
)
424 sel
.mode
= SEL_EMPTY
;
425 sel
.type
= SEL_REGULAR
;
427 sel
.oe
.x
= sel
.ob
.x
= col
;
428 sel
.oe
.y
= sel
.ob
.y
= row
;
432 sel
.mode
= SEL_READY
;
433 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
437 selextend(int col
, int row
, int type
, int done
)
439 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
443 if (done
&& sel
.mode
== SEL_EMPTY
) {
454 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
460 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
461 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
463 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
471 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
472 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
473 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
475 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
476 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
478 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
479 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
481 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
482 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
484 /* expand selection over line breaks */
485 if (sel
.type
== SEL_RECTANGULAR
)
487 i
= tlinelen(sel
.nb
.y
);
490 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
491 sel
.ne
.x
= term
.col
- 1;
495 selected(int x
, int y
)
497 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
498 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
501 if (sel
.type
== SEL_RECTANGULAR
)
502 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
503 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
505 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
506 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
507 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
511 selsnap(int *x
, int *y
, int direction
)
513 int newx
, newy
, xt
, yt
;
514 int delim
, prevdelim
;
520 * Snap around if the word wraps around at the end or
521 * beginning of a line.
523 prevgp
= &term
.line
[*y
][*x
];
524 prevdelim
= ISDELIM(prevgp
->u
);
526 newx
= *x
+ direction
;
528 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
530 newx
= (newx
+ term
.col
) % term
.col
;
531 if (!BETWEEN(newy
, 0, term
.row
- 1))
537 yt
= newy
, xt
= newx
;
538 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
542 if (newx
>= tlinelen(newy
))
545 gp
= &term
.line
[newy
][newx
];
546 delim
= ISDELIM(gp
->u
);
547 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
548 || (delim
&& gp
->u
!= prevgp
->u
)))
559 * Snap around if the the previous line or the current one
560 * has set ATTR_WRAP at its end. Then the whole next or
561 * previous line will be selected.
563 *x
= (direction
< 0) ? 0 : term
.col
- 1;
565 for (; *y
> 0; *y
+= direction
) {
566 if (!(term
.line
[*y
-1][term
.col
-1].mode
571 } else if (direction
> 0) {
572 for (; *y
< term
.row
-1; *y
+= direction
) {
573 if (!(term
.line
[*y
][term
.col
-1].mode
587 int y
, bufsize
, lastx
, linelen
;
593 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
594 ptr
= str
= xmalloc(bufsize
);
596 /* append every set & selected glyph to the selection */
597 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
598 if ((linelen
= tlinelen(y
)) == 0) {
603 if (sel
.type
== SEL_RECTANGULAR
) {
604 gp
= &term
.line
[y
][sel
.nb
.x
];
607 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
608 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
610 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
611 while (last
>= gp
&& last
->u
== ' ')
614 for ( ; gp
<= last
; ++gp
) {
615 if (gp
->mode
& ATTR_WDUMMY
)
618 ptr
+= utf8encode(gp
->u
, ptr
);
622 * Copy and pasting of line endings is inconsistent
623 * in the inconsistent terminal and GUI world.
624 * The best solution seems like to produce '\n' when
625 * something is copied from st and convert '\n' to
626 * '\r', when something to be pasted is received by
628 * FIXME: Fix the computer world.
630 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
644 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
648 die(const char *errstr
, ...)
652 va_start(ap
, errstr
);
653 vfprintf(stderr
, errstr
, ap
);
659 execsh(char *cmd
, char **args
)
662 const struct passwd
*pw
;
665 if ((pw
= getpwuid(getuid())) == NULL
) {
667 die("getpwuid:%s\n", strerror(errno
));
669 die("who are you?\n");
672 if ((sh
= getenv("SHELL")) == NULL
)
673 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: cmd
;
681 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
686 setenv("LOGNAME", pw
->pw_name
, 1);
687 setenv("USER", pw
->pw_name
, 1);
688 setenv("SHELL", sh
, 1);
689 setenv("HOME", pw
->pw_dir
, 1);
690 setenv("TERM", termname
, 1);
692 signal(SIGCHLD
, SIG_DFL
);
693 signal(SIGHUP
, SIG_DFL
);
694 signal(SIGINT
, SIG_DFL
);
695 signal(SIGQUIT
, SIG_DFL
);
696 signal(SIGTERM
, SIG_DFL
);
697 signal(SIGALRM
, SIG_DFL
);
709 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
710 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
715 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
716 die("child finished with error '%d'\n", stat
);
724 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
727 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
728 die("incorrect stty parameters\n");
729 memcpy(cmd
, stty_args
, n
);
731 siz
= sizeof(cmd
) - n
;
732 for (p
= args
; p
&& (s
= *p
); ++p
) {
733 if ((n
= strlen(s
)) > siz
-1)
734 die("stty parameter length too long\n");
741 if (system(cmd
) != 0)
742 perror("Couldn't call stty");
746 ttynew(char *line
, char *cmd
, char *out
, char **args
)
751 term
.mode
|= MODE_PRINT
;
752 iofd
= (!strcmp(out
, "-")) ?
753 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
755 fprintf(stderr
, "Error opening %s:%s\n",
756 out
, strerror(errno
));
761 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
762 die("open line failed: %s\n", strerror(errno
));
768 /* seems to work fine on linux, openbsd and freebsd */
769 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
770 die("openpty failed: %s\n", strerror(errno
));
772 switch (pid
= fork()) {
774 die("fork failed\n");
778 setsid(); /* create a new process group */
782 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
783 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
791 signal(SIGCHLD
, sigchld
);
800 static char buf
[BUFSIZ
];
801 static int buflen
= 0;
805 /* append read bytes to unprocessed bytes */
806 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
807 die("Couldn't read from shell: %s\n", strerror(errno
));
810 written
= twrite(buf
, buflen
, 0);
812 /* keep any uncomplete utf8 char for the next call */
814 memmove(buf
, buf
+ written
, buflen
);
820 ttywrite(const char *s
, size_t n
, int may_echo
)
824 if (may_echo
&& IS_SET(MODE_ECHO
))
827 if (!IS_SET(MODE_CRLF
)) {
832 /* This is similar to how the kernel handles ONLCR for ttys */
836 ttywriteraw("\r\n", 2);
838 next
= memchr(s
, '\r', n
);
839 DEFAULT(next
, s
+ n
);
840 ttywriteraw(s
, next
- s
);
848 ttywriteraw(const char *s
, size_t n
)
855 * Remember that we are using a pty, which might be a modem line.
856 * Writing too much will clog the line. That's why we are doing this
858 * FIXME: Migrate the world to Plan 9.
866 /* Check if we can write. */
867 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
870 die("select failed: %s\n", strerror(errno
));
872 if (FD_ISSET(cmdfd
, &wfd
)) {
874 * Only write the bytes written by ttywrite() or the
875 * default of 256. This seems to be a reasonable value
876 * for a serial line. Bigger values might clog the I/O.
878 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
882 * We weren't able to write out everything.
883 * This means the buffer is getting full
891 /* All bytes have been written. */
895 if (FD_ISSET(cmdfd
, &rfd
))
901 die("write error on tty: %s\n", strerror(errno
));
905 ttyresize(int tw
, int th
)
913 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
914 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
920 /* Send SIGHUP to shell */
929 for (i
= 0; i
< term
.row
-1; i
++) {
930 for (j
= 0; j
< term
.col
-1; j
++) {
931 if (term
.line
[i
][j
].mode
& attr
)
940 tsetdirt(int top
, int bot
)
944 LIMIT(top
, 0, term
.row
-1);
945 LIMIT(bot
, 0, term
.row
-1);
947 for (i
= top
; i
<= bot
; i
++)
952 tsetdirtattr(int attr
)
956 for (i
= 0; i
< term
.row
-1; i
++) {
957 for (j
= 0; j
< term
.col
-1; j
++) {
958 if (term
.line
[i
][j
].mode
& attr
) {
969 tsetdirt(0, term
.row
-1);
976 int alt
= IS_SET(MODE_ALTSCREEN
);
978 if (mode
== CURSOR_SAVE
) {
980 } else if (mode
== CURSOR_LOAD
) {
982 tmoveto(c
[alt
].x
, c
[alt
].y
);
995 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
997 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
998 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
1001 term
.bot
= term
.row
- 1;
1002 term
.mode
= MODE_WRAP
|MODE_UTF8
;
1003 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1006 for (i
= 0; i
< 2; i
++) {
1008 tcursor(CURSOR_SAVE
);
1009 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1015 tnew(int col
, int row
)
1017 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1025 Line
*tmp
= term
.line
;
1027 term
.line
= term
.alt
;
1029 term
.mode
^= MODE_ALTSCREEN
;
1034 tscrolldown(int orig
, int n
)
1039 LIMIT(n
, 0, term
.bot
-orig
+1);
1041 tsetdirt(orig
, term
.bot
-n
);
1042 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1044 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1045 temp
= term
.line
[i
];
1046 term
.line
[i
] = term
.line
[i
-n
];
1047 term
.line
[i
-n
] = temp
;
1054 tscrollup(int orig
, int n
)
1059 LIMIT(n
, 0, term
.bot
-orig
+1);
1061 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1062 tsetdirt(orig
+n
, term
.bot
);
1064 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1065 temp
= term
.line
[i
];
1066 term
.line
[i
] = term
.line
[i
+n
];
1067 term
.line
[i
+n
] = temp
;
1070 selscroll(orig
, -n
);
1074 selscroll(int orig
, int n
)
1079 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1080 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1084 if (sel
.type
== SEL_RECTANGULAR
) {
1085 if (sel
.ob
.y
< term
.top
)
1086 sel
.ob
.y
= term
.top
;
1087 if (sel
.oe
.y
> term
.bot
)
1088 sel
.oe
.y
= term
.bot
;
1090 if (sel
.ob
.y
< term
.top
) {
1091 sel
.ob
.y
= term
.top
;
1094 if (sel
.oe
.y
> term
.bot
) {
1095 sel
.oe
.y
= term
.bot
;
1096 sel
.oe
.x
= term
.col
;
1104 tnewline(int first_col
)
1108 if (y
== term
.bot
) {
1109 tscrollup(term
.top
, 1);
1113 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1119 char *p
= csiescseq
.buf
, *np
;
1128 csiescseq
.buf
[csiescseq
.len
] = '\0';
1129 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1131 v
= strtol(p
, &np
, 10);
1134 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1136 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1138 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1142 csiescseq
.mode
[0] = *p
++;
1143 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1146 /* for absolute user moves, when decom is set */
1148 tmoveato(int x
, int y
)
1150 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1154 tmoveto(int x
, int y
)
1158 if (term
.c
.state
& CURSOR_ORIGIN
) {
1163 maxy
= term
.row
- 1;
1165 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1166 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1167 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1171 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1173 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1174 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1175 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1176 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1177 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1178 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1179 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1180 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1181 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1185 * The table is proudly stolen from rxvt.
1187 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1188 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1189 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1191 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1192 if (x
+1 < term
.col
) {
1193 term
.line
[y
][x
+1].u
= ' ';
1194 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1196 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1197 term
.line
[y
][x
-1].u
= ' ';
1198 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1202 term
.line
[y
][x
] = *attr
;
1203 term
.line
[y
][x
].u
= u
;
1207 tclearregion(int x1
, int y1
, int x2
, int y2
)
1213 temp
= x1
, x1
= x2
, x2
= temp
;
1215 temp
= y1
, y1
= y2
, y2
= temp
;
1217 LIMIT(x1
, 0, term
.col
-1);
1218 LIMIT(x2
, 0, term
.col
-1);
1219 LIMIT(y1
, 0, term
.row
-1);
1220 LIMIT(y2
, 0, term
.row
-1);
1222 for (y
= y1
; y
<= y2
; y
++) {
1224 for (x
= x1
; x
<= x2
; x
++) {
1225 gp
= &term
.line
[y
][x
];
1228 gp
->fg
= term
.c
.attr
.fg
;
1229 gp
->bg
= term
.c
.attr
.bg
;
1242 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1246 size
= term
.col
- src
;
1247 line
= term
.line
[term
.c
.y
];
1249 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1250 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1259 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1263 size
= term
.col
- dst
;
1264 line
= term
.line
[term
.c
.y
];
1266 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1267 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1271 tinsertblankline(int n
)
1273 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1274 tscrolldown(term
.c
.y
, n
);
1280 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1281 tscrollup(term
.c
.y
, n
);
1285 tdefcolor(int *attr
, int *npar
, int l
)
1290 switch (attr
[*npar
+ 1]) {
1291 case 2: /* direct color in RGB space */
1292 if (*npar
+ 4 >= l
) {
1294 "erresc(38): Incorrect number of parameters (%d)\n",
1298 r
= attr
[*npar
+ 2];
1299 g
= attr
[*npar
+ 3];
1300 b
= attr
[*npar
+ 4];
1302 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1303 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1306 idx
= TRUECOLOR(r
, g
, b
);
1308 case 5: /* indexed color */
1309 if (*npar
+ 2 >= l
) {
1311 "erresc(38): Incorrect number of parameters (%d)\n",
1316 if (!BETWEEN(attr
[*npar
], 0, 255))
1317 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1321 case 0: /* implemented defined (only foreground) */
1322 case 1: /* transparent */
1323 case 3: /* direct color in CMY space */
1324 case 4: /* direct color in CMYK space */
1327 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1335 tsetattr(int *attr
, int l
)
1340 for (i
= 0; i
< l
; i
++) {
1343 term
.c
.attr
.mode
&= ~(
1352 term
.c
.attr
.fg
= defaultfg
;
1353 term
.c
.attr
.bg
= defaultbg
;
1356 term
.c
.attr
.mode
|= ATTR_BOLD
;
1359 term
.c
.attr
.mode
|= ATTR_FAINT
;
1362 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1365 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1367 case 5: /* slow blink */
1369 case 6: /* rapid blink */
1370 term
.c
.attr
.mode
|= ATTR_BLINK
;
1373 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1376 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1379 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1382 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1385 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1388 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1391 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1394 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1397 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1400 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1403 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1404 term
.c
.attr
.fg
= idx
;
1407 term
.c
.attr
.fg
= defaultfg
;
1410 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1411 term
.c
.attr
.bg
= idx
;
1414 term
.c
.attr
.bg
= defaultbg
;
1417 if (BETWEEN(attr
[i
], 30, 37)) {
1418 term
.c
.attr
.fg
= attr
[i
] - 30;
1419 } else if (BETWEEN(attr
[i
], 40, 47)) {
1420 term
.c
.attr
.bg
= attr
[i
] - 40;
1421 } else if (BETWEEN(attr
[i
], 90, 97)) {
1422 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1423 } else if (BETWEEN(attr
[i
], 100, 107)) {
1424 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1427 "erresc(default): gfx attr %d unknown\n",
1428 attr
[i
]), csidump();
1436 tsetscroll(int t
, int b
)
1440 LIMIT(t
, 0, term
.row
-1);
1441 LIMIT(b
, 0, term
.row
-1);
1452 tsetmode(int priv
, int set
, int *args
, int narg
)
1456 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1459 case 1: /* DECCKM -- Cursor key */
1460 xsetmode(set
, MODE_APPCURSOR
);
1462 case 5: /* DECSCNM -- Reverse video */
1463 xsetmode(set
, MODE_REVERSE
);
1465 case 6: /* DECOM -- Origin */
1466 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1469 case 7: /* DECAWM -- Auto wrap */
1470 MODBIT(term
.mode
, set
, MODE_WRAP
);
1472 case 0: /* Error (IGNORED) */
1473 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1474 case 3: /* DECCOLM -- Column (IGNORED) */
1475 case 4: /* DECSCLM -- Scroll (IGNORED) */
1476 case 8: /* DECARM -- Auto repeat (IGNORED) */
1477 case 18: /* DECPFF -- Printer feed (IGNORED) */
1478 case 19: /* DECPEX -- Printer extent (IGNORED) */
1479 case 42: /* DECNRCM -- National characters (IGNORED) */
1480 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1482 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1483 xsetmode(!set
, MODE_HIDE
);
1485 case 9: /* X10 mouse compatibility mode */
1486 xsetpointermotion(0);
1487 xsetmode(0, MODE_MOUSE
);
1488 xsetmode(set
, MODE_MOUSEX10
);
1490 case 1000: /* 1000: report button press */
1491 xsetpointermotion(0);
1492 xsetmode(0, MODE_MOUSE
);
1493 xsetmode(set
, MODE_MOUSEBTN
);
1495 case 1002: /* 1002: report motion on button press */
1496 xsetpointermotion(0);
1497 xsetmode(0, MODE_MOUSE
);
1498 xsetmode(set
, MODE_MOUSEMOTION
);
1500 case 1003: /* 1003: enable all mouse motions */
1501 xsetpointermotion(set
);
1502 xsetmode(0, MODE_MOUSE
);
1503 xsetmode(set
, MODE_MOUSEMANY
);
1505 case 1004: /* 1004: send focus events to tty */
1506 xsetmode(set
, MODE_FOCUS
);
1508 case 1006: /* 1006: extended reporting mode */
1509 xsetmode(set
, MODE_MOUSESGR
);
1512 xsetmode(set
, MODE_8BIT
);
1514 case 1049: /* swap screen & set/restore cursor as xterm */
1515 if (!allowaltscreen
)
1517 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1519 case 47: /* swap screen */
1521 if (!allowaltscreen
)
1523 alt
= IS_SET(MODE_ALTSCREEN
);
1525 tclearregion(0, 0, term
.col
-1,
1528 if (set
^ alt
) /* set is always 1 or 0 */
1534 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1536 case 2004: /* 2004: bracketed paste mode */
1537 xsetmode(set
, MODE_BRCKTPASTE
);
1539 /* Not implemented mouse modes. See comments there. */
1540 case 1001: /* mouse highlight mode; can hang the
1541 terminal by design when implemented. */
1542 case 1005: /* UTF-8 mouse mode; will confuse
1543 applications not supporting UTF-8
1545 case 1015: /* urxvt mangled mouse mode; incompatible
1546 and can be mistaken for other control
1550 "erresc: unknown private set/reset mode %d\n",
1556 case 0: /* Error (IGNORED) */
1559 xsetmode(set
, MODE_KBDLOCK
);
1561 case 4: /* IRM -- Insertion-replacement */
1562 MODBIT(term
.mode
, set
, MODE_INSERT
);
1564 case 12: /* SRM -- Send/Receive */
1565 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1567 case 20: /* LNM -- Linefeed/new line */
1568 MODBIT(term
.mode
, set
, MODE_CRLF
);
1572 "erresc: unknown set/reset mode %d\n",
1586 switch (csiescseq
.mode
[0]) {
1589 fprintf(stderr
, "erresc: unknown csi ");
1593 case '@': /* ICH -- Insert <n> blank char */
1594 DEFAULT(csiescseq
.arg
[0], 1);
1595 tinsertblank(csiescseq
.arg
[0]);
1597 case 'A': /* CUU -- Cursor <n> Up */
1598 DEFAULT(csiescseq
.arg
[0], 1);
1599 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1601 case 'B': /* CUD -- Cursor <n> Down */
1602 case 'e': /* VPR --Cursor <n> Down */
1603 DEFAULT(csiescseq
.arg
[0], 1);
1604 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1606 case 'i': /* MC -- Media Copy */
1607 switch (csiescseq
.arg
[0]) {
1612 tdumpline(term
.c
.y
);
1618 term
.mode
&= ~MODE_PRINT
;
1621 term
.mode
|= MODE_PRINT
;
1625 case 'c': /* DA -- Device Attributes */
1626 if (csiescseq
.arg
[0] == 0)
1627 ttywrite(vtiden
, strlen(vtiden
), 0);
1629 case 'C': /* CUF -- Cursor <n> Forward */
1630 case 'a': /* HPR -- Cursor <n> Forward */
1631 DEFAULT(csiescseq
.arg
[0], 1);
1632 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1634 case 'D': /* CUB -- Cursor <n> Backward */
1635 DEFAULT(csiescseq
.arg
[0], 1);
1636 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1638 case 'E': /* CNL -- Cursor <n> Down and first col */
1639 DEFAULT(csiescseq
.arg
[0], 1);
1640 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1642 case 'F': /* CPL -- Cursor <n> Up and first col */
1643 DEFAULT(csiescseq
.arg
[0], 1);
1644 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1646 case 'g': /* TBC -- Tabulation clear */
1647 switch (csiescseq
.arg
[0]) {
1648 case 0: /* clear current tab stop */
1649 term
.tabs
[term
.c
.x
] = 0;
1651 case 3: /* clear all the tabs */
1652 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1658 case 'G': /* CHA -- Move to <col> */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1663 case 'H': /* CUP -- Move to <row> <col> */
1665 DEFAULT(csiescseq
.arg
[0], 1);
1666 DEFAULT(csiescseq
.arg
[1], 1);
1667 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1669 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1670 DEFAULT(csiescseq
.arg
[0], 1);
1671 tputtab(csiescseq
.arg
[0]);
1673 case 'J': /* ED -- Clear screen */
1675 switch (csiescseq
.arg
[0]) {
1677 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1678 if (term
.c
.y
< term
.row
-1) {
1679 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1685 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1686 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1689 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1695 case 'K': /* EL -- Clear line */
1696 switch (csiescseq
.arg
[0]) {
1698 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1702 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1705 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1709 case 'S': /* SU -- Scroll <n> line up */
1710 DEFAULT(csiescseq
.arg
[0], 1);
1711 tscrollup(term
.top
, csiescseq
.arg
[0]);
1713 case 'T': /* SD -- Scroll <n> line down */
1714 DEFAULT(csiescseq
.arg
[0], 1);
1715 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1717 case 'L': /* IL -- Insert <n> blank lines */
1718 DEFAULT(csiescseq
.arg
[0], 1);
1719 tinsertblankline(csiescseq
.arg
[0]);
1721 case 'l': /* RM -- Reset Mode */
1722 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1724 case 'M': /* DL -- Delete <n> lines */
1725 DEFAULT(csiescseq
.arg
[0], 1);
1726 tdeleteline(csiescseq
.arg
[0]);
1728 case 'X': /* ECH -- Erase <n> char */
1729 DEFAULT(csiescseq
.arg
[0], 1);
1730 tclearregion(term
.c
.x
, term
.c
.y
,
1731 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1733 case 'P': /* DCH -- Delete <n> char */
1734 DEFAULT(csiescseq
.arg
[0], 1);
1735 tdeletechar(csiescseq
.arg
[0]);
1737 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1738 DEFAULT(csiescseq
.arg
[0], 1);
1739 tputtab(-csiescseq
.arg
[0]);
1741 case 'd': /* VPA -- Move to <row> */
1742 DEFAULT(csiescseq
.arg
[0], 1);
1743 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1745 case 'h': /* SM -- Set terminal mode */
1746 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1748 case 'm': /* SGR -- Terminal attribute (color) */
1749 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1751 case 'n': /* DSR – Device Status Report (cursor position) */
1752 if (csiescseq
.arg
[0] == 6) {
1753 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1754 term
.c
.y
+1, term
.c
.x
+1);
1755 ttywrite(buf
, len
, 0);
1758 case 'r': /* DECSTBM -- Set Scrolling Region */
1759 if (csiescseq
.priv
) {
1762 DEFAULT(csiescseq
.arg
[0], 1);
1763 DEFAULT(csiescseq
.arg
[1], term
.row
);
1764 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1768 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1769 tcursor(CURSOR_SAVE
);
1771 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1772 tcursor(CURSOR_LOAD
);
1775 switch (csiescseq
.mode
[1]) {
1776 case 'q': /* DECSCUSR -- Set Cursor Style */
1777 if (xsetcursor(csiescseq
.arg
[0]))
1793 fprintf(stderr
, "ESC[");
1794 for (i
= 0; i
< csiescseq
.len
; i
++) {
1795 c
= csiescseq
.buf
[i
] & 0xff;
1798 } else if (c
== '\n') {
1799 fprintf(stderr
, "(\\n)");
1800 } else if (c
== '\r') {
1801 fprintf(stderr
, "(\\r)");
1802 } else if (c
== 0x1b) {
1803 fprintf(stderr
, "(\\e)");
1805 fprintf(stderr
, "(%02x)", c
);
1814 memset(&csiescseq
, 0, sizeof(csiescseq
));
1823 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1825 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1827 switch (strescseq
.type
) {
1828 case ']': /* OSC -- Operating System Command */
1834 xsettitle(strescseq
.args
[1]);
1840 dec
= base64dec(strescseq
.args
[2]);
1845 fprintf(stderr
, "erresc: invalid base64\n");
1849 case 4: /* color set */
1852 p
= strescseq
.args
[2];
1854 case 104: /* color reset, here p = NULL */
1855 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1856 if (xsetcolorname(j
, p
)) {
1857 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1860 * TODO if defaultbg color is changed, borders
1868 case 'k': /* old title set compatibility */
1869 xsettitle(strescseq
.args
[0]);
1871 case 'P': /* DCS -- Device Control String */
1872 term
.mode
|= ESC_DCS
;
1873 case '_': /* APC -- Application Program Command */
1874 case '^': /* PM -- Privacy Message */
1878 fprintf(stderr
, "erresc: unknown str ");
1886 char *p
= strescseq
.buf
;
1889 strescseq
.buf
[strescseq
.len
] = '\0';
1894 while (strescseq
.narg
< STR_ARG_SIZ
) {
1895 strescseq
.args
[strescseq
.narg
++] = p
;
1896 while ((c
= *p
) != ';' && c
!= '\0')
1910 fprintf(stderr
, "ESC%c", strescseq
.type
);
1911 for (i
= 0; i
< strescseq
.len
; i
++) {
1912 c
= strescseq
.buf
[i
] & 0xff;
1916 } else if (isprint(c
)) {
1918 } else if (c
== '\n') {
1919 fprintf(stderr
, "(\\n)");
1920 } else if (c
== '\r') {
1921 fprintf(stderr
, "(\\r)");
1922 } else if (c
== 0x1b) {
1923 fprintf(stderr
, "(\\e)");
1925 fprintf(stderr
, "(%02x)", c
);
1928 fprintf(stderr
, "ESC\\\n");
1934 memset(&strescseq
, 0, sizeof(strescseq
));
1938 sendbreak(const Arg
*arg
)
1940 if (tcsendbreak(cmdfd
, 0))
1941 perror("Error sending break");
1945 tprinter(char *s
, size_t len
)
1947 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1948 perror("Error writing to output file");
1955 iso14755(const Arg
*arg
)
1958 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1959 unsigned long utf32
;
1961 if (!(p
= popen(ISO14755CMD
, "r")))
1964 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1967 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1969 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1970 (*e
!= '\n' && *e
!= '\0'))
1973 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
1977 toggleprinter(const Arg
*arg
)
1979 term
.mode
^= MODE_PRINT
;
1983 printscreen(const Arg
*arg
)
1989 printsel(const Arg
*arg
)
1999 if ((ptr
= getsel())) {
2000 tprinter(ptr
, strlen(ptr
));
2011 bp
= &term
.line
[n
][0];
2012 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2013 if (bp
!= end
|| bp
->u
!= ' ') {
2014 for ( ;bp
<= end
; ++bp
)
2015 tprinter(buf
, utf8encode(bp
->u
, buf
));
2025 for (i
= 0; i
< term
.row
; ++i
)
2035 while (x
< term
.col
&& n
--)
2036 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2039 while (x
> 0 && n
++)
2040 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2043 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2047 tdefutf8(char ascii
)
2050 term
.mode
|= MODE_UTF8
;
2051 else if (ascii
== '@')
2052 term
.mode
&= ~MODE_UTF8
;
2056 tdeftran(char ascii
)
2058 static char cs
[] = "0B";
2059 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2062 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2063 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2065 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2074 if (c
== '8') { /* DEC screen alignment test. */
2075 for (x
= 0; x
< term
.col
; ++x
) {
2076 for (y
= 0; y
< term
.row
; ++y
)
2077 tsetchar('E', &term
.c
.attr
, x
, y
);
2083 tstrsequence(uchar c
)
2088 case 0x90: /* DCS -- Device Control String */
2090 term
.esc
|= ESC_DCS
;
2092 case 0x9f: /* APC -- Application Program Command */
2095 case 0x9e: /* PM -- Privacy Message */
2098 case 0x9d: /* OSC -- Operating System Command */
2103 term
.esc
|= ESC_STR
;
2107 tcontrolcode(uchar ascii
)
2114 tmoveto(term
.c
.x
-1, term
.c
.y
);
2117 tmoveto(0, term
.c
.y
);
2122 /* go to first col if the mode is set */
2123 tnewline(IS_SET(MODE_CRLF
));
2125 case '\a': /* BEL */
2126 if (term
.esc
& ESC_STR_END
) {
2127 /* backwards compatibility to xterm */
2133 case '\033': /* ESC */
2135 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2136 term
.esc
|= ESC_START
;
2138 case '\016': /* SO (LS1 -- Locking shift 1) */
2139 case '\017': /* SI (LS0 -- Locking shift 0) */
2140 term
.charset
= 1 - (ascii
- '\016');
2142 case '\032': /* SUB */
2143 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2144 case '\030': /* CAN */
2147 case '\005': /* ENQ (IGNORED) */
2148 case '\000': /* NUL (IGNORED) */
2149 case '\021': /* XON (IGNORED) */
2150 case '\023': /* XOFF (IGNORED) */
2151 case 0177: /* DEL (IGNORED) */
2153 case 0x80: /* TODO: PAD */
2154 case 0x81: /* TODO: HOP */
2155 case 0x82: /* TODO: BPH */
2156 case 0x83: /* TODO: NBH */
2157 case 0x84: /* TODO: IND */
2159 case 0x85: /* NEL -- Next line */
2160 tnewline(1); /* always go to first col */
2162 case 0x86: /* TODO: SSA */
2163 case 0x87: /* TODO: ESA */
2165 case 0x88: /* HTS -- Horizontal tab stop */
2166 term
.tabs
[term
.c
.x
] = 1;
2168 case 0x89: /* TODO: HTJ */
2169 case 0x8a: /* TODO: VTS */
2170 case 0x8b: /* TODO: PLD */
2171 case 0x8c: /* TODO: PLU */
2172 case 0x8d: /* TODO: RI */
2173 case 0x8e: /* TODO: SS2 */
2174 case 0x8f: /* TODO: SS3 */
2175 case 0x91: /* TODO: PU1 */
2176 case 0x92: /* TODO: PU2 */
2177 case 0x93: /* TODO: STS */
2178 case 0x94: /* TODO: CCH */
2179 case 0x95: /* TODO: MW */
2180 case 0x96: /* TODO: SPA */
2181 case 0x97: /* TODO: EPA */
2182 case 0x98: /* TODO: SOS */
2183 case 0x99: /* TODO: SGCI */
2185 case 0x9a: /* DECID -- Identify Terminal */
2186 ttywrite(vtiden
, strlen(vtiden
), 0);
2188 case 0x9b: /* TODO: CSI */
2189 case 0x9c: /* TODO: ST */
2191 case 0x90: /* DCS -- Device Control String */
2192 case 0x9d: /* OSC -- Operating System Command */
2193 case 0x9e: /* PM -- Privacy Message */
2194 case 0x9f: /* APC -- Application Program Command */
2195 tstrsequence(ascii
);
2198 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2199 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2203 * returns 1 when the sequence is finished and it hasn't to read
2204 * more characters for this sequence, otherwise 0
2207 eschandle(uchar ascii
)
2211 term
.esc
|= ESC_CSI
;
2214 term
.esc
|= ESC_TEST
;
2217 term
.esc
|= ESC_UTF8
;
2219 case 'P': /* DCS -- Device Control String */
2220 case '_': /* APC -- Application Program Command */
2221 case '^': /* PM -- Privacy Message */
2222 case ']': /* OSC -- Operating System Command */
2223 case 'k': /* old title set compatibility */
2224 tstrsequence(ascii
);
2226 case 'n': /* LS2 -- Locking shift 2 */
2227 case 'o': /* LS3 -- Locking shift 3 */
2228 term
.charset
= 2 + (ascii
- 'n');
2230 case '(': /* GZD4 -- set primary charset G0 */
2231 case ')': /* G1D4 -- set secondary charset G1 */
2232 case '*': /* G2D4 -- set tertiary charset G2 */
2233 case '+': /* G3D4 -- set quaternary charset G3 */
2234 term
.icharset
= ascii
- '(';
2235 term
.esc
|= ESC_ALTCHARSET
;
2237 case 'D': /* IND -- Linefeed */
2238 if (term
.c
.y
== term
.bot
) {
2239 tscrollup(term
.top
, 1);
2241 tmoveto(term
.c
.x
, term
.c
.y
+1);
2244 case 'E': /* NEL -- Next line */
2245 tnewline(1); /* always go to first col */
2247 case 'H': /* HTS -- Horizontal tab stop */
2248 term
.tabs
[term
.c
.x
] = 1;
2250 case 'M': /* RI -- Reverse index */
2251 if (term
.c
.y
== term
.top
) {
2252 tscrolldown(term
.top
, 1);
2254 tmoveto(term
.c
.x
, term
.c
.y
-1);
2257 case 'Z': /* DECID -- Identify Terminal */
2258 ttywrite(vtiden
, strlen(vtiden
), 0);
2260 case 'c': /* RIS -- Reset to inital state */
2265 case '=': /* DECPAM -- Application keypad */
2266 xsetmode(1, MODE_APPKEYPAD
);
2268 case '>': /* DECPNM -- Normal keypad */
2269 xsetmode(0, MODE_APPKEYPAD
);
2271 case '7': /* DECSC -- Save Cursor */
2272 tcursor(CURSOR_SAVE
);
2274 case '8': /* DECRC -- Restore Cursor */
2275 tcursor(CURSOR_LOAD
);
2277 case '\\': /* ST -- String Terminator */
2278 if (term
.esc
& ESC_STR_END
)
2282 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2283 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2297 control
= ISCONTROL(u
);
2298 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2302 len
= utf8encode(u
, c
);
2303 if (!control
&& (width
= wcwidth(u
)) == -1) {
2304 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2309 if (IS_SET(MODE_PRINT
))
2313 * STR sequence must be checked before anything else
2314 * because it uses all following characters until it
2315 * receives a ESC, a SUB, a ST or any other C1 control
2318 if (term
.esc
& ESC_STR
) {
2319 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2321 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2322 if (IS_SET(MODE_SIXEL
)) {
2323 /* TODO: render sixel */;
2324 term
.mode
&= ~MODE_SIXEL
;
2327 term
.esc
|= ESC_STR_END
;
2328 goto check_control_code
;
2332 if (IS_SET(MODE_SIXEL
)) {
2333 /* TODO: implement sixel mode */
2336 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2337 term
.mode
|= MODE_SIXEL
;
2339 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2341 * Here is a bug in terminals. If the user never sends
2342 * some code to stop the str or esc command, then st
2343 * will stop responding. But this is better than
2344 * silently failing with unknown characters. At least
2345 * then users will report back.
2347 * In the case users ever get fixed, here is the code:
2356 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2357 strescseq
.len
+= len
;
2363 * Actions of control codes must be performed as soon they arrive
2364 * because they can be embedded inside a control sequence, and
2365 * they must not cause conflicts with sequences.
2370 * control codes are not shown ever
2373 } else if (term
.esc
& ESC_START
) {
2374 if (term
.esc
& ESC_CSI
) {
2375 csiescseq
.buf
[csiescseq
.len
++] = u
;
2376 if (BETWEEN(u
, 0x40, 0x7E)
2377 || csiescseq
.len
>= \
2378 sizeof(csiescseq
.buf
)-1) {
2384 } else if (term
.esc
& ESC_UTF8
) {
2386 } else if (term
.esc
& ESC_ALTCHARSET
) {
2388 } else if (term
.esc
& ESC_TEST
) {
2393 /* sequence already finished */
2397 * All characters which form part of a sequence are not
2402 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2405 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2406 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2407 gp
->mode
|= ATTR_WRAP
;
2409 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2412 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2413 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2415 if (term
.c
.x
+width
> term
.col
) {
2417 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2420 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2423 gp
->mode
|= ATTR_WIDE
;
2424 if (term
.c
.x
+1 < term
.col
) {
2426 gp
[1].mode
= ATTR_WDUMMY
;
2429 if (term
.c
.x
+width
< term
.col
) {
2430 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2432 term
.c
.state
|= CURSOR_WRAPNEXT
;
2437 twrite(const char *buf
, int buflen
, int show_ctrl
)
2443 for (n
= 0; n
< buflen
; n
+= charsize
) {
2444 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2445 /* process a complete utf8 char */
2446 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2453 if (show_ctrl
&& ISCONTROL(u
)) {
2458 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2469 tresize(int col
, int row
)
2472 int minrow
= MIN(row
, term
.row
);
2473 int mincol
= MIN(col
, term
.col
);
2477 if (col
< 1 || row
< 1) {
2479 "tresize: error resizing to %dx%d\n", col
, row
);
2484 * slide screen to keep cursor where we expect it -
2485 * tscrollup would work here, but we can optimize to
2486 * memmove because we're freeing the earlier lines
2488 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2492 /* ensure that both src and dst are not NULL */
2494 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2495 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2497 for (i
+= row
; i
< term
.row
; i
++) {
2502 /* resize to new height */
2503 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2504 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2505 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2506 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2508 /* resize each row to new width, zero-pad if needed */
2509 for (i
= 0; i
< minrow
; i
++) {
2510 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2511 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2514 /* allocate any new rows */
2515 for (/* i = minrow */; i
< row
; i
++) {
2516 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2517 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2519 if (col
> term
.col
) {
2520 bp
= term
.tabs
+ term
.col
;
2522 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2523 while (--bp
> term
.tabs
&& !*bp
)
2525 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2528 /* update terminal size */
2531 /* reset scrolling region */
2532 tsetscroll(0, row
-1);
2533 /* make use of the LIMIT in tmoveto */
2534 tmoveto(term
.c
.x
, term
.c
.y
);
2535 /* Clearing both screens (it makes dirty all lines) */
2537 for (i
= 0; i
< 2; i
++) {
2538 if (mincol
< col
&& 0 < minrow
) {
2539 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2541 if (0 < col
&& minrow
< row
) {
2542 tclearregion(0, minrow
, col
- 1, row
- 1);
2545 tcursor(CURSOR_LOAD
);
2557 drawregion(int x1
, int y1
, int x2
, int y2
)
2560 for (y
= y1
; y
< y2
; y
++) {
2565 xdrawline(term
.line
[y
], x1
, y
, x2
);
2577 /* adjust cursor position */
2578 LIMIT(term
.ocx
, 0, term
.col
-1);
2579 LIMIT(term
.ocy
, 0, term
.row
-1);
2580 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2582 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2585 drawregion(0, 0, term
.col
, term
.row
);
2586 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2587 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2588 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;