From c586208991e4291450757e3a19739f368aecbe5d Mon Sep 17 00:00:00 2001 From: nicm <nicm> Date: Fri, 5 Jun 2020 07:33:57 +0000 Subject: [PATCH] Add support for pausing a pane when the output buffered for a control mode client gets too far behind. The pause-after flag with a time is set on the pane with refresh-client -f and a paused pane may be resumed with refresh-client -A. GitHub issue 2217. --- cmd-refresh-client.c | 4 ++- control.c | 63 +++++++++++++++++++++++++++++++++++--------- resize.c | 16 +++++++---- server-client.c | 40 +++++++++++++++++++++------- tmux.1 | 45 ++++++++++++++++++++++++------- tmux.h | 9 +++++-- 6 files changed, 138 insertions(+), 39 deletions(-) diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 4eb8417b..bbe0c736 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -66,6 +66,8 @@ cmd_refresh_client_update_offset(struct client *tc, const char *value) control_set_pane_on(tc, wp); else if (strcmp(colon, "off") == 0) control_set_pane_off(tc, wp); + else if (strcmp(colon, "continue") == 0) + control_continue_pane(tc, wp); out: free(copy); @@ -168,7 +170,7 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) } tty_set_size(&tc->tty, x, y, 0, 0); tc->flags |= CLIENT_SIZECHANGED; - recalculate_sizes(); + recalculate_sizes_now(1); return (CMD_RETURN_NORMAL); } diff --git a/control.c b/control.c index 1801bd75..b9b22567 100644 --- a/control.c +++ b/control.c @@ -65,6 +65,7 @@ struct control_pane { int flags; #define CONTROL_PANE_OFF 0x1 +#define CONTROL_PANE_PAUSED 0x2 int pending_flag; TAILQ_ENTRY(control_pane) pending_entry; @@ -153,6 +154,19 @@ control_add_pane(struct client *c, struct window_pane *wp) return (cp); } +/* Discard output for a pane. */ +static void +control_discard_pane(struct client *c, struct control_pane *cp) +{ + struct control_state *cs = c->control_state; + struct control_block *cb, *cb1; + + TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { + TAILQ_REMOVE(&cp->blocks, cb, entry); + control_free_block(cs, cb); + } +} + /* Get actual pane for this client. */ static struct window_pane * control_window_pane(struct client *c, u_int pane) @@ -197,7 +211,7 @@ control_pane_offset(struct client *c, struct window_pane *wp, int *off) } cp = control_get_pane(c, wp); - if (cp == NULL) { + if (cp == NULL || (cp->flags & CONTROL_PANE_PAUSED)) { *off = 0; return (NULL); } @@ -216,7 +230,7 @@ control_set_pane_on(struct client *c, struct window_pane *wp) struct control_pane *cp; cp = control_get_pane(c, wp); - if (cp != NULL) { + if (cp != NULL && (cp->flags & CONTROL_PANE_OFF)) { cp->flags &= ~CONTROL_PANE_OFF; memcpy(&cp->offset, &wp->offset, sizeof cp->offset); memcpy(&cp->queued, &wp->offset, sizeof cp->queued); @@ -233,6 +247,21 @@ control_set_pane_off(struct client *c, struct window_pane *wp) cp->flags |= CONTROL_PANE_OFF; } +/* Continue a paused pane. */ +void +control_continue_pane(struct client *c, struct window_pane *wp) +{ + struct control_pane *cp; + + cp = control_get_pane(c, wp); + if (cp != NULL && (cp->flags & CONTROL_PANE_PAUSED)) { + cp->flags &= ~CONTROL_PANE_PAUSED; + memcpy(&cp->offset, &wp->offset, sizeof cp->offset); + memcpy(&cp->queued, &wp->offset, sizeof cp->queued); + control_write(c, "%%continue %%%u", wp->id); + } +} + /* Write a line. */ static void control_vwrite(struct client *c, const char *fmt, va_list ap) @@ -285,6 +314,7 @@ control_write_output(struct client *c, struct window_pane *wp) struct control_pane *cp; struct control_block *cb; size_t new_size; + uint64_t t; if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) return; @@ -296,8 +326,22 @@ control_write_output(struct client *c, struct window_pane *wp) return; } cp = control_add_pane(c, wp); - if (cp->flags & CONTROL_PANE_OFF) + if (cp->flags & (CONTROL_PANE_OFF|CONTROL_PANE_PAUSED)) goto ignore; + if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { + cb = TAILQ_FIRST(&cp->blocks); + if (cb != NULL) { + t = get_timer(); + log_debug("%s: %s: %%%u is %lld behind", __func__, + c->name, wp->id, (long long)t - cb->t); + if (cb->t < t - c->pause_age) { + cp->flags |= CONTROL_PANE_PAUSED; + control_discard_pane(c, cp); + control_write(c, "%%pause %%%u", wp->id); + return; + } + } + } window_pane_get_new_data(wp, &cp->queued, &new_size); if (new_size == 0) @@ -585,20 +629,15 @@ control_start(struct client *c) } } -/* Flush all output for a client that is detaching. */ +/* Discard all output for a client. */ void -control_flush(struct client *c) +control_discard(struct client *c) { struct control_state *cs = c->control_state; struct control_pane *cp; - struct control_block *cb, *cb1; - RB_FOREACH(cp, control_panes, &cs->panes) { - TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { - TAILQ_REMOVE(&cp->blocks, cb, entry); - control_free_block(cs, cb); - } - } + RB_FOREACH(cp, control_panes, &cs->panes) + control_discard_pane(c, cp); } /* Stop control mode. */ diff --git a/resize.c b/resize.c index 68717e35..d6e6dce2 100644 --- a/resize.c +++ b/resize.c @@ -227,7 +227,7 @@ done: } void -recalculate_size(struct window *w) +recalculate_size(struct window *w, int now) { struct session *s; struct client *c; @@ -348,10 +348,10 @@ recalculate_size(struct window *w) break; } if (w->flags & WINDOW_RESIZE) { - if (changed && w->new_sx == sx && w->new_sy == sy) + if (!now && changed && w->new_sx == sx && w->new_sy == sy) changed = 0; } else { - if (changed && w->sx == sx && w->sy == sy) + if (!now && changed && w->sx == sx && w->sy == sy) changed = 0; } @@ -360,7 +360,7 @@ recalculate_size(struct window *w) return; } log_debug("%s: @%u new size %u,%u", __func__, w->id, sx, sy); - if (type == WINDOW_SIZE_MANUAL) + if (now || type == WINDOW_SIZE_MANUAL) resize_window(w, sx, sy, xpixel, ypixel); else { w->new_sx = sx; @@ -375,6 +375,12 @@ recalculate_size(struct window *w) void recalculate_sizes(void) +{ + recalculate_sizes_now(0); +} + +void +recalculate_sizes_now(int now) { struct session *s; struct client *c; @@ -407,5 +413,5 @@ recalculate_sizes(void) /* Walk each window and adjust the size. */ RB_FOREACH(w, windows, &windows) - recalculate_size(w); + recalculate_size(w, now); } diff --git a/server-client.c b/server-client.c index e3383aab..1a02a240 100644 --- a/server-client.c +++ b/server-client.c @@ -1093,7 +1093,7 @@ server_client_update_latest(struct client *c) w->latest = c; if (options_get_number(w->options, "window-size") == WINDOW_SIZE_LATEST) - recalculate_size(w); + recalculate_size(w, 0); } /* @@ -1541,7 +1541,7 @@ server_client_check_pane_buffer(struct window_pane *wp) __func__, c->name, wpo->used - wp->base_offset, new_size, wp->id); if (new_size > SERVER_CLIENT_PANE_LIMIT) { - control_flush(c); + control_discard(c); c->flags |= CLIENT_EXIT; } if (wpo->used < minimum) @@ -1785,7 +1785,7 @@ server_client_check_exit(struct client *c) return; if (c->flags & CLIENT_CONTROL) { - control_flush(c); + control_discard(c); if (!control_all_done(c)) return; } @@ -2362,6 +2362,23 @@ server_client_get_cwd(struct client *c, struct session *s) return ("/"); } +/* Get control client flags. */ +static uint64_t +server_client_control_flags(struct client *c, const char *next) +{ + if (strcmp(next, "pause-after") == 0) { + c->pause_age = 0; + return (CLIENT_CONTROL_PAUSEAFTER); + } + if (sscanf(next, "pause-after=%u", &c->pause_age) == 1) { + c->pause_age *= 1000; + return (CLIENT_CONTROL_PAUSEAFTER); + } + if (strcmp(next, "no-output") == 0) + return (CLIENT_CONTROL_NOOUTPUT); + return (0); +} + /* Set client flags. */ void server_client_set_flags(struct client *c, const char *flags) @@ -2376,11 +2393,10 @@ server_client_set_flags(struct client *c, const char *flags) if (not) next++; - flag = 0; - if (c->flags & CLIENT_CONTROL) { - if (strcmp(next, "no-output") == 0) - flag = CLIENT_CONTROL_NOOUTPUT; - } + if (c->flags & CLIENT_CONTROL) + flag = server_client_control_flags(c, next); + else + flag = 0; if (strcmp(next, "read-only") == 0) flag = CLIENT_READONLY; else if (strcmp(next, "ignore-size") == 0) @@ -2405,7 +2421,8 @@ server_client_set_flags(struct client *c, const char *flags) const char * server_client_get_flags(struct client *c) { - static char s[256]; + static char s[256]; + char tmp[32]; *s = '\0'; if (c->flags & CLIENT_ATTACHED) @@ -2416,6 +2433,11 @@ server_client_get_flags(struct client *c) strlcat(s, "ignore-size,", sizeof s); if (c->flags & CLIENT_CONTROL_NOOUTPUT) strlcat(s, "no-output,", sizeof s); + if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { + xsnprintf(tmp, sizeof tmp, "pause-after=%u,", + c->pause_age / 1000); + strlcat(s, tmp, sizeof s); + } if (c->flags & CLIENT_READONLY) strlcat(s, "read-only,", sizeof s); if (c->flags & CLIENT_ACTIVEPANE) diff --git a/tmux.1 b/tmux.1 index 68d8b3cc..4489c9ae 100644 --- a/tmux.1 +++ b/tmux.1 @@ -977,14 +977,18 @@ detaching the client, typically causing it to exit. sets a comma-separated list of client flags. The flags are: .Bl -tag -width Ds -.It read-only -the client is read-only +.It active-pane +the client has an independent active pane .It ignore-size the client does not affect the size of other clients .It no-output the client does not receive pane output in control mode -.It active-pane -the client has an independent active pane +.It pause-after=seconds +output is paused once the pane is +.Ar seconds +behind in control mode +.It read-only +the client is read-only .El .Pp A leading @@ -1295,22 +1299,27 @@ it. .Fl C sets the width and height of a control mode client. .Fl A -informs -.Nm -of a control mode client's interest in a pane. +allows a control mode client to trigger actions on a pane. The argument is a pane ID (with leading .Ql % ) , a colon, then one of -.Ql on +.Ql on , +.Ql off or -.Ql off . +.Ql continue . If .Ql off , .Nm will not send output from the pane to the client and if all clients have turned the pane off, will stop reading from the pane. +If +.Ql continue , +.Nm +will return to sending output to a paused pane (see the +.Ar pause-after +flag). .Fl A -may be given multiple times. +may be given multiple times for different panes. .Pp .Fl f sets a comma-separated list of client flags, see @@ -3345,6 +3354,10 @@ Allows setting the system clipboard. Allows setting the cursor colour. .It cstyle Allows setting the cursor style. +.It extkeys +Supports extended keys. +.It focus +Supports focus reporting. .It margins Supports DECSLRM margins. .It overline @@ -3353,6 +3366,8 @@ Supports the overline SGR attribute. Supports the DECFRA rectangle fill escape sequence. .It RGB Supports RGB colour with the SGR escape sequences. +.It strikethrough +Supports the strikethrough SGR escape sequence. .It sync Supports synchronized updates. .It title @@ -5881,6 +5896,12 @@ The client is now attached to the session with ID .Ar session-id , which is named .Ar name . +.It Ic %continue Ar pane-id +The pane has been continued after being paused (if the +.Ar pause-after +flag is set, see +.Ic refresh-client +.Fl A ) . .It Ic %exit Op Ar reason The .Nm @@ -5907,6 +5928,10 @@ escapes non-printable characters and backslash as octal \\xxx. The pane with ID .Ar pane-id has changed mode. +.It Ic %pause Ar pane-id +The pane has been paused (if the +.Ar pause-after +flag is set). .It Ic %session-changed Ar session-id Ar name The client is now attached to the session with ID .Ar session-id , diff --git a/tmux.h b/tmux.h index 4523fce4..fa11708c 100644 --- a/tmux.h +++ b/tmux.h @@ -1575,7 +1575,9 @@ struct client { struct cmdq_list *queue; struct client_windows windows; + struct control_state *control_state; + u_int pause_age; pid_t pid; int fd; @@ -1643,6 +1645,7 @@ struct client { #define CLIENT_REDRAWPANES 0x20000000 #define CLIENT_NOFORK 0x40000000 #define CLIENT_ACTIVEPANE 0x80000000ULL +#define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ @@ -2449,8 +2452,9 @@ void status_prompt_save_history(void); void resize_window(struct window *, u_int, u_int, int, int); void default_window_size(struct client *, struct session *, struct window *, u_int *, u_int *, u_int *, u_int *, int); -void recalculate_size(struct window *); +void recalculate_size(struct window *, int); void recalculate_sizes(void); +void recalculate_sizes_now(int); /* input.c */ struct input_ctx *input_init(struct window_pane *, struct bufferevent *); @@ -2837,11 +2841,12 @@ char *default_window_name(struct window *); char *parse_window_name(const char *); /* control.c */ -void control_flush(struct client *); +void control_discard(struct client *); void control_start(struct client *); void control_stop(struct client *); void control_set_pane_on(struct client *, struct window_pane *); void control_set_pane_off(struct client *, struct window_pane *); +void control_continue_pane(struct client *, struct window_pane *); struct window_pane_offset *control_pane_offset(struct client *, struct window_pane *, int *); void control_reset_offsets(struct client *);