root/usr.bin/tmux/control.c
/* $OpenBSD: control.c,v 1.52 2026/03/09 14:33:55 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);

/* Subscription pane. */
struct control_sub_pane {
        u_int                            pane;
        u_int                            idx;
        char                            *last;

        RB_ENTRY(control_sub_pane)       entry;
};
RB_HEAD(control_sub_panes, control_sub_pane);

/* Subscription window. */
struct control_sub_window {
        u_int                            window;
        u_int                            idx;
        char                            *last;

        RB_ENTRY(control_sub_window)     entry;
};
RB_HEAD(control_sub_windows, control_sub_window);

/* Control client subscription. */
struct control_sub {
        char                            *name;
        char                            *format;

        enum control_sub_type            type;
        u_int                            id;

        char                            *last;
        struct control_sub_panes         panes;
        struct control_sub_windows       windows;

        RB_ENTRY(control_sub)            entry;
};
RB_HEAD(control_subs, control_sub);

/* 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 control_subs              subs;
        struct event                     subs_timer;
};

/* 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);

/* Compare client subs. */
static int
control_sub_cmp(struct control_sub *csub1, struct control_sub *csub2)
{
        return (strcmp(csub1->name, csub2->name));
}
RB_GENERATE_STATIC(control_subs, control_sub, entry, control_sub_cmp);

/* Compare client subscription panes. */
static int
control_sub_pane_cmp(struct control_sub_pane *csp1,
    struct control_sub_pane *csp2)
{
        if (csp1->pane < csp2->pane)
                return (-1);
        if (csp1->pane > csp2->pane)
                return (1);
        if (csp1->idx < csp2->idx)
                return (-1);
        if (csp1->idx > csp2->idx)
                return (1);
        return (0);
}
RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry,
    control_sub_pane_cmp);

/* Compare client subscription windows. */
static int
control_sub_window_cmp(struct control_sub_window *csw1,
    struct control_sub_window *csw2)
{
        if (csw1->window < csw2->window)
                return (-1);
        if (csw1->window > csw2->window)
                return (1);
        if (csw1->idx < csw2->idx)
                return (-1);
        if (csw1->idx > csw2->idx)
                return (1);
        return (0);
}
RB_GENERATE_STATIC(control_sub_windows, control_sub_window, entry,
    control_sub_window_cmp);

/* Free a subscription. */
static void
control_free_sub(struct control_state *cs, struct control_sub *csub)
{
        struct control_sub_pane         *csp, *csp1;
        struct control_sub_window       *csw, *csw1;

        RB_FOREACH_SAFE(csp, control_sub_panes, &csub->panes, csp1) {
                RB_REMOVE(control_sub_panes, &csub->panes, csp);
                free(csp);
        }
        RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) {
                RB_REMOVE(control_sub_windows, &csub->windows, csw);
                free(csw);
        }
        free(csub->last);

        RB_REMOVE(control_subs, &cs->subs, csub);
        free(csub->name);
        free(csub->format);
        free(csub);
}

/* 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) {
                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);
        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) {
                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);
}

/* 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);
        RB_INIT(&cs->subs);

        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;
        struct control_sub      *csub, *csub1;

        if (~c->flags & CLIENT_CONTROLCONTROL)
                bufferevent_free(cs->write_event);
        bufferevent_free(cs->read_event);

        RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1)
                control_free_sub(cs, csub);
        if (evtimer_initialized(&cs->subs_timer))
                evtimer_del(&cs->subs_timer);

        TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
                control_free_block(cs, cb);
        control_reset_offsets(c);

        free(cs);
}

/* Check session subscription. */
static void
control_check_subs_session(struct client *c, struct control_sub *csub,
    struct format_tree *ft)
{
        struct session          *s = c->session;
        char                    *value;

        value = format_expand(ft, csub->format);

        if (csub->last != NULL && strcmp(value, csub->last) == 0) {
                free(value);
                return;
        }
        control_write(c,
            "%%subscription-changed %s $%u - - - : %s",
            csub->name, s->id, value);
        free(csub->last);
        csub->last = value;
}

