Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options, it was decided to implement an XDR-like protocol code generator in portable AWK. It now has two backends, per each of: - xF, the X11 frontend, is in C, and is meant to be the primary user interface in the future. - xP, the web frontend, relies on a protocol proxy written in Go, and is meant for use on-the-go (no pun intended). They are very much work-in-progress proofs of concept right now, and the relay protocol is certain to change.
This commit is contained in:
785
xC.c
785
xC.c
@@ -50,6 +50,7 @@ enum
|
||||
|
||||
#include "common.c"
|
||||
#include "xD-replies.c"
|
||||
#include "xC-proto.c"
|
||||
|
||||
#include <math.h>
|
||||
#include <langinfo.h>
|
||||
@@ -1526,6 +1527,7 @@ enum buffer_line_flags
|
||||
BUFFER_LINE_HIGHLIGHT = 1 << 2, ///< The user was highlighted by this
|
||||
};
|
||||
|
||||
// NOTE: This sequence must match up with xC-proto, only one lower.
|
||||
enum buffer_line_rendition
|
||||
{
|
||||
BUFFER_LINE_BARE, ///< Unadorned
|
||||
@@ -1666,6 +1668,50 @@ buffer_destroy (struct buffer *self)
|
||||
REF_COUNTABLE_METHODS (buffer)
|
||||
#define buffer_ref do_not_use_dangerous
|
||||
|
||||
// ~~~ Relay ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
struct client
|
||||
{
|
||||
LIST_HEADER (struct client)
|
||||
struct app_context *ctx; ///< Application context
|
||||
|
||||
// TODO: Convert this all to TLS, and only TLS, with required client cert.
|
||||
// That means replacing plumbing functions with the /other/ set from xD.
|
||||
|
||||
int socket_fd; ///< The TCP socket
|
||||
struct str read_buffer; ///< Unprocessed input
|
||||
struct str write_buffer; ///< Output yet to be sent out
|
||||
|
||||
uint32_t event_seq; ///< Outgoing message counter
|
||||
bool initialized; ///< Initial sync took place
|
||||
|
||||
struct poller_fd socket_event; ///< The socket can be read/written to
|
||||
};
|
||||
|
||||
static struct client *
|
||||
client_new (void)
|
||||
{
|
||||
struct client *self = xcalloc (1, sizeof *self);
|
||||
self->socket_fd = -1;
|
||||
self->read_buffer = str_make ();
|
||||
self->write_buffer = str_make ();
|
||||
return self;
|
||||
}
|
||||
|
||||
static void
|
||||
client_destroy (struct client *self)
|
||||
{
|
||||
if (!soft_assert (self->socket_fd == -1))
|
||||
xclose (self->socket_fd);
|
||||
|
||||
str_free (&self->read_buffer);
|
||||
str_free (&self->write_buffer);
|
||||
free (self);
|
||||
}
|
||||
|
||||
static void client_kill (struct client *c);
|
||||
static bool client_process_buffer (struct client *c);
|
||||
|
||||
// ~~~ Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// The only real purpose of this is to abstract away TLS
|
||||
@@ -2079,10 +2125,19 @@ struct app_context
|
||||
|
||||
struct str_map servers; ///< Our servers
|
||||
|
||||
// Relay:
|
||||
|
||||
int relay_fd; ///< Listening socket FD
|
||||
struct client *clients; ///< Our relay clients
|
||||
|
||||
/// A single message buffer to prepare all outcoming messages within
|
||||
struct relay_event_message relay_message;
|
||||
|
||||
// Events:
|
||||
|
||||
struct poller_fd tty_event; ///< Terminal input event
|
||||
struct poller_fd signal_event; ///< Signal FD event
|
||||
struct poller_fd relay_event; ///< New relay connection available
|
||||
|
||||
struct poller_timer flush_timer; ///< Flush all open files (e.g. logs)
|
||||
struct poller_timer date_chg_tmr; ///< Print a date change
|
||||
@@ -2129,6 +2184,8 @@ struct app_context
|
||||
char *editor_filename; ///< The file being edited by user
|
||||
int terminal_suspended; ///< Terminal suspension level
|
||||
|
||||
// Plugins:
|
||||
|
||||
struct plugin *plugins; ///< Loaded plugins
|
||||
struct hook *input_hooks; ///< Input hooks
|
||||
struct hook *irc_hooks; ///< IRC hooks
|
||||
@@ -2197,6 +2254,8 @@ app_context_init (struct app_context *self)
|
||||
self->config = config_make ();
|
||||
poller_init (&self->poller);
|
||||
|
||||
self->relay_fd = -1;
|
||||
|
||||
self->servers = str_map_make ((str_map_free_fn) server_unref);
|
||||
self->servers.key_xfrm = tolower_ascii_strxfrm;
|
||||
|
||||
@@ -2222,6 +2281,17 @@ app_context_init (struct app_context *self)
|
||||
filter_color_cube_for_acceptable_nick_colors (&self->nick_palette_len);
|
||||
}
|
||||
|
||||
static void
|
||||
app_context_relay_stop (struct app_context *self)
|
||||
{
|
||||
if (self->relay_fd != -1)
|
||||
{
|
||||
poller_fd_reset (&self->relay_event);
|
||||
xclose (self->relay_fd);
|
||||
self->relay_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
app_context_free (struct app_context *self)
|
||||
{
|
||||
@@ -2247,6 +2317,11 @@ app_context_free (struct app_context *self)
|
||||
}
|
||||
str_map_free (&self->buffers_by_name);
|
||||
|
||||
app_context_relay_stop (self);
|
||||
LIST_FOR_EACH (struct client, c, self->clients)
|
||||
client_kill (c);
|
||||
relay_event_message_free (&self->relay_message);
|
||||
|
||||
str_map_free (&self->servers);
|
||||
poller_free (&self->poller);
|
||||
|
||||
@@ -2285,6 +2360,7 @@ on_config_show_all_prefixes_change (struct config_item *item)
|
||||
refresh_prompt (ctx);
|
||||
}
|
||||
|
||||
static void on_config_relay_bind_change (struct config_item *item);
|
||||
static void on_config_backlog_limit_change (struct config_item *item);
|
||||
static void on_config_attribute_change (struct config_item *item);
|
||||
static void on_config_logging_change (struct config_item *item);
|
||||
@@ -2479,6 +2555,11 @@ static struct config_schema g_config_general[] =
|
||||
.comment = "Plugins to automatically load on start",
|
||||
.type = CONFIG_ITEM_STRING_ARRAY,
|
||||
.validate = config_validate_nonjunk_string },
|
||||
{ .name = "relay_bind",
|
||||
.comment = "Address to bind to for a user interface relay point",
|
||||
.type = CONFIG_ITEM_STRING,
|
||||
.validate = config_validate_nonjunk_string,
|
||||
.on_change = on_config_relay_bind_change },
|
||||
|
||||
// Buffer history:
|
||||
{ .name = "backlog_limit",
|
||||
@@ -2681,6 +2762,418 @@ serialize_configuration (struct config_item *root, struct str *output)
|
||||
config_item_write (root, true, output);
|
||||
}
|
||||
|
||||
// --- Relay plumbing ----------------------------------------------------------
|
||||
|
||||
static void
|
||||
client_kill (struct client *c)
|
||||
{
|
||||
struct app_context *ctx = c->ctx;
|
||||
poller_fd_reset (&c->socket_event);
|
||||
xclose (c->socket_fd);
|
||||
c->socket_fd = -1;
|
||||
|
||||
LIST_UNLINK (ctx->clients, c);
|
||||
client_destroy (c);
|
||||
}
|
||||
|
||||
static bool
|
||||
client_try_read (struct client *c)
|
||||
{
|
||||
struct str *buf = &c->read_buffer;
|
||||
ssize_t n_read;
|
||||
|
||||
while ((n_read = read (c->socket_fd, buf->str + buf->len,
|
||||
buf->alloc - buf->len - 1 /* null byte */)) > 0)
|
||||
{
|
||||
buf->len += n_read;
|
||||
if (!client_process_buffer (c))
|
||||
break;
|
||||
str_reserve (buf, 512);
|
||||
}
|
||||
|
||||
if (n_read < 0)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EINTR)
|
||||
return true;
|
||||
|
||||
print_debug ("%s: %s: %s", __func__, "read", strerror (errno));
|
||||
}
|
||||
|
||||
client_kill (c);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
client_try_write (struct client *c)
|
||||
{
|
||||
struct str *buf = &c->write_buffer;
|
||||
ssize_t n_written;
|
||||
|
||||
while (buf->len)
|
||||
{
|
||||
n_written = write (c->socket_fd, buf->str, buf->len);
|
||||
if (n_written >= 0)
|
||||
{
|
||||
str_remove_slice (buf, 0, n_written);
|
||||
continue;
|
||||
}
|
||||
if (errno == EAGAIN || errno == EINTR)
|
||||
return true;
|
||||
|
||||
print_debug ("%s: %s: %s", __func__, "write", strerror (errno));
|
||||
client_kill (c);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
client_update_poller (struct client *c, const struct pollfd *pfd)
|
||||
{
|
||||
int new_events = POLLIN;
|
||||
if (c->write_buffer.len)
|
||||
new_events |= POLLOUT;
|
||||
|
||||
hard_assert (new_events != 0);
|
||||
if (!pfd || pfd->events != new_events)
|
||||
poller_fd_set (&c->socket_event, new_events);
|
||||
}
|
||||
|
||||
static void
|
||||
on_client_ready (const struct pollfd *pfd, void *user_data)
|
||||
{
|
||||
struct client *c = user_data;
|
||||
if (client_try_read (c) && client_try_write (c))
|
||||
client_update_poller (c, pfd);
|
||||
}
|
||||
|
||||
static bool
|
||||
relay_try_fetch_client (struct app_context *ctx, int listen_fd)
|
||||
{
|
||||
// XXX: `struct sockaddr_storage' is not the most portable thing
|
||||
struct sockaddr_storage peer;
|
||||
socklen_t peer_len = sizeof peer;
|
||||
|
||||
int fd = accept (listen_fd, (struct sockaddr *) &peer, &peer_len);
|
||||
if (fd == -1)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
return false;
|
||||
if (errno == EINTR)
|
||||
return true;
|
||||
|
||||
if (accept_error_is_transient (errno))
|
||||
print_warning ("%s: %s", "accept", strerror (errno));
|
||||
else
|
||||
print_fatal ("%s: %s", "accept", strerror (errno));
|
||||
return true;
|
||||
}
|
||||
|
||||
hard_assert (peer_len <= sizeof peer);
|
||||
set_blocking (fd, false);
|
||||
set_cloexec (fd);
|
||||
|
||||
// We already buffer our output, so reduce latencies.
|
||||
int yes = 1;
|
||||
soft_assert (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY,
|
||||
&yes, sizeof yes) != -1);
|
||||
|
||||
struct client *c = client_new ();
|
||||
c->ctx = ctx;
|
||||
c->socket_fd = fd;
|
||||
LIST_PREPEND (ctx->clients, c);
|
||||
|
||||
c->socket_event = poller_fd_make (&c->ctx->poller, c->socket_fd);
|
||||
c->socket_event.dispatcher = (poller_fd_fn) on_client_ready;
|
||||
c->socket_event.user_data = c;
|
||||
|
||||
client_update_poller (c, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
on_relay_client_available (const struct pollfd *pfd, void *user_data)
|
||||
{
|
||||
struct app_context *ctx = user_data;
|
||||
while (relay_try_fetch_client (ctx, pfd->fd))
|
||||
;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static int
|
||||
relay_listen (struct addrinfo *ai, struct error **e)
|
||||
{
|
||||
int fd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||
if (fd == -1)
|
||||
{
|
||||
error_set (e, "socket: %s", strerror (errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
set_cloexec (fd);
|
||||
|
||||
int yes = 1;
|
||||
soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
|
||||
&yes, sizeof yes) != -1);
|
||||
soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
|
||||
&yes, sizeof yes) != -1);
|
||||
|
||||
if (bind (fd, ai->ai_addr, ai->ai_addrlen))
|
||||
error_set (e, "bind: %s", strerror (errno));
|
||||
else if (listen (fd, 16 /* arbitrary number */))
|
||||
error_set (e, "listen: %s", strerror (errno));
|
||||
else
|
||||
return fd;
|
||||
|
||||
xclose (fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
relay_listen_with_context (struct addrinfo *ai, struct error **e)
|
||||
{
|
||||
char *address = gai_reconstruct_address (ai);
|
||||
print_debug ("binding to `%s'", address);
|
||||
|
||||
struct error *error = NULL;
|
||||
int fd = relay_listen (ai, &error);
|
||||
if (fd == -1)
|
||||
{
|
||||
error_set (e, "binding to `%s' failed: %s", address, error->message);
|
||||
error_free (error);
|
||||
}
|
||||
free (address);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static bool
|
||||
relay_start (struct app_context *ctx, char *address, struct error **e)
|
||||
{
|
||||
const char *port = NULL, *host = tokenize_host_port (address, &port);
|
||||
if (!port || !*port)
|
||||
return error_set (e, "missing port");
|
||||
|
||||
struct addrinfo hints = {}, *result = NULL;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
int err = getaddrinfo (*host ? host : NULL, port, &hints, &result);
|
||||
if (err)
|
||||
{
|
||||
return error_set (e, "failed to resolve `%s', port `%s': %s: %s",
|
||||
host, port, "getaddrinfo", gai_strerror (err));
|
||||
}
|
||||
|
||||
// Just try the first one, disregarding IPv4/IPv6 ordering.
|
||||
int fd = relay_listen_with_context (result, e);
|
||||
freeaddrinfo (result);
|
||||
if (fd == -1)
|
||||
return false;
|
||||
|
||||
set_blocking (fd, false);
|
||||
|
||||
struct poller_fd *event = &ctx->relay_event;
|
||||
*event = poller_fd_make (&ctx->poller, fd);
|
||||
event->dispatcher = (poller_fd_fn) on_relay_client_available;
|
||||
event->user_data = ctx;
|
||||
|
||||
ctx->relay_fd = fd;
|
||||
poller_fd_set (event, POLLIN);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
on_config_relay_bind_change (struct config_item *item)
|
||||
{
|
||||
struct app_context *ctx = item->user_data;
|
||||
char *value = item->value.string.str;
|
||||
app_context_relay_stop (ctx);
|
||||
if (!value)
|
||||
return;
|
||||
|
||||
struct error *e = NULL;
|
||||
char *address = xstrdup (value);
|
||||
if (!relay_start (ctx, address, &e))
|
||||
{
|
||||
// TODO: Try to make sure this finds its way to the global buffer.
|
||||
print_error ("%s: %s", item->schema->name, e->message);
|
||||
error_free (e);
|
||||
}
|
||||
free (address);
|
||||
}
|
||||
|
||||
// --- Relay output ------------------------------------------------------------
|
||||
|
||||
static void
|
||||
relay_send (struct client *c)
|
||||
{
|
||||
struct relay_event_message *m = &c->ctx->relay_message;
|
||||
m->event_seq = c->event_seq++;
|
||||
|
||||
// TODO: Also don't try sending anything if half-closed.
|
||||
if (!c->initialized || c->socket_fd == -1)
|
||||
return;
|
||||
|
||||
// liberty has msg_{reader,writer} already, but they use 8-byte lengths.
|
||||
size_t frame_len_pos = c->write_buffer.len, frame_len = 0;
|
||||
str_pack_u32 (&c->write_buffer, 0);
|
||||
if (!relay_event_message_serialize (m, &c->write_buffer)
|
||||
|| (frame_len = c->write_buffer.len - frame_len_pos - 4) > UINT32_MAX)
|
||||
{
|
||||
print_error ("serialization failed, killing client");
|
||||
client_kill (c);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t len = htonl (frame_len);
|
||||
memcpy (c->write_buffer.str + frame_len_pos, &len, sizeof len);
|
||||
client_update_poller (c, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
relay_broadcast (struct app_context *ctx)
|
||||
{
|
||||
LIST_FOR_EACH (struct client, c, ctx->clients)
|
||||
relay_send (c);
|
||||
}
|
||||
|
||||
static struct relay_event_message *
|
||||
relay_prepare (struct app_context *ctx)
|
||||
{
|
||||
struct relay_event_message *m = &ctx->relay_message;
|
||||
relay_event_message_free (m);
|
||||
memset (m, 0, sizeof *m);
|
||||
return m;
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_ping (struct app_context *ctx)
|
||||
{
|
||||
relay_prepare (ctx)->data.event = RELAY_EVENT_PING;
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (ctx);
|
||||
struct relay_event_data_buffer_update *e = &m->data.buffer_update;
|
||||
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
||||
e->buffer_name = str_from_cstr (buffer->name);
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_buffer_rename (struct app_context *ctx, struct buffer *buffer,
|
||||
const char *new_name)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (ctx);
|
||||
struct relay_event_data_buffer_rename *e = &m->data.buffer_rename;
|
||||
e->event = RELAY_EVENT_BUFFER_RENAME;
|
||||
e->buffer_name = str_from_cstr (buffer->name);
|
||||
e->new = str_from_cstr (new_name);
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_buffer_remove (struct app_context *ctx, struct buffer *buffer)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (ctx);
|
||||
struct relay_event_data_buffer_remove *e = &m->data.buffer_remove;
|
||||
e->event = RELAY_EVENT_BUFFER_REMOVE;
|
||||
e->buffer_name = str_from_cstr (buffer->name);
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (ctx);
|
||||
struct relay_event_data_buffer_activate *e = &m->data.buffer_activate;
|
||||
e->event = RELAY_EVENT_BUFFER_ACTIVATE;
|
||||
e->buffer_name = str_from_cstr (buffer->name);
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
|
||||
struct buffer_line *line)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (ctx);
|
||||
struct relay_event_data_buffer_line *e = &m->data.buffer_line;
|
||||
e->event = RELAY_EVENT_BUFFER_LINE;
|
||||
e->buffer_name = str_from_cstr (buffer->name);
|
||||
e->is_unimportant = !!(line->flags & BUFFER_LINE_UNIMPORTANT);
|
||||
e->is_highlight = !!(line->flags & BUFFER_LINE_HIGHLIGHT);
|
||||
e->rendition = 1 + line->r;
|
||||
e->when = line->when;
|
||||
|
||||
size_t len = 0;
|
||||
for (size_t i = 0; line->items[i].type; i++)
|
||||
len++;
|
||||
|
||||
// XXX: This way helps xP's JSON conversion, but is super annoying for us.
|
||||
union relay_item_data *p = e->items = xcalloc (len * 6, sizeof *e->items);
|
||||
for (struct formatter_item *i = line->items; len--; i++)
|
||||
{
|
||||
switch (i->type)
|
||||
{
|
||||
case FORMATTER_ITEM_TEXT:
|
||||
p->text.text = str_from_cstr (i->text);
|
||||
(p++)->kind = RELAY_ITEM_TEXT;
|
||||
break;
|
||||
case FORMATTER_ITEM_ATTR:
|
||||
// For future consideration.
|
||||
(p++)->kind = RELAY_ITEM_RESET;
|
||||
break;
|
||||
case FORMATTER_ITEM_FG_COLOR:
|
||||
p->fg_color.color = i->color;
|
||||
(p++)->kind = RELAY_ITEM_FG_COLOR;
|
||||
break;
|
||||
case FORMATTER_ITEM_BG_COLOR:
|
||||
p->bg_color.color = i->color;
|
||||
(p++)->kind = RELAY_ITEM_BG_COLOR;
|
||||
break;
|
||||
case FORMATTER_ITEM_SIMPLE:
|
||||
if (i->attribute & TEXT_BOLD)
|
||||
(p++)->kind = RELAY_ITEM_FLIP_BOLD;
|
||||
if (i->attribute & TEXT_ITALIC)
|
||||
(p++)->kind = RELAY_ITEM_FLIP_ITALIC;
|
||||
if (i->attribute & TEXT_UNDERLINE)
|
||||
(p++)->kind = RELAY_ITEM_FLIP_UNDERLINE;
|
||||
if (i->attribute & TEXT_INVERSE)
|
||||
(p++)->kind = RELAY_ITEM_FLIP_INVERSE;
|
||||
if (i->attribute & TEXT_CROSSED_OUT)
|
||||
(p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT;
|
||||
if (i->attribute & TEXT_MONOSPACE)
|
||||
(p++)->kind = RELAY_ITEM_FLIP_MONOSPACE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
e->items_len = p - e->items;
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_buffer_clear (struct app_context *ctx,
|
||||
struct buffer *buffer)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (ctx);
|
||||
struct relay_event_data_buffer_clear *e = &m->data.buffer_clear;
|
||||
e->event = RELAY_EVENT_BUFFER_CLEAR;
|
||||
e->buffer_name = str_from_cstr (buffer->name);
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_error (struct app_context *ctx, uint32_t seq, const char *message)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (ctx);
|
||||
struct relay_event_data_error *e = &m->data.error;
|
||||
e->event = RELAY_EVENT_ERROR;
|
||||
e->command_seq = seq;
|
||||
e->error = str_from_cstr (message);
|
||||
}
|
||||
|
||||
// --- Terminal output ---------------------------------------------------------
|
||||
|
||||
/// Default colour pair
|
||||
@@ -4089,6 +4582,9 @@ log_formatter (struct app_context *ctx, struct buffer *buffer,
|
||||
if (buffer->log_file)
|
||||
buffer_line_write_to_log (ctx, line, buffer->log_file);
|
||||
|
||||
relay_prepare_buffer_line (ctx, buffer, line);
|
||||
relay_broadcast (ctx);
|
||||
|
||||
bool unseen_pm = buffer->type == BUFFER_PM
|
||||
&& buffer != ctx->current_buffer
|
||||
&& !(flags & BUFFER_LINE_UNIMPORTANT);
|
||||
@@ -4302,6 +4798,9 @@ buffer_add (struct app_context *ctx, struct buffer *buffer)
|
||||
|
||||
buffer_open_log_file (ctx, buffer);
|
||||
|
||||
relay_prepare_buffer_update (ctx, buffer);
|
||||
relay_broadcast (ctx);
|
||||
|
||||
// Normally this doesn't cause changes in the prompt but a prompt hook
|
||||
// could decide to show some information for all buffers nonetheless
|
||||
refresh_prompt (ctx);
|
||||
@@ -4328,6 +4827,9 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
|
||||
if (buffer->type == BUFFER_SERVER)
|
||||
buffer->server->buffer = NULL;
|
||||
|
||||
relay_prepare_buffer_remove (ctx, buffer);
|
||||
relay_broadcast (ctx);
|
||||
|
||||
str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
|
||||
LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
|
||||
buffer_unref (buffer);
|
||||
@@ -4457,6 +4959,9 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer)
|
||||
ctx->last_buffer = ctx->current_buffer;
|
||||
ctx->current_buffer = buffer;
|
||||
|
||||
relay_prepare_buffer_activate (ctx, buffer);
|
||||
relay_broadcast (ctx);
|
||||
|
||||
refresh_prompt (ctx);
|
||||
}
|
||||
|
||||
@@ -4491,12 +4996,19 @@ buffer_merge (struct app_context *ctx,
|
||||
merged->lines_tail = start->prev;
|
||||
merged->lines_count -= n;
|
||||
|
||||
// And append them to current lines in the buffer
|
||||
// Append them to current lines in the buffer
|
||||
buffer->lines_tail->next = start;
|
||||
start->prev = buffer->lines_tail;
|
||||
buffer->lines_tail = tail;
|
||||
buffer->lines_count += n;
|
||||
|
||||
// And since there is no log_*() call, send them to relays manually
|
||||
LIST_FOR_EACH (struct buffer_line, line, start)
|
||||
{
|
||||
relay_prepare_buffer_line (ctx, buffer, line);
|
||||
relay_broadcast (ctx);
|
||||
}
|
||||
|
||||
log_full (ctx, NULL, buffer, BUFFER_LINE_SKIP_FILE, BUFFER_LINE_STATUS,
|
||||
"End of merged content");
|
||||
}
|
||||
@@ -4511,6 +5023,9 @@ buffer_rename (struct app_context *ctx,
|
||||
|
||||
hard_assert (!collision);
|
||||
|
||||
relay_prepare_buffer_rename (ctx, buffer, new_name);
|
||||
relay_broadcast (ctx);
|
||||
|
||||
str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
|
||||
str_map_set (&ctx->buffers_by_name, new_name, buffer);
|
||||
|
||||
@@ -4524,13 +5039,16 @@ buffer_rename (struct app_context *ctx,
|
||||
}
|
||||
|
||||
static void
|
||||
buffer_clear (struct buffer *buffer)
|
||||
buffer_clear (struct app_context *ctx, struct buffer *buffer)
|
||||
{
|
||||
LIST_FOR_EACH (struct buffer_line, iter, buffer->lines)
|
||||
buffer_line_destroy (iter);
|
||||
|
||||
buffer->lines = buffer->lines_tail = NULL;
|
||||
buffer->lines_count = 0;
|
||||
|
||||
relay_prepare_buffer_clear (ctx, buffer);
|
||||
relay_broadcast (ctx);
|
||||
}
|
||||
|
||||
static struct buffer *
|
||||
@@ -5947,29 +6465,6 @@ irc_finish_connection (struct server *s, int socket, const char *hostname)
|
||||
refresh_prompt (s->ctx);
|
||||
}
|
||||
|
||||
/// Unwrap IPv6 addresses in format_host_port_pair() format
|
||||
static void
|
||||
irc_split_host_port (char *s, char **host, char **port)
|
||||
{
|
||||
*host = s;
|
||||
*port = "6667";
|
||||
|
||||
char *right_bracket = strchr (s, ']');
|
||||
if (s[0] == '[' && right_bracket)
|
||||
{
|
||||
*right_bracket = '\0';
|
||||
*host = s + 1;
|
||||
s = right_bracket + 1;
|
||||
}
|
||||
|
||||
char *colon = strchr (s, ':');
|
||||
if (colon)
|
||||
{
|
||||
*colon = '\0';
|
||||
*port = colon + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
@@ -6019,8 +6514,8 @@ irc_setup_connector (struct server *s, const struct strv *addresses)
|
||||
|
||||
for (size_t i = 0; i < addresses->len; i++)
|
||||
{
|
||||
char *host, *port;
|
||||
irc_split_host_port (addresses->vector[i], &host, &port);
|
||||
const char *port = "6667",
|
||||
*host = tokenize_host_port (addresses->vector[i], &port);
|
||||
connector_add_target (connector, host, port);
|
||||
}
|
||||
}
|
||||
@@ -6062,9 +6557,8 @@ irc_setup_connector_socks (struct server *s, const struct strv *addresses,
|
||||
|
||||
for (size_t i = 0; i < addresses->len; i++)
|
||||
{
|
||||
char *host, *port;
|
||||
irc_split_host_port (addresses->vector[i], &host, &port);
|
||||
|
||||
const char *port = "6667",
|
||||
*host = tokenize_host_port (addresses->vector[i], &port);
|
||||
if (!socks_connector_add_target (connector, host, port, e))
|
||||
return false;
|
||||
}
|
||||
@@ -7644,7 +8138,7 @@ irc_on_registered (struct server *s, const char *nickname)
|
||||
if (command)
|
||||
{
|
||||
log_server_debug (s, "Executing \"#s\"", command);
|
||||
process_input_utf8 (s->ctx, s->buffer, command, 0);
|
||||
(void) process_input_utf8 (s->ctx, s->buffer, command, 0);
|
||||
}
|
||||
|
||||
int64_t command_delay = get_config_integer (s->config, "command_delay");
|
||||
@@ -8229,6 +8723,24 @@ irc_handle_rpl_isupport (struct server *s, const struct irc_message *msg)
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
irc_adjust_motd (char **motd)
|
||||
{
|
||||
// Heuristic, force MOTD to be monospace in graphical frontends.
|
||||
if (!strchr (*motd, '\x11'))
|
||||
{
|
||||
struct str s = str_make ();
|
||||
str_append_c (&s, '\x11');
|
||||
for (const char *p = *motd; *p; p++)
|
||||
{
|
||||
str_append_c (&s, *p);
|
||||
if (*p == '\x0f')
|
||||
str_append_c (&s, '\x11');
|
||||
}
|
||||
cstr_set (motd, str_steal (&s));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
irc_process_numeric (struct server *s,
|
||||
const struct irc_message *msg, unsigned long numeric)
|
||||
@@ -8251,6 +8763,10 @@ irc_process_numeric (struct server *s,
|
||||
if (msg->params.len == 2)
|
||||
irc_try_parse_welcome_for_userhost (s, msg->params.vector[1]);
|
||||
break;
|
||||
case IRC_RPL_MOTD:
|
||||
if (copy.len)
|
||||
irc_adjust_motd (©.vector[0]);
|
||||
break;
|
||||
|
||||
case IRC_RPL_ISUPPORT:
|
||||
irc_handle_rpl_isupport (s, msg); break;
|
||||
@@ -9248,7 +9764,7 @@ lua_buffer_execute (lua_State *L)
|
||||
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
|
||||
struct buffer *buffer = wrapper->object;
|
||||
const char *line = lua_plugin_check_utf8 (L, 2);
|
||||
process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0);
|
||||
(void) process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -11304,7 +11820,7 @@ handle_command_buffer (struct handler_args *a)
|
||||
show_buffers_list (ctx);
|
||||
else if (!strcasecmp_ascii (action, "clear"))
|
||||
{
|
||||
buffer_clear (a->buffer);
|
||||
buffer_clear (ctx, a->buffer);
|
||||
if (a->buffer == ctx->current_buffer)
|
||||
buffer_print_backlog (ctx, a->buffer);
|
||||
}
|
||||
@@ -12926,8 +13442,8 @@ complete_set_value_array (struct config_item *item, const char *word,
|
||||
cstr_split (item->value.string.str, ",", false, &items);
|
||||
for (size_t i = 0; i < items.len; i++)
|
||||
{
|
||||
struct str wrapped = str_make (), serialized = str_make ();
|
||||
str_append (&wrapped, items.vector[i]);
|
||||
struct str wrapped = str_from_cstr (items.vector[i]);
|
||||
struct str serialized = str_make ();
|
||||
config_item_write_string (&serialized, &wrapped);
|
||||
str_free (&wrapped);
|
||||
|
||||
@@ -13546,6 +14062,25 @@ on_display_backlog_nowrap (int count, int key, void *user_data)
|
||||
return display_backlog (user_data, FLUSH_OPT_NOWRAP);
|
||||
}
|
||||
|
||||
static FILE *
|
||||
open_log_path (struct app_context *ctx, struct buffer *buffer, const char *path)
|
||||
{
|
||||
FILE *fp = fopen (path, "rb");
|
||||
if (!fp)
|
||||
{
|
||||
log_global_error (ctx,
|
||||
"Failed to open `#l': #l", path, strerror (errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (buffer->log_file)
|
||||
// The regular flush will log any error eventually
|
||||
(void) fflush (buffer->log_file);
|
||||
|
||||
set_cloexec (fileno (fp));
|
||||
return fp;
|
||||
}
|
||||
|
||||
static bool
|
||||
on_display_full_log (int count, int key, void *user_data)
|
||||
{
|
||||
@@ -13555,20 +14090,13 @@ on_display_full_log (int count, int key, void *user_data)
|
||||
|
||||
struct buffer *buffer = ctx->current_buffer;
|
||||
char *path = buffer_get_log_path (buffer);
|
||||
FILE *full_log = fopen (path, "rb");
|
||||
FILE *full_log = open_log_path (ctx, buffer, path);
|
||||
if (!full_log)
|
||||
{
|
||||
log_global_error (ctx, "Failed to open log file for #s: #l",
|
||||
ctx->current_buffer->name, strerror (errno));
|
||||
free (path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer->log_file)
|
||||
// The regular flush will log any error eventually
|
||||
(void) fflush (buffer->log_file);
|
||||
|
||||
set_cloexec (fileno (full_log));
|
||||
launch_pager (ctx, fileno (full_log), buffer->name, path);
|
||||
fclose (full_log);
|
||||
free (path);
|
||||
@@ -14601,6 +15129,177 @@ init_poller_events (struct app_context *ctx)
|
||||
ctx->input_event.user_data = ctx;
|
||||
}
|
||||
|
||||
// --- Relay processing --------------------------------------------------------
|
||||
|
||||
// XXX: This could be below completion code if reset_autoaway() was higher up.
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
client_resync (struct client *c)
|
||||
{
|
||||
LIST_FOR_EACH (struct buffer, buffer, c->ctx->buffers)
|
||||
{
|
||||
relay_prepare_buffer_update (c->ctx, buffer);
|
||||
relay_send (c);
|
||||
|
||||
LIST_FOR_EACH (struct buffer_line, line, buffer->lines)
|
||||
{
|
||||
relay_prepare_buffer_line (c->ctx, buffer, line);
|
||||
relay_send (c);
|
||||
}
|
||||
}
|
||||
|
||||
relay_prepare_buffer_activate (c->ctx, c->ctx->current_buffer);
|
||||
relay_send (c);
|
||||
}
|
||||
|
||||
static const char *
|
||||
client_message_buffer_name (const struct relay_command_message *m)
|
||||
{
|
||||
switch (m->data.command)
|
||||
{
|
||||
case RELAY_COMMAND_BUFFER_COMPLETE:
|
||||
return m->data.buffer_input.buffer_name.str;
|
||||
case RELAY_COMMAND_BUFFER_INPUT:
|
||||
return m->data.buffer_input.buffer_name.str;
|
||||
case RELAY_COMMAND_BUFFER_ACTIVATE:
|
||||
return m->data.buffer_activate.buffer_name.str;
|
||||
case RELAY_COMMAND_BUFFER_LOG:
|
||||
return m->data.buffer_log.buffer_name.str;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
client_process_buffer_log
|
||||
(struct client *c, uint32_t seq, struct buffer *buffer)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (c->ctx);
|
||||
struct relay_event_data_response *e = &m->data.response;
|
||||
e->event = RELAY_EVENT_RESPONSE;
|
||||
e->command_seq = seq;
|
||||
e->data.command = RELAY_COMMAND_BUFFER_LOG;
|
||||
|
||||
char *path = buffer_get_log_path (buffer);
|
||||
FILE *fp = open_log_path (c->ctx, buffer, path);
|
||||
if (fp)
|
||||
{
|
||||
struct str log = str_make ();
|
||||
char buf[BUFSIZ];
|
||||
size_t len;
|
||||
while ((len = fread (buf, 1, sizeof buf, fp)))
|
||||
str_append_data (&log, buf, len);
|
||||
if (ferror (fp))
|
||||
log_global_error (c->ctx, "Failed to read `#l': #l",
|
||||
path, strerror (errno));
|
||||
|
||||
// On overflow, it will later fail serialization.
|
||||
e->data.buffer_log.log_len = MIN (UINT32_MAX, log.len);
|
||||
e->data.buffer_log.log = (uint8_t *) str_steal (&log);
|
||||
fclose (fp);
|
||||
}
|
||||
|
||||
// XXX: We log failures to the global buffer,
|
||||
// so the client just receives nothing if there is no log file.
|
||||
|
||||
free (path);
|
||||
relay_send (c);
|
||||
}
|
||||
|
||||
static bool
|
||||
client_process_message (struct client *c,
|
||||
struct msg_unpacker *r, struct relay_command_message *m)
|
||||
{
|
||||
if (!relay_command_message_deserialize (m, r)
|
||||
|| msg_unpacker_get_available (r))
|
||||
{
|
||||
print_error ("deserialization failed, killing client");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *buffer_name = client_message_buffer_name (m);
|
||||
struct buffer *buffer = NULL;
|
||||
if (buffer_name && !(buffer = buffer_by_name (c->ctx, buffer_name)))
|
||||
{
|
||||
relay_prepare_error (c->ctx, m->command_seq, "Unknown buffer");
|
||||
relay_send (c);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (m->data.command)
|
||||
{
|
||||
case RELAY_COMMAND_HELLO:
|
||||
if (m->data.hello.version != RELAY_VERSION)
|
||||
{
|
||||
// TODO: This should send back an error message and shut down.
|
||||
print_error ("protocol version mismatch, killing client");
|
||||
return false;
|
||||
}
|
||||
c->initialized = true;
|
||||
client_resync (c);
|
||||
break;
|
||||
case RELAY_COMMAND_PING:
|
||||
relay_prepare_ping (c->ctx);
|
||||
relay_send (c);
|
||||
break;
|
||||
case RELAY_COMMAND_ACTIVE:
|
||||
reset_autoaway (c->ctx);
|
||||
break;
|
||||
case RELAY_COMMAND_BUFFER_COMPLETE:
|
||||
// TODO: Run the completion machinery.
|
||||
relay_prepare_error (c->ctx, m->command_seq, "Not implemented");
|
||||
relay_send (c);
|
||||
break;
|
||||
case RELAY_COMMAND_BUFFER_INPUT:
|
||||
(void) process_input_utf8 (c->ctx,
|
||||
buffer, m->data.buffer_input.text.str, 0);
|
||||
break;
|
||||
case RELAY_COMMAND_BUFFER_ACTIVATE:
|
||||
buffer_activate (c->ctx, buffer);
|
||||
break;
|
||||
case RELAY_COMMAND_BUFFER_LOG:
|
||||
client_process_buffer_log (c, m->command_seq, buffer);
|
||||
break;
|
||||
default:
|
||||
print_warning ("unhandled client command");
|
||||
relay_prepare_error (c->ctx, m->command_seq, "Unknown command");
|
||||
relay_send (c);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
client_process_buffer (struct client *c)
|
||||
{
|
||||
struct str *buf = &c->read_buffer;
|
||||
size_t offset = 0;
|
||||
while (true)
|
||||
{
|
||||
uint32_t frame_len = 0;
|
||||
struct msg_unpacker r =
|
||||
msg_unpacker_make (buf->str + offset, buf->len - offset);
|
||||
if (!msg_unpacker_u32 (&r, &frame_len))
|
||||
break;
|
||||
|
||||
r.len = MIN (r.len, sizeof frame_len + frame_len);
|
||||
if (msg_unpacker_get_available (&r) < frame_len)
|
||||
break;
|
||||
|
||||
struct relay_command_message m = {};
|
||||
bool ok = client_process_message (c, &r, &m);
|
||||
relay_command_message_free (&m);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
offset += r.offset;
|
||||
}
|
||||
|
||||
str_remove_slice (buf, 0, offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Tests -------------------------------------------------------------------
|
||||
|
||||
// The application is quite monolithic and can only be partially unit-tested.
|
||||
|
||||
Reference in New Issue
Block a user