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