diff --git a/client.c b/client.c index d1a31de7..be9dccbf 100644 --- a/client.c +++ b/client.c @@ -384,6 +384,11 @@ client_main(struct event_base *base, int argc, char **argv, int flags, int feat) client_exec(client_execshell, client_execcmd); } + /* Restore streams to blocking. */ + setblocking(STDIN_FILENO, 1); + setblocking(STDOUT_FILENO, 1); + setblocking(STDERR_FILENO, 1); + /* Print the exit message, if any, and exit. */ if (client_attached) { if (client_exitreason != CLIENT_EXIT_NONE) @@ -392,18 +397,17 @@ client_main(struct event_base *base, int argc, char **argv, int flags, int feat) ppid = getppid(); if (client_exittype == MSG_DETACHKILL && ppid > 1) kill(ppid, SIGHUP); - } else if (client_flags & CLIENT_CONTROLCONTROL) { + } else if (client_flags & CLIENT_CONTROL) { if (client_exitreason != CLIENT_EXIT_NONE) printf("%%exit %s\n", client_exit_message()); else printf("%%exit\n"); - printf("\033\\"); - tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio); + if (client_flags & CLIENT_CONTROLCONTROL) { + printf("\033\\"); + tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio); + } } else if (client_exitreason != CLIENT_EXIT_NONE) fprintf(stderr, "%s\n", client_exit_message()); - setblocking(STDIN_FILENO, 1); - setblocking(STDOUT_FILENO, 1); - setblocking(STDERR_FILENO, 1); return (client_exitval); } diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 588b0fd5..6f37bc8f 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -214,15 +214,20 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); if (args_has(args, 'p')) { - if (!file_can_print(c)) { - cmdq_error(item, "can't write output to client"); - free(buf); - return (CMD_RETURN_ERROR); - } - file_print_buffer(c, buf, len); - if (args_has(args, 'P') && len > 0) + if (len > 0 && buf[len - 1] == '\n') + len--; + if (c->flags & CLIENT_CONTROL) + control_write(c, "%.*s", (int)len, buf); + else { + if (!file_can_print(c)) { + cmdq_error(item, "can't write to client"); + free(buf); + return (CMD_RETURN_ERROR); + } + file_print_buffer(c, buf, len); file_print(c, "\n"); - free(buf); + free(buf); + } } else { bufname = NULL; if (args_has(args, 'b')) diff --git a/cmd-new-session.c b/cmd-new-session.c index be29122d..cc3494de 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -165,7 +165,10 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) * the terminal as that calls tcsetattr() to prepare for tmux taking * over. */ - if (!detached && !already_attached && c->fd != -1) { + if (!detached && + !already_attached && + c->fd != -1 && + (~c->flags & CLIENT_CONTROL)) { if (server_client_check_nested(cmdq_get_client(item))) { cmdq_error(item, "sessions should be nested with care, " "unset $TMUX to force"); diff --git a/cmd-queue.c b/cmd-queue.c index b0c70428..693f7d90 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -780,7 +780,7 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags) u_int number = item->number; if (c != NULL && (c->flags & CLIENT_CONTROL)) - file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags); + control_write(c, "%%%s %ld %u %d", guard, t, number, flags); } /* Show message from command. */ @@ -807,7 +807,10 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) msg = utf8_sanitize(tmp); free(tmp); } - file_print(c, "%s\n", msg); + if (c->flags & CLIENT_CONTROL) + control_write(c, "%s", msg); + else + file_print(c, "%s\n", msg); } else { wp = server_client_get_pane(c); wme = TAILQ_FIRST(&wp->modes); @@ -849,7 +852,7 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) free(tmp); } if (c->flags & CLIENT_CONTROL) - file_print(c, "%s\n", msg); + control_write(c, "%s", msg); else file_error(c, "%s\n", msg); c->retval = 1; diff --git a/control.c b/control.c index 7e1a869b..c1e0b7eb 100644 --- a/control.c +++ b/control.c @@ -27,112 +27,183 @@ #include "tmux.h" -/* Control client offset. */ -struct control_offset { - u_int pane; +/* + * Block of data to output. Each client has one "all" queue of blocks and + * another queue for each pane (in struct client_offset). %output blocks are + * added to both queues and other output lines (notifications) added only to + * the client queue. + * + * When a client becomes writeable, data from blocks on the pane queue are sent + * up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written, + * it is removed from both pane and client queues and if this means non-%output + * blocks are now at the head of the client queue, they are written. + * + * This means a %output block holds up any subsequent non-%output blocks until + * it is written which enforces ordering even if the client cannot accept the + * entire block in one go. + */ +struct control_block { + size_t size; + char *line; - struct window_pane_offset offset; - int flags; -#define CONTROL_OFFSET_OFF 0x1 + TAILQ_ENTRY(control_block) entry; + TAILQ_ENTRY(control_block) all_entry; + }; - RB_ENTRY(control_offset) entry; +/* Control client pane. */ +struct control_pane { + u_int pane; + + /* + * Offsets into the pane data. The first (offset) is the data we have + * written; the second (queued) the data we have queued (pointed to by + * a block). + */ + struct window_pane_offset offset; + struct window_pane_offset queued; + + int flags; +#define CONTROL_PANE_OFF 0x1 + + int pending_flag; + TAILQ_ENTRY(control_pane) pending_entry; + + TAILQ_HEAD(, control_block) blocks; + + RB_ENTRY(control_pane) entry; }; -RB_HEAD(control_offsets, control_offset); +RB_HEAD(control_panes, control_pane); /* Control client state. */ struct control_state { - struct control_offsets offsets; + struct control_panes panes; - struct bufferevent *read_event; - struct bufferevent *write_event; + TAILQ_HEAD(, control_pane) pending_list; + u_int pending_count; + + TAILQ_HEAD(, control_block) all_blocks; + + struct bufferevent *read_event; + struct bufferevent *write_event; }; -/* Compare client offsets. */ +/* Low watermark. */ +#define CONTROL_BUFFER_LOW 512 +#define CONTROL_BUFFER_HIGH 8192 + +/* Minimum to write to each client. */ +#define CONTROL_WRITE_MINIMUM 32 + +/* Flags to ignore client. */ +#define CONTROL_IGNORE_FLAGS \ + (CLIENT_CONTROL_NOOUTPUT| \ + CLIENT_UNATTACHEDFLAGS) + +/* Compare client panes. */ static int -control_offset_cmp(struct control_offset *co1, struct control_offset *co2) +control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2) { - if (co1->pane < co2->pane) + if (cp1->pane < cp2->pane) return (-1); - if (co1->pane > co2->pane) + if (cp1->pane > cp2->pane) return (1); return (0); } -RB_GENERATE_STATIC(control_offsets, control_offset, entry, control_offset_cmp); +RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp); + +/* Free a block. */ +static void +control_free_block(struct control_state *cs, struct control_block *cb) +{ + free(cb->line); + TAILQ_REMOVE(&cs->all_blocks, cb, all_entry); + free(cb); +} /* Get pane offsets for this client. */ -static struct control_offset * -control_get_offset(struct client *c, struct window_pane *wp) +static struct control_pane * +control_get_pane(struct client *c, struct window_pane *wp) { struct control_state *cs = c->control_state; - struct control_offset co = { .pane = wp->id }; + struct control_pane cp = { .pane = wp->id }; - return (RB_FIND(control_offsets, &cs->offsets, &co)); + return (RB_FIND(control_panes, &cs->panes, &cp)); } /* Add pane offsets for this client. */ -static struct control_offset * -control_add_offset(struct client *c, struct window_pane *wp) +static struct control_pane * +control_add_pane(struct client *c, struct window_pane *wp) { struct control_state *cs = c->control_state; - struct control_offset *co; + struct control_pane *cp; - co = control_get_offset(c, wp); - if (co != NULL) - return (co); + cp = control_get_pane(c, wp); + if (cp != NULL) + return (cp); - co = xcalloc(1, sizeof *co); - co->pane = wp->id; - RB_INSERT(control_offsets, &cs->offsets, co); - memcpy(&co->offset, &wp->offset, sizeof co->offset); - return (co); + cp = xcalloc(1, sizeof *cp); + cp->pane = wp->id; + RB_INSERT(control_panes, &cs->panes, cp); + + memcpy(&cp->offset, &wp->offset, sizeof cp->offset); + memcpy(&cp->queued, &wp->offset, sizeof cp->queued); + TAILQ_INIT(&cp->blocks); + + return (cp); } -/* Free control offsets. */ +/* Reset control offsets. */ void -control_free_offsets(struct client *c) +control_reset_offsets(struct client *c) { struct control_state *cs = c->control_state; - struct control_offset *co, *co1; + struct control_pane *cp, *cp1; - RB_FOREACH_SAFE(co, control_offsets, &cs->offsets, co1) { - RB_REMOVE(control_offsets, &cs->offsets, co); - free(co); + RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) { + RB_REMOVE(control_panes, &cs->panes, cp); + free(cp); } + + TAILQ_INIT(&cs->pending_list); + cs->pending_count = 0; } /* Get offsets for client. */ struct window_pane_offset * control_pane_offset(struct client *c, struct window_pane *wp, int *off) { - struct control_offset *co; + struct control_state *cs = c->control_state; + struct control_pane *cp; if (c->flags & CLIENT_CONTROL_NOOUTPUT) { *off = 0; return (NULL); } - co = control_get_offset(c, wp); - if (co == NULL) { + cp = control_get_pane(c, wp); + if (cp == NULL) { *off = 0; return (NULL); } - if (co->flags & CONTROL_OFFSET_OFF) { + if (cp->flags & CONTROL_PANE_OFF) { *off = 1; return (NULL); } - return (&co->offset); + *off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW); + return (&cp->offset); } /* Set pane as on. */ void control_set_pane_on(struct client *c, struct window_pane *wp) { - struct control_offset *co; + struct control_pane *cp; - co = control_get_offset(c, wp); - if (co != NULL) { - co->flags &= ~CONTROL_OFFSET_OFF; - memcpy(&co->offset, &wp->offset, sizeof co->offset); + cp = control_get_pane(c, wp); + if (cp != NULL) { + cp->flags &= ~CONTROL_PANE_OFF; + memcpy(&cp->offset, &wp->offset, sizeof cp->offset); + memcpy(&cp->queued, &wp->offset, sizeof cp->queued); } } @@ -140,10 +211,27 @@ control_set_pane_on(struct client *c, struct window_pane *wp) void control_set_pane_off(struct client *c, struct window_pane *wp) { - struct control_offset *co; + struct control_pane *cp; - co = control_add_offset(c, wp); - co->flags |= CONTROL_OFFSET_OFF; + cp = control_add_pane(c, wp); + cp->flags |= CONTROL_PANE_OFF; +} + +/* Write a line. */ +static void +control_vwrite(struct client *c, const char *fmt, va_list ap) +{ + struct control_state *cs = c->control_state; + char *s; + + xvasprintf(&s, fmt, ap); + log_debug("%s: %s: writing line: %s", __func__, c->name, s); + + bufferevent_write(cs->write_event, s, strlen(s)); + bufferevent_write(cs->write_event, "\n", 1); + + bufferevent_enable(cs->write_event, EV_WRITE); + free(s); } /* Write a line. */ @@ -151,16 +239,25 @@ void control_write(struct client *c, const char *fmt, ...) { struct control_state *cs = c->control_state; + struct control_block *cb; va_list ap; - char *s; va_start(ap, fmt); - xvasprintf(&s, fmt, ap); - va_end(ap); - bufferevent_write(cs->write_event, s, strlen(s)); - bufferevent_write(cs->write_event, "\n", 1); - free(s); + if (TAILQ_EMPTY(&cs->all_blocks)) { + control_vwrite(c, fmt, ap); + va_end(ap); + return; + } + + cb = xcalloc(1, sizeof *cb); + xvasprintf(&cb->line, fmt, ap); + TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); + + log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line); + bufferevent_enable(cs->write_event, EV_WRITE); + + va_end(ap); } /* Write output from a pane. */ @@ -168,42 +265,50 @@ void control_write_output(struct client *c, struct window_pane *wp) { struct control_state *cs = c->control_state; - struct control_offset *co; - struct evbuffer *message; - u_char *new_data; - size_t new_size, i; + struct control_pane *cp; + struct control_block *cb; + size_t new_size; - if (c->flags & CLIENT_CONTROL_NOOUTPUT) - return; if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) return; - co = control_add_offset(c, wp); - if (co->flags & CONTROL_OFFSET_OFF) { - window_pane_update_used_data(wp, &co->offset, SIZE_MAX, 1); + if (c->flags & CONTROL_IGNORE_FLAGS) { + cp = control_get_pane(c, wp); + if (cp != NULL) + goto ignore; return; } - new_data = window_pane_get_new_data(wp, &co->offset, &new_size); + cp = control_add_pane(c, wp); + if (cp->flags & CONTROL_PANE_OFF) + goto ignore; + + window_pane_get_new_data(wp, &cp->queued, &new_size); if (new_size == 0) return; + window_pane_update_used_data(wp, &cp->queued, new_size); - message = evbuffer_new(); - if (message == NULL) - fatalx("out of memory"); - evbuffer_add_printf(message, "%%output %%%u ", wp->id); + cb = xcalloc(1, sizeof *cb); + cb->size = new_size; + TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); - for (i = 0; i < new_size; i++) { - if (new_data[i] < ' ' || new_data[i] == '\\') - evbuffer_add_printf(message, "\\%03o", new_data[i]); - else - evbuffer_add_printf(message, "%c", new_data[i]); + TAILQ_INSERT_TAIL(&cp->blocks, cb, entry); + log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name, + cb->size, wp->id); + + if (!cp->pending_flag) { + log_debug("%s: %s: %%%u now pending", __func__, c->name, + wp->id); + TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry); + cp->pending_flag = 1; + cs->pending_count++; } - evbuffer_add(message, "\n", 1); + bufferevent_enable(cs->write_event, EV_WRITE); + return; - bufferevent_write_buffer(cs->write_event, message); - evbuffer_free(message); - - window_pane_update_used_data(wp, &co->offset, new_size, 1); +ignore: + log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id); + window_pane_update_used_data(wp, &cp->offset, SIZE_MAX); + window_pane_update_used_data(wp, &cp->queued, SIZE_MAX); } /* Control client error callback. */ @@ -246,8 +351,8 @@ control_read_callback(__unused struct bufferevent *bufev, void *data) line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); if (line == NULL) break; - log_debug("%s: %s", __func__, line); - if (*line == '\0') { /* empty line exit */ + log_debug("%s: %s: %s", __func__, c->name, line); + if (*line == '\0') { /* empty line detach */ free(line); c->flags |= CLIENT_EXIT; break; @@ -263,6 +368,168 @@ control_read_callback(__unused struct bufferevent *bufev, void *data) } } +/* Does this control client have outstanding data to write? */ +int +control_all_done(struct client *c) +{ + struct control_state *cs = c->control_state; + + if (!TAILQ_EMPTY(&cs->all_blocks)) + return (0); + return (EVBUFFER_LENGTH(cs->write_event->output) == 0); +} + +/* Flush all blocks until output. */ +static void +control_flush_all_blocks(struct client *c) +{ + struct control_state *cs = c->control_state; + struct control_block *cb, *cb1; + + TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) { + if (cb->size != 0) + break; + log_debug("%s: %s: flushing line: %s", __func__, c->name, + cb->line); + + bufferevent_write(cs->write_event, cb->line, strlen(cb->line)); + bufferevent_write(cs->write_event, "\n", 1); + control_free_block(cs, cb); + } +} + +/* Append data to buffer. */ +static struct evbuffer * +control_append_data(struct control_pane *cp, struct evbuffer *message, + struct window_pane *wp, size_t size) +{ + u_char *new_data; + size_t new_size; + u_int i; + + if (message == NULL) { + message = evbuffer_new(); + if (message == NULL) + fatalx("out of memory"); + evbuffer_add_printf(message, "%%output %%%u ", wp->id); + } + + new_data = window_pane_get_new_data(wp, &cp->offset, &new_size); + if (new_size < size) + fatalx("not enough data: %zu < %zu", new_size, size); + for (i = 0; i < size; i++) { + if (new_data[i] < ' ' || new_data[i] == '\\') + evbuffer_add_printf(message, "\\%03o", new_data[i]); + else + evbuffer_add_printf(message, "%c", new_data[i]); + } + window_pane_update_used_data(wp, &cp->offset, size); + return (message); +} + +/* Write buffer. */ +static void +control_write_data(struct client *c, struct evbuffer *message) +{ + struct control_state *cs = c->control_state; + + log_debug("%s: %s: %.*s", __func__, c->name, + (int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message)); + + evbuffer_add(message, "\n", 1); + bufferevent_write_buffer(cs->write_event, message); + evbuffer_free(message); +} + +/* Write output to client. */ +static int +control_write_pending(struct client *c, struct control_pane *cp, size_t limit) +{ + struct control_state *cs = c->control_state; + struct session *s = c->session; + struct window_pane *wp = NULL; + struct evbuffer *message = NULL; + size_t used = 0, size; + struct control_block *cb, *cb1; + + if (s == NULL || + (wp = window_pane_find_by_id(cp->pane)) == NULL || + winlink_find_by_window(&s->windows, wp->window) == NULL) { + TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) + control_free_block(cs, cb); + control_flush_all_blocks(c); + return (0); + } + + while (used != limit && !TAILQ_EMPTY(&cp->blocks)) { + cb = TAILQ_FIRST(&cp->blocks); + log_debug("%s: %s: output block %zu for %%%u (used %zu/%zu)", + __func__, c->name, cb->size, cp->pane, used, limit); + + size = cb->size; + if (size > limit - used) + size = limit - used; + used += size; + + message = control_append_data(cp, message, wp, size); + + cb->size -= size; + if (cb->size == 0) { + TAILQ_REMOVE(&cp->blocks, cb, entry); + control_free_block(cs, cb); + + cb = TAILQ_FIRST(&cs->all_blocks); + if (cb != NULL && cb->size == 0) { + if (wp != NULL && message != NULL) { + control_write_data(c, message); + message = NULL; + } + control_flush_all_blocks(c); + } + } + } + if (message != NULL) + control_write_data(c, message); + return (!TAILQ_EMPTY(&cp->blocks)); +} + +/* Control client write callback. */ +static void +control_write_callback(__unused struct bufferevent *bufev, void *data) +{ + struct client *c = data; + struct control_state *cs = c->control_state; + struct control_pane *cp, *cp1; + struct evbuffer *evb = cs->write_event->output; + size_t space, limit; + + control_flush_all_blocks(c); + + while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) { + if (cs->pending_count == 0) + break; + space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb); + log_debug("%s: %s: %zu bytes available, %u panes", __func__, + c->name, space, cs->pending_count); + + limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */ + if (limit < CONTROL_WRITE_MINIMUM) + limit = CONTROL_WRITE_MINIMUM; + + TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) { + if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH) + break; + if (control_write_pending(c, cp, limit)) + continue; + TAILQ_REMOVE(&cs->pending_list, cp, pending_entry); + cp->pending_flag = 0; + cs->pending_count--; + } + } + if (EVBUFFER_LENGTH(evb) == 0) + bufferevent_disable(cs->write_event, EV_WRITE); +} + /* Initialize for control mode. */ void control_start(struct client *c) @@ -277,22 +544,43 @@ control_start(struct client *c) setblocking(c->fd, 0); cs = c->control_state = xcalloc(1, sizeof *cs); - RB_INIT(&cs->offsets); + RB_INIT(&cs->panes); + TAILQ_INIT(&cs->pending_list); + TAILQ_INIT(&cs->all_blocks); - cs->read_event = bufferevent_new(c->fd, control_read_callback, NULL, - control_error_callback, c); + cs->read_event = bufferevent_new(c->fd, control_read_callback, + control_write_callback, control_error_callback, c); bufferevent_enable(cs->read_event, EV_READ); if (c->flags & CLIENT_CONTROLCONTROL) cs->write_event = cs->read_event; else { - cs->write_event = bufferevent_new(c->out_fd, NULL, NULL, - control_error_callback, c); + cs->write_event = bufferevent_new(c->out_fd, NULL, + control_write_callback, control_error_callback, c); } - bufferevent_enable(cs->write_event, EV_WRITE); + bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW, + 0); - if (c->flags & CLIENT_CONTROLCONTROL) - control_write(c, "\033P1000p"); + if (c->flags & CLIENT_CONTROLCONTROL) { + bufferevent_write(cs->write_event, "\033P1000p", 7); + bufferevent_enable(cs->write_event, EV_WRITE); + } +} + +/* Flush all output for a client that is detaching. */ +void +control_flush(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); + } + } } /* Stop control mode. */ @@ -300,11 +588,15 @@ void control_stop(struct client *c) { struct control_state *cs = c->control_state; + struct control_block *cb, *cb1; if (~c->flags & CLIENT_CONTROLCONTROL) bufferevent_free(cs->write_event); bufferevent_free(cs->read_event); - control_free_offsets(c); + TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) + control_free_block(cs, cb); + control_reset_offsets(c); + free(cs); } diff --git a/input.c b/input.c index 7bb69663..46d8734d 100644 --- a/input.c +++ b/input.c @@ -947,7 +947,7 @@ input_parse_pane(struct window_pane *wp) new_data = window_pane_get_new_data(wp, &wp->offset, &new_size); input_parse_buffer(wp, new_data, new_size); - window_pane_update_used_data(wp, &wp->offset, new_size, 1); + window_pane_update_used_data(wp, &wp->offset, new_size); } /* Parse given input. */ diff --git a/server-client.c b/server-client.c index d4fa92dc..5218340e 100644 --- a/server-client.c +++ b/server-client.c @@ -58,6 +58,9 @@ static void server_client_dispatch_read_data(struct client *, static void server_client_dispatch_read_done(struct client *, struct imsg *); +/* Maximum data allowed to be held for a pane for a control client. */ +#define SERVER_CLIENT_PANE_LIMIT 16777216 + /* Compare client windows. */ static int server_client_window_cmp(struct client_window *cw1, @@ -80,7 +83,7 @@ server_client_how_many(void) n = 0; TAILQ_FOREACH(c, &clients, entry) { - if (c->session != NULL && (~c->flags & CLIENT_DETACHING)) + if (c->session != NULL && (~c->flags & CLIENT_UNATTACHEDFLAGS)) n++; } return (n); @@ -386,7 +389,7 @@ server_client_suspend(struct client *c) { struct session *s = c->session; - if (s == NULL || (c->flags & CLIENT_DETACHING)) + if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; tty_stop_tty(&c->tty); @@ -400,12 +403,14 @@ server_client_detach(struct client *c, enum msgtype msgtype) { struct session *s = c->session; - if (s == NULL || (c->flags & CLIENT_DETACHING)) + if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; - c->flags |= CLIENT_DETACHING; - notify_client("client-detached", c); - proc_send(c->peer, msgtype, -1, s->name, strlen(s->name) + 1); + c->flags |= CLIENT_EXIT; + + c->exit_type = CLIENT_EXIT_DETACH; + c->exit_msgtype = msgtype; + c->exit_session = xstrdup(s->name); } /* Execute command to replace a client. */ @@ -1507,12 +1512,12 @@ server_client_check_pane_buffer(struct window_pane *wp) u_int attached_clients = 0; /* - * Work out the minimum acknowledged size. This is the most that can be - * removed from the buffer. + * Work out the minimum used size. This is the most that can be removed + * from the buffer. */ - minimum = wp->offset.acknowledged; - if (wp->pipe_fd != -1 && wp->pipe_offset.acknowledged < minimum) - minimum = wp->pipe_offset.acknowledged; + minimum = wp->offset.used; + if (wp->pipe_fd != -1 && wp->pipe_offset.used < minimum) + minimum = wp->pipe_offset.used; TAILQ_FOREACH(c, &clients, entry) { if (c->session == NULL) continue; @@ -1530,11 +1535,13 @@ server_client_check_pane_buffer(struct window_pane *wp) if (!flag) off = 0; - log_debug("%s: %s has %zu bytes used, %zu bytes acknowledged " - "for %%%u", __func__, c->name, wpo->used, wpo->acknowledged, - wp->id); - if (wpo->acknowledged < minimum) - minimum = wpo->acknowledged; + log_debug("%s: %s has %zu bytes used for %%%u", __func__, + c->name, wpo->used - wp->base_offset, wp->id); + if (wpo->used - wp->base_offset > SERVER_CLIENT_PANE_LIMIT) { + control_flush(c); + c->flags |= CLIENT_EXIT; + } else if (wpo->used < minimum) + minimum = wpo->used; } if (attached_clients == 0) off = 0; @@ -1543,8 +1550,8 @@ server_client_check_pane_buffer(struct window_pane *wp) goto out; /* Drain the buffer. */ - log_debug("%s: %%%u has %zu minimum (of %zu) bytes acknowledged", - __func__, wp->id, minimum, EVBUFFER_LENGTH(evb)); + log_debug("%s: %%%u has %zu minimum (of %zu) bytes used", __func__, + wp->id, minimum, EVBUFFER_LENGTH(evb)); evbuffer_drain(evb, minimum); /* @@ -1553,20 +1560,15 @@ server_client_check_pane_buffer(struct window_pane *wp) */ if (wp->base_offset > SIZE_MAX - minimum) { log_debug("%s: %%%u base offset has wrapped", __func__, wp->id); - wp->offset.acknowledged -= wp->base_offset; wp->offset.used -= wp->base_offset; - if (wp->pipe_fd != -1) { - wp->pipe_offset.acknowledged -= wp->base_offset; + if (wp->pipe_fd != -1) wp->pipe_offset.used -= wp->base_offset; - } TAILQ_FOREACH(c, &clients, entry) { if (c->session == NULL || (~c->flags & CLIENT_CONTROL)) continue; wpo = control_pane_offset(c, wp, &flag); - if (wpo != NULL && !flag) { - wpo->acknowledged -= wp->base_offset; + if (wpo != NULL && !flag) wpo->used -= wp->base_offset; - } } wp->base_offset = minimum; } else @@ -1579,6 +1581,7 @@ out: * clients, all of which are control clients which are not able to * accept any more data. */ + log_debug("%s: pane %%%u is %s", __func__, wp->id, off ? "off" : "on"); if (off) bufferevent_disable(wp->event, EV_READ); else @@ -1770,12 +1773,16 @@ static void server_client_check_exit(struct client *c) { struct client_file *cf; + const char *name = c->exit_session; - if (~c->flags & CLIENT_EXIT) - return; - if (c->flags & CLIENT_EXITED) + if ((c->flags & CLIENT_EXITED) || (~c->flags & CLIENT_EXIT)) return; + if (c->flags & CLIENT_CONTROL) { + control_flush(c); + if (!control_all_done(c)) + return; + } RB_FOREACH(cf, client_files, &c->files) { if (EVBUFFER_LENGTH(cf->buffer) != 0) return; @@ -1783,8 +1790,20 @@ server_client_check_exit(struct client *c) if (c->flags & CLIENT_ATTACHED) notify_client("client-detached", c); - proc_send(c->peer, MSG_EXIT, -1, &c->retval, sizeof c->retval); c->flags |= CLIENT_EXITED; + + switch (c->exit_type) { + case CLIENT_EXIT_RETURN: + proc_send(c->peer, MSG_EXIT, -1, &c->retval, sizeof c->retval); + break; + case CLIENT_EXIT_SHUTDOWN: + proc_send(c->peer, MSG_SHUTDOWN, -1, NULL, 0); + break; + case CLIENT_EXIT_DETACH: + proc_send(c->peer, c->exit_msgtype, -1, name, strlen(name) + 1); + break; + } + free(c->exit_session); } /* Redraw timer callback. */ @@ -1996,7 +2015,6 @@ server_client_dispatch(struct imsg *imsg, void *arg) case MSG_EXITING: if (datalen != 0) fatalx("bad MSG_EXITING size"); - c->session = NULL; tty_close(&c->tty); proc_send(c->peer, MSG_EXITED, -1, NULL, 0); @@ -2050,7 +2068,7 @@ server_client_command_done(struct cmdq_item *item, __unused void *data) if (~c->flags & CLIENT_ATTACHED) c->flags |= CLIENT_EXIT; - else if (~c->flags & CLIENT_DETACHING) + else if (~c->flags & CLIENT_EXIT) tty_send_requests(&c->tty); return (CMD_RETURN_NORMAL); } @@ -2372,7 +2390,7 @@ server_client_set_flags(struct client *c, const char *flags) else c->flags |= flag; if (flag == CLIENT_CONTROL_NOOUTPUT) - control_free_offsets(c); + control_reset_offsets(c); } free(copy); } diff --git a/server.c b/server.c index f07479ae..67d47f23 100644 --- a/server.c +++ b/server.c @@ -199,6 +199,7 @@ server_start(struct tmuxproc *client, int flags, struct event_base *base, "tty ps", NULL) != 0) fatal("pledge failed"); + input_key_build(); RB_INIT(&windows); RB_INIT(&all_window_panes); TAILQ_INIT(&clients); @@ -294,9 +295,8 @@ server_send_exit(void) if (c->flags & CLIENT_SUSPENDED) server_client_lost(c); else { - if (c->flags & CLIENT_ATTACHED) - notify_client("client-detached", c); - proc_send(c->peer, MSG_SHUTDOWN, -1, NULL, 0); + c->flags |= CLIENT_EXIT; + c->exit_type = CLIENT_EXIT_SHUTDOWN; } c->session = NULL; } diff --git a/tmux.h b/tmux.h index 4187ddd8..a14d4ac2 100644 --- a/tmux.h +++ b/tmux.h @@ -85,9 +85,6 @@ struct winlink; /* Automatic name refresh interval, in microseconds. Must be < 1 second. */ #define NAME_INTERVAL 500000 -/* Maximum size of data to hold from a pane. */ -#define READ_SIZE 8192 - /* Default pixel cell sizes. */ #define DEFAULT_XPIXEL 16 #define DEFAULT_YPIXEL 32 @@ -915,7 +912,6 @@ struct window_mode_entry { /* Offsets into pane buffer. */ struct window_pane_offset { size_t used; - size_t acknowledged; }; /* Child window structure. */ @@ -1627,7 +1623,7 @@ struct client { #define CLIENT_DEAD 0x200 #define CLIENT_REDRAWBORDERS 0x400 #define CLIENT_READONLY 0x800 -#define CLIENT_DETACHING 0x1000 +/* 0x1000 unused */ #define CLIENT_CONTROL 0x2000 #define CLIENT_CONTROLCONTROL 0x4000 #define CLIENT_FOCUSED 0x8000 @@ -1657,12 +1653,21 @@ struct client { #define CLIENT_UNATTACHEDFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ - CLIENT_DETACHING) + CLIENT_EXIT) #define CLIENT_NOSIZEFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ - CLIENT_DETACHING) + CLIENT_EXIT) uint64_t flags; + + enum { + CLIENT_EXIT_RETURN, + CLIENT_EXIT_SHUTDOWN, + CLIENT_EXIT_DETACH + } exit_type; + enum msgtype exit_msgtype; + char *exit_session; + struct key_table *keytable; uint64_t redraw_panes; @@ -2712,8 +2717,6 @@ int window_pane_start_input(struct window_pane *, void *window_pane_get_new_data(struct window_pane *, struct window_pane_offset *, size_t *); void window_pane_update_used_data(struct window_pane *, - struct window_pane_offset *, size_t, int); -void window_pane_acknowledge_data(struct window_pane *, struct window_pane_offset *, size_t); /* layout.c */ @@ -2829,15 +2832,17 @@ char *default_window_name(struct window *); char *parse_window_name(const char *); /* control.c */ +void control_flush(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 *); struct window_pane_offset *control_pane_offset(struct client *, struct window_pane *, int *); -void control_free_offsets(struct client *); +void control_reset_offsets(struct client *); void printflike(2, 3) control_write(struct client *, const char *, ...); void control_write_output(struct client *, struct window_pane *); +int control_all_done(struct client *); /* control-notify.c */ void control_notify_input(struct client *, struct window_pane *, diff --git a/window-client.c b/window-client.c index 159efa5a..5e02462b 100644 --- a/window-client.c +++ b/window-client.c @@ -166,7 +166,7 @@ window_client_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, data->item_size = 0; TAILQ_FOREACH(c, &clients, entry) { - if (c->session == NULL || (c->flags & (CLIENT_DETACHING))) + if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) continue; item = window_client_add_item(data); diff --git a/window.c b/window.c index c7a17551..020ae4bf 100644 --- a/window.c +++ b/window.c @@ -944,16 +944,17 @@ window_pane_read_callback(__unused struct bufferevent *bufev, void *data) new_data = window_pane_get_new_data(wp, wpo, &new_size); if (new_size > 0) { bufferevent_write(wp->pipe_event, new_data, new_size); - window_pane_update_used_data(wp, wpo, new_size, 1); + window_pane_update_used_data(wp, wpo, new_size); } } log_debug("%%%u has %zu bytes", wp->id, size); TAILQ_FOREACH(c, &clients, entry) { - if (c->session != NULL && c->flags & CLIENT_CONTROL) + if (c->session != NULL && (c->flags & CLIENT_CONTROL)) control_write_output(c, wp); } input_parse_pane(wp); + bufferevent_disable(wp->event, EV_READ); } static void @@ -978,7 +979,6 @@ window_pane_set_event(struct window_pane *wp) NULL, window_pane_error_callback, wp); wp->ictx = input_init(wp, wp->event); - bufferevent_setwatermark(wp->event, EV_READ, 0, READ_SIZE); bufferevent_enable(wp->event, EV_READ|EV_WRITE); } @@ -1559,27 +1559,11 @@ window_pane_get_new_data(struct window_pane *wp, void window_pane_update_used_data(struct window_pane *wp, - struct window_pane_offset *wpo, size_t size, int acknowledge) + struct window_pane_offset *wpo, size_t size) { size_t used = wpo->used - wp->base_offset; if (size > EVBUFFER_LENGTH(wp->event->input) - used) size = EVBUFFER_LENGTH(wp->event->input) - used; wpo->used += size; - - if (acknowledge) - window_pane_acknowledge_data(wp, wpo, size); -} - -void -window_pane_acknowledge_data(struct window_pane *wp, - struct window_pane_offset *wpo, size_t size) -{ - size_t acknowledged = wpo->acknowledged - wp->base_offset; - - if (size > EVBUFFER_LENGTH(wp->event->input) - acknowledged) - size = EVBUFFER_LENGTH(wp->event->input) - acknowledged; - wpo->acknowledged += size; - if (wpo->acknowledged > wpo->used) - wpo->acknowledged = wpo->used; }