diff options
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | NEWS | 10 | ||||
m--------- | liberty | 0 | ||||
-rw-r--r-- | xA/xA.go | 2 | ||||
-rw-r--r-- | xC.c | 42 | ||||
-rw-r--r-- | xC.lxdr | 53 | ||||
-rw-r--r-- | xM/main.swift | 2 | ||||
-rw-r--r-- | xP/public/xP.js | 24 | ||||
-rw-r--r-- | xT/xT.cpp | 10 | ||||
-rw-r--r-- | xW/xW.cpp | 10 |
10 files changed, 113 insertions, 42 deletions
@@ -1,4 +1,4 @@ -Copyright (c) 2014 - 2024, Přemysl Eric Janouch <p@janouch.name> +Copyright (c) 2014 - 2025, Přemysl Eric Janouch <p@janouch.name> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. @@ -1,3 +1,13 @@ +Unreleased + + * xC: added more characters as nickname delimiters, + so that @nick works as a highlight + + * xP: added a network lag indicator to the user interface + + * Bumped relay protocol version + + 2.1.0 (2024-12-19) "Bunnyrific" * xC: fixed a crash when the channel topic had too many formatting items diff --git a/liberty b/liberty -Subproject af889b733e81fa40d7a7ff652386585115e186f +Subproject b69d3f8692b1d34f9b8616e046cdabe0d2fb67c @@ -339,6 +339,8 @@ func relaySend(data RelayCommandData, callback callback) bool { } if callback != nil { commandCallbacks[m.CommandSeq] = callback + } else { + // TODO(p) } commandSeq++ @@ -1818,6 +1818,7 @@ struct client uint32_t event_seq; ///< Outgoing message counter bool initialized; ///< Initial sync took place + bool shutdown; ///< Shutting down struct poller_fd socket_event; ///< The socket can be read/written to }; @@ -4168,9 +4169,7 @@ 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) + if (!c->initialized || c->shutdown || c->socket_fd == -1) return; // liberty has msg_{reader,writer} already, but they use 8-byte lengths. @@ -4180,7 +4179,10 @@ relay_send (struct client *c) || (frame_len = c->write_buffer.len - frame_len_pos - 4) > UINT32_MAX) { print_error ("serialization failed, killing client"); - client_kill (c); + // FIXME: This must not be done immediately! + //client_kill (c); + // TODO: Perhaps set an idle task that collects shutdown clients. + c->shutdown = true; return; } @@ -15716,26 +15718,31 @@ client_process_message (struct client *c, return true; } + bool acknowledge = false; switch (m->data.command) { case RELAY_COMMAND_HELLO: + c->initialized = true; if (m->data.hello.version != RELAY_VERSION) { - // TODO: This should send back an error message and shut down. log_global_error (c->ctx, "Protocol version mismatch, killing client"); - return false; + relay_prepare_error (c->ctx, + m->command_seq, "Protocol version mismatch"); + relay_send (c); + + c->shutdown = true; + return true; } - c->initialized = true; client_resync (c); + acknowledge = true; break; case RELAY_COMMAND_PING: - relay_prepare_response (c->ctx, m->command_seq) - ->data.command = RELAY_COMMAND_PING; - relay_send (c); + acknowledge = true; break; case RELAY_COMMAND_ACTIVE: reset_autoaway (c->ctx); + acknowledge = true; break; case RELAY_COMMAND_BUFFER_COMPLETE: client_process_buffer_complete (c, m->command_seq, buffer, @@ -15743,12 +15750,15 @@ client_process_message (struct client *c, break; case RELAY_COMMAND_BUFFER_ACTIVATE: buffer_activate (c->ctx, buffer); + acknowledge = true; break; case RELAY_COMMAND_BUFFER_INPUT: client_process_buffer_input (c, buffer, m->data.buffer_input.text.str); + acknowledge = true; break; case RELAY_COMMAND_BUFFER_TOGGLE_UNIMPORTANT: buffer_toggle_unimportant (c->ctx, buffer); + acknowledge = true; break; case RELAY_COMMAND_BUFFER_LOG: client_process_buffer_log (c, m->command_seq, buffer); @@ -15758,6 +15768,12 @@ client_process_message (struct client *c, relay_prepare_error (c->ctx, m->command_seq, "Unknown command"); relay_send (c); } + if (acknowledge) + { + relay_prepare_response (c->ctx, m->command_seq) + ->data.command = m->data.command; + relay_send (c); + } return true; } @@ -15851,7 +15867,13 @@ 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); + + // There must be something in the write buffer if you request shutdown. + if (c->shutdown && !c->write_buffer.len) + client_kill (c); + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1,7 +1,8 @@ // Backwards-compatible protocol version. -const VERSION = 1; +const VERSION = 2; // From the frontend to the relay. +// All commands receive either an Event.RESPONSE, or an Event.ERROR. struct CommandMessage { // The command sequence number will be repeated in responses // in the respective fields. @@ -32,13 +33,10 @@ struct CommandMessage { // XXX: Perhaps this should rather be handled through a /buffer command. case BUFFER_TOGGLE_UNIMPORTANT: string buffer_name; - case PING_RESPONSE: - u32 event_seq; - - // Only these commands may produce Event.RESPONSE, as below, - // but any command may produce an error. case PING: void; + case PING_RESPONSE: + u32 event_seq; case BUFFER_COMPLETE: string buffer_name; string text; @@ -52,6 +50,9 @@ struct CommandMessage { struct EventMessage { u32 event_seq; union EventData switch (enum Event { + ERROR, + RESPONSE, + PING, BUFFER_LINE, BUFFER_UPDATE, @@ -64,12 +65,28 @@ struct EventMessage { SERVER_UPDATE, SERVER_RENAME, SERVER_REMOVE, - ERROR, - RESPONSE, } event) { + // Restriction: command_seq strictly follows the sequence received + // by the relay, across both of these replies. + case ERROR: + u32 command_seq; + string error; + case RESPONSE: + u32 command_seq; + union ResponseData switch (Command command) { + case BUFFER_COMPLETE: + u32 start; + string completions<>; + case BUFFER_LOG: + // UTF-8, but not guaranteed. + u8 log<>; + default: + // Reception acknowledged. + void; + } data; + case PING: void; - case BUFFER_LINE: string buffer_name; // Whether the line should also be displayed in the active buffer. @@ -188,23 +205,5 @@ struct EventMessage { string new; case SERVER_REMOVE: string server_name; - - // Restriction: command_seq strictly follows the sequence received - // by the relay, across both of these replies. - case ERROR: - u32 command_seq; - string error; - case RESPONSE: - u32 command_seq; - union ResponseData switch (Command command) { - case PING: - void; - case BUFFER_COMPLETE: - u32 start; - string completions<>; - case BUFFER_LOG: - // UTF-8, but not guaranteed. - u8 log<>; - } data; } data; }; diff --git a/xM/main.swift b/xM/main.swift index 48f26c4..1235f04 100644 --- a/xM/main.swift +++ b/xM/main.swift @@ -175,6 +175,8 @@ class RelayRPC { let m = RelayCommandMessage(commandSeq: self.commandSeq, data: data) if let callback = callback { self.commandCallbacks[m.commandSeq] = callback + } else { + // TODO(p): Add an empty callback. } var w = RelayWriter() diff --git a/xP/public/xP.js b/xP/public/xP.js index 6035db3..bc91bb3 100644 --- a/xP/public/xP.js +++ b/xP/public/xP.js @@ -69,14 +69,22 @@ class RelayRPC extends EventTarget { let e = message.data switch (e.event) { case Relay.Event.Error: - if (this.promised[e.commandSeq] !== undefined) - this.promised[e.commandSeq].reject(e.error) + let p = this.promised[e.commandSeq] + // TODO(p): Network indicator. + if (p === true) + break + else if (p !== undefined) + p.reject(e.error) else console.error(`Unawaited error: ${e.error}`) break case Relay.Event.Response: - if (this.promised[e.commandSeq] !== undefined) - this.promised[e.commandSeq].resolve(e.data) + let p = this.promised[e.commandSeq] + // TODO(p): Network indicator. + if (p === true) + break + else if (p !== undefined) + p.resolve(e.data) else console.error("Unawaited response") break @@ -110,6 +118,9 @@ class RelayRPC extends EventTarget { this.ws.send(JSON.stringify({commandSeq: seq, data: params})) + // TODO(p): Network indicator. + this.promised[seq] = true + // Automagically detect if we want a result. let data = undefined const promise = new Promise( @@ -998,6 +1009,11 @@ let Input = { onKeyDown: event => { // TODO: And perhaps on other actions, too. + // TODO: Throttle these, for example by remembering when the last + // one was sent (or attempted to be sent), then setting a timeout + // and bumping that timeout when already present. + // Or even just refusing to resend it within a timeframe. + // This deserves a function. rpc.send({command: 'Active'}) let b = buffers.get(bufferCurrent) @@ -180,6 +180,14 @@ beep() // --- Networking -------------------------------------------------------------- static void +on_relay_generic_response( + std::wstring error, const Relay::ResponseData *response) +{ + if (!response) + show_error_message(QString::fromStdWString(error)); +} + +static void relay_send(Relay::CommandData *data, Callback callback = {}) { Relay::CommandMessage m = {}; @@ -190,6 +198,8 @@ relay_send(Relay::CommandData *data, Callback callback = {}) if (callback) g.command_callbacks[m.command_seq] = std::move(callback); + else + g.command_callbacks[m.command_seq] = on_relay_generic_response; auto len = qToBigEndian<uint32_t>(w.data.size()); auto prefix = reinterpret_cast<const char *>(&len); @@ -222,6 +222,14 @@ relay_try_write(std::wstring &error) } static void +on_relay_generic_response( + std::wstring error, const Relay::ResponseData *response) +{ + if (!response) + show_error_message(error.c_str()); +} + +static void relay_send(Relay::CommandData *data, Callback callback = {}) { Relay::CommandMessage m = {}; @@ -232,6 +240,8 @@ relay_send(Relay::CommandData *data, Callback callback = {}) if (callback) g.command_callbacks[m.command_seq] = std::move(callback); + else + g.command_callbacks[m.command_seq] = on_relay_generic_response; uint32_t len = htonl(w.data.size()); uint8_t *prefix = reinterpret_cast<uint8_t *>(&len); |