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