Compare commits
	
		
			No commits in common. "b4ee523628b5e91ec06c6c8c61fe03507e1f412e" and "62773acaa099d7633a6d83d44b790f4f53fb274e" have entirely different histories.
		
	
	
		
			b4ee523628
			...
			62773acaa0
		
	
		
							
								
								
									
										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 SERVER_UPDATE, BUFFER_UPDATE, BUFFER_STATS, | 		// an initial stream of BUFFER_UPDATE, BUFFER_STATS, BUFFER_LINE, | ||||||
| 		// BUFFER_LINE, and finally a BUFFER_ACTIVATE message. | 		// and finally a BUFFER_ACTIVATE message. | ||||||
| 	case ACTIVE: | 	case ACTIVE: | ||||||
| 		void; | 		void; | ||||||
| 	case BUFFER_INPUT: | 	case BUFFER_INPUT: | ||||||
| @ -56,33 +56,14 @@ 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. | ||||||
| @ -149,23 +130,6 @@ 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,28 +3109,6 @@ 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 | ||||||
| @ -3270,51 +3248,6 @@ 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) | ||||||
| { | { | ||||||
| @ -4957,14 +4890,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); | ||||||
| @ -4976,9 +4909,6 @@ 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; | ||||||
| 
 | 
 | ||||||
| @ -4994,6 +4924,9 @@ 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); | ||||||
| @ -5107,9 +5040,6 @@ 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) | ||||||
| @ -5126,6 +5056,9 @@ 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); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -5213,14 +5146,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 * | ||||||
| @ -5732,17 +5665,6 @@ 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) | ||||||
| { | { | ||||||
| @ -5758,7 +5680,7 @@ irc_real_shutdown (struct server *s) | |||||||
| 		if (!soft_assert (errno == EINTR)) | 		if (!soft_assert (errno == EINTR)) | ||||||
| 			break; | 			break; | ||||||
| 
 | 
 | ||||||
| 	irc_set_state (s, IRC_HALF_CLOSED); | 	s->state = IRC_HALF_CLOSED; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static void | ||||||
| @ -5769,7 +5691,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
 | ||||||
| 	irc_set_state (s, IRC_CLOSING); | 	s->state = 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
 | ||||||
| @ -5791,7 +5713,7 @@ irc_destroy_connector (struct server *s) | |||||||
| 	s->socks_conn = NULL; | 	s->socks_conn = NULL; | ||||||
| 
 | 
 | ||||||
| 	// Not connecting anymore
 | 	// Not connecting anymore
 | ||||||
| 	irc_set_state (s, IRC_DISCONNECTED); | 	s->state = IRC_DISCONNECTED; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static void | ||||||
| @ -5822,7 +5744,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; | ||||||
| 	irc_set_state (s, IRC_DISCONNECTED); | 	s->state = IRC_DISCONNECTED; | ||||||
| 
 | 
 | ||||||
| 	str_reset (&s->read_buffer); | 	str_reset (&s->read_buffer); | ||||||
| 	str_reset (&s->write_buffer); | 	str_reset (&s->write_buffer); | ||||||
| @ -5883,6 +5805,8 @@ 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 | ||||||
| @ -6634,7 +6558,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"); | ||||||
| 	irc_set_state (s, IRC_CONNECTED); | 	s->state = 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; | ||||||
| @ -6643,6 +6567,8 @@ 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); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
| @ -6789,7 +6715,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) | ||||||
| 		irc_set_state (s, IRC_CONNECTING); | 		s->state = IRC_CONNECTING; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // --- Input prompt ------------------------------------------------------------
 | // --- Input prompt ------------------------------------------------------------
 | ||||||
| @ -8337,7 +8263,8 @@ 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); | ||||||
| 
 | 
 | ||||||
| 	irc_set_state (s, IRC_REGISTERED); | 	s->state = 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
 | ||||||
| @ -9495,9 +9422,6 @@ 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; | ||||||
| @ -9521,10 +9445,6 @@ 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)); | ||||||
| 
 | 
 | ||||||
