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 NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
46 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
47 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
48 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
49 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL)
52 #define ISO14755CMD "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
54 enum cursor_movement
{
78 ESC_STR
= 4, /* OSC, PM, APC */
80 ESC_STR_END
= 16, /* a final string was encountered */
81 ESC_TEST
= 32, /* Enter in test mode */
86 /* CSI Escape sequence structs */
87 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
89 char buf
[ESC_BUF_SIZ
]; /* raw string */
90 int len
; /* raw string length */
93 int narg
; /* nb of args */
97 /* STR Escape sequence structs */
98 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
100 char type
; /* ESC type ... */
101 char buf
[STR_BUF_SIZ
]; /* raw string */
102 int len
; /* raw string length */
103 char *args
[STR_ARG_SIZ
];
104 int narg
; /* nb of args */
108 static void execsh(char **);
109 static void stty(char **);
110 static void sigchld(int);
112 static void csidump(void);
113 static void csihandle(void);
114 static void csiparse(void);
115 static void csireset(void);
116 static int eschandle(uchar
);
117 static void strdump(void);
118 static void strhandle(void);
119 static void strparse(void);
120 static void strreset(void);
122 static void tprinter(char *, size_t);
123 static void tdumpsel(void);
124 static void tdumpline(int);
125 static void tdump(void);
126 static void tclearregion(int, int, int, int);
127 static void tcursor(int);
128 static void tdeletechar(int);
129 static void tdeleteline(int);
130 static void tinsertblank(int);
131 static void tinsertblankline(int);
132 static int tlinelen(int);
133 static void tmoveto(int, int);
134 static void tmoveato(int, int);
135 static void tnewline(int);
136 static void tputtab(int);
137 static void tputc(Rune
);
138 static void treset(void);
139 static void tscrollup(int, int);
140 static void tscrolldown(int, int);
141 static void tsetattr(int *, int);
142 static void tsetchar(Rune
, Glyph
*, int, int);
143 static void tsetdirt(int, int);
144 static void tsetscroll(int, int);
145 static void tswapscreen(void);
146 static void tsetmode(int, int, int *, int);
147 static int twrite(const char *, int, int);
148 static void tfulldirt(void);
149 static void tcontrolcode(uchar
);
150 static void tdectest(char );
151 static void tdefutf8(char);
152 static int32_t tdefcolor(int *, int *, int);
153 static void tdeftran(char);
154 static void tstrsequence(uchar
);
156 static void selscroll(int, int);
157 static void selsnap(int *, int *, int);
159 static Rune
utf8decodebyte(char, size_t *);
160 static char utf8encodebyte(Rune
, size_t);
161 static char *utf8strchr(char *s
, Rune u
);
162 static size_t utf8validate(Rune
*, size_t);
164 static char *base64dec(const char *);
166 static ssize_t
xwrite(int, const char *, size_t);
173 int oldbutton
= 3; /* button event on startup: 3 = release */
175 static CSIEscape csiescseq
;
176 static STREscape strescseq
;
179 static uchar utfbyte
[UTF_SIZ
+ 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
180 static uchar utfmask
[UTF_SIZ
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
181 static Rune utfmin
[UTF_SIZ
+ 1] = { 0, 0, 0x80, 0x800, 0x10000};
182 static Rune utfmax
[UTF_SIZ
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
185 xwrite(int fd
, const char *s
, size_t len
)
191 r
= write(fd
, s
, len
);
204 void *p
= malloc(len
);
207 die("Out of memory\n");
213 xrealloc(void *p
, size_t len
)
215 if ((p
= realloc(p
, len
)) == NULL
)
216 die("Out of memory\n");
224 if ((s
= strdup(s
)) == NULL
)
225 die("Out of memory\n");
231 utf8decode(const char *c
, Rune
*u
, size_t clen
)
233 size_t i
, j
, len
, type
;
239 udecoded
= utf8decodebyte(c
[0], &len
);
240 if (!BETWEEN(len
, 1, UTF_SIZ
))
242 for (i
= 1, j
= 1; i
< clen
&& j
< len
; ++i
, ++j
) {
243 udecoded
= (udecoded
<< 6) | utf8decodebyte(c
[i
], &type
);
250 utf8validate(u
, len
);
256 utf8decodebyte(char c
, size_t *i
)
258 for (*i
= 0; *i
< LEN(utfmask
); ++(*i
))
259 if (((uchar
)c
& utfmask
[*i
]) == utfbyte
[*i
])
260 return (uchar
)c
& ~utfmask
[*i
];
266 utf8encode(Rune u
, char *c
)
270 len
= utf8validate(&u
, 0);
274 for (i
= len
- 1; i
!= 0; --i
) {
275 c
[i
] = utf8encodebyte(u
, 0);
278 c
[0] = utf8encodebyte(u
, len
);
284 utf8encodebyte(Rune u
, size_t i
)
286 return utfbyte
[i
] | (u
& ~utfmask
[i
]);
290 utf8strchr(char *s
, Rune u
)
296 for (i
= 0, j
= 0; i
< len
; i
+= j
) {
297 if (!(j
= utf8decode(&s
[i
], &r
, len
- i
)))
307 utf8validate(Rune
*u
, size_t i
)
309 if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF))
311 for (i
= 1; *u
> utfmax
[i
]; ++i
)
317 static const char base64_digits
[] = {
318 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
319 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
320 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
321 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
322 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
323 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
324 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
325 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
326 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
327 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
328 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
329 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
333 base64dec_getc(const char **src
)
335 while (**src
&& !isprint(**src
)) (*src
)++;
340 base64dec(const char *src
)
342 size_t in_len
= strlen(src
);
346 in_len
+= 4 - (in_len
% 4);
347 result
= dst
= xmalloc(in_len
/ 4 * 3 + 1);
349 int a
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
350 int b
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
351 int c
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
352 int d
= base64_digits
[(unsigned char) base64dec_getc(&src
)];
354 *dst
++ = (a
<< 2) | ((b
& 0x30) >> 4);
357 *dst
++ = ((b
& 0x0f) << 4) | ((c
& 0x3c) >> 2);
360 *dst
++ = ((c
& 0x03) << 6) | d
;
379 if (term
.line
[y
][i
- 1].mode
& ATTR_WRAP
)
382 while (i
> 0 && term
.line
[y
][i
- 1].u
== ' ')
389 selstart(int col
, int row
, int snap
)
392 sel
.mode
= SEL_EMPTY
;
393 sel
.type
= SEL_REGULAR
;
395 sel
.oe
.x
= sel
.ob
.x
= col
;
396 sel
.oe
.y
= sel
.ob
.y
= row
;
400 sel
.mode
= SEL_READY
;
401 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
405 selextend(int col
, int row
, int type
)
407 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
414 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
420 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
421 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
429 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
430 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
431 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
433 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
434 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
436 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
437 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
439 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
440 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
442 /* expand selection over line breaks */
443 if (sel
.type
== SEL_RECTANGULAR
)
445 i
= tlinelen(sel
.nb
.y
);
448 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
449 sel
.ne
.x
= term
.col
- 1;
453 selected(int x
, int y
)
455 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
456 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
459 if (sel
.type
== SEL_RECTANGULAR
)
460 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
461 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
463 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
464 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
465 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
469 selsnap(int *x
, int *y
, int direction
)
471 int newx
, newy
, xt
, yt
;
472 int delim
, prevdelim
;
478 * Snap around if the word wraps around at the end or
479 * beginning of a line.
481 prevgp
= &term
.line
[*y
][*x
];
482 prevdelim
= ISDELIM(prevgp
->u
);
484 newx
= *x
+ direction
;
486 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
488 newx
= (newx
+ term
.col
) % term
.col
;
489 if (!BETWEEN(newy
, 0, term
.row
- 1))
495 yt
= newy
, xt
= newx
;
496 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
500 if (newx
>= tlinelen(newy
))
503 gp
= &term
.line
[newy
][newx
];
504 delim
= ISDELIM(gp
->u
);
505 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
506 || (delim
&& gp
->u
!= prevgp
->u
)))
517 * Snap around if the the previous line or the current one
518 * has set ATTR_WRAP at its end. Then the whole next or
519 * previous line will be selected.
521 *x
= (direction
< 0) ? 0 : term
.col
- 1;
523 for (; *y
> 0; *y
+= direction
) {
524 if (!(term
.line
[*y
-1][term
.col
-1].mode
529 } else if (direction
> 0) {
530 for (; *y
< term
.row
-1; *y
+= direction
) {
531 if (!(term
.line
[*y
][term
.col
-1].mode
545 int y
, bufsize
, lastx
, linelen
;
551 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
552 ptr
= str
= xmalloc(bufsize
);
554 /* append every set & selected glyph to the selection */
555 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
556 if ((linelen
= tlinelen(y
)) == 0) {
561 if (sel
.type
== SEL_RECTANGULAR
) {
562 gp
= &term
.line
[y
][sel
.nb
.x
];
565 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
566 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
568 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
569 while (last
>= gp
&& last
->u
== ' ')
572 for ( ; gp
<= last
; ++gp
) {
573 if (gp
->mode
& ATTR_WDUMMY
)
576 ptr
+= utf8encode(gp
->u
, ptr
);
580 * Copy and pasting of line endings is inconsistent
581 * in the inconsistent terminal and GUI world.
582 * The best solution seems like to produce '\n' when
583 * something is copied from st and convert '\n' to
584 * '\r', when something to be pasted is received by
586 * FIXME: Fix the computer world.
588 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
602 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
606 die(const char *errstr
, ...)
610 va_start(ap
, errstr
);
611 vfprintf(stderr
, errstr
, ap
);
620 const struct passwd
*pw
;
623 if ((pw
= getpwuid(getuid())) == NULL
) {
625 die("getpwuid:%s\n", strerror(errno
));
627 die("who are you?\n");
630 if ((sh
= getenv("SHELL")) == NULL
)
631 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
639 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
644 setenv("LOGNAME", pw
->pw_name
, 1);
645 setenv("USER", pw
->pw_name
, 1);
646 setenv("SHELL", sh
, 1);
647 setenv("HOME", pw
->pw_dir
, 1);
648 setenv("TERM", termname
, 1);
650 signal(SIGCHLD
, SIG_DFL
);
651 signal(SIGHUP
, SIG_DFL
);
652 signal(SIGINT
, SIG_DFL
);
653 signal(SIGQUIT
, SIG_DFL
);
654 signal(SIGTERM
, SIG_DFL
);
655 signal(SIGALRM
, SIG_DFL
);
667 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
668 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
673 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
674 die("child finished with error '%d'\n", stat
);
682 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
685 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
686 die("incorrect stty parameters\n");
687 memcpy(cmd
, stty_args
, n
);
689 siz
= sizeof(cmd
) - n
;
690 for (p
= args
; p
&& (s
= *p
); ++p
) {
691 if ((n
= strlen(s
)) > siz
-1)
692 die("stty parameter length too long\n");
699 if (system(cmd
) != 0)
700 perror("Couldn't call stty");
704 ttynew(char *line
, char *out
, char **args
)
709 term
.mode
|= MODE_PRINT
;
710 iofd
= (!strcmp(out
, "-")) ?
711 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
713 fprintf(stderr
, "Error opening %s:%s\n",
714 out
, strerror(errno
));
719 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
720 die("open line failed: %s\n", strerror(errno
));
726 /* seems to work fine on linux, openbsd and freebsd */
727 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
728 die("openpty failed: %s\n", strerror(errno
));
730 switch (pid
= fork()) {
732 die("fork failed\n");
736 setsid(); /* create a new process group */
740 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
741 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
749 signal(SIGCHLD
, sigchld
);
757 static char buf
[BUFSIZ
];
758 static int buflen
= 0;
762 /* append read bytes to unprocessed bytes */
763 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
764 die("Couldn't read from shell: %s\n", strerror(errno
));
767 written
= twrite(buf
, buflen
, 0);
769 /* keep any uncomplete utf8 char for the next call */
771 memmove(buf
, buf
+ written
, buflen
);
777 ttywrite(const char *s
, size_t n
)
784 * Remember that we are using a pty, which might be a modem line.
785 * Writing too much will clog the line. That's why we are doing this
787 * FIXME: Migrate the world to Plan 9.
795 /* Check if we can write. */
796 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
799 die("select failed: %s\n", strerror(errno
));
801 if (FD_ISSET(cmdfd
, &wfd
)) {
803 * Only write the bytes written by ttywrite() or the
804 * default of 256. This seems to be a reasonable value
805 * for a serial line. Bigger values might clog the I/O.
807 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
811 * We weren't able to write out everything.
812 * This means the buffer is getting full
820 /* All bytes have been written. */
824 if (FD_ISSET(cmdfd
, &rfd
))
830 die("write error on tty: %s\n", strerror(errno
));
834 ttysend(char *s
, size_t n
)
837 if (IS_SET(MODE_ECHO
))
842 ttyresize(int tw
, int th
)
850 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
851 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
859 for (i
= 0; i
< term
.row
-1; i
++) {
860 for (j
= 0; j
< term
.col
-1; j
++) {
861 if (term
.line
[i
][j
].mode
& attr
)
870 tsetdirt(int top
, int bot
)
874 LIMIT(top
, 0, term
.row
-1);
875 LIMIT(bot
, 0, term
.row
-1);
877 for (i
= top
; i
<= bot
; i
++)
882 tsetdirtattr(int attr
)
886 for (i
= 0; i
< term
.row
-1; i
++) {
887 for (j
= 0; j
< term
.col
-1; j
++) {
888 if (term
.line
[i
][j
].mode
& attr
) {
899 tsetdirt(0, term
.row
-1);
906 int alt
= IS_SET(MODE_ALTSCREEN
);
908 if (mode
== CURSOR_SAVE
) {
910 } else if (mode
== CURSOR_LOAD
) {
912 tmoveto(c
[alt
].x
, c
[alt
].y
);
925 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
927 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
928 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
931 term
.bot
= term
.row
- 1;
932 term
.mode
= MODE_WRAP
|MODE_UTF8
;
933 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
936 for (i
= 0; i
< 2; i
++) {
938 tcursor(CURSOR_SAVE
);
939 tclearregion(0, 0, term
.col
-1, term
.row
-1);
945 tnew(int col
, int row
)
947 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
957 Line
*tmp
= term
.line
;
959 term
.line
= term
.alt
;
961 term
.mode
^= MODE_ALTSCREEN
;
966 tscrolldown(int orig
, int n
)
971 LIMIT(n
, 0, term
.bot
-orig
+1);
973 tsetdirt(orig
, term
.bot
-n
);
974 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
976 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
978 term
.line
[i
] = term
.line
[i
-n
];
979 term
.line
[i
-n
] = temp
;
986 tscrollup(int orig
, int n
)
991 LIMIT(n
, 0, term
.bot
-orig
+1);
993 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
994 tsetdirt(orig
+n
, term
.bot
);
996 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
998 term
.line
[i
] = term
.line
[i
+n
];
999 term
.line
[i
+n
] = temp
;
1002 selscroll(orig
, -n
);
1006 selscroll(int orig
, int n
)
1011 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1012 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1016 if (sel
.type
== SEL_RECTANGULAR
) {
1017 if (sel
.ob
.y
< term
.top
)
1018 sel
.ob
.y
= term
.top
;
1019 if (sel
.oe
.y
> term
.bot
)
1020 sel
.oe
.y
= term
.bot
;
1022 if (sel
.ob
.y
< term
.top
) {
1023 sel
.ob
.y
= term
.top
;
1026 if (sel
.oe
.y
> term
.bot
) {
1027 sel
.oe
.y
= term
.bot
;
1028 sel
.oe
.x
= term
.col
;
1036 tnewline(int first_col
)
1040 if (y
== term
.bot
) {
1041 tscrollup(term
.top
, 1);
1045 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1051 char *p
= csiescseq
.buf
, *np
;
1060 csiescseq
.buf
[csiescseq
.len
] = '\0';
1061 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1063 v
= strtol(p
, &np
, 10);
1066 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1068 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1070 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1074 csiescseq
.mode
[0] = *p
++;
1075 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1078 /* for absolute user moves, when decom is set */
1080 tmoveato(int x
, int y
)
1082 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1086 tmoveto(int x
, int y
)
1090 if (term
.c
.state
& CURSOR_ORIGIN
) {
1095 maxy
= term
.row
- 1;
1097 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1098 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1099 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1103 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1105 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1106 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1107 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1108 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1109 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1110 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1111 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1112 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1113 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1117 * The table is proudly stolen from rxvt.
1119 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1120 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1121 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1123 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1124 if (x
+1 < term
.col
) {
1125 term
.line
[y
][x
+1].u
= ' ';
1126 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1128 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1129 term
.line
[y
][x
-1].u
= ' ';
1130 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1134 term
.line
[y
][x
] = *attr
;
1135 term
.line
[y
][x
].u
= u
;
1139 tclearregion(int x1
, int y1
, int x2
, int y2
)
1145 temp
= x1
, x1
= x2
, x2
= temp
;
1147 temp
= y1
, y1
= y2
, y2
= temp
;
1149 LIMIT(x1
, 0, term
.col
-1);
1150 LIMIT(x2
, 0, term
.col
-1);
1151 LIMIT(y1
, 0, term
.row
-1);
1152 LIMIT(y2
, 0, term
.row
-1);
1154 for (y
= y1
; y
<= y2
; y
++) {
1156 for (x
= x1
; x
<= x2
; x
++) {
1157 gp
= &term
.line
[y
][x
];
1160 gp
->fg
= term
.c
.attr
.fg
;
1161 gp
->bg
= term
.c
.attr
.bg
;
1174 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1178 size
= term
.col
- src
;
1179 line
= term
.line
[term
.c
.y
];
1181 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1182 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1191 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1195 size
= term
.col
- dst
;
1196 line
= term
.line
[term
.c
.y
];
1198 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1199 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1203 tinsertblankline(int n
)
1205 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1206 tscrolldown(term
.c
.y
, n
);
1212 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1213 tscrollup(term
.c
.y
, n
);
1217 tdefcolor(int *attr
, int *npar
, int l
)
1222 switch (attr
[*npar
+ 1]) {
1223 case 2: /* direct color in RGB space */
1224 if (*npar
+ 4 >= l
) {
1226 "erresc(38): Incorrect number of parameters (%d)\n",
1230 r
= attr
[*npar
+ 2];
1231 g
= attr
[*npar
+ 3];
1232 b
= attr
[*npar
+ 4];
1234 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1235 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1238 idx
= TRUECOLOR(r
, g
, b
);
1240 case 5: /* indexed color */
1241 if (*npar
+ 2 >= l
) {
1243 "erresc(38): Incorrect number of parameters (%d)\n",
1248 if (!BETWEEN(attr
[*npar
], 0, 255))
1249 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1253 case 0: /* implemented defined (only foreground) */
1254 case 1: /* transparent */
1255 case 3: /* direct color in CMY space */
1256 case 4: /* direct color in CMYK space */
1259 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1267 tsetattr(int *attr
, int l
)
1272 for (i
= 0; i
< l
; i
++) {
1275 term
.c
.attr
.mode
&= ~(
1284 term
.c
.attr
.fg
= defaultfg
;
1285 term
.c
.attr
.bg
= defaultbg
;
1288 term
.c
.attr
.mode
|= ATTR_BOLD
;
1291 term
.c
.attr
.mode
|= ATTR_FAINT
;
1294 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1297 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1299 case 5: /* slow blink */
1301 case 6: /* rapid blink */
1302 term
.c
.attr
.mode
|= ATTR_BLINK
;
1305 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1308 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1311 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1314 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1317 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1320 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1323 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1326 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1329 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1332 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1335 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1336 term
.c
.attr
.fg
= idx
;
1339 term
.c
.attr
.fg
= defaultfg
;
1342 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1343 term
.c
.attr
.bg
= idx
;
1346 term
.c
.attr
.bg
= defaultbg
;
1349 if (BETWEEN(attr
[i
], 30, 37)) {
1350 term
.c
.attr
.fg
= attr
[i
] - 30;
1351 } else if (BETWEEN(attr
[i
], 40, 47)) {
1352 term
.c
.attr
.bg
= attr
[i
] - 40;
1353 } else if (BETWEEN(attr
[i
], 90, 97)) {
1354 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1355 } else if (BETWEEN(attr
[i
], 100, 107)) {
1356 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1359 "erresc(default): gfx attr %d unknown\n",
1360 attr
[i
]), csidump();
1368 tsetscroll(int t
, int b
)
1372 LIMIT(t
, 0, term
.row
-1);
1373 LIMIT(b
, 0, term
.row
-1);
1384 tsetmode(int priv
, int set
, int *args
, int narg
)
1389 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1392 case 1: /* DECCKM -- Cursor key */
1393 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1395 case 5: /* DECSCNM -- Reverse video */
1397 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1398 if (mode
!= term
.mode
)
1401 case 6: /* DECOM -- Origin */
1402 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1405 case 7: /* DECAWM -- Auto wrap */
1406 MODBIT(term
.mode
, set
, MODE_WRAP
);
1408 case 0: /* Error (IGNORED) */
1409 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1410 case 3: /* DECCOLM -- Column (IGNORED) */
1411 case 4: /* DECSCLM -- Scroll (IGNORED) */
1412 case 8: /* DECARM -- Auto repeat (IGNORED) */
1413 case 18: /* DECPFF -- Printer feed (IGNORED) */
1414 case 19: /* DECPEX -- Printer extent (IGNORED) */
1415 case 42: /* DECNRCM -- National characters (IGNORED) */
1416 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1418 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1419 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1421 case 9: /* X10 mouse compatibility mode */
1422 xsetpointermotion(0);
1423 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1424 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1426 case 1000: /* 1000: report button press */
1427 xsetpointermotion(0);
1428 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1429 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1431 case 1002: /* 1002: report motion on button press */
1432 xsetpointermotion(0);
1433 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1434 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1436 case 1003: /* 1003: enable all mouse motions */
1437 xsetpointermotion(set
);
1438 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1439 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1441 case 1004: /* 1004: send focus events to tty */
1442 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1444 case 1006: /* 1006: extended reporting mode */
1445 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1448 MODBIT(term
.mode
, set
, MODE_8BIT
);
1450 case 1049: /* swap screen & set/restore cursor as xterm */
1451 if (!allowaltscreen
)
1453 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1455 case 47: /* swap screen */
1457 if (!allowaltscreen
)
1459 alt
= IS_SET(MODE_ALTSCREEN
);
1461 tclearregion(0, 0, term
.col
-1,
1464 if (set
^ alt
) /* set is always 1 or 0 */
1470 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1472 case 2004: /* 2004: bracketed paste mode */
1473 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1475 /* Not implemented mouse modes. See comments there. */
1476 case 1001: /* mouse highlight mode; can hang the
1477 terminal by design when implemented. */
1478 case 1005: /* UTF-8 mouse mode; will confuse
1479 applications not supporting UTF-8
1481 case 1015: /* urxvt mangled mouse mode; incompatible
1482 and can be mistaken for other control
1486 "erresc: unknown private set/reset mode %d\n",
1492 case 0: /* Error (IGNORED) */
1494 case 2: /* KAM -- keyboard action */
1495 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1497 case 4: /* IRM -- Insertion-replacement */
1498 MODBIT(term
.mode
, set
, MODE_INSERT
);
1500 case 12: /* SRM -- Send/Receive */
1501 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1503 case 20: /* LNM -- Linefeed/new line */
1504 MODBIT(term
.mode
, set
, MODE_CRLF
);
1508 "erresc: unknown set/reset mode %d\n",
1522 switch (csiescseq
.mode
[0]) {
1525 fprintf(stderr
, "erresc: unknown csi ");
1529 case '@': /* ICH -- Insert <n> blank char */
1530 DEFAULT(csiescseq
.arg
[0], 1);
1531 tinsertblank(csiescseq
.arg
[0]);
1533 case 'A': /* CUU -- Cursor <n> Up */
1534 DEFAULT(csiescseq
.arg
[0], 1);
1535 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1537 case 'B': /* CUD -- Cursor <n> Down */
1538 case 'e': /* VPR --Cursor <n> Down */
1539 DEFAULT(csiescseq
.arg
[0], 1);
1540 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1542 case 'i': /* MC -- Media Copy */
1543 switch (csiescseq
.arg
[0]) {
1548 tdumpline(term
.c
.y
);
1554 term
.mode
&= ~MODE_PRINT
;
1557 term
.mode
|= MODE_PRINT
;
1561 case 'c': /* DA -- Device Attributes */
1562 if (csiescseq
.arg
[0] == 0)
1563 ttywrite(vtiden
, strlen(vtiden
));
1565 case 'C': /* CUF -- Cursor <n> Forward */
1566 case 'a': /* HPR -- Cursor <n> Forward */
1567 DEFAULT(csiescseq
.arg
[0], 1);
1568 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1570 case 'D': /* CUB -- Cursor <n> Backward */
1571 DEFAULT(csiescseq
.arg
[0], 1);
1572 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1574 case 'E': /* CNL -- Cursor <n> Down and first col */
1575 DEFAULT(csiescseq
.arg
[0], 1);
1576 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1578 case 'F': /* CPL -- Cursor <n> Up and first col */
1579 DEFAULT(csiescseq
.arg
[0], 1);
1580 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1582 case 'g': /* TBC -- Tabulation clear */
1583 switch (csiescseq
.arg
[0]) {
1584 case 0: /* clear current tab stop */
1585 term
.tabs
[term
.c
.x
] = 0;
1587 case 3: /* clear all the tabs */
1588 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1594 case 'G': /* CHA -- Move to <col> */
1596 DEFAULT(csiescseq
.arg
[0], 1);
1597 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1599 case 'H': /* CUP -- Move to <row> <col> */
1601 DEFAULT(csiescseq
.arg
[0], 1);
1602 DEFAULT(csiescseq
.arg
[1], 1);
1603 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1605 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1606 DEFAULT(csiescseq
.arg
[0], 1);
1607 tputtab(csiescseq
.arg
[0]);
1609 case 'J': /* ED -- Clear screen */
1611 switch (csiescseq
.arg
[0]) {
1613 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1614 if (term
.c
.y
< term
.row
-1) {
1615 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1621 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1622 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1625 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1631 case 'K': /* EL -- Clear line */
1632 switch (csiescseq
.arg
[0]) {
1634 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1638 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1641 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1645 case 'S': /* SU -- Scroll <n> line up */
1646 DEFAULT(csiescseq
.arg
[0], 1);
1647 tscrollup(term
.top
, csiescseq
.arg
[0]);
1649 case 'T': /* SD -- Scroll <n> line down */
1650 DEFAULT(csiescseq
.arg
[0], 1);
1651 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1653 case 'L': /* IL -- Insert <n> blank lines */
1654 DEFAULT(csiescseq
.arg
[0], 1);
1655 tinsertblankline(csiescseq
.arg
[0]);
1657 case 'l': /* RM -- Reset Mode */
1658 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1660 case 'M': /* DL -- Delete <n> lines */
1661 DEFAULT(csiescseq
.arg
[0], 1);
1662 tdeleteline(csiescseq
.arg
[0]);
1664 case 'X': /* ECH -- Erase <n> char */
1665 DEFAULT(csiescseq
.arg
[0], 1);
1666 tclearregion(term
.c
.x
, term
.c
.y
,
1667 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1669 case 'P': /* DCH -- Delete <n> char */
1670 DEFAULT(csiescseq
.arg
[0], 1);
1671 tdeletechar(csiescseq
.arg
[0]);
1673 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1674 DEFAULT(csiescseq
.arg
[0], 1);
1675 tputtab(-csiescseq
.arg
[0]);
1677 case 'd': /* VPA -- Move to <row> */
1678 DEFAULT(csiescseq
.arg
[0], 1);
1679 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1681 case 'h': /* SM -- Set terminal mode */
1682 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1684 case 'm': /* SGR -- Terminal attribute (color) */
1685 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1687 case 'n': /* DSR – Device Status Report (cursor position) */
1688 if (csiescseq
.arg
[0] == 6) {
1689 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1690 term
.c
.y
+1, term
.c
.x
+1);
1694 case 'r': /* DECSTBM -- Set Scrolling Region */
1695 if (csiescseq
.priv
) {
1698 DEFAULT(csiescseq
.arg
[0], 1);
1699 DEFAULT(csiescseq
.arg
[1], term
.row
);
1700 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1704 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1705 tcursor(CURSOR_SAVE
);
1707 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1708 tcursor(CURSOR_LOAD
);
1711 switch (csiescseq
.mode
[1]) {
1712 case 'q': /* DECSCUSR -- Set Cursor Style */
1713 if (xsetcursor(csiescseq
.arg
[0]))
1729 fprintf(stderr
, "ESC[");
1730 for (i
= 0; i
< csiescseq
.len
; i
++) {
1731 c
= csiescseq
.buf
[i
] & 0xff;
1734 } else if (c
== '\n') {
1735 fprintf(stderr
, "(\\n)");
1736 } else if (c
== '\r') {
1737 fprintf(stderr
, "(\\r)");
1738 } else if (c
== 0x1b) {
1739 fprintf(stderr
, "(\\e)");
1741 fprintf(stderr
, "(%02x)", c
);
1750 memset(&csiescseq
, 0, sizeof(csiescseq
));
1759 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1761 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1763 switch (strescseq
.type
) {
1764 case ']': /* OSC -- Operating System Command */
1770 xsettitle(strescseq
.args
[1]);
1776 dec
= base64dec(strescseq
.args
[2]);
1781 fprintf(stderr
, "erresc: invalid base64\n");
1785 case 4: /* color set */
1788 p
= strescseq
.args
[2];
1790 case 104: /* color reset, here p = NULL */
1791 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1792 if (xsetcolorname(j
, p
)) {
1793 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1796 * TODO if defaultbg color is changed, borders
1804 case 'k': /* old title set compatibility */
1805 xsettitle(strescseq
.args
[0]);
1807 case 'P': /* DCS -- Device Control String */
1808 term
.mode
|= ESC_DCS
;
1809 case '_': /* APC -- Application Program Command */
1810 case '^': /* PM -- Privacy Message */
1814 fprintf(stderr
, "erresc: unknown str ");
1822 char *p
= strescseq
.buf
;
1825 strescseq
.buf
[strescseq
.len
] = '\0';
1830 while (strescseq
.narg
< STR_ARG_SIZ
) {
1831 strescseq
.args
[strescseq
.narg
++] = p
;
1832 while ((c
= *p
) != ';' && c
!= '\0')
1846 fprintf(stderr
, "ESC%c", strescseq
.type
);
1847 for (i
= 0; i
< strescseq
.len
; i
++) {
1848 c
= strescseq
.buf
[i
] & 0xff;
1852 } else if (isprint(c
)) {
1854 } else if (c
== '\n') {
1855 fprintf(stderr
, "(\\n)");
1856 } else if (c
== '\r') {
1857 fprintf(stderr
, "(\\r)");
1858 } else if (c
== 0x1b) {
1859 fprintf(stderr
, "(\\e)");
1861 fprintf(stderr
, "(%02x)", c
);
1864 fprintf(stderr
, "ESC\\\n");
1870 memset(&strescseq
, 0, sizeof(strescseq
));
1874 sendbreak(const Arg
*arg
)
1876 if (tcsendbreak(cmdfd
, 0))
1877 perror("Error sending break");
1881 tprinter(char *s
, size_t len
)
1883 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1884 perror("Error writing to output file");
1891 iso14755(const Arg
*arg
)
1894 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1895 unsigned long utf32
;
1897 if (!(p
= popen(ISO14755CMD
, "r")))
1900 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1903 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1905 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1906 (*e
!= '\n' && *e
!= '\0'))
1909 ttysend(uc
, utf8encode(utf32
, uc
));
1913 toggleprinter(const Arg
*arg
)
1915 term
.mode
^= MODE_PRINT
;
1919 printscreen(const Arg
*arg
)
1925 printsel(const Arg
*arg
)
1935 if ((ptr
= getsel())) {
1936 tprinter(ptr
, strlen(ptr
));
1947 bp
= &term
.line
[n
][0];
1948 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1949 if (bp
!= end
|| bp
->u
!= ' ') {
1950 for ( ;bp
<= end
; ++bp
)
1951 tprinter(buf
, utf8encode(bp
->u
, buf
));
1961 for (i
= 0; i
< term
.row
; ++i
)
1971 while (x
< term
.col
&& n
--)
1972 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1975 while (x
> 0 && n
++)
1976 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1979 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1983 tdefutf8(char ascii
)
1986 term
.mode
|= MODE_UTF8
;
1987 else if (ascii
== '@')
1988 term
.mode
&= ~MODE_UTF8
;
1992 tdeftran(char ascii
)
1994 static char cs
[] = "0B";
1995 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
1998 if ((p
= strchr(cs
, ascii
)) == NULL
) {
1999 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2001 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2010 if (c
== '8') { /* DEC screen alignment test. */
2011 for (x
= 0; x
< term
.col
; ++x
) {
2012 for (y
= 0; y
< term
.row
; ++y
)
2013 tsetchar('E', &term
.c
.attr
, x
, y
);
2019 tstrsequence(uchar c
)
2024 case 0x90: /* DCS -- Device Control String */
2026 term
.esc
|= ESC_DCS
;
2028 case 0x9f: /* APC -- Application Program Command */
2031 case 0x9e: /* PM -- Privacy Message */
2034 case 0x9d: /* OSC -- Operating System Command */
2039 term
.esc
|= ESC_STR
;
2043 tcontrolcode(uchar ascii
)
2050 tmoveto(term
.c
.x
-1, term
.c
.y
);
2053 tmoveto(0, term
.c
.y
);
2058 /* go to first col if the mode is set */
2059 tnewline(IS_SET(MODE_CRLF
));
2061 case '\a': /* BEL */
2062 if (term
.esc
& ESC_STR_END
) {
2063 /* backwards compatibility to xterm */
2069 case '\033': /* ESC */
2071 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2072 term
.esc
|= ESC_START
;
2074 case '\016': /* SO (LS1 -- Locking shift 1) */
2075 case '\017': /* SI (LS0 -- Locking shift 0) */
2076 term
.charset
= 1 - (ascii
- '\016');
2078 case '\032': /* SUB */
2079 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2080 case '\030': /* CAN */
2083 case '\005': /* ENQ (IGNORED) */
2084 case '\000': /* NUL (IGNORED) */
2085 case '\021': /* XON (IGNORED) */
2086 case '\023': /* XOFF (IGNORED) */
2087 case 0177: /* DEL (IGNORED) */
2089 case 0x80: /* TODO: PAD */
2090 case 0x81: /* TODO: HOP */
2091 case 0x82: /* TODO: BPH */
2092 case 0x83: /* TODO: NBH */
2093 case 0x84: /* TODO: IND */
2095 case 0x85: /* NEL -- Next line */
2096 tnewline(1); /* always go to first col */
2098 case 0x86: /* TODO: SSA */
2099 case 0x87: /* TODO: ESA */
2101 case 0x88: /* HTS -- Horizontal tab stop */
2102 term
.tabs
[term
.c
.x
] = 1;
2104 case 0x89: /* TODO: HTJ */
2105 case 0x8a: /* TODO: VTS */
2106 case 0x8b: /* TODO: PLD */
2107 case 0x8c: /* TODO: PLU */
2108 case 0x8d: /* TODO: RI */
2109 case 0x8e: /* TODO: SS2 */
2110 case 0x8f: /* TODO: SS3 */
2111 case 0x91: /* TODO: PU1 */
2112 case 0x92: /* TODO: PU2 */
2113 case 0x93: /* TODO: STS */
2114 case 0x94: /* TODO: CCH */
2115 case 0x95: /* TODO: MW */
2116 case 0x96: /* TODO: SPA */
2117 case 0x97: /* TODO: EPA */
2118 case 0x98: /* TODO: SOS */
2119 case 0x99: /* TODO: SGCI */
2121 case 0x9a: /* DECID -- Identify Terminal */
2122 ttywrite(vtiden
, strlen(vtiden
));
2124 case 0x9b: /* TODO: CSI */
2125 case 0x9c: /* TODO: ST */
2127 case 0x90: /* DCS -- Device Control String */
2128 case 0x9d: /* OSC -- Operating System Command */
2129 case 0x9e: /* PM -- Privacy Message */
2130 case 0x9f: /* APC -- Application Program Command */
2131 tstrsequence(ascii
);
2134 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2135 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2139 * returns 1 when the sequence is finished and it hasn't to read
2140 * more characters for this sequence, otherwise 0
2143 eschandle(uchar ascii
)
2147 term
.esc
|= ESC_CSI
;
2150 term
.esc
|= ESC_TEST
;
2153 term
.esc
|= ESC_UTF8
;
2155 case 'P': /* DCS -- Device Control String */
2156 case '_': /* APC -- Application Program Command */
2157 case '^': /* PM -- Privacy Message */
2158 case ']': /* OSC -- Operating System Command */
2159 case 'k': /* old title set compatibility */
2160 tstrsequence(ascii
);
2162 case 'n': /* LS2 -- Locking shift 2 */
2163 case 'o': /* LS3 -- Locking shift 3 */
2164 term
.charset
= 2 + (ascii
- 'n');
2166 case '(': /* GZD4 -- set primary charset G0 */
2167 case ')': /* G1D4 -- set secondary charset G1 */
2168 case '*': /* G2D4 -- set tertiary charset G2 */
2169 case '+': /* G3D4 -- set quaternary charset G3 */
2170 term
.icharset
= ascii
- '(';
2171 term
.esc
|= ESC_ALTCHARSET
;
2173 case 'D': /* IND -- Linefeed */
2174 if (term
.c
.y
== term
.bot
) {
2175 tscrollup(term
.top
, 1);
2177 tmoveto(term
.c
.x
, term
.c
.y
+1);
2180 case 'E': /* NEL -- Next line */
2181 tnewline(1); /* always go to first col */
2183 case 'H': /* HTS -- Horizontal tab stop */
2184 term
.tabs
[term
.c
.x
] = 1;
2186 case 'M': /* RI -- Reverse index */
2187 if (term
.c
.y
== term
.top
) {
2188 tscrolldown(term
.top
, 1);
2190 tmoveto(term
.c
.x
, term
.c
.y
-1);
2193 case 'Z': /* DECID -- Identify Terminal */
2194 ttywrite(vtiden
, strlen(vtiden
));
2196 case 'c': /* RIS -- Reset to inital state */
2201 case '=': /* DECPAM -- Application keypad */
2202 term
.mode
|= MODE_APPKEYPAD
;
2204 case '>': /* DECPNM -- Normal keypad */
2205 term
.mode
&= ~MODE_APPKEYPAD
;
2207 case '7': /* DECSC -- Save Cursor */
2208 tcursor(CURSOR_SAVE
);
2210 case '8': /* DECRC -- Restore Cursor */
2211 tcursor(CURSOR_LOAD
);
2213 case '\\': /* ST -- String Terminator */
2214 if (term
.esc
& ESC_STR_END
)
2218 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2219 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2233 control
= ISCONTROL(u
);
2234 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2238 len
= utf8encode(u
, c
);
2239 if (!control
&& (width
= wcwidth(u
)) == -1) {
2240 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2245 if (IS_SET(MODE_PRINT
))
2249 * STR sequence must be checked before anything else
2250 * because it uses all following characters until it
2251 * receives a ESC, a SUB, a ST or any other C1 control
2254 if (term
.esc
& ESC_STR
) {
2255 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2257 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2258 if (IS_SET(MODE_SIXEL
)) {
2259 /* TODO: render sixel */;
2260 term
.mode
&= ~MODE_SIXEL
;
2263 term
.esc
|= ESC_STR_END
;
2264 goto check_control_code
;
2268 if (IS_SET(MODE_SIXEL
)) {
2269 /* TODO: implement sixel mode */
2272 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2273 term
.mode
|= MODE_SIXEL
;
2275 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2277 * Here is a bug in terminals. If the user never sends
2278 * some code to stop the str or esc command, then st
2279 * will stop responding. But this is better than
2280 * silently failing with unknown characters. At least
2281 * then users will report back.
2283 * In the case users ever get fixed, here is the code:
2292 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2293 strescseq
.len
+= len
;
2299 * Actions of control codes must be performed as soon they arrive
2300 * because they can be embedded inside a control sequence, and
2301 * they must not cause conflicts with sequences.
2306 * control codes are not shown ever
2309 } else if (term
.esc
& ESC_START
) {
2310 if (term
.esc
& ESC_CSI
) {
2311 csiescseq
.buf
[csiescseq
.len
++] = u
;
2312 if (BETWEEN(u
, 0x40, 0x7E)
2313 || csiescseq
.len
>= \
2314 sizeof(csiescseq
.buf
)-1) {
2320 } else if (term
.esc
& ESC_UTF8
) {
2322 } else if (term
.esc
& ESC_ALTCHARSET
) {
2324 } else if (term
.esc
& ESC_TEST
) {
2329 /* sequence already finished */
2333 * All characters which form part of a sequence are not
2338 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2341 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2342 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2343 gp
->mode
|= ATTR_WRAP
;
2345 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2348 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2349 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2351 if (term
.c
.x
+width
> term
.col
) {
2353 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2356 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2359 gp
->mode
|= ATTR_WIDE
;
2360 if (term
.c
.x
+1 < term
.col
) {
2362 gp
[1].mode
= ATTR_WDUMMY
;
2365 if (term
.c
.x
+width
< term
.col
) {
2366 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2368 term
.c
.state
|= CURSOR_WRAPNEXT
;
2373 twrite(const char *buf
, int buflen
, int show_ctrl
)
2379 for (n
= 0; n
< buflen
; n
+= charsize
) {
2380 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2381 /* process a complete utf8 char */
2382 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2389 if (show_ctrl
&& ISCONTROL(u
)) {
2394 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2405 tresize(int col
, int row
)
2408 int minrow
= MIN(row
, term
.row
);
2409 int mincol
= MIN(col
, term
.col
);
2413 if (col
< 1 || row
< 1) {
2415 "tresize: error resizing to %dx%d\n", col
, row
);
2420 * slide screen to keep cursor where we expect it -
2421 * tscrollup would work here, but we can optimize to
2422 * memmove because we're freeing the earlier lines
2424 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2428 /* ensure that both src and dst are not NULL */
2430 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2431 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2433 for (i
+= row
; i
< term
.row
; i
++) {
2438 /* resize to new height */
2439 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2440 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2441 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2442 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2444 /* resize each row to new width, zero-pad if needed */
2445 for (i
= 0; i
< minrow
; i
++) {
2446 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2447 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2450 /* allocate any new rows */
2451 for (/* i = minrow */; i
< row
; i
++) {
2452 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2453 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2455 if (col
> term
.col
) {
2456 bp
= term
.tabs
+ term
.col
;
2458 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2459 while (--bp
> term
.tabs
&& !*bp
)
2461 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2464 /* update terminal size */
2467 /* reset scrolling region */
2468 tsetscroll(0, row
-1);
2469 /* make use of the LIMIT in tmoveto */
2470 tmoveto(term
.c
.x
, term
.c
.y
);
2471 /* Clearing both screens (it makes dirty all lines) */
2473 for (i
= 0; i
< 2; i
++) {
2474 if (mincol
< col
&& 0 < minrow
) {
2475 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2477 if (0 < col
&& minrow
< row
) {
2478 tclearregion(0, minrow
, col
- 1, row
- 1);
2481 tcursor(CURSOR_LOAD
);
2500 numlock(const Arg
*dummy
)