st.c (57347B)
1 /* See LICENSE for license details. */ 2 #include <ctype.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <limits.h> 6 #include <pwd.h> 7 #include <stdarg.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <signal.h> 12 #include <sys/ioctl.h> 13 #include <sys/select.h> 14 #include <sys/types.h> 15 #include <sys/wait.h> 16 #include <termios.h> 17 #include <unistd.h> 18 #include <wchar.h> 19 20 #include "st.h" 21 #include "win.h" 22 23 #if defined(__linux) 24 #include <pty.h> 25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 26 #include <util.h> 27 #elif defined(__FreeBSD__) || defined(__DragonFly__) 28 #include <libutil.h> 29 #endif 30 31 /* Arbitrary sizes */ 32 #define UTF_INVALID 0xFFFD 33 #define UTF_SIZ 4 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 38 #define HISTSIZE 2000 39 40 /* macros */ 41 #define IS_SET(flag) ((term.mode & (flag)) != 0) 42 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) 43 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 44 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 45 #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 46 #define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 47 term.scr + HISTSIZE + 1) % HISTSIZE] : \ 48 term.line[(y) - term.scr]) 49 50 51 enum term_mode { 52 MODE_WRAP = 1 << 0, 53 MODE_INSERT = 1 << 1, 54 MODE_ALTSCREEN = 1 << 2, 55 MODE_CRLF = 1 << 3, 56 MODE_ECHO = 1 << 4, 57 MODE_PRINT = 1 << 5, 58 MODE_UTF8 = 1 << 6, 59 }; 60 61 enum cursor_movement { 62 CURSOR_SAVE, 63 CURSOR_LOAD 64 }; 65 66 enum cursor_state { 67 CURSOR_DEFAULT = 0, 68 CURSOR_WRAPNEXT = 1, 69 CURSOR_ORIGIN = 2 70 }; 71 72 enum charset { 73 CS_GRAPHIC0, 74 CS_GRAPHIC1, 75 CS_UK, 76 CS_USA, 77 CS_MULTI, 78 CS_GER, 79 CS_FIN 80 }; 81 82 enum escape_state { 83 ESC_START = 1, 84 ESC_CSI = 2, 85 ESC_STR = 4, /* DCS, OSC, PM, APC */ 86 ESC_ALTCHARSET = 8, 87 ESC_STR_END = 16, /* a final string was encountered */ 88 ESC_TEST = 32, /* Enter in test mode */ 89 ESC_UTF8 = 64, 90 }; 91 92 typedef struct { 93 Glyph attr; /* current char attributes */ 94 int x; 95 int y; 96 char state; 97 } TCursor; 98 99 typedef struct { 100 int mode; 101 int type; 102 int snap; 103 /* 104 * Selection variables: 105 * nb – normalized coordinates of the beginning of the selection 106 * ne – normalized coordinates of the end of the selection 107 * ob – original coordinates of the beginning of the selection 108 * oe – original coordinates of the end of the selection 109 */ 110 struct { 111 int x, y; 112 } nb, ne, ob, oe; 113 114 int alt; 115 } Selection; 116 117 /* Internal representation of the screen */ 118 typedef struct { 119 int row; /* nb row */ 120 int col; /* nb col */ 121 Line *line; /* screen */ 122 Line *alt; /* alternate screen */ 123 Line hist[HISTSIZE]; /* history buffer */ 124 int histi; /* history index */ 125 int scr; /* scroll back */ 126 int *dirty; /* dirtyness of lines */ 127 TCursor c; /* cursor */ 128 int ocx; /* old cursor col */ 129 int ocy; /* old cursor row */ 130 int top; /* top scroll limit */ 131 int bot; /* bottom scroll limit */ 132 int mode; /* terminal mode flags */ 133 int esc; /* escape state flags */ 134 char trantbl[4]; /* charset table translation */ 135 int charset; /* current charset */ 136 int icharset; /* selected charset for sequence */ 137 int *tabs; 138 Rune lastc; /* last printed char outside of sequence, 0 if control */ 139 } Term; 140 141 /* CSI Escape sequence structs */ 142 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 143 typedef struct { 144 char buf[ESC_BUF_SIZ]; /* raw string */ 145 size_t len; /* raw string length */ 146 char priv; 147 int arg[ESC_ARG_SIZ]; 148 int narg; /* nb of args */ 149 char mode[2]; 150 } CSIEscape; 151 152 /* STR Escape sequence structs */ 153 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ 154 typedef struct { 155 char type; /* ESC type ... */ 156 char *buf; /* allocated raw string */ 157 size_t siz; /* allocation size */ 158 size_t len; /* raw string length */ 159 char *args[STR_ARG_SIZ]; 160 int narg; /* nb of args */ 161 } STREscape; 162 163 static void execsh(char *, char **); 164 static void stty(char **); 165 static void sigchld(int); 166 static void ttywriteraw(const char *, size_t); 167 168 static void csidump(void); 169 static void csihandle(void); 170 static void csiparse(void); 171 static void csireset(void); 172 static int eschandle(uchar); 173 static void strdump(void); 174 static void strhandle(void); 175 static void strparse(void); 176 static void strreset(void); 177 178 static void tprinter(char *, size_t); 179 static void tdumpsel(void); 180 static void tdumpline(int); 181 static void tdump(void); 182 static void tclearregion(int, int, int, int); 183 static void tcursor(int); 184 static void tdeletechar(int); 185 static void tdeleteline(int); 186 static void tinsertblank(int); 187 static void tinsertblankline(int); 188 static int tlinelen(int); 189 static void tmoveto(int, int); 190 static void tmoveato(int, int); 191 static void tnewline(int); 192 static void tputtab(int); 193 static void tputc(Rune); 194 static void treset(void); 195 static void tscrollup(int, int, int); 196 static void tscrolldown(int, int, int); 197 static void tsetattr(int *, int); 198 static void tsetchar(Rune, Glyph *, int, int); 199 static void tsetdirt(int, int); 200 static void tsetscroll(int, int); 201 static void tswapscreen(void); 202 static void tsetmode(int, int, int *, int); 203 static int twrite(const char *, int, int); 204 static void tfulldirt(void); 205 static void tcontrolcode(uchar ); 206 static void tdectest(char ); 207 static void tdefutf8(char); 208 static int32_t tdefcolor(int *, int *, int); 209 static void tdeftran(char); 210 static void tstrsequence(uchar); 211 212 static void drawregion(int, int, int, int); 213 214 static void selnormalize(void); 215 static void selscroll(int, int); 216 static void selsnap(int *, int *, int); 217 218 static size_t utf8decode(const char *, Rune *, size_t); 219 static Rune utf8decodebyte(char, size_t *); 220 static char utf8encodebyte(Rune, size_t); 221 static size_t utf8validate(Rune *, size_t); 222 223 static char *base64dec(const char *); 224 static char base64dec_getc(const char **); 225 226 static ssize_t xwrite(int, const char *, size_t); 227 228 /* Globals */ 229 static Term term; 230 static Selection sel; 231 static CSIEscape csiescseq; 232 static STREscape strescseq; 233 static int iofd = 1; 234 static int cmdfd; 235 static pid_t pid; 236 237 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 238 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 239 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 240 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 241 242 ssize_t 243 xwrite(int fd, const char *s, size_t len) 244 { 245 size_t aux = len; 246 ssize_t r; 247 248 while (len > 0) { 249 r = write(fd, s, len); 250 if (r < 0) 251 return r; 252 len -= r; 253 s += r; 254 } 255 256 return aux; 257 } 258 259 void * 260 xmalloc(size_t len) 261 { 262 void *p; 263 264 if (!(p = malloc(len))) 265 die("malloc: %s\n", strerror(errno)); 266 267 return p; 268 } 269 270 void * 271 xrealloc(void *p, size_t len) 272 { 273 if ((p = realloc(p, len)) == NULL) 274 die("realloc: %s\n", strerror(errno)); 275 276 return p; 277 } 278 279 char * 280 xstrdup(char *s) 281 { 282 if ((s = strdup(s)) == NULL) 283 die("strdup: %s\n", strerror(errno)); 284 285 return s; 286 } 287 288 size_t 289 utf8decode(const char *c, Rune *u, size_t clen) 290 { 291 size_t i, j, len, type; 292 Rune udecoded; 293 294 *u = UTF_INVALID; 295 if (!clen) 296 return 0; 297 udecoded = utf8decodebyte(c[0], &len); 298 if (!BETWEEN(len, 1, UTF_SIZ)) 299 return 1; 300 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 301 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 302 if (type != 0) 303 return j; 304 } 305 if (j < len) 306 return 0; 307 *u = udecoded; 308 utf8validate(u, len); 309 310 return len; 311 } 312 313 Rune 314 utf8decodebyte(char c, size_t *i) 315 { 316 for (*i = 0; *i < LEN(utfmask); ++(*i)) 317 if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 318 return (uchar)c & ~utfmask[*i]; 319 320 return 0; 321 } 322 323 size_t 324 utf8encode(Rune u, char *c) 325 { 326 size_t len, i; 327 328 len = utf8validate(&u, 0); 329 if (len > UTF_SIZ) 330 return 0; 331 332 for (i = len - 1; i != 0; --i) { 333 c[i] = utf8encodebyte(u, 0); 334 u >>= 6; 335 } 336 c[0] = utf8encodebyte(u, len); 337 338 return len; 339 } 340 341 char 342 utf8encodebyte(Rune u, size_t i) 343 { 344 return utfbyte[i] | (u & ~utfmask[i]); 345 } 346 347 size_t 348 utf8validate(Rune *u, size_t i) 349 { 350 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 351 *u = UTF_INVALID; 352 for (i = 1; *u > utfmax[i]; ++i) 353 ; 354 355 return i; 356 } 357 358 static const char base64_digits[] = { 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, 62, 0, 0, 0, 361 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 362 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 363 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 364 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 365 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 366 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 367 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 368 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 369 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 370 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 371 }; 372 373 char 374 base64dec_getc(const char **src) 375 { 376 while (**src && !isprint(**src)) 377 (*src)++; 378 return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 379 } 380 381 char * 382 base64dec(const char *src) 383 { 384 size_t in_len = strlen(src); 385 char *result, *dst; 386 387 if (in_len % 4) 388 in_len += 4 - (in_len % 4); 389 result = dst = xmalloc(in_len / 4 * 3 + 1); 390 while (*src) { 391 int a = base64_digits[(unsigned char) base64dec_getc(&src)]; 392 int b = base64_digits[(unsigned char) base64dec_getc(&src)]; 393 int c = base64_digits[(unsigned char) base64dec_getc(&src)]; 394 int d = base64_digits[(unsigned char) base64dec_getc(&src)]; 395 396 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ 397 if (a == -1 || b == -1) 398 break; 399 400 *dst++ = (a << 2) | ((b & 0x30) >> 4); 401 if (c == -1) 402 break; 403 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 404 if (d == -1) 405 break; 406 *dst++ = ((c & 0x03) << 6) | d; 407 } 408 *dst = '\0'; 409 return result; 410 } 411 412 void 413 selinit(void) 414 { 415 sel.mode = SEL_IDLE; 416 sel.snap = 0; 417 sel.ob.x = -1; 418 } 419 420 int 421 tlinelen(int y) 422 { 423 int i = term.col; 424 425 if (TLINE(y)[i - 1].mode & ATTR_WRAP) 426 return i; 427 428 while (i > 0 && TLINE(y)[i - 1].u == ' ') 429 --i; 430 431 return i; 432 } 433 434 void 435 selstart(int col, int row, int snap) 436 { 437 selclear(); 438 sel.mode = SEL_EMPTY; 439 sel.type = SEL_REGULAR; 440 sel.alt = IS_SET(MODE_ALTSCREEN); 441 sel.snap = snap; 442 sel.oe.x = sel.ob.x = col; 443 sel.oe.y = sel.ob.y = row; 444 selnormalize(); 445 446 if (sel.snap != 0) 447 sel.mode = SEL_READY; 448 tsetdirt(sel.nb.y, sel.ne.y); 449 } 450 451 void 452 selextend(int col, int row, int type, int done) 453 { 454 int oldey, oldex, oldsby, oldsey, oldtype; 455 456 if (sel.mode == SEL_IDLE) 457 return; 458 if (done && sel.mode == SEL_EMPTY) { 459 selclear(); 460 return; 461 } 462 463 oldey = sel.oe.y; 464 oldex = sel.oe.x; 465 oldsby = sel.nb.y; 466 oldsey = sel.ne.y; 467 oldtype = sel.type; 468 469 sel.oe.x = col; 470 sel.oe.y = row; 471 selnormalize(); 472 sel.type = type; 473 474 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 475 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 476 477 sel.mode = done ? SEL_IDLE : SEL_READY; 478 } 479 480 void 481 selnormalize(void) 482 { 483 int i; 484 485 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 486 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 487 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 488 } else { 489 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 490 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 491 } 492 sel.nb.y = MIN(sel.ob.y, sel.oe.y); 493 sel.ne.y = MAX(sel.ob.y, sel.oe.y); 494 495 selsnap(&sel.nb.x, &sel.nb.y, -1); 496 selsnap(&sel.ne.x, &sel.ne.y, +1); 497 498 /* expand selection over line breaks */ 499 if (sel.type == SEL_RECTANGULAR) 500 return; 501 i = tlinelen(sel.nb.y); 502 if (i < sel.nb.x) 503 sel.nb.x = i; 504 if (tlinelen(sel.ne.y) <= sel.ne.x) 505 sel.ne.x = term.col - 1; 506 } 507 508 int 509 selected(int x, int y) 510 { 511 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || 512 sel.alt != IS_SET(MODE_ALTSCREEN)) 513 return 0; 514 515 if (sel.type == SEL_RECTANGULAR) 516 return BETWEEN(y, sel.nb.y, sel.ne.y) 517 && BETWEEN(x, sel.nb.x, sel.ne.x); 518 519 return BETWEEN(y, sel.nb.y, sel.ne.y) 520 && (y != sel.nb.y || x >= sel.nb.x) 521 && (y != sel.ne.y || x <= sel.ne.x); 522 } 523 524 void 525 selsnap(int *x, int *y, int direction) 526 { 527 int newx, newy, xt, yt; 528 int delim, prevdelim; 529 Glyph *gp, *prevgp; 530 531 switch (sel.snap) { 532 case SNAP_WORD: 533 /* 534 * Snap around if the word wraps around at the end or 535 * beginning of a line. 536 */ 537 prevgp = &TLINE(*y)[*x]; 538 prevdelim = ISDELIM(prevgp->u); 539 for (;;) { 540 newx = *x + direction; 541 newy = *y; 542 if (!BETWEEN(newx, 0, term.col - 1)) { 543 newy += direction; 544 newx = (newx + term.col) % term.col; 545 if (!BETWEEN(newy, 0, term.row - 1)) 546 break; 547 548 if (direction > 0) 549 yt = *y, xt = *x; 550 else 551 yt = newy, xt = newx; 552 if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 553 break; 554 } 555 556 if (newx >= tlinelen(newy)) 557 break; 558 559 gp = &TLINE(newy)[newx]; 560 delim = ISDELIM(gp->u); 561 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 562 || (delim && gp->u != prevgp->u))) 563 break; 564 565 *x = newx; 566 *y = newy; 567 prevgp = gp; 568 prevdelim = delim; 569 } 570 break; 571 case SNAP_LINE: 572 /* 573 * Snap around if the the previous line or the current one 574 * has set ATTR_WRAP at its end. Then the whole next or 575 * previous line will be selected. 576 */ 577 *x = (direction < 0) ? 0 : term.col - 1; 578 if (direction < 0) { 579 for (; *y > 0; *y += direction) { 580 if (!(TLINE(*y-1)[term.col-1].mode 581 & ATTR_WRAP)) { 582 break; 583 } 584 } 585 } else if (direction > 0) { 586 for (; *y < term.row-1; *y += direction) { 587 if (!(TLINE(*y)[term.col-1].mode 588 & ATTR_WRAP)) { 589 break; 590 } 591 } 592 } 593 break; 594 } 595 } 596 597 char * 598 getsel(void) 599 { 600 char *str, *ptr; 601 int y, bufsize, lastx, linelen; 602 Glyph *gp, *last; 603 604 if (sel.ob.x == -1) 605 return NULL; 606 607 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 608 ptr = str = xmalloc(bufsize); 609 610 /* append every set & selected glyph to the selection */ 611 for (y = sel.nb.y; y <= sel.ne.y; y++) { 612 if ((linelen = tlinelen(y)) == 0) { 613 *ptr++ = '\n'; 614 continue; 615 } 616 617 if (sel.type == SEL_RECTANGULAR) { 618 gp = &TLINE(y)[sel.nb.x]; 619 lastx = sel.ne.x; 620 } else { 621 gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; 622 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 623 } 624 last = &TLINE(y)[MIN(lastx, linelen-1)]; 625 while (last >= gp && last->u == ' ') 626 --last; 627 628 for ( ; gp <= last; ++gp) { 629 if (gp->mode & ATTR_WDUMMY) 630 continue; 631 632 ptr += utf8encode(gp->u, ptr); 633 } 634 635 /* 636 * Copy and pasting of line endings is inconsistent 637 * in the inconsistent terminal and GUI world. 638 * The best solution seems like to produce '\n' when 639 * something is copied from st and convert '\n' to 640 * '\r', when something to be pasted is received by 641 * st. 642 * FIXME: Fix the computer world. 643 */ 644 if ((y < sel.ne.y || lastx >= linelen) && 645 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) 646 *ptr++ = '\n'; 647 } 648 *ptr = 0; 649 return str; 650 } 651 652 void 653 selclear(void) 654 { 655 if (sel.ob.x == -1) 656 return; 657 sel.mode = SEL_IDLE; 658 sel.ob.x = -1; 659 tsetdirt(sel.nb.y, sel.ne.y); 660 } 661 662 void 663 die(const char *errstr, ...) 664 { 665 va_list ap; 666 667 va_start(ap, errstr); 668 vfprintf(stderr, errstr, ap); 669 va_end(ap); 670 exit(1); 671 } 672 673 void 674 execsh(char *cmd, char **args) 675 { 676 char *sh, *prog, *arg; 677 const struct passwd *pw; 678 679 errno = 0; 680 if ((pw = getpwuid(getuid())) == NULL) { 681 if (errno) 682 die("getpwuid: %s\n", strerror(errno)); 683 else 684 die("who are you?\n"); 685 } 686 687 if ((sh = getenv("SHELL")) == NULL) 688 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; 689 690 if (args) { 691 prog = args[0]; 692 arg = NULL; 693 } else if (scroll) { 694 prog = scroll; 695 arg = utmp ? utmp : sh; 696 } else if (utmp) { 697 prog = utmp; 698 arg = NULL; 699 } else { 700 prog = sh; 701 arg = NULL; 702 } 703 DEFAULT(args, ((char *[]) {prog, arg, NULL})); 704 705 unsetenv("COLUMNS"); 706 unsetenv("LINES"); 707 unsetenv("TERMCAP"); 708 setenv("LOGNAME", pw->pw_name, 1); 709 setenv("USER", pw->pw_name, 1); 710 setenv("SHELL", sh, 1); 711 setenv("HOME", pw->pw_dir, 1); 712 setenv("TERM", termname, 1); 713 714 signal(SIGCHLD, SIG_DFL); 715 signal(SIGHUP, SIG_DFL); 716 signal(SIGINT, SIG_DFL); 717 signal(SIGQUIT, SIG_DFL); 718 signal(SIGTERM, SIG_DFL); 719 signal(SIGALRM, SIG_DFL); 720 721 execvp(prog, args); 722 _exit(1); 723 } 724 725 void 726 sigchld(int a) 727 { 728 int stat; 729 pid_t p; 730 731 if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 732 die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 733 734 if (pid != p) 735 return; 736 737 if (WIFEXITED(stat) && WEXITSTATUS(stat)) 738 die("child exited with status %d\n", WEXITSTATUS(stat)); 739 else if (WIFSIGNALED(stat)) 740 die("child terminated due to signal %d\n", WTERMSIG(stat)); 741 _exit(0); 742 } 743 744 void 745 stty(char **args) 746 { 747 char cmd[_POSIX_ARG_MAX], **p, *q, *s; 748 size_t n, siz; 749 750 if ((n = strlen(stty_args)) > sizeof(cmd)-1) 751 die("incorrect stty parameters\n"); 752 memcpy(cmd, stty_args, n); 753 q = cmd + n; 754 siz = sizeof(cmd) - n; 755 for (p = args; p && (s = *p); ++p) { 756 if ((n = strlen(s)) > siz-1) 757 die("stty parameter length too long\n"); 758 *q++ = ' '; 759 memcpy(q, s, n); 760 q += n; 761 siz -= n + 1; 762 } 763 *q = '\0'; 764 if (system(cmd) != 0) 765 perror("Couldn't call stty"); 766 } 767 768 int 769 ttynew(char *line, char *cmd, char *out, char **args) 770 { 771 int m, s; 772 773 if (out) { 774 term.mode |= MODE_PRINT; 775 iofd = (!strcmp(out, "-")) ? 776 1 : open(out, O_WRONLY | O_CREAT, 0666); 777 if (iofd < 0) { 778 fprintf(stderr, "Error opening %s:%s\n", 779 out, strerror(errno)); 780 } 781 } 782 783 if (line) { 784 if ((cmdfd = open(line, O_RDWR)) < 0) 785 die("open line '%s' failed: %s\n", 786 line, strerror(errno)); 787 dup2(cmdfd, 0); 788 stty(args); 789 return cmdfd; 790 } 791 792 /* seems to work fine on linux, openbsd and freebsd */ 793 if (openpty(&m, &s, NULL, NULL, NULL) < 0) 794 die("openpty failed: %s\n", strerror(errno)); 795 796 switch (pid = fork()) { 797 case -1: 798 die("fork failed: %s\n", strerror(errno)); 799 break; 800 case 0: 801 close(iofd); 802 setsid(); /* create a new process group */ 803 dup2(s, 0); 804 dup2(s, 1); 805 dup2(s, 2); 806 if (ioctl(s, TIOCSCTTY, NULL) < 0) 807 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 808 close(s); 809 close(m); 810 #ifdef __OpenBSD__ 811 if (pledge("stdio getpw proc exec", NULL) == -1) 812 die("pledge\n"); 813 #endif 814 execsh(cmd, args); 815 break; 816 default: 817 #ifdef __OpenBSD__ 818 if (pledge("stdio rpath tty proc", NULL) == -1) 819 die("pledge\n"); 820 #endif 821 close(s); 822 cmdfd = m; 823 signal(SIGCHLD, sigchld); 824 break; 825 } 826 return cmdfd; 827 } 828 829 size_t 830 ttyread(void) 831 { 832 static char buf[BUFSIZ]; 833 static int buflen = 0; 834 int ret, written; 835 836 /* append read bytes to unprocessed bytes */ 837 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); 838 839 switch (ret) { 840 case 0: 841 exit(0); 842 case -1: 843 die("couldn't read from shell: %s\n", strerror(errno)); 844 default: 845 buflen += ret; 846 written = twrite(buf, buflen, 0); 847 buflen -= written; 848 /* keep any incomplete UTF-8 byte sequence for the next call */ 849 if (buflen > 0) 850 memmove(buf, buf + written, buflen); 851 return ret; 852 } 853 } 854 855 void 856 ttywrite(const char *s, size_t n, int may_echo) 857 { 858 const char *next; 859 Arg arg = (Arg) { .i = term.scr }; 860 861 kscrolldown(&arg); 862 863 if (may_echo && IS_SET(MODE_ECHO)) 864 twrite(s, n, 1); 865 866 if (!IS_SET(MODE_CRLF)) { 867 ttywriteraw(s, n); 868 return; 869 } 870 871 /* This is similar to how the kernel handles ONLCR for ttys */ 872 while (n > 0) { 873 if (*s == '\r') { 874 next = s + 1; 875 ttywriteraw("\r\n", 2); 876 } else { 877 next = memchr(s, '\r', n); 878 DEFAULT(next, s + n); 879 ttywriteraw(s, next - s); 880 } 881 n -= next - s; 882 s = next; 883 } 884 } 885 886 void 887 ttywriteraw(const char *s, size_t n) 888 { 889 fd_set wfd, rfd; 890 ssize_t r; 891 size_t lim = 256; 892 893 /* 894 * Remember that we are using a pty, which might be a modem line. 895 * Writing too much will clog the line. That's why we are doing this 896 * dance. 897 * FIXME: Migrate the world to Plan 9. 898 */ 899 while (n > 0) { 900 FD_ZERO(&wfd); 901 FD_ZERO(&rfd); 902 FD_SET(cmdfd, &wfd); 903 FD_SET(cmdfd, &rfd); 904 905 /* Check if we can write. */ 906 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 907 if (errno == EINTR) 908 continue; 909 die("select failed: %s\n", strerror(errno)); 910 } 911 if (FD_ISSET(cmdfd, &wfd)) { 912 /* 913 * Only write the bytes written by ttywrite() or the 914 * default of 256. This seems to be a reasonable value 915 * for a serial line. Bigger values might clog the I/O. 916 */ 917 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) 918 goto write_error; 919 if (r < n) { 920 /* 921 * We weren't able to write out everything. 922 * This means the buffer is getting full 923 * again. Empty it. 924 */ 925 if (n < lim) 926 lim = ttyread(); 927 n -= r; 928 s += r; 929 } else { 930 /* All bytes have been written. */ 931 break; 932 } 933 } 934 if (FD_ISSET(cmdfd, &rfd)) 935 lim = ttyread(); 936 } 937 return; 938 939 write_error: 940 die("write error on tty: %s\n", strerror(errno)); 941 } 942 943 void 944 ttyresize(int tw, int th) 945 { 946 struct winsize w; 947 948 w.ws_row = term.row; 949 w.ws_col = term.col; 950 w.ws_xpixel = tw; 951 w.ws_ypixel = th; 952 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 953 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 954 } 955 956 void 957 ttyhangup() 958 { 959 /* Send SIGHUP to shell */ 960 kill(pid, SIGHUP); 961 } 962 963 int 964 tattrset(int attr) 965 { 966 int i, j; 967 968 for (i = 0; i < term.row-1; i++) { 969 for (j = 0; j < term.col-1; j++) { 970 if (term.line[i][j].mode & attr) 971 return 1; 972 } 973 } 974 975 return 0; 976 } 977 978 void 979 tsetdirt(int top, int bot) 980 { 981 int i; 982 983 LIMIT(top, 0, term.row-1); 984 LIMIT(bot, 0, term.row-1); 985 986 for (i = top; i <= bot; i++) 987 term.dirty[i] = 1; 988 } 989 990 void 991 tsetdirtattr(int attr) 992 { 993 int i, j; 994 995 for (i = 0; i < term.row-1; i++) { 996 for (j = 0; j < term.col-1; j++) { 997 if (term.line[i][j].mode & attr) { 998 tsetdirt(i, i); 999 break; 1000 } 1001 } 1002 } 1003 } 1004 1005 void 1006 tfulldirt(void) 1007 { 1008 tsetdirt(0, term.row-1); 1009 } 1010 1011 void 1012 tcursor(int mode) 1013 { 1014 static TCursor c[2]; 1015 int alt = IS_SET(MODE_ALTSCREEN); 1016 1017 if (mode == CURSOR_SAVE) { 1018 c[alt] = term.c; 1019 } else if (mode == CURSOR_LOAD) { 1020 term.c = c[alt]; 1021 tmoveto(c[alt].x, c[alt].y); 1022 } 1023 } 1024 1025 void 1026 treset(void) 1027 { 1028 uint i; 1029 1030 term.c = (TCursor){{ 1031 .mode = ATTR_NULL, 1032 .fg = defaultfg, 1033 .bg = defaultbg 1034 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 1035 1036 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1037 for (i = tabspaces; i < term.col; i += tabspaces) 1038 term.tabs[i] = 1; 1039 term.top = 0; 1040 term.bot = term.row - 1; 1041 term.mode = MODE_WRAP|MODE_UTF8; 1042 memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 1043 term.charset = 0; 1044 1045 for (i = 0; i < 2; i++) { 1046 tmoveto(0, 0); 1047 tcursor(CURSOR_SAVE); 1048 tclearregion(0, 0, term.col-1, term.row-1); 1049 tswapscreen(); 1050 } 1051 } 1052 1053 void 1054 tnew(int col, int row) 1055 { 1056 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; 1057 tresize(col, row); 1058 treset(); 1059 } 1060 1061 void 1062 tswapscreen(void) 1063 { 1064 Line *tmp = term.line; 1065 1066 term.line = term.alt; 1067 term.alt = tmp; 1068 term.mode ^= MODE_ALTSCREEN; 1069 tfulldirt(); 1070 } 1071 void 1072 kscrolldown(const Arg* a) 1073 { 1074 int n = a->i; 1075 1076 if (n < 0) 1077 n = term.row + n; 1078 1079 if (n > term.scr) 1080 n = term.scr; 1081 1082 if (term.scr > 0) { 1083 term.scr -= n; 1084 selscroll(0, -n); 1085 tfulldirt(); 1086 } 1087 } 1088 1089 void 1090 kscrollup(const Arg* a) 1091 { 1092 int n = a->i; 1093 if (n < 0) 1094 n = term.row + n; 1095 1096 if (term.scr <= HISTSIZE-n) { 1097 term.scr += n; 1098 selscroll(0, n); 1099 tfulldirt(); 1100 } 1101 } 1102 1103 void 1104 tscrolldown(int orig, int n, int copyhist) 1105 { 1106 int i; 1107 Line temp; 1108 1109 LIMIT(n, 0, term.bot-orig+1); 1110 1111 if (copyhist) { 1112 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1113 temp = term.hist[term.histi]; 1114 term.hist[term.histi] = term.line[term.bot]; 1115 term.line[term.bot] = temp; 1116 } 1117 1118 tsetdirt(orig, term.bot-n); 1119 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1120 1121 for (i = term.bot; i >= orig+n; i--) { 1122 temp = term.line[i]; 1123 term.line[i] = term.line[i-n]; 1124 term.line[i-n] = temp; 1125 } 1126 1127 selscroll(orig, n); 1128 } 1129 1130 void 1131 tscrollup(int orig, int n, int copyhist) 1132 { 1133 int i; 1134 Line temp; 1135 1136 LIMIT(n, 0, term.bot-orig+1); 1137 1138 if (copyhist) { 1139 term.histi = (term.histi + 1) % HISTSIZE; 1140 temp = term.hist[term.histi]; 1141 term.hist[term.histi] = term.line[orig]; 1142 term.line[orig] = temp; 1143 } 1144 1145 if (term.scr > 0 && term.scr < HISTSIZE) 1146 term.scr = MIN(term.scr + n, HISTSIZE-1); 1147 1148 tclearregion(0, orig, term.col-1, orig+n-1); 1149 tsetdirt(orig+n, term.bot); 1150 1151 for (i = orig; i <= term.bot-n; i++) { 1152 temp = term.line[i]; 1153 term.line[i] = term.line[i+n]; 1154 term.line[i+n] = temp; 1155 } 1156 1157 selscroll(orig, -n); 1158 } 1159 1160 void 1161 selscroll(int orig, int n) 1162 { 1163 if (sel.ob.x == -1) 1164 return; 1165 1166 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { 1167 selclear(); 1168 } else if (BETWEEN(sel.nb.y, orig, term.bot)) { 1169 sel.ob.y += n; 1170 sel.oe.y += n; 1171 if (sel.ob.y < term.top || sel.ob.y > term.bot || 1172 sel.oe.y < term.top || sel.oe.y > term.bot) { 1173 selclear(); 1174 } else { 1175 selnormalize(); 1176 } 1177 } 1178 } 1179 1180 void 1181 tnewline(int first_col) 1182 { 1183 int y = term.c.y; 1184 1185 if (y == term.bot) { 1186 tscrollup(term.top, 1, 1); 1187 } else { 1188 y++; 1189 } 1190 tmoveto(first_col ? 0 : term.c.x, y); 1191 } 1192 1193 void 1194 csiparse(void) 1195 { 1196 char *p = csiescseq.buf, *np; 1197 long int v; 1198 1199 csiescseq.narg = 0; 1200 if (*p == '?') { 1201 csiescseq.priv = 1; 1202 p++; 1203 } 1204 1205 csiescseq.buf[csiescseq.len] = '\0'; 1206 while (p < csiescseq.buf+csiescseq.len) { 1207 np = NULL; 1208 v = strtol(p, &np, 10); 1209 if (np == p) 1210 v = 0; 1211 if (v == LONG_MAX || v == LONG_MIN) 1212 v = -1; 1213 csiescseq.arg[csiescseq.narg++] = v; 1214 p = np; 1215 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1216 break; 1217 p++; 1218 } 1219 csiescseq.mode[0] = *p++; 1220 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1221 } 1222 1223 /* for absolute user moves, when decom is set */ 1224 void 1225 tmoveato(int x, int y) 1226 { 1227 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1228 } 1229 1230 void 1231 tmoveto(int x, int y) 1232 { 1233 int miny, maxy; 1234 1235 if (term.c.state & CURSOR_ORIGIN) { 1236 miny = term.top; 1237 maxy = term.bot; 1238 } else { 1239 miny = 0; 1240 maxy = term.row - 1; 1241 } 1242 term.c.state &= ~CURSOR_WRAPNEXT; 1243 term.c.x = LIMIT(x, 0, term.col-1); 1244 term.c.y = LIMIT(y, miny, maxy); 1245 } 1246 1247 void 1248 tsetchar(Rune u, Glyph *attr, int x, int y) 1249 { 1250 static char *vt100_0[62] = { /* 0x41 - 0x7e */ 1251 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1252 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1253 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1254 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1255 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1256 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1257 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1258 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1259 }; 1260 1261 /* 1262 * The table is proudly stolen from rxvt. 1263 */ 1264 if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1265 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1266 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1267 1268 if (term.line[y][x].mode & ATTR_WIDE) { 1269 if (x+1 < term.col) { 1270 term.line[y][x+1].u = ' '; 1271 term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1272 } 1273 } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1274 term.line[y][x-1].u = ' '; 1275 term.line[y][x-1].mode &= ~ATTR_WIDE; 1276 } 1277 1278 term.dirty[y] = 1; 1279 term.line[y][x] = *attr; 1280 term.line[y][x].u = u; 1281 } 1282 1283 void 1284 tclearregion(int x1, int y1, int x2, int y2) 1285 { 1286 int x, y, temp; 1287 Glyph *gp; 1288 1289 if (x1 > x2) 1290 temp = x1, x1 = x2, x2 = temp; 1291 if (y1 > y2) 1292 temp = y1, y1 = y2, y2 = temp; 1293 1294 LIMIT(x1, 0, term.col-1); 1295 LIMIT(x2, 0, term.col-1); 1296 LIMIT(y1, 0, term.row-1); 1297 LIMIT(y2, 0, term.row-1); 1298 1299 for (y = y1; y <= y2; y++) { 1300 term.dirty[y] = 1; 1301 for (x = x1; x <= x2; x++) { 1302 gp = &term.line[y][x]; 1303 if (selected(x, y)) 1304 selclear(); 1305 gp->fg = term.c.attr.fg; 1306 gp->bg = term.c.attr.bg; 1307 gp->mode = 0; 1308 gp->u = ' '; 1309 } 1310 } 1311 } 1312 1313 void 1314 tdeletechar(int n) 1315 { 1316 int dst, src, size; 1317 Glyph *line; 1318 1319 LIMIT(n, 0, term.col - term.c.x); 1320 1321 dst = term.c.x; 1322 src = term.c.x + n; 1323 size = term.col - src; 1324 line = term.line[term.c.y]; 1325 1326 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1327 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1328 } 1329 1330 void 1331 tinsertblank(int n) 1332 { 1333 int dst, src, size; 1334 Glyph *line; 1335 1336 LIMIT(n, 0, term.col - term.c.x); 1337 1338 dst = term.c.x + n; 1339 src = term.c.x; 1340 size = term.col - dst; 1341 line = term.line[term.c.y]; 1342 1343 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1344 tclearregion(src, term.c.y, dst - 1, term.c.y); 1345 } 1346 1347 void 1348 tinsertblankline(int n) 1349 { 1350 if (BETWEEN(term.c.y, term.top, term.bot)) 1351 tscrolldown(term.c.y, n, 0); 1352 } 1353 1354 void 1355 tdeleteline(int n) 1356 { 1357 if (BETWEEN(term.c.y, term.top, term.bot)) 1358 tscrollup(term.c.y, n, 0); 1359 } 1360 1361 int32_t 1362 tdefcolor(int *attr, int *npar, int l) 1363 { 1364 int32_t idx = -1; 1365 uint r, g, b; 1366 1367 switch (attr[*npar + 1]) { 1368 case 2: /* direct color in RGB space */ 1369 if (*npar + 4 >= l) { 1370 fprintf(stderr, 1371 "erresc(38): Incorrect number of parameters (%d)\n", 1372 *npar); 1373 break; 1374 } 1375 r = attr[*npar + 2]; 1376 g = attr[*npar + 3]; 1377 b = attr[*npar + 4]; 1378 *npar += 4; 1379 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1380 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1381 r, g, b); 1382 else 1383 idx = TRUECOLOR(r, g, b); 1384 break; 1385 case 5: /* indexed color */ 1386 if (*npar + 2 >= l) { 1387 fprintf(stderr, 1388 "erresc(38): Incorrect number of parameters (%d)\n", 1389 *npar); 1390 break; 1391 } 1392 *npar += 2; 1393 if (!BETWEEN(attr[*npar], 0, 255)) 1394 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1395 else 1396 idx = attr[*npar]; 1397 break; 1398 case 0: /* implemented defined (only foreground) */ 1399 case 1: /* transparent */ 1400 case 3: /* direct color in CMY space */ 1401 case 4: /* direct color in CMYK space */ 1402 default: 1403 fprintf(stderr, 1404 "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1405 break; 1406 } 1407 1408 return idx; 1409 } 1410 1411 void 1412 tsetattr(int *attr, int l) 1413 { 1414 int i; 1415 int32_t idx; 1416 1417 for (i = 0; i < l; i++) { 1418 switch (attr[i]) { 1419 case 0: 1420 term.c.attr.mode &= ~( 1421 ATTR_BOLD | 1422 ATTR_FAINT | 1423 ATTR_ITALIC | 1424 ATTR_UNDERLINE | 1425 ATTR_BLINK | 1426 ATTR_REVERSE | 1427 ATTR_INVISIBLE | 1428 ATTR_STRUCK ); 1429 term.c.attr.fg = defaultfg; 1430 term.c.attr.bg = defaultbg; 1431 break; 1432 case 1: 1433 term.c.attr.mode |= ATTR_BOLD; 1434 break; 1435 case 2: 1436 term.c.attr.mode |= ATTR_FAINT; 1437 break; 1438 case 3: 1439 term.c.attr.mode |= ATTR_ITALIC; 1440 break; 1441 case 4: 1442 term.c.attr.mode |= ATTR_UNDERLINE; 1443 break; 1444 case 5: /* slow blink */ 1445 /* FALLTHROUGH */ 1446 case 6: /* rapid blink */ 1447 term.c.attr.mode |= ATTR_BLINK; 1448 break; 1449 case 7: 1450 term.c.attr.mode |= ATTR_REVERSE; 1451 break; 1452 case 8: 1453 term.c.attr.mode |= ATTR_INVISIBLE; 1454 break; 1455 case 9: 1456 term.c.attr.mode |= ATTR_STRUCK; 1457 break; 1458 case 22: 1459 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1460 break; 1461 case 23: 1462 term.c.attr.mode &= ~ATTR_ITALIC; 1463 break; 1464 case 24: 1465 term.c.attr.mode &= ~ATTR_UNDERLINE; 1466 break; 1467 case 25: 1468 term.c.attr.mode &= ~ATTR_BLINK; 1469 break; 1470 case 27: 1471 term.c.attr.mode &= ~ATTR_REVERSE; 1472 break; 1473 case 28: 1474 term.c.attr.mode &= ~ATTR_INVISIBLE; 1475 break; 1476 case 29: 1477 term.c.attr.mode &= ~ATTR_STRUCK; 1478 break; 1479 case 38: 1480 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1481 term.c.attr.fg = idx; 1482 break; 1483 case 39: 1484 term.c.attr.fg = defaultfg; 1485 break; 1486 case 48: 1487 if ((idx = tdefcolor(attr, &i, l)) >= 0) 1488 term.c.attr.bg = idx; 1489 break; 1490 case 49: 1491 term.c.attr.bg = defaultbg; 1492 break; 1493 default: 1494 if (BETWEEN(attr[i], 30, 37)) { 1495 term.c.attr.fg = attr[i] - 30; 1496 } else if (BETWEEN(attr[i], 40, 47)) { 1497 term.c.attr.bg = attr[i] - 40; 1498 } else if (BETWEEN(attr[i], 90, 97)) { 1499 term.c.attr.fg = attr[i] - 90 + 8; 1500 } else if (BETWEEN(attr[i], 100, 107)) { 1501 term.c.attr.bg = attr[i] - 100 + 8; 1502 } else { 1503 fprintf(stderr, 1504 "erresc(default): gfx attr %d unknown\n", 1505 attr[i]); 1506 csidump(); 1507 } 1508 break; 1509 } 1510 } 1511 } 1512 1513 void 1514 tsetscroll(int t, int b) 1515 { 1516 int temp; 1517 1518 LIMIT(t, 0, term.row-1); 1519 LIMIT(b, 0, term.row-1); 1520 if (t > b) { 1521 temp = t; 1522 t = b; 1523 b = temp; 1524 } 1525 term.top = t; 1526 term.bot = b; 1527 } 1528 1529 void 1530 tsetmode(int priv, int set, int *args, int narg) 1531 { 1532 int alt, *lim; 1533 1534 for (lim = args + narg; args < lim; ++args) { 1535 if (priv) { 1536 switch (*args) { 1537 case 1: /* DECCKM -- Cursor key */ 1538 xsetmode(set, MODE_APPCURSOR); 1539 break; 1540 case 5: /* DECSCNM -- Reverse video */ 1541 xsetmode(set, MODE_REVERSE); 1542 break; 1543 case 6: /* DECOM -- Origin */ 1544 MODBIT(term.c.state, set, CURSOR_ORIGIN); 1545 tmoveato(0, 0); 1546 break; 1547 case 7: /* DECAWM -- Auto wrap */ 1548 MODBIT(term.mode, set, MODE_WRAP); 1549 break; 1550 case 0: /* Error (IGNORED) */ 1551 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1552 case 3: /* DECCOLM -- Column (IGNORED) */ 1553 case 4: /* DECSCLM -- Scroll (IGNORED) */ 1554 case 8: /* DECARM -- Auto repeat (IGNORED) */ 1555 case 18: /* DECPFF -- Printer feed (IGNORED) */ 1556 case 19: /* DECPEX -- Printer extent (IGNORED) */ 1557 case 42: /* DECNRCM -- National characters (IGNORED) */ 1558 case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1559 break; 1560 case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1561 xsetmode(!set, MODE_HIDE); 1562 break; 1563 case 9: /* X10 mouse compatibility mode */ 1564 xsetpointermotion(0); 1565 xsetmode(0, MODE_MOUSE); 1566 xsetmode(set, MODE_MOUSEX10); 1567 break; 1568 case 1000: /* 1000: report button press */ 1569 xsetpointermotion(0); 1570 xsetmode(0, MODE_MOUSE); 1571 xsetmode(set, MODE_MOUSEBTN); 1572 break; 1573 case 1002: /* 1002: report motion on button press */ 1574 xsetpointermotion(0); 1575 xsetmode(0, MODE_MOUSE); 1576 xsetmode(set, MODE_MOUSEMOTION); 1577 break; 1578 case 1003: /* 1003: enable all mouse motions */ 1579 xsetpointermotion(set); 1580 xsetmode(0, MODE_MOUSE); 1581 xsetmode(set, MODE_MOUSEMANY); 1582 break; 1583 case 1004: /* 1004: send focus events to tty */ 1584 xsetmode(set, MODE_FOCUS); 1585 break; 1586 case 1006: /* 1006: extended reporting mode */ 1587 xsetmode(set, MODE_MOUSESGR); 1588 break; 1589 case 1034: 1590 xsetmode(set, MODE_8BIT); 1591 break; 1592 case 1049: /* swap screen & set/restore cursor as xterm */ 1593 if (!allowaltscreen) 1594 break; 1595 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1596 /* FALLTHROUGH */ 1597 case 47: /* swap screen */ 1598 case 1047: 1599 if (!allowaltscreen) 1600 break; 1601 alt = IS_SET(MODE_ALTSCREEN); 1602 if (alt) { 1603 tclearregion(0, 0, term.col-1, 1604 term.row-1); 1605 } 1606 if (set ^ alt) /* set is always 1 or 0 */ 1607 tswapscreen(); 1608 if (*args != 1049) 1609 break; 1610 /* FALLTHROUGH */ 1611 case 1048: 1612 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1613 break; 1614 case 2004: /* 2004: bracketed paste mode */ 1615 xsetmode(set, MODE_BRCKTPASTE); 1616 break; 1617 /* Not implemented mouse modes. See comments there. */ 1618 case 1001: /* mouse highlight mode; can hang the 1619 terminal by design when implemented. */ 1620 case 1005: /* UTF-8 mouse mode; will confuse 1621 applications not supporting UTF-8 1622 and luit. */ 1623 case 1015: /* urxvt mangled mouse mode; incompatible 1624 and can be mistaken for other control 1625 codes. */ 1626 break; 1627 default: 1628 fprintf(stderr, 1629 "erresc: unknown private set/reset mode %d\n", 1630 *args); 1631 break; 1632 } 1633 } else { 1634 switch (*args) { 1635 case 0: /* Error (IGNORED) */ 1636 break; 1637 case 2: 1638 xsetmode(set, MODE_KBDLOCK); 1639 break; 1640 case 4: /* IRM -- Insertion-replacement */ 1641 MODBIT(term.mode, set, MODE_INSERT); 1642 break; 1643 case 12: /* SRM -- Send/Receive */ 1644 MODBIT(term.mode, !set, MODE_ECHO); 1645 break; 1646 case 20: /* LNM -- Linefeed/new line */ 1647 MODBIT(term.mode, set, MODE_CRLF); 1648 break; 1649 default: 1650 fprintf(stderr, 1651 "erresc: unknown set/reset mode %d\n", 1652 *args); 1653 break; 1654 } 1655 } 1656 } 1657 } 1658 1659 void 1660 csihandle(void) 1661 { 1662 char buf[40]; 1663 int len; 1664 1665 switch (csiescseq.mode[0]) { 1666 default: 1667 unknown: 1668 fprintf(stderr, "erresc: unknown csi "); 1669 csidump(); 1670 /* die(""); */ 1671 break; 1672 case '@': /* ICH -- Insert <n> blank char */ 1673 DEFAULT(csiescseq.arg[0], 1); 1674 tinsertblank(csiescseq.arg[0]); 1675 break; 1676 case 'A': /* CUU -- Cursor <n> Up */ 1677 DEFAULT(csiescseq.arg[0], 1); 1678 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1679 break; 1680 case 'B': /* CUD -- Cursor <n> Down */ 1681 case 'e': /* VPR --Cursor <n> Down */ 1682 DEFAULT(csiescseq.arg[0], 1); 1683 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1684 break; 1685 case 'i': /* MC -- Media Copy */ 1686 switch (csiescseq.arg[0]) { 1687 case 0: 1688 tdump(); 1689 break; 1690 case 1: 1691 tdumpline(term.c.y); 1692 break; 1693 case 2: 1694 tdumpsel(); 1695 break; 1696 case 4: 1697 term.mode &= ~MODE_PRINT; 1698 break; 1699 case 5: 1700 term.mode |= MODE_PRINT; 1701 break; 1702 } 1703 break; 1704 case 'c': /* DA -- Device Attributes */ 1705 if (csiescseq.arg[0] == 0) 1706 ttywrite(vtiden, strlen(vtiden), 0); 1707 break; 1708 case 'b': /* REP -- if last char is printable print it <n> more times */ 1709 DEFAULT(csiescseq.arg[0], 1); 1710 if (term.lastc) 1711 while (csiescseq.arg[0]-- > 0) 1712 tputc(term.lastc); 1713 break; 1714 case 'C': /* CUF -- Cursor <n> Forward */ 1715 case 'a': /* HPR -- Cursor <n> Forward */ 1716 DEFAULT(csiescseq.arg[0], 1); 1717 tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1718 break; 1719 case 'D': /* CUB -- Cursor <n> Backward */ 1720 DEFAULT(csiescseq.arg[0], 1); 1721 tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1722 break; 1723 case 'E': /* CNL -- Cursor <n> Down and first col */ 1724 DEFAULT(csiescseq.arg[0], 1); 1725 tmoveto(0, term.c.y+csiescseq.arg[0]); 1726 break; 1727 case 'F': /* CPL -- Cursor <n> Up and first col */ 1728 DEFAULT(csiescseq.arg[0], 1); 1729 tmoveto(0, term.c.y-csiescseq.arg[0]); 1730 break; 1731 case 'g': /* TBC -- Tabulation clear */ 1732 switch (csiescseq.arg[0]) { 1733 case 0: /* clear current tab stop */ 1734 term.tabs[term.c.x] = 0; 1735 break; 1736 case 3: /* clear all the tabs */ 1737 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1738 break; 1739 default: 1740 goto unknown; 1741 } 1742 break; 1743 case 'G': /* CHA -- Move to <col> */ 1744 case '`': /* HPA */ 1745 DEFAULT(csiescseq.arg[0], 1); 1746 tmoveto(csiescseq.arg[0]-1, term.c.y); 1747 break; 1748 case 'H': /* CUP -- Move to <row> <col> */ 1749 case 'f': /* HVP */ 1750 DEFAULT(csiescseq.arg[0], 1); 1751 DEFAULT(csiescseq.arg[1], 1); 1752 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1753 break; 1754 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 1755 DEFAULT(csiescseq.arg[0], 1); 1756 tputtab(csiescseq.arg[0]); 1757 break; 1758 case 'J': /* ED -- Clear screen */ 1759 switch (csiescseq.arg[0]) { 1760 case 0: /* below */ 1761 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1762 if (term.c.y < term.row-1) { 1763 tclearregion(0, term.c.y+1, term.col-1, 1764 term.row-1); 1765 } 1766 break; 1767 case 1: /* above */ 1768 if (term.c.y > 1) 1769 tclearregion(0, 0, term.col-1, term.c.y-1); 1770 tclearregion(0, term.c.y, term.c.x, term.c.y); 1771 break; 1772 case 2: /* all */ 1773 tclearregion(0, 0, term.col-1, term.row-1); 1774 break; 1775 default: 1776 goto unknown; 1777 } 1778 break; 1779 case 'K': /* EL -- Clear line */ 1780 switch (csiescseq.arg[0]) { 1781 case 0: /* right */ 1782 tclearregion(term.c.x, term.c.y, term.col-1, 1783 term.c.y); 1784 break; 1785 case 1: /* left */ 1786 tclearregion(0, term.c.y, term.c.x, term.c.y); 1787 break; 1788 case 2: /* all */ 1789 tclearregion(0, term.c.y, term.col-1, term.c.y); 1790 break; 1791 } 1792 break; 1793 case 'S': /* SU -- Scroll <n> line up */ 1794 DEFAULT(csiescseq.arg[0], 1); 1795 tscrollup(term.top, csiescseq.arg[0], 0); 1796 break; 1797 case 'T': /* SD -- Scroll <n> line down */ 1798 DEFAULT(csiescseq.arg[0], 1); 1799 tscrolldown(term.top, csiescseq.arg[0], 0); 1800 break; 1801 case 'L': /* IL -- Insert <n> blank lines */ 1802 DEFAULT(csiescseq.arg[0], 1); 1803 tinsertblankline(csiescseq.arg[0]); 1804 break; 1805 case 'l': /* RM -- Reset Mode */ 1806 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1807 break; 1808 case 'M': /* DL -- Delete <n> lines */ 1809 DEFAULT(csiescseq.arg[0], 1); 1810 tdeleteline(csiescseq.arg[0]); 1811 break; 1812 case 'X': /* ECH -- Erase <n> char */ 1813 DEFAULT(csiescseq.arg[0], 1); 1814 tclearregion(term.c.x, term.c.y, 1815 term.c.x + csiescseq.arg[0] - 1, term.c.y); 1816 break; 1817 case 'P': /* DCH -- Delete <n> char */ 1818 DEFAULT(csiescseq.arg[0], 1); 1819 tdeletechar(csiescseq.arg[0]); 1820 break; 1821 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 1822 DEFAULT(csiescseq.arg[0], 1); 1823 tputtab(-csiescseq.arg[0]); 1824 break; 1825 case 'd': /* VPA -- Move to <row> */ 1826 DEFAULT(csiescseq.arg[0], 1); 1827 tmoveato(term.c.x, csiescseq.arg[0]-1); 1828 break; 1829 case 'h': /* SM -- Set terminal mode */ 1830 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1831 break; 1832 case 'm': /* SGR -- Terminal attribute (color) */ 1833 tsetattr(csiescseq.arg, csiescseq.narg); 1834 break; 1835 case 'n': /* DSR – Device Status Report (cursor position) */ 1836 if (csiescseq.arg[0] == 6) { 1837 len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 1838 term.c.y+1, term.c.x+1); 1839 ttywrite(buf, len, 0); 1840 } 1841 break; 1842 case 'r': /* DECSTBM -- Set Scrolling Region */ 1843 if (csiescseq.priv) { 1844 goto unknown; 1845 } else { 1846 DEFAULT(csiescseq.arg[0], 1); 1847 DEFAULT(csiescseq.arg[1], term.row); 1848 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1849 tmoveato(0, 0); 1850 } 1851 break; 1852 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1853 tcursor(CURSOR_SAVE); 1854 break; 1855 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1856 tcursor(CURSOR_LOAD); 1857 break; 1858 case ' ': 1859 switch (csiescseq.mode[1]) { 1860 case 'q': /* DECSCUSR -- Set Cursor Style */ 1861 if (xsetcursor(csiescseq.arg[0])) 1862 goto unknown; 1863 break; 1864 default: 1865 goto unknown; 1866 } 1867 break; 1868 } 1869 } 1870 1871 void 1872 csidump(void) 1873 { 1874 size_t i; 1875 uint c; 1876 1877 fprintf(stderr, "ESC["); 1878 for (i = 0; i < csiescseq.len; i++) { 1879 c = csiescseq.buf[i] & 0xff; 1880 if (isprint(c)) { 1881 putc(c, stderr); 1882 } else if (c == '\n') { 1883 fprintf(stderr, "(\\n)"); 1884 } else if (c == '\r') { 1885 fprintf(stderr, "(\\r)"); 1886 } else if (c == 0x1b) { 1887 fprintf(stderr, "(\\e)"); 1888 } else { 1889 fprintf(stderr, "(%02x)", c); 1890 } 1891 } 1892 putc('\n', stderr); 1893 } 1894 1895 void 1896 csireset(void) 1897 { 1898 memset(&csiescseq, 0, sizeof(csiescseq)); 1899 } 1900 1901 void 1902 strhandle(void) 1903 { 1904 char *p = NULL, *dec; 1905 int j, narg, par; 1906 1907 term.esc &= ~(ESC_STR_END|ESC_STR); 1908 strparse(); 1909 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1910 1911 switch (strescseq.type) { 1912 case ']': /* OSC -- Operating System Command */ 1913 switch (par) { 1914 case 0: 1915 if (narg > 1) { 1916 xsettitle(strescseq.args[1]); 1917 xseticontitle(strescseq.args[1]); 1918 } 1919 return; 1920 case 1: 1921 if (narg > 1) 1922 xseticontitle(strescseq.args[1]); 1923 return; 1924 case 2: 1925 if (narg > 1) 1926 xsettitle(strescseq.args[1]); 1927 return; 1928 case 52: 1929 if (narg > 2 && allowwindowops) { 1930 dec = base64dec(strescseq.args[2]); 1931 if (dec) { 1932 xsetsel(dec); 1933 xclipcopy(); 1934 } else { 1935 fprintf(stderr, "erresc: invalid base64\n"); 1936 } 1937 } 1938 return; 1939 case 4: /* color set */ 1940 if (narg < 3) 1941 break; 1942 p = strescseq.args[2]; 1943 /* FALLTHROUGH */ 1944 case 104: /* color reset, here p = NULL */ 1945 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 1946 if (xsetcolorname(j, p)) { 1947 if (par == 104 && narg <= 1) 1948 return; /* color reset without parameter */ 1949 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 1950 j, p ? p : "(null)"); 1951 } else { 1952 /* 1953 * TODO if defaultbg color is changed, borders 1954 * are dirty 1955 */ 1956 redraw(); 1957 } 1958 return; 1959 } 1960 break; 1961 case 'k': /* old title set compatibility */ 1962 xsettitle(strescseq.args[0]); 1963 return; 1964 case 'P': /* DCS -- Device Control String */ 1965 case '_': /* APC -- Application Program Command */ 1966 case '^': /* PM -- Privacy Message */ 1967 return; 1968 } 1969 1970 fprintf(stderr, "erresc: unknown str "); 1971 strdump(); 1972 } 1973 1974 void 1975 strparse(void) 1976 { 1977 int c; 1978 char *p = strescseq.buf; 1979 1980 strescseq.narg = 0; 1981 strescseq.buf[strescseq.len] = '\0'; 1982 1983 if (*p == '\0') 1984 return; 1985 1986 while (strescseq.narg < STR_ARG_SIZ) { 1987 strescseq.args[strescseq.narg++] = p; 1988 while ((c = *p) != ';' && c != '\0') 1989 ++p; 1990 if (c == '\0') 1991 return; 1992 *p++ = '\0'; 1993 } 1994 } 1995 1996 void 1997 strdump(void) 1998 { 1999 size_t i; 2000 uint c; 2001 2002 fprintf(stderr, "ESC%c", strescseq.type); 2003 for (i = 0; i < strescseq.len; i++) { 2004 c = strescseq.buf[i] & 0xff; 2005 if (c == '\0') { 2006 putc('\n', stderr); 2007 return; 2008 } else if (isprint(c)) { 2009 putc(c, stderr); 2010 } else if (c == '\n') { 2011 fprintf(stderr, "(\\n)"); 2012 } else if (c == '\r') { 2013 fprintf(stderr, "(\\r)"); 2014 } else if (c == 0x1b) { 2015 fprintf(stderr, "(\\e)"); 2016 } else { 2017 fprintf(stderr, "(%02x)", c); 2018 } 2019 } 2020 fprintf(stderr, "ESC\\\n"); 2021 } 2022 2023 void 2024 strreset(void) 2025 { 2026 strescseq = (STREscape){ 2027 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2028 .siz = STR_BUF_SIZ, 2029 }; 2030 } 2031 2032 void 2033 sendbreak(const Arg *arg) 2034 { 2035 if (tcsendbreak(cmdfd, 0)) 2036 perror("Error sending break"); 2037 } 2038 2039 void 2040 tprinter(char *s, size_t len) 2041 { 2042 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2043 perror("Error writing to output file"); 2044 close(iofd); 2045 iofd = -1; 2046 } 2047 } 2048 2049 void 2050 toggleprinter(const Arg *arg) 2051 { 2052 term.mode ^= MODE_PRINT; 2053 } 2054 2055 void 2056 printscreen(const Arg *arg) 2057 { 2058 tdump(); 2059 } 2060 2061 void 2062 printsel(const Arg *arg) 2063 { 2064 tdumpsel(); 2065 } 2066 2067 void 2068 tdumpsel(void) 2069 { 2070 char *ptr; 2071 2072 if ((ptr = getsel())) { 2073 tprinter(ptr, strlen(ptr)); 2074 free(ptr); 2075 } 2076 } 2077 2078 void 2079 tdumpline(int n) 2080 { 2081 char buf[UTF_SIZ]; 2082 Glyph *bp, *end; 2083 2084 bp = &term.line[n][0]; 2085 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2086 if (bp != end || bp->u != ' ') { 2087 for ( ; bp <= end; ++bp) 2088 tprinter(buf, utf8encode(bp->u, buf)); 2089 } 2090 tprinter("\n", 1); 2091 } 2092 2093 void 2094 tdump(void) 2095 { 2096 int i; 2097 2098 for (i = 0; i < term.row; ++i) 2099 tdumpline(i); 2100 } 2101 2102 void 2103 tputtab(int n) 2104 { 2105 uint x = term.c.x; 2106 2107 if (n > 0) { 2108 while (x < term.col && n--) 2109 for (++x; x < term.col && !term.tabs[x]; ++x) 2110 /* nothing */ ; 2111 } else if (n < 0) { 2112 while (x > 0 && n++) 2113 for (--x; x > 0 && !term.tabs[x]; --x) 2114 /* nothing */ ; 2115 } 2116 term.c.x = LIMIT(x, 0, term.col-1); 2117 } 2118 2119 void 2120 tdefutf8(char ascii) 2121 { 2122 if (ascii == 'G') 2123 term.mode |= MODE_UTF8; 2124 else if (ascii == '@') 2125 term.mode &= ~MODE_UTF8; 2126 } 2127 2128 void 2129 tdeftran(char ascii) 2130 { 2131 static char cs[] = "0B"; 2132 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2133 char *p; 2134 2135 if ((p = strchr(cs, ascii)) == NULL) { 2136 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2137 } else { 2138 term.trantbl[term.icharset] = vcs[p - cs]; 2139 } 2140 } 2141 2142 void 2143 tdectest(char c) 2144 { 2145 int x, y; 2146 2147 if (c == '8') { /* DEC screen alignment test. */ 2148 for (x = 0; x < term.col; ++x) { 2149 for (y = 0; y < term.row; ++y) 2150 tsetchar('E', &term.c.attr, x, y); 2151 } 2152 } 2153 } 2154 2155 void 2156 tstrsequence(uchar c) 2157 { 2158 switch (c) { 2159 case 0x90: /* DCS -- Device Control String */ 2160 c = 'P'; 2161 break; 2162 case 0x9f: /* APC -- Application Program Command */ 2163 c = '_'; 2164 break; 2165 case 0x9e: /* PM -- Privacy Message */ 2166 c = '^'; 2167 break; 2168 case 0x9d: /* OSC -- Operating System Command */ 2169 c = ']'; 2170 break; 2171 } 2172 strreset(); 2173 strescseq.type = c; 2174 term.esc |= ESC_STR; 2175 } 2176 2177 void 2178 tcontrolcode(uchar ascii) 2179 { 2180 switch (ascii) { 2181 case '\t': /* HT */ 2182 tputtab(1); 2183 return; 2184 case '\b': /* BS */ 2185 tmoveto(term.c.x-1, term.c.y); 2186 return; 2187 case '\r': /* CR */ 2188 tmoveto(0, term.c.y); 2189 return; 2190 case '\f': /* LF */ 2191 case '\v': /* VT */ 2192 case '\n': /* LF */ 2193 /* go to first col if the mode is set */ 2194 tnewline(IS_SET(MODE_CRLF)); 2195 return; 2196 case '\a': /* BEL */ 2197 if (term.esc & ESC_STR_END) { 2198 /* backwards compatibility to xterm */ 2199 strhandle(); 2200 } else { 2201 xbell(); 2202 } 2203 break; 2204 case '\033': /* ESC */ 2205 csireset(); 2206 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2207 term.esc |= ESC_START; 2208 return; 2209 case '\016': /* SO (LS1 -- Locking shift 1) */ 2210 case '\017': /* SI (LS0 -- Locking shift 0) */ 2211 term.charset = 1 - (ascii - '\016'); 2212 return; 2213 case '\032': /* SUB */ 2214 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2215 /* FALLTHROUGH */ 2216 case '\030': /* CAN */ 2217 csireset(); 2218 break; 2219 case '\005': /* ENQ (IGNORED) */ 2220 case '\000': /* NUL (IGNORED) */ 2221 case '\021': /* XON (IGNORED) */ 2222 case '\023': /* XOFF (IGNORED) */ 2223 case 0177: /* DEL (IGNORED) */ 2224 return; 2225 case 0x80: /* TODO: PAD */ 2226 case 0x81: /* TODO: HOP */ 2227 case 0x82: /* TODO: BPH */ 2228 case 0x83: /* TODO: NBH */ 2229 case 0x84: /* TODO: IND */ 2230 break; 2231 case 0x85: /* NEL -- Next line */ 2232 tnewline(1); /* always go to first col */ 2233 break; 2234 case 0x86: /* TODO: SSA */ 2235 case 0x87: /* TODO: ESA */ 2236 break; 2237 case 0x88: /* HTS -- Horizontal tab stop */ 2238 term.tabs[term.c.x] = 1; 2239 break; 2240 case 0x89: /* TODO: HTJ */ 2241 case 0x8a: /* TODO: VTS */ 2242 case 0x8b: /* TODO: PLD */ 2243 case 0x8c: /* TODO: PLU */ 2244 case 0x8d: /* TODO: RI */ 2245 case 0x8e: /* TODO: SS2 */ 2246 case 0x8f: /* TODO: SS3 */ 2247 case 0x91: /* TODO: PU1 */ 2248 case 0x92: /* TODO: PU2 */ 2249 case 0x93: /* TODO: STS */ 2250 case 0x94: /* TODO: CCH */ 2251 case 0x95: /* TODO: MW */ 2252 case 0x96: /* TODO: SPA */ 2253 case 0x97: /* TODO: EPA */ 2254 case 0x98: /* TODO: SOS */ 2255 case 0x99: /* TODO: SGCI */ 2256 break; 2257 case 0x9a: /* DECID -- Identify Terminal */ 2258 ttywrite(vtiden, strlen(vtiden), 0); 2259 break; 2260 case 0x9b: /* TODO: CSI */ 2261 case 0x9c: /* TODO: ST */ 2262 break; 2263 case 0x90: /* DCS -- Device Control String */ 2264 case 0x9d: /* OSC -- Operating System Command */ 2265 case 0x9e: /* PM -- Privacy Message */ 2266 case 0x9f: /* APC -- Application Program Command */ 2267 tstrsequence(ascii); 2268 return; 2269 } 2270 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2271 term.esc &= ~(ESC_STR_END|ESC_STR); 2272 } 2273 2274 /* 2275 * returns 1 when the sequence is finished and it hasn't to read 2276 * more characters for this sequence, otherwise 0 2277 */ 2278 int 2279 eschandle(uchar ascii) 2280 { 2281 switch (ascii) { 2282 case '[': 2283 term.esc |= ESC_CSI; 2284 return 0; 2285 case '#': 2286 term.esc |= ESC_TEST; 2287 return 0; 2288 case '%': 2289 term.esc |= ESC_UTF8; 2290 return 0; 2291 case 'P': /* DCS -- Device Control String */ 2292 case '_': /* APC -- Application Program Command */ 2293 case '^': /* PM -- Privacy Message */ 2294 case ']': /* OSC -- Operating System Command */ 2295 case 'k': /* old title set compatibility */ 2296 tstrsequence(ascii); 2297 return 0; 2298 case 'n': /* LS2 -- Locking shift 2 */ 2299 case 'o': /* LS3 -- Locking shift 3 */ 2300 term.charset = 2 + (ascii - 'n'); 2301 break; 2302 case '(': /* GZD4 -- set primary charset G0 */ 2303 case ')': /* G1D4 -- set secondary charset G1 */ 2304 case '*': /* G2D4 -- set tertiary charset G2 */ 2305 case '+': /* G3D4 -- set quaternary charset G3 */ 2306 term.icharset = ascii - '('; 2307 term.esc |= ESC_ALTCHARSET; 2308 return 0; 2309 case 'D': /* IND -- Linefeed */ 2310 if (term.c.y == term.bot) { 2311 tscrollup(term.top, 1, 1); 2312 } else { 2313 tmoveto(term.c.x, term.c.y+1); 2314 } 2315 break; 2316 case 'E': /* NEL -- Next line */ 2317 tnewline(1); /* always go to first col */ 2318 break; 2319 case 'H': /* HTS -- Horizontal tab stop */ 2320 term.tabs[term.c.x] = 1; 2321 break; 2322 case 'M': /* RI -- Reverse index */ 2323 if (term.c.y == term.top) { 2324 tscrolldown(term.top, 1, 1); 2325 } else { 2326 tmoveto(term.c.x, term.c.y-1); 2327 } 2328 break; 2329 case 'Z': /* DECID -- Identify Terminal */ 2330 ttywrite(vtiden, strlen(vtiden), 0); 2331 break; 2332 case 'c': /* RIS -- Reset to initial state */ 2333 treset(); 2334 resettitle(); 2335 xloadcols(); 2336 break; 2337 case '=': /* DECPAM -- Application keypad */ 2338 xsetmode(1, MODE_APPKEYPAD); 2339 break; 2340 case '>': /* DECPNM -- Normal keypad */ 2341 xsetmode(0, MODE_APPKEYPAD); 2342 break; 2343 case '7': /* DECSC -- Save Cursor */ 2344 tcursor(CURSOR_SAVE); 2345 break; 2346 case '8': /* DECRC -- Restore Cursor */ 2347 tcursor(CURSOR_LOAD); 2348 break; 2349 case '\\': /* ST -- String Terminator */ 2350 if (term.esc & ESC_STR_END) 2351 strhandle(); 2352 break; 2353 default: 2354 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2355 (uchar) ascii, isprint(ascii)? ascii:'.'); 2356 break; 2357 } 2358 return 1; 2359 } 2360 2361 void 2362 tputc(Rune u) 2363 { 2364 char c[UTF_SIZ]; 2365 int control; 2366 int width, len; 2367 Glyph *gp; 2368 2369 control = ISCONTROL(u); 2370 if (u < 127 || !IS_SET(MODE_UTF8)) { 2371 c[0] = u; 2372 width = len = 1; 2373 } else { 2374 len = utf8encode(u, c); 2375 if (!control && (width = wcwidth(u)) == -1) 2376 width = 1; 2377 } 2378 2379 if (IS_SET(MODE_PRINT)) 2380 tprinter(c, len); 2381 2382 /* 2383 * STR sequence must be checked before anything else 2384 * because it uses all following characters until it 2385 * receives a ESC, a SUB, a ST or any other C1 control 2386 * character. 2387 */ 2388 if (term.esc & ESC_STR) { 2389 if (u == '\a' || u == 030 || u == 032 || u == 033 || 2390 ISCONTROLC1(u)) { 2391 term.esc &= ~(ESC_START|ESC_STR); 2392 term.esc |= ESC_STR_END; 2393 goto check_control_code; 2394 } 2395 2396 if (strescseq.len+len >= strescseq.siz) { 2397 /* 2398 * Here is a bug in terminals. If the user never sends 2399 * some code to stop the str or esc command, then st 2400 * will stop responding. But this is better than 2401 * silently failing with unknown characters. At least 2402 * then users will report back. 2403 * 2404 * In the case users ever get fixed, here is the code: 2405 */ 2406 /* 2407 * term.esc = 0; 2408 * strhandle(); 2409 */ 2410 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2411 return; 2412 strescseq.siz *= 2; 2413 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2414 } 2415 2416 memmove(&strescseq.buf[strescseq.len], c, len); 2417 strescseq.len += len; 2418 return; 2419 } 2420 2421 check_control_code: 2422 /* 2423 * Actions of control codes must be performed as soon they arrive 2424 * because they can be embedded inside a control sequence, and 2425 * they must not cause conflicts with sequences. 2426 */ 2427 if (control) { 2428 tcontrolcode(u); 2429 /* 2430 * control codes are not shown ever 2431 */ 2432 if (!term.esc) 2433 term.lastc = 0; 2434 return; 2435 } else if (term.esc & ESC_START) { 2436 if (term.esc & ESC_CSI) { 2437 csiescseq.buf[csiescseq.len++] = u; 2438 if (BETWEEN(u, 0x40, 0x7E) 2439 || csiescseq.len >= \ 2440 sizeof(csiescseq.buf)-1) { 2441 term.esc = 0; 2442 csiparse(); 2443 csihandle(); 2444 } 2445 return; 2446 } else if (term.esc & ESC_UTF8) { 2447 tdefutf8(u); 2448 } else if (term.esc & ESC_ALTCHARSET) { 2449 tdeftran(u); 2450 } else if (term.esc & ESC_TEST) { 2451 tdectest(u); 2452 } else { 2453 if (!eschandle(u)) 2454 return; 2455 /* sequence already finished */ 2456 } 2457 term.esc = 0; 2458 /* 2459 * All characters which form part of a sequence are not 2460 * printed 2461 */ 2462 return; 2463 } 2464 if (selected(term.c.x, term.c.y)) 2465 selclear(); 2466 2467 gp = &term.line[term.c.y][term.c.x]; 2468 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2469 gp->mode |= ATTR_WRAP; 2470 tnewline(1); 2471 gp = &term.line[term.c.y][term.c.x]; 2472 } 2473 2474 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) 2475 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2476 2477 if (term.c.x+width > term.col) { 2478 tnewline(1); 2479 gp = &term.line[term.c.y][term.c.x]; 2480 } 2481 2482 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2483 term.lastc = u; 2484 2485 if (width == 2) { 2486 gp->mode |= ATTR_WIDE; 2487 if (term.c.x+1 < term.col) { 2488 gp[1].u = '\0'; 2489 gp[1].mode = ATTR_WDUMMY; 2490 } 2491 } 2492 if (term.c.x+width < term.col) { 2493 tmoveto(term.c.x+width, term.c.y); 2494 } else { 2495 term.c.state |= CURSOR_WRAPNEXT; 2496 } 2497 } 2498 2499 int 2500 twrite(const char *buf, int buflen, int show_ctrl) 2501 { 2502 int charsize; 2503 Rune u; 2504 int n; 2505 2506 for (n = 0; n < buflen; n += charsize) { 2507 if (IS_SET(MODE_UTF8)) { 2508 /* process a complete utf8 char */ 2509 charsize = utf8decode(buf + n, &u, buflen - n); 2510 if (charsize == 0) 2511 break; 2512 } else { 2513 u = buf[n] & 0xFF; 2514 charsize = 1; 2515 } 2516 if (show_ctrl && ISCONTROL(u)) { 2517 if (u & 0x80) { 2518 u &= 0x7f; 2519 tputc('^'); 2520 tputc('['); 2521 } else if (u != '\n' && u != '\r' && u != '\t') { 2522 u ^= 0x40; 2523 tputc('^'); 2524 } 2525 } 2526 tputc(u); 2527 } 2528 return n; 2529 } 2530 2531 void 2532 tresize(int col, int row) 2533 { 2534 int i, j; 2535 int minrow = MIN(row, term.row); 2536 int mincol = MIN(col, term.col); 2537 int *bp; 2538 TCursor c; 2539 2540 if (col < 1 || row < 1) { 2541 fprintf(stderr, 2542 "tresize: error resizing to %dx%d\n", col, row); 2543 return; 2544 } 2545 2546 /* 2547 * slide screen to keep cursor where we expect it - 2548 * tscrollup would work here, but we can optimize to 2549 * memmove because we're freeing the earlier lines 2550 */ 2551 for (i = 0; i <= term.c.y - row; i++) { 2552 free(term.line[i]); 2553 free(term.alt[i]); 2554 } 2555 /* ensure that both src and dst are not NULL */ 2556 if (i > 0) { 2557 memmove(term.line, term.line + i, row * sizeof(Line)); 2558 memmove(term.alt, term.alt + i, row * sizeof(Line)); 2559 } 2560 for (i += row; i < term.row; i++) { 2561 free(term.line[i]); 2562 free(term.alt[i]); 2563 } 2564 2565 /* resize to new height */ 2566 term.line = xrealloc(term.line, row * sizeof(Line)); 2567 term.alt = xrealloc(term.alt, row * sizeof(Line)); 2568 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2569 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2570 2571 for (i = 0; i < HISTSIZE; i++) { 2572 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2573 for (j = mincol; j < col; j++) { 2574 term.hist[i][j] = term.c.attr; 2575 term.hist[i][j].u = ' '; 2576 } 2577 } 2578 2579 /* resize each row to new width, zero-pad if needed */ 2580 for (i = 0; i < minrow; i++) { 2581 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2582 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 2583 } 2584 2585 /* allocate any new rows */ 2586 for (/* i = minrow */; i < row; i++) { 2587 term.line[i] = xmalloc(col * sizeof(Glyph)); 2588 term.alt[i] = xmalloc(col * sizeof(Glyph)); 2589 } 2590 if (col > term.col) { 2591 bp = term.tabs + term.col; 2592 2593 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2594 while (--bp > term.tabs && !*bp) 2595 /* nothing */ ; 2596 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2597 *bp = 1; 2598 } 2599 /* update terminal size */ 2600 term.col = col; 2601 term.row = row; 2602 /* reset scrolling region */ 2603 tsetscroll(0, row-1); 2604 /* make use of the LIMIT in tmoveto */ 2605 tmoveto(term.c.x, term.c.y); 2606 /* Clearing both screens (it makes dirty all lines) */ 2607 c = term.c; 2608 for (i = 0; i < 2; i++) { 2609 if (mincol < col && 0 < minrow) { 2610 tclearregion(mincol, 0, col - 1, minrow - 1); 2611 } 2612 if (0 < col && minrow < row) { 2613 tclearregion(0, minrow, col - 1, row - 1); 2614 } 2615 tswapscreen(); 2616 tcursor(CURSOR_LOAD); 2617 } 2618 term.c = c; 2619 } 2620 2621 void 2622 resettitle(void) 2623 { 2624 xsettitle(NULL); 2625 } 2626 2627 void 2628 drawregion(int x1, int y1, int x2, int y2) 2629 { 2630 int y; 2631 2632 for (y = y1; y < y2; y++) { 2633 if (!term.dirty[y]) 2634 continue; 2635 2636 term.dirty[y] = 0; 2637 xdrawline(TLINE(y), x1, y, x2); 2638 } 2639 } 2640 2641 void 2642 draw(void) 2643 { 2644 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2645 2646 if (!xstartdraw()) 2647 return; 2648 2649 /* adjust cursor position */ 2650 LIMIT(term.ocx, 0, term.col-1); 2651 LIMIT(term.ocy, 0, term.row-1); 2652 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 2653 term.ocx--; 2654 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 2655 cx--; 2656 2657 drawregion(0, 0, term.col, term.row); 2658 if (term.scr == 0) 2659 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2660 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2661 term.ocx = cx; 2662 term.ocy = term.c.y; 2663 xfinishdraw(); 2664 if (ocx != term.ocx || ocy != term.ocy) 2665 xximspot(term.ocx, term.ocy); 2666 } 2667 2668 void 2669 redraw(void) 2670 { 2671 tfulldirt(); 2672 draw(); 2673 }