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);
172 int oldbutton
= 3; /* button event on startup: 3 = release */
174 static Selection sel
;
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
, int done
)
407 int oldey
, oldex
, oldsby
, oldsey
, oldtype
;
411 if (done
&& sel
.mode
== SEL_EMPTY
) {
422 sel
.alt
= IS_SET(MODE_ALTSCREEN
);
428 if (oldey
!= sel
.oe
.y
|| oldex
!= sel
.oe
.x
|| oldtype
!= sel
.type
)
429 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
));
431 sel
.mode
= done
? SEL_IDLE
: SEL_READY
;
439 if (sel
.type
== SEL_REGULAR
&& sel
.ob
.y
!= sel
.oe
.y
) {
440 sel
.nb
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.ob
.x
: sel
.oe
.x
;
441 sel
.ne
.x
= sel
.ob
.y
< sel
.oe
.y
? sel
.oe
.x
: sel
.ob
.x
;
443 sel
.nb
.x
= MIN(sel
.ob
.x
, sel
.oe
.x
);
444 sel
.ne
.x
= MAX(sel
.ob
.x
, sel
.oe
.x
);
446 sel
.nb
.y
= MIN(sel
.ob
.y
, sel
.oe
.y
);
447 sel
.ne
.y
= MAX(sel
.ob
.y
, sel
.oe
.y
);
449 selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1);
450 selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1);
452 /* expand selection over line breaks */
453 if (sel
.type
== SEL_RECTANGULAR
)
455 i
= tlinelen(sel
.nb
.y
);
458 if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
)
459 sel
.ne
.x
= term
.col
- 1;
463 selected(int x
, int y
)
465 if (sel
.mode
== SEL_EMPTY
|| sel
.ob
.x
== -1 ||
466 sel
.alt
!= IS_SET(MODE_ALTSCREEN
))
469 if (sel
.type
== SEL_RECTANGULAR
)
470 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
471 && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
);
473 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
)
474 && (y
!= sel
.nb
.y
|| x
>= sel
.nb
.x
)
475 && (y
!= sel
.ne
.y
|| x
<= sel
.ne
.x
);
479 selsnap(int *x
, int *y
, int direction
)
481 int newx
, newy
, xt
, yt
;
482 int delim
, prevdelim
;
488 * Snap around if the word wraps around at the end or
489 * beginning of a line.
491 prevgp
= &term
.line
[*y
][*x
];
492 prevdelim
= ISDELIM(prevgp
->u
);
494 newx
= *x
+ direction
;
496 if (!BETWEEN(newx
, 0, term
.col
- 1)) {
498 newx
= (newx
+ term
.col
) % term
.col
;
499 if (!BETWEEN(newy
, 0, term
.row
- 1))
505 yt
= newy
, xt
= newx
;
506 if (!(term
.line
[yt
][xt
].mode
& ATTR_WRAP
))
510 if (newx
>= tlinelen(newy
))
513 gp
= &term
.line
[newy
][newx
];
514 delim
= ISDELIM(gp
->u
);
515 if (!(gp
->mode
& ATTR_WDUMMY
) && (delim
!= prevdelim
516 || (delim
&& gp
->u
!= prevgp
->u
)))
527 * Snap around if the the previous line or the current one
528 * has set ATTR_WRAP at its end. Then the whole next or
529 * previous line will be selected.
531 *x
= (direction
< 0) ? 0 : term
.col
- 1;
533 for (; *y
> 0; *y
+= direction
) {
534 if (!(term
.line
[*y
-1][term
.col
-1].mode
539 } else if (direction
> 0) {
540 for (; *y
< term
.row
-1; *y
+= direction
) {
541 if (!(term
.line
[*y
][term
.col
-1].mode
555 int y
, bufsize
, lastx
, linelen
;
561 bufsize
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
;
562 ptr
= str
= xmalloc(bufsize
);
564 /* append every set & selected glyph to the selection */
565 for (y
= sel
.nb
.y
; y
<= sel
.ne
.y
; y
++) {
566 if ((linelen
= tlinelen(y
)) == 0) {
571 if (sel
.type
== SEL_RECTANGULAR
) {
572 gp
= &term
.line
[y
][sel
.nb
.x
];
575 gp
= &term
.line
[y
][sel
.nb
.y
== y
? sel
.nb
.x
: 0];
576 lastx
= (sel
.ne
.y
== y
) ? sel
.ne
.x
: term
.col
-1;
578 last
= &term
.line
[y
][MIN(lastx
, linelen
-1)];
579 while (last
>= gp
&& last
->u
== ' ')
582 for ( ; gp
<= last
; ++gp
) {
583 if (gp
->mode
& ATTR_WDUMMY
)
586 ptr
+= utf8encode(gp
->u
, ptr
);
590 * Copy and pasting of line endings is inconsistent
591 * in the inconsistent terminal and GUI world.
592 * The best solution seems like to produce '\n' when
593 * something is copied from st and convert '\n' to
594 * '\r', when something to be pasted is received by
596 * FIXME: Fix the computer world.
598 if ((y
< sel
.ne
.y
|| lastx
>= linelen
) && !(last
->mode
& ATTR_WRAP
))
612 tsetdirt(sel
.nb
.y
, sel
.ne
.y
);
616 die(const char *errstr
, ...)
620 va_start(ap
, errstr
);
621 vfprintf(stderr
, errstr
, ap
);
630 const struct passwd
*pw
;
633 if ((pw
= getpwuid(getuid())) == NULL
) {
635 die("getpwuid:%s\n", strerror(errno
));
637 die("who are you?\n");
640 if ((sh
= getenv("SHELL")) == NULL
)
641 sh
= (pw
->pw_shell
[0]) ? pw
->pw_shell
: shell
;
649 DEFAULT(args
, ((char *[]) {prog
, NULL
}));
654 setenv("LOGNAME", pw
->pw_name
, 1);
655 setenv("USER", pw
->pw_name
, 1);
656 setenv("SHELL", sh
, 1);
657 setenv("HOME", pw
->pw_dir
, 1);
658 setenv("TERM", termname
, 1);
660 signal(SIGCHLD
, SIG_DFL
);
661 signal(SIGHUP
, SIG_DFL
);
662 signal(SIGINT
, SIG_DFL
);
663 signal(SIGQUIT
, SIG_DFL
);
664 signal(SIGTERM
, SIG_DFL
);
665 signal(SIGALRM
, SIG_DFL
);
677 if ((p
= waitpid(pid
, &stat
, WNOHANG
)) < 0)
678 die("Waiting for pid %hd failed: %s\n", pid
, strerror(errno
));
683 if (!WIFEXITED(stat
) || WEXITSTATUS(stat
))
684 die("child finished with error '%d'\n", stat
);
692 char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
;
695 if ((n
= strlen(stty_args
)) > sizeof(cmd
)-1)
696 die("incorrect stty parameters\n");
697 memcpy(cmd
, stty_args
, n
);
699 siz
= sizeof(cmd
) - n
;
700 for (p
= args
; p
&& (s
= *p
); ++p
) {
701 if ((n
= strlen(s
)) > siz
-1)
702 die("stty parameter length too long\n");
709 if (system(cmd
) != 0)
710 perror("Couldn't call stty");
714 ttynew(char *line
, char *out
, char **args
)
719 term
.mode
|= MODE_PRINT
;
720 iofd
= (!strcmp(out
, "-")) ?
721 1 : open(out
, O_WRONLY
| O_CREAT
, 0666);
723 fprintf(stderr
, "Error opening %s:%s\n",
724 out
, strerror(errno
));
729 if ((cmdfd
= open(line
, O_RDWR
)) < 0)
730 die("open line failed: %s\n", strerror(errno
));
736 /* seems to work fine on linux, openbsd and freebsd */
737 if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0)
738 die("openpty failed: %s\n", strerror(errno
));
740 switch (pid
= fork()) {
742 die("fork failed\n");
746 setsid(); /* create a new process group */
750 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0)
751 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
));
759 signal(SIGCHLD
, sigchld
);
767 static char buf
[BUFSIZ
];
768 static int buflen
= 0;
772 /* append read bytes to unprocessed bytes */
773 if ((ret
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0)
774 die("Couldn't read from shell: %s\n", strerror(errno
));
777 written
= twrite(buf
, buflen
, 0);
779 /* keep any uncomplete utf8 char for the next call */
781 memmove(buf
, buf
+ written
, buflen
);
787 ttywrite(const char *s
, size_t n
, int may_echo
)
793 if (may_echo
&& IS_SET(MODE_ECHO
))
797 * Remember that we are using a pty, which might be a modem line.
798 * Writing too much will clog the line. That's why we are doing this
800 * FIXME: Migrate the world to Plan 9.
808 /* Check if we can write. */
809 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) {
812 die("select failed: %s\n", strerror(errno
));
814 if (FD_ISSET(cmdfd
, &wfd
)) {
816 * Only write the bytes written by ttywrite() or the
817 * default of 256. This seems to be a reasonable value
818 * for a serial line. Bigger values might clog the I/O.
820 if ((r
= write(cmdfd
, s
, (n
< lim
)? n
: lim
)) < 0)
824 * We weren't able to write out everything.
825 * This means the buffer is getting full
833 /* All bytes have been written. */
837 if (FD_ISSET(cmdfd
, &rfd
))
843 die("write error on tty: %s\n", strerror(errno
));
847 ttyresize(int tw
, int th
)
855 if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0)
856 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
));
864 for (i
= 0; i
< term
.row
-1; i
++) {
865 for (j
= 0; j
< term
.col
-1; j
++) {
866 if (term
.line
[i
][j
].mode
& attr
)
875 tsetdirt(int top
, int bot
)
879 LIMIT(top
, 0, term
.row
-1);
880 LIMIT(bot
, 0, term
.row
-1);
882 for (i
= top
; i
<= bot
; i
++)
887 tsetdirtattr(int attr
)
891 for (i
= 0; i
< term
.row
-1; i
++) {
892 for (j
= 0; j
< term
.col
-1; j
++) {
893 if (term
.line
[i
][j
].mode
& attr
) {
904 tsetdirt(0, term
.row
-1);
911 int alt
= IS_SET(MODE_ALTSCREEN
);
913 if (mode
== CURSOR_SAVE
) {
915 } else if (mode
== CURSOR_LOAD
) {
917 tmoveto(c
[alt
].x
, c
[alt
].y
);
930 }, .x
= 0, .y
= 0, .state
= CURSOR_DEFAULT
};
932 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
933 for (i
= tabspaces
; i
< term
.col
; i
+= tabspaces
)
936 term
.bot
= term
.row
- 1;
937 term
.mode
= MODE_WRAP
|MODE_UTF8
;
938 memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
));
941 for (i
= 0; i
< 2; i
++) {
943 tcursor(CURSOR_SAVE
);
944 tclearregion(0, 0, term
.col
-1, term
.row
-1);
950 tnew(int col
, int row
)
952 term
= (Term
){ .c
= { .attr
= { .fg
= defaultfg
, .bg
= defaultbg
} } };
962 Line
*tmp
= term
.line
;
964 term
.line
= term
.alt
;
966 term
.mode
^= MODE_ALTSCREEN
;
971 tscrolldown(int orig
, int n
)
976 LIMIT(n
, 0, term
.bot
-orig
+1);
978 tsetdirt(orig
, term
.bot
-n
);
979 tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
);
981 for (i
= term
.bot
; i
>= orig
+n
; i
--) {
983 term
.line
[i
] = term
.line
[i
-n
];
984 term
.line
[i
-n
] = temp
;
991 tscrollup(int orig
, int n
)
996 LIMIT(n
, 0, term
.bot
-orig
+1);
998 tclearregion(0, orig
, term
.col
-1, orig
+n
-1);
999 tsetdirt(orig
+n
, term
.bot
);
1001 for (i
= orig
; i
<= term
.bot
-n
; i
++) {
1002 temp
= term
.line
[i
];
1003 term
.line
[i
] = term
.line
[i
+n
];
1004 term
.line
[i
+n
] = temp
;
1007 selscroll(orig
, -n
);
1011 selscroll(int orig
, int n
)
1016 if (BETWEEN(sel
.ob
.y
, orig
, term
.bot
) || BETWEEN(sel
.oe
.y
, orig
, term
.bot
)) {
1017 if ((sel
.ob
.y
+= n
) > term
.bot
|| (sel
.oe
.y
+= n
) < term
.top
) {
1021 if (sel
.type
== SEL_RECTANGULAR
) {
1022 if (sel
.ob
.y
< term
.top
)
1023 sel
.ob
.y
= term
.top
;
1024 if (sel
.oe
.y
> term
.bot
)
1025 sel
.oe
.y
= term
.bot
;
1027 if (sel
.ob
.y
< term
.top
) {
1028 sel
.ob
.y
= term
.top
;
1031 if (sel
.oe
.y
> term
.bot
) {
1032 sel
.oe
.y
= term
.bot
;
1033 sel
.oe
.x
= term
.col
;
1041 tnewline(int first_col
)
1045 if (y
== term
.bot
) {
1046 tscrollup(term
.top
, 1);
1050 tmoveto(first_col
? 0 : term
.c
.x
, y
);
1056 char *p
= csiescseq
.buf
, *np
;
1065 csiescseq
.buf
[csiescseq
.len
] = '\0';
1066 while (p
< csiescseq
.buf
+csiescseq
.len
) {
1068 v
= strtol(p
, &np
, 10);
1071 if (v
== LONG_MAX
|| v
== LONG_MIN
)
1073 csiescseq
.arg
[csiescseq
.narg
++] = v
;
1075 if (*p
!= ';' || csiescseq
.narg
== ESC_ARG_SIZ
)
1079 csiescseq
.mode
[0] = *p
++;
1080 csiescseq
.mode
[1] = (p
< csiescseq
.buf
+csiescseq
.len
) ? *p
: '\0';
1083 /* for absolute user moves, when decom is set */
1085 tmoveato(int x
, int y
)
1087 tmoveto(x
, y
+ ((term
.c
.state
& CURSOR_ORIGIN
) ? term
.top
: 0));
1091 tmoveto(int x
, int y
)
1095 if (term
.c
.state
& CURSOR_ORIGIN
) {
1100 maxy
= term
.row
- 1;
1102 term
.c
.state
&= ~CURSOR_WRAPNEXT
;
1103 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1104 term
.c
.y
= LIMIT(y
, miny
, maxy
);
1108 tsetchar(Rune u
, Glyph
*attr
, int x
, int y
)
1110 static char *vt100_0
[62] = { /* 0x41 - 0x7e */
1111 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1112 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1113 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1114 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1115 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1116 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1117 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1118 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1122 * The table is proudly stolen from rxvt.
1124 if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0
&&
1125 BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u
- 0x41])
1126 utf8decode(vt100_0
[u
- 0x41], &u
, UTF_SIZ
);
1128 if (term
.line
[y
][x
].mode
& ATTR_WIDE
) {
1129 if (x
+1 < term
.col
) {
1130 term
.line
[y
][x
+1].u
= ' ';
1131 term
.line
[y
][x
+1].mode
&= ~ATTR_WDUMMY
;
1133 } else if (term
.line
[y
][x
].mode
& ATTR_WDUMMY
) {
1134 term
.line
[y
][x
-1].u
= ' ';
1135 term
.line
[y
][x
-1].mode
&= ~ATTR_WIDE
;
1139 term
.line
[y
][x
] = *attr
;
1140 term
.line
[y
][x
].u
= u
;
1144 tclearregion(int x1
, int y1
, int x2
, int y2
)
1150 temp
= x1
, x1
= x2
, x2
= temp
;
1152 temp
= y1
, y1
= y2
, y2
= temp
;
1154 LIMIT(x1
, 0, term
.col
-1);
1155 LIMIT(x2
, 0, term
.col
-1);
1156 LIMIT(y1
, 0, term
.row
-1);
1157 LIMIT(y2
, 0, term
.row
-1);
1159 for (y
= y1
; y
<= y2
; y
++) {
1161 for (x
= x1
; x
<= x2
; x
++) {
1162 gp
= &term
.line
[y
][x
];
1165 gp
->fg
= term
.c
.attr
.fg
;
1166 gp
->bg
= term
.c
.attr
.bg
;
1179 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1183 size
= term
.col
- src
;
1184 line
= term
.line
[term
.c
.y
];
1186 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1187 tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1196 LIMIT(n
, 0, term
.col
- term
.c
.x
);
1200 size
= term
.col
- dst
;
1201 line
= term
.line
[term
.c
.y
];
1203 memmove(&line
[dst
], &line
[src
], size
* sizeof(Glyph
));
1204 tclearregion(src
, term
.c
.y
, dst
- 1, term
.c
.y
);
1208 tinsertblankline(int n
)
1210 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1211 tscrolldown(term
.c
.y
, n
);
1217 if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
))
1218 tscrollup(term
.c
.y
, n
);
1222 tdefcolor(int *attr
, int *npar
, int l
)
1227 switch (attr
[*npar
+ 1]) {
1228 case 2: /* direct color in RGB space */
1229 if (*npar
+ 4 >= l
) {
1231 "erresc(38): Incorrect number of parameters (%d)\n",
1235 r
= attr
[*npar
+ 2];
1236 g
= attr
[*npar
+ 3];
1237 b
= attr
[*npar
+ 4];
1239 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255))
1240 fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n",
1243 idx
= TRUECOLOR(r
, g
, b
);
1245 case 5: /* indexed color */
1246 if (*npar
+ 2 >= l
) {
1248 "erresc(38): Incorrect number of parameters (%d)\n",
1253 if (!BETWEEN(attr
[*npar
], 0, 255))
1254 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]);
1258 case 0: /* implemented defined (only foreground) */
1259 case 1: /* transparent */
1260 case 3: /* direct color in CMY space */
1261 case 4: /* direct color in CMYK space */
1264 "erresc(38): gfx attr %d unknown\n", attr
[*npar
]);
1272 tsetattr(int *attr
, int l
)
1277 for (i
= 0; i
< l
; i
++) {
1280 term
.c
.attr
.mode
&= ~(
1289 term
.c
.attr
.fg
= defaultfg
;
1290 term
.c
.attr
.bg
= defaultbg
;
1293 term
.c
.attr
.mode
|= ATTR_BOLD
;
1296 term
.c
.attr
.mode
|= ATTR_FAINT
;
1299 term
.c
.attr
.mode
|= ATTR_ITALIC
;
1302 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1304 case 5: /* slow blink */
1306 case 6: /* rapid blink */
1307 term
.c
.attr
.mode
|= ATTR_BLINK
;
1310 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1313 term
.c
.attr
.mode
|= ATTR_INVISIBLE
;
1316 term
.c
.attr
.mode
|= ATTR_STRUCK
;
1319 term
.c
.attr
.mode
&= ~(ATTR_BOLD
| ATTR_FAINT
);
1322 term
.c
.attr
.mode
&= ~ATTR_ITALIC
;
1325 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1328 term
.c
.attr
.mode
&= ~ATTR_BLINK
;
1331 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1334 term
.c
.attr
.mode
&= ~ATTR_INVISIBLE
;
1337 term
.c
.attr
.mode
&= ~ATTR_STRUCK
;
1340 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1341 term
.c
.attr
.fg
= idx
;
1344 term
.c
.attr
.fg
= defaultfg
;
1347 if ((idx
= tdefcolor(attr
, &i
, l
)) >= 0)
1348 term
.c
.attr
.bg
= idx
;
1351 term
.c
.attr
.bg
= defaultbg
;
1354 if (BETWEEN(attr
[i
], 30, 37)) {
1355 term
.c
.attr
.fg
= attr
[i
] - 30;
1356 } else if (BETWEEN(attr
[i
], 40, 47)) {
1357 term
.c
.attr
.bg
= attr
[i
] - 40;
1358 } else if (BETWEEN(attr
[i
], 90, 97)) {
1359 term
.c
.attr
.fg
= attr
[i
] - 90 + 8;
1360 } else if (BETWEEN(attr
[i
], 100, 107)) {
1361 term
.c
.attr
.bg
= attr
[i
] - 100 + 8;
1364 "erresc(default): gfx attr %d unknown\n",
1365 attr
[i
]), csidump();
1373 tsetscroll(int t
, int b
)
1377 LIMIT(t
, 0, term
.row
-1);
1378 LIMIT(b
, 0, term
.row
-1);
1389 tsetmode(int priv
, int set
, int *args
, int narg
)
1394 for (lim
= args
+ narg
; args
< lim
; ++args
) {
1397 case 1: /* DECCKM -- Cursor key */
1398 MODBIT(term
.mode
, set
, MODE_APPCURSOR
);
1400 case 5: /* DECSCNM -- Reverse video */
1402 MODBIT(term
.mode
, set
, MODE_REVERSE
);
1403 if (mode
!= term
.mode
)
1406 case 6: /* DECOM -- Origin */
1407 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
);
1410 case 7: /* DECAWM -- Auto wrap */
1411 MODBIT(term
.mode
, set
, MODE_WRAP
);
1413 case 0: /* Error (IGNORED) */
1414 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1415 case 3: /* DECCOLM -- Column (IGNORED) */
1416 case 4: /* DECSCLM -- Scroll (IGNORED) */
1417 case 8: /* DECARM -- Auto repeat (IGNORED) */
1418 case 18: /* DECPFF -- Printer feed (IGNORED) */
1419 case 19: /* DECPEX -- Printer extent (IGNORED) */
1420 case 42: /* DECNRCM -- National characters (IGNORED) */
1421 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1423 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1424 MODBIT(term
.mode
, !set
, MODE_HIDE
);
1426 case 9: /* X10 mouse compatibility mode */
1427 xsetpointermotion(0);
1428 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1429 MODBIT(term
.mode
, set
, MODE_MOUSEX10
);
1431 case 1000: /* 1000: report button press */
1432 xsetpointermotion(0);
1433 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1434 MODBIT(term
.mode
, set
, MODE_MOUSEBTN
);
1436 case 1002: /* 1002: report motion on button press */
1437 xsetpointermotion(0);
1438 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1439 MODBIT(term
.mode
, set
, MODE_MOUSEMOTION
);
1441 case 1003: /* 1003: enable all mouse motions */
1442 xsetpointermotion(set
);
1443 MODBIT(term
.mode
, 0, MODE_MOUSE
);
1444 MODBIT(term
.mode
, set
, MODE_MOUSEMANY
);
1446 case 1004: /* 1004: send focus events to tty */
1447 MODBIT(term
.mode
, set
, MODE_FOCUS
);
1449 case 1006: /* 1006: extended reporting mode */
1450 MODBIT(term
.mode
, set
, MODE_MOUSESGR
);
1453 MODBIT(term
.mode
, set
, MODE_8BIT
);
1455 case 1049: /* swap screen & set/restore cursor as xterm */
1456 if (!allowaltscreen
)
1458 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1460 case 47: /* swap screen */
1462 if (!allowaltscreen
)
1464 alt
= IS_SET(MODE_ALTSCREEN
);
1466 tclearregion(0, 0, term
.col
-1,
1469 if (set
^ alt
) /* set is always 1 or 0 */
1475 tcursor((set
) ? CURSOR_SAVE
: CURSOR_LOAD
);
1477 case 2004: /* 2004: bracketed paste mode */
1478 MODBIT(term
.mode
, set
, MODE_BRCKTPASTE
);
1480 /* Not implemented mouse modes. See comments there. */
1481 case 1001: /* mouse highlight mode; can hang the
1482 terminal by design when implemented. */
1483 case 1005: /* UTF-8 mouse mode; will confuse
1484 applications not supporting UTF-8
1486 case 1015: /* urxvt mangled mouse mode; incompatible
1487 and can be mistaken for other control
1491 "erresc: unknown private set/reset mode %d\n",
1497 case 0: /* Error (IGNORED) */
1499 case 2: /* KAM -- keyboard action */
1500 MODBIT(term
.mode
, set
, MODE_KBDLOCK
);
1502 case 4: /* IRM -- Insertion-replacement */
1503 MODBIT(term
.mode
, set
, MODE_INSERT
);
1505 case 12: /* SRM -- Send/Receive */
1506 MODBIT(term
.mode
, !set
, MODE_ECHO
);
1508 case 20: /* LNM -- Linefeed/new line */
1509 MODBIT(term
.mode
, set
, MODE_CRLF
);
1513 "erresc: unknown set/reset mode %d\n",
1527 switch (csiescseq
.mode
[0]) {
1530 fprintf(stderr
, "erresc: unknown csi ");
1534 case '@': /* ICH -- Insert <n> blank char */
1535 DEFAULT(csiescseq
.arg
[0], 1);
1536 tinsertblank(csiescseq
.arg
[0]);
1538 case 'A': /* CUU -- Cursor <n> Up */
1539 DEFAULT(csiescseq
.arg
[0], 1);
1540 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]);
1542 case 'B': /* CUD -- Cursor <n> Down */
1543 case 'e': /* VPR --Cursor <n> Down */
1544 DEFAULT(csiescseq
.arg
[0], 1);
1545 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]);
1547 case 'i': /* MC -- Media Copy */
1548 switch (csiescseq
.arg
[0]) {
1553 tdumpline(term
.c
.y
);
1559 term
.mode
&= ~MODE_PRINT
;
1562 term
.mode
|= MODE_PRINT
;
1566 case 'c': /* DA -- Device Attributes */
1567 if (csiescseq
.arg
[0] == 0)
1568 ttywrite(vtiden
, strlen(vtiden
), 0);
1570 case 'C': /* CUF -- Cursor <n> Forward */
1571 case 'a': /* HPR -- Cursor <n> Forward */
1572 DEFAULT(csiescseq
.arg
[0], 1);
1573 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
);
1575 case 'D': /* CUB -- Cursor <n> Backward */
1576 DEFAULT(csiescseq
.arg
[0], 1);
1577 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
);
1579 case 'E': /* CNL -- Cursor <n> Down and first col */
1580 DEFAULT(csiescseq
.arg
[0], 1);
1581 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]);
1583 case 'F': /* CPL -- Cursor <n> Up and first col */
1584 DEFAULT(csiescseq
.arg
[0], 1);
1585 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]);
1587 case 'g': /* TBC -- Tabulation clear */
1588 switch (csiescseq
.arg
[0]) {
1589 case 0: /* clear current tab stop */
1590 term
.tabs
[term
.c
.x
] = 0;
1592 case 3: /* clear all the tabs */
1593 memset(term
.tabs
, 0, term
.col
* sizeof(*term
.tabs
));
1599 case 'G': /* CHA -- Move to <col> */
1601 DEFAULT(csiescseq
.arg
[0], 1);
1602 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
);
1604 case 'H': /* CUP -- Move to <row> <col> */
1606 DEFAULT(csiescseq
.arg
[0], 1);
1607 DEFAULT(csiescseq
.arg
[1], 1);
1608 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1);
1610 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1611 DEFAULT(csiescseq
.arg
[0], 1);
1612 tputtab(csiescseq
.arg
[0]);
1614 case 'J': /* ED -- Clear screen */
1616 switch (csiescseq
.arg
[0]) {
1618 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1619 if (term
.c
.y
< term
.row
-1) {
1620 tclearregion(0, term
.c
.y
+1, term
.col
-1,
1626 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1);
1627 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1630 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1636 case 'K': /* EL -- Clear line */
1637 switch (csiescseq
.arg
[0]) {
1639 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1,
1643 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1646 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1650 case 'S': /* SU -- Scroll <n> line up */
1651 DEFAULT(csiescseq
.arg
[0], 1);
1652 tscrollup(term
.top
, csiescseq
.arg
[0]);
1654 case 'T': /* SD -- Scroll <n> line down */
1655 DEFAULT(csiescseq
.arg
[0], 1);
1656 tscrolldown(term
.top
, csiescseq
.arg
[0]);
1658 case 'L': /* IL -- Insert <n> blank lines */
1659 DEFAULT(csiescseq
.arg
[0], 1);
1660 tinsertblankline(csiescseq
.arg
[0]);
1662 case 'l': /* RM -- Reset Mode */
1663 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
);
1665 case 'M': /* DL -- Delete <n> lines */
1666 DEFAULT(csiescseq
.arg
[0], 1);
1667 tdeleteline(csiescseq
.arg
[0]);
1669 case 'X': /* ECH -- Erase <n> char */
1670 DEFAULT(csiescseq
.arg
[0], 1);
1671 tclearregion(term
.c
.x
, term
.c
.y
,
1672 term
.c
.x
+ csiescseq
.arg
[0] - 1, term
.c
.y
);
1674 case 'P': /* DCH -- Delete <n> char */
1675 DEFAULT(csiescseq
.arg
[0], 1);
1676 tdeletechar(csiescseq
.arg
[0]);
1678 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1679 DEFAULT(csiescseq
.arg
[0], 1);
1680 tputtab(-csiescseq
.arg
[0]);
1682 case 'd': /* VPA -- Move to <row> */
1683 DEFAULT(csiescseq
.arg
[0], 1);
1684 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1);
1686 case 'h': /* SM -- Set terminal mode */
1687 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
);
1689 case 'm': /* SGR -- Terminal attribute (color) */
1690 tsetattr(csiescseq
.arg
, csiescseq
.narg
);
1692 case 'n': /* DSR – Device Status Report (cursor position) */
1693 if (csiescseq
.arg
[0] == 6) {
1694 len
= snprintf(buf
, sizeof(buf
),"\033[%i;%iR",
1695 term
.c
.y
+1, term
.c
.x
+1);
1696 ttywrite(buf
, len
, 0);
1699 case 'r': /* DECSTBM -- Set Scrolling Region */
1700 if (csiescseq
.priv
) {
1703 DEFAULT(csiescseq
.arg
[0], 1);
1704 DEFAULT(csiescseq
.arg
[1], term
.row
);
1705 tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1);
1709 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1710 tcursor(CURSOR_SAVE
);
1712 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1713 tcursor(CURSOR_LOAD
);
1716 switch (csiescseq
.mode
[1]) {
1717 case 'q': /* DECSCUSR -- Set Cursor Style */
1718 if (xsetcursor(csiescseq
.arg
[0]))
1734 fprintf(stderr
, "ESC[");
1735 for (i
= 0; i
< csiescseq
.len
; i
++) {
1736 c
= csiescseq
.buf
[i
] & 0xff;
1739 } else if (c
== '\n') {
1740 fprintf(stderr
, "(\\n)");
1741 } else if (c
== '\r') {
1742 fprintf(stderr
, "(\\r)");
1743 } else if (c
== 0x1b) {
1744 fprintf(stderr
, "(\\e)");
1746 fprintf(stderr
, "(%02x)", c
);
1755 memset(&csiescseq
, 0, sizeof(csiescseq
));
1764 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
1766 par
= (narg
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0;
1768 switch (strescseq
.type
) {
1769 case ']': /* OSC -- Operating System Command */
1775 xsettitle(strescseq
.args
[1]);
1781 dec
= base64dec(strescseq
.args
[2]);
1786 fprintf(stderr
, "erresc: invalid base64\n");
1790 case 4: /* color set */
1793 p
= strescseq
.args
[2];
1795 case 104: /* color reset, here p = NULL */
1796 j
= (narg
> 1) ? atoi(strescseq
.args
[1]) : -1;
1797 if (xsetcolorname(j
, p
)) {
1798 fprintf(stderr
, "erresc: invalid color %s\n", p
);
1801 * TODO if defaultbg color is changed, borders
1809 case 'k': /* old title set compatibility */
1810 xsettitle(strescseq
.args
[0]);
1812 case 'P': /* DCS -- Device Control String */
1813 term
.mode
|= ESC_DCS
;
1814 case '_': /* APC -- Application Program Command */
1815 case '^': /* PM -- Privacy Message */
1819 fprintf(stderr
, "erresc: unknown str ");
1827 char *p
= strescseq
.buf
;
1830 strescseq
.buf
[strescseq
.len
] = '\0';
1835 while (strescseq
.narg
< STR_ARG_SIZ
) {
1836 strescseq
.args
[strescseq
.narg
++] = p
;
1837 while ((c
= *p
) != ';' && c
!= '\0')
1851 fprintf(stderr
, "ESC%c", strescseq
.type
);
1852 for (i
= 0; i
< strescseq
.len
; i
++) {
1853 c
= strescseq
.buf
[i
] & 0xff;
1857 } else if (isprint(c
)) {
1859 } else if (c
== '\n') {
1860 fprintf(stderr
, "(\\n)");
1861 } else if (c
== '\r') {
1862 fprintf(stderr
, "(\\r)");
1863 } else if (c
== 0x1b) {
1864 fprintf(stderr
, "(\\e)");
1866 fprintf(stderr
, "(%02x)", c
);
1869 fprintf(stderr
, "ESC\\\n");
1875 memset(&strescseq
, 0, sizeof(strescseq
));
1879 sendbreak(const Arg
*arg
)
1881 if (tcsendbreak(cmdfd
, 0))
1882 perror("Error sending break");
1886 tprinter(char *s
, size_t len
)
1888 if (iofd
!= -1 && xwrite(iofd
, s
, len
) < 0) {
1889 perror("Error writing to output file");
1896 iso14755(const Arg
*arg
)
1899 char *us
, *e
, codepoint
[9], uc
[UTF_SIZ
];
1900 unsigned long utf32
;
1902 if (!(p
= popen(ISO14755CMD
, "r")))
1905 us
= fgets(codepoint
, sizeof(codepoint
), p
);
1908 if (!us
|| *us
== '\0' || *us
== '-' || strlen(us
) > 7)
1910 if ((utf32
= strtoul(us
, &e
, 16)) == ULONG_MAX
||
1911 (*e
!= '\n' && *e
!= '\0'))
1914 ttywrite(uc
, utf8encode(utf32
, uc
), 1);
1918 toggleprinter(const Arg
*arg
)
1920 term
.mode
^= MODE_PRINT
;
1924 printscreen(const Arg
*arg
)
1930 printsel(const Arg
*arg
)
1940 if ((ptr
= getsel())) {
1941 tprinter(ptr
, strlen(ptr
));
1952 bp
= &term
.line
[n
][0];
1953 end
= &bp
[MIN(tlinelen(n
), term
.col
) - 1];
1954 if (bp
!= end
|| bp
->u
!= ' ') {
1955 for ( ;bp
<= end
; ++bp
)
1956 tprinter(buf
, utf8encode(bp
->u
, buf
));
1966 for (i
= 0; i
< term
.row
; ++i
)
1976 while (x
< term
.col
&& n
--)
1977 for (++x
; x
< term
.col
&& !term
.tabs
[x
]; ++x
)
1980 while (x
> 0 && n
++)
1981 for (--x
; x
> 0 && !term
.tabs
[x
]; --x
)
1984 term
.c
.x
= LIMIT(x
, 0, term
.col
-1);
1988 tdefutf8(char ascii
)
1991 term
.mode
|= MODE_UTF8
;
1992 else if (ascii
== '@')
1993 term
.mode
&= ~MODE_UTF8
;
1997 tdeftran(char ascii
)
1999 static char cs
[] = "0B";
2000 static int vcs
[] = {CS_GRAPHIC0
, CS_USA
};
2003 if ((p
= strchr(cs
, ascii
)) == NULL
) {
2004 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
2006 term
.trantbl
[term
.icharset
] = vcs
[p
- cs
];
2015 if (c
== '8') { /* DEC screen alignment test. */
2016 for (x
= 0; x
< term
.col
; ++x
) {
2017 for (y
= 0; y
< term
.row
; ++y
)
2018 tsetchar('E', &term
.c
.attr
, x
, y
);
2024 tstrsequence(uchar c
)
2029 case 0x90: /* DCS -- Device Control String */
2031 term
.esc
|= ESC_DCS
;
2033 case 0x9f: /* APC -- Application Program Command */
2036 case 0x9e: /* PM -- Privacy Message */
2039 case 0x9d: /* OSC -- Operating System Command */
2044 term
.esc
|= ESC_STR
;
2048 tcontrolcode(uchar ascii
)
2055 tmoveto(term
.c
.x
-1, term
.c
.y
);
2058 tmoveto(0, term
.c
.y
);
2063 /* go to first col if the mode is set */
2064 tnewline(IS_SET(MODE_CRLF
));
2066 case '\a': /* BEL */
2067 if (term
.esc
& ESC_STR_END
) {
2068 /* backwards compatibility to xterm */
2074 case '\033': /* ESC */
2076 term
.esc
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
);
2077 term
.esc
|= ESC_START
;
2079 case '\016': /* SO (LS1 -- Locking shift 1) */
2080 case '\017': /* SI (LS0 -- Locking shift 0) */
2081 term
.charset
= 1 - (ascii
- '\016');
2083 case '\032': /* SUB */
2084 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2085 case '\030': /* CAN */
2088 case '\005': /* ENQ (IGNORED) */
2089 case '\000': /* NUL (IGNORED) */
2090 case '\021': /* XON (IGNORED) */
2091 case '\023': /* XOFF (IGNORED) */
2092 case 0177: /* DEL (IGNORED) */
2094 case 0x80: /* TODO: PAD */
2095 case 0x81: /* TODO: HOP */
2096 case 0x82: /* TODO: BPH */
2097 case 0x83: /* TODO: NBH */
2098 case 0x84: /* TODO: IND */
2100 case 0x85: /* NEL -- Next line */
2101 tnewline(1); /* always go to first col */
2103 case 0x86: /* TODO: SSA */
2104 case 0x87: /* TODO: ESA */
2106 case 0x88: /* HTS -- Horizontal tab stop */
2107 term
.tabs
[term
.c
.x
] = 1;
2109 case 0x89: /* TODO: HTJ */
2110 case 0x8a: /* TODO: VTS */
2111 case 0x8b: /* TODO: PLD */
2112 case 0x8c: /* TODO: PLU */
2113 case 0x8d: /* TODO: RI */
2114 case 0x8e: /* TODO: SS2 */
2115 case 0x8f: /* TODO: SS3 */
2116 case 0x91: /* TODO: PU1 */
2117 case 0x92: /* TODO: PU2 */
2118 case 0x93: /* TODO: STS */
2119 case 0x94: /* TODO: CCH */
2120 case 0x95: /* TODO: MW */
2121 case 0x96: /* TODO: SPA */
2122 case 0x97: /* TODO: EPA */
2123 case 0x98: /* TODO: SOS */
2124 case 0x99: /* TODO: SGCI */
2126 case 0x9a: /* DECID -- Identify Terminal */
2127 ttywrite(vtiden
, strlen(vtiden
), 0);
2129 case 0x9b: /* TODO: CSI */
2130 case 0x9c: /* TODO: ST */
2132 case 0x90: /* DCS -- Device Control String */
2133 case 0x9d: /* OSC -- Operating System Command */
2134 case 0x9e: /* PM -- Privacy Message */
2135 case 0x9f: /* APC -- Application Program Command */
2136 tstrsequence(ascii
);
2139 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2140 term
.esc
&= ~(ESC_STR_END
|ESC_STR
);
2144 * returns 1 when the sequence is finished and it hasn't to read
2145 * more characters for this sequence, otherwise 0
2148 eschandle(uchar ascii
)
2152 term
.esc
|= ESC_CSI
;
2155 term
.esc
|= ESC_TEST
;
2158 term
.esc
|= ESC_UTF8
;
2160 case 'P': /* DCS -- Device Control String */
2161 case '_': /* APC -- Application Program Command */
2162 case '^': /* PM -- Privacy Message */
2163 case ']': /* OSC -- Operating System Command */
2164 case 'k': /* old title set compatibility */
2165 tstrsequence(ascii
);
2167 case 'n': /* LS2 -- Locking shift 2 */
2168 case 'o': /* LS3 -- Locking shift 3 */
2169 term
.charset
= 2 + (ascii
- 'n');
2171 case '(': /* GZD4 -- set primary charset G0 */
2172 case ')': /* G1D4 -- set secondary charset G1 */
2173 case '*': /* G2D4 -- set tertiary charset G2 */
2174 case '+': /* G3D4 -- set quaternary charset G3 */
2175 term
.icharset
= ascii
- '(';
2176 term
.esc
|= ESC_ALTCHARSET
;
2178 case 'D': /* IND -- Linefeed */
2179 if (term
.c
.y
== term
.bot
) {
2180 tscrollup(term
.top
, 1);
2182 tmoveto(term
.c
.x
, term
.c
.y
+1);
2185 case 'E': /* NEL -- Next line */
2186 tnewline(1); /* always go to first col */
2188 case 'H': /* HTS -- Horizontal tab stop */
2189 term
.tabs
[term
.c
.x
] = 1;
2191 case 'M': /* RI -- Reverse index */
2192 if (term
.c
.y
== term
.top
) {
2193 tscrolldown(term
.top
, 1);
2195 tmoveto(term
.c
.x
, term
.c
.y
-1);
2198 case 'Z': /* DECID -- Identify Terminal */
2199 ttywrite(vtiden
, strlen(vtiden
), 0);
2201 case 'c': /* RIS -- Reset to inital state */
2206 case '=': /* DECPAM -- Application keypad */
2207 term
.mode
|= MODE_APPKEYPAD
;
2209 case '>': /* DECPNM -- Normal keypad */
2210 term
.mode
&= ~MODE_APPKEYPAD
;
2212 case '7': /* DECSC -- Save Cursor */
2213 tcursor(CURSOR_SAVE
);
2215 case '8': /* DECRC -- Restore Cursor */
2216 tcursor(CURSOR_LOAD
);
2218 case '\\': /* ST -- String Terminator */
2219 if (term
.esc
& ESC_STR_END
)
2223 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2224 (uchar
) ascii
, isprint(ascii
)? ascii
:'.');
2238 control
= ISCONTROL(u
);
2239 if (!IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2243 len
= utf8encode(u
, c
);
2244 if (!control
&& (width
= wcwidth(u
)) == -1) {
2245 memcpy(c
, "\357\277\275", 4); /* UTF_INVALID */
2250 if (IS_SET(MODE_PRINT
))
2254 * STR sequence must be checked before anything else
2255 * because it uses all following characters until it
2256 * receives a ESC, a SUB, a ST or any other C1 control
2259 if (term
.esc
& ESC_STR
) {
2260 if (u
== '\a' || u
== 030 || u
== 032 || u
== 033 ||
2262 term
.esc
&= ~(ESC_START
|ESC_STR
|ESC_DCS
);
2263 if (IS_SET(MODE_SIXEL
)) {
2264 /* TODO: render sixel */;
2265 term
.mode
&= ~MODE_SIXEL
;
2268 term
.esc
|= ESC_STR_END
;
2269 goto check_control_code
;
2273 if (IS_SET(MODE_SIXEL
)) {
2274 /* TODO: implement sixel mode */
2277 if (term
.esc
&ESC_DCS
&& strescseq
.len
== 0 && u
== 'q')
2278 term
.mode
|= MODE_SIXEL
;
2280 if (strescseq
.len
+len
>= sizeof(strescseq
.buf
)-1) {
2282 * Here is a bug in terminals. If the user never sends
2283 * some code to stop the str or esc command, then st
2284 * will stop responding. But this is better than
2285 * silently failing with unknown characters. At least
2286 * then users will report back.
2288 * In the case users ever get fixed, here is the code:
2297 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
);
2298 strescseq
.len
+= len
;
2304 * Actions of control codes must be performed as soon they arrive
2305 * because they can be embedded inside a control sequence, and
2306 * they must not cause conflicts with sequences.
2311 * control codes are not shown ever
2314 } else if (term
.esc
& ESC_START
) {
2315 if (term
.esc
& ESC_CSI
) {
2316 csiescseq
.buf
[csiescseq
.len
++] = u
;
2317 if (BETWEEN(u
, 0x40, 0x7E)
2318 || csiescseq
.len
>= \
2319 sizeof(csiescseq
.buf
)-1) {
2325 } else if (term
.esc
& ESC_UTF8
) {
2327 } else if (term
.esc
& ESC_ALTCHARSET
) {
2329 } else if (term
.esc
& ESC_TEST
) {
2334 /* sequence already finished */
2338 * All characters which form part of a sequence are not
2343 if (sel
.ob
.x
!= -1 && BETWEEN(term
.c
.y
, sel
.ob
.y
, sel
.oe
.y
))
2346 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2347 if (IS_SET(MODE_WRAP
) && (term
.c
.state
& CURSOR_WRAPNEXT
)) {
2348 gp
->mode
|= ATTR_WRAP
;
2350 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2353 if (IS_SET(MODE_INSERT
) && term
.c
.x
+width
< term
.col
)
2354 memmove(gp
+width
, gp
, (term
.col
- term
.c
.x
- width
) * sizeof(Glyph
));
2356 if (term
.c
.x
+width
> term
.col
) {
2358 gp
= &term
.line
[term
.c
.y
][term
.c
.x
];
2361 tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
);
2364 gp
->mode
|= ATTR_WIDE
;
2365 if (term
.c
.x
+1 < term
.col
) {
2367 gp
[1].mode
= ATTR_WDUMMY
;
2370 if (term
.c
.x
+width
< term
.col
) {
2371 tmoveto(term
.c
.x
+width
, term
.c
.y
);
2373 term
.c
.state
|= CURSOR_WRAPNEXT
;
2378 twrite(const char *buf
, int buflen
, int show_ctrl
)
2384 for (n
= 0; n
< buflen
; n
+= charsize
) {
2385 if (IS_SET(MODE_UTF8
) && !IS_SET(MODE_SIXEL
)) {
2386 /* process a complete utf8 char */
2387 charsize
= utf8decode(buf
+ n
, &u
, buflen
- n
);
2394 if (show_ctrl
&& ISCONTROL(u
)) {
2399 } else if (u
!= '\n' && u
!= '\r' && u
!= '\t') {
2410 tresize(int col
, int row
)
2413 int minrow
= MIN(row
, term
.row
);
2414 int mincol
= MIN(col
, term
.col
);
2418 if (col
< 1 || row
< 1) {
2420 "tresize: error resizing to %dx%d\n", col
, row
);
2425 * slide screen to keep cursor where we expect it -
2426 * tscrollup would work here, but we can optimize to
2427 * memmove because we're freeing the earlier lines
2429 for (i
= 0; i
<= term
.c
.y
- row
; i
++) {
2433 /* ensure that both src and dst are not NULL */
2435 memmove(term
.line
, term
.line
+ i
, row
* sizeof(Line
));
2436 memmove(term
.alt
, term
.alt
+ i
, row
* sizeof(Line
));
2438 for (i
+= row
; i
< term
.row
; i
++) {
2443 /* resize to new height */
2444 term
.line
= xrealloc(term
.line
, row
* sizeof(Line
));
2445 term
.alt
= xrealloc(term
.alt
, row
* sizeof(Line
));
2446 term
.dirty
= xrealloc(term
.dirty
, row
* sizeof(*term
.dirty
));
2447 term
.tabs
= xrealloc(term
.tabs
, col
* sizeof(*term
.tabs
));
2449 /* resize each row to new width, zero-pad if needed */
2450 for (i
= 0; i
< minrow
; i
++) {
2451 term
.line
[i
] = xrealloc(term
.line
[i
], col
* sizeof(Glyph
));
2452 term
.alt
[i
] = xrealloc(term
.alt
[i
], col
* sizeof(Glyph
));
2455 /* allocate any new rows */
2456 for (/* i = minrow */; i
< row
; i
++) {
2457 term
.line
[i
] = xmalloc(col
* sizeof(Glyph
));
2458 term
.alt
[i
] = xmalloc(col
* sizeof(Glyph
));
2460 if (col
> term
.col
) {
2461 bp
= term
.tabs
+ term
.col
;
2463 memset(bp
, 0, sizeof(*term
.tabs
) * (col
- term
.col
));
2464 while (--bp
> term
.tabs
&& !*bp
)
2466 for (bp
+= tabspaces
; bp
< term
.tabs
+ col
; bp
+= tabspaces
)
2469 /* update terminal size */
2472 /* reset scrolling region */
2473 tsetscroll(0, row
-1);
2474 /* make use of the LIMIT in tmoveto */
2475 tmoveto(term
.c
.x
, term
.c
.y
);
2476 /* Clearing both screens (it makes dirty all lines) */
2478 for (i
= 0; i
< 2; i
++) {
2479 if (mincol
< col
&& 0 < minrow
) {
2480 tclearregion(mincol
, 0, col
- 1, minrow
- 1);
2482 if (0 < col
&& minrow
< row
) {
2483 tclearregion(0, minrow
, col
- 1, row
- 1);
2486 tcursor(CURSOR_LOAD
);
2505 numlock(const Arg
*dummy
)