/* Check pane subscription. */
static void
control_check_subs_pane(struct client *c, struct control_sub *csub)
{
        struct session          *s = c->session;
        struct window_pane      *wp;
        struct window           *w;
        struct winlink          *wl;
        struct format_tree      *ft;
        char                    *value;
        struct control_sub_pane *csp, find;

        wp = window_pane_find_by_id(csub->id);
        if (wp == NULL || wp->fd == -1)
                return;
        w = wp->window;

        TAILQ_FOREACH(wl, &w->winlinks, wentry) {
                if (wl->session != s)
                        continue;

                ft = format_create_defaults(NULL, c, s, wl, wp);
                value = format_expand(ft, csub->format);
                format_free(ft);

                find.pane = wp->id;
                find.idx = wl->idx;

                csp = RB_FIND(control_sub_panes, &csub->panes, &find);
                if (csp == NULL) {
                        csp = xcalloc(1, sizeof *csp);
                        csp->pane = wp->id;
                        csp->idx = wl->idx;
                        RB_INSERT(control_sub_panes, &csub->panes, csp);
                }

                if (csp->last != NULL && strcmp(value, csp->last) == 0) {
                        free(value);
                        continue;
                }
                control_write(c,
                    "%%subscription-changed %s $%u @%u %u %%%u : %s",
                    csub->name, s->id, w->id, wl->idx, wp->id, value);
                free(csp->last);
                csp->last = value;
        }
}

/* Check all-panes subscription for a pane. */
static void
control_check_subs_all_panes_one(struct client *c, struct control_sub *csub,
    struct format_tree *ft, struct winlink *wl, struct window_pane *wp)
{
        struct session          *s = c->session;
        struct window           *w = wl->window;
        char                    *value;
        struct control_sub_pane *csp, find;

        value = format_expand(ft, csub->format);

        find.pane = wp->id;
        find.idx = wl->idx;

        csp = RB_FIND(control_sub_panes, &csub->panes, &find);
        if (csp == NULL) {
                csp = xcalloc(1, sizeof *csp);
                csp->pane = wp->id;
                csp->idx = wl->idx;
                RB_INSERT(control_sub_panes, &csub->panes, csp);
        }

        if (csp->last != NULL && strcmp(value, csp->last) == 0) {
                free(value);
                return;
        }
        control_write(c,
            "%%subscription-changed %s $%u @%u %u %%%u : %s",
            csub->name, s->id, w->id, wl->idx, wp->id, value);
        free(csp->last);
        csp->last = value;
}

/* Check window subscription. */
static void
control_check_subs_window(struct client *c, struct control_sub *csub)
{
        struct session                  *s = c->session;
        struct window                   *w;
        struct winlink                  *wl;
        struct format_tree              *ft;
        char                            *value;
        struct control_sub_window       *csw, find;

        w = window_find_by_id(csub->id);
        if (w == NULL)
                return;

        TAILQ_FOREACH(wl, &w->winlinks, wentry) {
                if (wl->session != s)
                        continue;

                ft = format_create_defaults(NULL, c, s, wl, NULL);
                value = format_expand(ft, csub->format);
                format_free(ft);

                find.window = w->id;
                find.idx = wl->idx;

                csw = RB_FIND(control_sub_windows, &csub->windows, &find);
                if (csw == NULL) {
                        csw = xcalloc(1, sizeof *csw);
                        csw->window = w->id;
                        csw->idx = wl->idx;
                        RB_INSERT(control_sub_windows, &csub->windows, csw);
                }

                if (csw->last != NULL && strcmp(value, csw->last) == 0) {
                        free(value);
                        continue;
                }
                control_write(c,
                    "%%subscription-changed %s $%u @%u %u - : %s",
                    csub->name, s->id, w->id, wl->idx, value);
                free(csw->last);
                csw->last = value;
        }
}

/* Check all-windows subscription for a window. */
static void
control_check_subs_all_windows_one(struct client *c, struct control_sub *csub,
    struct format_tree *ft, struct winlink *wl)
{
        struct session                  *s = c->session;
        struct window                   *w = wl->window;
        char                            *value;
        struct control_sub_window       *csw, find;

        value = format_expand(ft, csub->format);

        find.window = w->id;
        find.idx = wl->idx;

        csw = RB_FIND(control_sub_windows, &csub->windows, &find);
        if (csw == NULL) {
                csw = xcalloc(1, sizeof *csw);
                csw->window = w->id;
                csw->idx = wl->idx;
                RB_INSERT(control_sub_windows, &csub->windows, csw);
        }

        if (csw->last != NULL && strcmp(value, csw->last) == 0) {
                free(value);
                return;
        }
        control_write(c,
            "%%subscription-changed %s $%u @%u %u - : %s",
            csub->name, s->id, w->id, wl->idx, value);
        free(csw->last);
        csw->last = value;
}

