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