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