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