summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client.c16
-rw-r--r--cmd-capture-pane.c19
-rw-r--r--cmd-new-session.c5
-rw-r--r--cmd-queue.c9
-rw-r--r--control.c476
-rw-r--r--input.c2
-rw-r--r--server-client.c82
-rw-r--r--server.c5
-rw-r--r--tmux.h25
-rw-r--r--window-client.c2
-rw-r--r--window.c24
11 files changed, 489 insertions, 176 deletions
diff --git a/client.c b/client.c
index f57f4df6..7a55ffa1 100644
--- a/client.c
+++ b/client.c
@@ -386,6 +386,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)
@@ -394,18 +399,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");
+ 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);
- return (CMD_RETURN_ERROR);
}
- file_print_buffer(c, buf, len);
- if (args_has(args, 'P') && len > 0)
- file_print(c, "\n");
- 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;
+
+ TAILQ_ENTRY(control_block) entry;
+ TAILQ_ENTRY(control_block) all_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
- struct window_pane_offset offset;
- int flags;
-#define CONTROL_OFFSET_OFF 0x1
+ int pending_flag;
+ TAILQ_ENTRY(control_pane) pending_entry;
- RB_ENTRY(control_offset) 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;
+
+ TAILQ_HEAD(, control_pane) pending_list;
+ u_int pending_count;
- struct bufferevent *read_event;
- struct bufferevent *write_event;
+ 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;
+
+ cp = control_get_pane(c, wp);
+ if (cp != NULL)
+ return (cp);
+
+ cp = xcalloc(1, sizeof *cp);
+ cp->pane = wp->id;
+ RB_INSERT(control_panes, &cs->panes, cp);
- co = control_get_offset(c, wp);
- if (co != NULL)
- return (co);
+ memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
+ memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
+ TAILQ_INIT(&cp->blocks);
- 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);
+ 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,70 +211,104 @@ 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. */
-void
-control_write(struct client *c, const char *fmt, ...)
+static void
+control_vwrite(struct client *c, const char *fmt, va_list ap)
{
struct control_state *cs = c->control_state;
- va_list ap;
char *s;
- va_start(ap, fmt);
xvasprintf(&s, fmt, ap);
- va_end(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. */
+void
+control_write(struct client *c, const char *fmt, ...)
+{
+ struct control_state *cs = c->control_state;
+ struct control_block *cb;
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ 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. */
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;
-
- message = evbuffer_new();
- if (message == NULL)
- fatalx("out of memory");
- evbuffer_add_printf(message, "%%output %%%u ", wp->id);
-
- 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]);
+ window_pane_update_used_data(wp, &cp->queued, new_size);
+
+ cb = xcalloc(1, sizeof *cb);
+ cb->size = new_size;
+ TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
+
+ 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_write_buffer(cs->write_event, message);
- evbuffer_free(message);
+ bufferevent_enable(cs->write_event, EV_WRITE);
+ return;
- 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 ac2272fc..b3394e65 100644
--- a/server-client.c
+++ b/server-client.c
@@ -56,6 +56,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,
@@ -78,7 +81,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);
@@ -384,7 +387,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);
@@ -398,12 +401,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. */
@@ -1505,12 +1510,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;
@@ -1528,11 +1533,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;
@@ -1541,8 +1548,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);
/*
@@ -1551,20 +1558,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
@@ -1577,6 +1579,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
@@ -1768,12 +1771,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;
@@ -1781,8 +1788,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. */
@@ -1994,7 +2013,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);
@@ -2048,7 +2066,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);
}
@@ -2374,7 +2392,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 5613ddea..073f3f90 100644
--- a/server.c
+++ b/server.c
@@ -294,9 +294,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 500272e7..2f441730 100644
--- a/tmux.h
+++ b/tmux.h
@@ -87,9 +87,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
@@ -917,7 +914,6 @@ struct window_mode_entry {
/* Offsets into pane buffer. */
struct window_pane_offset {
size_t used;
- size_t acknowledged;
};
/* Child window structure. */
@@ -1629,7 +1625,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
@@ -1659,12 +1655,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;
@@ -2714,8 +2719,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 */
@@ -2831,15 +2834,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/