diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 050c7ca38..6557cef32 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-attach-session.c,v 1.91 2026/07/10 13:38:45 nicm Exp $ */ +/* $OpenBSD: cmd-attach-session.c,v 1.92 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -91,8 +91,10 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, wp = target.wp; if (wl != NULL) { - if (wp != NULL) + if (wp != NULL) { + window_redraw_active_switch(wp->window, wp); window_set_active_pane(wp->window, wp, 1); + } session_set_current(s, wl); if (wp != NULL) cmd_find_from_winlink_pane(current, wl, wp, 0); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 8f4033855..28002bcd1 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-join-pane.c,v 1.75 2026/09/21 10:33:16 nicm Exp $ */ +/* $OpenBSD: cmd-join-pane.c,v 1.76 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2011 George Nachman @@ -308,7 +308,7 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m) struct window *w; struct window_pane *wp; struct layout_cell *lc; - int y, ly, x, lx; + int y, ly, x, lx, oxoff, oyoff, osx, osy; wp = cmd_mouse_pane(m, NULL, &wl); if (wp == NULL) { @@ -330,10 +330,16 @@ cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m) ly = m->statusat - 1; if (x != lx || y != ly) { + oxoff = wp->xoff; + oyoff = wp->yoff; + osx = wp->sx; + osy = wp->sy; + lc->g.xoff += x - lx; lc->g.yoff += y - ly; layout_fix_panes(w, NULL); - server_redraw_window(w); + + window_redraw_floating_pane(wp, oxoff, oyoff, osx, osy); server_redraw_window_borders(w); } } diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index c644dd783..90ebff4c1 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-resize-pane.c,v 1.69 2026/09/21 10:33:16 nicm Exp $ */ +/* $OpenBSD: cmd-resize-pane.c,v 1.70 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -237,8 +237,8 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c, struct window_pane *wp; struct layout_cell *lc; int y, ly, x, lx, sx, sy, new_sx, new_sy; - int left, right; - int new_xoff, new_yoff, resizes = 0; + int left, right, resizes = 0; + int new_xoff, new_yoff, old_xoff, old_yoff; wp = cmd_mouse_pane(m, NULL, &wl); if (wp == NULL) { @@ -249,6 +249,8 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c, lc = wp->layout_cell; sx = wp->sx; sy = wp->sy; + old_xoff = wp->xoff; + old_yoff = wp->yoff; left = wp->xoff - 1; right = wp->xoff + sx; if (window_pane_scrollbar_reserve(wp) && @@ -348,7 +350,7 @@ cmd_resize_pane_mouse_resize_move_floating(struct client *c, } if (resizes != 0) { layout_fix_panes(w, NULL); - server_redraw_window(w); + window_redraw_floating_pane(wp, old_xoff, old_yoff, sx, sy); server_redraw_window_borders(w); } } diff --git a/cmd-split-window.c b/cmd-split-window.c index 89e74d400..71812308c 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-split-window.c,v 1.151 2026/09/10 11:02:18 nicm Exp $ */ +/* $OpenBSD: cmd-split-window.c,v 1.152 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -363,6 +363,7 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m) enum pane_lines lines; u_int sx, sy; int x, y, xoff, yoff, border; + int oxoff, oyoff, osx, osy; if (c->tty.mouse_last_pane == -1) return; @@ -418,8 +419,14 @@ cmd_split_window_mouse_resize(struct client *c, struct mouse_event *m) if (sy < PANE_MINIMUM) sy = PANE_MINIMUM; + oxoff = wp->xoff; + oyoff = wp->yoff; + osx = wp->sx; + osy = wp->sy; + layout_set_size(lc, sx, sy, xoff, yoff); layout_fix_panes(w, NULL); - server_redraw_window(w); + + window_redraw_floating_pane(wp, oxoff, oyoff, osx, osy); server_redraw_window_borders(w); } diff --git a/layout.c b/layout.c index 74c3c29bb..92dad4946 100644 --- a/layout.c +++ b/layout.c @@ -1,4 +1,4 @@ -/* $OpenBSD: layout.c,v 1.101 2026/09/20 08:42:46 nicm Exp $ */ +/* $OpenBSD: layout.c,v 1.102 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -469,7 +469,6 @@ layout_fix_panes(struct window *w, struct window_pane *skip) sx = PANE_MINIMUM; else sx = sx - sb_w - sb_pad; - wp->flags |= PANE_REDRAWSCROLLBAR; } window_pane_resize(wp, sx, sy); @@ -477,8 +476,11 @@ layout_fix_panes(struct window *w, struct window_pane *skip) if (wp->xoff != old_xoff || wp->yoff != old_yoff || wp->sx != old_sx || - wp->sy != old_sy) + wp->sy != old_sy) { + if (window_pane_scrollbar_reserve(wp)) + wp->flags |= PANE_REDRAWSCROLLBAR; changed = 1; + } } if (changed) redraw_invalidate_scene(w); diff --git a/screen-redraw.c b/screen-redraw.c index 6bce2271f..c68b35478 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen-redraw.c,v 1.162 2026/09/24 11:19:39 nicm Exp $ */ +/* $OpenBSD: screen-redraw.c,v 1.163 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -204,14 +204,36 @@ struct redraw_scene { u_int oy; }; +/* A damaged rectangle in a window. */ +struct redraw_damage { + u_int x; + u_int y; + u_int sx; + u_int sy; + + TAILQ_ENTRY(redraw_damage) entry; +}; + +/* + * If there are more damage rectangles than this, they are collapsed into + * one. + */ +#define REDRAW_DAMAGE_MAX 16 + /* 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; +/* + * We can reuse the same pane status lines during one damage redraw, but when + * we enter a new one, the client or format variables may have changed, so we + * need to make them again. The generation is increased so this happens. + */ +static u_int redraw_status_generation; + /* Context for building the scene. */ struct redraw_build_ctx { struct client *c; @@ -1054,6 +1076,13 @@ redraw_free_scene(struct redraw_scene *scene) free(scene); } +/* Does a client's cached scene show this window? */ +int +redraw_client_has_window(struct client *c, struct window *w) +{ + return (c->redraw_scene != NULL && c->redraw_scene->w == w); +} + /* Mark a window's cached redraw scenes as out of date. */ void redraw_invalidate_scene(struct window *w) @@ -1061,6 +1090,104 @@ redraw_invalidate_scene(struct window *w) w->redraw_scene_generation++; } +/* Free all pending damage for a window. */ +void +redraw_free_damage(struct window *w) +{ + struct redraw_damage *rd, *rd1; + + TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) { + TAILQ_REMOVE(&w->damage, rd, entry); + free(rd); + } + w->damage_count = 0; +} + +/* Collapse all pending damage for a window into one rectangle. */ +static void +redraw_collapse_damage(struct window *w) +{ + struct redraw_damage *rd, *rd1, *first; + u_int x0, y0, x1, y1; + + first = TAILQ_FIRST(&w->damage); + if (first == NULL) + return; + x0 = first->x; + y0 = first->y; + x1 = first->x + first->sx; + y1 = first->y + first->sy; + + TAILQ_FOREACH_SAFE(rd, &w->damage, entry, rd1) { + if (rd->x < x0) + x0 = rd->x; + if (rd->y < y0) + y0 = rd->y; + if (rd->x + rd->sx > x1) + x1 = rd->x + rd->sx; + if (rd->y + rd->sy > y1) + y1 = rd->y + rd->sy; + if (rd != first) { + TAILQ_REMOVE(&w->damage, rd, entry); + free(rd); + } + } + + first->x = x0; + first->y = y0; + first->sx = x1 - x0; + first->sy = y1 - y0; + w->damage_count = 1; +} + +/* Record window damage, merging nearby rectangles and limiting the count. */ +void +redraw_damage_window(struct window *w, u_int x, u_int y, u_int sx, u_int sy) +{ + struct redraw_damage *rd; + u_int x0, y0, x1, y1, area, union_area; + + if (x >= w->sx || y >= w->sy) + return; + if (x + sx > w->sx) + sx = w->sx - x; + if (y + sy > w->sy) + sy = w->sy - y; + if (sx == 0 || sy == 0) + return; + + TAILQ_FOREACH(rd, &w->damage, entry) { + if (x > rd->x + rd->sx || rd->x > x + sx || + y > rd->y + rd->sy || rd->y > y + sy) + continue; + + x0 = (x < rd->x) ? x : rd->x; + y0 = (y < rd->y) ? y : rd->y; + x1 = (x + sx > rd->x + rd->sx) ? x + sx : rd->x + rd->sx; + y1 = (y + sy > rd->y + rd->sy) ? y + sy : rd->y + rd->sy; + + area = sx * sy + rd->sx * rd->sy; + union_area = (x1 - x0) * (y1 - y0); + if (union_area > 2 * area) + continue; + + rd->x = x0; + rd->y = y0; + rd->sx = x1 - x0; + rd->sy = y1 - y0; + return; + } + + rd = xcalloc(1, sizeof *rd); + rd->x = x; + rd->y = y; + rd->sx = sx; + rd->sy = sy; + TAILQ_INSERT_TAIL(&w->damage, rd, entry); + if (++w->damage_count > REDRAW_DAMAGE_MAX) + redraw_collapse_damage(w); +} + /* Mark all cached redraw scenes as out of date. */ void redraw_invalidate_all_scenes(void) @@ -1603,6 +1730,24 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx, dctx->flags |= REDRAW_ISOLATES; } +/* Build a pane prompt. */ +static void +redraw_make_pane_prompt(struct window_pane *wp, struct screen *screen) +{ + struct screen_write_ctx ctx; + struct prompt_draw_data pdd; + + screen_init(screen, wp->sx, 1, 0); + screen_write_start(&ctx, screen); + pdd.ctx = &ctx; + pdd.cursor_x = &wp->prompt_cx; + pdd.area_x = 0; + pdd.area_width = wp->sx; + pdd.prompt_line = 0; + prompt_draw(wp->prompt, &pdd); + screen_write_stop(&ctx); +} + /* Draw a pane's prompt over its content. */ static void redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp) @@ -1611,8 +1756,6 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp) struct client *c = scene->c; struct tty *tty = &c->tty; struct screen screen; - struct screen_write_ctx ctx; - struct prompt_draw_data pdd; int ox = scene->ox, oy = scene->oy; int sx = scene->sx, sy = scene->sy; int line, cy, px, offset, width, wy; @@ -1645,16 +1788,7 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp) if (px + width > sx) width = sx - px; - screen_init(&screen, wp->sx, 1, 0); - screen_write_start(&ctx, &screen); - pdd.ctx = &ctx; - pdd.cursor_x = &wp->prompt_cx; - pdd.area_x = 0; - pdd.area_width = wp->sx; - pdd.prompt_line = 0; - prompt_draw(wp->prompt, &pdd); - screen_write_stop(&ctx); - + redraw_make_pane_prompt(wp, &screen); tty_draw_line(tty, &screen, offset, 0, width, px, cy, NULL); screen_free(&screen); } @@ -1684,7 +1818,9 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) redraw = status_prompt_redraw(c); else redraw = status_redraw(c); - if (!redraw && !REDRAW_IS_ALL(flags)) { + if (!redraw && + (~c->flags & CLIENT_REDRAWSTATUSALWAYS) && + !REDRAW_IS_ALL(flags)) { flags &= ~REDRAW_STATUS; if (flags == 0) return; @@ -1834,7 +1970,7 @@ redraw_screen(struct client *c) else { if (c->flags & CLIENT_REDRAWBORDERS) flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS); - if (c->flags & CLIENT_REDRAWSTATUS) + if (c->flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS)) flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS); if (c->flags & CLIENT_REDRAWMENU) flags |= REDRAW_MENU; @@ -1860,3 +1996,138 @@ redraw_pane_scrollbar(struct client *c, struct window_pane *wp) { redraw_draw(c, wp, REDRAW_PANE_SCROLLBAR); } + +/* Rebuild damaged pane status. */ +static void +redraw_damage_refresh_status(struct redraw_draw_ctx *dctx, + struct window_pane *wp) +{ + struct redraw_span *first; + u_int g = wp->status_generation, width; + + if ((wp->flags & PANE_NEWSTATUS) && g == redraw_status_generation) + return; + width = redraw_pane_status_width(dctx, wp, &first); + if (width != 0) { + window_make_pane_status(wp, dctx->scene->c, width, first); + wp->flags |= PANE_NEWSTATUS; + wp->status_generation = redraw_status_generation; + } +} + +/* Draw a pane's prompt over a damaged span. */ +static void +redraw_damage_draw_pane_prompt(struct redraw_draw_ctx *dctx, + struct redraw_span *span, u_int y) +{ + struct redraw_scene *scene = dctx->scene; + struct window_pane *wp = span->data.p.wp; + struct tty *tty = &scene->c->tty; + struct screen screen; + u_int px = span->data.p.px, width, prompt_y; + + if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0) + return; + if (dctx->flags & REDRAW_STATUS_TOP) + prompt_y = 0; + else + prompt_y = wp->sy - 1; + if (span->data.p.py != prompt_y) + return; + redraw_make_pane_prompt(wp, &screen); + if (px < screen_size_x(&screen)) { + width = span->width; + if (width > screen_size_x(&screen) - px) + width = screen_size_x(&screen) - px; + tty_draw_line(tty, &screen, px, 0, width, span->x, y, NULL); + } + screen_free(&screen); +} + +/* Draw the spans intersecting a damaged rectangle. */ +static void +redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y, + u_int sx, u_int sy) +{ + struct redraw_scene *scene = dctx->scene; + struct redraw_line *line; + struct redraw_spans *spans; + struct redraw_span *span; + u_int cy, yy, type; + + if (x >= scene->sx || y >= scene->sy) + return; + if (x + sx > scene->sx) + sx = scene->sx - x; + if (y + sy > scene->sy) + sy = scene->sy - y; + if (sx == 0 || sy == 0) + return; + + for (yy = y; yy < y + sy; yy++) { + line = &scene->lines[yy]; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + yy; + else + cy = yy; + for (type = 0; type < REDRAW_SPAN_TYPES; type++) { + spans = &line->spans[type]; + TAILQ_FOREACH(span, spans, entry) { + if (span->x >= x + sx) + continue; + if (span->x + span->width <= x) + continue; + if (type == REDRAW_SPAN_STATUS) { + redraw_damage_refresh_status(dctx, + span->data.st.wp); + } + redraw_draw_span(dctx, span, cy); + if (type == REDRAW_SPAN_PANE) { + redraw_damage_draw_pane_prompt(dctx, + span, cy); + } + } + } + } +} + +/* Draw pending window damage on this client. */ +void +redraw_client_damage(struct client *c) +{ + struct window *w = c->session->curw->window; + struct window_pane *wp; + struct redraw_scene *scene; + struct redraw_draw_ctx dctx; + struct redraw_damage *rd; + u_int ox, oy, sx, sy, x0, y0, x1, y1; + + if (TAILQ_EMPTY(&w->damage)) + return; + redraw_status_generation++; + + scene = redraw_get_scene(c); + if (scene == NULL) + return; + redraw_set_draw_context(&dctx, scene); + redraw_get_window_offset(c, &ox, &oy, &sx, &sy); + + TAILQ_FOREACH(wp, &w->panes, entry) { + wp->border_gc_set = 0; + wp->active_border_gc_set = 0; + } + + tty_sync_start(&c->tty); + tty_update_mode(&c->tty, c->tty.mode & ~CURSOR_MODES, NULL); + + TAILQ_FOREACH(rd, &w->damage, entry) { + x0 = (rd->x > ox) ? rd->x : ox; + y0 = (rd->y > oy) ? rd->y : oy; + x1 = (rd->x + rd->sx < ox + sx) ? rd->x + rd->sx : ox + sx; + y1 = (rd->y + rd->sy < oy + sy) ? rd->y + rd->sy : oy + sy; + if (x0 < x1 && y0 < y1) { + redraw_draw_damage_rectangle(&dctx, x0 - ox, y0 - oy, + x1 - x0, y1 - y0); + } + } +} diff --git a/screen-write.c b/screen-write.c index 6e3d46661..b024e474b 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1,4 +1,4 @@ -/* $OpenBSD: screen-write.c,v 1.294 2026/09/28 10:10:16 nicm Exp $ */ +/* $OpenBSD: screen-write.c,v 1.295 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -122,14 +122,26 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy) evtimer_add(&w->offset_timer, &tv); } -/* Do a full redraw. */ +/* Redraw lines. */ static void -screen_write_redraw_cb(const struct tty_ctx *ttyctx) +screen_write_redraw_cb(const struct tty_ctx *ttyctx, u_int py, u_int ny) { struct window_pane *wp = ttyctx->arg; + int x0, y0, x1, y1; - if (wp != NULL) - wp->flags |= PANE_REDRAW; + if (wp == NULL) + return; + + x0 = wp->xoff; + y0 = wp->yoff + (int)py; + x1 = x0 + (int)wp->sx; + y1 = y0 + (int)ny; + if (x0 < 0) + x0 = 0; + if (y0 < 0) + y0 = 0; + if (x1 > x0 && y1 > y0) + redraw_damage_window(wp->window, x0, y0, x1 - x0, y1 - y0); } /* Update context for client. */ @@ -2185,7 +2197,7 @@ screen_write_fullredraw(struct screen_write_ctx *ctx) screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) - ttyctx.redraw_cb(&ttyctx); + ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy); } /* Trim collected items. */ @@ -3138,7 +3150,7 @@ screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) - ttyctx.redraw_cb(&ttyctx); + ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy); } /* Turn alternate screen off. */ @@ -3163,5 +3175,5 @@ screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc, screen_write_initctx(ctx, &ttyctx, 1, 0); if (ttyctx.redraw_cb != NULL) - ttyctx.redraw_cb(&ttyctx); + ttyctx.redraw_cb(&ttyctx, 0, ttyctx.sy); } diff --git a/server-client.c b/server-client.c index 051b6de9b..c57bf2651 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.516 2026/10/02 12:28:07 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.517 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -361,7 +361,13 @@ server_client_set_session(struct client *c, struct session *s) tty_update_client_offset(c); status_timer_start(c); server_client_fire_session_changed(c, old); - server_redraw_client(c); + + /* + * Redraw if the session or displayed window changed. Use the + * cached scene because the session's current window is already set. + */ + if (old != s || !redraw_client_has_window(c, s->curw->window)) + server_redraw_client(c); } server_check_unattached(); @@ -1353,10 +1359,11 @@ server_client_key_callback(struct cmdq_item *item, void *data) m->key = key; /* - * Mouse drag is in progress, so fire the callback (now that - * the mouse event is valid). + * Synchronize direct drag output with the later damage redraw + * before invoking the drag callback. */ if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) { + tty_sync_start(&c->tty); c->tty.mouse_drag_update(c, m); goto out; } @@ -1783,8 +1790,8 @@ server_client_loop(void) } /* - * Any windows will have been redrawn as part of clients, so clear - * their flags now. + * Clear window redraw state after processing all clients. Deferred + * redraws are preserved in client flags. */ RB_FOREACH(w, windows, &windows) { TAILQ_FOREACH(wp, &w->panes, entry) { @@ -1795,6 +1802,8 @@ server_client_loop(void) wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR| PANE_ACTIVITY); } + redraw_free_damage(w); + check_window_name(w); } @@ -2340,6 +2349,8 @@ server_client_any_pane_redraw(struct client *c) if (c->flags & CLIENT_REDRAWWINDOW) return (1); + if (!TAILQ_EMPTY(&w->damage)) + return (1); TAILQ_FOREACH(wp, &w->panes, entry) { if (wp->flags & (PANE_REDRAW|PANE_REDRAWSCROLLBAR)) return (1); @@ -2356,6 +2367,7 @@ server_client_check_redraw(struct client *c) struct window *w = s->curw->window; struct window_pane *wp; int needed, tflags, mode = tty->mode; + int damaged = !TAILQ_EMPTY(&w->damage); struct timeval tv = { .tv_usec = 1000 }; static struct event ev; size_t n; @@ -2381,12 +2393,12 @@ server_client_check_redraw(struct client *c) return; } - /* - * If there is outstanding data, defer the redraw until it has been - * consumed. We can just add a timer to get out of the event loop and - * end up back here. - */ + /* Ignore output queued within the current synchronized frame. */ n = EVBUFFER_LENGTH(tty->out); + if ((tty->flags & TTY_SYNCING) && n > tty->sync_offset) + n = tty->sync_offset; + + /* Defer until output drains, preserving damage in client flags. */ if (n != 0 || (tty->flags & TTY_BLOCK)) { if (n != 0) log_debug("%s: redraw deferred (%zu left)", c->name, n); @@ -2398,6 +2410,10 @@ server_client_check_redraw(struct client *c) log_debug("redraw timer started"); evtimer_add(&ev, &tv); } + if (damaged) { + c->flags |= CLIENT_REDRAWWINDOW; + return; + } TAILQ_FOREACH(wp, &w->panes, entry) { if (wp->flags & PANE_REDRAW) { c->flags |= CLIENT_REDRAWWINDOW; @@ -2431,6 +2447,10 @@ server_client_check_redraw(struct client *c) redraw_pane_scrollbar(c, wp); } } + + /* Draw damage here if no client redraw flags will handle it. */ + if (damaged && (c->flags & CLIENT_ALLREDRAWFLAGS) == 0) + redraw_client_damage(c); } /* @@ -2444,6 +2464,7 @@ server_client_check_redraw(struct client *c) } server_client_set_progress_bar(c); redraw_screen(c); + redraw_client_damage(c); } /* Put the tty back how it was. */ diff --git a/tmux.h b/tmux.h index 01191a823..fe49d66d4 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1450 2026/10/02 12:28:07 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1451 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -68,6 +68,7 @@ struct options_array_item; struct options_entry; struct prompt; struct window_pane_prompt; +struct redraw_damage; struct redraw_scene; struct redraw_span; struct screen_write_citem; @@ -1372,6 +1373,7 @@ struct window_pane { struct screen base; struct screen status_screen; + u_int status_generation; TAILQ_HEAD(, window_mode_entry) modes; @@ -1402,6 +1404,7 @@ struct window_pane { TAILQ_HEAD(window_panes, window_pane); TAILQ_HEAD(window_panes_zindex, window_pane); RB_HEAD(window_pane_tree, window_pane); +TAILQ_HEAD(redraw_damages, redraw_damage); /* Window structure. */ struct window { @@ -1445,6 +1448,9 @@ struct window { uint64_t redraw_scene_generation; + struct redraw_damages damage; + u_int damage_count; + struct menu_data *menu; u_int menu_last_px; u_int menu_last_py; @@ -1767,6 +1773,8 @@ struct tty { struct event timer; size_t discarded; + size_t sync_offset; + struct termios tio; struct grid_cell cell; @@ -1814,7 +1822,7 @@ struct tty { }; /* Terminal command context. */ -typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *); +typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *, u_int, u_int); typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *); struct tty_ctx { struct screen *s; @@ -3589,8 +3597,12 @@ void redraw_screen(struct client *); void redraw_pane(struct client *, struct window_pane *); void redraw_pane_scrollbar(struct client *, struct window_pane *); void redraw_free_scene(struct redraw_scene *); +int redraw_client_has_window(struct client *, struct window *); void redraw_invalidate_scene(struct window *); void redraw_invalidate_all_scenes(void); +void redraw_damage_window(struct window *, u_int, u_int, u_int, u_int); +void redraw_free_damage(struct window *); +void redraw_client_damage(struct client *); int redraw_get_status_border_cell_type(struct redraw_span **, u_int); /* screen.c */ @@ -3767,6 +3779,8 @@ struct style_range *window_pane_status_get_range(struct window_pane *, u_int, u_int); int window_pane_is_floating(struct window_pane *); int window_pane_is_floating_with_hidden(struct window_pane *); +void window_redraw_floating_pane(struct window_pane *, int, int, + int, int); /* window-border.c */ void window_set_fill_cells(struct window *); diff --git a/tty.c b/tty.c index 3190bd8b0..ac5ceb3e2 100644 --- a/tty.c +++ b/tty.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tty.c,v 1.482 2026/09/22 06:58:06 nicm Exp $ */ +/* $OpenBSD: tty.c,v 1.483 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1110,7 +1110,7 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) */ if (tty_large_region(tty, ctx) || ctx->flags & TTY_CTX_PANE_OBSCURED) { log_debug("%s: %s large region redraw", __func__, c->name); - ctx->redraw_cb(ctx); + ctx->redraw_cb(ctx, ctx->orupper, ctx->orlower - ctx->orupper + 1); return; } @@ -1458,6 +1458,7 @@ tty_sync_start(struct tty *tty) if (tty->flags & TTY_SYNCING) return; tty->flags |= TTY_SYNCING; + tty->sync_offset = EVBUFFER_LENGTH(tty->out); if (tty_term_has(tty->term, TTYC_SYNC)) { log_debug("%s sync start", tty->client->name); @@ -1851,7 +1852,7 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) u_int i, j; if (ctx->flags & TTY_CTX_WINDOW_BIGGER) { - ctx->redraw_cb(ctx); + ctx->redraw_cb(ctx, 0, ctx->sy); return; } @@ -1910,7 +1911,7 @@ tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) tty->cy == tty->rlower) tty_draw_pane(tty, ctx, ctx->ocy); else - ctx->redraw_cb(ctx); + ctx->redraw_cb(ctx, ctx->ocy, 1); return; } diff --git a/window-copy.c b/window-copy.c index fd42e91bd..c6126de38 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-copy.c,v 1.431 2026/09/21 10:43:37 nicm Exp $ */ +/* $OpenBSD: window-copy.c,v 1.432 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -768,6 +768,7 @@ window_copy_scroll(struct window_pane *wp, int sl_mpos, u_int my, struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); if (wme != NULL) { + window_redraw_active_switch(wp->window, wp); window_set_active_pane(wp->window, wp, 0); window_copy_scroll1(wme, wp, sl_mpos, my, tty_oy, scroll_exit); } diff --git a/window.c b/window.c index a2f64ed85..662871313 100644 --- a/window.c +++ b/window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window.c,v 1.383 2026/09/28 10:42:01 nicm Exp $ */ +/* $OpenBSD: window.c,v 1.384 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -425,6 +425,7 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) TAILQ_INIT(&w->panes); TAILQ_INIT(&w->z_index); TAILQ_INIT(&w->last_panes); + TAILQ_INIT(&w->damage); w->active = NULL; w->lastlayout = -1; @@ -478,6 +479,7 @@ window_destroy(struct window *w) menu_destroy(w); window_destroy_panes(w); + redraw_free_damage(w); if (event_initialized(&w->name_event)) evtimer_del(&w->name_event); @@ -747,6 +749,7 @@ int window_set_active_pane(struct window *w, struct window_pane *wp, int notify) { struct window_pane *lastwp; + int unzoomed; log_debug("%s: pane %%%u", __func__, wp->id); @@ -754,7 +757,8 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) return (0); if (w->modal != NULL && wp != w->modal) return (0); - if ((w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp)) + unzoomed = (w->flags & WINDOW_ZOOMED) && !window_pane_is_visible(wp); + if (unzoomed) window_unzoom(w, 1); lastwp = w->active; @@ -771,7 +775,19 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) } tty_update_window_offset(w); - server_redraw_window(w); + + /* + * Unzooming changes every pane's geometry and needs a full window + * redraw. Otherwise, only the previous and new active pane's border + * and status appearance changed, so avoid redrawing unaffected pane + * content. + */ + if (unzoomed) + server_redraw_window(w); + else { + server_redraw_window_borders(w); + server_status_window(w); + } if (notify) window_fire_pane_changed(w, w->active, lastwp); @@ -2960,3 +2976,40 @@ window_pane_is_floating_with_hidden(struct window_pane *wp) return (0); return (1); } + +/* Report damage for a floating pane, including its border and scrollbar. */ +static void +window_damage_floating_pane(struct window_pane *wp, int xoff, int yoff, + int sx, int sy) +{ + struct window *w = wp->window; + int x0, x1, y0, y1, sb_left = 0, sb_right = 0; + struct style *sb_sy = &wp->scrollbar_style; + + if (window_pane_scrollbar_reserve(wp)) { + if (w->sb_pos == PANE_SCROLLBARS_LEFT) + sb_left = sb_sy->width + sb_sy->pad; + else + sb_right = sb_sy->width + sb_sy->pad; + } + x0 = xoff - 1 - sb_left; + x1 = xoff + sx + sb_right; + y0 = yoff - 1; + y1 = yoff + sy; + if (x0 < 0) + x0 = 0; + if (y0 < 0) + y0 = 0; + if (x1 >= x0 && y1 >= y0) + redraw_damage_window(w, x0, y0, x1 - x0 + 1U, y1 - y0 + 1U); +} + +/* Report damage for a floating pane's old and new areas. */ +void +window_redraw_floating_pane(struct window_pane *wp, int oxoff, int oyoff, + int osx, int osy) +{ + window_damage_floating_pane(wp, oxoff, oyoff, osx, osy); + window_damage_floating_pane(wp, wp->xoff, wp->yoff, wp->sx, wp->sy); + server_status_window(wp->window); +}