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