1 /* $OpenBSD: screen-redraw.c,v 1.65 2019/07/24 21:16: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 24 #include "tmux.h" 25 26 static void screen_redraw_draw_borders(struct screen_redraw_ctx *); 27 static void screen_redraw_draw_panes(struct screen_redraw_ctx *); 28 static void screen_redraw_draw_status(struct screen_redraw_ctx *); 29 static void screen_redraw_draw_pane(struct screen_redraw_ctx *, 30 struct window_pane *); 31 32 #define CELL_INSIDE 0 33 #define CELL_LEFTRIGHT 1 34 #define CELL_TOPBOTTOM 2 35 #define CELL_TOPLEFT 3 36 #define CELL_TOPRIGHT 4 37 #define CELL_BOTTOMLEFT 5 38 #define CELL_BOTTOMRIGHT 6 39 #define CELL_TOPJOIN 7 40 #define CELL_BOTTOMJOIN 8 41 #define CELL_LEFTJOIN 9 42 #define CELL_RIGHTJOIN 10 43 #define CELL_JOIN 11 44 #define CELL_OUTSIDE 12 45 46 #define CELL_BORDERS " xqlkmjwvtun~" 47 48 /* Check if cell is on the border of a particular pane. */ 49 static int 50 screen_redraw_cell_border1(struct window_pane *wp, u_int px, u_int py) 51 { 52 /* Inside pane. */ 53 if (px >= wp->xoff && px < wp->xoff + wp->sx && 54 py >= wp->yoff && py < wp->yoff + wp->sy) 55 return (0); 56 57 /* Left/right borders. */ 58 if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= wp->yoff + wp->sy) { 59 if (wp->xoff != 0 && px == wp->xoff - 1) 60 return (1); 61 if (px == wp->xoff + wp->sx) 62 return (2); 63 } 64 65 /* Top/bottom borders. */ 66 if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= wp->xoff + wp->sx) { 67 if (wp->yoff != 0 && py == wp->yoff - 1) 68 return (3); 69 if (py == wp->yoff + wp->sy) 70 return (4); 71 } 72 73 /* Outside pane. */ 74 return (-1); 75 } 76 77 /* Check if a cell is on the pane border. */ 78 static int 79 screen_redraw_cell_border(struct client *c, u_int px, u_int py) 80 { 81 struct window *w = c->session->curw->window; 82 struct window_pane *wp; 83 int retval; 84 85 /* Check all the panes. */ 86 TAILQ_FOREACH(wp, &w->panes, entry) { 87 if (!window_pane_visible(wp)) 88 continue; 89 if ((retval = screen_redraw_cell_border1(wp, px, py)) != -1) 90 return (!!retval); 91 } 92 93 return (0); 94 } 95 96 /* Check if cell inside a pane. */ 97 static int 98 screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, 99 struct window_pane **wpp) 100 { 101 struct window *w = c->session->curw->window; 102 struct window_pane *wp; 103 int borders; 104 u_int right, line; 105 106 *wpp = NULL; 107 108 if (px > w->sx || py > w->sy) 109 return (CELL_OUTSIDE); 110 111 if (pane_status != PANE_STATUS_OFF) { 112 TAILQ_FOREACH(wp, &w->panes, entry) { 113 if (!window_pane_visible(wp)) 114 continue; 115 116 if (pane_status == PANE_STATUS_TOP) 117 line = wp->yoff - 1; 118 else 119 line = wp->yoff + wp->sy; 120 right = wp->xoff + 2 + wp->status_size - 1; 121 122 if (py == line && px >= wp->xoff + 2 && px <= right) 123 return (CELL_INSIDE); 124 } 125 } 126 127 TAILQ_FOREACH(wp, &w->panes, entry) { 128 if (!window_pane_visible(wp)) 129 continue; 130 *wpp = wp; 131 132 /* If outside the pane and its border, skip it. */ 133 if ((wp->xoff != 0 && px < wp->xoff - 1) || 134 px > wp->xoff + wp->sx || 135 (wp->yoff != 0 && py < wp->yoff - 1) || 136 py > wp->yoff + wp->sy) 137 continue; 138 139 /* If definitely inside, return so. */ 140 if (!screen_redraw_cell_border(c, px, py)) 141 return (CELL_INSIDE); 142 143 /* 144 * Construct a bitmask of whether the cells to the left (bit 145 * 4), right, top, and bottom (bit 1) of this cell are borders. 146 */ 147 borders = 0; 148 if (px == 0 || screen_redraw_cell_border(c, px - 1, py)) 149 borders |= 8; 150 if (px <= w->sx && screen_redraw_cell_border(c, px + 1, py)) 151 borders |= 4; 152 if (pane_status == PANE_STATUS_TOP) { 153 if (py != 0 && screen_redraw_cell_border(c, px, py - 1)) 154 borders |= 2; 155 } else { 156 if (py == 0 || screen_redraw_cell_border(c, px, py - 1)) 157 borders |= 2; 158 } 159 if (py <= w->sy && screen_redraw_cell_border(c, px, py + 1)) 160 borders |= 1; 161 162 /* 163 * Figure out what kind of border this cell is. Only one bit 164 * set doesn't make sense (can't have a border cell with no 165 * others connected). 166 */ 167 switch (borders) { 168 case 15: /* 1111, left right top bottom */ 169 return (CELL_JOIN); 170 case 14: /* 1110, left right top */ 171 return (CELL_BOTTOMJOIN); 172 case 13: /* 1101, left right bottom */ 173 return (CELL_TOPJOIN); 174 case 12: /* 1100, left right */ 175 return (CELL_TOPBOTTOM); 176 case 11: /* 1011, left top bottom */ 177 return (CELL_RIGHTJOIN); 178 case 10: /* 1010, left top */ 179 return (CELL_BOTTOMRIGHT); 180 case 9: /* 1001, left bottom */ 181 return (CELL_TOPRIGHT); 182 case 7: /* 0111, right top bottom */ 183 return (CELL_LEFTJOIN); 184 case 6: /* 0110, right top */ 185 return (CELL_BOTTOMLEFT); 186 case 5: /* 0101, right bottom */ 187 return (CELL_TOPLEFT); 188 case 3: /* 0011, top bottom */ 189 return (CELL_LEFTRIGHT); 190 } 191 } 192 193 return (CELL_OUTSIDE); 194 } 195 196 /* Check if the border of a particular pane. */ 197 static int 198 screen_redraw_check_is(u_int px, u_int py, int type, int pane_status, 199 struct window *w, struct window_pane *wantwp, struct window_pane *wp) 200 { 201 int border; 202 203 /* Is this off the active pane border? */ 204 border = screen_redraw_cell_border1(wantwp, px, py); 205 if (border == 0 || border == -1) 206 return (0); 207 if (pane_status == PANE_STATUS_TOP && border == 4) 208 return (0); 209 if (pane_status == PANE_STATUS_BOTTOM && border == 3) 210 return (0); 211 212 /* If there are more than two panes, that's enough. */ 213 if (window_count_panes(w) != 2) 214 return (1); 215 216 /* Else if the cell is not a border cell, forget it. */ 217 if (wp == NULL || (type == CELL_OUTSIDE || type == CELL_INSIDE)) 218 return (1); 219 220 /* With status lines mark the entire line. */ 221 if (pane_status != PANE_STATUS_OFF) 222 return (1); 223 224 /* Check if the pane covers the whole width. */ 225 if (wp->xoff == 0 && wp->sx == w->sx) { 226 /* This can either be the top pane or the bottom pane. */ 227 if (wp->yoff == 0) { /* top pane */ 228 if (wp == wantwp) 229 return (px <= wp->sx / 2); 230 return (px > wp->sx / 2); 231 } 232 return (0); 233 } 234 235 /* Check if the pane covers the whole height. */ 236 if (wp->yoff == 0 && wp->sy == w->sy) { 237 /* This can either be the left pane or the right pane. */ 238 if (wp->xoff == 0) { /* left pane */ 239 if (wp == wantwp) 240 return (py <= wp->sy / 2); 241 return (py > wp->sy / 2); 242 } 243 return (0); 244 } 245 246 return (1); 247 } 248 249 /* Update pane status. */ 250 static int 251 screen_redraw_make_pane_status(struct client *c, struct window *w, 252 struct window_pane *wp) 253 { 254 struct grid_cell gc; 255 const char *fmt; 256 struct format_tree *ft; 257 char *expanded; 258 u_int width, i; 259 struct screen_write_ctx ctx; 260 struct screen old; 261 262 if (wp == w->active) 263 style_apply(&gc, w->options, "pane-active-border-style"); 264 else 265 style_apply(&gc, w->options, "pane-border-style"); 266 267 fmt = options_get_string(w->options, "pane-border-format"); 268 269 ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); 270 format_defaults(ft, c, NULL, NULL, wp); 271 272 expanded = format_expand_time(ft, fmt); 273 if (wp->sx < 4) 274 wp->status_size = width = 0; 275 else 276 wp->status_size = width = wp->sx - 4; 277 278 memcpy(&old, &wp->status_screen, sizeof old); 279 screen_init(&wp->status_screen, width, 1, 0); 280 wp->status_screen.mode = 0; 281 282 screen_write_start(&ctx, NULL, &wp->status_screen); 283 284 gc.attr |= GRID_ATTR_CHARSET; 285 for (i = 0; i < width; i++) 286 screen_write_putc(&ctx, &gc, 'q'); 287 gc.attr &= ~GRID_ATTR_CHARSET; 288 289 screen_write_cursormove(&ctx, 0, 0, 0); 290 format_draw(&ctx, &gc, width, expanded, NULL); 291 screen_write_stop(&ctx); 292 293 free(expanded); 294 format_free(ft); 295 296 if (grid_compare(wp->status_screen.grid, old.grid) == 0) { 297 screen_free(&old); 298 return (0); 299 } 300 screen_free(&old); 301 return (1); 302 } 303 304 /* Draw pane status. */ 305 static void 306 screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) 307 { 308 struct client *c = ctx->c; 309 struct window *w = c->session->curw->window; 310 struct tty *tty = &c->tty; 311 struct window_pane *wp; 312 struct screen *s; 313 u_int i, x, width, xoff, yoff, size; 314 315 log_debug("%s: %s @%u", __func__, c->name, w->id); 316 317 TAILQ_FOREACH(wp, &w->panes, entry) { 318 if (!window_pane_visible(wp)) 319 continue; 320 s = &wp->status_screen; 321 322 size = wp->status_size; 323 if (ctx->pane_status == PANE_STATUS_TOP) 324 yoff = wp->yoff - 1; 325 else 326 yoff = wp->yoff + wp->sy; 327 xoff = wp->xoff + 2; 328 329 if (xoff + size <= ctx->ox || 330 xoff >= ctx->ox + ctx->sx || 331 yoff < ctx->oy || 332 yoff >= ctx->oy + ctx->sy) 333 continue; 334 335 if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) { 336 /* All visible. */ 337 i = 0; 338 x = xoff - ctx->ox; 339 width = size; 340 } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) { 341 /* Both left and right not visible. */ 342 i = ctx->ox; 343 x = 0; 344 width = ctx->sx; 345 } else if (xoff < ctx->ox) { 346 /* Left not visible. */ 347 i = ctx->ox - xoff; 348 x = 0; 349 width = size - i; 350 } else { 351 /* Right not visible. */ 352 i = 0; 353 x = xoff - ctx->ox; 354 width = size - x; 355 } 356 357 if (ctx->statustop) 358 yoff += ctx->statuslines; 359 tty_draw_line(tty, NULL, s, i, 0, width, x, yoff - ctx->oy); 360 } 361 tty_cursor(tty, 0, 0); 362 } 363 364 /* Update status line and change flags if unchanged. */ 365 static int 366 screen_redraw_update(struct client *c, int flags) 367 { 368 struct window *w = c->session->curw->window; 369 struct window_pane *wp; 370 struct options *wo = w->options; 371 int redraw; 372 373 if (c->message_string != NULL) 374 redraw = status_message_redraw(c); 375 else if (c->prompt_string != NULL) 376 redraw = status_prompt_redraw(c); 377 else 378 redraw = status_redraw(c); 379 if (!redraw && (~flags & CLIENT_REDRAWSTATUSALWAYS)) 380 flags &= ~CLIENT_REDRAWSTATUS; 381 382 if (c->overlay_draw != NULL) 383 flags |= CLIENT_REDRAWOVERLAY; 384 385 if (options_get_number(wo, "pane-border-status") != PANE_STATUS_OFF) { 386 redraw = 0; 387 TAILQ_FOREACH(wp, &w->panes, entry) { 388 if (screen_redraw_make_pane_status(c, w, wp)) 389 redraw = 1; 390 } 391 if (redraw) 392 flags |= CLIENT_REDRAWBORDERS; 393 } 394 return (flags); 395 } 396 397 /* Set up redraw context. */ 398 static void 399 screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx) 400 { 401 struct session *s = c->session; 402 struct options *oo = s->options; 403 struct window *w = s->curw->window; 404 struct options *wo = w->options; 405 u_int lines; 406 407 memset(ctx, 0, sizeof *ctx); 408 ctx->c = c; 409 410 lines = status_line_size(c); 411 if (c->message_string != NULL || c->prompt_string != NULL) 412 lines = (lines == 0) ? 1 : lines; 413 if (lines != 0 && options_get_number(oo, "status-position") == 0) 414 ctx->statustop = 1; 415 ctx->statuslines = lines; 416 417 ctx->pane_status = options_get_number(wo, "pane-border-status"); 418 419 tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy); 420 421 log_debug("%s: %s @%u ox=%u oy=%u sx=%u sy=%u %u/%d", __func__, c->name, 422 w->id, ctx->ox, ctx->oy, ctx->sx, ctx->sy, ctx->statuslines, 423 ctx->statustop); 424 } 425 426 /* Redraw entire screen. */ 427 void 428 screen_redraw_screen(struct client *c) 429 { 430 struct screen_redraw_ctx ctx; 431 int flags; 432 433 if (c->flags & CLIENT_SUSPENDED) 434 return; 435 436 flags = screen_redraw_update(c, c->flags); 437 screen_redraw_set_context(c, &ctx); 438 439 if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) { 440 if (ctx.pane_status != PANE_STATUS_OFF) 441 screen_redraw_draw_pane_status(&ctx); 442 screen_redraw_draw_borders(&ctx); 443 } 444 if (flags & CLIENT_REDRAWWINDOW) 445 screen_redraw_draw_panes(&ctx); 446 if (ctx.statuslines != 0 && 447 (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) 448 screen_redraw_draw_status(&ctx); 449 if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) 450 c->overlay_draw(c, &ctx); 451 tty_reset(&c->tty); 452 } 453 454 /* Redraw a single pane. */ 455 void 456 screen_redraw_pane(struct client *c, struct window_pane *wp) 457 { 458 struct screen_redraw_ctx ctx; 459 460 if (c->overlay_draw != NULL || !window_pane_visible(wp)) 461 return; 462 463 screen_redraw_set_context(c, &ctx); 464 465 screen_redraw_draw_pane(&ctx, wp); 466 tty_reset(&c->tty); 467 } 468 469 /* Draw a border cell. */ 470 static void 471 screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j, 472 struct grid_cell *m_active_gc, struct grid_cell *active_gc, 473 struct grid_cell *m_other_gc, struct grid_cell *other_gc) 474 { 475 struct client *c = ctx->c; 476 struct session *s = c->session; 477 struct window *w = s->curw->window; 478 struct tty *tty = &c->tty; 479 struct window_pane *wp; 480 struct window_pane *active = w->active; 481 struct window_pane *marked = marked_pane.wp; 482 u_int type, x = ctx->ox + i, y = ctx->oy + j; 483 int flag, pane_status = ctx->pane_status; 484 485 type = screen_redraw_check_cell(c, x, y, pane_status, &wp); 486 if (type == CELL_INSIDE) 487 return; 488 flag = screen_redraw_check_is(x, y, type, pane_status, w, active, wp); 489 490 if (server_is_marked(s, s->curw, marked_pane.wp) && 491 screen_redraw_check_is(x, y, type, pane_status, w, marked, wp)) { 492 if (flag) 493 tty_attributes(tty, m_active_gc, NULL); 494 else 495 tty_attributes(tty, m_other_gc, NULL); 496 } else if (flag) 497 tty_attributes(tty, active_gc, NULL); 498 else 499 tty_attributes(tty, other_gc, NULL); 500 if (ctx->statustop) 501 tty_cursor(tty, i, ctx->statuslines + j); 502 else 503 tty_cursor(tty, i, j); 504 tty_putc(tty, CELL_BORDERS[type]); 505 } 506 507 /* Draw the borders. */ 508 static void 509 screen_redraw_draw_borders(struct screen_redraw_ctx *ctx) 510 { 511 struct client *c = ctx->c; 512 struct session *s = c->session; 513 struct window *w = s->curw->window; 514 struct tty *tty = &c->tty; 515 struct options *oo = w->options; 516 struct grid_cell m_active_gc, active_gc, m_other_gc, other_gc; 517 u_int i, j; 518 519 log_debug("%s: %s @%u", __func__, c->name, w->id); 520 521 style_apply(&other_gc, oo, "pane-border-style"); 522 style_apply(&active_gc, oo, "pane-active-border-style"); 523 active_gc.attr = other_gc.attr = GRID_ATTR_CHARSET; 524 525 memcpy(&m_other_gc, &other_gc, sizeof m_other_gc); 526 m_other_gc.attr ^= GRID_ATTR_REVERSE; 527 memcpy(&m_active_gc, &active_gc, sizeof m_active_gc); 528 m_active_gc.attr ^= GRID_ATTR_REVERSE; 529 530 for (j = 0; j < tty->sy - ctx->statuslines; j++) { 531 for (i = 0; i < tty->sx; i++) { 532 screen_redraw_draw_borders_cell(ctx, i, j, 533 &m_active_gc, &active_gc, &m_other_gc, &other_gc); 534 } 535 } 536 } 537 538 /* Draw the panes. */ 539 static void 540 screen_redraw_draw_panes(struct screen_redraw_ctx *ctx) 541 { 542 struct client *c = ctx->c; 543 struct window *w = c->session->curw->window; 544 struct window_pane *wp; 545 546 log_debug("%s: %s @%u", __func__, c->name, w->id); 547 548 TAILQ_FOREACH(wp, &w->panes, entry) { 549 if (window_pane_visible(wp)) 550 screen_redraw_draw_pane(ctx, wp); 551 } 552 } 553 554 /* Draw the status line. */ 555 static void 556 screen_redraw_draw_status(struct screen_redraw_ctx *ctx) 557 { 558 struct client *c = ctx->c; 559 struct window *w = c->session->curw->window; 560 struct tty *tty = &c->tty; 561 struct screen *s = c->status.active; 562 u_int i, y; 563 564 log_debug("%s: %s @%u", __func__, c->name, w->id); 565 566 if (ctx->statustop) 567 y = 0; 568 else 569 y = c->tty.sy - ctx->statuslines; 570 for (i = 0; i < ctx->statuslines; i++) 571 tty_draw_line(tty, NULL, s, 0, i, UINT_MAX, 0, y + i); 572 } 573 574 /* Draw one pane. */ 575 static void 576 screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) 577 { 578 struct client *c = ctx->c; 579 struct window *w = c->session->curw->window; 580 struct tty *tty = &c->tty; 581 struct screen *s; 582 u_int i, j, top, x, y, width; 583 584 log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); 585 586 if (wp->xoff + wp->sx <= ctx->ox || wp->xoff >= ctx->ox + ctx->sx) 587 return; 588 if (ctx->statustop) 589 top = ctx->statuslines; 590 else 591 top = 0; 592 593 s = wp->screen; 594 for (j = 0; j < wp->sy; j++) { 595 if (wp->yoff + j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) 596 continue; 597 y = top + wp->yoff + j - ctx->oy; 598 599 if (wp->xoff >= ctx->ox && 600 wp->xoff + wp->sx <= ctx->ox + ctx->sx) { 601 /* All visible. */ 602 i = 0; 603 x = wp->xoff - ctx->ox; 604 width = wp->sx; 605 } else if (wp->xoff < ctx->ox && 606 wp->xoff + wp->sx > ctx->ox + ctx->sx) { 607 /* Both left and right not visible. */ 608 i = ctx->ox; 609 x = 0; 610 width = ctx->sx; 611 } else if (wp->xoff < ctx->ox) { 612 /* Left not visible. */ 613 i = ctx->ox - wp->xoff; 614 x = 0; 615 width = wp->sx - i; 616 } else { 617 /* Right not visible. */ 618 i = 0; 619 x = wp->xoff - ctx->ox; 620 width = ctx->sx - x; 621 } 622 log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", 623 __func__, c->name, wp->id, i, j, x, y, width); 624 625 tty_draw_line(tty, wp, s, i, j, width, x, y); 626 } 627 } 628