Compare commits
6 Commits
62773acaa0
...
b4ee523628
Author | SHA1 | Date | |
---|---|---|---|
b4ee523628 | |||
c3a52b9e4c | |||
96fc12bc4c | |||
1493d9998b | |||
36f77e74fb | |||
23deca45c9 |
40
xC-proto
40
xC-proto
@ -19,8 +19,8 @@ struct CommandMessage {
|
|||||||
case HELLO:
|
case HELLO:
|
||||||
u32 version;
|
u32 version;
|
||||||
// If the version check succeeds, the client will receive
|
// If the version check succeeds, the client will receive
|
||||||
// an initial stream of BUFFER_UPDATE, BUFFER_STATS, BUFFER_LINE,
|
// an initial stream of SERVER_UPDATE, BUFFER_UPDATE, BUFFER_STATS,
|
||||||
// and finally a BUFFER_ACTIVATE message.
|
// BUFFER_LINE, and finally a BUFFER_ACTIVATE message.
|
||||||
case ACTIVE:
|
case ACTIVE:
|
||||||
void;
|
void;
|
||||||
case BUFFER_INPUT:
|
case BUFFER_INPUT:
|
||||||
@ -56,14 +56,33 @@ struct EventMessage {
|
|||||||
BUFFER_ACTIVATE,
|
BUFFER_ACTIVATE,
|
||||||
BUFFER_LINE,
|
BUFFER_LINE,
|
||||||
BUFFER_CLEAR,
|
BUFFER_CLEAR,
|
||||||
|
SERVER_UPDATE,
|
||||||
|
SERVER_RENAME,
|
||||||
|
SERVER_REMOVE,
|
||||||
ERROR,
|
ERROR,
|
||||||
RESPONSE,
|
RESPONSE,
|
||||||
} event) {
|
} event) {
|
||||||
case PING:
|
case PING:
|
||||||
void;
|
void;
|
||||||
|
|
||||||
case BUFFER_UPDATE:
|
case BUFFER_UPDATE:
|
||||||
string buffer_name;
|
string buffer_name;
|
||||||
bool hide_unimportant;
|
bool hide_unimportant;
|
||||||
|
union BufferContext switch (enum BufferKind {
|
||||||
|
GLOBAL,
|
||||||
|
SERVER,
|
||||||
|
CHANNEL,
|
||||||
|
PRIVATE_MESSAGE,
|
||||||
|
} kind) {
|
||||||
|
case GLOBAL:
|
||||||
|
void;
|
||||||
|
case SERVER:
|
||||||
|
string server_name;
|
||||||
|
case CHANNEL:
|
||||||
|
string server_name;
|
||||||
|
case PRIVATE_MESSAGE:
|
||||||
|
string server_name;
|
||||||
|
} context;
|
||||||
case BUFFER_STATS:
|
case BUFFER_STATS:
|
||||||
string buffer_name;
|
string buffer_name;
|
||||||
// These are cumulative, even for lines flushed out from buffers.
|
// These are cumulative, even for lines flushed out from buffers.
|
||||||
@ -130,6 +149,23 @@ struct EventMessage {
|
|||||||
case BUFFER_CLEAR:
|
case BUFFER_CLEAR:
|
||||||
string buffer_name;
|
string buffer_name;
|
||||||
|
|
||||||
|
case SERVER_UPDATE:
|
||||||
|
string server_name;
|
||||||
|
enum ServerState {
|
||||||
|
DISCONNECTED,
|
||||||
|
CONNECTING,
|
||||||
|
CONNECTED,
|
||||||
|
REGISTERED,
|
||||||
|
DISCONNECTING,
|
||||||
|
} state;
|
||||||
|
case SERVER_RENAME:
|
||||||
|
// Buffers aren't sent updates for in this circumstance,
|
||||||
|
// as that wouldn't be sufficiently atomic anyway.
|
||||||
|
string server_name;
|
||||||
|
string new;
|
||||||
|
case SERVER_REMOVE:
|
||||||
|
string server_name;
|
||||||
|
|
||||||
// Restriction: command_seq strictly follows the sequence received
|
// Restriction: command_seq strictly follows the sequence received
|
||||||
// by the relay, across both of these replies.
|
// by the relay, across both of these replies.
|
||||||
case ERROR:
|
case ERROR:
|
||||||
|
161
xC.c
161
xC.c
@ -3109,6 +3109,28 @@ relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
|
|||||||
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
e->hide_unimportant = buffer->hide_unimportant;
|
e->hide_unimportant = buffer->hide_unimportant;
|
||||||
|
|
||||||
|
struct str *server_name = NULL;
|
||||||
|
switch (buffer->type)
|
||||||
|
{
|
||||||
|
case BUFFER_GLOBAL:
|
||||||
|
e->context.kind = RELAY_BUFFER_KIND_GLOBAL;
|
||||||
|
break;
|
||||||
|
case BUFFER_SERVER:
|
||||||
|
e->context.kind = RELAY_BUFFER_KIND_SERVER;
|
||||||
|
server_name = &e->context.server.server_name;
|
||||||
|
break;
|
||||||
|
case BUFFER_CHANNEL:
|
||||||
|
e->context.kind = RELAY_BUFFER_KIND_CHANNEL;
|
||||||
|
server_name = &e->context.channel.server_name;
|
||||||
|
break;
|
||||||
|
case BUFFER_PM:
|
||||||
|
e->context.kind = RELAY_BUFFER_KIND_PRIVATE_MESSAGE;
|
||||||
|
server_name = &e->context.private_message.server_name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (server_name)
|
||||||
|
*server_name = str_from_cstr (buffer->server->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -3248,6 +3270,51 @@ relay_prepare_buffer_clear (struct app_context *ctx,
|
|||||||
e->buffer_name = str_from_cstr (buffer->name);
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum relay_server_state
|
||||||
|
relay_server_state_for_server (struct server *s)
|
||||||
|
{
|
||||||
|
switch (s->state)
|
||||||
|
{
|
||||||
|
case IRC_DISCONNECTED: return RELAY_SERVER_STATE_DISCONNECTED;
|
||||||
|
case IRC_CONNECTING: return RELAY_SERVER_STATE_CONNECTING;
|
||||||
|
case IRC_CONNECTED: return RELAY_SERVER_STATE_CONNECTED;
|
||||||
|
case IRC_REGISTERED: return RELAY_SERVER_STATE_REGISTERED;
|
||||||
|
case IRC_CLOSING:
|
||||||
|
case IRC_HALF_CLOSED: return RELAY_SERVER_STATE_DISCONNECTING;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_server_update (struct app_context *ctx, struct server *s)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_server_update *e = &m->data.server_update;
|
||||||
|
e->event = RELAY_EVENT_SERVER_UPDATE;
|
||||||
|
e->server_name = str_from_cstr (s->name);
|
||||||
|
e->state = relay_server_state_for_server (s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_server_rename (struct app_context *ctx, struct server *s,
|
||||||
|
const char *new_name)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_server_rename *e = &m->data.server_rename;
|
||||||
|
e->event = RELAY_EVENT_SERVER_RENAME;
|
||||||
|
e->server_name = str_from_cstr (s->name);
|
||||||
|
e->new = str_from_cstr (new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_server_remove (struct app_context *ctx, struct server *s)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_server_remove *e = &m->data.server_remove;
|
||||||
|
e->event = RELAY_EVENT_SERVER_REMOVE;
|
||||||
|
e->server_name = str_from_cstr (s->name);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
relay_prepare_error (struct app_context *ctx, uint32_t seq, const char *message)
|
relay_prepare_error (struct app_context *ctx, uint32_t seq, const char *message)
|
||||||
{
|
{
|
||||||
@ -4890,14 +4957,14 @@ buffer_add (struct app_context *ctx, struct buffer *buffer)
|
|||||||
{
|
{
|
||||||
hard_assert (!buffer_by_name (ctx, buffer->name));
|
hard_assert (!buffer_by_name (ctx, buffer->name));
|
||||||
|
|
||||||
|
relay_prepare_buffer_update (ctx, buffer);
|
||||||
|
relay_broadcast (ctx);
|
||||||
|
|
||||||
str_map_set (&ctx->buffers_by_name, buffer->name, buffer);
|
str_map_set (&ctx->buffers_by_name, buffer->name, buffer);
|
||||||
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
|
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
|
||||||
|
|
||||||
buffer_open_log_file (ctx, 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
|
// Normally this doesn't cause changes in the prompt but a prompt hook
|
||||||
// could decide to show some information for all buffers nonetheless
|
// could decide to show some information for all buffers nonetheless
|
||||||
refresh_prompt (ctx);
|
refresh_prompt (ctx);
|
||||||
@ -4909,6 +4976,9 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
|
|||||||
hard_assert (buffer != ctx->current_buffer);
|
hard_assert (buffer != ctx->current_buffer);
|
||||||
hard_assert (buffer != ctx->global_buffer);
|
hard_assert (buffer != ctx->global_buffer);
|
||||||
|
|
||||||
|
relay_prepare_buffer_remove (ctx, buffer);
|
||||||
|
relay_broadcast (ctx);
|
||||||
|
|
||||||
CALL_ (ctx->input, buffer_destroy, buffer->input_data);
|
CALL_ (ctx->input, buffer_destroy, buffer->input_data);
|
||||||
buffer->input_data = NULL;
|
buffer->input_data = NULL;
|
||||||
|
|
||||||
@ -4924,9 +4994,6 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer)
|
|||||||
if (buffer->type == BUFFER_SERVER)
|
if (buffer->type == BUFFER_SERVER)
|
||||||
buffer->server->buffer = NULL;
|
buffer->server->buffer = NULL;
|
||||||
|
|
||||||
relay_prepare_buffer_remove (ctx, buffer);
|
|
||||||
relay_broadcast (ctx);
|
|
||||||
|
|
||||||
str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
|
str_map_set (&ctx->buffers_by_name, buffer->name, NULL);
|
||||||
LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
|
LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer);
|
||||||
buffer_unref (buffer);
|
buffer_unref (buffer);
|
||||||
@ -5040,6 +5107,9 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer)
|
|||||||
if (ctx->current_buffer == buffer)
|
if (ctx->current_buffer == buffer)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
relay_prepare_buffer_activate (ctx, buffer);
|
||||||
|
relay_broadcast (ctx);
|
||||||
|
|
||||||
// This is the only place where the unread messages marker
|
// This is the only place where the unread messages marker
|
||||||
// and highlight indicator are reset
|
// and highlight indicator are reset
|
||||||
if (ctx->current_buffer)
|
if (ctx->current_buffer)
|
||||||
@ -5056,9 +5126,6 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer)
|
|||||||
ctx->last_buffer = ctx->current_buffer;
|
ctx->last_buffer = ctx->current_buffer;
|
||||||
ctx->current_buffer = buffer;
|
ctx->current_buffer = buffer;
|
||||||
|
|
||||||
relay_prepare_buffer_activate (ctx, buffer);
|
|
||||||
relay_broadcast (ctx);
|
|
||||||
|
|
||||||
refresh_prompt (ctx);
|
refresh_prompt (ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5146,14 +5213,14 @@ buffer_rename (struct app_context *ctx,
|
|||||||
static void
|
static void
|
||||||
buffer_clear (struct app_context *ctx, struct buffer *buffer)
|
buffer_clear (struct app_context *ctx, struct buffer *buffer)
|
||||||
{
|
{
|
||||||
|
relay_prepare_buffer_clear (ctx, buffer);
|
||||||
|
relay_broadcast (ctx);
|
||||||
|
|
||||||
LIST_FOR_EACH (struct buffer_line, iter, buffer->lines)
|
LIST_FOR_EACH (struct buffer_line, iter, buffer->lines)
|
||||||
buffer_line_destroy (iter);
|
buffer_line_destroy (iter);
|
||||||
|
|
||||||
buffer->lines = buffer->lines_tail = NULL;
|
buffer->lines = buffer->lines_tail = NULL;
|
||||||
buffer->lines_count = 0;
|
buffer->lines_count = 0;
|
||||||
|
|
||||||
relay_prepare_buffer_clear (ctx, buffer);
|
|
||||||
relay_broadcast (ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct buffer *
|
static struct buffer *
|
||||||
@ -5665,6 +5732,17 @@ irc_send (struct server *s, const char *format, ...)
|
|||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_set_state (struct server *s, enum server_state state)
|
||||||
|
{
|
||||||
|
s->state = state;
|
||||||
|
|
||||||
|
relay_prepare_server_update (s->ctx, s);
|
||||||
|
relay_broadcast (s->ctx);
|
||||||
|
|
||||||
|
refresh_prompt (s->ctx);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
irc_real_shutdown (struct server *s)
|
irc_real_shutdown (struct server *s)
|
||||||
{
|
{
|
||||||
@ -5680,7 +5758,7 @@ irc_real_shutdown (struct server *s)
|
|||||||
if (!soft_assert (errno == EINTR))
|
if (!soft_assert (errno == EINTR))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
s->state = IRC_HALF_CLOSED;
|
irc_set_state (s, IRC_HALF_CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -5691,7 +5769,7 @@ irc_shutdown (struct server *s)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO: set a timer to cut the connection if we don't receive an EOF
|
// TODO: set a timer to cut the connection if we don't receive an EOF
|
||||||
s->state = IRC_CLOSING;
|
irc_set_state (s, IRC_CLOSING);
|
||||||
|
|
||||||
// Either there's still some data in the write buffer and we wait
|
// Either there's still some data in the write buffer and we wait
|
||||||
// until they're sent, or we send an EOF to the server right away
|
// until they're sent, or we send an EOF to the server right away
|
||||||
@ -5713,7 +5791,7 @@ irc_destroy_connector (struct server *s)
|
|||||||
s->socks_conn = NULL;
|
s->socks_conn = NULL;
|
||||||
|
|
||||||
// Not connecting anymore
|
// Not connecting anymore
|
||||||
s->state = IRC_DISCONNECTED;
|
irc_set_state (s, IRC_DISCONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -5744,7 +5822,7 @@ irc_destroy_transport (struct server *s)
|
|||||||
poller_fd_reset (&s->socket_event);
|
poller_fd_reset (&s->socket_event);
|
||||||
xclose (s->socket);
|
xclose (s->socket);
|
||||||
s->socket = -1;
|
s->socket = -1;
|
||||||
s->state = IRC_DISCONNECTED;
|
irc_set_state (s, IRC_DISCONNECTED);
|
||||||
|
|
||||||
str_reset (&s->read_buffer);
|
str_reset (&s->read_buffer);
|
||||||
str_reset (&s->write_buffer);
|
str_reset (&s->write_buffer);
|
||||||
@ -5805,8 +5883,6 @@ irc_disconnect (struct server *s)
|
|||||||
s->reconnect_attempt = 0;
|
s->reconnect_attempt = 0;
|
||||||
irc_queue_reconnect (s);
|
irc_queue_reconnect (s);
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh_prompt (s->ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -6558,7 +6634,7 @@ irc_finish_connection (struct server *s, int socket, const char *hostname)
|
|||||||
}
|
}
|
||||||
|
|
||||||
log_server_status (s, s->buffer, "Connection established");
|
log_server_status (s, s->buffer, "Connection established");
|
||||||
s->state = IRC_CONNECTED;
|
irc_set_state (s, IRC_CONNECTED);
|
||||||
|
|
||||||
s->socket_event = poller_fd_make (&ctx->poller, s->socket);
|
s->socket_event = poller_fd_make (&ctx->poller, s->socket);
|
||||||
s->socket_event.dispatcher = (poller_fd_fn) on_irc_ready;
|
s->socket_event.dispatcher = (poller_fd_fn) on_irc_ready;
|
||||||
@ -6567,8 +6643,6 @@ irc_finish_connection (struct server *s, int socket, const char *hostname)
|
|||||||
irc_update_poller (s, NULL);
|
irc_update_poller (s, NULL);
|
||||||
irc_reset_connection_timeouts (s);
|
irc_reset_connection_timeouts (s);
|
||||||
irc_register (s);
|
irc_register (s);
|
||||||
|
|
||||||
refresh_prompt (s->ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
@ -6715,7 +6789,7 @@ irc_initiate_connect (struct server *s)
|
|||||||
irc_queue_reconnect (s);
|
irc_queue_reconnect (s);
|
||||||
}
|
}
|
||||||
else if (s->state != IRC_CONNECTED)
|
else if (s->state != IRC_CONNECTED)
|
||||||
s->state = IRC_CONNECTING;
|
irc_set_state (s, IRC_CONNECTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Input prompt ------------------------------------------------------------
|
// --- Input prompt ------------------------------------------------------------
|
||||||
@ -8263,8 +8337,7 @@ irc_on_registered (struct server *s, const char *nickname)
|
|||||||
str_reset (&s->irc_user_mode);
|
str_reset (&s->irc_user_mode);
|
||||||
cstr_set (&s->irc_user_host, NULL);
|
cstr_set (&s->irc_user_host, NULL);
|
||||||
|
|
||||||
s->state = IRC_REGISTERED;
|
irc_set_state (s, IRC_REGISTERED);
|
||||||
refresh_prompt (s->ctx);
|
|
||||||
|
|
||||||
// XXX: we can also use WHOIS if it's not supported (optional by RFC 2812)
|
// XXX: we can also use WHOIS if it's not supported (optional by RFC 2812)
|
||||||
// TODO: maybe rather always use RPL_ISUPPORT NICKLEN & USERLEN & HOSTLEN
|
// TODO: maybe rather always use RPL_ISUPPORT NICKLEN & USERLEN & HOSTLEN
|
||||||
@ -9422,6 +9495,9 @@ server_remove (struct app_context *ctx, struct server *s)
|
|||||||
if (s->buffer)
|
if (s->buffer)
|
||||||
buffer_remove_safe (ctx, s->buffer);
|
buffer_remove_safe (ctx, s->buffer);
|
||||||
|
|
||||||
|
relay_prepare_server_remove (ctx, s);
|
||||||
|
relay_broadcast (ctx);
|
||||||
|
|
||||||
struct str_map_unset_iter iter =
|
struct str_map_unset_iter iter =
|
||||||
str_map_unset_iter_make (&s->irc_buffer_map);
|
str_map_unset_iter_make (&s->irc_buffer_map);
|
||||||
struct buffer *buffer;
|
struct buffer *buffer;
|
||||||
@ -9445,6 +9521,10 @@ static void
|
|||||||
server_rename (struct app_context *ctx, struct server *s, const char *new_name)
|
server_rename (struct app_context *ctx, struct server *s, const char *new_name)
|
||||||
{
|
{
|
||||||
hard_assert (!str_map_find (&ctx->servers, new_name));
|
hard_assert (!str_map_find (&ctx->servers, new_name));
|
||||||
|
|
||||||
|
relay_prepare_server_rename (ctx, s, new_name);
|
||||||
|
relay_broadcast (ctx);
|
||||||
|
|
||||||
str_map_set (&ctx->servers, new_name,
|
str_map_set (&ctx->servers, new_name,
|
||||||
str_map_steal (&ctx->servers, s->name));
|
str_map_steal (&ctx->servers, s->name));
|
||||||
|
|
||||||
@ -9940,20 +10020,27 @@ lua_server_gc (lua_State *L)
|
|||||||
return lua_weak_gc (L, &lua_server_info);
|
return lua_weak_gc (L, &lua_server_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
lua_server_state_to_string (enum server_state state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case IRC_DISCONNECTED: return "disconnected";
|
||||||
|
case IRC_CONNECTING: return "connecting";
|
||||||
|
case IRC_CONNECTED: return "connected";
|
||||||
|
case IRC_REGISTERED: return "registered";
|
||||||
|
case IRC_CLOSING: return "closing";
|
||||||
|
case IRC_HALF_CLOSED: return "half-closed";
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
lua_server_get_state (lua_State *L)
|
lua_server_get_state (lua_State *L)
|
||||||
{
|
{
|
||||||
struct lua_weak *wrapper = lua_weak_deref (L, &lua_server_info);
|
struct lua_weak *wrapper = lua_weak_deref (L, &lua_server_info);
|
||||||
struct server *server = wrapper->object;
|
struct server *server = wrapper->object;
|
||||||
switch (server->state)
|
lua_pushstring (L, lua_server_state_to_string (server->state));
|
||||||
{
|
|
||||||
case IRC_DISCONNECTED: lua_pushstring (L, "disconnected"); break;
|
|
||||||
case IRC_CONNECTING: lua_pushstring (L, "connecting"); break;
|
|
||||||
case IRC_CONNECTED: lua_pushstring (L, "connected"); break;
|
|
||||||
case IRC_REGISTERED: lua_pushstring (L, "registered"); break;
|
|
||||||
case IRC_CLOSING: lua_pushstring (L, "closing"); break;
|
|
||||||
case IRC_HALF_CLOSED: lua_pushstring (L, "half_closed"); break;
|
|
||||||
}
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15312,6 +15399,14 @@ init_poller_events (struct app_context *ctx)
|
|||||||
static void
|
static void
|
||||||
client_resync (struct client *c)
|
client_resync (struct client *c)
|
||||||
{
|
{
|
||||||
|
struct str_map_iter iter = str_map_iter_make (&c->ctx->servers);
|
||||||
|
struct server *s;
|
||||||
|
while ((s = str_map_iter_next (&iter)))
|
||||||
|
{
|
||||||
|
relay_prepare_server_update (c->ctx, s);
|
||||||
|
relay_send (c);
|
||||||
|
}
|
||||||
|
|
||||||
LIST_FOR_EACH (struct buffer, buffer, c->ctx->buffers)
|
LIST_FOR_EACH (struct buffer, buffer, c->ctx->buffers)
|
||||||
{
|
{
|
||||||
relay_prepare_buffer_update (c->ctx, buffer);
|
relay_prepare_buffer_update (c->ctx, buffer);
|
||||||
|
@ -17,6 +17,10 @@ body {
|
|||||||
padding: .05em .3em;
|
padding: .05em .3em;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
border-top: 3px solid #ccc;
|
border-top: 3px solid #ccc;
|
||||||
border-bottom: 2px solid #888;
|
border-bottom: 2px solid #888;
|
||||||
@ -39,10 +43,8 @@ body {
|
|||||||
bottom: -1px;
|
bottom: -1px;
|
||||||
background: #ccc;
|
background: #ccc;
|
||||||
}
|
}
|
||||||
.title {
|
button {
|
||||||
display: flex;
|
font: inherit;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: baseline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.middle {
|
.middle {
|
||||||
@ -112,9 +114,8 @@ body {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.unread {
|
.unread {
|
||||||
height: 1px;
|
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
background: #ff5f00;
|
border-top: 1px solid #ff5f00;
|
||||||
}
|
}
|
||||||
.time {
|
.time {
|
||||||
padding: .1em .3em;
|
padding: .1em .3em;
|
||||||
@ -122,6 +123,13 @@ body {
|
|||||||
color: #bbb;
|
color: #bbb;
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
.time.hidden:after {
|
||||||
|
border-top: .2em dotted #ccc;
|
||||||
|
display: block;
|
||||||
|
width: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
.mark {
|
.mark {
|
||||||
padding-right: .3em;
|
padding-right: .3em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
141
xP/public/xP.js
141
xP/public/xP.js
@ -129,6 +129,9 @@ class RelayRpc extends EventTarget {
|
|||||||
|
|
||||||
// ---- Utilities --------------------------------------------------------------
|
// ---- Utilities --------------------------------------------------------------
|
||||||
|
|
||||||
|
function utf8Encode(s) { return new TextEncoder().encode(s) }
|
||||||
|
function utf8Decode(s) { return new TextDecoder().decode(s) }
|
||||||
|
|
||||||
// On macOS, the Alt/Option key transforms characters, which basically breaks
|
// On macOS, the Alt/Option key transforms characters, which basically breaks
|
||||||
// all event.altKey shortcuts, so require combining them with Control as well
|
// all event.altKey shortcuts, so require combining them with Control as well
|
||||||
// on that system.
|
// on that system.
|
||||||
@ -153,6 +156,33 @@ function beep() {
|
|||||||
oscillator.stop(audioContext.currentTime + 0.1)
|
oscillator.stop(audioContext.currentTime + 0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let iconLink = undefined
|
||||||
|
let iconState = undefined
|
||||||
|
|
||||||
|
function updateIcon(highlighted) {
|
||||||
|
if (iconState === highlighted)
|
||||||
|
return
|
||||||
|
|
||||||
|
iconState = highlighted
|
||||||
|
let canvas = document.createElement('canvas')
|
||||||
|
canvas.width = 32
|
||||||
|
canvas.height = 32
|
||||||
|
|
||||||
|
let ctx = canvas.getContext('2d')
|
||||||
|
ctx.arc(16, 16, 12, 0, 2 * Math.PI)
|
||||||
|
ctx.fillStyle = highlighted ? '#ff5f00' : '#ccc'
|
||||||
|
ctx.fill()
|
||||||
|
|
||||||
|
if (iconLink === undefined) {
|
||||||
|
iconLink = document.createElement('link')
|
||||||
|
iconLink.type = 'image/png'
|
||||||
|
iconLink.rel = 'icon'
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(iconLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
iconLink.href = canvas.toDataURL();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Event processing -------------------------------------------------------
|
// ---- Event processing -------------------------------------------------------
|
||||||
|
|
||||||
let rpc = new RelayRpc(proxy)
|
let rpc = new RelayRpc(proxy)
|
||||||
@ -163,6 +193,8 @@ let bufferCurrent = undefined
|
|||||||
let bufferLog = undefined
|
let bufferLog = undefined
|
||||||
let bufferAutoscroll = true
|
let bufferAutoscroll = true
|
||||||
|
|
||||||
|
let servers = new Map()
|
||||||
|
|
||||||
function bufferResetStats(b) {
|
function bufferResetStats(b) {
|
||||||
b.newMessages = 0
|
b.newMessages = 0
|
||||||
b.newUnimportantMessages = 0
|
b.newUnimportantMessages = 0
|
||||||
@ -204,6 +236,8 @@ rpc.connect().then(result => {
|
|||||||
bufferLog = undefined
|
bufferLog = undefined
|
||||||
bufferAutoscroll = true
|
bufferAutoscroll = true
|
||||||
|
|
||||||
|
servers.clear()
|
||||||
|
|
||||||
rpc.send({command: 'Hello', version: 1})
|
rpc.send({command: 'Hello', version: 1})
|
||||||
connecting = false
|
connecting = false
|
||||||
m.redraw()
|
m.redraw()
|
||||||
@ -220,6 +254,8 @@ rpc.addEventListener('Ping', event => {
|
|||||||
rpc.send({command: 'PingResponse', eventSeq: event.detail.eventSeq})
|
rpc.send({command: 'PingResponse', eventSeq: event.detail.eventSeq})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ~~~ Buffer events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
rpc.addEventListener('BufferUpdate', event => {
|
rpc.addEventListener('BufferUpdate', event => {
|
||||||
let e = event.detail, b = buffers.get(e.bufferName)
|
let e = event.detail, b = buffers.get(e.bufferName)
|
||||||
if (b === undefined) {
|
if (b === undefined) {
|
||||||
@ -230,7 +266,10 @@ rpc.addEventListener('BufferUpdate', event => {
|
|||||||
}))
|
}))
|
||||||
bufferResetStats(b)
|
bufferResetStats(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.hideUnimportant = e.hideUnimportant
|
b.hideUnimportant = e.hideUnimportant
|
||||||
|
b.kind = e.context.kind
|
||||||
|
b.server = servers.get(e.context.serverName)
|
||||||
})
|
})
|
||||||
|
|
||||||
rpc.addEventListener('BufferStats', event => {
|
rpc.addEventListener('BufferStats', event => {
|
||||||
@ -324,9 +363,8 @@ rpc.addEventListener('BufferLine', event => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Find some way of highlighting the tab in a browser.
|
if (line.isHighlight ||
|
||||||
// TODO: Also highlight on unseen private messages, like xC does.
|
(!visible && b.kind === 'PrivateMessage' && !line.isUnimportant)) {
|
||||||
if (line.isHighlight) {
|
|
||||||
beep()
|
beep()
|
||||||
if (!visible)
|
if (!visible)
|
||||||
b.highlighted = true
|
b.highlighted = true
|
||||||
@ -339,6 +377,26 @@ rpc.addEventListener('BufferClear', event => {
|
|||||||
b.lines.length = 0
|
b.lines.length = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ~~~ Server events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
rpc.addEventListener('ServerUpdate', event => {
|
||||||
|
let e = event.detail, s = servers.get(e.serverName)
|
||||||
|
if (s === undefined)
|
||||||
|
servers.set(e.serverName, (s = {}))
|
||||||
|
s.state = e.state
|
||||||
|
})
|
||||||
|
|
||||||
|
rpc.addEventListener('ServerRename', event => {
|
||||||
|
let e = event.detail
|
||||||
|
servers.set(e.new, servers.get(e.serverName))
|
||||||
|
servers.delete(e.serverName)
|
||||||
|
})
|
||||||
|
|
||||||
|
rpc.addEventListener('ServerRemove', event => {
|
||||||
|
let e = event.detail
|
||||||
|
servers.delete(e.serverName)
|
||||||
|
})
|
||||||
|
|
||||||
// --- Colours -----------------------------------------------------------------
|
// --- Colours -----------------------------------------------------------------
|
||||||
|
|
||||||
let palette = [
|
let palette = [
|
||||||
@ -383,13 +441,16 @@ let Toolbar = {
|
|||||||
|
|
||||||
let BufferList = {
|
let BufferList = {
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
|
let highlighted = false
|
||||||
let items = Array.from(buffers, ([name, b]) => {
|
let items = Array.from(buffers, ([name, b]) => {
|
||||||
let classes = [], displayName = name
|
let classes = [], displayName = name
|
||||||
if (name == bufferCurrent) {
|
if (name == bufferCurrent) {
|
||||||
classes.push('current')
|
classes.push('current')
|
||||||
} else {
|
} else {
|
||||||
if (b.highlighted)
|
if (b.highlighted) {
|
||||||
classes.push('highlighted')
|
classes.push('highlighted')
|
||||||
|
highlighted = true
|
||||||
|
}
|
||||||
if (b.newMessages) {
|
if (b.newMessages) {
|
||||||
classes.push('activity')
|
classes.push('activity')
|
||||||
displayName += ` (${b.newMessages})`
|
displayName += ` (${b.newMessages})`
|
||||||
@ -400,6 +461,8 @@ let BufferList = {
|
|||||||
class: classes.join(' '),
|
class: classes.join(' '),
|
||||||
}, displayName)
|
}, displayName)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
updateIcon(highlighted)
|
||||||
return m('.list', {}, items)
|
return m('.list', {}, items)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -517,13 +580,20 @@ let Buffer = {
|
|||||||
return m('.buffer')
|
return m('.buffer')
|
||||||
|
|
||||||
let lastDateMark = undefined
|
let lastDateMark = undefined
|
||||||
|
let squashing = false
|
||||||
let markBefore = b.lines.length
|
let markBefore = b.lines.length
|
||||||
- b.newMessages - b.newUnimportantMessages
|
- b.newMessages - b.newUnimportantMessages
|
||||||
b.lines.forEach((line, i) => {
|
b.lines.forEach((line, i) => {
|
||||||
if (i == markBefore)
|
if (i == markBefore)
|
||||||
lines.push(m('.unread'))
|
lines.push(m('.unread'))
|
||||||
if (line.isUnimportant && b.hideUnimportant)
|
|
||||||
|
if (!line.isUnimportant || !b.hideUnimportant) {
|
||||||
|
squashing = false
|
||||||
|
} else if (squashing) {
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
squashing = true
|
||||||
|
}
|
||||||
|
|
||||||
let date = new Date(line.when)
|
let date = new Date(line.when)
|
||||||
let dateMark = date.toLocaleDateString()
|
let dateMark = date.toLocaleDateString()
|
||||||
@ -531,6 +601,11 @@ let Buffer = {
|
|||||||
lines.push(m('.date', {}, dateMark))
|
lines.push(m('.date', {}, dateMark))
|
||||||
lastDateMark = dateMark
|
lastDateMark = dateMark
|
||||||
}
|
}
|
||||||
|
if (squashing) {
|
||||||
|
lines.push(m('.time.hidden'))
|
||||||
|
lines.push(m('.content'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let attrs = {}
|
let attrs = {}
|
||||||
if (line.leaked)
|
if (line.leaked)
|
||||||
@ -580,6 +655,21 @@ let BufferContainer = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Status = {
|
||||||
|
view: vnode => {
|
||||||
|
let b = buffers.get(bufferCurrent)
|
||||||
|
if (b === undefined)
|
||||||
|
return m('.status', {}, 'Synchronizing...')
|
||||||
|
|
||||||
|
let status = `${bufferCurrent}`
|
||||||
|
if (b.hideUnimportant)
|
||||||
|
status += `<H>`
|
||||||
|
if (b.server !== undefined)
|
||||||
|
status += ` (${b.server.state})`
|
||||||
|
return m('.status', {}, status)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let Input = {
|
let Input = {
|
||||||
counter: 0,
|
counter: 0,
|
||||||
stamp: textarea => {
|
stamp: textarea => {
|
||||||
@ -598,15 +688,22 @@ let Input = {
|
|||||||
command: 'BufferComplete',
|
command: 'BufferComplete',
|
||||||
bufferName: bufferCurrent,
|
bufferName: bufferCurrent,
|
||||||
text: textarea.value,
|
text: textarea.value,
|
||||||
position: textarea.selectionEnd,
|
position: utf8Encode(
|
||||||
|
textarea.value.slice(0, textarea.selectionEnd)).length,
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
if (!Input.stamp(textarea).every((v, k) => v === state[k]))
|
if (!Input.stamp(textarea).every((v, k) => v === state[k]))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
let preceding = utf8Encode(textarea.value).slice(0, resp.start)
|
||||||
|
let start = utf8Decode(preceding).length
|
||||||
|
|
||||||
// TODO: Somehow display remaining options, or cycle through.
|
// TODO: Somehow display remaining options, or cycle through.
|
||||||
if (resp.completions.length)
|
if (resp.completions.length) {
|
||||||
textarea.setRangeText(resp.completions[0],
|
textarea.setRangeText(resp.completions[0],
|
||||||
resp.start, textarea.selectionEnd, 'end')
|
start, textarea.selectionEnd, 'end')
|
||||||
|
} else {
|
||||||
|
beep()
|
||||||
|
}
|
||||||
if (resp.completions.length === 1)
|
if (resp.completions.length === 1)
|
||||||
textarea.setRangeText(' ',
|
textarea.setRangeText(' ',
|
||||||
textarea.selectionStart, textarea.selectionEnd, 'end')
|
textarea.selectionStart, textarea.selectionEnd, 'end')
|
||||||
@ -708,8 +805,7 @@ let Main = {
|
|||||||
return m('.xP', {}, [
|
return m('.xP', {}, [
|
||||||
m('.title', {}, [`xP (${state})`, m(Toolbar)]),
|
m('.title', {}, [`xP (${state})`, m(Toolbar)]),
|
||||||
m('.middle', {}, [m(BufferList), m(BufferContainer)]),
|
m('.middle', {}, [m(BufferList), m(BufferContainer)]),
|
||||||
// TODO: Indicate hideUnimportant.
|
m(Status),
|
||||||
m('.status', {}, bufferCurrent),
|
|
||||||
m(Input),
|
m(Input),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@ -721,11 +817,8 @@ document.addEventListener('keydown', event => {
|
|||||||
if (rpc.ws == undefined || !hasShortcutModifiers(event))
|
if (rpc.ws == undefined || !hasShortcutModifiers(event))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
let names = undefined
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Tab':
|
|
||||||
if (bufferLast !== undefined)
|
|
||||||
bufferActivate(bufferLast)
|
|
||||||
break
|
|
||||||
case 'h':
|
case 'h':
|
||||||
bufferToggleLog()
|
bufferToggleLog()
|
||||||
break
|
break
|
||||||
@ -743,6 +836,26 @@ document.addEventListener('keydown', event => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case 'Tab':
|
||||||
|
if (bufferLast !== undefined)
|
||||||
|
bufferActivate(bufferLast)
|
||||||
|
break
|
||||||
|
case 'PageUp':
|
||||||
|
names = [...buffers.keys()]
|
||||||
|
for (let i = 0; i < names.length; i++)
|
||||||
|
if (names[i] === bufferCurrent) {
|
||||||
|
bufferActivate(names.at(--i))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'PageDown':
|
||||||
|
names = [...buffers.keys()]
|
||||||
|
for (let i = 0; i < names.length; i++)
|
||||||
|
if (names[i] === bufferCurrent) {
|
||||||
|
bufferActivate(names.at(++i) || names[0])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user