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