1 /* $OpenBSD: screen.c,v 1.88 2024/11/15 09:01:16 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 21 #include <stdlib.h> 22 #include <string.h> 23 #include <unistd.h> 24 #include <vis.h> 25 26 #include "tmux.h" 27 28 /* Selected area in screen. */ 29 struct screen_sel { 30 int hidden; 31 int rectangle; 32 int modekeys; 33 34 u_int sx; 35 u_int sy; 36 37 u_int ex; 38 u_int ey; 39 40 struct grid_cell cell; 41 }; 42 43 /* Entry on title stack. */ 44 struct screen_title_entry { 45 char *text; 46 47 TAILQ_ENTRY(screen_title_entry) entry; 48 }; 49 TAILQ_HEAD(screen_titles, screen_title_entry); 50 51 static void screen_resize_y(struct screen *, u_int, int, u_int *); 52 static void screen_reflow(struct screen *, u_int, u_int *, u_int *, int); 53 54 /* Free titles stack. */ 55 static void 56 screen_free_titles(struct screen *s) 57 { 58 struct screen_title_entry *title_entry; 59 60 if (s->titles == NULL) 61 return; 62 63 while ((title_entry = TAILQ_FIRST(s->titles)) != NULL) { 64 TAILQ_REMOVE(s->titles, title_entry, entry); 65 free(title_entry->text); 66 free(title_entry); 67 } 68 69 free(s->titles); 70 s->titles = NULL; 71 } 72 73 /* Create a new screen. */ 74 void 75 screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) 76 { 77 s->grid = grid_create(sx, sy, hlimit); 78 s->saved_grid = NULL; 79 80 s->title = xstrdup(""); 81 s->titles = NULL; 82 s->path = NULL; 83 84 s->cstyle = SCREEN_CURSOR_DEFAULT; 85 s->default_cstyle = SCREEN_CURSOR_DEFAULT; 86 s->mode = MODE_CURSOR; 87 s->default_mode = 0; 88 s->ccolour = -1; 89 s->default_ccolour = -1; 90 s->tabs = NULL; 91 s->sel = NULL; 92 93 s->write_list = NULL; 94 s->hyperlinks = NULL; 95 96 screen_reinit(s); 97 } 98 99 /* Reinitialise screen. */ 100 void 101 screen_reinit(struct screen *s) 102 { 103 s->cx = 0; 104 s->cy = 0; 105 106 s->rupper = 0; 107 s->rlower = screen_size_y(s) - 1; 108 109 s->mode = MODE_CURSOR|MODE_WRAP|(s->mode & MODE_CRLF); 110 111 if (options_get_number(global_options, "extended-keys") == 2) 112 s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED; 113 114 if (SCREEN_IS_ALTERNATE(s)) 115 screen_alternate_off(s, NULL, 0); 116 s->saved_cx = UINT_MAX; 117 s->saved_cy = UINT_MAX; 118 119 screen_reset_tabs(s); 120 121 grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8); 122 123 screen_clear_selection(s); 124 screen_free_titles(s); 125 screen_reset_hyperlinks(s); 126 } 127 128 /* Reset hyperlinks of a screen. */ 129 void 130 screen_reset_hyperlinks(struct screen *s) 131 { 132 if (s->hyperlinks == NULL) 133 s->hyperlinks = hyperlinks_init(); 134 else 135 hyperlinks_reset(s->hyperlinks); 136 } 137 138 /* Destroy a screen. */ 139 void 140 screen_free(struct screen *s) 141 { 142 free(s->sel); 143 free(s->tabs); 144 free(s->path); 145 free(s->title); 146 147 if (s->write_list != NULL) 148 screen_write_free_list(s); 149 150 if (SCREEN_IS_ALTERNATE(s)) 151 grid_destroy(s->saved_grid); 152 grid_destroy(s->grid); 153 154 if (s->hyperlinks != NULL) 155 hyperlinks_free(s->hyperlinks); 156 screen_free_titles(s); 157 } 158 159 /* Reset tabs to default, eight spaces apart. */ 160 void 161 screen_reset_tabs(struct screen *s) 162 { 163 u_int i; 164 165 free(s->tabs); 166 167 if ((s->tabs = bit_alloc(screen_size_x(s))) == NULL) 168 fatal("bit_alloc failed"); 169 for (i = 8; i < screen_size_x(s); i += 8) 170 bit_set(s->tabs, i); 171 } 172 173 /* Set default cursor style and colour from options. */ 174 void 175 screen_set_default_cursor(struct screen *s, struct options *oo) 176 { 177 int c; 178 179 c = options_get_number(oo, "cursor-colour"); 180 s->default_ccolour = c; 181 182 c = options_get_number(oo, "cursor-style"); 183 s->default_mode = 0; 184 screen_set_cursor_style(c, &s->default_cstyle, &s->default_mode); 185 } 186 187 /* Set screen cursor style and mode. */ 188 void 189 screen_set_cursor_style(u_int style, enum screen_cursor_style *cstyle, 190 int *mode) 191 { 192 switch (style) { 193 case 0: 194 *cstyle = SCREEN_CURSOR_DEFAULT; 195 break; 196 case 1: 197 *cstyle = SCREEN_CURSOR_BLOCK; 198 *mode |= MODE_CURSOR_BLINKING; 199 break; 200 case 2: 201 *cstyle = SCREEN_CURSOR_BLOCK; 202 *mode &= ~MODE_CURSOR_BLINKING; 203 break; 204 case 3: 205 *cstyle = SCREEN_CURSOR_UNDERLINE; 206 *mode |= MODE_CURSOR_BLINKING; 207 break; 208 case 4: 209 *cstyle = SCREEN_CURSOR_UNDERLINE; 210 *mode &= ~MODE_CURSOR_BLINKING; 211 break; 212 case 5: 213 *cstyle = SCREEN_CURSOR_BAR; 214 *mode |= MODE_CURSOR_BLINKING; 215 break; 216 case 6: 217 *cstyle = SCREEN_CURSOR_BAR; 218 *mode &= ~MODE_CURSOR_BLINKING; 219 break; 220 } 221 } 222 223 /* Set screen cursor colour. */ 224 void 225 screen_set_cursor_colour(struct screen *s, int colour) 226 { 227 s->ccolour = colour; 228 } 229 230 /* Set screen title. */ 231 int 232 screen_set_title(struct screen *s, const char *title) 233 { 234 if (!utf8_isvalid(title)) 235 return (0); 236 free(s->title); 237 s->title = xstrdup(title); 238 return (1); 239 } 240 241 /* Set screen path. */ 242 void 243 screen_set_path(struct screen *s, const char *path) 244 { 245 free(s->path); 246 utf8_stravis(&s->path, path, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); 247 } 248 249 /* Push the current title onto the stack. */ 250 void 251 screen_push_title(struct screen *s) 252 { 253 struct screen_title_entry *title_entry; 254 255 if (s->titles == NULL) { 256 s->titles = xmalloc(sizeof *s->titles); 257 TAILQ_INIT(s->titles); 258 } 259 title_entry = xmalloc(sizeof *title_entry); 260 title_entry->text = xstrdup(s->title); 261 TAILQ_INSERT_HEAD(s->titles, title_entry, entry); 262 } 263 264 /* 265 * Pop a title from the stack and set it as the screen title. If the stack is 266 * empty, do nothing. 267 */ 268 void 269 screen_pop_title(struct screen *s) 270 { 271 struct screen_title_entry *title_entry; 272 273 if (s->titles == NULL) 274 return; 275 276 title_entry = TAILQ_FIRST(s->titles); 277 if (title_entry != NULL) { 278 screen_set_title(s, title_entry->text); 279 280 TAILQ_REMOVE(s->titles, title_entry, entry); 281 free(title_entry->text); 282 free(title_entry); 283 } 284 } 285 286 /* Resize screen with options. */ 287 void 288 screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, 289 int eat_empty, int cursor) 290 { 291 u_int cx = s->cx, cy = s->grid->hsize + s->cy; 292 293 if (s->write_list != NULL) 294 screen_write_free_list(s); 295 296 log_debug("%s: new size %ux%u, now %ux%u (cursor %u,%u = %u,%u)", 297 __func__, sx, sy, screen_size_x(s), screen_size_y(s), s->cx, s->cy, 298 cx, cy); 299 300 if (sx < 1) 301 sx = 1; 302 if (sy < 1) 303 sy = 1; 304 305 if (sx != screen_size_x(s)) { 306 s->grid->sx = sx; 307 screen_reset_tabs(s); 308 } else 309 reflow = 0; 310 311 if (sy != screen_size_y(s)) 312 screen_resize_y(s, sy, eat_empty, &cy); 313 314 if (reflow) 315 screen_reflow(s, sx, &cx, &cy, cursor); 316 317 if (cy >= s->grid->hsize) { 318 s->cx = cx; 319 s->cy = cy - s->grid->hsize; 320 } else { 321 s->cx = 0; 322 s->cy = 0; 323 } 324 325 log_debug("%s: cursor finished at %u,%u = %u,%u", __func__, s->cx, 326 s->cy, cx, cy); 327 328 if (s->write_list != NULL) 329 screen_write_make_list(s); 330 } 331 332 /* Resize screen. */ 333 void 334 screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) 335 { 336 screen_resize_cursor(s, sx, sy, reflow, 1, 1); 337 } 338 339 static void 340 screen_resize_y(struct screen *s, u_int sy, int eat_empty, u_int *cy) 341 { 342 struct grid *gd = s->grid; 343 u_int needed, available, oldy, i; 344 345 if (sy == 0) 346 fatalx("zero size"); 347 oldy = screen_size_y(s); 348 349 /* 350 * When resizing: 351 * 352 * If the height is decreasing, delete lines from the bottom until 353 * hitting the cursor, then push lines from the top into the history. 354 * 355 * When increasing, pull as many lines as possible from scrolled 356 * history (not explicitly cleared from view) to the top, then fill the 357 * remaining with blanks at the bottom. 358 */ 359 360 /* Size decreasing. */ 361 if (sy < oldy) { 362 needed = oldy - sy; 363 364 /* Delete as many lines as possible from the bottom. */ 365 if (eat_empty) { 366 available = oldy - 1 - s->cy; 367 if (available > 0) { 368 if (available > needed) 369 available = needed; 370 grid_view_delete_lines(gd, oldy - available, 371 available, 8); 372 } 373 needed -= available; 374 } 375 376 /* 377 * Now just increase the history size, if possible, to take 378 * over the lines which are left. If history is off, delete 379 * lines from the top. 380 */ 381 available = s->cy; 382 if (gd->flags & GRID_HISTORY) { 383 gd->hscrolled += needed; 384 gd->hsize += needed; 385 } else if (needed > 0 && available > 0) { 386 if (available > needed) 387 available = needed; 388 grid_view_delete_lines(gd, 0, available, 8); 389 (*cy) -= available; 390 } 391 } 392 393 /* Resize line array. */ 394 grid_adjust_lines(gd, gd->hsize + sy); 395 396 /* Size increasing. */ 397 if (sy > oldy) { 398 needed = sy - oldy; 399 400 /* 401 * Try to pull as much as possible out of scrolled history, if 402 * it is enabled. 403 */ 404 available = gd->hscrolled; 405 if (gd->flags & GRID_HISTORY && available > 0) { 406 if (available > needed) 407 available = needed; 408 gd->hscrolled -= available; 409 gd->hsize -= available; 410 } else 411 available = 0; 412 needed -= available; 413 414 /* Then fill the rest in with blanks. */ 415 for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++) 416 grid_empty_line(gd, i, 8); 417 } 418 419 /* Set the new size, and reset the scroll region. */ 420 gd->sy = sy; 421 s->rupper = 0; 422 s->rlower = screen_size_y(s) - 1; 423 } 424 425 /* Set selection. */ 426 void 427 screen_set_selection(struct screen *s, u_int sx, u_int sy, 428 u_int ex, u_int ey, u_int rectangle, int modekeys, struct grid_cell *gc) 429 { 430 if (s->sel == NULL) 431 s->sel = xcalloc(1, sizeof *s->sel); 432 433 memcpy(&s->sel->cell, gc, sizeof s->sel->cell); 434 s->sel->hidden = 0; 435 s->sel->rectangle = rectangle; 436 s->sel->modekeys = modekeys; 437 438 s->sel->sx = sx; 439 s->sel->sy = sy; 440 s->sel->ex = ex; 441 s->sel->ey = ey; 442 } 443 444 /* Clear selection. */ 445 void 446 screen_clear_selection(struct screen *s) 447 { 448 free(s->sel); 449 s->sel = NULL; 450 } 451 452 /* Hide selection. */ 453 void 454 screen_hide_selection(struct screen *s) 455 { 456 if (s->sel != NULL) 457 s->sel->hidden = 1; 458 } 459 460 /* Check if cell in selection. */ 461 int 462 screen_check_selection(struct screen *s, u_int px, u_int py) 463 { 464 struct screen_sel *sel = s->sel; 465 u_int xx; 466 467 if (sel == NULL || sel->hidden) 468 return (0); 469 470 if (sel->rectangle) { 471 if (sel->sy < sel->ey) { 472 /* start line < end line -- downward selection. */ 473 if (py < sel->sy || py > sel->ey) 474 return (0); 475 } else if (sel->sy > sel->ey) { 476 /* start line > end line -- upward selection. */ 477 if (py > sel->sy || py < sel->ey) 478 return (0); 479 } else { 480 /* starting line == ending line. */ 481 if (py != sel->sy) 482 return (0); 483 } 484 485 /* 486 * Need to include the selection start row, but not the cursor 487 * row, which means the selection changes depending on which 488 * one is on the left. 489 */ 490 if (sel->ex < sel->sx) { 491 /* Cursor (ex) is on the left. */ 492 if (px < sel->ex) 493 return (0); 494 495 if (px > sel->sx) 496 return (0); 497 } else { 498 /* Selection start (sx) is on the left. */ 499 if (px < sel->sx) 500 return (0); 501 502 if (px > sel->ex) 503 return (0); 504 } 505 } else { 506 /* 507 * Like emacs, keep the top-left-most character, and drop the 508 * bottom-right-most, regardless of copy direction. 509 */ 510 if (sel->sy < sel->ey) { 511 /* starting line < ending line -- downward selection. */ 512 if (py < sel->sy || py > sel->ey) 513 return (0); 514 515 if (py == sel->sy && px < sel->sx) 516 return (0); 517 518 if (sel->modekeys == MODEKEY_EMACS) 519 xx = (sel->ex == 0 ? 0 : sel->ex - 1); 520 else 521 xx = sel->ex; 522 if (py == sel->ey && px > xx) 523 return (0); 524 } else if (sel->sy > sel->ey) { 525 /* starting line > ending line -- upward selection. */ 526 if (py > sel->sy || py < sel->ey) 527 return (0); 528 529 if (py == sel->ey && px < sel->ex) 530 return (0); 531 532 if (sel->modekeys == MODEKEY_EMACS) 533 xx = sel->sx - 1; 534 else 535 xx = sel->sx; 536 if (py == sel->sy && (sel->sx == 0 || px > xx)) 537 return (0); 538 } else { 539 /* starting line == ending line. */ 540 if (py != sel->sy) 541 return (0); 542 543 if (sel->ex < sel->sx) { 544 /* cursor (ex) is on the left */ 545 if (sel->modekeys == MODEKEY_EMACS) 546 xx = sel->sx - 1; 547 else 548 xx = sel->sx; 549 if (px > xx || px < sel->ex) 550 return (0); 551 } else { 552 /* selection start (sx) is on the left */ 553 if (sel->modekeys == MODEKEY_EMACS) 554 xx = (sel->ex == 0 ? 0 : sel->ex - 1); 555 else 556 xx = sel->ex; 557 if (px < sel->sx || px > xx) 558 return (0); 559 } 560 } 561 } 562 563 return (1); 564 } 565 566 /* Get selected grid cell. */ 567 void 568 screen_select_cell(struct screen *s, struct grid_cell *dst, 569 const struct grid_cell *src) 570 { 571 if (s->sel == NULL || s->sel->hidden) 572 return; 573 574 memcpy(dst, &s->sel->cell, sizeof *dst); 575 576 utf8_copy(&dst->data, &src->data); 577 dst->attr = dst->attr & ~GRID_ATTR_CHARSET; 578 dst->attr |= src->attr & GRID_ATTR_CHARSET; 579 dst->flags = src->flags; 580 } 581 582 /* Reflow wrapped lines. */ 583 static void 584 screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy, int cursor) 585 { 586 u_int wx, wy; 587 588 if (cursor) { 589 grid_wrap_position(s->grid, *cx, *cy, &wx, &wy); 590 log_debug("%s: cursor %u,%u is %u,%u", __func__, *cx, *cy, wx, 591 wy); 592 } 593 594 grid_reflow(s->grid, new_x); 595 596 if (cursor) { 597 grid_unwrap_position(s->grid, cx, cy, wx, wy); 598 log_debug("%s: new cursor is %u,%u", __func__, *cx, *cy); 599 } 600 else { 601 *cx = 0; 602 *cy = s->grid->hsize; 603 } 604 } 605 606 /* 607 * Enter alternative screen mode. A copy of the visible screen is saved and the 608 * history is not updated. 609 */ 610 void 611 screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) 612 { 613 u_int sx, sy; 614 615 if (SCREEN_IS_ALTERNATE(s)) 616 return; 617 sx = screen_size_x(s); 618 sy = screen_size_y(s); 619 620 s->saved_grid = grid_create(sx, sy, 0); 621 grid_duplicate_lines(s->saved_grid, 0, s->grid, screen_hsize(s), sy); 622 if (cursor) { 623 s->saved_cx = s->cx; 624 s->saved_cy = s->cy; 625 } 626 memcpy(&s->saved_cell, gc, sizeof s->saved_cell); 627 628 grid_view_clear(s->grid, 0, 0, sx, sy, 8); 629 630 s->saved_flags = s->grid->flags; 631 s->grid->flags &= ~GRID_HISTORY; 632 } 633 634 /* Exit alternate screen mode and restore the copied grid. */ 635 void 636 screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) 637 { 638 u_int sx = screen_size_x(s), sy = screen_size_y(s); 639 640 /* 641 * If the current size is different, temporarily resize to the old size 642 * before copying back. 643 */ 644 if (SCREEN_IS_ALTERNATE(s)) 645 screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 0); 646 647 /* 648 * Restore the cursor position and cell. This happens even if not 649 * currently in the alternate screen. 650 */ 651 if (cursor && s->saved_cx != UINT_MAX && s->saved_cy != UINT_MAX) { 652 s->cx = s->saved_cx; 653 s->cy = s->saved_cy; 654 if (gc != NULL) 655 memcpy(gc, &s->saved_cell, sizeof *gc); 656 } 657 658 /* If not in the alternate screen, do nothing more. */ 659 if (!SCREEN_IS_ALTERNATE(s)) { 660 if (s->cx > screen_size_x(s) - 1) 661 s->cx = screen_size_x(s) - 1; 662 if (s->cy > screen_size_y(s) - 1) 663 s->cy = screen_size_y(s) - 1; 664 return; 665 } 666 667 /* Restore the saved grid. */ 668 grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0, 669 s->saved_grid->sy); 670 671 /* 672 * Turn history back on (so resize can use it) and then resize back to 673 * the current size. 674 */ 675 if (s->saved_flags & GRID_HISTORY) 676 s->grid->flags |= GRID_HISTORY; 677 screen_resize(s, sx, sy, 1); 678 679 grid_destroy(s->saved_grid); 680 s->saved_grid = NULL; 681 682 if (s->cx > screen_size_x(s) - 1) 683 s->cx = screen_size_x(s) - 1; 684 if (s->cy > screen_size_y(s) - 1) 685 s->cy = screen_size_y(s) - 1; 686 } 687 688 /* Get mode as a string. */ 689 const char * 690 screen_mode_to_string(int mode) 691 { 692 static char tmp[1024]; 693 694 if (mode == 0) 695 return ("NONE"); 696 if (mode == ALL_MODES) 697 return ("ALL"); 698 699 *tmp = '\0'; 700 if (mode & MODE_CURSOR) 701 strlcat(tmp, "CURSOR,", sizeof tmp); 702 if (mode & MODE_INSERT) 703 strlcat(tmp, "INSERT,", sizeof tmp); 704 if (mode & MODE_KCURSOR) 705 strlcat(tmp, "KCURSOR,", sizeof tmp); 706 if (mode & MODE_KKEYPAD) 707 strlcat(tmp, "KKEYPAD,", sizeof tmp); 708 if (mode & MODE_WRAP) 709 strlcat(tmp, "WRAP,", sizeof tmp); 710 if (mode & MODE_MOUSE_STANDARD) 711 strlcat(tmp, "MOUSE_STANDARD,", sizeof tmp); 712 if (mode & MODE_MOUSE_BUTTON) 713 strlcat(tmp, "MOUSE_BUTTON,", sizeof tmp); 714 if (mode & MODE_CURSOR_BLINKING) 715 strlcat(tmp, "CURSOR_BLINKING,", sizeof tmp); 716 if (mode & MODE_CURSOR_VERY_VISIBLE) 717 strlcat(tmp, "CURSOR_VERY_VISIBLE,", sizeof tmp); 718 if (mode & MODE_MOUSE_UTF8) 719 strlcat(tmp, "MOUSE_UTF8,", sizeof tmp); 720 if (mode & MODE_MOUSE_SGR) 721 strlcat(tmp, "MOUSE_SGR,", sizeof tmp); 722 if (mode & MODE_BRACKETPASTE) 723 strlcat(tmp, "BRACKETPASTE,", sizeof tmp); 724 if (mode & MODE_FOCUSON) 725 strlcat(tmp, "FOCUSON,", sizeof tmp); 726 if (mode & MODE_MOUSE_ALL) 727 strlcat(tmp, "MOUSE_ALL,", sizeof tmp); 728 if (mode & MODE_ORIGIN) 729 strlcat(tmp, "ORIGIN,", sizeof tmp); 730 if (mode & MODE_CRLF) 731 strlcat(tmp, "CRLF,", sizeof tmp); 732 if (mode & MODE_KEYS_EXTENDED) 733 strlcat(tmp, "KEYS_EXTENDED,", sizeof tmp); 734 if (mode & MODE_KEYS_EXTENDED_2) 735 strlcat(tmp, "KEYS_EXTENDED_2,", sizeof tmp); 736 tmp[strlen(tmp) - 1] = '\0'; 737 return (tmp); 738 } 739