server-client: consume window damage during the normal redraw pass

redraw_client_damage() (added previously, unused until now) is called
from server_client_check_redraw()'s normal redraw pass, and
server_client_any_pane_redraw() now also checks for pending window
damage so a client with only damage (no PANE_REDRAW/PANE_REDRAWSCROLLBAR
flags) still gets its redraw pass run.

server_client_check_redraw() now returns whether the redraw was deferred
(waiting for outstanding tty output to drain) rather than performed. A
deferred redraw no longer escalates to a full CLIENT_REDRAWWINDOW to
avoid losing what was pending - server_client_loop() now only clears
PANE_REDRAW, PANE_REDRAWSCROLLBAR and window damage once every client
viewing a window actually drew this pass (tracked via a new per-window
redraw_deferred flag), otherwise they're left in place and retried in
their normal, narrowly-scoped form.

server_client_set_session() now redraws only if the client's session or
current window actually changed, not on every call (e.g. switch-client
-t= from clicking a pane name in the status line resolves here even
when nothing besides the active pane changed).

A drag callback's mouse_drag_update() now opens a sync region itself
(tty_sync_start()) before its first write, so a fast-path write it makes
directly and a later correction arriving via redraw_client_damage() end
up in the same atomic terminal update instead of two visible frames.
This commit is contained in:
Michael Grant
2026-08-22 16:36:25 +01:00
parent f255f089fc
commit 48cdd85886

View File

@@ -38,7 +38,7 @@ static void server_client_repeat_timer(int, short, void *);
static void server_client_click_timer(int, short, void *);
static void server_client_check_exit(struct client *, int);
static void server_client_exit_timer(int, short, void *);
static void server_client_check_redraw(struct client *);
static int server_client_check_redraw(struct client *);
static void server_client_check_modes(struct client *);
static void server_client_set_title(struct client *);
static void server_client_set_path(struct client *);
@@ -472,7 +472,24 @@ 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);
/*
* A full redraw is only needed if the client's session or
* current window actually changed - not if this merely
* confirmed the client is still looking at the same window
* (as happens when switch-client -t targets a pane in the
* already-current window, e.g. clicking a pane name in a
* second #{P:} status line: the default MouseDown1Status
* binding resolves that click to switch-client -t=, which
* reaches here regardless of whether anything besides the
* active pane changed). Redrawing unconditionally here
* forced a full window redraw for what should have been
* just an active-pane change, already handled narrowly by
* window_set_active_pane() and window_redraw_active_switch()
* before this is reached.
*/
if (old == NULL || old != s || old->curw != s->curw)
server_redraw_client(c);
}
server_check_unattached();
@@ -1444,8 +1461,19 @@ server_client_key_callback(struct cmdq_item *item, void *data)
/*
* Mouse drag is in progress, so fire the callback (now that
* the mouse event is valid).
*
* Start a synchronized-output region here rather than
* leaving it to whatever redraw eventually follows: a drag
* callback may write directly via the pane's fast path
* immediately, with any correction only arriving later via
* redraw_client_damage(), which opens its own sync region.
* Since tty_sync_end() is only called once, at the very end
* of this client's pass in server_client_reset_state(),
* starting it here merges both into one atomic terminal
* update instead of two visible frames.
*/
if ((key & KEYC_MASK_KEY) == KEYC_DRAGGING) {
tty_sync_start(&c->tty);
c->tty.mouse_drag_update(c, m);
goto out;
}
@@ -1855,14 +1883,25 @@ server_client_loop(void)
server_client_check_exit(c, 0);
if (c->session != NULL && c->session->curw != NULL) {
server_client_check_modes(c);
server_client_check_redraw(c);
if (server_client_check_redraw(c))
c->session->curw->window->redraw_deferred = 1;
server_client_reset_state(c);
}
}
/*
* Any windows will have been redrawn as part of clients, so clear
* their flags now.
* their flags now. Neither window damage nor PANE_REDRAW/
* PANE_REDRAWSCROLLBAR are lossy to leave for a later pass, so only
* clear them once every client actually viewing the window has
* drawn (none deferred this pass, per the loop above), otherwise
* leave them for the deferred redraw timer to retry. Checking
* EVBUFFER_LENGTH(tty->out) here instead would be wrong: a redraw
* that was NOT deferred still just wrote fresh bytes into that same
* buffer, which libevent has not flushed to the fd yet, so it would
* look identical to a deferred one and nothing would ever clear.
* PANE_ACTIVITY is unrelated bookkeeping (activity monitoring, not
* redraw) and keeps its original unconditional-clear behaviour.
*/
RB_FOREACH(w, windows, &windows) {
TAILQ_FOREACH(wp, &w->panes, entry) {
@@ -1870,9 +1909,16 @@ server_client_loop(void)
server_client_check_pane_resize(wp);
server_client_check_pane_buffer(wp);
}
wp->flags &= ~(PANE_REDRAW|PANE_REDRAWSCROLLBAR|
PANE_ACTIVITY);
wp->flags &= ~PANE_ACTIVITY;
if (!w->redraw_deferred)
wp->flags &= ~(PANE_REDRAW|
PANE_REDRAWSCROLLBAR);
}
if (!w->redraw_deferred)
redraw_free_damage(w);
w->redraw_deferred = 0;
check_window_name(w);
}
@@ -2419,6 +2465,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);
@@ -2426,8 +2474,11 @@ server_client_any_pane_redraw(struct client *c)
return (0);
}
/* Check for client redraws. */
static void
/*
* Check for client redraws. Returns 1 if the redraw was deferred (waiting
* for outstanding tty output to drain) rather than actually performed.
*/
static int
server_client_check_redraw(struct client *c)
{
struct session *s = c->session;
@@ -2440,7 +2491,7 @@ server_client_check_redraw(struct client *c)
size_t n;
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED))
return;
return (0);
if (c->flags & CLIENT_ALLREDRAWFLAGS) {
log_debug("%s: redraw%s%s%s%s%s", c->name,
(c->flags & CLIENT_REDRAWWINDOW) ? " window" : "",
@@ -2458,13 +2509,19 @@ server_client_check_redraw(struct client *c)
needed = 1;
if (!needed) {
c->flags &= ~CLIENT_STATUSFORCE;
return;
return (0);
}
/*
* 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.
* end up back here. Unlike before, this does not need to escalate to
* a full CLIENT_REDRAWWINDOW redraw to avoid losing whatever is
* pending: server_client_loop() only clears PANE_REDRAW,
* PANE_REDRAWSCROLLBAR and window damage once a pass actually drew
* them (see the redraw_deferred handling there), so they are simply
* left in place and retried in their normal, narrowly-scoped form
* next time.
*/
n = EVBUFFER_LENGTH(tty->out);
if (n != 0 || (tty->flags & TTY_BLOCK)) {
@@ -2478,9 +2535,7 @@ server_client_check_redraw(struct client *c)
log_debug("redraw timer started");
evtimer_add(&ev, &tv);
}
if (server_client_any_pane_redraw(c))
c->flags |= CLIENT_REDRAWWINDOW;
return;
return (1);
}
/* Unfreeze the tty and turn off the cursor. */
@@ -2517,6 +2572,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. */
@@ -2531,6 +2587,7 @@ server_client_check_redraw(struct client *c)
c->flags &= ~(CLIENT_ALLREDRAWFLAGS|CLIENT_STATUSFORCE);
c->redraw = EVBUFFER_LENGTH(tty->out);
log_debug("%s: redraw added %zu bytes", c->name, c->redraw);
return (0);
}
/* Set client title. */