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