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