/* Check subscriptions timer. */
static void
control_check_subs_timer(__unused int fd, __unused short events, void *data)
{
        struct client           *c = data;
        struct control_state    *cs = c->control_state;
        struct control_sub      *csub, *csub1;
        struct session          *s = c->session;
        struct format_tree      *ft;
        struct winlink          *wl;
        struct window_pane      *wp;
        struct timeval           tv = { .tv_sec = 1 };
        int                      have_session = 0, have_all_panes = 0;
        int                      have_all_windows = 0;

        log_debug("%s: timer fired", __func__);
        evtimer_add(&cs->subs_timer, &tv);

        if (s == NULL)
                return;

        /* Find which subscription types are present. */
        RB_FOREACH(csub, control_subs, &cs->subs) {
                switch (csub->type) {
                case CONTROL_SUB_SESSION:
                        have_session = 1;
                        break;
                case CONTROL_SUB_ALL_PANES:
                        have_all_panes = 1;
                        break;
                case CONTROL_SUB_ALL_WINDOWS:
                        have_all_windows = 1;
                        break;
                default:
                        break;
                }
        }

        /* Check session subscriptions. */
        if (have_session) {
                ft = format_create_defaults(NULL, c, s, NULL, NULL);
                RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
                        if (csub->type == CONTROL_SUB_SESSION)
                                control_check_subs_session(c, csub, ft);
                }
                format_free(ft);
        }

        /* Check pane and window subscriptions. */
        RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
                switch (csub->type) {
                case CONTROL_SUB_PANE:
                        control_check_subs_pane(c, csub);
                        break;
                case CONTROL_SUB_WINDOW:
                        control_check_subs_window(c, csub);
                        break;
                case CONTROL_SUB_SESSION:
                case CONTROL_SUB_ALL_PANES:
                case CONTROL_SUB_ALL_WINDOWS:
                        break;
                }
        }

        /* Check all-panes subscriptions. */
        if (have_all_panes) {
                RB_FOREACH(wl, winlinks, &s->windows) {
                        TAILQ_FOREACH(wp, &wl->window->panes, entry) {
                                ft = format_create_defaults(NULL, c, s, wl, wp);
                                RB_FOREACH_SAFE(csub, control_subs, &cs->subs,
                                    csub1) {
                                        if (csub->type != CONTROL_SUB_ALL_PANES)
                                                continue;
                                        control_check_subs_all_panes_one(c,
                                            csub, ft, wl, wp);
                                }
                                format_free(ft);
                        }
                }
        }

        /* Check all-windows subscriptions. */
        if (have_all_windows) {
                RB_FOREACH(wl, winlinks, &s->windows) {
                        ft = format_create_defaults(NULL, c, s, wl, NULL);
                        RB_FOREACH_SAFE(csub, control_subs, &cs->subs,
                            csub1) {
                                if (csub->type != CONTROL_SUB_ALL_WINDOWS)
                                        continue;
                                control_check_subs_all_windows_one(c, csub, ft,
                                    wl);
                        }
                        format_free(ft);
                }
        }
}

/* Add a subscription. */
void
control_add_sub(struct client *c, const char *name, enum control_sub_type type,
    int id, const char *format)
{
        struct control_state    *cs = c->control_state;
        struct control_sub      *csub, find;
        struct timeval           tv = { .tv_sec = 1 };

        find.name = (char *)name;
        if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
                control_free_sub(cs, csub);

        csub = xcalloc(1, sizeof *csub);
        csub->name = xstrdup(name);
        csub->type = type;
        csub->id = id;
        csub->format = xstrdup(format);
        RB_INSERT(control_subs, &cs->subs, csub);

        RB_INIT(&csub->panes);
        RB_INIT(&csub->windows);

        if (!evtimer_initialized(&cs->subs_timer))
                evtimer_set(&cs->subs_timer, control_check_subs_timer, c);
        if (!evtimer_pending(&cs->subs_timer, NULL))
                evtimer_add(&cs->subs_timer, &tv);
}

/* Remove a subscription. */
void
control_remove_sub(struct client *c, const char *name)
{
        struct control_state    *cs = c->control_state;
        struct control_sub      *csub, find;

        find.name = (char *)name;
        if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
                control_free_sub(cs, csub);
        if (RB_EMPTY(&cs->subs))
                evtimer_del(&cs->subs_timer);
}