/* $OpenBSD$ */
/*
* 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 <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 <