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