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