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