Merge remote-tracking branch 'refs/remotes/tmux-openbsd/master'

* refs/remotes/tmux-openbsd/master:
  Sync redraw should use the given rows not the scroll region now.
  Instead of redrawing the entire pane or scene when moving or redrawing a pane, add damage rectangles and redraw only the affected spans. From Michael Grant.
  Do not leak client if lost before configuration is loaded, and do not load multiple times. GitHub issues 5661 and 5662 from Alexandre Fiori.
  Restore the zoom when swap-pane refuses a floating pane, GitHub issue 5660 from Alexandre Fiori.
  Do not use client for copy-mode -S if NULL, reported by Martin Vlach.
This commit is contained in:
tmux update bot
2026-10-02 14:10:43 +00:00
15 changed files with 493 additions and 70 deletions

30
cfg.c
View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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;

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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'));

View File

@@ -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 <tmux@georgester.com>
@@ -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);
}
}

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);
}
}

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);
}

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);
}

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);
}
}
}

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);
}

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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. */

19
tmux.h
View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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 *);

9
tty.c
View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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;
}

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);
}

View File

@@ -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 <nicholas.marriott@gmail.com>
@@ -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);
}