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