diff --git a/cfg.c b/cfg.c index 78d164363..31294f92a 100644 --- a/cfg.c +++ b/cfg.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cfg.c,v 1.91 2026/08/03 13:38:42 nicm Exp $ */ +/* $OpenBSD: cfg.c,v 1.92 2026/10/02 12:28:07 nicm Exp $ */ /* * Copyright (c) 2008 Nicholas Marriott @@ -31,14 +31,17 @@ int cfg_finished; static char **cfg_causes; static u_int cfg_ncauses; static struct cmdq_item *cfg_item; +static int cfg_started; int cfg_quiet = 1; char **cfg_files; u_int cfg_nfiles; static enum cmd_retval -cfg_client_done(__unused struct cmdq_item *item, __unused void *data) +cfg_client_done(struct cmdq_item *item, __unused void *data) { + if (cmdq_get_client(item)->flags & CLIENT_DEAD) + return (CMD_RETURN_NORMAL); if (!cfg_finished) return (CMD_RETURN_WAIT); return (CMD_RETURN_NORMAL); @@ -53,14 +56,28 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data) cfg_show_causes(NULL); - if (cfg_item != NULL) + if (cfg_item != NULL) { cmdq_continue(cfg_item); + cfg_item = NULL; + } prompt_load_history(); return (CMD_RETURN_NORMAL); } +void +cfg_client_lost(struct client *c) +{ + if (c != cfg_client) + return; + cfg_client = NULL; + if (cfg_item != NULL) { + cmdq_continue(cfg_item); + cfg_item = NULL; + } +} + void start_cfg(void) { @@ -77,12 +94,19 @@ start_cfg(void) * Because start_cfg() is called so early, we can be sure the client's * command queue is currently empty and our callback will be at the * front - we need to get in before MSG_COMMAND. + * + * If the initial client is lost before the configuration finishes, the + * next client to identify is first in the list and ends up here. Block + * it instead, but do not load the files again. */ cfg_client = c = TAILQ_FIRST(&clients); if (c != NULL) { cfg_item = cmdq_get_callback(cfg_client_done, NULL); cmdq_append(c, cfg_item); } + if (cfg_started) + return; + cfg_started = 1; if (cfg_quiet) flags = CMD_PARSE_QUIET; 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-copy-mode.c b/cmd-copy-mode.c index e6888ca7e..28a1a742e 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-copy-mode.c,v 1.54 2026/07/14 17:17:17 nicm Exp $ */ +/* $OpenBSD: cmd-copy-mode.c,v 1.55 2026/10/02 12:12:51 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -103,6 +103,8 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'd')) window_copy_pagedown(wp, 0, args_has(args, 'e')); if (args_has(args, 'S')) { + if (c == NULL) + return (CMD_RETURN_NORMAL); tty_window_offset(&c->tty, &tty_ox, &tty_oy, &tty_sx, &tty_sy); window_copy_scroll(wp, c->tty.mouse_slider_mpos, event->m.y, tty_oy, args_has(args, 'e')); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 44d79cafb..89db90d75 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 @@ -307,7 +307,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) { @@ -329,10 +329,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 641792e2c..4949b1298 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 @@ -362,6 +362,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; @@ -417,8 +418,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/cmd-swap-pane.c b/cmd-swap-pane.c index 04e1cc7a9..1d0664cd6 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-swap-pane.c,v 1.55 2026/07/15 13:02:33 nicm Exp $ */ +/* $OpenBSD: cmd-swap-pane.c,v 1.56 2026/10/02 12:23:44 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -88,7 +88,7 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'D')) { if (window_pane_is_floating(dst_wp)) { cmdq_error(item, "cannot swap down on floating pane"); - return (CMD_RETURN_ERROR); + goto error; } src_w = dst_w; src_wp = TAILQ_NEXT(dst_wp, entry); @@ -100,7 +100,7 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) } else if (args_has(args, 'U')) { if (window_pane_is_floating(dst_wp)) { cmdq_error(item, "cannot swap up on floating pane"); - return (CMD_RETURN_ERROR); + goto error; } src_w = dst_w; src_wp = TAILQ_PREV(dst_wp, window_panes, entry); @@ -202,4 +202,9 @@ out: if (src_w != dst_w && window_pop_zoom(dst_w)) server_redraw_window(dst_w); return (CMD_RETURN_NORMAL); + +error: + if (window_pop_zoom(dst_w)) + server_redraw_window(dst_w); + return (CMD_RETURN_ERROR); } 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 01cdd1177..e4fb93281 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; @@ -1842,7 +1978,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; @@ -1868,3 +2004,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 c3f34ff61..02552f5b4 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.296 2026/10/02 12:53:26 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. */ @@ -1376,11 +1388,11 @@ screen_write_sync_scroll_dirty(struct screen_write_ctx *ctx) /* Redraw the scrolled lines for a client which cannot scroll them. */ static void -screen_write_sync_redraw_cb(const struct tty_ctx *ttyctx) +screen_write_sync_redraw_cb(const struct tty_ctx *ttyctx, u_int py, u_int ny) { struct window_pane *wp = ttyctx->arg; - bit_nset(wp->sync_dirty, wp->sync_rupper, wp->sync_rlower); + bit_nset(wp->sync_dirty, py, py + ny - 1); } /* Send the deferred scroll to the client. */ @@ -2272,7 +2284,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. */ @@ -3290,7 +3302,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. */ @@ -3315,5 +3327,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 4a29055c8..f97b7516b 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.515 2026/09/28 11:25:51 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.517 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -356,7 +356,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(); @@ -369,8 +375,7 @@ server_client_lost(struct client *c) { struct client_file *cf, *cf1; - if (cfg_client == c) - cfg_client = NULL; + cfg_client_lost(c); c->flags |= CLIENT_DEAD; status_prompt_clear(c); @@ -1349,10 +1354,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; } @@ -1779,8 +1785,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) { @@ -1791,6 +1797,8 @@ server_client_loop(void) wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR| PANE_ACTIVITY); } + redraw_free_damage(w); + check_window_name(w); } @@ -2336,6 +2344,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); @@ -2352,6 +2362,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; @@ -2377,12 +2388,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); @@ -2394,6 +2405,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; @@ -2427,6 +2442,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); } /* @@ -2440,6 +2459,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 dee768686..f03100936 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1449 2026/09/28 10:10:16 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1451 2026/10/02 12:48:52 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -69,6 +69,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; @@ -1406,6 +1407,7 @@ struct window_pane { struct screen base; struct screen status_screen; + u_int status_generation; TAILQ_HEAD(, window_mode_entry) modes; @@ -1436,6 +1438,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 { @@ -1479,6 +1482,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; @@ -1804,6 +1810,8 @@ struct tty { struct event timer; size_t discarded; + size_t sync_offset; + struct termios tio; struct grid_cell cell; @@ -1851,7 +1859,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; @@ -2611,6 +2619,7 @@ extern char **cfg_files; extern u_int cfg_nfiles; extern int cfg_quiet; void start_cfg(void); +void cfg_client_lost(struct client *); int load_cfg(const char *, struct client *, struct cmdq_item *, struct cmd_find_state *, int, struct cmdq_item **); int load_cfg_from_buffer(const void *, size_t, const char *, @@ -3638,8 +3647,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 */ @@ -3816,6 +3829,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 11330d99d..38fe5bf51 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 @@ -1115,7 +1115,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; } @@ -1517,6 +1517,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); @@ -1923,7 +1924,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; } @@ -1982,7 +1983,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 515c2ffc6..ae79e2e90 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 @@ -423,6 +423,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; @@ -476,6 +477,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); @@ -754,6 +756,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); @@ -761,7 +764,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; @@ -778,7 +782,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); @@ -2971,3 +2987,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); +}