Xinqi Bao's Git
   1 /* See LICENSE for license details. */ 
  12 #include <sys/ioctl.h> 
  13 #include <sys/select.h> 
  14 #include <sys/types.h> 
  25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 
  27 #elif defined(__FreeBSD__) || defined(__DragonFly__) 
  32 #define UTF_INVALID   0xFFFD 
  34 #define ESC_BUF_SIZ   (128*UTF_SIZ) 
  35 #define ESC_ARG_SIZ   16 
  36 #define STR_BUF_SIZ   ESC_BUF_SIZ 
  37 #define STR_ARG_SIZ   ESC_ARG_SIZ 
  40 #define IS_SET(flag)            ((term.mode & (flag)) != 0) 
  41 #define ISCONTROLC0(c)          (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) 
  42 #define ISCONTROLC1(c)          (BETWEEN(c, 0x80, 0x9f)) 
  43 #define ISCONTROL(c)            (ISCONTROLC0(c) || ISCONTROLC1(c)) 
  44 #define ISDELIM(u)              (u && wcschr(worddelimiters, u)) 
  49         MODE_ALTSCREEN   
= 1 << 2, 
  56 enum cursor_movement 
{ 
  80         ESC_STR        
= 4,  /* DCS, OSC, PM, APC */ 
  82         ESC_STR_END    
= 16, /* a final string was encountered */ 
  83         ESC_TEST       
= 32, /* Enter in test mode */ 
  88         Glyph attr
; /* current char attributes */ 
  99          * Selection variables: 
 100          * nb – normalized coordinates of the beginning of the selection 
 101          * ne – normalized coordinates of the end of the selection 
 102          * ob – original coordinates of the beginning of the selection 
 103          * oe – original coordinates of the end of the selection 
 112 /* Internal representation of the screen */ 
 114         int row
;      /* nb row */ 
 115         int col
;      /* nb col */ 
 116         Line 
*line
;   /* screen */ 
 117         Line 
*alt
;    /* alternate screen */ 
 118         int *dirty
;   /* dirtyness of lines */ 
 119         TCursor c
;    /* cursor */ 
 120         int ocx
;      /* old cursor col */ 
 121         int ocy
;      /* old cursor row */ 
 122         int top
;      /* top    scroll limit */ 
 123         int bot
;      /* bottom scroll limit */ 
 124         int mode
;     /* terminal mode flags */ 
 125         int esc
;      /* escape state flags */ 
 126         char trantbl
[4]; /* charset table translation */ 
 127         int charset
;  /* current charset */ 
 128         int icharset
; /* selected charset for sequence */ 
 130         Rune lastc
;   /* last printed char outside of sequence, 0 if control */ 
 133 /* CSI Escape sequence structs */ 
 134 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 
 136         char buf
[ESC_BUF_SIZ
]; /* raw string */ 
 137         size_t len
;            /* raw string length */ 
 139         int arg
[ESC_ARG_SIZ
]; 
 140         int narg
;              /* nb of args */ 
 144 /* STR Escape sequence structs */ 
 145 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ 
 147         char type
;             /* ESC type ... */ 
 148         char *buf
;             /* allocated raw string */ 
 149         size_t siz
;            /* allocation size */ 
 150         size_t len
;            /* raw string length */ 
 151         char *args
[STR_ARG_SIZ
]; 
 152         int narg
;              /* nb of args */ 
 155 static void execsh(char *, char **); 
 156 static void stty(char **); 
 157 static void sigchld(int); 
 158 static void ttywriteraw(const char *, size_t); 
 160 static void csidump(void); 
 161 static void csihandle(void); 
 162 static void csiparse(void); 
 163 static void csireset(void); 
 164 static int eschandle(uchar
); 
 165 static void strdump(void); 
 166 static void strhandle(void); 
 167 static void strparse(void); 
 168 static void strreset(void); 
 170 static void tprinter(char *, size_t); 
 171 static void tdumpsel(void); 
 172 static void tdumpline(int); 
 173 static void tdump(void); 
 174 static void tclearregion(int, int, int, int); 
 175 static void tcursor(int); 
 176 static void tdeletechar(int); 
 177 static void tdeleteline(int); 
 178 static void tinsertblank(int); 
 179 static void tinsertblankline(int); 
 180 static int tlinelen(int); 
 181 static void tmoveto(int, int); 
 182 static void tmoveato(int, int); 
 183 static void tnewline(int); 
 184 static void tputtab(int); 
 185 static void tputc(Rune
); 
 186 static void treset(void); 
 187 static void tscrollup(int, int); 
 188 static void tscrolldown(int, int); 
 189 static void tsetattr(int *, int); 
 190 static void tsetchar(Rune
, Glyph 
*, int, int); 
 191 static void tsetdirt(int, int); 
 192 static void tsetscroll(int, int); 
 193 static void tswapscreen(void); 
 194 static void tsetmode(int, int, int *, int); 
 195 static int twrite(const char *, int, int); 
 196 static void tfulldirt(void); 
 197 static void tcontrolcode(uchar 
); 
 198 static void tdectest(char ); 
 199 static void tdefutf8(char); 
 200 static int32_t tdefcolor(int *, int *, int); 
 201 static void tdeftran(char); 
 202 static void tstrsequence(uchar
); 
 204 static void drawregion(int, int, int, int); 
 206 static void selnormalize(void); 
 207 static void selscroll(int, int); 
 208 static void selsnap(int *, int *, int); 
 210 static size_t utf8decode(const char *, Rune 
*, size_t); 
 211 static Rune 
utf8decodebyte(char, size_t *); 
 212 static char utf8encodebyte(Rune
, size_t); 
 213 static size_t utf8validate(Rune 
*, size_t); 
 215 static char *base64dec(const char *); 
 216 static char base64dec_getc(const char **); 
 218 static ssize_t 
xwrite(int, const char *, size_t); 
 222 static Selection sel
; 
 223 static CSIEscape csiescseq
; 
 224 static STREscape strescseq
; 
 229 static uchar utfbyte
[UTF_SIZ 
+ 1] = {0x80,    0, 0xC0, 0xE0, 0xF0}; 
 230 static uchar utfmask
[UTF_SIZ 
+ 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 
 231 static Rune utfmin
[UTF_SIZ 
+ 1] = {       0,    0,  0x80,  0x800,  0x10000}; 
 232 static Rune utfmax
[UTF_SIZ 
+ 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 
 235 xwrite(int fd
, const char *s
, size_t len
) 
 241                 r 
= write(fd
, s
, len
); 
 256         if (!(p 
= malloc(len
))) 
 257                 die("malloc: %s\n", strerror(errno
)); 
 263 xrealloc(void *p
, size_t len
) 
 265         if ((p 
= realloc(p
, len
)) == NULL
) 
 266                 die("realloc: %s\n", strerror(errno
)); 
 274         if ((s 
= strdup(s
)) == NULL
) 
 275                 die("strdup: %s\n", strerror(errno
)); 
 281 utf8decode(const char *c
, Rune 
*u
, size_t clen
) 
 283         size_t i
, j
, len
, type
; 
 289         udecoded 
= utf8decodebyte(c
[0], &len
); 
 290         if (!BETWEEN(len
, 1, UTF_SIZ
)) 
 292         for (i 
= 1, j 
= 1; i 
< clen 
&& j 
< len
; ++i
, ++j
) { 
 293                 udecoded 
= (udecoded 
<< 6) | utf8decodebyte(c
[i
], &type
); 
 300         utf8validate(u
, len
); 
 306 utf8decodebyte(char c
, size_t *i
) 
 308         for (*i 
= 0; *i 
< LEN(utfmask
); ++(*i
)) 
 309                 if (((uchar
)c 
& utfmask
[*i
]) == utfbyte
[*i
]) 
 310                         return (uchar
)c 
& ~utfmask
[*i
]; 
 316 utf8encode(Rune u
, char *c
) 
 320         len 
= utf8validate(&u
, 0); 
 324         for (i 
= len 
- 1; i 
!= 0; --i
) { 
 325                 c
[i
] = utf8encodebyte(u
, 0); 
 328         c
[0] = utf8encodebyte(u
, len
); 
 334 utf8encodebyte(Rune u
, size_t i
) 
 336         return utfbyte
[i
] | (u 
& ~utfmask
[i
]); 
 340 utf8validate(Rune 
*u
, size_t i
) 
 342         if (!BETWEEN(*u
, utfmin
[i
], utfmax
[i
]) || BETWEEN(*u
, 0xD800, 0xDFFF)) 
 344         for (i 
= 1; *u 
> utfmax
[i
]; ++i
) 
 350 static const char base64_digits
[] = { 
 351         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 352         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 
 353         63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 
 354         2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 
 355         22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 
 356         35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 
 357         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 358         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 359         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 360         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 361         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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 
 366 base64dec_getc(const char **src
) 
 368         while (**src 
&& !isprint(**src
)) 
 370         return **src 
? *((*src
)++) : '=';  /* emulate padding if string ends */ 
 374 base64dec(const char *src
) 
 376         size_t in_len 
= strlen(src
); 
 380                 in_len 
+= 4 - (in_len 
% 4); 
 381         result 
= dst 
= xmalloc(in_len 
/ 4 * 3 + 1); 
 383                 int a 
= base64_digits
[(unsigned char) base64dec_getc(&src
)]; 
 384                 int b 
= base64_digits
[(unsigned char) base64dec_getc(&src
)]; 
 385                 int c 
= base64_digits
[(unsigned char) base64dec_getc(&src
)]; 
 386                 int d 
= base64_digits
[(unsigned char) base64dec_getc(&src
)]; 
 388                 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ 
 389                 if (a 
== -1 || b 
== -1) 
 392                 *dst
++ = (a 
<< 2) | ((b 
& 0x30) >> 4); 
 395                 *dst
++ = ((b 
& 0x0f) << 4) | ((c 
& 0x3c) >> 2); 
 398                 *dst
++ = ((c 
& 0x03) << 6) | d
; 
 417         if (term
.line
[y
][i 
- 1].mode 
& ATTR_WRAP
) 
 420         while (i 
> 0 && term
.line
[y
][i 
- 1].u 
== ' ') 
 427 selstart(int col
, int row
, int snap
) 
 430         sel
.mode 
= SEL_EMPTY
; 
 431         sel
.type 
= SEL_REGULAR
; 
 432         sel
.alt 
= IS_SET(MODE_ALTSCREEN
); 
 434         sel
.oe
.x 
= sel
.ob
.x 
= col
; 
 435         sel
.oe
.y 
= sel
.ob
.y 
= row
; 
 439                 sel
.mode 
= SEL_READY
; 
 440         tsetdirt(sel
.nb
.y
, sel
.ne
.y
); 
 444 selextend(int col
, int row
, int type
, int done
) 
 446         int oldey
, oldex
, oldsby
, oldsey
, oldtype
; 
 448         if (sel
.mode 
== SEL_IDLE
) 
 450         if (done 
&& sel
.mode 
== SEL_EMPTY
) { 
 466         if (oldey 
!= sel
.oe
.y 
|| oldex 
!= sel
.oe
.x 
|| oldtype 
!= sel
.type 
|| sel
.mode 
== SEL_EMPTY
) 
 467                 tsetdirt(MIN(sel
.nb
.y
, oldsby
), MAX(sel
.ne
.y
, oldsey
)); 
 469         sel
.mode 
= done 
? SEL_IDLE 
: SEL_READY
; 
 477         if (sel
.type 
== SEL_REGULAR 
&& sel
.ob
.y 
!= sel
.oe
.y
) { 
 478                 sel
.nb
.x 
= sel
.ob
.y 
< sel
.oe
.y 
? sel
.ob
.x 
: sel
.oe
.x
; 
 479                 sel
.ne
.x 
= sel
.ob
.y 
< sel
.oe
.y 
? sel
.oe
.x 
: sel
.ob
.x
; 
 481                 sel
.nb
.x 
= MIN(sel
.ob
.x
, sel
.oe
.x
); 
 482                 sel
.ne
.x 
= MAX(sel
.ob
.x
, sel
.oe
.x
); 
 484         sel
.nb
.y 
= MIN(sel
.ob
.y
, sel
.oe
.y
); 
 485         sel
.ne
.y 
= MAX(sel
.ob
.y
, sel
.oe
.y
); 
 487         selsnap(&sel
.nb
.x
, &sel
.nb
.y
, -1); 
 488         selsnap(&sel
.ne
.x
, &sel
.ne
.y
, +1); 
 490         /* expand selection over line breaks */ 
 491         if (sel
.type 
== SEL_RECTANGULAR
) 
 493         i 
= tlinelen(sel
.nb
.y
); 
 496         if (tlinelen(sel
.ne
.y
) <= sel
.ne
.x
) 
 497                 sel
.ne
.x 
= term
.col 
- 1; 
 501 selected(int x
, int y
) 
 503         if (sel
.mode 
== SEL_EMPTY 
|| sel
.ob
.x 
== -1 || 
 504                         sel
.alt 
!= IS_SET(MODE_ALTSCREEN
)) 
 507         if (sel
.type 
== SEL_RECTANGULAR
) 
 508                 return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
) 
 509                     && BETWEEN(x
, sel
.nb
.x
, sel
.ne
.x
); 
 511         return BETWEEN(y
, sel
.nb
.y
, sel
.ne
.y
) 
 512             && (y 
!= sel
.nb
.y 
|| x 
>= sel
.nb
.x
) 
 513             && (y 
!= sel
.ne
.y 
|| x 
<= sel
.ne
.x
); 
 517 selsnap(int *x
, int *y
, int direction
) 
 519         int newx
, newy
, xt
, yt
; 
 520         int delim
, prevdelim
; 
 526                  * Snap around if the word wraps around at the end or 
 527                  * beginning of a line. 
 529                 prevgp 
= &term
.line
[*y
][*x
]; 
 530                 prevdelim 
= ISDELIM(prevgp
->u
); 
 532                         newx 
= *x 
+ direction
; 
 534                         if (!BETWEEN(newx
, 0, term
.col 
- 1)) { 
 536                                 newx 
= (newx 
+ term
.col
) % term
.col
; 
 537                                 if (!BETWEEN(newy
, 0, term
.row 
- 1)) 
 543                                         yt 
= newy
, xt 
= newx
; 
 544                                 if (!(term
.line
[yt
][xt
].mode 
& ATTR_WRAP
)) 
 548                         if (newx 
>= tlinelen(newy
)) 
 551                         gp 
= &term
.line
[newy
][newx
]; 
 552                         delim 
= ISDELIM(gp
->u
); 
 553                         if (!(gp
->mode 
& ATTR_WDUMMY
) && (delim 
!= prevdelim
 
 554                                         || (delim 
&& gp
->u 
!= prevgp
->u
))) 
 565                  * Snap around if the the previous line or the current one 
 566                  * has set ATTR_WRAP at its end. Then the whole next or 
 567                  * previous line will be selected. 
 569                 *x 
= (direction 
< 0) ? 0 : term
.col 
- 1; 
 571                         for (; *y 
> 0; *y 
+= direction
) { 
 572                                 if (!(term
.line
[*y
-1][term
.col
-1].mode
 
 577                 } else if (direction 
> 0) { 
 578                         for (; *y 
< term
.row
-1; *y 
+= direction
) { 
 579                                 if (!(term
.line
[*y
][term
.col
-1].mode
 
 593         int y
, bufsize
, lastx
, linelen
; 
 599         bufsize 
= (term
.col
+1) * (sel
.ne
.y
-sel
.nb
.y
+1) * UTF_SIZ
; 
 600         ptr 
= str 
= xmalloc(bufsize
); 
 602         /* append every set & selected glyph to the selection */ 
 603         for (y 
= sel
.nb
.y
; y 
<= sel
.ne
.y
; y
++) { 
 604                 if ((linelen 
= tlinelen(y
)) == 0) { 
 609                 if (sel
.type 
== SEL_RECTANGULAR
) { 
 610                         gp 
= &term
.line
[y
][sel
.nb
.x
]; 
 613                         gp 
= &term
.line
[y
][sel
.nb
.y 
== y 
? sel
.nb
.x 
: 0]; 
 614                         lastx 
= (sel
.ne
.y 
== y
) ? sel
.ne
.x 
: term
.col
-1; 
 616                 last 
= &term
.line
[y
][MIN(lastx
, linelen
-1)]; 
 617                 while (last 
>= gp 
&& last
->u 
== ' ') 
 620                 for ( ; gp 
<= last
; ++gp
) { 
 621                         if (gp
->mode 
& ATTR_WDUMMY
) 
 624                         ptr 
+= utf8encode(gp
->u
, ptr
); 
 628                  * Copy and pasting of line endings is inconsistent 
 629                  * in the inconsistent terminal and GUI world. 
 630                  * The best solution seems like to produce '\n' when 
 631                  * something is copied from st and convert '\n' to 
 632                  * '\r', when something to be pasted is received by 
 634                  * FIXME: Fix the computer world. 
 636                 if ((y 
< sel
.ne
.y 
|| lastx 
>= linelen
) && 
 637                     (!(last
->mode 
& ATTR_WRAP
) || sel
.type 
== SEL_RECTANGULAR
)) 
 651         tsetdirt(sel
.nb
.y
, sel
.ne
.y
); 
 655 die(const char *errstr
, ...) 
 659         va_start(ap
, errstr
); 
 660         vfprintf(stderr
, errstr
, ap
); 
 666 execsh(char *cmd
, char **args
) 
 668         char *sh
, *prog
, *arg
; 
 669         const struct passwd 
*pw
; 
 672         if ((pw 
= getpwuid(getuid())) == NULL
) { 
 674                         die("getpwuid: %s\n", strerror(errno
)); 
 676                         die("who are you?\n"); 
 679         if ((sh 
= getenv("SHELL")) == NULL
) 
 680                 sh 
= (pw
->pw_shell
[0]) ? pw
->pw_shell 
: cmd
; 
 687                 arg 
= utmp 
? utmp 
: sh
; 
 695         DEFAULT(args
, ((char *[]) {prog
, arg
, NULL
})); 
 700         setenv("LOGNAME", pw
->pw_name
, 1); 
 701         setenv("USER", pw
->pw_name
, 1); 
 702         setenv("SHELL", sh
, 1); 
 703         setenv("HOME", pw
->pw_dir
, 1); 
 704         setenv("TERM", termname
, 1); 
 706         signal(SIGCHLD
, SIG_DFL
); 
 707         signal(SIGHUP
, SIG_DFL
); 
 708         signal(SIGINT
, SIG_DFL
); 
 709         signal(SIGQUIT
, SIG_DFL
); 
 710         signal(SIGTERM
, SIG_DFL
); 
 711         signal(SIGALRM
, SIG_DFL
); 
 723         if ((p 
= waitpid(pid
, &stat
, WNOHANG
)) < 0) 
 724                 die("waiting for pid %hd failed: %s\n", pid
, strerror(errno
)); 
 729         if (WIFEXITED(stat
) && WEXITSTATUS(stat
)) 
 730                 die("child exited with status %d\n", WEXITSTATUS(stat
)); 
 731         else if (WIFSIGNALED(stat
)) 
 732                 die("child terminated due to signal %d\n", WTERMSIG(stat
)); 
 739         char cmd
[_POSIX_ARG_MAX
], **p
, *q
, *s
; 
 742         if ((n 
= strlen(stty_args
)) > sizeof(cmd
)-1) 
 743                 die("incorrect stty parameters\n"); 
 744         memcpy(cmd
, stty_args
, n
); 
 746         siz 
= sizeof(cmd
) - n
; 
 747         for (p 
= args
; p 
&& (s 
= *p
); ++p
) { 
 748                 if ((n 
= strlen(s
)) > siz
-1) 
 749                         die("stty parameter length too long\n"); 
 756         if (system(cmd
) != 0) 
 757                 perror("Couldn't call stty"); 
 761 ttynew(char *line
, char *cmd
, char *out
, char **args
) 
 766                 term
.mode 
|= MODE_PRINT
; 
 767                 iofd 
= (!strcmp(out
, "-")) ? 
 768                           1 : open(out
, O_WRONLY 
| O_CREAT
, 0666); 
 770                         fprintf(stderr
, "Error opening %s:%s\n", 
 771                                 out
, strerror(errno
)); 
 776                 if ((cmdfd 
= open(line
, O_RDWR
)) < 0) 
 777                         die("open line '%s' failed: %s\n", 
 778                             line
, strerror(errno
)); 
 784         /* seems to work fine on linux, openbsd and freebsd */ 
 785         if (openpty(&m
, &s
, NULL
, NULL
, NULL
) < 0) 
 786                 die("openpty failed: %s\n", strerror(errno
)); 
 788         switch (pid 
= fork()) { 
 790                 die("fork failed: %s\n", strerror(errno
)); 
 794                 setsid(); /* create a new process group */ 
 798                 if (ioctl(s
, TIOCSCTTY
, NULL
) < 0) 
 799                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno
)); 
 803                 if (pledge("stdio getpw proc exec", NULL
) == -1) 
 810                 if (pledge("stdio rpath tty proc", NULL
) == -1) 
 815                 signal(SIGCHLD
, sigchld
); 
 824         static char buf
[BUFSIZ
]; 
 825         static int buflen 
= 0; 
 828         /* append read bytes to unprocessed bytes */ 
 829         ret 
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
); 
 835                 die("couldn't read from shell: %s\n", strerror(errno
)); 
 838                 written 
= twrite(buf
, buflen
, 0); 
 840                 /* keep any incomplete UTF-8 byte sequence for the next call */ 
 842                         memmove(buf
, buf 
+ written
, buflen
); 
 848 ttywrite(const char *s
, size_t n
, int may_echo
) 
 852         if (may_echo 
&& IS_SET(MODE_ECHO
)) 
 855         if (!IS_SET(MODE_CRLF
)) { 
 860         /* This is similar to how the kernel handles ONLCR for ttys */ 
 864                         ttywriteraw("\r\n", 2); 
 866                         next 
= memchr(s
, '\r', n
); 
 867                         DEFAULT(next
, s 
+ n
); 
 868                         ttywriteraw(s
, next 
- s
); 
 876 ttywriteraw(const char *s
, size_t n
) 
 883          * Remember that we are using a pty, which might be a modem line. 
 884          * Writing too much will clog the line. That's why we are doing this 
 886          * FIXME: Migrate the world to Plan 9. 
 894                 /* Check if we can write. */ 
 895                 if (pselect(cmdfd
+1, &rfd
, &wfd
, NULL
, NULL
, NULL
) < 0) { 
 898                         die("select failed: %s\n", strerror(errno
)); 
 900                 if (FD_ISSET(cmdfd
, &wfd
)) { 
 902                          * Only write the bytes written by ttywrite() or the 
 903                          * default of 256. This seems to be a reasonable value 
 904                          * for a serial line. Bigger values might clog the I/O. 
 906                         if ((r 
= write(cmdfd
, s
, (n 
< lim
)? n 
: lim
)) < 0) 
 910                                  * We weren't able to write out everything. 
 911                                  * This means the buffer is getting full 
 919                                 /* All bytes have been written. */ 
 923                 if (FD_ISSET(cmdfd
, &rfd
)) 
 929         die("write error on tty: %s\n", strerror(errno
)); 
 933 ttyresize(int tw
, int th
) 
 941         if (ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0) 
 942                 fprintf(stderr
, "Couldn't set window size: %s\n", strerror(errno
)); 
 948         /* Send SIGHUP to shell */ 
 957         for (i 
= 0; i 
< term
.row
-1; i
++) { 
 958                 for (j 
= 0; j 
< term
.col
-1; j
++) { 
 959                         if (term
.line
[i
][j
].mode 
& attr
) 
 968 tsetdirt(int top
, int bot
) 
 972         LIMIT(top
, 0, term
.row
-1); 
 973         LIMIT(bot
, 0, term
.row
-1); 
 975         for (i 
= top
; i 
<= bot
; i
++) 
 980 tsetdirtattr(int attr
) 
 984         for (i 
= 0; i 
< term
.row
-1; i
++) { 
 985                 for (j 
= 0; j 
< term
.col
-1; j
++) { 
 986                         if (term
.line
[i
][j
].mode 
& attr
) { 
 997         tsetdirt(0, term
.row
-1); 
1003         static TCursor c
[2]; 
1004         int alt 
= IS_SET(MODE_ALTSCREEN
); 
1006         if (mode 
== CURSOR_SAVE
) { 
1008         } else if (mode 
== CURSOR_LOAD
) { 
1010                 tmoveto(c
[alt
].x
, c
[alt
].y
); 
1019         term
.c 
= (TCursor
){{ 
1023         }, .x 
= 0, .y 
= 0, .state 
= CURSOR_DEFAULT
}; 
1025         memset(term
.tabs
, 0, term
.col 
* sizeof(*term
.tabs
)); 
1026         for (i 
= tabspaces
; i 
< term
.col
; i 
+= tabspaces
) 
1029         term
.bot 
= term
.row 
- 1; 
1030         term
.mode 
= MODE_WRAP
|MODE_UTF8
; 
1031         memset(term
.trantbl
, CS_USA
, sizeof(term
.trantbl
)); 
1034         for (i 
= 0; i 
< 2; i
++) { 
1036                 tcursor(CURSOR_SAVE
); 
1037                 tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1043 tnew(int col
, int row
) 
1045         term 
= (Term
){ .c 
= { .attr 
= { .fg 
= defaultfg
, .bg 
= defaultbg 
} } }; 
1053         Line 
*tmp 
= term
.line
; 
1055         term
.line 
= term
.alt
; 
1057         term
.mode 
^= MODE_ALTSCREEN
; 
1062 tscrolldown(int orig
, int n
) 
1067         LIMIT(n
, 0, term
.bot
-orig
+1); 
1069         tsetdirt(orig
, term
.bot
-n
); 
1070         tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
); 
1072         for (i 
= term
.bot
; i 
>= orig
+n
; i
--) { 
1073                 temp 
= term
.line
[i
]; 
1074                 term
.line
[i
] = term
.line
[i
-n
]; 
1075                 term
.line
[i
-n
] = temp
; 
1082 tscrollup(int orig
, int n
) 
1087         LIMIT(n
, 0, term
.bot
-orig
+1); 
1089         tclearregion(0, orig
, term
.col
-1, orig
+n
-1); 
1090         tsetdirt(orig
+n
, term
.bot
); 
1092         for (i 
= orig
; i 
<= term
.bot
-n
; i
++) { 
1093                 temp 
= term
.line
[i
]; 
1094                 term
.line
[i
] = term
.line
[i
+n
]; 
1095                 term
.line
[i
+n
] = temp
; 
1098         selscroll(orig
, -n
); 
1102 selscroll(int orig
, int n
) 
1107         if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
) != BETWEEN(sel
.ne
.y
, orig
, term
.bot
)) { 
1109         } else if (BETWEEN(sel
.nb
.y
, orig
, term
.bot
)) { 
1112                 if (sel
.ob
.y 
< term
.top 
|| sel
.ob
.y 
> term
.bot 
|| 
1113                     sel
.oe
.y 
< term
.top 
|| sel
.oe
.y 
> term
.bot
) { 
1122 tnewline(int first_col
) 
1126         if (y 
== term
.bot
) { 
1127                 tscrollup(term
.top
, 1); 
1131         tmoveto(first_col 
? 0 : term
.c
.x
, y
); 
1137         char *p 
= csiescseq
.buf
, *np
; 
1146         csiescseq
.buf
[csiescseq
.len
] = '\0'; 
1147         while (p 
< csiescseq
.buf
+csiescseq
.len
) { 
1149                 v 
= strtol(p
, &np
, 10); 
1152                 if (v 
== LONG_MAX 
|| v 
== LONG_MIN
) 
1154                 csiescseq
.arg
[csiescseq
.narg
++] = v
; 
1156                 if (*p 
!= ';' || csiescseq
.narg 
== ESC_ARG_SIZ
) 
1160         csiescseq
.mode
[0] = *p
++; 
1161         csiescseq
.mode
[1] = (p 
< csiescseq
.buf
+csiescseq
.len
) ? *p 
: '\0'; 
1164 /* for absolute user moves, when decom is set */ 
1166 tmoveato(int x
, int y
) 
1168         tmoveto(x
, y 
+ ((term
.c
.state 
& CURSOR_ORIGIN
) ? term
.top
: 0)); 
1172 tmoveto(int x
, int y
) 
1176         if (term
.c
.state 
& CURSOR_ORIGIN
) { 
1181                 maxy 
= term
.row 
- 1; 
1183         term
.c
.state 
&= ~CURSOR_WRAPNEXT
; 
1184         term
.c
.x 
= LIMIT(x
, 0, term
.col
-1); 
1185         term
.c
.y 
= LIMIT(y
, miny
, maxy
); 
1189 tsetchar(Rune u
, Glyph 
*attr
, int x
, int y
) 
1191         static char *vt100_0
[62] = { /* 0x41 - 0x7e */ 
1192                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 
1193                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 
1194                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 
1195                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 
1196                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 
1197                 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 
1198                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 
1199                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 
1203          * The table is proudly stolen from rxvt. 
1205         if (term
.trantbl
[term
.charset
] == CS_GRAPHIC0 
&& 
1206            BETWEEN(u
, 0x41, 0x7e) && vt100_0
[u 
- 0x41]) 
1207                 utf8decode(vt100_0
[u 
- 0x41], &u
, UTF_SIZ
); 
1209         if (term
.line
[y
][x
].mode 
& ATTR_WIDE
) { 
1210                 if (x
+1 < term
.col
) { 
1211                         term
.line
[y
][x
+1].u 
= ' '; 
1212                         term
.line
[y
][x
+1].mode 
&= ~ATTR_WDUMMY
; 
1214         } else if (term
.line
[y
][x
].mode 
& ATTR_WDUMMY
) { 
1215                 term
.line
[y
][x
-1].u 
= ' '; 
1216                 term
.line
[y
][x
-1].mode 
&= ~ATTR_WIDE
; 
1220         term
.line
[y
][x
] = *attr
; 
1221         term
.line
[y
][x
].u 
= u
; 
1225 tclearregion(int x1
, int y1
, int x2
, int y2
) 
1231                 temp 
= x1
, x1 
= x2
, x2 
= temp
; 
1233                 temp 
= y1
, y1 
= y2
, y2 
= temp
; 
1235         LIMIT(x1
, 0, term
.col
-1); 
1236         LIMIT(x2
, 0, term
.col
-1); 
1237         LIMIT(y1
, 0, term
.row
-1); 
1238         LIMIT(y2
, 0, term
.row
-1); 
1240         for (y 
= y1
; y 
<= y2
; y
++) { 
1242                 for (x 
= x1
; x 
<= x2
; x
++) { 
1243                         gp 
= &term
.line
[y
][x
]; 
1246                         gp
->fg 
= term
.c
.attr
.fg
; 
1247                         gp
->bg 
= term
.c
.attr
.bg
; 
1260         LIMIT(n
, 0, term
.col 
- term
.c
.x
); 
1264         size 
= term
.col 
- src
; 
1265         line 
= term
.line
[term
.c
.y
]; 
1267         memmove(&line
[dst
], &line
[src
], size 
* sizeof(Glyph
)); 
1268         tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1277         LIMIT(n
, 0, term
.col 
- term
.c
.x
); 
1281         size 
= term
.col 
- dst
; 
1282         line 
= term
.line
[term
.c
.y
]; 
1284         memmove(&line
[dst
], &line
[src
], size 
* sizeof(Glyph
)); 
1285         tclearregion(src
, term
.c
.y
, dst 
- 1, term
.c
.y
); 
1289 tinsertblankline(int n
) 
1291         if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
)) 
1292                 tscrolldown(term
.c
.y
, n
); 
1298         if (BETWEEN(term
.c
.y
, term
.top
, term
.bot
)) 
1299                 tscrollup(term
.c
.y
, n
); 
1303 tdefcolor(int *attr
, int *npar
, int l
) 
1308         switch (attr
[*npar 
+ 1]) { 
1309         case 2: /* direct color in RGB space */ 
1310                 if (*npar 
+ 4 >= l
) { 
1312                                 "erresc(38): Incorrect number of parameters (%d)\n", 
1316                 r 
= attr
[*npar 
+ 2]; 
1317                 g 
= attr
[*npar 
+ 3]; 
1318                 b 
= attr
[*npar 
+ 4]; 
1320                 if (!BETWEEN(r
, 0, 255) || !BETWEEN(g
, 0, 255) || !BETWEEN(b
, 0, 255)) 
1321                         fprintf(stderr
, "erresc: bad rgb color (%u,%u,%u)\n", 
1324                         idx 
= TRUECOLOR(r
, g
, b
); 
1326         case 5: /* indexed color */ 
1327                 if (*npar 
+ 2 >= l
) { 
1329                                 "erresc(38): Incorrect number of parameters (%d)\n", 
1334                 if (!BETWEEN(attr
[*npar
], 0, 255)) 
1335                         fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[*npar
]); 
1339         case 0: /* implemented defined (only foreground) */ 
1340         case 1: /* transparent */ 
1341         case 3: /* direct color in CMY space */ 
1342         case 4: /* direct color in CMYK space */ 
1345                         "erresc(38): gfx attr %d unknown\n", attr
[*npar
]); 
1353 tsetattr(int *attr
, int l
) 
1358         for (i 
= 0; i 
< l
; i
++) { 
1361                         term
.c
.attr
.mode 
&= ~( 
1370                         term
.c
.attr
.fg 
= defaultfg
; 
1371                         term
.c
.attr
.bg 
= defaultbg
; 
1374                         term
.c
.attr
.mode 
|= ATTR_BOLD
; 
1377                         term
.c
.attr
.mode 
|= ATTR_FAINT
; 
1380                         term
.c
.attr
.mode 
|= ATTR_ITALIC
; 
1383                         term
.c
.attr
.mode 
|= ATTR_UNDERLINE
; 
1385                 case 5: /* slow blink */ 
1387                 case 6: /* rapid blink */ 
1388                         term
.c
.attr
.mode 
|= ATTR_BLINK
; 
1391                         term
.c
.attr
.mode 
|= ATTR_REVERSE
; 
1394                         term
.c
.attr
.mode 
|= ATTR_INVISIBLE
; 
1397                         term
.c
.attr
.mode 
|= ATTR_STRUCK
; 
1400                         term
.c
.attr
.mode 
&= ~(ATTR_BOLD 
| ATTR_FAINT
); 
1403                         term
.c
.attr
.mode 
&= ~ATTR_ITALIC
; 
1406                         term
.c
.attr
.mode 
&= ~ATTR_UNDERLINE
; 
1409                         term
.c
.attr
.mode 
&= ~ATTR_BLINK
; 
1412                         term
.c
.attr
.mode 
&= ~ATTR_REVERSE
; 
1415                         term
.c
.attr
.mode 
&= ~ATTR_INVISIBLE
; 
1418                         term
.c
.attr
.mode 
&= ~ATTR_STRUCK
; 
1421                         if ((idx 
= tdefcolor(attr
, &i
, l
)) >= 0) 
1422                                 term
.c
.attr
.fg 
= idx
; 
1425                         term
.c
.attr
.fg 
= defaultfg
; 
1428                         if ((idx 
= tdefcolor(attr
, &i
, l
)) >= 0) 
1429                                 term
.c
.attr
.bg 
= idx
; 
1432                         term
.c
.attr
.bg 
= defaultbg
; 
1435                         if (BETWEEN(attr
[i
], 30, 37)) { 
1436                                 term
.c
.attr
.fg 
= attr
[i
] - 30; 
1437                         } else if (BETWEEN(attr
[i
], 40, 47)) { 
1438                                 term
.c
.attr
.bg 
= attr
[i
] - 40; 
1439                         } else if (BETWEEN(attr
[i
], 90, 97)) { 
1440                                 term
.c
.attr
.fg 
= attr
[i
] - 90 + 8; 
1441                         } else if (BETWEEN(attr
[i
], 100, 107)) { 
1442                                 term
.c
.attr
.bg 
= attr
[i
] - 100 + 8; 
1445                                         "erresc(default): gfx attr %d unknown\n", 
1455 tsetscroll(int t
, int b
) 
1459         LIMIT(t
, 0, term
.row
-1); 
1460         LIMIT(b
, 0, term
.row
-1); 
1471 tsetmode(int priv
, int set
, int *args
, int narg
) 
1475         for (lim 
= args 
+ narg
; args 
< lim
; ++args
) { 
1478                         case 1: /* DECCKM -- Cursor key */ 
1479                                 xsetmode(set
, MODE_APPCURSOR
); 
1481                         case 5: /* DECSCNM -- Reverse video */ 
1482                                 xsetmode(set
, MODE_REVERSE
); 
1484                         case 6: /* DECOM -- Origin */ 
1485                                 MODBIT(term
.c
.state
, set
, CURSOR_ORIGIN
); 
1488                         case 7: /* DECAWM -- Auto wrap */ 
1489                                 MODBIT(term
.mode
, set
, MODE_WRAP
); 
1491                         case 0:  /* Error (IGNORED) */ 
1492                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */ 
1493                         case 3:  /* DECCOLM -- Column  (IGNORED) */ 
1494                         case 4:  /* DECSCLM -- Scroll (IGNORED) */ 
1495                         case 8:  /* DECARM -- Auto repeat (IGNORED) */ 
1496                         case 18: /* DECPFF -- Printer feed (IGNORED) */ 
1497                         case 19: /* DECPEX -- Printer extent (IGNORED) */ 
1498                         case 42: /* DECNRCM -- National characters (IGNORED) */ 
1499                         case 12: /* att610 -- Start blinking cursor (IGNORED) */ 
1501                         case 25: /* DECTCEM -- Text Cursor Enable Mode */ 
1502                                 xsetmode(!set
, MODE_HIDE
); 
1504                         case 9:    /* X10 mouse compatibility mode */ 
1505                                 xsetpointermotion(0); 
1506                                 xsetmode(0, MODE_MOUSE
); 
1507                                 xsetmode(set
, MODE_MOUSEX10
); 
1509                         case 1000: /* 1000: report button press */ 
1510                                 xsetpointermotion(0); 
1511                                 xsetmode(0, MODE_MOUSE
); 
1512                                 xsetmode(set
, MODE_MOUSEBTN
); 
1514                         case 1002: /* 1002: report motion on button press */ 
1515                                 xsetpointermotion(0); 
1516                                 xsetmode(0, MODE_MOUSE
); 
1517                                 xsetmode(set
, MODE_MOUSEMOTION
); 
1519                         case 1003: /* 1003: enable all mouse motions */ 
1520                                 xsetpointermotion(set
); 
1521                                 xsetmode(0, MODE_MOUSE
); 
1522                                 xsetmode(set
, MODE_MOUSEMANY
); 
1524                         case 1004: /* 1004: send focus events to tty */ 
1525                                 xsetmode(set
, MODE_FOCUS
); 
1527                         case 1006: /* 1006: extended reporting mode */ 
1528                                 xsetmode(set
, MODE_MOUSESGR
); 
1531                                 xsetmode(set
, MODE_8BIT
); 
1533                         case 1049: /* swap screen & set/restore cursor as xterm */ 
1534                                 if (!allowaltscreen
) 
1536                                 tcursor((set
) ? CURSOR_SAVE 
: CURSOR_LOAD
); 
1538                         case 47: /* swap screen */ 
1540                                 if (!allowaltscreen
) 
1542                                 alt 
= IS_SET(MODE_ALTSCREEN
); 
1544                                         tclearregion(0, 0, term
.col
-1, 
1547                                 if (set 
^ alt
) /* set is always 1 or 0 */ 
1553                                 tcursor((set
) ? CURSOR_SAVE 
: CURSOR_LOAD
); 
1555                         case 2004: /* 2004: bracketed paste mode */ 
1556                                 xsetmode(set
, MODE_BRCKTPASTE
); 
1558                         /* Not implemented mouse modes. See comments there. */ 
1559                         case 1001: /* mouse highlight mode; can hang the 
1560                                       terminal by design when implemented. */ 
1561                         case 1005: /* UTF-8 mouse mode; will confuse 
1562                                       applications not supporting UTF-8 
1564                         case 1015: /* urxvt mangled mouse mode; incompatible 
1565                                       and can be mistaken for other control 
1570                                         "erresc: unknown private set/reset mode %d\n", 
1576                         case 0:  /* Error (IGNORED) */ 
1579                                 xsetmode(set
, MODE_KBDLOCK
); 
1581                         case 4:  /* IRM -- Insertion-replacement */ 
1582                                 MODBIT(term
.mode
, set
, MODE_INSERT
); 
1584                         case 12: /* SRM -- Send/Receive */ 
1585                                 MODBIT(term
.mode
, !set
, MODE_ECHO
); 
1587                         case 20: /* LNM -- Linefeed/new line */ 
1588                                 MODBIT(term
.mode
, set
, MODE_CRLF
); 
1592                                         "erresc: unknown set/reset mode %d\n", 
1606         switch (csiescseq
.mode
[0]) { 
1609                 fprintf(stderr
, "erresc: unknown csi "); 
1613         case '@': /* ICH -- Insert <n> blank char */ 
1614                 DEFAULT(csiescseq
.arg
[0], 1); 
1615                 tinsertblank(csiescseq
.arg
[0]); 
1617         case 'A': /* CUU -- Cursor <n> Up */ 
1618                 DEFAULT(csiescseq
.arg
[0], 1); 
1619                 tmoveto(term
.c
.x
, term
.c
.y
-csiescseq
.arg
[0]); 
1621         case 'B': /* CUD -- Cursor <n> Down */ 
1622         case 'e': /* VPR --Cursor <n> Down */ 
1623                 DEFAULT(csiescseq
.arg
[0], 1); 
1624                 tmoveto(term
.c
.x
, term
.c
.y
+csiescseq
.arg
[0]); 
1626         case 'i': /* MC -- Media Copy */ 
1627                 switch (csiescseq
.arg
[0]) { 
1632                         tdumpline(term
.c
.y
); 
1638                         term
.mode 
&= ~MODE_PRINT
; 
1641                         term
.mode 
|= MODE_PRINT
; 
1645         case 'c': /* DA -- Device Attributes */ 
1646                 if (csiescseq
.arg
[0] == 0) 
1647                         ttywrite(vtiden
, strlen(vtiden
), 0); 
1649         case 'b': /* REP -- if last char is printable print it <n> more times */ 
1650                 DEFAULT(csiescseq
.arg
[0], 1); 
1652                         while (csiescseq
.arg
[0]-- > 0) 
1655         case 'C': /* CUF -- Cursor <n> Forward */ 
1656         case 'a': /* HPR -- Cursor <n> Forward */ 
1657                 DEFAULT(csiescseq
.arg
[0], 1); 
1658                 tmoveto(term
.c
.x
+csiescseq
.arg
[0], term
.c
.y
); 
1660         case 'D': /* CUB -- Cursor <n> Backward */ 
1661                 DEFAULT(csiescseq
.arg
[0], 1); 
1662                 tmoveto(term
.c
.x
-csiescseq
.arg
[0], term
.c
.y
); 
1664         case 'E': /* CNL -- Cursor <n> Down and first col */ 
1665                 DEFAULT(csiescseq
.arg
[0], 1); 
1666                 tmoveto(0, term
.c
.y
+csiescseq
.arg
[0]); 
1668         case 'F': /* CPL -- Cursor <n> Up and first col */ 
1669                 DEFAULT(csiescseq
.arg
[0], 1); 
1670                 tmoveto(0, term
.c
.y
-csiescseq
.arg
[0]); 
1672         case 'g': /* TBC -- Tabulation clear */ 
1673                 switch (csiescseq
.arg
[0]) { 
1674                 case 0: /* clear current tab stop */ 
1675                         term
.tabs
[term
.c
.x
] = 0; 
1677                 case 3: /* clear all the tabs */ 
1678                         memset(term
.tabs
, 0, term
.col 
* sizeof(*term
.tabs
)); 
1684         case 'G': /* CHA -- Move to <col> */ 
1686                 DEFAULT(csiescseq
.arg
[0], 1); 
1687                 tmoveto(csiescseq
.arg
[0]-1, term
.c
.y
); 
1689         case 'H': /* CUP -- Move to <row> <col> */ 
1691                 DEFAULT(csiescseq
.arg
[0], 1); 
1692                 DEFAULT(csiescseq
.arg
[1], 1); 
1693                 tmoveato(csiescseq
.arg
[1]-1, csiescseq
.arg
[0]-1); 
1695         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 
1696                 DEFAULT(csiescseq
.arg
[0], 1); 
1697                 tputtab(csiescseq
.arg
[0]); 
1699         case 'J': /* ED -- Clear screen */ 
1700                 switch (csiescseq
.arg
[0]) { 
1702                         tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1703                         if (term
.c
.y 
< term
.row
-1) { 
1704                                 tclearregion(0, term
.c
.y
+1, term
.col
-1, 
1710                                 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1); 
1711                         tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
); 
1714                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1720         case 'K': /* EL -- Clear line */ 
1721                 switch (csiescseq
.arg
[0]) { 
1723                         tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, 
1727                         tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
); 
1730                         tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1734         case 'S': /* SU -- Scroll <n> line up */ 
1735                 DEFAULT(csiescseq
.arg
[0], 1); 
1736                 tscrollup(term
.top
, csiescseq
.arg
[0]); 
1738         case 'T': /* SD -- Scroll <n> line down */ 
1739                 DEFAULT(csiescseq
.arg
[0], 1); 
1740                 tscrolldown(term
.top
, csiescseq
.arg
[0]); 
1742         case 'L': /* IL -- Insert <n> blank lines */ 
1743                 DEFAULT(csiescseq
.arg
[0], 1); 
1744                 tinsertblankline(csiescseq
.arg
[0]); 
1746         case 'l': /* RM -- Reset Mode */ 
1747                 tsetmode(csiescseq
.priv
, 0, csiescseq
.arg
, csiescseq
.narg
); 
1749         case 'M': /* DL -- Delete <n> lines */ 
1750                 DEFAULT(csiescseq
.arg
[0], 1); 
1751                 tdeleteline(csiescseq
.arg
[0]); 
1753         case 'X': /* ECH -- Erase <n> char */ 
1754                 DEFAULT(csiescseq
.arg
[0], 1); 
1755                 tclearregion(term
.c
.x
, term
.c
.y
, 
1756                                 term
.c
.x 
+ csiescseq
.arg
[0] - 1, term
.c
.y
); 
1758         case 'P': /* DCH -- Delete <n> char */ 
1759                 DEFAULT(csiescseq
.arg
[0], 1); 
1760                 tdeletechar(csiescseq
.arg
[0]); 
1762         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 
1763                 DEFAULT(csiescseq
.arg
[0], 1); 
1764                 tputtab(-csiescseq
.arg
[0]); 
1766         case 'd': /* VPA -- Move to <row> */ 
1767                 DEFAULT(csiescseq
.arg
[0], 1); 
1768                 tmoveato(term
.c
.x
, csiescseq
.arg
[0]-1); 
1770         case 'h': /* SM -- Set terminal mode */ 
1771                 tsetmode(csiescseq
.priv
, 1, csiescseq
.arg
, csiescseq
.narg
); 
1773         case 'm': /* SGR -- Terminal attribute (color) */ 
1774                 tsetattr(csiescseq
.arg
, csiescseq
.narg
); 
1776         case 'n': /* DSR – Device Status Report (cursor position) */ 
1777                 if (csiescseq
.arg
[0] == 6) { 
1778                         len 
= snprintf(buf
, sizeof(buf
), "\033[%i;%iR", 
1779                                         term
.c
.y
+1, term
.c
.x
+1); 
1780                         ttywrite(buf
, len
, 0); 
1783         case 'r': /* DECSTBM -- Set Scrolling Region */ 
1784                 if (csiescseq
.priv
) { 
1787                         DEFAULT(csiescseq
.arg
[0], 1); 
1788                         DEFAULT(csiescseq
.arg
[1], term
.row
); 
1789                         tsetscroll(csiescseq
.arg
[0]-1, csiescseq
.arg
[1]-1); 
1793         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 
1794                 tcursor(CURSOR_SAVE
); 
1796         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 
1797                 tcursor(CURSOR_LOAD
); 
1800                 switch (csiescseq
.mode
[1]) { 
1801                 case 'q': /* DECSCUSR -- Set Cursor Style */ 
1802                         if (xsetcursor(csiescseq
.arg
[0])) 
1818         fprintf(stderr
, "ESC["); 
1819         for (i 
= 0; i 
< csiescseq
.len
; i
++) { 
1820                 c 
= csiescseq
.buf
[i
] & 0xff; 
1823                 } else if (c 
== '\n') { 
1824                         fprintf(stderr
, "(\\n)"); 
1825                 } else if (c 
== '\r') { 
1826                         fprintf(stderr
, "(\\r)"); 
1827                 } else if (c 
== 0x1b) { 
1828                         fprintf(stderr
, "(\\e)"); 
1830                         fprintf(stderr
, "(%02x)", c
); 
1839         memset(&csiescseq
, 0, sizeof(csiescseq
)); 
1845         char *p 
= NULL
, *dec
; 
1848         term
.esc 
&= ~(ESC_STR_END
|ESC_STR
); 
1850         par 
= (narg 
= strescseq
.narg
) ? atoi(strescseq
.args
[0]) : 0; 
1852         switch (strescseq
.type
) { 
1853         case ']': /* OSC -- Operating System Command */ 
1857                                 xsettitle(strescseq
.args
[1]); 
1858                                 xseticontitle(strescseq
.args
[1]); 
1863                                 xseticontitle(strescseq
.args
[1]); 
1867                                 xsettitle(strescseq
.args
[1]); 
1870                         if (narg 
> 2 && allowwindowops
) { 
1871                                 dec 
= base64dec(strescseq
.args
[2]); 
1876                                         fprintf(stderr
, "erresc: invalid base64\n"); 
1880                 case 4: /* color set */ 
1883                         p 
= strescseq
.args
[2]; 
1885                 case 104: /* color reset, here p = NULL */ 
1886                         j 
= (narg 
> 1) ? atoi(strescseq
.args
[1]) : -1; 
1887                         if (xsetcolorname(j
, p
)) { 
1888                                 if (par 
== 104 && narg 
<= 1) 
1889                                         return; /* color reset without parameter */ 
1890                                 fprintf(stderr
, "erresc: invalid color j=%d, p=%s\n", 
1891                                         j
, p 
? p 
: "(null)"); 
1894                                  * TODO if defaultbg color is changed, borders 
1902         case 'k': /* old title set compatibility */ 
1903                 xsettitle(strescseq
.args
[0]); 
1905         case 'P': /* DCS -- Device Control String */ 
1906         case '_': /* APC -- Application Program Command */ 
1907         case '^': /* PM -- Privacy Message */ 
1911         fprintf(stderr
, "erresc: unknown str "); 
1919         char *p 
= strescseq
.buf
; 
1922         strescseq
.buf
[strescseq
.len
] = '\0'; 
1927         while (strescseq
.narg 
< STR_ARG_SIZ
) { 
1928                 strescseq
.args
[strescseq
.narg
++] = p
; 
1929                 while ((c 
= *p
) != ';' && c 
!= '\0') 
1943         fprintf(stderr
, "ESC%c", strescseq
.type
); 
1944         for (i 
= 0; i 
< strescseq
.len
; i
++) { 
1945                 c 
= strescseq
.buf
[i
] & 0xff; 
1949                 } else if (isprint(c
)) { 
1951                 } else if (c 
== '\n') { 
1952                         fprintf(stderr
, "(\\n)"); 
1953                 } else if (c 
== '\r') { 
1954                         fprintf(stderr
, "(\\r)"); 
1955                 } else if (c 
== 0x1b) { 
1956                         fprintf(stderr
, "(\\e)"); 
1958                         fprintf(stderr
, "(%02x)", c
); 
1961         fprintf(stderr
, "ESC\\\n"); 
1967         strescseq 
= (STREscape
){ 
1968                 .buf 
= xrealloc(strescseq
.buf
, STR_BUF_SIZ
), 
1974 sendbreak(const Arg 
*arg
) 
1976         if (tcsendbreak(cmdfd
, 0)) 
1977                 perror("Error sending break"); 
1981 tprinter(char *s
, size_t len
) 
1983         if (iofd 
!= -1 && xwrite(iofd
, s
, len
) < 0) { 
1984                 perror("Error writing to output file"); 
1991 toggleprinter(const Arg 
*arg
) 
1993         term
.mode 
^= MODE_PRINT
; 
1997 printscreen(const Arg 
*arg
) 
2003 printsel(const Arg 
*arg
) 
2013         if ((ptr 
= getsel())) { 
2014                 tprinter(ptr
, strlen(ptr
)); 
2025         bp 
= &term
.line
[n
][0]; 
2026         end 
= &bp
[MIN(tlinelen(n
), term
.col
) - 1]; 
2027         if (bp 
!= end 
|| bp
->u 
!= ' ') { 
2028                 for ( ; bp 
<= end
; ++bp
) 
2029                         tprinter(buf
, utf8encode(bp
->u
, buf
)); 
2039         for (i 
= 0; i 
< term
.row
; ++i
) 
2049                 while (x 
< term
.col 
&& n
--) 
2050                         for (++x
; x 
< term
.col 
&& !term
.tabs
[x
]; ++x
) 
2053                 while (x 
> 0 && n
++) 
2054                         for (--x
; x 
> 0 && !term
.tabs
[x
]; --x
) 
2057         term
.c
.x 
= LIMIT(x
, 0, term
.col
-1); 
2061 tdefutf8(char ascii
) 
2064                 term
.mode 
|= MODE_UTF8
; 
2065         else if (ascii 
== '@') 
2066                 term
.mode 
&= ~MODE_UTF8
; 
2070 tdeftran(char ascii
) 
2072         static char cs
[] = "0B"; 
2073         static int vcs
[] = {CS_GRAPHIC0
, CS_USA
}; 
2076         if ((p 
= strchr(cs
, ascii
)) == NULL
) { 
2077                 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
); 
2079                 term
.trantbl
[term
.icharset
] = vcs
[p 
- cs
]; 
2088         if (c 
== '8') { /* DEC screen alignment test. */ 
2089                 for (x 
= 0; x 
< term
.col
; ++x
) { 
2090                         for (y 
= 0; y 
< term
.row
; ++y
) 
2091                                 tsetchar('E', &term
.c
.attr
, x
, y
); 
2097 tstrsequence(uchar c
) 
2100         case 0x90:   /* DCS -- Device Control String */ 
2103         case 0x9f:   /* APC -- Application Program Command */ 
2106         case 0x9e:   /* PM -- Privacy Message */ 
2109         case 0x9d:   /* OSC -- Operating System Command */ 
2115         term
.esc 
|= ESC_STR
; 
2119 tcontrolcode(uchar ascii
) 
2126                 tmoveto(term
.c
.x
-1, term
.c
.y
); 
2129                 tmoveto(0, term
.c
.y
); 
2134                 /* go to first col if the mode is set */ 
2135                 tnewline(IS_SET(MODE_CRLF
)); 
2137         case '\a':   /* BEL */ 
2138                 if (term
.esc 
& ESC_STR_END
) { 
2139                         /* backwards compatibility to xterm */ 
2145         case '\033': /* ESC */ 
2147                 term
.esc 
&= ~(ESC_CSI
|ESC_ALTCHARSET
|ESC_TEST
); 
2148                 term
.esc 
|= ESC_START
; 
2150         case '\016': /* SO (LS1 -- Locking shift 1) */ 
2151         case '\017': /* SI (LS0 -- Locking shift 0) */ 
2152                 term
.charset 
= 1 - (ascii 
- '\016'); 
2154         case '\032': /* SUB */ 
2155                 tsetchar('?', &term
.c
.attr
, term
.c
.x
, term
.c
.y
); 
2157         case '\030': /* CAN */ 
2160         case '\005': /* ENQ (IGNORED) */ 
2161         case '\000': /* NUL (IGNORED) */ 
2162         case '\021': /* XON (IGNORED) */ 
2163         case '\023': /* XOFF (IGNORED) */ 
2164         case 0177:   /* DEL (IGNORED) */ 
2166         case 0x80:   /* TODO: PAD */ 
2167         case 0x81:   /* TODO: HOP */ 
2168         case 0x82:   /* TODO: BPH */ 
2169         case 0x83:   /* TODO: NBH */ 
2170         case 0x84:   /* TODO: IND */ 
2172         case 0x85:   /* NEL -- Next line */ 
2173                 tnewline(1); /* always go to first col */ 
2175         case 0x86:   /* TODO: SSA */ 
2176         case 0x87:   /* TODO: ESA */ 
2178         case 0x88:   /* HTS -- Horizontal tab stop */ 
2179                 term
.tabs
[term
.c
.x
] = 1; 
2181         case 0x89:   /* TODO: HTJ */ 
2182         case 0x8a:   /* TODO: VTS */ 
2183         case 0x8b:   /* TODO: PLD */ 
2184         case 0x8c:   /* TODO: PLU */ 
2185         case 0x8d:   /* TODO: RI */ 
2186         case 0x8e:   /* TODO: SS2 */ 
2187         case 0x8f:   /* TODO: SS3 */ 
2188         case 0x91:   /* TODO: PU1 */ 
2189         case 0x92:   /* TODO: PU2 */ 
2190         case 0x93:   /* TODO: STS */ 
2191         case 0x94:   /* TODO: CCH */ 
2192         case 0x95:   /* TODO: MW */ 
2193         case 0x96:   /* TODO: SPA */ 
2194         case 0x97:   /* TODO: EPA */ 
2195         case 0x98:   /* TODO: SOS */ 
2196         case 0x99:   /* TODO: SGCI */ 
2198         case 0x9a:   /* DECID -- Identify Terminal */ 
2199                 ttywrite(vtiden
, strlen(vtiden
), 0); 
2201         case 0x9b:   /* TODO: CSI */ 
2202         case 0x9c:   /* TODO: ST */ 
2204         case 0x90:   /* DCS -- Device Control String */ 
2205         case 0x9d:   /* OSC -- Operating System Command */ 
2206         case 0x9e:   /* PM -- Privacy Message */ 
2207         case 0x9f:   /* APC -- Application Program Command */ 
2208                 tstrsequence(ascii
); 
2211         /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 
2212         term
.esc 
&= ~(ESC_STR_END
|ESC_STR
); 
2216  * returns 1 when the sequence is finished and it hasn't to read 
2217  * more characters for this sequence, otherwise 0 
2220 eschandle(uchar ascii
) 
2224                 term
.esc 
|= ESC_CSI
; 
2227                 term
.esc 
|= ESC_TEST
; 
2230                 term
.esc 
|= ESC_UTF8
; 
2232         case 'P': /* DCS -- Device Control String */ 
2233         case '_': /* APC -- Application Program Command */ 
2234         case '^': /* PM -- Privacy Message */ 
2235         case ']': /* OSC -- Operating System Command */ 
2236         case 'k': /* old title set compatibility */ 
2237                 tstrsequence(ascii
); 
2239         case 'n': /* LS2 -- Locking shift 2 */ 
2240         case 'o': /* LS3 -- Locking shift 3 */ 
2241                 term
.charset 
= 2 + (ascii 
- 'n'); 
2243         case '(': /* GZD4 -- set primary charset G0 */ 
2244         case ')': /* G1D4 -- set secondary charset G1 */ 
2245         case '*': /* G2D4 -- set tertiary charset G2 */ 
2246         case '+': /* G3D4 -- set quaternary charset G3 */ 
2247                 term
.icharset 
= ascii 
- '('; 
2248                 term
.esc 
|= ESC_ALTCHARSET
; 
2250         case 'D': /* IND -- Linefeed */ 
2251                 if (term
.c
.y 
== term
.bot
) { 
2252                         tscrollup(term
.top
, 1); 
2254                         tmoveto(term
.c
.x
, term
.c
.y
+1); 
2257         case 'E': /* NEL -- Next line */ 
2258                 tnewline(1); /* always go to first col */ 
2260         case 'H': /* HTS -- Horizontal tab stop */ 
2261                 term
.tabs
[term
.c
.x
] = 1; 
2263         case 'M': /* RI -- Reverse index */ 
2264                 if (term
.c
.y 
== term
.top
) { 
2265                         tscrolldown(term
.top
, 1); 
2267                         tmoveto(term
.c
.x
, term
.c
.y
-1); 
2270         case 'Z': /* DECID -- Identify Terminal */ 
2271                 ttywrite(vtiden
, strlen(vtiden
), 0); 
2273         case 'c': /* RIS -- Reset to initial state */ 
2278         case '=': /* DECPAM -- Application keypad */ 
2279                 xsetmode(1, MODE_APPKEYPAD
); 
2281         case '>': /* DECPNM -- Normal keypad */ 
2282                 xsetmode(0, MODE_APPKEYPAD
); 
2284         case '7': /* DECSC -- Save Cursor */ 
2285                 tcursor(CURSOR_SAVE
); 
2287         case '8': /* DECRC -- Restore Cursor */ 
2288                 tcursor(CURSOR_LOAD
); 
2290         case '\\': /* ST -- String Terminator */ 
2291                 if (term
.esc 
& ESC_STR_END
) 
2295                 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n", 
2296                         (uchar
) ascii
, isprint(ascii
)? ascii
:'.'); 
2310         control 
= ISCONTROL(u
); 
2311         if (u 
< 127 || !IS_SET(MODE_UTF8
)) { 
2315                 len 
= utf8encode(u
, c
); 
2316                 if (!control 
&& (width 
= wcwidth(u
)) == -1) 
2320         if (IS_SET(MODE_PRINT
)) 
2324          * STR sequence must be checked before anything else 
2325          * because it uses all following characters until it 
2326          * receives a ESC, a SUB, a ST or any other C1 control 
2329         if (term
.esc 
& ESC_STR
) { 
2330                 if (u 
== '\a' || u 
== 030 || u 
== 032 || u 
== 033 || 
2332                         term
.esc 
&= ~(ESC_START
|ESC_STR
); 
2333                         term
.esc 
|= ESC_STR_END
; 
2334                         goto check_control_code
; 
2337                 if (strescseq
.len
+len 
>= strescseq
.siz
) { 
2339                          * Here is a bug in terminals. If the user never sends 
2340                          * some code to stop the str or esc command, then st 
2341                          * will stop responding. But this is better than 
2342                          * silently failing with unknown characters. At least 
2343                          * then users will report back. 
2345                          * In the case users ever get fixed, here is the code: 
2351                         if (strescseq
.siz 
> (SIZE_MAX 
- UTF_SIZ
) / 2) 
2354                         strescseq
.buf 
= xrealloc(strescseq
.buf
, strescseq
.siz
); 
2357                 memmove(&strescseq
.buf
[strescseq
.len
], c
, len
); 
2358                 strescseq
.len 
+= len
; 
2364          * Actions of control codes must be performed as soon they arrive 
2365          * because they can be embedded inside a control sequence, and 
2366          * they must not cause conflicts with sequences. 
2371                  * control codes are not shown ever 
2376         } else if (term
.esc 
& ESC_START
) { 
2377                 if (term
.esc 
& ESC_CSI
) { 
2378                         csiescseq
.buf
[csiescseq
.len
++] = u
; 
2379                         if (BETWEEN(u
, 0x40, 0x7E) 
2380                                         || csiescseq
.len 
>= \
 
2381                                         sizeof(csiescseq
.buf
)-1) { 
2387                 } else if (term
.esc 
& ESC_UTF8
) { 
2389                 } else if (term
.esc 
& ESC_ALTCHARSET
) { 
2391                 } else if (term
.esc 
& ESC_TEST
) { 
2396                         /* sequence already finished */ 
2400                  * All characters which form part of a sequence are not 
2405         if (selected(term
.c
.x
, term
.c
.y
)) 
2408         gp 
= &term
.line
[term
.c
.y
][term
.c
.x
]; 
2409         if (IS_SET(MODE_WRAP
) && (term
.c
.state 
& CURSOR_WRAPNEXT
)) { 
2410                 gp
->mode 
|= ATTR_WRAP
; 
2412                 gp 
= &term
.line
[term
.c
.y
][term
.c
.x
]; 
2415         if (IS_SET(MODE_INSERT
) && term
.c
.x
+width 
< term
.col
) 
2416                 memmove(gp
+width
, gp
, (term
.col 
- term
.c
.x 
- width
) * sizeof(Glyph
)); 
2418         if (term
.c
.x
+width 
> term
.col
) { 
2420                 gp 
= &term
.line
[term
.c
.y
][term
.c
.x
]; 
2423         tsetchar(u
, &term
.c
.attr
, term
.c
.x
, term
.c
.y
); 
2427                 gp
->mode 
|= ATTR_WIDE
; 
2428                 if (term
.c
.x
+1 < term
.col
) { 
2430                         gp
[1].mode 
= ATTR_WDUMMY
; 
2433         if (term
.c
.x
+width 
< term
.col
) { 
2434                 tmoveto(term
.c
.x
+width
, term
.c
.y
); 
2436                 term
.c
.state 
|= CURSOR_WRAPNEXT
; 
2441 twrite(const char *buf
, int buflen
, int show_ctrl
) 
2447         for (n 
= 0; n 
< buflen
; n 
+= charsize
) { 
2448                 if (IS_SET(MODE_UTF8
)) { 
2449                         /* process a complete utf8 char */ 
2450                         charsize 
= utf8decode(buf 
+ n
, &u
, buflen 
- n
); 
2457                 if (show_ctrl 
&& ISCONTROL(u
)) { 
2462                         } else if (u 
!= '\n' && u 
!= '\r' && u 
!= '\t') { 
2473 tresize(int col
, int row
) 
2476         int minrow 
= MIN(row
, term
.row
); 
2477         int mincol 
= MIN(col
, term
.col
); 
2481         if (col 
< 1 || row 
< 1) { 
2483                         "tresize: error resizing to %dx%d\n", col
, row
); 
2488          * slide screen to keep cursor where we expect it - 
2489          * tscrollup would work here, but we can optimize to 
2490          * memmove because we're freeing the earlier lines 
2492         for (i 
= 0; i 
<= term
.c
.y 
- row
; i
++) { 
2496         /* ensure that both src and dst are not NULL */ 
2498                 memmove(term
.line
, term
.line 
+ i
, row 
* sizeof(Line
)); 
2499                 memmove(term
.alt
, term
.alt 
+ i
, row 
* sizeof(Line
)); 
2501         for (i 
+= row
; i 
< term
.row
; i
++) { 
2506         /* resize to new height */ 
2507         term
.line 
= xrealloc(term
.line
, row 
* sizeof(Line
)); 
2508         term
.alt  
= xrealloc(term
.alt
,  row 
* sizeof(Line
)); 
2509         term
.dirty 
= xrealloc(term
.dirty
, row 
* sizeof(*term
.dirty
)); 
2510         term
.tabs 
= xrealloc(term
.tabs
, col 
* sizeof(*term
.tabs
)); 
2512         /* resize each row to new width, zero-pad if needed */ 
2513         for (i 
= 0; i 
< minrow
; i
++) { 
2514                 term
.line
[i
] = xrealloc(term
.line
[i
], col 
* sizeof(Glyph
)); 
2515                 term
.alt
[i
]  = xrealloc(term
.alt
[i
],  col 
* sizeof(Glyph
)); 
2518         /* allocate any new rows */ 
2519         for (/* i = minrow */; i 
< row
; i
++) { 
2520                 term
.line
[i
] = xmalloc(col 
* sizeof(Glyph
)); 
2521                 term
.alt
[i
] = xmalloc(col 
* sizeof(Glyph
)); 
2523         if (col 
> term
.col
) { 
2524                 bp 
= term
.tabs 
+ term
.col
; 
2526                 memset(bp
, 0, sizeof(*term
.tabs
) * (col 
- term
.col
)); 
2527                 while (--bp 
> term
.tabs 
&& !*bp
) 
2529                 for (bp 
+= tabspaces
; bp 
< term
.tabs 
+ col
; bp 
+= tabspaces
) 
2532         /* update terminal size */ 
2535         /* reset scrolling region */ 
2536         tsetscroll(0, row
-1); 
2537         /* make use of the LIMIT in tmoveto */ 
2538         tmoveto(term
.c
.x
, term
.c
.y
); 
2539         /* Clearing both screens (it makes dirty all lines) */ 
2541         for (i 
= 0; i 
< 2; i
++) { 
2542                 if (mincol 
< col 
&& 0 < minrow
) { 
2543                         tclearregion(mincol
, 0, col 
- 1, minrow 
- 1); 
2545                 if (0 < col 
&& minrow 
< row
) { 
2546                         tclearregion(0, minrow
, col 
- 1, row 
- 1); 
2549                 tcursor(CURSOR_LOAD
); 
2561 drawregion(int x1
, int y1
, int x2
, int y2
) 
2565         for (y 
= y1
; y 
< y2
; y
++) { 
2570                 xdrawline(term
.line
[y
], x1
, y
, x2
); 
2577         int cx 
= term
.c
.x
, ocx 
= term
.ocx
, ocy 
= term
.ocy
; 
2582         /* adjust cursor position */ 
2583         LIMIT(term
.ocx
, 0, term
.col
-1); 
2584         LIMIT(term
.ocy
, 0, term
.row
-1); 
2585         if (term
.line
[term
.ocy
][term
.ocx
].mode 
& ATTR_WDUMMY
) 
2587         if (term
.line
[term
.c
.y
][cx
].mode 
& ATTR_WDUMMY
) 
2590         drawregion(0, 0, term
.col
, term
.row
); 
2591         xdrawcursor(cx
, term
.c
.y
, term
.line
[term
.c
.y
][cx
], 
2592                         term
.ocx
, term
.ocy
, term
.line
[term
.ocy
][term
.ocx
]); 
2594         term
.ocy 
= term
.c
.y
; 
2596         if (ocx 
!= term
.ocx 
|| ocy 
!= term
.ocy
) 
2597                 xximspot(term
.ocx
, term
.ocy
);