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