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