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