mirror of
https://github.com/tmux/tmux.git
synced 2024-12-04 19:58:48 +00:00
Instead of sending all data to control mode clients as fast as possible,
add a limit of how much data will be sent to the client and try to use it for panes with some degree of fairness. GitHub issue 2217, with George Nachman.
This commit is contained in:
parent
175e45005f
commit
a54a88edd6
16
client.c
16
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);
|
||||
}
|
||||
|
||||
|
@ -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'))
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
474
control.c
474
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);
|
||||
}
|
||||
|
2
input.c
2
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. */
|
||||
|
@ -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);
|
||||
}
|
||||
|
6
server.c
6
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;
|
||||
}
|
||||
|
25
tmux.h
25
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 *,
|
||||
|
@ -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);
|
||||
|
24
window.c
24
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user