/* $OpenBSD$ */ /* * Copyright (c) 2026 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "tmux.h" /* * Draw the visible area of a window to a client. * * A scene is built for the client and cached (in struct client). When the * offset or size of the visible part of the window changes, the scene is * invalidated. It is also invalidated when the generation number is increased; * this is done at various points, such as when a pane is moved or resized. The * scene only includes the part of the client used for the window: panes, pane * status lines, borders, scrollbars, and any area outside the window. The * client status line and overlay are not included. * * A scene is made from spans. A span is a horizontal run of cells on one * visible line that can be drawn in the same way. Each span has a type, for * example: pane content or pane border cells or pane status line. A span also * includes enough additional data to draw it, but does not include items such * as the style and content - those are determined when it is drawn. * * Scenes are built in two stages: * * 1) redraw_build_cells fills a temporary grid of struct redraw_build_cell * objects, one per visible cell. It marks pane contents, scrollbars, * borders, pane status lines and any cells outside the window. Border cells * may belong to multiple panes, so they may be marked multiple times, with * each pane adding its own state. * * 2) redraw_make_scene takes the grid of struct redraw_build_cell objects and * converts them into spans by joining adjacent cells that have the same * type and data. These spans together make up the scene (struct * redraw_scene). * * Once generated, a scene is redrawn by looping over some or all of the spans * (in redraw_draw), working out the style and content, and writing to the * client terminal. Until it is invalidated, the scene may be redrawn multiple * times without being rebuilt. */ /* Type of span in the scene. */ enum redraw_span_type { REDRAW_SPAN_PANE, /* inside a pane */ REDRAW_SPAN_OUTSIDE, /* outside the window */ REDRAW_SPAN_EMPTY, /* inside the window but nothing visible */ REDRAW_SPAN_STATUS, /* pane status line */ REDRAW_SPAN_BORDER, /* pane border */ REDRAW_SPAN_SCROLLBAR, /* pane scrollbar */ }; #define REDRAW_SPAN_TYPES 6 /* Border connections to adjacent cells. */ #define REDRAW_BORDER_L 0x1 #define REDRAW_BORDER_R 0x2 #define REDRAW_BORDER_U 0x4 #define REDRAW_BORDER_D 0x8 /* Span flags. */ #define REDRAW_BORDER_IS_ARROW 0x1 #define REDRAW_SCROLLBAR_LEFT 0x2 #define REDRAW_SCROLLBAR_RIGHT 0x4 /* Draw operations. */ #define REDRAW_PANE 0x1 #define REDRAW_OUTSIDE 0x2 #define REDRAW_EMPTY 0x4 #define REDRAW_PANE_BORDER 0x8 #define REDRAW_PANE_STATUS 0x10 #define REDRAW_PANE_SCROLLBAR 0x20 #define REDRAW_STATUS 0x40 #define REDRAW_OVERLAY 0x80 /* Draw everything. */ #define REDRAW_ALL 0x7fffffff #define REDRAW_IS_ALL(flags) ((flags) == REDRAW_ALL) /* UTF-8 isolate characters. */ #define REDRAW_START_ISOLATE "\342\201\246" #define REDRAW_END_ISOLATE "\342\201\251" /* Data for a span. */ struct redraw_span_data { enum redraw_span_type type; union { struct { /* Pane this span belongs to. */ struct window_pane *wp; /* Position of span inside the pane. */ u_int px; u_int py; } p; /* pane */ struct { /* Adjacent panes on each side. */ struct window_pane *top_wp; struct window_pane *bottom_wp; struct window_pane *left_wp; struct window_pane *right_wp; /* * The pane this span belongs if that is known when the * scene is built. This is used for the half coloured * active pane indicator. */ struct window_pane *style_wp; /* Cell type for this span. */ int cell_type; int cell_mask; /* Line styles for this span. */ enum pane_lines top_lines; enum pane_lines bottom_lines; enum pane_lines left_lines; enum pane_lines right_lines; /* Flags for this span. */ int flags; } b; /* pane border */ struct { /* The pane and the offset into the status line. */ struct window_pane *wp; u_int offset; int cell_type; } st; /* pane status line */ struct { /* Pane this span belongs to. */ struct window_pane *wp; /* Line within the scrollbar. */ u_int y; /* Full height of scrollbar. */ u_int height; /* Flags for this span. */ int flags; } sb; /* pane scrollbar */ }; }; /* A span of cells of the same type inside a line. */ struct redraw_span { u_int x; u_int width; struct redraw_span_data data; TAILQ_ENTRY(redraw_span) entry; }; TAILQ_HEAD(redraw_spans, redraw_span); /* A visible line on the client. */ struct redraw_line { struct redraw_spans spans[REDRAW_SPAN_TYPES]; }; /* A scene representing all the spans on the client. */ struct redraw_scene { struct client *c; struct window *w; struct redraw_line *lines; uint64_t generation; u_int sx; u_int sy; u_int ox; u_int oy; }; /* Cell for building the scene. */ struct redraw_build_cell { struct redraw_span_data data; }; static struct redraw_build_cell *redraw_cells; static size_t redraw_ncells; /* Context for building the scene. */ struct redraw_build_ctx { struct client *c; struct window *w; u_int ox; u_int oy; u_int sx; u_int sy; int sb; int sbp; int ind; struct redraw_build_cell *cells; }; /* Context for redrawing. */ struct redraw_draw_ctx { struct redraw_scene *scene; struct window_pane *active; struct window_pane *marked; u_int status_lines; enum pane_lines pane_lines; struct grid_cell default_gc; int flags; #define REDRAW_ISOLATES 0x1 #define REDRAW_DEFAULT_SET 0x2 #define REDRAW_STATUS_TOP 0x4 }; /* Make redraw flags into a string. */ static const char * redraw_flags_to_string(int flags) { static char s[128]; *s = '\0'; if (flags & REDRAW_STATUS) strlcat(s, "status ", sizeof s); if (flags & REDRAW_PANE) strlcat(s, "pane ", sizeof s); if (flags & REDRAW_PANE_BORDER) strlcat(s, "border ", sizeof s); if (flags & REDRAW_PANE_STATUS) strlcat(s, "pane-status ", sizeof s); if (flags & REDRAW_PANE_SCROLLBAR) strlcat(s, "scrollbar ", sizeof s); if (flags & REDRAW_OVERLAY) strlcat(s, "overlay ", sizeof s); if (REDRAW_IS_ALL(flags)) strlcat(s, "all ", sizeof s); if (*s != '\0') s[strlen(s) - 1] = '\0'; return (s); } /* Get window offset and expand size to cover any part outside the window. */ static void redraw_get_window_offset(struct client *c, u_int *ox, u_int *oy, u_int *sx, u_int *sy) { u_int tty_sx, tty_sy; tty_window_offset(&c->tty, ox, oy, sx, sy); tty_sx = c->tty.sx; tty_sy = c->tty.sy - status_line_size(c); if (*sx < tty_sx) *sx = tty_sx; if (*sy < tty_sy) *sy = tty_sy; } /* Initialize the context for building scene. */ static void redraw_set_context(struct client *c, struct redraw_build_ctx *bctx) { struct session *s = c->session; struct window *w = s->curw->window; struct options *oo = w->options; memset(bctx, 0, sizeof *bctx); bctx->c = c; bctx->w = w; redraw_get_window_offset(c, &bctx->ox, &bctx->oy, &bctx->sx, &bctx->sy); bctx->sb = options_get_number(oo, "pane-scrollbars"); bctx->sbp = options_get_number(oo, "pane-scrollbars-position"); bctx->ind = options_get_number(oo, "pane-border-indicators"); } /* Return a cell. */ static struct redraw_build_cell * redraw_get_build_cell(struct redraw_build_ctx *bctx, u_int x, u_int y) { return (&bctx->cells[(y * bctx->sx) + x]); } /* Reset cell to either empty or outside the window. */ static void redraw_reset_cell(struct redraw_build_ctx *bctx, u_int x, u_int y) { struct redraw_build_cell *bc = redraw_get_build_cell(bctx, x, y); struct window *w = bctx->w; memset(bc, 0, sizeof *bc); if (bctx->ox + x <= w->sx && bctx->oy + y <= w->sy) bc->data.type = REDRAW_SPAN_EMPTY; else bc->data.type = REDRAW_SPAN_OUTSIDE; } /* Convert window position to scene position. Return 0 if outside the scene. */ static int redraw_window_to_scene(struct redraw_build_ctx *bctx, int wx, int wy, u_int *x, u_int *y) { int sx, sy; if (wx < 0 || wy < 0) return (0); if ((u_int)wx > bctx->w->sx || (u_int)wy > bctx->w->sy) return (0); if (wx < (int)bctx->ox || wy < (int)bctx->oy) return (0); sx = wx - (int)bctx->ox; sy = wy - (int)bctx->oy; if ((u_int)sx >= bctx->sx || (u_int)sy >= bctx->sy) return (0); *x = sx; *y = sy; return (1); } /* * Convert pane position to scene position. Return 0 if outside the scene. A * floating pane is clipped to the window edge. */ static int redraw_pane_to_scene(struct redraw_build_ctx *bctx, struct window_pane *wp, int px, int py, u_int *x, u_int *y) { int wx = wp->xoff + px, wy = wp->yoff + py; int left, right, top, bottom; if (window_pane_is_floating(wp)) { left = wp->xoff - 1; right = wp->xoff + wp->sx; top = wp->yoff - 1; bottom = wp->yoff + wp->sy; if (left < 0 && wx <= 0) return (0); if (right > (int)bctx->w->sx && wx >= (int)bctx->w->sx) return (0); if (top < 0 && wy <= 0) return (0); if (bottom > (int)bctx->w->sy && wy >= (int)bctx->w->sy) return (0); } return (redraw_window_to_scene(bctx, wx, wy, x, y)); } /* Convert redraw border mask to a border cell type. */ static int redraw_get_cell_type(int mask) { switch (mask) { case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_U|REDRAW_BORDER_D: return (CELL_LRUD); case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_U: return (CELL_LRU); case REDRAW_BORDER_L|REDRAW_BORDER_R|REDRAW_BORDER_D: return (CELL_LRD); case REDRAW_BORDER_L|REDRAW_BORDER_R: case REDRAW_BORDER_L: case REDRAW_BORDER_R: return (CELL_LR); case REDRAW_BORDER_L|REDRAW_BORDER_U|REDRAW_BORDER_D: return (CELL_ULD); case REDRAW_BORDER_L|REDRAW_BORDER_U: return (CELL_LU); case REDRAW_BORDER_L|REDRAW_BORDER_D: return (CELL_LD); case REDRAW_BORDER_R|REDRAW_BORDER_U|REDRAW_BORDER_D: return (CELL_URD); case REDRAW_BORDER_R|REDRAW_BORDER_U: return (CELL_RU); case REDRAW_BORDER_R|REDRAW_BORDER_D: return (CELL_RD); case REDRAW_BORDER_U|REDRAW_BORDER_D: case REDRAW_BORDER_U: case REDRAW_BORDER_D: return (CELL_UD); } return (CELL_NONE); } /* Return if there are two panes for the border colour indicator. */ static int redraw_check_two_pane_colours(struct window *w, enum layout_type *type) { struct window_pane *wp; u_int count = 0; TAILQ_FOREACH(wp, &w->panes, entry) { if (window_pane_is_floating(wp) || wp->layout_cell == NULL) continue; count++; if (count > 2 || wp->layout_cell->parent == NULL) return (0); *type = wp->layout_cell->parent->type; } return (count == 2); } /* Mark pane inside data. */ static void redraw_mark_pane_inside(struct redraw_build_ctx *bctx, struct window_pane *wp) { struct redraw_build_cell *bc; u_int px, py, x, y; for (py = 0; py < wp->sy; py++) { for (px = 0; px < wp->sx; px++) { if (!redraw_pane_to_scene(bctx, wp, px, py, &x, &y)) continue; bc = redraw_get_build_cell(bctx, x, y); memset(bc, 0, sizeof *bc); bc->data.type = REDRAW_SPAN_PANE; bc->data.p.wp = wp; bc->data.p.px = px; bc->data.p.py = py; } } } /* Mark scrollbar data. */ static void redraw_mark_pane_scrollbar(struct redraw_build_ctx *bctx, struct window_pane *wp, int sb_w, int sb_left) { struct redraw_build_cell *bc; u_int x, y; int wx, wy, sx, ex; u_int sy; if (sb_w == 0) return; if (sb_left) { sx = wp->xoff - sb_w; ex = wp->xoff - 1; } else { sx = wp->xoff + (int)wp->sx; ex = sx + sb_w - 1; } for (sy = 0; sy < wp->sy; sy++) { wy = wp->yoff + (int)sy; for (wx = sx; wx <= ex; wx++) { if (!redraw_window_to_scene(bctx, wx, wy, &x, &y)) continue; bc = redraw_get_build_cell(bctx, x, y); memset(bc, 0, sizeof *bc); bc->data.type = REDRAW_SPAN_SCROLLBAR; bc->data.sb.wp = wp; bc->data.sb.y = sy; bc->data.sb.height = wp->sy; if (sb_left) bc->data.sb.flags |= REDRAW_SCROLLBAR_LEFT; else bc->data.sb.flags |= REDRAW_SCROLLBAR_RIGHT; } } } /* * Return if span data belongs to pane, that is: is the cell adjacent to this * pane? */ static int redraw_data_has_pane(struct redraw_span_data *data, struct window_pane *wp) { if (data->b.top_wp == wp) return (1); if (data->b.bottom_wp == wp) return (1); if (data->b.left_wp == wp) return (1); if (data->b.right_wp == wp) return (1); return (0); } /* * Mark one border cell. If a non-border cell is marked as a border, replace * it. If it is already a border and this is not a floating pane, merge the * border mask and pane ownership. */ static void redraw_mark_border_cell(struct redraw_build_ctx *bctx, int wx, int wy, struct window_pane *wp, int top_owner, int bottom_owner, int mask, enum pane_lines pane_lines, int floating) { struct redraw_build_cell *bc; u_int x, y; int reset = 0; if (!redraw_window_to_scene(bctx, wx, wy, &x, &y)) return; bc = redraw_get_build_cell(bctx, x, y); /* * If this is a tiled pane, only empty and border cells may be marked. * Border cells are merged and empty cells are updated. * * Floating panes may mark any existing cell type. All cells are reset * except borders that already belong to this pane, they need to be * merged. */ if (!floating) { if (bc->data.type == REDRAW_SPAN_EMPTY) reset = 1; else if (bc->data.type != REDRAW_SPAN_BORDER) return; } else { if (bc->data.type != REDRAW_SPAN_BORDER || !redraw_data_has_pane(&bc->data, wp)) reset = 1; } if (reset) { memset(bc, 0, sizeof *bc); bc->data.type = REDRAW_SPAN_BORDER; } if (top_owner) { bc->data.b.top_wp = wp; bc->data.b.top_lines = pane_lines; } if (bottom_owner) { bc->data.b.bottom_wp = wp; bc->data.b.bottom_lines = pane_lines; } if (mask & (REDRAW_BORDER_U|REDRAW_BORDER_D)) { if (wx < wp->xoff) { bc->data.b.right_wp = wp; bc->data.b.right_lines = pane_lines; } else if (wx >= wp->xoff + (int)wp->sx) { bc->data.b.left_wp = wp; bc->data.b.left_lines = pane_lines; } } mask |= bc->data.b.cell_mask; bc->data.b.cell_mask = mask; bc->data.b.cell_type = redraw_get_cell_type(mask); } /* * Mark border cells for a pane status line, keeping the border cell type for * drawing. */ static void redraw_mark_border_status(struct redraw_build_ctx *bctx, struct window_pane *wp, __unused int left, int right, int top, int bottom) { struct redraw_build_cell *bc; u_int x, y, off = 0; int pane_status, wy, sx, ex, wx, cell_type; pane_status = window_pane_get_pane_status(wp); if (pane_status == PANE_STATUS_OFF) return; if (pane_status == PANE_STATUS_TOP) wy = top; else wy = bottom; sx = wp->xoff + 2; ex = right - 1; if (sx > ex) return; for (wx = sx; wx <= ex; wx++, off++) { if (!redraw_window_to_scene(bctx, wx, wy, &x, &y)) continue; bc = redraw_get_build_cell(bctx, x, y); if (bc->data.type != REDRAW_SPAN_BORDER) continue; cell_type = bc->data.b.cell_type; bc->data.type = REDRAW_SPAN_STATUS; bc->data.st.wp = wp; bc->data.st.offset = off; bc->data.st.cell_type = cell_type; } } /* Mark existing border cells where indicator arrows will be drawn. */ static void redraw_mark_border_arrows(struct redraw_build_ctx *bctx, struct window_pane *wp, int left, int right, int top, int bottom) { struct redraw_build_cell *bc; u_int x, y; int wx, wy; if (bctx->ind != PANE_BORDER_ARROWS && bctx->ind != PANE_BORDER_BOTH) return; wx = wp->xoff + 1; if (wx >= left && wx <= right) { wy = top; if (redraw_window_to_scene(bctx, wx, wy, &x, &y)) { bc = redraw_get_build_cell(bctx, x, y); if (bc->data.type == REDRAW_SPAN_BORDER) bc->data.b.flags |= REDRAW_BORDER_IS_ARROW; } wy = bottom; if (redraw_window_to_scene(bctx, wx, wy, &x, &y)) { bc = redraw_get_build_cell(bctx, x, y); if (bc->data.type == REDRAW_SPAN_BORDER) bc->data.b.flags |= REDRAW_BORDER_IS_ARROW; } } wy = wp->yoff + 1; if (wy >= top && wy <= bottom) { wx = left; if (redraw_window_to_scene(bctx, wx, wy, &x, &y)) { bc = redraw_get_build_cell(bctx, x, y); if (bc->data.type == REDRAW_SPAN_BORDER) bc->data.b.flags |= REDRAW_BORDER_IS_ARROW; } wx = right; if (redraw_window_to_scene(bctx, wx, wy, &x, &y)) { bc = redraw_get_build_cell(bctx, x, y); if (bc->data.type == REDRAW_SPAN_BORDER) bc->data.b.flags |= REDRAW_BORDER_IS_ARROW; } } } /* Mark pane borders. */ static void redraw_mark_pane_borders(struct redraw_build_ctx *bctx, struct window_pane *wp, int sb_w, int sb_left) { enum pane_lines pane_lines = window_pane_get_pane_lines(wp); int pane_status, left, right, top, bottom, wx, wy; int mark_top, mark_bottom, mark_left, mark_right, mask = 0; int floating = window_pane_is_floating(wp); if (floating && pane_lines == PANE_LINES_NONE) return; pane_status = window_pane_get_pane_status(wp); left = wp->xoff - 1; right = wp->xoff + wp->sx; if (sb_w != 0) { if (sb_left) left -= sb_w; else right += sb_w; } top = wp->yoff - 1; bottom = wp->yoff + wp->sy; mark_left = (left >= 0); mark_right = (right <= (int)bctx->w->sx); mark_top = (top >= 0); mark_bottom = (bottom <= (int)bctx->w->sy); if (floating) { if (left < 0) left = 1; if (right > (int)bctx->w->sx) right = (int)bctx->w->sx - 1; if (top < 0) top = 1; if (bottom > (int)bctx->w->sy) bottom = (int)bctx->w->sy - 1; } else { if (pane_status == PANE_STATUS_TOP) mark_bottom = 0; else if (pane_status == PANE_STATUS_BOTTOM) mark_top = 0; } if (mark_top) { for (wx = left; wx <= right; wx++) { mask = 0; if (wx > left) mask |= REDRAW_BORDER_L; if (wx < right) mask |= REDRAW_BORDER_R; redraw_mark_border_cell(bctx, wx, top, wp, 0, 1, mask, pane_lines, floating); } } if (mark_bottom) { for (wx = left; wx <= right; wx++) { mask = 0; if (wx > left) mask |= REDRAW_BORDER_L; if (wx < right) mask |= REDRAW_BORDER_R; redraw_mark_border_cell(bctx, wx, bottom, wp, 1, 0, mask, pane_lines, floating); } } if (mark_left) { for (wy = top; wy <= bottom; wy++) { mask = 0; if (wy > top) mask |= REDRAW_BORDER_U; if (wy < bottom) mask |= REDRAW_BORDER_D; redraw_mark_border_cell(bctx, left, wy, wp, 0, 0, mask, pane_lines, floating); } } if (mark_right) { for (wy = top; wy <= bottom; wy++) { mask = 0; if (wy > top) mask |= REDRAW_BORDER_U; if (wy < bottom) mask |= REDRAW_BORDER_D; redraw_mark_border_cell(bctx, right, wy, wp, 0, 0, mask, pane_lines, floating); } } redraw_mark_border_status(bctx, wp, left, right, top, bottom); redraw_mark_border_arrows(bctx, wp, left, right, top, bottom); } /* * Mark an entire pane in the build grid. Floating panes overwrite anything * already below them. */ static void redraw_mark_pane(struct redraw_build_ctx *bctx, struct window_pane *wp) { int sb_w = 0, sb_left = 0; if (!window_pane_is_visible(wp)) return; if (window_pane_show_scrollbar(wp, bctx->sb)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if (sb_w != 0 && bctx->sbp == PANE_SCROLLBARS_LEFT) sb_left = 1; redraw_mark_pane_inside(bctx, wp); redraw_mark_pane_borders(bctx, wp, sb_w, sb_left); redraw_mark_pane_scrollbar(bctx, wp, sb_w, sb_left); } /* Choose the pane that will provide the border style for two-pane layouts. */ static void redraw_mark_two_pane_colours(struct redraw_build_ctx *bctx) { struct redraw_build_cell *bc; struct redraw_span_data *sd; enum layout_type type; u_int x, y, wx, wy; if (bctx->ind != PANE_BORDER_COLOUR && bctx->ind != PANE_BORDER_BOTH) return; if (!redraw_check_two_pane_colours(bctx->w, &type)) return; for (y = 0; y < bctx->sy; y++) { for (x = 0; x < bctx->sx; x++) { bc = redraw_get_build_cell(bctx, x, y); if (bc->data.type != REDRAW_SPAN_BORDER) continue; sd = &bc->data; wx = bctx->ox + x; wy = bctx->oy + y; if (type == LAYOUT_LEFTRIGHT && sd->b.left_wp != NULL && sd->b.right_wp != NULL) { if (wy <= bctx->w->sy / 2) sd->b.style_wp = sd->b.left_wp; else sd->b.style_wp = sd->b.right_wp; } else if (type == LAYOUT_TOPBOTTOM && sd->b.top_wp != NULL && sd->b.bottom_wp != NULL) { if (wx <= bctx->w->sx / 2) sd->b.style_wp = sd->b.top_wp; else sd->b.style_wp = sd->b.bottom_wp; } } } } /* Return true if two adjacent build cells can be joined into one span. */ static int redraw_compare_data(struct redraw_build_cell *a, struct redraw_build_cell *b) { struct redraw_span_data *ad = &a->data, *bd = &b->data; if (ad->type != bd->type) return (0); switch (ad->type) { case REDRAW_SPAN_PANE: if (ad->p.wp != bd->p.wp || ad->p.py != bd->p.py || ad->p.px + 1 != bd->p.px) return (0); return (1); case REDRAW_SPAN_BORDER: if (ad->b.top_wp != bd->b.top_wp || ad->b.bottom_wp != bd->b.bottom_wp || ad->b.left_wp != bd->b.left_wp || ad->b.right_wp != bd->b.right_wp || ad->b.style_wp != bd->b.style_wp || ad->b.top_lines != bd->b.top_lines || ad->b.bottom_lines != bd->b.bottom_lines || ad->b.left_lines != bd->b.left_lines || ad->b.right_lines != bd->b.right_lines || ad->b.cell_type != bd->b.cell_type || ad->b.cell_mask != bd->b.cell_mask || ad->b.flags != bd->b.flags) return (0); if (ad->b.flags & REDRAW_BORDER_IS_ARROW) return (0); return (1); case REDRAW_SPAN_STATUS: if (ad->st.wp != bd->st.wp || ad->st.offset + 1 != bd->st.offset || ad->st.cell_type != bd->st.cell_type) return (0); return (1); case REDRAW_SPAN_SCROLLBAR: if (ad->sb.wp != bd->sb.wp || ad->sb.y != bd->sb.y || ad->sb.height != bd->sb.height || ad->sb.flags != bd->sb.flags) return (0); return (1); case REDRAW_SPAN_OUTSIDE: case REDRAW_SPAN_EMPTY: return (1); } return (0); } /* Build the temporary cells for a redraw scene. */ static void redraw_build_cells(struct redraw_build_ctx *bctx) { struct window *w = bctx->w; struct window_pane *wp; size_t ncells; u_int x, y; if (bctx->sx != 0 && bctx->sy > SIZE_MAX / bctx->sx) fatalx("%s: too many cells", __func__); ncells = (size_t)bctx->sx * bctx->sy; if (ncells > redraw_ncells) { redraw_cells = xreallocarray(redraw_cells, ncells, sizeof *redraw_cells); redraw_ncells = ncells; } bctx->cells = redraw_cells; for (y = 0; y < bctx->sy; y++) { for (x = 0; x < bctx->sx; x++) redraw_reset_cell(bctx, x, y); } TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) redraw_mark_pane(bctx, wp); redraw_mark_two_pane_colours(bctx); } /* * Build and return a redraw scene for a client. The caller owns the scene and * must free it with redraw_free_scene. */ static struct redraw_scene * redraw_make_scene(struct client *c) { struct session *s = c->session; struct window *w = s->curw->window; struct redraw_build_ctx bctx; struct redraw_scene *scene; struct redraw_build_cell *bc, *last; struct redraw_line *line; struct redraw_span *span; enum redraw_span_type type; u_int x, y, x0; if (c->flags & CLIENT_SUSPENDED) return (NULL); redraw_set_context(c, &bctx); log_debug("%s: building @%u scene (%ux%u %u,%u; generation %llu)", c->name, w->id, bctx.sx, bctx.sy, bctx.ox, bctx.oy, (unsigned long long)w->redraw_scene_generation); redraw_build_cells(&bctx); scene = xcalloc(1, sizeof *scene); scene->c = c; scene->w = w; scene->lines = xcalloc(bctx.sy, sizeof *scene->lines); scene->generation = w->redraw_scene_generation; scene->sx = bctx.sx; scene->sy = bctx.sy; scene->ox = bctx.ox; scene->oy = bctx.oy; for (y = 0; y < bctx.sy; y++) { line = &scene->lines[y]; for (type = 0; type < REDRAW_SPAN_TYPES; type++) TAILQ_INIT(&line->spans[type]); x = 0; while (x < bctx.sx) { x0 = x; last = redraw_get_build_cell(&bctx, x, y); x++; while (x < bctx.sx) { bc = redraw_get_build_cell(&bctx, x, y); if (!redraw_compare_data(last, bc)) break; last = bc; x++; } bc = redraw_get_build_cell(&bctx, x0, y); type = bc->data.type; span = xcalloc(1, sizeof *span); span->x = x0; span->width = x - x0; span->data = bc->data; TAILQ_INSERT_TAIL(&line->spans[type], span, entry); } } log_debug("%s: finished building @%u scene", c->name, w->id); return (scene); } /* Free a scene. */ void redraw_free_scene(struct redraw_scene *scene) { struct redraw_spans *spans; struct redraw_span *span, *span1; u_int y, type; if (scene == NULL) return; for (y = 0; y < scene->sy; y++) { for (type = 0; type < REDRAW_SPAN_TYPES; type++) { spans = &scene->lines[y].spans[type]; TAILQ_FOREACH_SAFE(span, spans, entry, span1) { TAILQ_REMOVE(spans, span, entry); free(span); } } } free(scene->lines); free(scene); } /* Mark a window's cached redraw scenes as out of date. */ void redraw_invalidate_scene(struct window *w) { w->redraw_scene_generation++; } /* Mark all cached redraw scenes as out of date. */ void redraw_invalidate_all_scenes(void) { struct window *w; RB_FOREACH(w, windows, &windows) redraw_invalidate_scene(w); } /* Get the cached redraw scene, rebuilding it if needed. */ static struct redraw_scene * redraw_get_scene(struct client *c) { struct redraw_scene *scene = c->redraw_scene; struct window *w = c->session->curw->window; const char *reason = NULL; u_int ox, oy, sx, sy; redraw_get_window_offset(c, &ox, &oy, &sx, &sy); if (scene == NULL) reason = "missing"; else if (scene->w != w) reason = "window changed"; else if (scene->generation != w->redraw_scene_generation) reason = "generation changed"; else if (scene->ox != ox || scene->oy != oy) reason = "offset changed"; else if (scene->sx != sx || scene->sy != sy) reason = "size changed"; if (reason != NULL) { log_debug("%s: @%u scene invalid: %s", c->name, w->id, reason); redraw_free_scene(scene); scene = redraw_make_scene(c); c->redraw_scene = scene; } return (scene); } /* Draw a pane span. */ static void redraw_draw_pane_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, u_int x, u_int y, u_int n) { struct redraw_scene *scene = dctx->scene; struct client *c = scene->c; struct tty *tty = &c->tty; struct window_pane *wp = span->data.p.wp; struct screen *s = wp->screen; struct grid_cell defaults; struct tty_style_ctx style_ctx; u_int px, py; tty_default_colours(&defaults, wp); style_ctx.defaults = &defaults; style_ctx.palette = &wp->palette; style_ctx.hyperlinks = s->hyperlinks; px = span->data.p.px + (x - span->x); py = span->data.p.py; tty_draw_line(tty, s, px, py, n, x, y, &style_ctx); } /* Get default border style for spans without a pane. */ static void redraw_get_default_border_style(struct redraw_draw_ctx *dctx, struct grid_cell *gc, enum pane_lines *pane_lines) { struct redraw_scene *scene = dctx->scene; struct client *c = scene->c; struct session *s = c->session; struct options *oo = scene->w->options; struct format_tree *ft; struct grid_cell *dgc = &dctx->default_gc; if (~dctx->flags & REDRAW_DEFAULT_SET) { ft = format_create_defaults(NULL, c, s, s->curw, NULL); memcpy(dgc, &grid_default_cell, sizeof *dgc); style_add(dgc, oo, "pane-border-style", ft); format_free(ft); dctx->pane_lines = options_get_number(oo, "pane-border-lines"); dctx->flags |= REDRAW_DEFAULT_SET; } memcpy(gc, dgc, sizeof *gc); *pane_lines = dctx->pane_lines; } /* * For this border span, pick the pane whose border style should colour it. * Prefer an explicitly assigned style owner, then the active adjacent pane, * then any adjacent pane. */ static struct window_pane * redraw_get_pane_for_border_style(struct redraw_draw_ctx *dctx, struct redraw_span *span) { struct window_pane *active = dctx->active; if (span->data.type != REDRAW_SPAN_BORDER) return (NULL); if (span->data.b.style_wp != NULL) return (span->data.b.style_wp); if (active != NULL && redraw_data_has_pane(&span->data, active)) return (active); if (span->data.b.top_wp != NULL) return (span->data.b.top_wp); if (span->data.b.bottom_wp != NULL) return (span->data.b.bottom_wp); if (span->data.b.left_wp != NULL) return (span->data.b.left_wp); if (span->data.b.right_wp != NULL) return (span->data.b.right_wp); return (NULL); } /* Draw arrow indicator if this border span is an arrow cell. */ static void redraw_draw_border_arrow(struct redraw_draw_ctx *dctx, struct redraw_span *span, struct grid_cell *gc) { struct window_pane *active = dctx->active; char ch; if (span->data.type != REDRAW_SPAN_BORDER || active == NULL) return; if (~span->data.b.flags & REDRAW_BORDER_IS_ARROW) return; if (span->data.b.left_wp == active) ch = ','; else if (span->data.b.right_wp == active) ch = '+'; else if (span->data.b.top_wp == active) ch = '-'; else if (span->data.b.bottom_wp == active) ch = '.'; else return; utf8_set(&gc->data, ch); gc->attr |= GRID_ATTR_CHARSET; } /* Draw a border span. */ static void redraw_draw_border_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, u_int x, u_int y, u_int n) { struct redraw_scene *scene = dctx->scene; struct client *c = scene->c; struct tty *tty = &c->tty; struct window *w = scene->w; struct window_pane *wp = NULL; struct grid_cell gc; enum pane_lines pane_lines; u_int i, cell_type; int isolates = 0; if (span->data.type != REDRAW_SPAN_BORDER) cell_type = CELL_NONE; else { wp = redraw_get_pane_for_border_style(dctx, span); cell_type = span->data.b.cell_type; } if (wp == NULL) { redraw_get_default_border_style(dctx, &gc, &pane_lines); window_get_border_cell(w, NULL, pane_lines, cell_type, &gc); } else { window_pane_get_border_style(wp, c, &gc); window_pane_get_border_cell(wp, cell_type, &gc); } if (span->data.type == REDRAW_SPAN_BORDER && dctx->marked != NULL && redraw_data_has_pane(&span->data, dctx->marked)) gc.attr ^= GRID_ATTR_REVERSE; redraw_draw_border_arrow(dctx, span, &gc); if (cell_type == CELL_UD && (dctx->flags & REDRAW_ISOLATES)) isolates = 1; tty_cursor(tty, x, y); if (isolates) tty_puts(tty, REDRAW_END_ISOLATE); for (i = 0; i < n; i++) tty_cell(tty, &gc, NULL); if (isolates) tty_puts(tty, REDRAW_START_ISOLATE); } /* Draw a pane status span. */ static void redraw_draw_status_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, u_int x, u_int y, u_int n) { struct redraw_scene *scene = dctx->scene; struct client *c = scene->c; struct tty *tty = &c->tty; struct window_pane *wp = span->data.st.wp; struct screen *s = &wp->status_screen; u_int px, sx = screen_size_x(s); px = span->data.st.offset + (x - span->x); if (px < sx) { if (n > sx - px) n = sx - px; tty_draw_line(tty, s, px, 0, n, x, y, NULL); } } /* Draw a scrollbar span. */ static void redraw_draw_scrollbar_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, u_int x, u_int y, u_int n) { struct redraw_scene *scene = dctx->scene; struct window_pane *wp = span->data.sb.wp; struct screen *s = wp->screen; struct tty *tty = &scene->c->tty; struct style *sb_style = &wp->scrollbar_style; struct grid_cell gc, slgc, *gcp; double pct_view; u_int total_height, slider_h, slider_y; u_int sb_h = span->data.sb.height; u_int sb_y = span->data.sb.y; u_int i, off, sb_w, sb_pad; int cm_y, cm_size; if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) { total_height = screen_size_y(s) + screen_hsize(s); if (total_height == 0) return; pct_view = (double)sb_h / total_height; slider_h = (double)sb_h * pct_view; slider_y = sb_h - slider_h; } else { if (TAILQ_FIRST(&wp->modes) == NULL) return; if (window_copy_get_current_offset(wp, &cm_y, &cm_size) == 0) return; total_height = cm_size + sb_h; if (total_height == 0) return; pct_view = (double)sb_h / total_height; slider_h = (double)sb_h * pct_view; slider_y = (sb_h + 1) * ((double)cm_y / total_height); } if (slider_h < 1) slider_h = 1; if (slider_y >= sb_h) slider_y = sb_h - 1; wp->sb_slider_y = slider_y; wp->sb_slider_h = slider_h; gc = sb_style->gc; memcpy(&slgc, &gc, sizeof slgc); slgc.fg = gc.bg; slgc.bg = gc.fg; sb_w = sb_style->width; sb_pad = sb_style->pad; off = x - span->x; tty_cursor(tty, x, y); for (i = 0; i < n; i++) { if (span->data.sb.flags & REDRAW_SCROLLBAR_LEFT) { if (off + i >= sb_w && off + i < sb_w + sb_pad) { tty_cell(tty, &grid_default_cell, NULL); continue; } } else { if (off + i < sb_pad) { tty_cell(tty, &grid_default_cell, NULL); continue; } } if (sb_y >= slider_y && sb_y < slider_y + slider_h) gcp = &slgc; else gcp = &gc; tty_cell(tty, gcp, NULL); } } /* Draw a span. */ static void redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, u_int y) { struct redraw_scene *scene = dctx->scene; struct redraw_span_data *data = &span->data; enum redraw_span_type type = data->type; struct client *c = scene->c; struct tty *tty = &c->tty; struct visible_ranges *r; struct visible_range *rr; u_int i, x, n; if (type == REDRAW_SPAN_STATUS && ~data->st.wp->flags & PANE_NEWSTATUS) return; r = tty_check_overlay_range(tty, span->x, y, span->width); for (i = 0; i < r->used; i++) { rr = &r->ranges[i]; if (rr->nx == 0) continue; x = rr->px; n = rr->nx; switch (span->data.type) { case REDRAW_SPAN_PANE: redraw_draw_pane_span(dctx, span, x, y, n); break; case REDRAW_SPAN_BORDER: case REDRAW_SPAN_EMPTY: case REDRAW_SPAN_OUTSIDE: redraw_draw_border_span(dctx, span, x, y, n); break; case REDRAW_SPAN_STATUS: redraw_draw_status_span(dctx, span, x, y, n); break; case REDRAW_SPAN_SCROLLBAR: redraw_draw_scrollbar_span(dctx, span, x, y, n); break; } } } /* Draw pane lines. */ static void redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp, int flags) { struct redraw_scene *scene = dctx->scene; struct redraw_line *line; struct redraw_spans *spans; struct redraw_span *span; u_int cy; int y, top, bottom; top = wp->yoff - (int)scene->oy; if (top < 0) top = 0; bottom = wp->yoff + (int)wp->sy - (int)scene->oy; if (bottom < 0) bottom = 0; if (bottom > (int)scene->sy) bottom = scene->sy; for (y = top; y < bottom; y++) { line = &scene->lines[y]; if (dctx->flags & REDRAW_STATUS_TOP) cy = dctx->status_lines + y; else cy = y; if (flags & REDRAW_PANE) { spans = &line->spans[REDRAW_SPAN_PANE]; TAILQ_FOREACH(span, spans, entry) { if (span->data.p.wp == wp) redraw_draw_span(dctx, span, cy); } } if (flags & REDRAW_PANE_SCROLLBAR) { spans = &line->spans[REDRAW_SPAN_SCROLLBAR]; TAILQ_FOREACH(span, spans, entry) { if (span->data.sb.wp == wp) redraw_draw_span(dctx, span, cy); } } } } /* Draw lines. */ static void redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags) { struct redraw_scene *scene = dctx->scene; struct redraw_line *line; struct redraw_spans *spans; struct redraw_span *span; u_int y, cy, type; for (y = 0; y < scene->sy; y++) { line = &scene->lines[y]; if (dctx->flags & REDRAW_STATUS_TOP) cy = dctx->status_lines + y; else cy = y; for (type = 0; type < REDRAW_SPAN_TYPES; type++) { if (!REDRAW_IS_ALL(flags)) { switch (type) { case REDRAW_SPAN_PANE: if (~flags & REDRAW_PANE) continue; break; case REDRAW_SPAN_OUTSIDE: if (~flags & REDRAW_OUTSIDE) continue; break; case REDRAW_SPAN_EMPTY: if (~flags & REDRAW_EMPTY) continue; break; case REDRAW_SPAN_BORDER: if (~flags & REDRAW_PANE_BORDER) continue; break; case REDRAW_SPAN_STATUS: if (~flags & REDRAW_PANE_STATUS) continue; break; case REDRAW_SPAN_SCROLLBAR: if (~flags & REDRAW_PANE_SCROLLBAR) continue; break; default: continue; } } spans = &line->spans[type]; TAILQ_FOREACH(span, spans, entry) redraw_draw_span(dctx, span, cy); } } } /* Get line for pane status line. */ static int redraw_pane_status_line(struct redraw_draw_ctx *dctx, struct window_pane *wp, u_int *line) { struct redraw_scene *scene = dctx->scene; int pane_status, wy; pane_status = window_pane_get_pane_status(wp); if (pane_status == PANE_STATUS_OFF) return (0); if (pane_status == PANE_STATUS_TOP) wy = (int)wp->yoff - 1; else wy = (int)wp->yoff + wp->sy; if (wy < 0 || wy < (int)scene->oy) return (0); if ((u_int)wy >= scene->oy + scene->sy) return (0); *line = wy - scene->oy; return (1); } /* Get available width for pane status line. */ static u_int redraw_pane_status_width(struct redraw_draw_ctx *dctx, struct window_pane *wp, struct redraw_span **first) { struct redraw_scene *scene = dctx->scene; struct redraw_span *span; u_int y, width = 0, end; if (!redraw_pane_status_line(dctx, wp, &y)) return (0); *first = NULL; TAILQ_FOREACH(span, &scene->lines[y].spans[REDRAW_SPAN_STATUS], entry) { if (span->data.st.wp == wp) { if (*first == NULL) *first = span; end = span->data.st.offset + span->width; if (end > width) width = end; } } return (width); } /* Set up draw context. */ static void redraw_set_draw_context(struct redraw_draw_ctx *dctx, struct redraw_scene *scene) { struct client *c = scene->c; struct session *s = c->session; struct options *oo = s->options; struct tty *tty = &c->tty; u_int lines; memset(dctx, 0, sizeof *dctx); dctx->scene = scene; if (server_is_marked(s, s->curw, marked_pane.wp)) dctx->marked = marked_pane.wp; dctx->active = server_client_get_pane(c); lines = status_line_size(c); if (options_get_number(oo, "status-position") == 0) dctx->flags |= REDRAW_STATUS_TOP; dctx->status_lines = lines; if ((c->flags & CLIENT_UTF8) && tty_term_has(tty->term, TTYC_BIDI)) dctx->flags |= REDRAW_ISOLATES; } /* Draw scene to client. */ static void redraw_draw(struct client *c, struct window_pane *wp, int flags) { struct redraw_draw_ctx dctx; struct session *s = c->session; struct window *w = s->curw->window; struct tty *tty = &c->tty; struct screen *sl; struct redraw_scene *scene; struct window_pane *loop; u_int width, i, y, lines; struct redraw_span *first; int redraw; if (c->flags & CLIENT_SUSPENDED) return; if (flags & REDRAW_STATUS) { if (c->message_string != NULL) redraw = status_message_redraw(c); else if (c->prompt_string != NULL) redraw = status_prompt_redraw(c); else redraw = status_redraw(c); if (!redraw && !REDRAW_IS_ALL(flags)) { flags &= ~REDRAW_STATUS; if (flags == 0) return; } } if (log_get_level() != 0) { log_debug("%s: starting @%u redraw (%s)", c->name, w->id, redraw_flags_to_string(flags)); } scene = redraw_get_scene(c); if (scene == NULL) return; redraw_set_draw_context(&dctx, scene); if (flags & (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS)) { TAILQ_FOREACH(loop, &scene->w->panes, entry) { loop->border_gc_set = 0; loop->active_border_gc_set = 0; } } if (flags & REDRAW_PANE_STATUS) { redraw = 0; TAILQ_FOREACH(loop, &scene->w->panes, entry) { if (REDRAW_IS_ALL(flags)) loop->flags |= PANE_NEWSTATUS; else loop->flags &= ~PANE_NEWSTATUS; width = redraw_pane_status_width(&dctx, loop, &first); if (width == 0) continue; if (window_make_pane_status(loop, c, width, first)) { loop->flags |= PANE_NEWSTATUS; redraw = 1; } } if (!redraw && !REDRAW_IS_ALL(flags)) { flags &= ~REDRAW_PANE_STATUS; if (flags == 0) return; } } if (flags & REDRAW_PANE) { if (wp != NULL) { if (wp->base.mode & MODE_SYNC) screen_write_stop_sync(wp); } else { TAILQ_FOREACH(loop, &scene->w->panes, entry) { if (!window_pane_is_visible(loop)) continue; if (loop->base.mode & MODE_SYNC) screen_write_stop_sync(loop); } } } tty_sync_start(tty); tty_update_mode(tty, 0, NULL); if (wp != NULL) redraw_draw_pane_lines(&dctx, wp, flags); else redraw_draw_lines(&dctx, flags); if (flags & REDRAW_STATUS) { lines = dctx.status_lines; if (c->message_string != NULL || c->prompt_string != NULL) lines = (lines == 0 ? 1 : lines); if (dctx.flags & REDRAW_STATUS_TOP) y = 0; else y = c->tty.sy - lines; sl = c->status.active; for (i = 0; i < lines; i++) tty_draw_line(tty, sl, 0, i, UINT_MAX, 0, y + i, NULL); } if (c->overlay_draw != NULL && (flags & REDRAW_OVERLAY)) c->overlay_draw(c, c->overlay_data); tty_reset(tty); tty_sync_end(tty); log_debug("%s: finished @%u redraw", c->name, scene->w->id); } /* Get border cell type beneath status cell at offset x in pane status line. */ int redraw_get_status_border_cell_type(struct redraw_span **spanp, u_int x) { struct redraw_span *span = *spanp; struct window_pane *wp; u_int start, end; if (span == NULL || span->data.type != REDRAW_SPAN_STATUS) return (CELL_LR); wp = span->data.st.wp; for (; span != NULL; span = TAILQ_NEXT(span, entry)) { if (span->data.type != REDRAW_SPAN_STATUS) continue; if (span->data.st.wp != wp) continue; start = span->data.st.offset; end = start + span->width; if (x >= start && x < end) { *spanp = span; return (span->data.st.cell_type); } if (start > x) { *spanp = span; break; } } if (span == NULL) *spanp = NULL; return (CELL_LR); } /* Draw screen. */ void redraw_screen(struct client *c) { int flags = 0; if (c->flags & CLIENT_REDRAWWINDOW) redraw_draw(c, NULL, REDRAW_ALL); else { if (c->flags & CLIENT_REDRAWBORDERS) flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS); if (c->flags & CLIENT_REDRAWSTATUS) flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS); if (c->flags & CLIENT_REDRAWOVERLAY) flags |= REDRAW_OVERLAY; if (flags != 0) redraw_draw(c, NULL, flags); } } /* Draw a single pane. */ void redraw_pane(struct client *c, struct window_pane *wp) { redraw_draw(c, wp, REDRAW_PANE|REDRAW_PANE_SCROLLBAR); } /* Draw a pane's scrollbar. */ void redraw_pane_scrollbar(struct client *c, struct window_pane *wp) { redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR); }