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