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 */
140 static void execsh(char **);
141 static void stty(char **);
142 static void sigchld(int);
143 static void ttywriteraw(const char *, size_t);
145 static void csidump(void);
146 static void csihandle(void);
147 static void csiparse(void);
148 static void csireset(void);
149 static int eschandle(uchar
);
150 static void strdump(void);
151 static void strhandle(void);
152 static void strparse(void);
153 static void strreset(void);
155 static void tprinter(char *, size_t);
156 static void tdumpsel(void);
157 static void tdumpline(int);
158 static void tdump(void);
159 static void tclearregion(int, int, int, int);
160 static void tcursor(int);
161 static void tdeletechar(int);
162 static void tdeleteline(int);
163 static void tinsertblank(int);
164 static void tinsertblankline(int);
165 static int tlinelen(int);
166 static void tmoveto(int, int);
167 static void tmoveato(int, int);
168 static void tnewline(int);
169 static void tputtab(int);
170 static void tputc(Rune
);
171 static void treset(void);
172 static void tscrollup(int, int);
173 static void tscrolldown(int, int);
174 static void tsetattr(int *, int);
175 static void tsetchar(Rune
, Glyph
*, int, int);
176 static void tsetdirt(int, int);
177 static void tsetscroll(int, int);
178 static void tswapscreen(void);
179 static void tsetmode(int, int, int *, int);
180 static int twrite(const char *, int, int);
181 static void tfulldirt(void);
182 static void tcontrolcode(uchar
);
183 static void tdectest(char );
184 static void tdefutf8(char);
185 static int32_t tdefcolor(int *, int *, int);
186 static void tdeftran(char);
187 static void tstrsequence(uchar
);
189 static void drawregion(int, int, int, int);
191 static void selscroll(int, int);
192 static void selsnap(int *, int *, int);
194 static Rune
utf8decodebyte(char, size_t *);
195 static char utf8encodebyte(Rune
, size_t);
196 static char *utf8strchr(char *s
, Rune u
);
197 static size_t utf8validate(Rune
*, size_t);
199 static char *base64dec(const char *);
201 static ssize_t
xwrite(int, const char *, size_t);
206 int oldbutton
= 3; /* button event on startup: 3 = release */
209 static Selection sel
;
210 static CSIEscape csiescseq
;
211 static STREscape strescseq
;
214 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
215 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
216 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
217 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
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(const 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
;
414 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
417 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
424 selstart(int col
, int row
, int snap
)
427 sel
.mode
= SEL_EMPTY
;
428 sel
.type
= SEL_REGULAR
;
430 sel
.oe
.x
= sel
.ob
.x
= col
;
431 sel
.oe
.y
= sel
.ob
.y
= row
;
435 sel
.mode
= SEL_READY
;
436 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
440 selextend(int col
, int row
, int type
, int done
)
442 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
446 if (done
&& sel
.mode
== SEL_EMPTY
) {
457 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
463 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
464 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
466 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
474 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
475 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
476 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
478 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
479 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
481 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
482 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
484 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
485 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
487 /* expand selection over line breaks */
488 if (sel
.type
== SEL_RECTANGULAR
)
490 i
= tlinelen(sel
.nb
.y
);
493 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
494 sel
.ne
.x
= term
.col
- 1;
498 selected(int x
, int y
)
500 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
501 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
504 if (sel
.type
== SEL_RECTANGULAR
)
505 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
506 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
508 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
509 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
510 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
514 selsnap(int *x
, int *y
, int direction
)
516 int newx
, newy
, xt
, yt
;
517 int delim
, prevdelim
;
523 * Snap around if the word wraps around at the end or
524 * beginning of a line.
526 prevgp
= &term
.line
[*y
][*x
];
527 prevdelim
= ISDELIM(prevgp
->u
);
529 newx
= *x
+ direction
;
531 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
533 newx
= (newx
+ term
.col
) % term
.col
;
534 if (!BETWEEN(newy
, 0, term
.row
- 1))
540 yt
= newy
, xt
= newx
;
541 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
545 if (newx
>= tlinelen(newy
))
548 gp
= &term
.line
[newy
][newx
];
549 delim
= ISDELIM(gp
->u
);
550 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
551 || (delim
&& gp
->u
!= prevgp
->u
)))
562 * Snap around if the the previous line or the current one
563 * has set ATTR_WRAP at its end. Then the whole next or
564 * previous line will be selected.
566 *x
= (direction
< 0) ? 0 : term
.col
- 1;
568 for (; *y
> 0; *y
+= direction
) {
569 if (!(term
.line
[*y
-1][term
.col
-1].mode
574 } else if (direction
> 0) {
575 for (; *y
< term
.row
-1; *y
+= direction
) {
576 if (!(term
.line
[*y
][term
.col
-1].mode
590 int y
, bufsize
, lastx
, linelen
;
596 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
597 ptr
= str
= xmalloc(bufsize
);
599 /* append every set & selected glyph to the selection */
600 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
601 if ((linelen
= tlinelen(y
)) == 0) {
606 if (sel
.type
== SEL_RECTANGULAR
) {
607 gp
= &term
.line
[y
][sel
.nb
.x
];
610 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
611 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
613 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
614 while (last
>= gp
&& last
->u
== ' ')
617 for ( ; gp
<= last
; ++gp
) {
618 if (gp
->mode
& ATTR_WDUMMY
)
621 ptr
+= utf8encode(gp
->u
, ptr
);
625 * Copy and pasting of line endings is inconsistent
626 * in the inconsistent terminal and GUI world.
627 * The best solution seems like to produce '\n' when
628 * something is copied from st and convert '\n' to
629 * '\r', when something to be pasted is received by
631 * FIXME: Fix the computer world.
633 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
647 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
651 die(const char *errstr
, ...)
655 va_start(ap
, errstr
);
656 vfprintf(stderr
, errstr
, ap
);
665 const struct passwd
*pw
;
668 if ((pw
= getpwuid(getuid())) == NULL
) {
670 die("getpwuid:%s\n", strerror(errno
));
672 die("who are you?\n");
675 if ((sh
= getenv("SHELL")) == NULL
)
676 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
684 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
689 setenv("LOGNAME", pw
->pw_name
, 1);
690 setenv("USER", pw
->pw_name
, 1);
691 setenv("SHELL", sh
, 1);
692 setenv("HOME", pw
->pw_dir
, 1);
693 setenv("TERM", termname
, 1);
695 signal(SIGCHLD
, SIG_DFL
);
696 signal(SIGHUP
, SIG_DFL
);
697 signal(SIGINT
, SIG_DFL
);
698 signal(SIGQUIT
, SIG_DFL
);
699 signal(SIGTERM
, SIG_DFL
);
700 signal(SIGALRM
, SIG_DFL
);
712 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
713 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
718 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
719 die("child finished with error '%d'\n", stat
);
727 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
730 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
731 die("incorrect stty parameters\n");
732 memcpy(cmd
, stty_args
, n
);
734 siz
= sizeof(cmd
) - n
;
735 for (p
= args
; p
&& (s
= *p
); ++p
) {
736 if ((n
= strlen(s
)) > siz
-1)
737 die("stty parameter length too long\n");
744 if (system(cmd
) != 0)
745 perror("Couldn't call stty");
749 ttynew(char *line
, char *out
, char **args
)
754 term
.mode
|= MODE_PRINT
;
755 iofd
= (!strcmp(out
, "-")) ?
756 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
758 fprintf(stderr
, "Error opening %s:%s\n",
759 out
, strerror(errno
));
764 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
765 die("open line failed: %s\n", strerror(errno
));
771 /* seems to work fine on linux, openbsd and freebsd */
772 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
773 die("openpty failed: %s\n", strerror(errno
));
775 switch (pid
= fork()) {
777 die("fork failed\n");
781 setsid(); /* create a new process group */
785 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
786 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
794 signal(SIGCHLD
, sigchld
);
802 static char buf
[BUFSIZ
];
803 static int buflen
= 0;
807 /* append read bytes to unprocessed bytes */
808 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
809 die("Couldn't read from shell: %s\n", strerror(errno
));
812 written
= twrite(buf
, buflen
, 0);
814 /* keep any uncomplete utf8 char for the next call */
816 memmove(buf
, buf
+ written
, buflen
);
822 ttywrite(const char *s
, size_t n
, int may_echo
)
826 if (may_echo
&& IS_SET(MODE_ECHO
))
829 if (!IS_SET(MODE_CRLF
)) {
834 /* This is similar to how the kernel handles ONLCR for ttys */
838 ttywriteraw("\r\n", 2);
840 next
= memchr(s
, '\r', n
);
841 DEFAULT(next
, s
+ n
);
842 ttywriteraw(s
, next
- s
);
850 ttywriteraw(const char *s
, size_t n
)
857 * Remember that we are using a pty, which might be a modem line.
858 * Writing too much will clog the line. That's why we are doing this
860 * FIXME: Migrate the world to Plan 9.
868 /* Check if we can write. */
869 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
872 die("select failed: %s\n", strerror(errno
));
874 if (FD_ISSET(cmdfd
, &wfd
)) {
876 * Only write the bytes written by ttywrite() or the
877 * default of 256. This seems to be a reasonable value
878 * for a serial line. Bigger values might clog the I/O.
880 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
884 * We weren't able to write out everything.
885 * This means the buffer is getting full
893 /* All bytes have been written. */
897 if (FD_ISSET(cmdfd
, &rfd
))
903 die("write error on tty: %s\n", strerror(errno
));
907 ttyresize(int tw
, int th
)
915 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
916 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
924 for (i
= 0; i
< term
.row
-1; i
++) {
925 for (j
= 0; j
< term
.col
-1; j
++) {
926 if (term
.line
[i
][j
].mode
& attr
)
935 tsetdirt(int top
, int bot
)
939 LIMIT(top
, 0, term
.row
-1);
940 LIMIT(bot
, 0, term
.row
-1);
942 for (i
= top
; i
<= bot
; i
++)
947 tsetdirtattr(int attr
)
951 for (i
= 0; i
< term
.row
-1; i
++) {
952 for (j
= 0; j
< term
.col
-1; j
++) {
953 if (term
.line
[i
][j
].mode
& attr
) {
964 tsetdirt(0, term
.row
-1);
971 int alt
= IS_SET(MODE_ALTSCREEN
);
973 if (mode
== CURSOR_SAVE
) {
975 } else if (mode
== CURSOR_LOAD
) {
977 tmoveto(c
[alt
].x
, c
[alt
].y
);
990 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
992 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
993 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
996 term
.bot
= term
.row
- 1;
997 term
.mode
= MODE_WRAP
|MODE_UTF8
;
998 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
1001 for (i
= 0; i
< 2; i
++) {
1003 tcursor(CURSOR_SAVE
);
1004 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1010 tnew(int col
, int row
)
1012 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
1020 Line
*tmp
= term
.line
;
1022 term
.line
= term
.alt
;
1024 term
.mode
^= MODE_ALTSCREEN
;
1029 tscrolldown(int orig
, int n
)
1034 LIMIT(n
, 0, term
.bot
-orig
+1);
1036 tsetdirt(orig
, term
.bot
-n
);
1037 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
1039 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
1040 temp
= term
.line
[i
];
1041 term
.line
[i
] = term
.line
[i
-n
];
1042 term
.line
[i
-n
] = temp
;
1049 tscrollup(int orig
, int n
)
1054 LIMIT(n
, 0, term
.bot
-orig
+1);
1056 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
1057 tsetdirt(orig
+n
, term
.bot
);
1059 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1060 temp
= term
.line
[i
];
1061 term
.line
[i
] = term
.line
[i
+n
];
1062 term
.line
[i
+n
] = temp
;
1065 selscroll(orig
, -n
);
1069 selscroll(int orig
, int n
)
1074 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1075 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1079 if (sel
.type
== SEL_RECTANGULAR
) {
1080 if (sel
.ob
.y
< term
.top
)
1081 sel
.ob
.y
= term
.top
;
1082 if (sel
.oe
.y
> term
.bot
)
1083 sel
.oe
.y
= term
.bot
;
1085 if (sel
.ob
.y
< term
.top
) {
1086 sel
.ob
.y
= term
.top
;
1089 if (sel
.oe
.y
> term
.bot
) {
1090 sel
.oe
.y
= term
.bot
;
1091 sel
.oe
.x
= term
.col
;
1099 tnewline(int first_col
)
1103 if (y
== term
.bot
) {
1104 tscrollup(term
.top
, 1);
1108 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1114 char *p
= csiescseq
.buf
, *np
;
1123 csiescseq
.buf
[csiescseq
.len
] = '\0';
1124 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1126 v
= strtol(p
, &np
, 10);
1129 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1131 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1133 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1137 csiescseq
.mode
[0] = *p
++;
1138 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1141 /* for absolute user moves, when decom is set */
1143 tmoveato(int x
, int y
)
1145 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1149 tmoveto(int x
, int y
)
1153 if (term
.c
.state
& CURSOR_ORIGIN
) {
1158 maxy
= term
.row
- 1;
1160 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1161 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1162 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1166 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1168 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1169 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1170 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1171 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1172 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1173 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1174 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1175 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1176 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1180 * The table is proudly stolen from rxvt.
1182 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1183 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1184 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1186 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1187 if (x
+1 < term
.col
) {
1188 term
.line
[y
][x
+1].u
= ' ';
1189 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1191 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1192 term
.line
[y
][x
-1].u
= ' ';
1193 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1197 term
.line
[y
][x
] = *attr
;
1198 term
.line
[y
][x
].u
= u
;
1202 tclearregion(int x1
, int y1
, int x2
, int y2
)
1208 temp
= x1
, x1
= x2
, x2
= temp
;
1210 temp
= y1
, y1
= y2
, y2
= temp
;
1212 LIMIT(x1
, 0, term
.col
-1);
1213 LIMIT(x2
, 0, term
.col
-1);
1214 LIMIT(y1
, 0, term
.row
-1);
1215 LIMIT(y2
, 0, term
.row
-1);
1217 for (y
= y1
; y
<= y2
; y
++) {
1219 for (x
= x1
; x
<= x2
; x
++) {
1220 gp
= &term
.line
[y
][x
];
1223 gp
->fg
= term
.c
.attr
.fg
;
1224 gp
->bg
= term
.c
.attr
.bg
;
1237 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1241 size
= term
.col
- src
;
1242 line
= term
.line
[term
.c
.y
];
1244 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1245 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1254 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1258 size
= term
.col
- dst
;
1259 line
= term
.line
[term
.c
.y
];
1261 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1262 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1266 tinsertblankline(int n
)
1268 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1269 tscrolldown(term
.c
.y
, n
);
1275 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1276 tscrollup(term
.c
.y
, n
);
1280 tdefcolor(int *attr
, int *npar
, int l
)
1285 switch (attr
[*npar
+ 1]) {
1286 case 2: /* direct color in RGB space */
1287 if (*npar
+ 4 >= l
) {
1289 "erresc(38): Incorrect number of parameters (%d)\n",
1293 r
= attr
[*npar
+ 2];
1294 g
= attr
[*npar
+ 3];
1295 b
= attr
[*npar
+ 4];
1297 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1298 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1301 idx
= TRUECOLOR(r
, g
, b
);
1303 case 5: /* indexed color */
1304 if (*npar
+ 2 >= l
) {
1306 "erresc(38): Incorrect number of parameters (%d)\n",
1311 if (!BETWEEN(attr
[*npar
], 0, 255))
1312 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1316 case 0: /* implemented defined (only foreground) */
1317 case 1: /* transparent */
1318 case 3: /* direct color in CMY space */
1319 case 4: /* direct color in CMYK space */
1322 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1330 tsetattr(int *attr
, int l
)
1335 for (i
= 0; i
< l
; i
++) {
1338 term
.c
.attr
.mode
&= ~(
1347 term
.c
.attr
.fg
= defaultfg
;
1348 term
.c
.attr
.bg
= defaultbg
;
1351 term
.c
.attr
.mode
|= ATTR_BOLD
;
1354 term
.c
.attr
.mode
|= ATTR_FAINT
;
1357 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1360 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1362 case 5: /* slow blink */
1364 case 6: /* rapid blink */
1365 term
.c
.attr
.mode
|= ATTR_BLINK
;
1368 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1371 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1374 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1377 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1380 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1383 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1386 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1389 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1392 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1395 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1398 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1399 term
.c
.attr
.fg
= idx
;
1402 term
.c
.attr
.fg
= defaultfg
;
1405 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1406 term
.c
.attr
.bg
= idx
;
1409 term
.c
.attr
.bg
= defaultbg
;
1412 if (BETWEEN(attr
[i
], 30, 37)) {
1413 term
.c
.attr
.fg
= attr
[i
] - 30;
1414 } else if (BETWEEN(attr
[i
], 40, 47)) {
1415 term
.c
.attr
.bg
= attr
[i
] - 40;
1416 } else if (BETWEEN(attr
[i
], 90, 97)) {
1417 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1418 } else if (BETWEEN(attr
[i
], 100, 107)) {
1419 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1422 "erresc(default): gfx attr %d unknown\n",
1423 attr
[i
]), csidump();
1431 tsetscroll(int t
, int b
)
1435 LIMIT(t
, 0, term
.row
-1);
1436 LIMIT(b
, 0, term
.row
-1);
1447 tsetmode(int priv
, int set
, int *args
, int narg
)
1451 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1454 case 1: /* DECCKM -- Cursor key */
1455 xsetmode(set
, MODE_APPCURSOR
);
1457 case 5: /* DECSCNM -- Reverse video */
1458 xsetmode(set
, MODE_REVERSE
);
1460 case 6: /* DECOM -- Origin */
1461 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1464 case 7: /* DECAWM -- Auto wrap */
1465 MODBIT(term
.mode
, set
, MODE_WRAP
);
1467 case 0: /* Error (IGNORED) */
1468 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1469 case 3: /* DECCOLM -- Column (IGNORED) */
1470 case 4: /* DECSCLM -- Scroll (IGNORED) */
1471 case 8: /* DECARM -- Auto repeat (IGNORED) */
1472 case 18: /* DECPFF -- Printer feed (IGNORED) */
1473 case 19: /* DECPEX -- Printer extent (IGNORED) */
1474 case 42: /* DECNRCM -- National characters (IGNORED) */
1475 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1477 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1478 xsetmode(!set
, MODE_HIDE
);
1480 case 9: /* X10 mouse compatibility mode */
1481 xsetpointermotion(0);
1482 xsetmode(0, MODE_MOUSE
);
1483 xsetmode(set
, MODE_MOUSEX10
);
1485 case 1000: /* 1000: report button press */
1486 xsetpointermotion(0);
1487 xsetmode(0, MODE_MOUSE
);
1488 xsetmode(set
, MODE_MOUSEBTN
);
1490 case 1002: /* 1002: report motion on button press */
1491 xsetpointermotion(0);
1492 xsetmode(0, MODE_MOUSE
);
1493 xsetmode(set
, MODE_MOUSEMOTION
);
1495 case 1003: /* 1003: enable all mouse motions */
1496 xsetpointermotion(set
);
1497 xsetmode(0, MODE_MOUSE
);
1498 xsetmode(set
, MODE_MOUSEMANY
);
1500 case 1004: /* 1004: send focus events to tty */
1501 xsetmode(set
, MODE_FOCUS
);
1503 case 1006: /* 1006: extended reporting mode */
1504 xsetmode(set
, MODE_MOUSESGR
);
1507 xsetmode(set
, MODE_8BIT
);
1509 case 1049: /* swap screen & set/restore cursor as xterm */
1510 if (!allowaltscreen
)
1512 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1514 case 47: /* swap screen */
1516 if (!allowaltscreen
)
1518 alt
= IS_SET(MODE_ALTSCREEN
);
1520 tclearregion(0, 0, term
.col
-1,
1523 if (set
^ alt
) /* set is always 1 or 0 */
1529 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1531 case 2004: /* 2004: bracketed paste mode */
1532 xsetmode(set
, MODE_BRCKTPASTE
);
1534 /* Not implemented mouse modes. See comments there. */
1535 case 1001: /* mouse highlight mode; can hang the
1536 terminal by design when implemented. */
1537 case 1005: /* UTF-8 mouse mode; will confuse
1538 applications not supporting UTF-8
1540 case 1015: /* urxvt mangled mouse mode; incompatible
1541 and can be mistaken for other control
1545 "erresc: unknown private set/reset mode %d\n",
1551 case 0: /* Error (IGNORED) */
1554 xsetmode(set
, MODE_KBDLOCK
);
1556 case 4: /* IRM -- Insertion-replacement */
1557 MODBIT(term
.mode
, set
, MODE_INSERT
);
1559 case 12: /* SRM -- Send/Receive */
1560 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1562 case 20: /* LNM -- Linefeed/new line */
1563 MODBIT(term
.mode
, set
, MODE_CRLF
);
1567 "erresc: unknown set/reset mode %d\n",
1581 switch (csiescseq
.mode
[0]) {
1584 fprintf(stderr
, "erresc: unknown csi ");
1588 case '@': /* ICH -- Insert <n> blank char */
1589 DEFAULT(csiescseq
.arg
[0], 1);
1590 tinsertblank(csiescseq
.arg
[0]);
1592 case 'A': /* CUU -- Cursor <n> Up */
1593 DEFAULT(csiescseq
.arg
[0], 1);
1594 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1596 case 'B': /* CUD -- Cursor <n> Down */
1597 case 'e': /* VPR --Cursor <n> Down */
1598 DEFAULT(csiescseq
.arg
[0], 1);
1599 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1601 case 'i': /* MC -- Media Copy */
1602 switch (csiescseq
.arg
[0]) {
1607 tdumpline(term
.c
.y
);
1613 term
.mode
&= ~MODE_PRINT
;
1616 term
.mode
|= MODE_PRINT
;
1620 case 'c': /* DA -- Device Attributes */
1621 if (csiescseq
.arg
[0] == 0)
1622 ttywrite(vtiden
, strlen(vtiden
), 0);
1624 case 'C': /* CUF -- Cursor <n> Forward */
1625 case 'a': /* HPR -- Cursor <n> Forward */
1626 DEFAULT(csiescseq
.arg
[0], 1);
1627 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1629 case 'D': /* CUB -- Cursor <n> Backward */
1630 DEFAULT(csiescseq
.arg
[0], 1);
1631 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1633 case 'E': /* CNL -- Cursor <n> Down and first col */
1634 DEFAULT(csiescseq
.arg
[0], 1);
1635 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1637 case 'F': /* CPL -- Cursor <n> Up and first col */
1638 DEFAULT(csiescseq
.arg
[0], 1);
1639 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1641 case 'g': /* TBC -- Tabulation clear */
1642 switch (csiescseq
.arg
[0]) {
1643 case 0: /* clear current tab stop */
1644 term
.tabs
[term
.c
.x
] = 0;
1646 case 3: /* clear all the tabs */
1647 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1653 case 'G': /* CHA -- Move to <col> */
1655 DEFAULT(csiescseq
.arg
[0], 1);
1656 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1658 case 'H': /* CUP -- Move to <row> <col> */
1660 DEFAULT(csiescseq
.arg
[0], 1);
1661 DEFAULT(csiescseq
.arg
[1], 1);
1662 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1664 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1665 DEFAULT(csiescseq
.arg
[0], 1);
1666 tputtab(csiescseq
.arg
[0]);
1668 case 'J': /* ED -- Clear screen */
1670 switch (csiescseq
.arg
[0]) {
1672 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1673 if (term
.c
.y
< term
.row
-1) {
1674 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1680 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1681 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1684 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1690 case 'K': /* EL -- Clear line */
1691 switch (csiescseq
.arg
[0]) {
1693 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1697 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1700 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1704 case 'S': /* SU -- Scroll <n> line up */
1705 DEFAULT(csiescseq
.arg
[0], 1);
1706 tscrollup(term
.top
, csiescseq
.arg
[0]);
1708 case 'T': /* SD -- Scroll <n> line down */
1709 DEFAULT(csiescseq
.arg
[0], 1);
1710 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1712 case 'L': /* IL -- Insert <n> blank lines */
1713 DEFAULT(csiescseq
.arg
[0], 1);
1714 tinsertblankline(csiescseq
.arg
[0]);
1716 case 'l': /* RM -- Reset Mode */
1717 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1719 case 'M': /* DL -- Delete <n> lines */
1720 DEFAULT(csiescseq
.arg
[0], 1);
1721 tdeleteline(csiescseq
.arg
[0]);
1723 case 'X': /* ECH -- Erase <n> char */
1724 DEFAULT(csiescseq
.arg
[0], 1);
1725 tclearregion(term
.c
.x
, term
.c
.y
,
1726 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1728 case 'P': /* DCH -- Delete <n> char */
1729 DEFAULT(csiescseq
.arg
[0], 1);
1730 tdeletechar(csiescseq
.arg
[0]);
1732 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1733 DEFAULT(csiescseq
.arg
[0], 1);
1734 tputtab(-csiescseq
.arg
[0]);
1736 case 'd': /* VPA -- Move to <row> */
1737 DEFAULT(csiescseq
.arg
[0], 1);
1738 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1740 case 'h': /* SM -- Set terminal mode */
1741 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1743 case 'm': /* SGR -- Terminal attribute (color) */
1744 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1746 case 'n': /* DSR – Device Status Report (cursor position) */
1747 if (csiescseq
.arg
[0] == 6) {
1748 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1749 term
.c
.y
+1, term
.c
.x
+1);
1750 ttywrite(buf
, len
, 0);
1753 case 'r': /* DECSTBM -- Set Scrolling Region */
1754 if (csiescseq
.priv
) {
1757 DEFAULT(csiescseq
.arg
[0], 1);
1758 DEFAULT(csiescseq
.arg
[1], term
.row
);
1759 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1763 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1764 tcursor(CURSOR_SAVE
);
1766 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1767 tcursor(CURSOR_LOAD
);
1770 switch (csiescseq
.mode
[1]) {
1771 case 'q': /* DECSCUSR -- Set Cursor Style */
1772 if (xsetcursor(csiescseq
.arg
[0]))
1788 fprintf(stderr
, "ESC[");
1789 for (i
= 0; i
< csiescseq
.len
; i
++) {
1790 c
= csiescseq
.buf
[i
] & 0xff;
1793 } else if (c
== '\n') {
1794 fprintf(stderr
, "(\\n)");
1795 } else if (c
== '\r') {
1796 fprintf(stderr
, "(\\r)");
1797 } else if (c
== 0x1b) {
1798 fprintf(stderr
, "(\\e)");
1800 fprintf(stderr
, "(%02x)", c
);
1809 memset(&csiescseq
, 0, sizeof(csiescseq
));
1818 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1820 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1822 switch (strescseq
.type
) {
1823 case ']': /* OSC -- Operating System Command */
1829 xsettitle(strescseq
.args
[1]);
1835 dec
= base64dec(strescseq
.args
[2]);
1840 fprintf(stderr
, "erresc: invalid base64\n");
1844 case 4: /* color set */
1847 p
= strescseq
.args
[2];
1849 case 104: /* color reset, here p = NULL */
1850 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1851 if (xsetcolorname(j
, p
)) {
1852 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1855 * TODO if defaultbg color is changed, borders
1863 case 'k': /* old title set compatibility */
1864 xsettitle(strescseq
.args
[0]);
1866 case 'P': /* DCS -- Device Control String */
1867 term
.mode
|= ESC_DCS
;
1868 case '_': /* APC -- Application Program Command */
1869 case '^': /* PM -- Privacy Message */
1873 fprintf(stderr
, "erresc: unknown str ");
1881 char *p
= strescseq
.buf
;
1884 strescseq
.buf
[strescseq
.len
] = '\0';
1889 while (strescseq
.narg
< STR_ARG_SIZ
) {
1890 strescseq
.args
[strescseq
.narg
++] = p
;
1891 while ((c
= *p
) != ';' && c
!= '\0')
1905 fprintf(stderr
, "ESC%c", strescseq
.type
);
1906 for (i
= 0; i
< strescseq
.len
; i
++) {
1907 c
= strescseq
.buf
[i
] & 0xff;
1911 } else if (isprint(c
)) {
1913 } else if (c
== '\n') {
1914 fprintf(stderr
, "(\\n)");
1915 } else if (c
== '\r') {
1916 fprintf(stderr
, "(\\r)");
1917 } else if (c
== 0x1b) {
1918 fprintf(stderr
, "(\\e)");
1920 fprintf(stderr
, "(%02x)", c
);
1923 fprintf(stderr
, "ESC\\\n");
1929 memset(&strescseq
, 0, sizeof(strescseq
));
1933 sendbreak(const Arg
*arg
)
1935 if (tcsendbreak(cmdfd
, 0))
1936 perror("Error sending break");
1940 tprinter(char *s
, size_t len
)
1942 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1943 perror("Error writing to output file");
1950 iso14755(const Arg
*arg
)
1953 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1954 unsigned long utf32
;
1956 if (!(p
= popen(ISO14755CMD
, "r")))
1959 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1962 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1964 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1965 (*e
!= '\n' && *e
!= '\0'))
1968 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
1972 toggleprinter(const Arg
*arg
)
1974 term
.mode
^= MODE_PRINT
;
1978 printscreen(const Arg
*arg
)
1984 printsel(const Arg
*arg
)
1994 if ((ptr
= getsel())) {
1995 tprinter(ptr
, strlen(ptr
));
2006 bp
= &term
.line
[n
][0];
2007 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
2008 if (bp
!= end
|| bp
->u
!= ' ') {
2009 for ( ;bp
<= end
; ++bp
)
2010 tprinter(buf
, utf8encode(bp
->u
, buf
));
2020 for (i
= 0; i
< term
.row
; ++i
)
2030 while (x
< term
.col
&& n
--)
2031 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
2034 while (x
> 0 && n
++)
2035 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
2038 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
2042 tdefutf8(char ascii
)
2045 term
.mode
|= MODE_UTF8
;
2046 else if (ascii
== '@')
2047 term
.mode
&= ~MODE_UTF8
;
2051 tdeftran(char ascii
)
2053 static char cs
[] = "0B";
2054 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2057 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2058 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2060 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2069 if (c
== '8') { /* DEC screen alignment test. */
2070 for (x
= 0; x
< term
.col
; ++x
) {
2071 for (y
= 0; y
< term
.row
; ++y
)
2072 tsetchar('E', &term
.c
.attr
, x
, y
);
2078 tstrsequence(uchar c
)
2083 case 0x90: /* DCS -- Device Control String */
2085 term
.esc
|= ESC_DCS
;
2087 case 0x9f: /* APC -- Application Program Command */
2090 case 0x9e: /* PM -- Privacy Message */
2093 case 0x9d: /* OSC -- Operating System Command */
2098 term
.esc
|= ESC_STR
;
2102 tcontrolcode(uchar ascii
)
2109 tmoveto(term
.c
.x
-1, term
.c
.y
);
2112 tmoveto(0, term
.c
.y
);
2117 /* go to first col if the mode is set */
2118 tnewline(IS_SET(MODE_CRLF
));
2120 case '\a': /* BEL */
2121 if (term
.esc
& ESC_STR_END
) {
2122 /* backwards compatibility to xterm */
2128 case '\033': /* ESC */
2130 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2131 term
.esc
|= ESC_START
;
2133 case '\016': /* SO (LS1 -- Locking shift 1) */
2134 case '\017': /* SI (LS0 -- Locking shift 0) */
2135 term
.charset
= 1 - (ascii
- '\016');
2137 case '\032': /* SUB */
2138 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2139 case '\030': /* CAN */
2142 case '\005': /* ENQ (IGNORED) */
2143 case '\000': /* NUL (IGNORED) */
2144 case '\021': /* XON (IGNORED) */
2145 case '\023': /* XOFF (IGNORED) */
2146 case 0177: /* DEL (IGNORED) */
2148 case 0x80: /* TODO: PAD */
2149 case 0x81: /* TODO: HOP */
2150 case 0x82: /* TODO: BPH */
2151 case 0x83: /* TODO: NBH */
2152 case 0x84: /* TODO: IND */
2154 case 0x85: /* NEL -- Next line */
2155 tnewline(1); /* always go to first col */
2157 case 0x86: /* TODO: SSA */
2158 case 0x87: /* TODO: ESA */
2160 case 0x88: /* HTS -- Horizontal tab stop */
2161 term
.tabs
[term
.c
.x
] = 1;
2163 case 0x89: /* TODO: HTJ */
2164 case 0x8a: /* TODO: VTS */
2165 case 0x8b: /* TODO: PLD */
2166 case 0x8c: /* TODO: PLU */
2167 case 0x8d: /* TODO: RI */
2168 case 0x8e: /* TODO: SS2 */
2169 case 0x8f: /* TODO: SS3 */
2170 case 0x91: /* TODO: PU1 */
2171 case 0x92: /* TODO: PU2 */
2172 case 0x93: /* TODO: STS */
2173 case 0x94: /* TODO: CCH */
2174 case 0x95: /* TODO: MW */
2175 case 0x96: /* TODO: SPA */
2176 case 0x97: /* TODO: EPA */
2177 case 0x98: /* TODO: SOS */
2178 case 0x99: /* TODO: SGCI */
2180 case 0x9a: /* DECID -- Identify Terminal */
2181 ttywrite(vtiden
, strlen(vtiden
), 0);
2183 case 0x9b: /* TODO: CSI */
2184 case 0x9c: /* TODO: ST */
2186 case 0x90: /* DCS -- Device Control String */
2187 case 0x9d: /* OSC -- Operating System Command */
2188 case 0x9e: /* PM -- Privacy Message */
2189 case 0x9f: /* APC -- Application Program Command */
2190 tstrsequence(ascii
);
2193 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2194 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2198 * returns 1 when the sequence is finished and it hasn't to read
2199 * more characters for this sequence, otherwise 0
2202 eschandle(uchar ascii
)
2206 term
.esc
|= ESC_CSI
;
2209 term
.esc
|= ESC_TEST
;
2212 term
.esc
|= ESC_UTF8
;
2214 case 'P': /* DCS -- Device Control String */
2215 case '_': /* APC -- Application Program Command */
2216 case '^': /* PM -- Privacy Message */
2217 case ']': /* OSC -- Operating System Command */
2218 case 'k': /* old title set compatibility */
2219 tstrsequence(ascii
);
2221 case 'n': /* LS2 -- Locking shift 2 */
2222 case 'o': /* LS3 -- Locking shift 3 */
2223 term
.charset
= 2 + (ascii
- 'n');
2225 case '(': /* GZD4 -- set primary charset G0 */
2226 case ')': /* G1D4 -- set secondary charset G1 */
2227 case '*': /* G2D4 -- set tertiary charset G2 */
2228 case '+': /* G3D4 -- set quaternary charset G3 */
2229 term
.icharset
= ascii
- '(';
2230 term
.esc
|= ESC_ALTCHARSET
;
2232 case 'D': /* IND -- Linefeed */
2233 if (term
.c
.y
== term
.bot
) {
2234 tscrollup(term
.top
, 1);
2236 tmoveto(term
.c
.x
, term
.c
.y
+1);
2239 case 'E': /* NEL -- Next line */
2240 tnewline(1); /* always go to first col */
2242 case 'H': /* HTS -- Horizontal tab stop */
2243 term
.tabs
[term
.c
.x
] = 1;
2245 case 'M': /* RI -- Reverse index */
2246 if (term
.c
.y
== term
.top
) {
2247 tscrolldown(term
.top
, 1);
2249 tmoveto(term
.c
.x
, term
.c
.y
-1);
2252 case 'Z': /* DECID -- Identify Terminal */
2253 ttywrite(vtiden
, strlen(vtiden
), 0);
2255 case 'c': /* RIS -- Reset to inital state */
2260 case '=': /* DECPAM -- Application keypad */
2261 xsetmode(1, MODE_APPKEYPAD
);
2263 case '>': /* DECPNM -- Normal keypad */
2264 xsetmode(0, MODE_APPKEYPAD
);
2266 case '7': /* DECSC -- Save Cursor */
2267 tcursor(CURSOR_SAVE
);
2269 case '8': /* DECRC -- Restore Cursor */
2270 tcursor(CURSOR_LOAD
);
2272 case '\\': /* ST -- String Terminator */
2273 if (term
.esc
& ESC_STR_END
)
2277 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2278 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2292 control
= ISCONTROL(u
);
2293 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2297 len
= utf8encode(u
, c
);
2298 if (!control
&& (width
= wcwidth(u
)) == -1) {
2299 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2304 if (IS_SET(MODE_PRINT
))
2308 * STR sequence must be checked before anything else
2309 * because it uses all following characters until it
2310 * receives a ESC, a SUB, a ST or any other C1 control
2313 if (term
.esc
& ESC_STR
) {
2314 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2316 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2317 if (IS_SET(MODE_SIXEL
)) {
2318 /* TODO: render sixel */;
2319 term
.mode
&= ~MODE_SIXEL
;
2322 term
.esc
|= ESC_STR_END
;
2323 goto check_control_code
;
2327 if (IS_SET(MODE_SIXEL
)) {
2328 /* TODO: implement sixel mode */
2331 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2332 term
.mode
|= MODE_SIXEL
;
2334 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2336 * Here is a bug in terminals. If the user never sends
2337 * some code to stop the str or esc command, then st
2338 * will stop responding. But this is better than
2339 * silently failing with unknown characters. At least
2340 * then users will report back.
2342 * In the case users ever get fixed, here is the code:
2351 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2352 strescseq
.len
+= len
;
2358 * Actions of control codes must be performed as soon they arrive
2359 * because they can be embedded inside a control sequence, and
2360 * they must not cause conflicts with sequences.
2365 * control codes are not shown ever
2368 } else if (term
.esc
& ESC_START
) {
2369 if (term
.esc
& ESC_CSI
) {
2370 csiescseq
.buf
[csiescseq
.len
++] = u
;
2371 if (BETWEEN(u
, 0x40, 0x7E)
2372 || csiescseq
.len
>= \
2373 sizeof(csiescseq
.buf
)-1) {
2379 } else if (term
.esc
& ESC_UTF8
) {
2381 } else if (term
.esc
& ESC_ALTCHARSET
) {
2383 } else if (term
.esc
& ESC_TEST
) {
2388 /* sequence already finished */
2392 * All characters which form part of a sequence are not
2397 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2400 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2401 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2402 gp
->mode
|= ATTR_WRAP
;
2404 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2407 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2408 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2410 if (term
.c
.x
+width
> term
.col
) {
2412 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2415 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2418 gp
->mode
|= ATTR_WIDE
;
2419 if (term
.c
.x
+1 < term
.col
) {
2421 gp
[1].mode
= ATTR_WDUMMY
;
2424 if (term
.c
.x
+width
< term
.col
) {
2425 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2427 term
.c
.state
|= CURSOR_WRAPNEXT
;
2432 twrite(const char *buf
, int buflen
, int show_ctrl
)
2438 for (n
= 0; n
< buflen
; n
+= charsize
) {
2439 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2440 /* process a complete utf8 char */
2441 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2448 if (show_ctrl
&& ISCONTROL(u
)) {
2453 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2464 tresize(int col
, int row
)
2467 int minrow
= MIN(row
, term
.row
);
2468 int mincol
= MIN(col
, term
.col
);
2472 if (col
< 1 || row
< 1) {
2474 "tresize: error resizing to %dx%d\n", col
, row
);
2479 * slide screen to keep cursor where we expect it -
2480 * tscrollup would work here, but we can optimize to
2481 * memmove because we're freeing the earlier lines
2483 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2487 /* ensure that both src and dst are not NULL */
2489 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2490 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2492 for (i
+= row
; i
< term
.row
; i
++) {
2497 /* resize to new height */
2498 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2499 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2500 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2501 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2503 /* resize each row to new width, zero-pad if needed */
2504 for (i
= 0; i
< minrow
; i
++) {
2505 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2506 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2509 /* allocate any new rows */
2510 for (/* i = minrow */; i
< row
; i
++) {
2511 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2512 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2514 if (col
> term
.col
) {
2515 bp
= term
.tabs
+ term
.col
;
2517 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2518 while (--bp
> term
.tabs
&& !*bp
)
2520 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2523 /* update terminal size */
2526 /* reset scrolling region */
2527 tsetscroll(0, row
-1);
2528 /* make use of the LIMIT in tmoveto */
2529 tmoveto(term
.c
.x
, term
.c
.y
);
2530 /* Clearing both screens (it makes dirty all lines) */
2532 for (i
= 0; i
< 2; i
++) {
2533 if (mincol
< col
&& 0 < minrow
) {
2534 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2536 if (0 < col
&& minrow
< row
) {
2537 tclearregion(0, minrow
, col
- 1, row
- 1);
2540 tcursor(CURSOR_LOAD
);
2552 drawregion(int x1
, int y1
, int x2
, int y2
)
2555 for (y
= y1
; y
< y2
; y
++) {
2560 xdrawline(term
.line
[y
], x1
, y
, x2
);
2572 /* adjust cursor position */
2573 LIMIT(term
.ocx
, 0, term
.col
-1);
2574 LIMIT(term
.ocy
, 0, term
.row
-1);
2575 if (term
.line
[term
.ocy
][term
.ocx
].mode
& ATTR_WDUMMY
)
2577 if (term
.line
[term
.c
.y
][cx
].mode
& ATTR_WDUMMY
)
2580 drawregion(0, 0, term
.col
, term
.row
);
2581 xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
],
2582 term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]);
2583 term
.ocx
= cx
, term
.ocy
= term
.c
.y
;