st.c (87325B)
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: /* set foreground color to default */ 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: /* set background color to default */ 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 #else 1868 case 58: 1869 /* This starts a sequence to change the color of 1870 * "underline" pixels. We don't support that and 1871 * instead eat up a following "5;n" or "2;r;g;b". */ 1872 tdefcolor(attr, &i, l); 1873 break; 1874 #endif // UNDERCURL_PATCH 1875 default: 1876 if (BETWEEN(attr[i], 30, 37)) { 1877 #if MONOCHROME_PATCH 1878 term.c.attr.fg = defaultfg; 1879 #else 1880 term.c.attr.fg = attr[i] - 30; 1881 #endif // MONOCHROME_PATCH 1882 } else if (BETWEEN(attr[i], 40, 47)) { 1883 #if MONOCHROME_PATCH 1884 term.c.attr.bg = 0; 1885 #else 1886 term.c.attr.bg = attr[i] - 40; 1887 #endif // MONOCHROME_PATCH 1888 } else if (BETWEEN(attr[i], 90, 97)) { 1889 #if MONOCHROME_PATCH 1890 term.c.attr.fg = defaultfg; 1891 #else 1892 term.c.attr.fg = attr[i] - 90 + 8; 1893 #endif // MONOCHROME_PATCH 1894 } else if (BETWEEN(attr[i], 100, 107)) { 1895 #if MONOCHROME_PATCH 1896 term.c.attr.bg = 0; 1897 #else 1898 term.c.attr.bg = attr[i] - 100 + 8; 1899 #endif // MONOCHROME_PATCH 1900 } else { 1901 fprintf(stderr, 1902 "erresc(default): gfx attr %d unknown\n", 1903 attr[i]); 1904 csidump(); 1905 } 1906 break; 1907 } 1908 } 1909 } 1910 1911 void 1912 tsetscroll(int t, int b) 1913 { 1914 int temp; 1915 1916 LIMIT(t, 0, term.row-1); 1917 LIMIT(b, 0, term.row-1); 1918 if (t > b) { 1919 temp = t; 1920 t = b; 1921 b = temp; 1922 } 1923 term.top = t; 1924 term.bot = b; 1925 } 1926 1927 void 1928 tsetmode(int priv, int set, const int *args, int narg) 1929 { 1930 int alt; 1931 const int *lim; 1932 1933 for (lim = args + narg; args < lim; ++args) { 1934 if (priv) { 1935 switch (*args) { 1936 case 1: /* DECCKM -- Cursor key */ 1937 xsetmode(set, MODE_APPCURSOR); 1938 break; 1939 case 5: /* DECSCNM -- Reverse video */ 1940 xsetmode(set, MODE_REVERSE); 1941 break; 1942 case 6: /* DECOM -- Origin */ 1943 MODBIT(term.c.state, set, CURSOR_ORIGIN); 1944 tmoveato(0, 0); 1945 break; 1946 case 7: /* DECAWM -- Auto wrap */ 1947 MODBIT(term.mode, set, MODE_WRAP); 1948 break; 1949 case 0: /* Error (IGNORED) */ 1950 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1951 case 3: /* DECCOLM -- Column (IGNORED) */ 1952 case 4: /* DECSCLM -- Scroll (IGNORED) */ 1953 case 8: /* DECARM -- Auto repeat (IGNORED) */ 1954 case 18: /* DECPFF -- Printer feed (IGNORED) */ 1955 case 19: /* DECPEX -- Printer extent (IGNORED) */ 1956 case 42: /* DECNRCM -- National characters (IGNORED) */ 1957 case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1958 break; 1959 case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1960 xsetmode(!set, MODE_HIDE); 1961 break; 1962 case 9: /* X10 mouse compatibility mode */ 1963 xsetpointermotion(0); 1964 xsetmode(0, MODE_MOUSE); 1965 xsetmode(set, MODE_MOUSEX10); 1966 break; 1967 case 1000: /* 1000: report button press */ 1968 xsetpointermotion(0); 1969 xsetmode(0, MODE_MOUSE); 1970 xsetmode(set, MODE_MOUSEBTN); 1971 break; 1972 case 1002: /* 1002: report motion on button press */ 1973 xsetpointermotion(0); 1974 xsetmode(0, MODE_MOUSE); 1975 xsetmode(set, MODE_MOUSEMOTION); 1976 break; 1977 case 1003: /* 1003: enable all mouse motions */ 1978 xsetpointermotion(set); 1979 xsetmode(0, MODE_MOUSE); 1980 xsetmode(set, MODE_MOUSEMANY); 1981 break; 1982 case 1004: /* 1004: send focus events to tty */ 1983 xsetmode(set, MODE_FOCUS); 1984 break; 1985 case 1006: /* 1006: extended reporting mode */ 1986 xsetmode(set, MODE_MOUSESGR); 1987 break; 1988 case 1034: /* 1034: enable 8-bit mode for keyboard input */ 1989 xsetmode(set, MODE_8BIT); 1990 break; 1991 case 1049: /* swap screen & set/restore cursor as xterm */ 1992 if (!allowaltscreen) 1993 break; 1994 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1995 /* FALLTHROUGH */ 1996 case 47: /* swap screen buffer */ 1997 case 1047: /* swap screen buffer */ 1998 if (!allowaltscreen) 1999 break; 2000 #if REFLOW_PATCH 2001 if (set) 2002 tloadaltscreen(*args != 47, *args == 1049); 2003 else 2004 tloaddefscreen(*args != 47, *args == 1049); 2005 break; 2006 #else 2007 alt = IS_SET(MODE_ALTSCREEN); 2008 if (alt) { 2009 #if COLUMNS_PATCH 2010 tclearregion(0, 0, term.maxcol-1, term.row-1); 2011 #else 2012 tclearregion(0, 0, term.col-1, term.row-1); 2013 #endif // COLUMNS_PATCH 2014 } 2015 if (set ^ alt) /* set is always 1 or 0 */ 2016 tswapscreen(); 2017 if (*args != 1049) 2018 break; 2019 /* FALLTHROUGH */ 2020 #endif // REFLOW_PATCH 2021 case 1048: /* save/restore cursor (like DECSC/DECRC) */ 2022 #if REFLOW_PATCH 2023 if (!allowaltscreen) 2024 break; 2025 #endif // REFLOW_PATCH 2026 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 2027 break; 2028 case 2004: /* 2004: bracketed paste mode */ 2029 xsetmode(set, MODE_BRCKTPASTE); 2030 break; 2031 /* Not implemented mouse modes. See comments there. */ 2032 case 1001: /* mouse highlight mode; can hang the 2033 terminal by design when implemented. */ 2034 case 1005: /* UTF-8 mouse mode; will confuse 2035 applications not supporting UTF-8 2036 and luit. */ 2037 case 1015: /* urxvt mangled mouse mode; incompatible 2038 and can be mistaken for other control 2039 codes. */ 2040 break; 2041 #if SIXEL_PATCH 2042 case 80: /* DECSDM -- Sixel Display Mode */ 2043 MODBIT(term.mode, set, MODE_SIXEL_SDM); 2044 break; 2045 case 8452: /* sixel scrolling leaves cursor to right of graphic */ 2046 MODBIT(term.mode, set, MODE_SIXEL_CUR_RT); 2047 break; 2048 #endif // SIXEL_PATCH 2049 #if SYNC_PATCH 2050 case 2026: 2051 if (set) { 2052 tsync_begin(); 2053 } else { 2054 tsync_end(); 2055 } 2056 break; 2057 #endif // SYNC_PATCH 2058 default: 2059 fprintf(stderr, 2060 "erresc: unknown private set/reset mode %d\n", 2061 *args); 2062 break; 2063 } 2064 } else { 2065 switch (*args) { 2066 case 0: /* Error (IGNORED) */ 2067 break; 2068 case 2: 2069 xsetmode(set, MODE_KBDLOCK); 2070 break; 2071 case 4: /* IRM -- Insertion-replacement */ 2072 MODBIT(term.mode, set, MODE_INSERT); 2073 break; 2074 case 12: /* SRM -- Send/Receive */ 2075 MODBIT(term.mode, !set, MODE_ECHO); 2076 break; 2077 case 20: /* LNM -- Linefeed/new line */ 2078 MODBIT(term.mode, set, MODE_CRLF); 2079 break; 2080 default: 2081 fprintf(stderr, 2082 "erresc: unknown set/reset mode %d\n", 2083 *args); 2084 break; 2085 } 2086 } 2087 } 2088 } 2089 2090 void 2091 csihandle(void) 2092 { 2093 char buffer[40]; 2094 int n = 0, len; 2095 #if SIXEL_PATCH 2096 ImageList *im, *next; 2097 int pi, pa; 2098 #endif // SIXEL_PATCH 2099 #if REFLOW_PATCH 2100 int x; 2101 #endif // REFLOW_PATCH 2102 #if COLUMNS_PATCH 2103 int maxcol = term.maxcol; 2104 #else 2105 int maxcol = term.col; 2106 #endif // COLUMNS_PATCH 2107 2108 switch (csiescseq.mode[0]) { 2109 default: 2110 unknown: 2111 fprintf(stderr, "erresc: unknown csi "); 2112 csidump(); 2113 /* die(""); */ 2114 break; 2115 case '@': /* ICH -- Insert <n> blank char */ 2116 DEFAULT(csiescseq.arg[0], 1); 2117 tinsertblank(csiescseq.arg[0]); 2118 break; 2119 case 'A': /* CUU -- Cursor <n> Up */ 2120 DEFAULT(csiescseq.arg[0], 1); 2121 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 2122 break; 2123 case 'B': /* CUD -- Cursor <n> Down */ 2124 case 'e': /* VPR --Cursor <n> Down */ 2125 DEFAULT(csiescseq.arg[0], 1); 2126 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 2127 break; 2128 case 'i': /* MC -- Media Copy */ 2129 switch (csiescseq.arg[0]) { 2130 case 0: 2131 tdump(); 2132 break; 2133 case 1: 2134 tdumpline(term.c.y); 2135 break; 2136 case 2: 2137 tdumpsel(); 2138 break; 2139 case 4: 2140 term.mode &= ~MODE_PRINT; 2141 break; 2142 case 5: 2143 term.mode |= MODE_PRINT; 2144 break; 2145 } 2146 break; 2147 case 'c': /* DA -- Device Attributes */ 2148 if (csiescseq.arg[0] == 0) 2149 ttywrite(vtiden, strlen(vtiden), 0); 2150 break; 2151 case 'b': /* REP -- if last char is printable print it <n> more times */ 2152 LIMIT(csiescseq.arg[0], 1, 65535); 2153 if (term.lastc) 2154 while (csiescseq.arg[0]-- > 0) 2155 tputc(term.lastc); 2156 break; 2157 case 'C': /* CUF -- Cursor <n> Forward */ 2158 case 'a': /* HPR -- Cursor <n> Forward */ 2159 DEFAULT(csiescseq.arg[0], 1); 2160 tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 2161 break; 2162 case 'D': /* CUB -- Cursor <n> Backward */ 2163 DEFAULT(csiescseq.arg[0], 1); 2164 tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 2165 break; 2166 case 'E': /* CNL -- Cursor <n> Down and first col */ 2167 DEFAULT(csiescseq.arg[0], 1); 2168 tmoveto(0, term.c.y+csiescseq.arg[0]); 2169 break; 2170 case 'F': /* CPL -- Cursor <n> Up and first col */ 2171 DEFAULT(csiescseq.arg[0], 1); 2172 tmoveto(0, term.c.y-csiescseq.arg[0]); 2173 break; 2174 case 'g': /* TBC -- Tabulation clear */ 2175 switch (csiescseq.arg[0]) { 2176 case 0: /* clear current tab stop */ 2177 term.tabs[term.c.x] = 0; 2178 break; 2179 case 3: /* clear all the tabs */ 2180 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 2181 break; 2182 default: 2183 goto unknown; 2184 } 2185 break; 2186 case 'G': /* CHA -- Move to <col> */ 2187 case '`': /* HPA */ 2188 DEFAULT(csiescseq.arg[0], 1); 2189 tmoveto(csiescseq.arg[0]-1, term.c.y); 2190 break; 2191 case 'H': /* CUP -- Move to <row> <col> */ 2192 case 'f': /* HVP */ 2193 DEFAULT(csiescseq.arg[0], 1); 2194 DEFAULT(csiescseq.arg[1], 1); 2195 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 2196 break; 2197 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 2198 DEFAULT(csiescseq.arg[0], 1); 2199 tputtab(csiescseq.arg[0]); 2200 break; 2201 case 'J': /* ED -- Clear screen */ 2202 switch (csiescseq.arg[0]) { 2203 case 0: /* below */ 2204 #if REFLOW_PATCH 2205 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); 2206 if (term.c.y < term.row-1) 2207 tclearregion(0, term.c.y+1, term.col-1, term.row-1, 1); 2208 #else 2209 tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y); 2210 if (term.c.y < term.row-1) 2211 tclearregion(0, term.c.y+1, maxcol-1, term.row-1); 2212 #endif // REFLOW_PATCH 2213 break; 2214 case 1: /* above */ 2215 #if REFLOW_PATCH 2216 if (term.c.y > 0) 2217 tclearregion(0, 0, term.col-1, term.c.y-1, 1); 2218 tclearregion(0, term.c.y, term.c.x, term.c.y, 1); 2219 #else 2220 if (term.c.y > 0) 2221 tclearregion(0, 0, maxcol-1, term.c.y-1); 2222 tclearregion(0, term.c.y, term.c.x, term.c.y); 2223 #endif // REFLOW_PATCH 2224 break; 2225 case 2: /* screen */ 2226 #if REFLOW_PATCH 2227 if (IS_SET(MODE_ALTSCREEN)) { 2228 tclearregion(0, 0, term.col-1, term.row-1, 1); 2229 #if SIXEL_PATCH 2230 tdeleteimages(); 2231 #endif // SIXEL_PATCH 2232 break; 2233 } 2234 /* vte does this: 2235 tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */ 2236 /* alacritty does this: */ 2237 for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--) 2238 ; 2239 #if SIXEL_PATCH 2240 for (im = term.images; im; im = im->next) 2241 n = MAX(im->y - term.scr, n); 2242 #endif // SIXEL_PATCH 2243 if (n >= 0) 2244 tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST); 2245 tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST); 2246 break; 2247 #else // !REFLOW_PATCH 2248 #if SCROLLBACK_PATCH 2249 if (!IS_SET(MODE_ALTSCREEN)) { 2250 #if SCROLLBACK_PATCH 2251 kscrolldown(&((Arg){ .i = term.scr })); 2252 #endif 2253 int n, m, bot = term.bot; 2254 term.bot = term.row-1; 2255 for (n = term.row-1; n >= 0; n--) { 2256 for (m = 0; m < maxcol && term.line[n][m].u == ' ' && !term.line[n][m].mode; m++); 2257 if (m < maxcol) { 2258 #if SCROLLBACK_PATCH 2259 tscrollup(0, n+1, 1); 2260 #else 2261 tscrollup(0, n+1); 2262 #endif 2263 break; 2264 } 2265 } 2266 if (n < term.row-1) 2267 tclearregion(0, 0, maxcol-1, term.row-n-2); 2268 term.bot = bot; 2269 break; 2270 } 2271 #endif // SCROLLBACK_PATCH 2272 2273 tclearregion(0, 0, maxcol-1, term.row-1); 2274 #if SIXEL_PATCH 2275 tdeleteimages(); 2276 #endif // SIXEL_PATCH 2277 #endif // REFLOW_PTCH 2278 break; 2279 case 3: /* scrollback */ 2280 #if REFLOW_PATCH 2281 if (IS_SET(MODE_ALTSCREEN)) 2282 break; 2283 kscrolldown(&((Arg){ .i = term.scr })); 2284 term.scr = 0; 2285 term.histi = 0; 2286 term.histf = 0; 2287 #if SIXEL_PATCH 2288 for (im = term.images; im; im = next) { 2289 next = im->next; 2290 if (im->y < 0) 2291 delete_image(im); 2292 } 2293 #endif // SIXEL_PATCH 2294 break; 2295 #else // !REFLOW_PATCH 2296 #if SCROLLBACK_PATCH 2297 if (!IS_SET(MODE_ALTSCREEN)) { 2298 term.scr = 0; 2299 term.histi = 0; 2300 term.histn = 0; 2301 Glyph g=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0}; 2302 for (int i = 0; i < HISTSIZE; i++) { 2303 for (int j = 0; j < maxcol; j++) 2304 term.hist[i][j] = g; 2305 } 2306 } 2307 #endif // SCROLLBACK_PATCH 2308 #if SIXEL_PATCH 2309 for (im = term.images; im; im = next) { 2310 next = im->next; 2311 if (im->y < 0) 2312 delete_image(im); 2313 } 2314 #endif // SIXEL_PATCH 2315 break; 2316 #endif // REFLOW_PATCH 2317 #if SIXEL_PATCH 2318 case 6: /* sixels */ 2319 tdeleteimages(); 2320 tfulldirt(); 2321 break; 2322 #endif // SIXEL_PATCH 2323 default: 2324 goto unknown; 2325 } 2326 break; 2327 case 'K': /* EL -- Clear line */ 2328 switch (csiescseq.arg[0]) { 2329 #if REFLOW_PATCH 2330 case 0: /* right */ 2331 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); 2332 break; 2333 case 1: /* left */ 2334 tclearregion(0, term.c.y, term.c.x, term.c.y, 1); 2335 break; 2336 case 2: /* all */ 2337 tclearregion(0, term.c.y, term.col-1, term.c.y, 1); 2338 break; 2339 } 2340 #else 2341 case 0: /* right */ 2342 tclearregion(term.c.x, term.c.y, maxcol-1, term.c.y); 2343 break; 2344 case 1: /* left */ 2345 tclearregion(0, term.c.y, term.c.x, term.c.y); 2346 break; 2347 case 2: /* all */ 2348 tclearregion(0, term.c.y, maxcol-1, term.c.y); 2349 break; 2350 } 2351 #endif // REFLOW_PATCH 2352 break; 2353 case 'S': /* SU -- Scroll <n> line up ; XTSMGRAPHICS */ 2354 if (csiescseq.priv) { 2355 #if SIXEL_PATCH 2356 if (csiescseq.narg > 1) { 2357 /* XTSMGRAPHICS */ 2358 pi = csiescseq.arg[0]; 2359 pa = csiescseq.arg[1]; 2360 if (pi == 1 && (pa == 1 || pa == 2 || pa == 4)) { 2361 /* number of sixel color registers */ 2362 /* (read, reset and read the maximum value give the same response) */ 2363 n = snprintf(buffer, sizeof buffer, "\033[?1;0;%dS", DECSIXEL_PALETTE_MAX); 2364 ttywrite(buffer, n, 1); 2365 break; 2366 } else if (pi == 2 && (pa == 1 || pa == 2 || pa == 4)) { 2367 /* sixel graphics geometry (in pixels) */ 2368 /* (read, reset and read the maximum value give the same response) */ 2369 n = snprintf(buffer, sizeof buffer, "\033[?2;0;%d;%dS", 2370 MIN(term.col * win.cw, DECSIXEL_WIDTH_MAX), 2371 MIN(term.row * win.ch, DECSIXEL_HEIGHT_MAX)); 2372 ttywrite(buffer, n, 1); 2373 break; 2374 } 2375 /* the number of color registers and sixel geometry can't be changed */ 2376 n = snprintf(buffer, sizeof buffer, "\033[?%d;3;0S", pi); /* failure */ 2377 ttywrite(buffer, n, 1); 2378 } 2379 #endif // SIXEL_PATCH 2380 goto unknown; 2381 } 2382 DEFAULT(csiescseq.arg[0], 1); 2383 #if REFLOW_PATCH 2384 /* xterm, urxvt, alacritty save this in history */ 2385 tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST); 2386 #elif SIXEL_PATCH && SCROLLBACK_PATCH 2387 tscrollup(term.top, csiescseq.arg[0], 1); 2388 #elif SCROLLBACK_PATCH 2389 tscrollup(term.top, csiescseq.arg[0], 0); 2390 #else 2391 tscrollup(term.top, csiescseq.arg[0]); 2392 #endif // SCROLLBACK_PATCH 2393 break; 2394 case 'T': /* SD -- Scroll <n> line down */ 2395 DEFAULT(csiescseq.arg[0], 1); 2396 tscrolldown(term.top, csiescseq.arg[0]); 2397 break; 2398 case 'L': /* IL -- Insert <n> blank lines */ 2399 DEFAULT(csiescseq.arg[0], 1); 2400 tinsertblankline(csiescseq.arg[0]); 2401 break; 2402 case 'l': /* RM -- Reset Mode */ 2403 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 2404 break; 2405 case 'M': /* DL -- Delete <n> lines */ 2406 DEFAULT(csiescseq.arg[0], 1); 2407 tdeleteline(csiescseq.arg[0]); 2408 break; 2409 case 'X': /* ECH -- Erase <n> char */ 2410 #if REFLOW_PATCH 2411 if (csiescseq.arg[0] < 0) 2412 return; 2413 DEFAULT(csiescseq.arg[0], 1); 2414 x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1; 2415 tclearregion(term.c.x, term.c.y, x, term.c.y, 1); 2416 #else 2417 DEFAULT(csiescseq.arg[0], 1); 2418 tclearregion(term.c.x, term.c.y, 2419 term.c.x + csiescseq.arg[0] - 1, term.c.y); 2420 #endif // REFLOW_PATCH 2421 break; 2422 case 'P': /* DCH -- Delete <n> char */ 2423 DEFAULT(csiescseq.arg[0], 1); 2424 tdeletechar(csiescseq.arg[0]); 2425 break; 2426 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 2427 DEFAULT(csiescseq.arg[0], 1); 2428 tputtab(-csiescseq.arg[0]); 2429 break; 2430 case 'd': /* VPA -- Move to <row> */ 2431 DEFAULT(csiescseq.arg[0], 1); 2432 tmoveato(term.c.x, csiescseq.arg[0]-1); 2433 break; 2434 case 'h': /* SM -- Set terminal mode */ 2435 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 2436 break; 2437 case 'm': /* SGR -- Terminal attribute (color) */ 2438 tsetattr(csiescseq.arg, csiescseq.narg); 2439 break; 2440 case 'n': /* DSR -- Device Status Report */ 2441 switch (csiescseq.arg[0]) { 2442 case 5: /* Status Report "OK" `0n` */ 2443 ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); 2444 break; 2445 case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */ 2446 len = snprintf(buffer, sizeof(buffer), "\033[%i;%iR", 2447 term.c.y+1, term.c.x+1); 2448 ttywrite(buffer, len, 0); 2449 break; 2450 default: 2451 goto unknown; 2452 } 2453 break; 2454 #if SYNC_PATCH || SIXEL_PATCH 2455 case '$': /* DECRQM -- DEC Request Mode (private) */ 2456 if (csiescseq.mode[1] == 'p' && csiescseq.priv) { 2457 switch (csiescseq.arg[0]) { 2458 #if SIXEL_PATCH 2459 case 80: 2460 /* Sixel Display Mode */ 2461 ttywrite(IS_SET(MODE_SIXEL_SDM) ? "\033[?80;1$y" 2462 : "\033[?80;2$y", 9, 0); 2463 break; 2464 case 8452: 2465 /* Sixel scrolling leaves cursor to right of graphic */ 2466 ttywrite(IS_SET(MODE_SIXEL_CUR_RT) ? "\033[?8452;1$y" 2467 : "\033[?8452;2$y", 11, 0); 2468 break; 2469 #endif // SIXEL_PATCH 2470 #if SYNC_PATCH 2471 case 2026: 2472 /* https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 */ 2473 ttywrite(su ? "\033[?2026;1$y" : "\033[?2026;2$y", 11, 0); 2474 break; 2475 #endif // SYNC_PATCH 2476 default: 2477 goto unknown; 2478 } 2479 break; 2480 } 2481 goto unknown; 2482 #endif // SYNC_PATCH | SIXEL_PATCH 2483 case 'r': /* DECSTBM -- Set Scrolling Region */ 2484 if (csiescseq.priv) { 2485 goto unknown; 2486 } else { 2487 DEFAULT(csiescseq.arg[0], 1); 2488 DEFAULT(csiescseq.arg[1], term.row); 2489 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 2490 tmoveato(0, 0); 2491 } 2492 break; 2493 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 2494 tcursor(CURSOR_SAVE); 2495 break; 2496 #if CSI_22_23_PATCH | SIXEL_PATCH 2497 case 't': /* title stack operations ; XTWINOPS */ 2498 switch (csiescseq.arg[0]) { 2499 #if SIXEL_PATCH 2500 case 14: /* text area size in pixels */ 2501 if (csiescseq.narg > 1) 2502 goto unknown; 2503 n = snprintf(buffer, sizeof buffer, "\033[4;%d;%dt", 2504 term.row * win.ch, term.col * win.cw); 2505 ttywrite(buffer, n, 1); 2506 break; 2507 case 16: /* character cell size in pixels */ 2508 n = snprintf(buffer, sizeof buffer, "\033[6;%d;%dt", win.ch, win.cw); 2509 ttywrite(buffer, n, 1); 2510 break; 2511 case 18: /* size of the text area in characters */ 2512 n = snprintf(buffer, sizeof buffer, "\033[8;%d;%dt", term.row, term.col); 2513 ttywrite(buffer, n, 1); 2514 break; 2515 #endif // SIXEL_PATCH 2516 #if CSI_22_23_PATCH 2517 case 22: /* pust current title on stack */ 2518 switch (csiescseq.arg[1]) { 2519 case 0: 2520 case 1: 2521 case 2: 2522 xpushtitle(); 2523 break; 2524 default: 2525 goto unknown; 2526 } 2527 break; 2528 case 23: /* pop last title from stack */ 2529 switch (csiescseq.arg[1]) { 2530 case 0: 2531 case 1: 2532 case 2: 2533 xsettitle(NULL, 1); 2534 break; 2535 default: 2536 goto unknown; 2537 } 2538 break; 2539 #endif // CSI_22_23_PATCH 2540 default: 2541 goto unknown; 2542 } 2543 break; 2544 #endif // CSI_22_23_PATCH | SIXEL_PATCH 2545 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 2546 if (csiescseq.priv) { 2547 goto unknown; 2548 } else { 2549 tcursor(CURSOR_LOAD); 2550 } 2551 break; 2552 case ' ': 2553 switch (csiescseq.mode[1]) { 2554 case 'q': /* DECSCUSR -- Set Cursor Style */ 2555 if (xsetcursor(csiescseq.arg[0])) 2556 goto unknown; 2557 break; 2558 default: 2559 goto unknown; 2560 } 2561 break; 2562 } 2563 } 2564 2565 void 2566 csidump(void) 2567 { 2568 size_t i; 2569 uint c; 2570 2571 fprintf(stderr, "ESC["); 2572 for (i = 0; i < csiescseq.len; i++) { 2573 c = csiescseq.buf[i] & 0xff; 2574 if (isprint(c)) { 2575 putc(c, stderr); 2576 } else if (c == '\n') { 2577 fprintf(stderr, "(\\n)"); 2578 } else if (c == '\r') { 2579 fprintf(stderr, "(\\r)"); 2580 } else if (c == 0x1b) { 2581 fprintf(stderr, "(\\e)"); 2582 } else { 2583 fprintf(stderr, "(%02x)", c); 2584 } 2585 } 2586 putc('\n', stderr); 2587 } 2588 2589 void 2590 csireset(void) 2591 { 2592 memset(&csiescseq, 0, sizeof(csiescseq)); 2593 } 2594 2595 void 2596 osc_color_response(int num, int index, int is_osc4) 2597 { 2598 int n; 2599 char buf[32]; 2600 unsigned char r, g, b; 2601 2602 if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { 2603 fprintf(stderr, "erresc: failed to fetch %s color %d\n", 2604 is_osc4 ? "osc4" : "osc", 2605 is_osc4 ? num : index); 2606 return; 2607 } 2608 2609 n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x%s", 2610 is_osc4 ? "4;" : "", num, r, r, g, g, b, b, strescseq.term); 2611 if (n < 0 || n >= sizeof(buf)) { 2612 fprintf(stderr, "error: %s while printing %s response\n", 2613 n < 0 ? "snprintf failed" : "truncation occurred", 2614 is_osc4 ? "osc4" : "osc"); 2615 } else { 2616 ttywrite(buf, n, 1); 2617 } 2618 } 2619 2620 void 2621 strhandle(void) 2622 { 2623 char *p = NULL, *dec; 2624 int j, narg, par; 2625 const struct { int idx; char *str; } osc_table[] = { 2626 { defaultfg, "foreground" }, 2627 { defaultbg, "background" }, 2628 { defaultcs, "cursor" } 2629 }; 2630 #if SIXEL_PATCH 2631 ImageList *im, *newimages, *next, *tail = NULL; 2632 int i, x1, y1, x2, y2, y, numimages; 2633 int cx, cy; 2634 Line line; 2635 #if SCROLLBACK_PATCH || REFLOW_PATCH 2636 int scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr; 2637 #else 2638 int scr = 0; 2639 #endif // SCROLLBACK_PATCH 2640 #endif // SIXEL_PATCH 2641 2642 term.esc &= ~(ESC_STR_END|ESC_STR); 2643 strparse(); 2644 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 2645 2646 switch (strescseq.type) { 2647 case ']': /* OSC -- Operating System Command */ 2648 switch (par) { 2649 case 0: 2650 if (narg > 1) { 2651 #if CSI_22_23_PATCH 2652 xsettitle(strescseq.args[1], 0); 2653 #else 2654 xsettitle(strescseq.args[1]); 2655 #endif // CSI_22_23_PATCH 2656 xseticontitle(strescseq.args[1]); 2657 } 2658 return; 2659 case 1: 2660 if (narg > 1) 2661 xseticontitle(strescseq.args[1]); 2662 return; 2663 case 2: 2664 if (narg > 1) 2665 #if CSI_22_23_PATCH 2666 xsettitle(strescseq.args[1], 0); 2667 #else 2668 xsettitle(strescseq.args[1]); 2669 #endif // CSI_22_23_PATCH 2670 return; 2671 case 52: /* manipulate selection data */ 2672 if (narg > 2 && allowwindowops) { 2673 dec = base64dec(strescseq.args[2]); 2674 if (dec) { 2675 xsetsel(dec); 2676 xclipcopy(); 2677 } else { 2678 fprintf(stderr, "erresc: invalid base64\n"); 2679 } 2680 } 2681 return; 2682 #if OSC7_PATCH 2683 case 7: 2684 osc7parsecwd((const char *)strescseq.args[1]); 2685 return; 2686 #endif // OSC7_PATCH 2687 case 8: /* Clear Hyperlinks */ 2688 return; 2689 case 10: /* set dynamic VT100 text foreground color */ 2690 case 11: /* set dynamic VT100 text background color */ 2691 case 12: /* set dynamic text cursor color */ 2692 if (narg < 2) 2693 break; 2694 p = strescseq.args[1]; 2695 if ((j = par - 10) < 0 || j >= LEN(osc_table)) 2696 break; /* shouldn't be possible */ 2697 2698 if (!strcmp(p, "?")) { 2699 osc_color_response(par, osc_table[j].idx, 0); 2700 } else if (xsetcolorname(osc_table[j].idx, p)) { 2701 fprintf(stderr, "erresc: invalid %s color: %s\n", 2702 osc_table[j].str, p); 2703 } else { 2704 tfulldirt(); 2705 } 2706 return; 2707 case 4: /* color set */ 2708 if (narg < 3) 2709 break; 2710 p = strescseq.args[2]; 2711 /* FALLTHROUGH */ 2712 case 104: /* color reset */ 2713 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2714 2715 if (p && !strcmp(p, "?")) { 2716 osc_color_response(j, 0, 1); 2717 } else if (xsetcolorname(j, p)) { 2718 if (par == 104 && narg <= 1) { 2719 xloadcols(); 2720 return; /* color reset without parameter */ 2721 } 2722 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 2723 j, p ? p : "(null)"); 2724 } else { 2725 /* 2726 * TODO if defaultbg color is changed, borders 2727 * are dirty 2728 */ 2729 tfulldirt(); 2730 } 2731 return; 2732 case 110: /* reset dynamic VT100 text foreground color */ 2733 case 111: /* reset dynamic VT100 text background color */ 2734 case 112: /* reset dynamic text cursor color */ 2735 if (narg != 1) 2736 break; 2737 if ((j = par - 110) < 0 || j >= LEN(osc_table)) 2738 break; /* shouldn't be possible */ 2739 if (xsetcolorname(osc_table[j].idx, NULL)) { 2740 fprintf(stderr, "erresc: %s color not found\n", osc_table[j].str); 2741 } else { 2742 tfulldirt(); 2743 } 2744 return; 2745 #if OSC133_PATCH 2746 case 133: 2747 if (narg < 2) 2748 break; 2749 switch (*strescseq.args[1]) { 2750 case 'A': 2751 term.c.attr.mode |= ATTR_FTCS_PROMPT; 2752 break; 2753 /* We don't handle these arguments yet */ 2754 case 'B': 2755 case 'C': 2756 case 'D': 2757 break; 2758 default: 2759 fprintf(stderr, "erresc: unknown OSC 133 argument: %c\n", *strescseq.args[1]); 2760 break; 2761 } 2762 return; 2763 #endif // OSC133_PATCH 2764 } 2765 break; 2766 case 'k': /* old title set compatibility */ 2767 #if CSI_22_23_PATCH 2768 xsettitle(strescseq.args[0], 0); 2769 #else 2770 xsettitle(strescseq.args[0]); 2771 #endif // CSI_22_23_PATCH 2772 return; 2773 case 'P': /* DCS -- Device Control String */ 2774 #if SIXEL_PATCH 2775 if (IS_SET(MODE_SIXEL)) { 2776 term.mode &= ~MODE_SIXEL; 2777 if (!sixel_st.image.data) { 2778 sixel_parser_deinit(&sixel_st); 2779 return; 2780 } 2781 cx = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.x; 2782 cy = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.y; 2783 if ((numimages = sixel_parser_finalize(&sixel_st, &newimages, 2784 cx, cy + scr, win.cw, win.ch)) <= 0) { 2785 sixel_parser_deinit(&sixel_st); 2786 perror("sixel_parser_finalize() failed"); 2787 return; 2788 } 2789 sixel_parser_deinit(&sixel_st); 2790 x1 = newimages->x; 2791 y1 = newimages->y; 2792 x2 = x1 + newimages->cols; 2793 y2 = y1 + numimages; 2794 /* Delete the old images that are covered by the new image(s). We also need 2795 * to check if they have already been deleted before adding the new ones. */ 2796 if (term.images) { 2797 char transparent[numimages]; 2798 for (i = 0, im = newimages; im; im = im->next, i++) { 2799 transparent[i] = im->transparent; 2800 } 2801 for (im = term.images; im; im = next) { 2802 next = im->next; 2803 if (im->y >= y1 && im->y < y2) { 2804 y = im->y - scr; 2805 if (y >= 0 && y < term.row && term.dirty[y]) { 2806 line = term.line[y]; 2807 j = MIN(im->x + im->cols, term.col); 2808 for (i = im->x; i < j; i++) { 2809 if (line[i].mode & ATTR_SIXEL) 2810 break; 2811 } 2812 if (i == j) { 2813 delete_image(im); 2814 continue; 2815 } 2816 } 2817 if (im->x >= x1 && im->x + im->cols <= x2 && !transparent[im->y - y1]) { 2818 delete_image(im); 2819 continue; 2820 } 2821 } 2822 tail = im; 2823 } 2824 } 2825 if (tail) { 2826 tail->next = newimages; 2827 newimages->prev = tail; 2828 } else { 2829 term.images = newimages; 2830 } 2831 #if COLUMNS_PATCH && !REFLOW_PATCH 2832 x2 = MIN(x2, term.maxcol) - 1; 2833 #else 2834 x2 = MIN(x2, term.col) - 1; 2835 #endif // COLUMNS_PATCH 2836 if (IS_SET(MODE_SIXEL_SDM)) { 2837 /* Sixel display mode: put the sixel in the upper left corner of 2838 * the screen, disable scrolling (the sixel will be truncated if 2839 * it is too long) and do not change the cursor position. */ 2840 for (i = 0, im = newimages; im; im = next, i++) { 2841 next = im->next; 2842 if (i >= term.row) { 2843 delete_image(im); 2844 continue; 2845 } 2846 im->y = i + scr; 2847 tsetsixelattr(term.line[i], x1, x2); 2848 term.dirty[MIN(im->y, term.row-1)] = 1; 2849 } 2850 } else { 2851 for (i = 0, im = newimages; im; im = next, i++) { 2852 next = im->next; 2853 #if SCROLLBACK_PATCH || REFLOW_PATCH 2854 scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr; 2855 #endif // SCROLLBACK_PATCH 2856 im->y = term.c.y + scr; 2857 tsetsixelattr(term.line[term.c.y], x1, x2); 2858 term.dirty[MIN(im->y, term.row-1)] = 1; 2859 if (i < numimages-1) { 2860 im->next = NULL; 2861 tnewline(0); 2862 im->next = next; 2863 } 2864 } 2865 /* if mode 8452 is set, sixel scrolling leaves cursor to right of graphic */ 2866 if (IS_SET(MODE_SIXEL_CUR_RT)) 2867 term.c.x = MIN(term.c.x + newimages->cols, term.col-1); 2868 } 2869 } 2870 #endif // SIXEL_PATCH 2871 #if SYNC_PATCH 2872 /* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */ 2873 if (strstr(strescseq.buf, "=1s") == strescseq.buf) 2874 tsync_begin(); /* BSU */ 2875 else if (strstr(strescseq.buf, "=2s") == strescseq.buf) 2876 tsync_end(); /* ESU */ 2877 #endif // SYNC_PATCH 2878 #if SIXEL_PATCH || SYNC_PATCH 2879 return; 2880 #endif // SIXEL_PATCH | SYNC_PATCH 2881 case '_': /* APC -- Application Program Command */ 2882 case '^': /* PM -- Privacy Message */ 2883 return; 2884 } 2885 2886 fprintf(stderr, "erresc: unknown str "); 2887 strdump(); 2888 } 2889 2890 void 2891 strparse(void) 2892 { 2893 int c; 2894 char *p = strescseq.buf; 2895 2896 strescseq.narg = 0; 2897 strescseq.buf[strescseq.len] = '\0'; 2898 2899 if (*p == '\0') 2900 return; 2901 2902 /* preserve semicolons in window titles, icon names and OSC 7 sequences */ 2903 if (strescseq.type == ']' && ( 2904 p[0] <= '2' 2905 #if OSC7_PATCH 2906 || p[0] == '7' 2907 #endif // OSC7_PATCH 2908 ) && p[1] == ';') { 2909 strescseq.args[strescseq.narg++] = p; 2910 strescseq.args[strescseq.narg++] = p + 2; 2911 p[1] = '\0'; 2912 return; 2913 } 2914 2915 while (strescseq.narg < STR_ARG_SIZ) { 2916 strescseq.args[strescseq.narg++] = p; 2917 while ((c = *p) != ';' && c != '\0') 2918 ++p; 2919 if (c == '\0') 2920 return; 2921 *p++ = '\0'; 2922 } 2923 } 2924 2925 void 2926 strdump(void) 2927 { 2928 size_t i; 2929 uint c; 2930 2931 fprintf(stderr, "ESC%c", strescseq.type); 2932 for (i = 0; i < strescseq.len; i++) { 2933 c = strescseq.buf[i] & 0xff; 2934 if (c == '\0') { 2935 putc('\n', stderr); 2936 return; 2937 } else if (isprint(c)) { 2938 putc(c, stderr); 2939 } else if (c == '\n') { 2940 fprintf(stderr, "(\\n)"); 2941 } else if (c == '\r') { 2942 fprintf(stderr, "(\\r)"); 2943 } else if (c == 0x1b) { 2944 fprintf(stderr, "(\\e)"); 2945 } else { 2946 fprintf(stderr, "(%02x)", c); 2947 } 2948 } 2949 fprintf(stderr, (strescseq.term[0] == 0x1b) ? "ESC\\\n" : "BEL\n"); 2950 } 2951 2952 void 2953 strreset(void) 2954 { 2955 strescseq = (STREscape){ 2956 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2957 .siz = STR_BUF_SIZ, 2958 }; 2959 } 2960 2961 void 2962 sendbreak(const Arg *arg) 2963 { 2964 if (tcsendbreak(cmdfd, 0)) 2965 perror("Error sending break"); 2966 } 2967 2968 void 2969 tprinter(char *s, size_t len) 2970 { 2971 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2972 perror("Error writing to output file"); 2973 close(iofd); 2974 iofd = -1; 2975 } 2976 } 2977 2978 void 2979 toggleprinter(const Arg *arg) 2980 { 2981 term.mode ^= MODE_PRINT; 2982 } 2983 2984 void 2985 printscreen(const Arg *arg) 2986 { 2987 tdump(); 2988 } 2989 2990 void 2991 printsel(const Arg *arg) 2992 { 2993 tdumpsel(); 2994 } 2995 2996 void 2997 tdumpsel(void) 2998 { 2999 char *ptr; 3000 3001 if ((ptr = getsel())) { 3002 tprinter(ptr, strlen(ptr)); 3003 free(ptr); 3004 } 3005 } 3006 3007 #if !REFLOW_PATCH 3008 void 3009 tdumpline(int n) 3010 { 3011 char buf[UTF_SIZ]; 3012 const Glyph *bp, *end; 3013 3014 bp = &term.line[n][0]; 3015 end = &bp[MIN(tlinelen(n), term.col) - 1]; 3016 if (bp != end || bp->u != ' ') { 3017 for ( ; bp <= end; ++bp) 3018 tprinter(buf, utf8encode(bp->u, buf)); 3019 } 3020 tprinter("\n", 1); 3021 } 3022 #endif // REFLOW_PATCH 3023 3024 void 3025 tdump(void) 3026 { 3027 int i; 3028 3029 for (i = 0; i < term.row; ++i) 3030 tdumpline(i); 3031 } 3032 3033 void 3034 tputtab(int n) 3035 { 3036 uint x = term.c.x; 3037 3038 if (n > 0) { 3039 while (x < term.col && n--) 3040 for (++x; x < term.col && !term.tabs[x]; ++x) 3041 /* nothing */ ; 3042 } else if (n < 0) { 3043 while (x > 0 && n++) 3044 for (--x; x > 0 && !term.tabs[x]; --x) 3045 /* nothing */ ; 3046 } 3047 term.c.x = LIMIT(x, 0, term.col-1); 3048 } 3049 3050 void 3051 tdefutf8(char ascii) 3052 { 3053 if (ascii == 'G') 3054 term.mode |= MODE_UTF8; 3055 else if (ascii == '@') 3056 term.mode &= ~MODE_UTF8; 3057 } 3058 3059 void 3060 tdeftran(char ascii) 3061 { 3062 static char cs[] = "0B"; 3063 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 3064 char *p; 3065 3066 if ((p = strchr(cs, ascii)) == NULL) { 3067 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 3068 } else { 3069 term.trantbl[term.icharset] = vcs[p - cs]; 3070 } 3071 } 3072 3073 void 3074 tdectest(char c) 3075 { 3076 int x, y; 3077 3078 if (c == '8') { /* DEC screen alignment test. */ 3079 for (x = 0; x < term.col; ++x) { 3080 for (y = 0; y < term.row; ++y) 3081 tsetchar('E', &term.c.attr, x, y); 3082 } 3083 } 3084 } 3085 3086 void 3087 tstrsequence(uchar c) 3088 { 3089 #if SIXEL_PATCH 3090 strreset(); 3091 #endif // SIXEL_PATCH 3092 3093 switch (c) { 3094 case 0x90: /* DCS -- Device Control String */ 3095 c = 'P'; 3096 #if SIXEL_PATCH 3097 term.esc |= ESC_DCS; 3098 #endif // SIXEL_PATCH 3099 break; 3100 case 0x9f: /* APC -- Application Program Command */ 3101 c = '_'; 3102 break; 3103 case 0x9e: /* PM -- Privacy Message */ 3104 c = '^'; 3105 break; 3106 case 0x9d: /* OSC -- Operating System Command */ 3107 c = ']'; 3108 break; 3109 } 3110 #if !SIXEL_PATCH 3111 strreset(); 3112 #endif // SIXEL_PATCH 3113 strescseq.type = c; 3114 term.esc |= ESC_STR; 3115 } 3116 3117 void 3118 tcontrolcode(uchar ascii) 3119 { 3120 switch (ascii) { 3121 case '\t': /* HT */ 3122 tputtab(1); 3123 return; 3124 case '\b': /* BS */ 3125 tmoveto(term.c.x-1, term.c.y); 3126 return; 3127 case '\r': /* CR */ 3128 tmoveto(0, term.c.y); 3129 return; 3130 case '\f': /* LF */ 3131 case '\v': /* VT */ 3132 case '\n': /* LF */ 3133 /* go to first col if the mode is set */ 3134 tnewline(IS_SET(MODE_CRLF)); 3135 return; 3136 case '\a': /* BEL */ 3137 if (term.esc & ESC_STR_END) { 3138 /* backwards compatibility to xterm */ 3139 strescseq.term = STR_TERM_BEL; 3140 strhandle(); 3141 } else { 3142 xbell(); 3143 } 3144 break; 3145 case '\033': /* ESC */ 3146 csireset(); 3147 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 3148 term.esc |= ESC_START; 3149 return; 3150 case '\016': /* SO (LS1 -- Locking shift 1) */ 3151 case '\017': /* SI (LS0 -- Locking shift 0) */ 3152 term.charset = 1 - (ascii - '\016'); 3153 return; 3154 case '\032': /* SUB */ 3155 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 3156 /* FALLTHROUGH */ 3157 case '\030': /* CAN */ 3158 csireset(); 3159 break; 3160 case '\005': /* ENQ (IGNORED) */ 3161 case '\000': /* NUL (IGNORED) */ 3162 case '\021': /* XON (IGNORED) */ 3163 case '\023': /* XOFF (IGNORED) */ 3164 case 0177: /* DEL (IGNORED) */ 3165 return; 3166 case 0x80: /* TODO: PAD */ 3167 case 0x81: /* TODO: HOP */ 3168 case 0x82: /* TODO: BPH */ 3169 case 0x83: /* TODO: NBH */ 3170 case 0x84: /* TODO: IND */ 3171 break; 3172 case 0x85: /* NEL -- Next line */ 3173 tnewline(1); /* always go to first col */ 3174 break; 3175 case 0x86: /* TODO: SSA */ 3176 case 0x87: /* TODO: ESA */ 3177 break; 3178 case 0x88: /* HTS -- Horizontal tab stop */ 3179 term.tabs[term.c.x] = 1; 3180 break; 3181 case 0x89: /* TODO: HTJ */ 3182 case 0x8a: /* TODO: VTS */ 3183 case 0x8b: /* TODO: PLD */ 3184 case 0x8c: /* TODO: PLU */ 3185 case 0x8d: /* TODO: RI */ 3186 case 0x8e: /* TODO: SS2 */ 3187 case 0x8f: /* TODO: SS3 */ 3188 case 0x91: /* TODO: PU1 */ 3189 case 0x92: /* TODO: PU2 */ 3190 case 0x93: /* TODO: STS */ 3191 case 0x94: /* TODO: CCH */ 3192 case 0x95: /* TODO: MW */ 3193 case 0x96: /* TODO: SPA */ 3194 case 0x97: /* TODO: EPA */ 3195 case 0x98: /* TODO: SOS */ 3196 case 0x99: /* TODO: SGCI */ 3197 break; 3198 case 0x9a: /* DECID -- Identify Terminal */ 3199 ttywrite(vtiden, strlen(vtiden), 0); 3200 break; 3201 case 0x9b: /* TODO: CSI */ 3202 case 0x9c: /* TODO: ST */ 3203 break; 3204 case 0x90: /* DCS -- Device Control String */ 3205 case 0x9d: /* OSC -- Operating System Command */ 3206 case 0x9e: /* PM -- Privacy Message */ 3207 case 0x9f: /* APC -- Application Program Command */ 3208 tstrsequence(ascii); 3209 return; 3210 } 3211 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 3212 term.esc &= ~(ESC_STR_END|ESC_STR); 3213 } 3214 3215 #if SIXEL_PATCH 3216 void 3217 dcshandle(void) 3218 { 3219 int bgcolor, transparent; 3220 unsigned char r, g, b, a = 255; 3221 3222 switch (csiescseq.mode[0]) { 3223 default: 3224 unknown: 3225 fprintf(stderr, "erresc: unknown csi "); 3226 csidump(); 3227 /* die(""); */ 3228 break; 3229 #if SYNC_PATCH 3230 case '=': 3231 /* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */ 3232 if (csiescseq.buf[2] == 's' && csiescseq.buf[1] == '1') 3233 tsync_begin(); /* BSU */ 3234 else if (csiescseq.buf[2] == 's' && csiescseq.buf[1] == '2') 3235 tsync_end(); /* ESU */ 3236 else 3237 goto unknown; 3238 break; 3239 #endif // SYNC_PATCH 3240 case 'q': /* DECSIXEL */ 3241 transparent = (csiescseq.narg >= 2 && csiescseq.arg[1] == 1); 3242 if (IS_TRUECOL(term.c.attr.bg)) { 3243 r = term.c.attr.bg >> 16 & 255; 3244 g = term.c.attr.bg >> 8 & 255; 3245 b = term.c.attr.bg >> 0 & 255; 3246 } else { 3247 xgetcolor(term.c.attr.bg, &r, &g, &b); 3248 if (term.c.attr.bg == defaultbg) 3249 a = dc.col[defaultbg].pixel >> 24 & 255; 3250 } 3251 bgcolor = a << 24 | r << 16 | g << 8 | b; 3252 if (sixel_parser_init(&sixel_st, transparent, (255 << 24), bgcolor, 1, win.cw, win.ch) != 0) 3253 perror("sixel_parser_init() failed"); 3254 term.mode |= MODE_SIXEL; 3255 break; 3256 } 3257 } 3258 #endif // SIXEL_PATCH 3259 3260 /* 3261 * returns 1 when the sequence is finished and it hasn't to read 3262 * more characters for this sequence, otherwise 0 3263 */ 3264 int 3265 eschandle(uchar ascii) 3266 { 3267 switch (ascii) { 3268 case '[': 3269 term.esc |= ESC_CSI; 3270 return 0; 3271 case '#': 3272 term.esc |= ESC_TEST; 3273 return 0; 3274 case '%': 3275 term.esc |= ESC_UTF8; 3276 return 0; 3277 case 'P': /* DCS -- Device Control String */ 3278 #if SIXEL_PATCH 3279 term.esc |= ESC_DCS; 3280 #endif // SIXEL_PATCH 3281 case '_': /* APC -- Application Program Command */ 3282 case '^': /* PM -- Privacy Message */ 3283 case ']': /* OSC -- Operating System Command */ 3284 case 'k': /* old title set compatibility */ 3285 tstrsequence(ascii); 3286 return 0; 3287 case 'n': /* LS2 -- Locking shift 2 */ 3288 case 'o': /* LS3 -- Locking shift 3 */ 3289 term.charset = 2 + (ascii - 'n'); 3290 break; 3291 case '(': /* GZD4 -- set primary charset G0 */ 3292 case ')': /* G1D4 -- set secondary charset G1 */ 3293 case '*': /* G2D4 -- set tertiary charset G2 */ 3294 case '+': /* G3D4 -- set quaternary charset G3 */ 3295 term.icharset = ascii - '('; 3296 term.esc |= ESC_ALTCHARSET; 3297 return 0; 3298 case 'D': /* IND -- Linefeed */ 3299 if (term.c.y == term.bot) { 3300 #if REFLOW_PATCH 3301 tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); 3302 #elif SCROLLBACK_PATCH 3303 tscrollup(term.top, 1, 1); 3304 #else 3305 tscrollup(term.top, 1); 3306 #endif // SCROLLBACK_PATCH 3307 } else { 3308 tmoveto(term.c.x, term.c.y+1); 3309 } 3310 break; 3311 case 'E': /* NEL -- Next line */ 3312 tnewline(1); /* always go to first col */ 3313 break; 3314 case 'H': /* HTS -- Horizontal tab stop */ 3315 term.tabs[term.c.x] = 1; 3316 break; 3317 case 'M': /* RI -- Reverse index */ 3318 if (term.c.y == term.top) { 3319 tscrolldown(term.top, 1); 3320 } else { 3321 tmoveto(term.c.x, term.c.y-1); 3322 } 3323 break; 3324 case 'Z': /* DECID -- Identify Terminal */ 3325 ttywrite(vtiden, strlen(vtiden), 0); 3326 break; 3327 case 'c': /* RIS -- Reset to initial state */ 3328 treset(); 3329 #if CSI_22_23_PATCH 3330 xfreetitlestack(); 3331 #endif // CSI_22_23_PATCH 3332 resettitle(); 3333 xloadcols(); 3334 xsetmode(0, MODE_HIDE); 3335 #if SCROLLBACK_PATCH && !REFLOW_PATCH 3336 if (!IS_SET(MODE_ALTSCREEN)) { 3337 term.scr = 0; 3338 term.histi = 0; 3339 term.histn = 0; 3340 } 3341 #endif // SCROLLBACK_PATCH 3342 break; 3343 case '=': /* DECPAM -- Application keypad */ 3344 xsetmode(1, MODE_APPKEYPAD); 3345 break; 3346 case '>': /* DECPNM -- Normal keypad */ 3347 xsetmode(0, MODE_APPKEYPAD); 3348 break; 3349 case '7': /* DECSC -- Save Cursor */ 3350 tcursor(CURSOR_SAVE); 3351 break; 3352 case '8': /* DECRC -- Restore Cursor */ 3353 tcursor(CURSOR_LOAD); 3354 break; 3355 case '\\': /* ST -- String Terminator */ 3356 if (term.esc & ESC_STR_END) { 3357 strescseq.term = STR_TERM_ST; 3358 strhandle(); 3359 } 3360 break; 3361 default: 3362 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 3363 (uchar) ascii, isprint(ascii)? ascii:'.'); 3364 break; 3365 } 3366 return 1; 3367 } 3368 3369 void 3370 tputc(Rune u) 3371 { 3372 char c[UTF_SIZ]; 3373 int control; 3374 int width, len; 3375 Glyph *gp; 3376 3377 control = ISCONTROL(u); 3378 if (u < 127 || !IS_SET(MODE_UTF8)) 3379 { 3380 c[0] = u; 3381 width = len = 1; 3382 } else { 3383 len = utf8encode(u, c); 3384 if (!control && (width = wcwidth(u)) == -1) 3385 width = 1; 3386 } 3387 3388 if (IS_SET(MODE_PRINT)) 3389 tprinter(c, len); 3390 3391 /* 3392 * STR sequence must be checked before anything else 3393 * because it uses all following characters until it 3394 * receives a ESC, a SUB, a ST or any other C1 control 3395 * character. 3396 */ 3397 if (term.esc & ESC_STR) { 3398 if (u == '\a' || u == 030 || u == 032 || u == 033 || 3399 ISCONTROLC1(u)) { 3400 #if SIXEL_PATCH 3401 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); 3402 #else 3403 term.esc &= ~(ESC_START|ESC_STR); 3404 #endif // SIXEL_PATCH 3405 term.esc |= ESC_STR_END; 3406 goto check_control_code; 3407 } 3408 3409 #if SIXEL_PATCH 3410 if (term.esc & ESC_DCS) 3411 goto check_control_code; 3412 #endif // SIXEL_PATCH 3413 3414 if (strescseq.len+len >= strescseq.siz) { 3415 /* 3416 * Here is a bug in terminals. If the user never sends 3417 * some code to stop the str or esc command, then st 3418 * will stop responding. But this is better than 3419 * silently failing with unknown characters. At least 3420 * then users will report back. 3421 * 3422 * In the case users ever get fixed, here is the code: 3423 */ 3424 /* 3425 * term.esc = 0; 3426 * strhandle(); 3427 */ 3428 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 3429 return; 3430 strescseq.siz *= 2; 3431 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 3432 } 3433 3434 memmove(&strescseq.buf[strescseq.len], c, len); 3435 strescseq.len += len; 3436 return; 3437 } 3438 3439 check_control_code: 3440 /* 3441 * Actions of control codes must be performed as soon they arrive 3442 * because they can be embedded inside a control sequence, and 3443 * they must not cause conflicts with sequences. 3444 */ 3445 if (control) { 3446 /* in UTF-8 mode ignore handling C1 control characters */ 3447 if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) 3448 return; 3449 tcontrolcode(u); 3450 /* 3451 * control codes are not shown ever 3452 */ 3453 if (!term.esc) 3454 term.lastc = 0; 3455 return; 3456 } else if (term.esc & ESC_START) { 3457 if (term.esc & ESC_CSI) { 3458 csiescseq.buf[csiescseq.len++] = u; 3459 if (BETWEEN(u, 0x40, 0x7E) 3460 || csiescseq.len >= \ 3461 sizeof(csiescseq.buf)-1) { 3462 term.esc = 0; 3463 csiparse(); 3464 csihandle(); 3465 } 3466 return; 3467 #if SIXEL_PATCH 3468 } else if (term.esc & ESC_DCS) { 3469 csiescseq.buf[csiescseq.len++] = u; 3470 if (BETWEEN(u, 0x40, 0x7E) 3471 || csiescseq.len >= \ 3472 sizeof(csiescseq.buf)-1) { 3473 csiparse(); 3474 dcshandle(); 3475 } 3476 return; 3477 #endif // SIXEL_PATCH 3478 } else if (term.esc & ESC_UTF8) { 3479 tdefutf8(u); 3480 } else if (term.esc & ESC_ALTCHARSET) { 3481 tdeftran(u); 3482 } else if (term.esc & ESC_TEST) { 3483 tdectest(u); 3484 } else { 3485 if (!eschandle(u)) 3486 return; 3487 /* sequence already finished */ 3488 } 3489 term.esc = 0; 3490 /* 3491 * All characters which form part of a sequence are not 3492 * printed 3493 */ 3494 return; 3495 } 3496 3497 #if REFLOW_PATCH 3498 /* selected() takes relative coordinates */ 3499 if (selected(term.c.x, term.c.y + term.scr)) 3500 selclear(); 3501 #else 3502 if (selected(term.c.x, term.c.y)) 3503 selclear(); 3504 #endif // REFLOW_PATCH 3505 3506 gp = &term.line[term.c.y][term.c.x]; 3507 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 3508 gp->mode |= ATTR_WRAP; 3509 tnewline(1); 3510 gp = &term.line[term.c.y][term.c.x]; 3511 } 3512 3513 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { 3514 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 3515 gp->mode &= ~ATTR_WIDE; 3516 } 3517 3518 if (term.c.x+width > term.col) { 3519 if (IS_SET(MODE_WRAP)) 3520 tnewline(1); 3521 else 3522 tmoveto(term.col - width, term.c.y); 3523 gp = &term.line[term.c.y][term.c.x]; 3524 } 3525 3526 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 3527 #if OSC133_PATCH 3528 term.c.attr.mode &= ~ATTR_FTCS_PROMPT; 3529 #endif // OSC133_PATCH 3530 term.lastc = u; 3531 3532 if (width == 2) { 3533 gp->mode |= ATTR_WIDE; 3534 if (term.c.x+1 < term.col) { 3535 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { 3536 gp[2].u = ' '; 3537 gp[2].mode &= ~ATTR_WDUMMY; 3538 } 3539 gp[1].u = '\0'; 3540 gp[1].mode = ATTR_WDUMMY; 3541 } 3542 } 3543 if (term.c.x+width < term.col) { 3544 tmoveto(term.c.x+width, term.c.y); 3545 } else { 3546 #if REFLOW_PATCH 3547 term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width; 3548 #endif // REFLOW_PATCH 3549 term.c.state |= CURSOR_WRAPNEXT; 3550 } 3551 } 3552 3553 int 3554 twrite(const char *buf, int buflen, int show_ctrl) 3555 { 3556 int charsize; 3557 Rune u; 3558 int n; 3559 3560 #if SYNC_PATCH 3561 int su0 = su; 3562 twrite_aborted = 0; 3563 #endif // SYNC_PATCH 3564 3565 for (n = 0; n < buflen; n += charsize) { 3566 #if SIXEL_PATCH 3567 if (IS_SET(MODE_SIXEL) && sixel_st.state != PS_ESC) { 3568 charsize = sixel_parser_parse(&sixel_st, (const unsigned char*)buf + n, buflen - n); 3569 continue; 3570 } else if (IS_SET(MODE_UTF8)) 3571 #else 3572 if (IS_SET(MODE_UTF8)) 3573 #endif // SIXEL_PATCH 3574 { 3575 /* process a complete utf8 char */ 3576 charsize = utf8decode(buf + n, &u, buflen - n); 3577 if (charsize == 0) 3578 break; 3579 } else { 3580 u = buf[n] & 0xFF; 3581 charsize = 1; 3582 } 3583 #if SYNC_PATCH 3584 if (su0 && !su) { 3585 twrite_aborted = 1; 3586 break; // ESU - allow rendering before a new BSU 3587 } 3588 #endif // SYNC_PATCH 3589 if (show_ctrl && ISCONTROL(u)) { 3590 if (u & 0x80) { 3591 u &= 0x7f; 3592 tputc('^'); 3593 tputc('['); 3594 } else if (u != '\n' && u != '\r' && u != '\t') { 3595 u ^= 0x40; 3596 tputc('^'); 3597 } 3598 } 3599 tputc(u); 3600 } 3601 return n; 3602 } 3603 3604 #if !REFLOW_PATCH 3605 void 3606 tresize(int col, int row) 3607 { 3608 int i, j; 3609 #if COLUMNS_PATCH 3610 int tmp = col; 3611 int minrow, mincol; 3612 3613 if (!term.maxcol) 3614 term.maxcol = term.col; 3615 col = MAX(col, term.maxcol); 3616 minrow = MIN(row, term.row); 3617 mincol = MIN(col, term.maxcol); 3618 #else 3619 int minrow = MIN(row, term.row); 3620 int mincol = MIN(col, term.col); 3621 #endif // COLUMNS_PATCH 3622 int *bp; 3623 #if SIXEL_PATCH 3624 int x2; 3625 Line line; 3626 ImageList *im, *next; 3627 #endif // SIXEL_PATCH 3628 3629 #if KEYBOARDSELECT_PATCH 3630 if ( row < term.row || col < term.col ) 3631 toggle_winmode(trt_kbdselect(XK_Escape, NULL, 0)); 3632 #endif // KEYBOARDSELECT_PATCH 3633 3634 if (col < 1 || row < 1) { 3635 fprintf(stderr, 3636 "tresize: error resizing to %dx%d\n", col, row); 3637 return; 3638 } 3639 3640 /* scroll both screens independently */ 3641 if (row < term.row) { 3642 tcursor(CURSOR_SAVE); 3643 tsetscroll(0, term.row - 1); 3644 for (i = 0; i < 2; i++) { 3645 if (term.c.y >= row) { 3646 #if SCROLLBACK_PATCH 3647 tscrollup(0, term.c.y - row + 1, !IS_SET(MODE_ALTSCREEN)); 3648 #else 3649 tscrollup(0, term.c.y - row + 1); 3650 #endif // SCROLLBACK_PATCH 3651 } 3652 for (j = row; j < term.row; j++) 3653 free(term.line[j]); 3654 tswapscreen(); 3655 tcursor(CURSOR_LOAD); 3656 } 3657 } 3658 3659 /* resize to new height */ 3660 term.line = xrealloc(term.line, row * sizeof(Line)); 3661 term.alt = xrealloc(term.alt, row * sizeof(Line)); 3662 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 3663 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 3664 3665 #if SCROLLBACK_PATCH 3666 Glyph gc=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0}; 3667 for (i = 0; i < HISTSIZE; i++) { 3668 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 3669 for (j = mincol; j < col; j++) 3670 term.hist[i][j] = gc; 3671 } 3672 #endif // SCROLLBACK_PATCH 3673 3674 /* resize each row to new width, zero-pad if needed */ 3675 for (i = 0; i < minrow; i++) { 3676 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 3677 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 3678 } 3679 3680 /* allocate any new rows */ 3681 for (/* i = minrow */; i < row; i++) { 3682 term.line[i] = xmalloc(col * sizeof(Glyph)); 3683 term.alt[i] = xmalloc(col * sizeof(Glyph)); 3684 } 3685 #if COLUMNS_PATCH 3686 if (col > term.maxcol) 3687 #else 3688 if (col > term.col) 3689 #endif // COLUMNS_PATCH 3690 { 3691 #if COLUMNS_PATCH 3692 bp = term.tabs + term.maxcol; 3693 memset(bp, 0, sizeof(*term.tabs) * (col - term.maxcol)); 3694 #else 3695 bp = term.tabs + term.col; 3696 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 3697 #endif // COLUMNS_PATCH 3698 3699 while (--bp > term.tabs && !*bp) 3700 /* nothing */ ; 3701 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 3702 *bp = 1; 3703 } 3704 3705 /* update terminal size */ 3706 #if COLUMNS_PATCH 3707 term.col = tmp; 3708 term.maxcol = col; 3709 #else 3710 term.col = col; 3711 #endif // COLUMNS_PATCH 3712 term.row = row; 3713 3714 /* reset scrolling region */ 3715 tsetscroll(0, row-1); 3716 /* Clearing both screens (it makes dirty all lines) */ 3717 for (i = 0; i < 2; i++) { 3718 tmoveto(term.c.x, term.c.y); /* make use of the LIMIT in tmoveto */ 3719 tcursor(CURSOR_SAVE); 3720 if (mincol < col && 0 < minrow) { 3721 tclearregion(mincol, 0, col - 1, minrow - 1); 3722 } 3723 if (0 < col && minrow < row) { 3724 tclearregion(0, minrow, col - 1, row - 1); 3725 } 3726 tswapscreen(); 3727 tcursor(CURSOR_LOAD); 3728 } 3729 3730 #if SIXEL_PATCH 3731 /* expand images into new text cells */ 3732 for (i = 0; i < 2; i++) { 3733 for (im = term.images; im; im = next) { 3734 next = im->next; 3735 #if SCROLLBACK_PATCH 3736 if (IS_SET(MODE_ALTSCREEN)) { 3737 if (im->y < 0 || im->y >= term.row) { 3738 delete_image(im); 3739 continue; 3740 } 3741 line = term.line[im->y]; 3742 } else { 3743 if (im->y - term.scr < -HISTSIZE || im->y - term.scr >= term.row) { 3744 delete_image(im); 3745 continue; 3746 } 3747 line = TLINE(im->y); 3748 } 3749 #else 3750 if (im->y < 0 || im->y >= term.row) { 3751 delete_image(im); 3752 continue; 3753 } 3754 line = term.line[im->y]; 3755 #endif // SCROLLBACK_PATCH 3756 x2 = MIN(im->x + im->cols, col) - 1; 3757 if (mincol < col && x2 >= mincol && im->x < col) 3758 tsetsixelattr(line, MAX(im->x, mincol), x2); 3759 } 3760 tswapscreen(); 3761 } 3762 #endif // SIXEL_PATCH 3763 } 3764 #endif // REFLOW_PATCH 3765 3766 void 3767 resettitle(void) 3768 { 3769 #if CSI_22_23_PATCH 3770 xsettitle(NULL, 0); 3771 #else 3772 xsettitle(NULL); 3773 #endif // CSI_22_23_PATCH 3774 } 3775 3776 void 3777 drawregion(int x1, int y1, int x2, int y2) 3778 { 3779 int y; 3780 3781 for (y = y1; y < y2; y++) { 3782 if (!term.dirty[y]) 3783 continue; 3784 3785 term.dirty[y] = 0; 3786 #if SCROLLBACK_PATCH || REFLOW_PATCH 3787 xdrawline(TLINE(y), x1, y, x2); 3788 #else 3789 xdrawline(term.line[y], x1, y, x2); 3790 #endif // SCROLLBACK_PATCH 3791 } 3792 } 3793 3794 #include "patch/st_include.c" 3795 3796 void 3797 draw(void) 3798 { 3799 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 3800 3801 if (!xstartdraw()) 3802 return; 3803 3804 /* adjust cursor position */ 3805 LIMIT(term.ocx, 0, term.col-1); 3806 LIMIT(term.ocy, 0, term.row-1); 3807 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) 3808 term.ocx--; 3809 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) 3810 cx--; 3811 3812 drawregion(0, 0, term.col, term.row); 3813 3814 #if KEYBOARDSELECT_PATCH && REFLOW_PATCH 3815 if (!kbds_drawcursor()) 3816 #elif REFLOW_PATCH || SCROLLBACK_PATCH 3817 if (term.scr == 0) 3818 #endif // SCROLLBACK_PATCH | REFLOW_PATCH | KEYBOARDSELECT_PATCH 3819 #if LIGATURES_PATCH 3820 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 3821 term.ocx, term.ocy, term.line[term.ocy][term.ocx], 3822 term.line[term.ocy], term.col); 3823 #else 3824 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 3825 term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 3826 #endif // LIGATURES_PATCH 3827 term.ocx = cx; 3828 term.ocy = term.c.y; 3829 xfinishdraw(); 3830 if (ocx != term.ocx || ocy != term.ocy) 3831 xximspot(term.ocx, term.ocy); 3832 } 3833 3834 void 3835 redraw(void) 3836 { 3837 tfulldirt(); 3838 draw(); 3839 }