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