1 /* $OpenBSD: screen-redraw.c,v 1.46 2017/05/01 12:20:55 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, int, u_int); 37 static void screen_redraw_draw_panes(struct client *, u_int); 38 static void screen_redraw_draw_status(struct client *, u_int); 39 static void screen_redraw_draw_number(struct client *, struct window_pane *, 40 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; 381 int status, pane_status, spos; 382 383 /* Suspended clients should not be updated. */ 384 if (c->flags & CLIENT_SUSPENDED) 385 return; 386 387 /* Get status line, er, status. */ 388 spos = options_get_number(oo, "status-position"); 389 if (c->message_string != NULL || c->prompt_string != NULL) 390 status = 1; 391 else 392 status = options_get_number(oo, "status"); 393 top = 0; 394 if (status && spos == 0) 395 top = 1; 396 if (!status) 397 draw_status = 0; 398 399 /* Draw the elements. */ 400 if (draw_borders) { 401 pane_status = options_get_number(wo, "pane-border-status"); 402 screen_redraw_draw_borders(c, status, pane_status, top); 403 if (pane_status != CELL_STATUS_OFF) 404 screen_redraw_draw_pane_status(c, pane_status); 405 } 406 if (draw_panes) 407 screen_redraw_draw_panes(c, top); 408 if (draw_status) 409 screen_redraw_draw_status(c, top); 410 tty_reset(tty); 411 } 412 413 /* Draw a single pane. */ 414 void 415 screen_redraw_pane(struct client *c, struct window_pane *wp) 416 { 417 u_int i, yoff; 418 419 if (!window_pane_visible(wp)) 420 return; 421 422 yoff = wp->yoff; 423 if (status_at_line(c) == 0) 424 yoff++; 425 426 log_debug("%s: redraw pane %%%u (at %u,%u)", c->name, wp->id, 427 wp->xoff, yoff); 428 429 for (i = 0; i < wp->sy; i++) 430 tty_draw_pane(&c->tty, wp, i, wp->xoff, yoff); 431 tty_reset(&c->tty); 432 } 433 434 /* Draw the borders. */ 435 static void 436 screen_redraw_draw_borders(struct client *c, int status, int pane_status, 437 u_int top) 438 { 439 struct session *s = c->session; 440 struct window *w = s->curw->window; 441 struct options *oo = w->options; 442 struct tty *tty = &c->tty; 443 struct window_pane *wp; 444 struct grid_cell m_active_gc, active_gc, m_other_gc, other_gc; 445 struct grid_cell msg_gc; 446 u_int i, j, type, msgx = 0, msgy = 0; 447 int active, small, flags; 448 char msg[256]; 449 const char *tmp; 450 size_t msglen = 0; 451 452 small = (tty->sy - status + top > w->sy) || (tty->sx > w->sx); 453 if (small) { 454 flags = w->flags & (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT); 455 if (flags == (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT)) 456 tmp = "force-width, force-height"; 457 else if (flags == WINDOW_FORCEWIDTH) 458 tmp = "force-width"; 459 else if (flags == WINDOW_FORCEHEIGHT) 460 tmp = "force-height"; 461 else 462 tmp = "a smaller client"; 463 xsnprintf(msg, sizeof msg, "(size %ux%u from %s)", 464 w->sx, w->sy, tmp); 465 msglen = strlen(msg); 466 467 if (tty->sy - 1 - status + top > w->sy && tty->sx >= msglen) { 468 msgx = tty->sx - msglen; 469 msgy = tty->sy - 1 - status + top; 470 } else if (tty->sx - w->sx > msglen) { 471 msgx = tty->sx - msglen; 472 msgy = tty->sy - 1 - status + top; 473 } else 474 small = 0; 475 } 476 477 style_apply(&other_gc, oo, "pane-border-style"); 478 style_apply(&active_gc, oo, "pane-active-border-style"); 479 active_gc.attr = other_gc.attr = GRID_ATTR_CHARSET; 480 481 memcpy(&m_other_gc, &other_gc, sizeof m_other_gc); 482 m_other_gc.attr ^= GRID_ATTR_REVERSE; 483 memcpy(&m_active_gc, &active_gc, sizeof m_active_gc); 484 m_active_gc.attr ^= GRID_ATTR_REVERSE; 485 486 for (j = 0; j < tty->sy - status; j++) { 487 for (i = 0; i < tty->sx; i++) { 488 type = screen_redraw_check_cell(c, i, j, pane_status, 489 &wp); 490 if (type == CELL_INSIDE) 491 continue; 492 if (type == CELL_OUTSIDE && small && 493 i > msgx && j == msgy) 494 continue; 495 active = screen_redraw_check_is(i, j, type, pane_status, 496 w, w->active, wp); 497 if (server_is_marked(s, s->curw, marked_pane.wp) && 498 screen_redraw_check_is(i, j, type, pane_status, w, 499 marked_pane.wp, wp)) { 500 if (active) 501 tty_attributes(tty, &m_active_gc, NULL); 502 else 503 tty_attributes(tty, &m_other_gc, NULL); 504 } else if (active) 505 tty_attributes(tty, &active_gc, NULL); 506 else 507 tty_attributes(tty, &other_gc, NULL); 508 tty_cursor(tty, i, top + j); 509 tty_putc(tty, CELL_BORDERS[type]); 510 } 511 } 512 513 if (small) { 514 memcpy(&msg_gc, &grid_default_cell, sizeof msg_gc); 515 tty_attributes(tty, &msg_gc, NULL); 516 tty_cursor(tty, msgx, msgy); 517 tty_puts(tty, msg); 518 } 519 } 520 521 /* Draw the panes. */ 522 static void 523 screen_redraw_draw_panes(struct client *c, u_int top) 524 { 525 struct window *w = c->session->curw->window; 526 struct tty *tty = &c->tty; 527 struct window_pane *wp; 528 u_int i; 529 530 TAILQ_FOREACH(wp, &w->panes, entry) { 531 if (!window_pane_visible(wp)) 532 continue; 533 for (i = 0; i < wp->sy; i++) 534 tty_draw_pane(tty, wp, i, wp->xoff, top + wp->yoff); 535 if (c->flags & CLIENT_IDENTIFY) 536 screen_redraw_draw_number(c, wp, top); 537 } 538 } 539 540 /* Draw the status line. */ 541 static void 542 screen_redraw_draw_status(struct client *c, u_int top) 543 { 544 struct tty *tty = &c->tty; 545 546 if (top) 547 tty_draw_line(tty, NULL, &c->status, 0, 0, 0); 548 else 549 tty_draw_line(tty, NULL, &c->status, 0, 0, tty->sy - 1); 550 } 551 552 /* Draw number on a pane. */ 553 static void 554 screen_redraw_draw_number(struct client *c, struct window_pane *wp, u_int top) 555 { 556 struct tty *tty = &c->tty; 557 struct session *s = c->session; 558 struct options *oo = s->options; 559 struct window *w = wp->window; 560 struct grid_cell gc; 561 u_int idx, px, py, i, j, xoff, yoff; 562 int colour, active_colour; 563 char buf[16], *ptr; 564 size_t len; 565 566 if (window_pane_index(wp, &idx) != 0) 567 fatalx("index not found"); 568 len = xsnprintf(buf, sizeof buf, "%u", idx); 569 570 if (wp->sx < len) 571 return; 572 colour = options_get_number(oo, "display-panes-colour"); 573 active_colour = options_get_number(oo, "display-panes-active-colour"); 574 575 px = wp->sx / 2; py = wp->sy / 2; 576 xoff = wp->xoff; yoff = wp->yoff; 577 578 if (top) 579 yoff++; 580 581 if (wp->sx < len * 6 || wp->sy < 5) { 582 tty_cursor(tty, xoff + px - len / 2, yoff + py); 583 goto draw_text; 584 } 585 586 px -= len * 3; 587 py -= 2; 588 589 memcpy(&gc, &grid_default_cell, sizeof gc); 590 if (w->active == wp) 591 gc.bg = active_colour; 592 else 593 gc.bg = colour; 594 gc.flags |= GRID_FLAG_NOPALETTE; 595 596 tty_attributes(tty, &gc, wp); 597 for (ptr = buf; *ptr != '\0'; ptr++) { 598 if (*ptr < '0' || *ptr > '9') 599 continue; 600 idx = *ptr - '0'; 601 602 for (j = 0; j < 5; j++) { 603 for (i = px; i < px + 5; i++) { 604 tty_cursor(tty, xoff + i, yoff + py + j); 605 if (window_clock_table[idx][j][i - px]) 606 tty_putc(tty, ' '); 607 } 608 } 609 px += 6; 610 } 611 612 len = xsnprintf(buf, sizeof buf, "%ux%u", wp->sx, wp->sy); 613 if (wp->sx < len || wp->sy < 6) 614 return; 615 tty_cursor(tty, xoff + wp->sx - len, yoff); 616 617 draw_text: 618 memcpy(&gc, &grid_default_cell, sizeof gc); 619 if (w->active == wp) 620 gc.fg = active_colour; 621 else 622 gc.fg = colour; 623 gc.flags |= GRID_FLAG_NOPALETTE; 624 625 tty_attributes(tty, &gc, wp); 626 tty_puts(tty, buf); 627 628 tty_cursor(tty, 0, 0); 629 } 630