| @ -10020,27 +9940,20 @@ 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; | ||||||
| 	lua_pushstring (L, lua_server_state_to_string (server->state)); | 	switch (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; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -15399,14 +15312,6 @@ 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,10 +17,6 @@ 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; | ||||||
| @ -43,8 +39,10 @@ body { | |||||||
| 	bottom: -1px; | 	bottom: -1px; | ||||||
| 	background: #ccc; | 	background: #ccc; | ||||||
| } | } | ||||||
| button { | .title { | ||||||
| 	font: inherit; | 	display: flex; | ||||||
|  | 	justify-content: space-between; | ||||||
|  | 	align-items: baseline; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .middle { | .middle { | ||||||
| @ -114,8 +112,9 @@ button { | |||||||
| 	font-weight: bold; | 	font-weight: bold; | ||||||
| } | } | ||||||
| .unread { | .unread { | ||||||
|  | 	height: 1px; | ||||||
| 	grid-column: span 2; | 	grid-column: span 2; | ||||||
| 	border-top: 1px solid #ff5f00; | 	background: #ff5f00; | ||||||
| } | } | ||||||
| .time { | .time { | ||||||
| 	padding: .1em .3em; | 	padding: .1em .3em; | ||||||
| @ -123,13 +122,6 @@ button { | |||||||
| 	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,9 +129,6 @@ 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.
 | ||||||
| @ -156,33 +153,6 @@ 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) | ||||||
| @ -193,8 +163,6 @@ 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 | ||||||
| @ -236,8 +204,6 @@ 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() | ||||||
| @ -254,8 +220,6 @@ 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) { | ||||||
| @ -266,10 +230,7 @@ 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 => { | ||||||
| @ -363,8 +324,9 @@ rpc.addEventListener('BufferLine', event => { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (line.isHighlight || | 	// TODO: Find some way of highlighting the tab in a browser.
 | ||||||
| 		(!visible && b.kind === 'PrivateMessage' && !line.isUnimportant)) { | 	// TODO: Also highlight on unseen private messages, like xC does.
 | ||||||
|  | 	if (line.isHighlight) { | ||||||
| 		beep() | 		beep() | ||||||
| 		if (!visible) | 		if (!visible) | ||||||
| 			b.highlighted = true | 			b.highlighted = true | ||||||
| @ -377,26 +339,6 @@ 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 = [ | ||||||
| @ -441,16 +383,13 @@ 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})` | ||||||
| @ -461,8 +400,6 @@ let BufferList = { | |||||||
| 				class: classes.join(' '), | 				class: classes.join(' '), | ||||||
| 			}, displayName) | 			}, displayName) | ||||||
| 		}) | 		}) | ||||||
| 
 |  | ||||||
| 		updateIcon(highlighted) |  | ||||||
| 		return m('.list', {}, items) | 		return m('.list', {}, items) | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
| @ -580,20 +517,13 @@ 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() | ||||||
| @ -601,11 +531,6 @@ 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) | ||||||
| @ -655,21 +580,6 @@ 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 => { | ||||||
| @ -688,22 +598,15 @@ let Input = { | |||||||
| 			command: 'BufferComplete', | 			command: 'BufferComplete', | ||||||
| 			bufferName: bufferCurrent, | 			bufferName: bufferCurrent, | ||||||
| 			text: textarea.value, | 			text: textarea.value, | ||||||
| 			position: utf8Encode( | 			position: textarea.selectionEnd, | ||||||
| 				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], | ||||||
| 					start, textarea.selectionEnd, 'end') | 					resp.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') | ||||||
| @ -805,7 +708,8 @@ 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)]), | ||||||
| 			m(Status), | 			// TODO: Indicate hideUnimportant.
 | ||||||
|  | 			m('.status', {}, bufferCurrent), | ||||||
| 			m(Input), | 			m(Input), | ||||||
| 		]) | 		]) | ||||||
| 	}, | 	}, | ||||||
| @ -817,8 +721,11 @@ 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 | ||||||
| @ -836,26 +743,6 @@ 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