version 1.34, 2020/05/26 08:41:47 |
version 1.35, 2020/06/01 09:43:01 |
|
|
|
|
#include "tmux.h" |
#include "tmux.h" |
|
|
/* Control client offset. */ |
/* |
struct control_offset { |
* Block of data to output. Each client has one "all" queue of blocks and |
u_int pane; |
* 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; |
TAILQ_ENTRY(control_block) entry; |
int flags; |
TAILQ_ENTRY(control_block) all_entry; |
#define CONTROL_OFFSET_OFF 0x1 |
}; |
|
|
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. */ |
/* Control client state. */ |
struct control_state { |
struct control_state { |
struct control_offsets offsets; |
struct control_panes panes; |
|
|
struct bufferevent *read_event; |
TAILQ_HEAD(, control_pane) pending_list; |
struct bufferevent *write_event; |
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 |
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); |
return (-1); |
if (co1->pane > co2->pane) |
if (cp1->pane > cp2->pane) |
return (1); |
return (1); |
return (0); |
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. */ |
/* Get pane offsets for this client. */ |
static struct control_offset * |
static struct control_pane * |
control_get_offset(struct client *c, struct window_pane *wp) |
control_get_pane(struct client *c, struct window_pane *wp) |
{ |
{ |
struct control_state *cs = c->control_state; |
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. */ |
/* Add pane offsets for this client. */ |
static struct control_offset * |
static struct control_pane * |
control_add_offset(struct client *c, struct window_pane *wp) |
control_add_pane(struct client *c, struct window_pane *wp) |
{ |
{ |
struct control_state *cs = c->control_state; |
struct control_state *cs = c->control_state; |
struct control_offset *co; |
struct control_pane *cp; |
|
|
co = control_get_offset(c, wp); |
cp = control_get_pane(c, wp); |
if (co != NULL) |
if (cp != NULL) |
return (co); |
return (cp); |
|
|
co = xcalloc(1, sizeof *co); |
cp = xcalloc(1, sizeof *cp); |
co->pane = wp->id; |
cp->pane = wp->id; |
RB_INSERT(control_offsets, &cs->offsets, co); |
RB_INSERT(control_panes, &cs->panes, cp); |
memcpy(&co->offset, &wp->offset, sizeof co->offset); |
|
return (co); |
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 |
void |
control_free_offsets(struct client *c) |
control_reset_offsets(struct client *c) |
{ |
{ |
struct control_state *cs = c->control_state; |
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_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) { |
RB_REMOVE(control_offsets, &cs->offsets, co); |
RB_REMOVE(control_panes, &cs->panes, cp); |
free(co); |
free(cp); |
} |
} |
|
|
|
TAILQ_INIT(&cs->pending_list); |
|
cs->pending_count = 0; |
} |
} |
|
|
/* Get offsets for client. */ |
/* Get offsets for client. */ |
struct window_pane_offset * |
struct window_pane_offset * |
control_pane_offset(struct client *c, struct window_pane *wp, int *off) |
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) { |
if (c->flags & CLIENT_CONTROL_NOOUTPUT) { |
*off = 0; |
*off = 0; |
return (NULL); |
return (NULL); |
} |
} |
|
|
co = control_get_offset(c, wp); |
cp = control_get_pane(c, wp); |
if (co == NULL) { |
if (cp == NULL) { |
*off = 0; |
*off = 0; |
return (NULL); |
return (NULL); |
} |
} |
if (co->flags & CONTROL_OFFSET_OFF) { |
if (cp->flags & CONTROL_PANE_OFF) { |
*off = 1; |
*off = 1; |
return (NULL); |
return (NULL); |
} |
} |
return (&co->offset); |
*off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW); |
|
return (&cp->offset); |
} |
} |
|
|
/* Set pane as on. */ |
/* Set pane as on. */ |
void |
void |
control_set_pane_on(struct client *c, struct window_pane *wp) |
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); |
cp = control_get_pane(c, wp); |
if (co != NULL) { |
if (cp != NULL) { |
co->flags &= ~CONTROL_OFFSET_OFF; |
cp->flags &= ~CONTROL_PANE_OFF; |
memcpy(&co->offset, &wp->offset, sizeof co->offset); |
memcpy(&cp->offset, &wp->offset, sizeof cp->offset); |
|
memcpy(&cp->queued, &wp->offset, sizeof cp->queued); |
} |
} |
} |
} |
|
|
|
|
void |
void |
control_set_pane_off(struct client *c, struct window_pane *wp) |
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); |
cp = control_add_pane(c, wp); |
co->flags |= CONTROL_OFFSET_OFF; |
cp->flags |= CONTROL_PANE_OFF; |
} |
} |
|
|
/* Write a line. */ |
/* Write a line. */ |
void |
static void |
control_write(struct client *c, const char *fmt, ...) |
control_vwrite(struct client *c, const char *fmt, va_list ap) |
{ |
{ |
struct control_state *cs = c->control_state; |
struct control_state *cs = c->control_state; |
va_list ap; |
|
char *s; |
char *s; |
|
|
va_start(ap, fmt); |
|
xvasprintf(&s, fmt, ap); |
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, s, strlen(s)); |
bufferevent_write(cs->write_event, "\n", 1); |
bufferevent_write(cs->write_event, "\n", 1); |
|
|
|
bufferevent_enable(cs->write_event, EV_WRITE); |
free(s); |
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. */ |
/* Write output from a pane. */ |
void |
void |
control_write_output(struct client *c, struct window_pane *wp) |
control_write_output(struct client *c, struct window_pane *wp) |
{ |
{ |
struct control_state *cs = c->control_state; |
struct control_state *cs = c->control_state; |
struct control_offset *co; |
struct control_pane *cp; |
struct evbuffer *message; |
struct control_block *cb; |
u_char *new_data; |
size_t new_size; |
size_t new_size, i; |
|
|
|
if (c->flags & CLIENT_CONTROL_NOOUTPUT) |
|
return; |
|
if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) |
if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) |
return; |
return; |
|
|
co = control_add_offset(c, wp); |
if (c->flags & CONTROL_IGNORE_FLAGS) { |
if (co->flags & CONTROL_OFFSET_OFF) { |
cp = control_get_pane(c, wp); |
window_pane_update_used_data(wp, &co->offset, SIZE_MAX, 1); |
if (cp != NULL) |
|
goto ignore; |
return; |
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) |
if (new_size == 0) |
return; |
return; |
|
window_pane_update_used_data(wp, &cp->queued, new_size); |
|
|
message = evbuffer_new(); |
cb = xcalloc(1, sizeof *cb); |
if (message == NULL) |
cb->size = new_size; |
fatalx("out of memory"); |
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); |
evbuffer_add_printf(message, "%%output %%%u ", wp->id); |
|
|
|
for (i = 0; i < new_size; i++) { |
TAILQ_INSERT_TAIL(&cp->blocks, cb, entry); |
if (new_data[i] < ' ' || new_data[i] == '\\') |
log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name, |
evbuffer_add_printf(message, "\\%03o", new_data[i]); |
cb->size, wp->id); |
else |
|
evbuffer_add_printf(message, "%c", new_data[i]); |
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); |
ignore: |
evbuffer_free(message); |
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, &co->offset, new_size, 1); |
window_pane_update_used_data(wp, &cp->queued, SIZE_MAX); |
} |
} |
|
|
/* Control client error callback. */ |
/* Control client error callback. */ |
|
|
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); |
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); |
if (line == NULL) |
if (line == NULL) |
break; |
break; |
log_debug("%s: %s", __func__, line); |
log_debug("%s: %s: %s", __func__, c->name, line); |
if (*line == '\0') { /* empty line exit */ |
if (*line == '\0') { /* empty line detach */ |
free(line); |
free(line); |
c->flags |= CLIENT_EXIT; |
c->flags |= CLIENT_EXIT; |
break; |
break; |
|
|
} |
} |
} |
} |
|
|
|
/* 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. */ |
/* Initialize for control mode. */ |
void |
void |
control_start(struct client *c) |
control_start(struct client *c) |
|
|
setblocking(c->fd, 0); |
setblocking(c->fd, 0); |
|
|
cs = c->control_state = xcalloc(1, sizeof *cs); |
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, |
cs->read_event = bufferevent_new(c->fd, control_read_callback, |
control_error_callback, c); |
control_write_callback, control_error_callback, c); |
bufferevent_enable(cs->read_event, EV_READ); |
bufferevent_enable(cs->read_event, EV_READ); |
|
|
if (c->flags & CLIENT_CONTROLCONTROL) |
if (c->flags & CLIENT_CONTROLCONTROL) |
cs->write_event = cs->read_event; |
cs->write_event = cs->read_event; |
else { |
else { |
cs->write_event = bufferevent_new(c->out_fd, NULL, NULL, |
cs->write_event = bufferevent_new(c->out_fd, NULL, |
control_error_callback, c); |
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) |
if (c->flags & CLIENT_CONTROLCONTROL) { |
control_write(c, "\033P1000p"); |
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. */ |
/* Stop control mode. */ |
void |
void |
control_stop(struct client *c) |
control_stop(struct client *c) |
{ |
{ |
struct control_state *cs = c->control_state; |
struct control_state *cs = c->control_state; |
|
struct control_block *cb, *cb1; |
|
|
if (~c->flags & CLIENT_CONTROLCONTROL) |
if (~c->flags & CLIENT_CONTROLCONTROL) |
bufferevent_free(cs->write_event); |
bufferevent_free(cs->write_event); |
bufferevent_free(cs->read_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); |
free(cs); |
} |
} |