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