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