mirror of
https://github.com/tmux/tmux.git
synced 2026-07-22 16:52:42 +00:00
additional payload (to reduce problems with lifetime of objects) and are delivered to one or more event sinks. This is more powerful and reduces the complex dependencies between control mode and hooks. Events are now used for hooks, control mode notifications and for monitors (set-hook -B). wait-for can now wait for an event to fire (-E flag, with -F to for filter), with -v to print the payload, as well as listing (-l) waiting clients on an event and forcing one to wake (-w). A few additional hooks are also now available (pane-created, pane-resized, etc) and some of the existing ones have additional format variables available.
790 lines
20 KiB
C
790 lines
20 KiB
C
/* $OpenBSD: control.c,v 1.60 2026/07/10 07:25:05 nicm Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
|
* Copyright (c) 2012 George Nachman <tmux@georgester.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <event.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/*
|
|
* 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;
|
|
uint64_t t;
|
|
|
|
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
|
|
#define CONTROL_PANE_PAUSED 0x2
|
|
|
|
int pending_flag;
|
|
TAILQ_ENTRY(control_pane) pending_entry;
|
|
|
|
TAILQ_HEAD(, control_block) blocks;
|
|
|
|
RB_ENTRY(control_pane) entry;
|
|
};
|
|
RB_HEAD(control_panes, control_pane);
|
|
|
|
/* Control client state. */
|
|
struct control_state {
|
|
struct control_panes panes;
|
|
|
|
TAILQ_HEAD(, control_pane) pending_list;
|
|
u_int pending_count;
|
|
|
|
TAILQ_HEAD(, control_block) all_blocks;
|
|
|
|
struct bufferevent *read_event;
|
|
struct bufferevent *write_event;
|
|
|
|
struct monitor_set *subs;
|
|
};
|
|
|
|
/* Low and high watermarks. */
|
|
#define CONTROL_BUFFER_LOW 512
|
|
#define CONTROL_BUFFER_HIGH 8192
|
|
|
|
/* Minimum to write to each client. */
|
|
#define CONTROL_WRITE_MINIMUM 32
|
|
|
|
/* Maximum age for clients that are not using pause mode. */
|
|
#define CONTROL_MAXIMUM_AGE 300000
|
|
|
|
/* Flags to ignore client. */
|
|
#define CONTROL_IGNORE_FLAGS \
|
|
(CLIENT_CONTROL_NOOUTPUT| \
|
|
CLIENT_UNATTACHEDFLAGS)
|
|
|
|
/* Compare client panes. */
|
|
static int
|
|
control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
|
|
{
|
|
if (cp1->pane < cp2->pane)
|
|
return (-1);
|
|
if (cp1->pane > cp2->pane)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
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_pane *
|
|
control_get_pane(struct client *c, struct window_pane *wp)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
struct control_pane cp = { .pane = wp->id };
|
|
|
|
return (RB_FIND(control_panes, &cs->panes, &cp));
|
|
}
|
|
|
|
/* Add pane offsets for this client. */
|
|
static struct control_pane *
|
|
control_add_pane(struct client *c, struct window_pane *wp)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
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);
|
|
|
|
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
|
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
|
TAILQ_INIT(&cp->blocks);
|
|
|
|
return (cp);
|
|
}
|
|
|
|
/* Discard output for a pane. */
|
|
static void
|
|
control_discard_pane(struct client *c, struct control_pane *cp)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
struct control_block *cb, *cb1;
|
|
|
|
TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
|
|
TAILQ_REMOVE(&cp->blocks, cb, entry);
|
|
control_free_block(cs, cb);
|
|
}
|
|
}
|
|
|
|
/* Get actual pane for this client. */
|
|
static struct window_pane *
|
|
control_window_pane(struct client *c, u_int pane)
|
|
{
|
|
struct window_pane *wp;
|
|
|
|
if (c->session == NULL)
|
|
return (NULL);
|
|
if ((wp = window_pane_find_by_id(pane)) == NULL)
|
|
return (NULL);
|
|
if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
|
|
return (NULL);
|
|
return (wp);
|
|
}
|
|
|
|
/* Reset control offsets. */
|
|
void
|
|
control_reset_offsets(struct client *c)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
struct control_pane *cp, *cp1;
|
|
|
|
RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) {
|
|
control_discard_pane(c, cp);
|
|
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_state *cs = c->control_state;
|
|
struct control_pane *cp;
|
|
|
|
if (c->flags & CLIENT_CONTROL_NOOUTPUT) {
|
|
*off = 0;
|
|
return (NULL);
|
|
}
|
|
|
|
cp = control_get_pane(c, wp);
|
|
if (cp == NULL || (cp->flags & CONTROL_PANE_PAUSED)) {
|
|
*off = 0;
|
|
return (NULL);
|
|
}
|
|
if (cp->flags & CONTROL_PANE_OFF) {
|
|
*off = 1;
|
|
return (NULL);
|
|
}
|
|
*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_pane *cp;
|
|
|
|
cp = control_get_pane(c, wp);
|
|
if (cp != NULL && (cp->flags & CONTROL_PANE_OFF)) {
|
|
cp->flags &= ~CONTROL_PANE_OFF;
|
|
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
|
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
|
}
|
|
}
|
|
|
|
/* Set pane as off. */
|
|
void
|
|
control_set_pane_off(struct client *c, struct window_pane *wp)
|
|
{
|
|
struct control_pane *cp;
|
|
|
|
cp = control_add_pane(c, wp);
|
|
control_discard_pane(c, cp);
|
|
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
|
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
|
cp->flags |= CONTROL_PANE_OFF;
|
|
}
|
|
|
|
/* Continue a paused pane. */
|
|
void
|
|
control_continue_pane(struct client *c, struct window_pane *wp)
|
|
{
|
|
struct control_pane *cp;
|
|
|
|
cp = control_get_pane(c, wp);
|
|
if (cp != NULL && (cp->flags & CONTROL_PANE_PAUSED)) {
|
|
cp->flags &= ~CONTROL_PANE_PAUSED;
|
|
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
|
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
|
control_write(c, "%%continue %%%u", wp->id);
|
|
}
|
|
}
|
|
|
|
/* Pause a pane. */
|
|
void
|
|
control_pause_pane(struct client *c, struct window_pane *wp)
|
|
{
|
|
struct control_pane *cp;
|
|
|
|
cp = control_add_pane(c, wp);
|
|
if (~cp->flags & CONTROL_PANE_PAUSED) {
|
|
cp->flags |= CONTROL_PANE_PAUSED;
|
|
control_discard_pane(c, cp);
|
|
control_write(c, "%%pause %%%u", wp->id);
|
|
}
|
|
}
|
|
|
|
/* Write a line. */
|
|
static void printflike(2, 0)
|
|
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. */
|
|
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);
|
|
cb->t = get_timer();
|
|
|
|
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
|
|
bufferevent_enable(cs->write_event, EV_WRITE);
|
|
|
|
va_end(ap);
|
|
}
|
|
|
|
/* Check age for this pane. */
|
|
static int
|
|
control_check_age(struct client *c, struct window_pane *wp,
|
|
struct control_pane *cp)
|
|
{
|
|
struct control_block *cb;
|
|
uint64_t t, age;
|
|
|
|
cb = TAILQ_FIRST(&cp->blocks);
|
|
if (cb == NULL)
|
|
return (0);
|
|
t = get_timer();
|
|
if (cb->t >= t)
|
|
return (0);
|
|
|
|
age = t - cb->t;
|
|
log_debug("%s: %s: %%%u is %llu behind", __func__, c->name, wp->id,
|
|
(unsigned long long)age);
|
|
|
|
if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
|
|
if (age < c->pause_age)
|
|
return (0);
|
|
cp->flags |= CONTROL_PANE_PAUSED;
|
|
control_discard_pane(c, cp);
|
|
control_write(c, "%%pause %%%u", wp->id);
|
|
} else {
|
|
if (age < CONTROL_MAXIMUM_AGE)
|
|
return (0);
|
|
c->exit_message = xstrdup("too far behind");
|
|
c->flags |= CLIENT_EXIT;
|
|
control_discard(c);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/* 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_pane *cp;
|
|
struct control_block *cb;
|
|
size_t new_size;
|
|
|
|
if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
|
|
return;
|
|
|
|
if (c->flags & (CONTROL_IGNORE_FLAGS|CLIENT_EXIT)) {
|
|
cp = control_get_pane(c, wp);
|
|
if (cp != NULL)
|
|
goto ignore;
|
|
return;
|
|
}
|
|
cp = control_add_pane(c, wp);
|
|
if (cp->flags & (CONTROL_PANE_OFF|CONTROL_PANE_PAUSED))
|
|
goto ignore;
|
|
if (control_check_age(c, wp, cp))
|
|
return;
|
|
|
|
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);
|
|
|
|
cb = xcalloc(1, sizeof *cb);
|
|
cb->size = new_size;
|
|
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
|
|
cb->t = get_timer();
|
|
|
|
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++;
|
|
}
|
|
bufferevent_enable(cs->write_event, EV_WRITE);
|
|
return;
|
|
|
|
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. */
|
|
static enum cmd_retval
|
|
control_error(struct cmdq_item *item, void *data)
|
|
{
|
|
struct client *c = cmdq_get_client(item);
|
|
char *error = data;
|
|
|
|
cmdq_guard(item, "begin", 1);
|
|
control_write(c, "parse error: %s", error);
|
|
cmdq_guard(item, "error", 1);
|
|
|
|
free(error);
|
|
return (CMD_RETURN_NORMAL);
|
|
}
|
|
|
|
/* Control client error callback. */
|
|
static void
|
|
control_error_callback(__unused struct bufferevent *bufev,
|
|
__unused short what, void *data)
|
|
{
|
|
struct client *c = data;
|
|
|
|
c->flags |= CLIENT_EXIT;
|
|
}
|
|
|
|
/* Control client input callback. Read lines and fire commands. */
|
|
static void
|
|
control_read_callback(__unused struct bufferevent *bufev, void *data)
|
|
{
|
|
struct client *c = data;
|
|
struct control_state *cs = c->control_state;
|
|
struct evbuffer *buffer = cs->read_event->input;
|
|
char *line, *error;
|
|
struct cmdq_state *state;
|
|
enum cmd_parse_status status;
|
|
|
|
for (;;) {
|
|
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
|
|
if (line == NULL)
|
|
break;
|
|
log_debug("%s: %s: %s", __func__, c->name, line);
|
|
if (*line == '\0') { /* empty line detach */
|
|
free(line);
|
|
c->flags |= CLIENT_EXIT;
|
|
break;
|
|
}
|
|
|
|
state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
|
|
status = cmd_parse_and_append(line, NULL, c, state, &error);
|
|
if (status == CMD_PARSE_ERROR)
|
|
cmdq_append(c, cmdq_get_callback(control_error, error));
|
|
cmdq_free_state(state);
|
|
|
|
free(line);
|
|
}
|
|
}
|
|
|
|
/* 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 client *c, struct control_pane *cp, uint64_t age,
|
|
struct evbuffer *message, struct window_pane *wp, size_t size)
|
|
{
|
|
u_char *new_data;
|
|
size_t new_size, start;
|
|
u_int i;
|
|
|
|
if (message == NULL) {
|
|
message = evbuffer_new();
|
|
if (message == NULL)
|
|
fatalx("out of memory");
|
|
if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
|
|
evbuffer_add_printf(message,
|
|
"%%extended-output %%%u %llu : ", wp->id,
|
|
(unsigned long long)age);
|
|
} else
|
|
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 {
|
|
start = i;
|
|
while (i + 1 < size &&
|
|
new_data[i + 1] >= ' ' &&
|
|
new_data[i + 1] != '\\')
|
|
i++;
|
|
evbuffer_add(message, new_data + start, i - start + 1);
|
|
}
|
|
}
|
|
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 window_pane *wp = NULL;
|
|
struct evbuffer *message = NULL;
|
|
size_t used = 0, size;
|
|
struct control_block *cb, *cb1;
|
|
uint64_t age, t = get_timer();
|
|
|
|
wp = control_window_pane(c, cp->pane);
|
|
if (wp == NULL || wp->fd == -1) {
|
|
TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
|
|
TAILQ_REMOVE(&cp->blocks, cb, entry);
|
|
control_free_block(cs, cb);
|
|
}
|
|
control_flush_all_blocks(c);
|
|
return (0);
|
|
}
|
|
|
|
while (used != limit && !TAILQ_EMPTY(&cp->blocks)) {
|
|
if (control_check_age(c, wp, cp)) {
|
|
if (message != NULL)
|
|
evbuffer_free(message);
|
|
message = NULL;
|
|
break;
|
|
}
|
|
|
|
cb = TAILQ_FIRST(&cp->blocks);
|
|
if (cb->t < t)
|
|
age = t - cb->t;
|
|
else
|
|
age = 0;
|
|
log_debug("%s: %s: output block %zu (age %llu) for %%%u "
|
|
"(used %zu/%zu)", __func__, c->name, cb->size,
|
|
(unsigned long long)age, cp->pane, used, limit);
|
|
|
|
size = cb->size;
|
|
if (size > limit - used)
|
|
size = limit - used;
|
|
used += size;
|
|
|
|
message = control_append_data(c, cp, age, 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);
|
|
}
|
|
|
|
/* Write a subscription change. */
|
|
static void
|
|
control_sub_change(struct monitor_change *change, __unused void *data)
|
|
{
|
|
struct client *c = change->c;
|
|
struct session *s = change->s;
|
|
struct winlink *wl = change->wl;
|
|
struct window_pane *wp = change->wp;
|
|
struct window *w;
|
|
|
|
if (wp != NULL) {
|
|
w = wp->window;
|
|
control_write(c, "%%subscription-changed %s $%u @%u %u %%%u : %s",
|
|
change->name, s->id, w->id, wl->idx, wp->id, change->value);
|
|
} else if (wl != NULL) {
|
|
w = wl->window;
|
|
control_write(c, "%%subscription-changed %s $%u @%u %u - : %s",
|
|
change->name, s->id, w->id, wl->idx, change->value);
|
|
} else {
|
|
control_write(c, "%%subscription-changed %s $%u - - - : %s",
|
|
change->name, s->id, change->value);
|
|
}
|
|
}
|
|
|
|
/* Initialize for control mode. */
|
|
void
|
|
control_start(struct client *c)
|
|
{
|
|
struct control_state *cs;
|
|
|
|
if (c->flags & CLIENT_CONTROLCONTROL) {
|
|
close(c->out_fd);
|
|
c->out_fd = -1;
|
|
} else
|
|
setblocking(c->out_fd, 0);
|
|
setblocking(c->fd, 0);
|
|
|
|
cs = c->control_state = xcalloc(1, sizeof *cs);
|
|
RB_INIT(&cs->panes);
|
|
TAILQ_INIT(&cs->pending_list);
|
|
TAILQ_INIT(&cs->all_blocks);
|
|
cs->subs = monitor_create_client(c, control_sub_change, NULL);
|
|
|
|
cs->read_event = bufferevent_new(c->fd, control_read_callback,
|
|
control_write_callback, control_error_callback, c);
|
|
if (cs->read_event == NULL)
|
|
fatalx("out of memory");
|
|
|
|
if (c->flags & CLIENT_CONTROLCONTROL)
|
|
cs->write_event = cs->read_event;
|
|
else {
|
|
cs->write_event = bufferevent_new(c->out_fd, NULL,
|
|
control_write_callback, control_error_callback, c);
|
|
if (cs->write_event == NULL)
|
|
fatalx("out of memory");
|
|
}
|
|
bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW,
|
|
0);
|
|
|
|
if (c->flags & CLIENT_CONTROLCONTROL) {
|
|
bufferevent_write(cs->write_event, "\033P1000p", 7);
|
|
bufferevent_enable(cs->write_event, EV_WRITE);
|
|
}
|
|
}
|
|
|
|
/* Control client ready. */
|
|
void
|
|
control_ready(struct client *c)
|
|
{
|
|
bufferevent_enable(c->control_state->read_event, EV_READ);
|
|
}
|
|
|
|
/* Discard all output for a client. */
|
|
void
|
|
control_discard(struct client *c)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
struct control_pane *cp;
|
|
|
|
RB_FOREACH(cp, control_panes, &cs->panes)
|
|
control_discard_pane(c, cp);
|
|
bufferevent_disable(cs->read_event, EV_READ);
|
|
}
|
|
|
|
/* Stop control mode. */
|
|
void
|
|
control_stop(struct client *c)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
struct control_block *cb, *cb1;
|
|
|
|
if (cs == NULL)
|
|
return;
|
|
|
|
monitor_destroy(cs->subs);
|
|
|
|
if (~c->flags & CLIENT_CONTROLCONTROL)
|
|
bufferevent_free(cs->write_event);
|
|
bufferevent_free(cs->read_event);
|
|
|
|
control_reset_offsets(c);
|
|
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
|
control_free_block(cs, cb);
|
|
|
|
c->control_state = NULL;
|
|
free(cs);
|
|
}
|
|
|
|
/* Add a subscription. */
|
|
void
|
|
control_add_sub(struct client *c, const char *name, enum monitor_type type,
|
|
int id, const char *format)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
|
|
monitor_add(cs->subs, name, type, id, format, MONITOR_NOTIFY_INITIAL);
|
|
}
|
|
|
|
/* Remove a subscription. */
|
|
void
|
|
control_remove_sub(struct client *c, const char *name)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
|
|
monitor_remove(cs->subs, name);
|
|
}
|