degesch: some more progress
Whoa, this thing is huge. Started implementing the basis for IRC and user command handlers.
This commit is contained in:
		
							parent
							
								
									08c0027397
								
							
						
					
					
						commit
						c421532e6e
					
				
							
								
								
									
										208
									
								
								degesch.c
									
									
									
									
									
								
							
							
						
						
									
										208
									
								
								degesch.c
									
									
									
									
									
								
							| @ -78,6 +78,8 @@ static struct config_item g_config_table[] = | ||||
| 	{ "socks_username",  NULL,    "SOCKS auth. username"                     }, | ||||
| 	{ "socks_password",  NULL,    "SOCKS auth. password"                     }, | ||||
| 
 | ||||
| 	{ "isolate_buffers", "off",   "Isolate global/server buffers"            }, | ||||
| 
 | ||||
| 	{ ATTR_PROMPT,       NULL,    "Terminal attributes for the prompt"       }, | ||||
| 	{ ATTR_RESET,        NULL,    "String to reset terminal attributes"      }, | ||||
| 	{ ATTR_WARNING,      NULL,    "Terminal attributes for warnings"         }, | ||||
| @ -244,6 +246,7 @@ struct app_context | ||||
| 	enum color_mode color_mode;         ///< Colour output mode
 | ||||
| 	bool reconnect;                     ///< Whether to reconnect on conn. fail.
 | ||||
| 	unsigned long reconnect_delay;      ///< Reconnect delay in seconds
 | ||||
| 	bool isolate_buffers;               ///< Isolate global/server buffers
 | ||||
| 
 | ||||
| 	// Server connection:
 | ||||
| 
 | ||||
| @ -810,7 +813,8 @@ buffer_add (struct app_context *ctx, struct buffer *buffer) | ||||
| 	str_map_set (&ctx->buffers_by_name, buffer->name, buffer); | ||||
| 	LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); | ||||
| 
 | ||||
| 	// TODO: refresh the prompt?  Or caller?
 | ||||
| 	// In theory this can't cause changes in the prompt
 | ||||
| 	refresh_prompt (ctx); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -848,7 +852,7 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) | ||||
| 	if (buffer == ctx->server_buffer) | ||||
| 		ctx->server_buffer = NULL; | ||||
| 
 | ||||
| 	// TODO: refresh the prompt?  Or caller?
 | ||||
| 	refresh_prompt (ctx); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -931,7 +935,7 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer) | ||||
| 			rl_redisplay (); | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: refresh the prompt?  Or caller?
 | ||||
| 	refresh_prompt (ctx); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -1395,6 +1399,38 @@ init_readline (void) | ||||
| 
 | ||||
| // --- Input handling ----------------------------------------------------------
 | ||||
| 
 | ||||
| // TODO: we will need a proper mode parser; to be shared with kike
 | ||||
| // TODO: we alse definitely need to parse server capability messages
 | ||||
| 
 | ||||
| static void | ||||
| irc_handle_ping (struct app_context *ctx, const struct irc_message *msg) | ||||
| { | ||||
| 	if (msg->params.len) | ||||
| 		irc_send (ctx, "PONG :%s", msg->params.vector[0]); | ||||
| 	else | ||||
| 		irc_send (ctx, "PONG"); | ||||
| } | ||||
| 
 | ||||
| static struct irc_handler | ||||
| { | ||||
| 	char *name; | ||||
| 	void (*handler) (struct app_context *ctx, const struct irc_message *msg); | ||||
| } | ||||
| g_irc_handlers[] = | ||||
| { | ||||
| 	// This list needs to stay sorted
 | ||||
| 	// TODO: handle as much as we can
 | ||||
| 	{ "PING", irc_handle_ping }, | ||||
| }; | ||||
| 
 | ||||
| static int | ||||
| irc_handler_cmp_by_name (const void *a, const void *b) | ||||
| { | ||||
| 	const struct irc_handler *first  = a; | ||||
| 	const struct irc_handler *second = b; | ||||
| 	return strcasecmp_ascii (first->name, second->name); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| irc_process_message (const struct irc_message *msg, | ||||
| 	const char *raw, void *user_data) | ||||
| @ -1414,16 +1450,7 @@ irc_process_message (const struct irc_message *msg, | ||||
| 			app_readline_restore (&state, ctx->readline_prompt); | ||||
| 	} | ||||
| 
 | ||||
| 	bool show_to_user = true; | ||||
| 	if (!strcasecmp (msg->command, "PING")) | ||||
| 	{ | ||||
| 		show_to_user = false; | ||||
| 		if (msg->params.len) | ||||
| 			irc_send (ctx, "PONG :%s", msg->params.vector[0]); | ||||
| 		else | ||||
| 			irc_send (ctx, "PONG"); | ||||
| 	} | ||||
| 	else if (!ctx->irc_ready && (!strcasecmp (msg->command, "MODE") | ||||
| 	if (!ctx->irc_ready && (!strcasecmp (msg->command, "MODE") | ||||
| 		|| !strcasecmp (msg->command, "376")    // RPL_ENDOFMOTD
 | ||||
| 		|| !strcasecmp (msg->command, "422")))  // ERR_NOMOTD
 | ||||
| 	{ | ||||
| @ -1435,29 +1462,56 @@ irc_process_message (const struct irc_message *msg, | ||||
| 		if (autojoin) | ||||
| 			irc_send (ctx, "JOIN :%s", autojoin); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// TODO: whatever processing we need
 | ||||
| 	} | ||||
| 
 | ||||
| 	// This is going to be a lot more complicated
 | ||||
| 	if (show_to_user) | ||||
| 	struct irc_handler key = { .name = msg->command }; | ||||
| 	struct irc_handler *handler = bsearch (&key, g_irc_handlers, | ||||
| 		N_ELEMENTS (g_irc_handlers), sizeof key, irc_handler_cmp_by_name); | ||||
| 	if (handler) | ||||
| 		handler->handler (ctx, msg); | ||||
| 
 | ||||
| 	// Numerics typically have human-readable information
 | ||||
| 	unsigned long dummy; | ||||
| 	if (xstrtoul (&dummy, msg->command, 10)) | ||||
| 		// TODO: ensure proper encoding
 | ||||
| 		// FIXME: print to the server buffer
 | ||||
| 		print_status ("%s", raw); | ||||
| } | ||||
| 
 | ||||
| // TODO: load and preprocess this table so that shortcuts are accepted
 | ||||
| struct command_handler | ||||
| // --- User input handling -----------------------------------------------------
 | ||||
| 
 | ||||
| static void handle_command_help (struct app_context *, const char *); | ||||
| 
 | ||||
| static void | ||||
| handle_command_buffer (struct app_context *ctx, const char *arguments) | ||||
| { | ||||
| 	// TODO: parse the arguments
 | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| handle_command_quit (struct app_context *ctx, const char *arguments) | ||||
| { | ||||
| 	if (ctx->irc_fd != -1) | ||||
| 	{ | ||||
| 		if (*arguments) | ||||
| 			irc_send (ctx, "QUIT :%s", arguments); | ||||
| 		else | ||||
| 			irc_send (ctx, "QUIT :%s", PROGRAM_NAME " " PROGRAM_VERSION); | ||||
| 	} | ||||
| 	initiate_quit (ctx); | ||||
| } | ||||
| 
 | ||||
| static struct command_handler | ||||
| { | ||||
| 	char *name; | ||||
| 	void (*handler) (struct app_context *ctx, const char *arguments); | ||||
| 	// TODO: probably also a usage string
 | ||||
| } | ||||
| g_handlers[] = | ||||
| g_command_handlers[] = | ||||
| { | ||||
| 	{ "buffer",  NULL }, | ||||
| 	{ "help",    NULL }, | ||||
| 
 | ||||
| 	{ "help",    handle_command_help   }, | ||||
| 	{ "quit",    handle_command_quit   }, | ||||
| 	{ "buffer",  handle_command_buffer }, | ||||
| #if 0 | ||||
| 	{ "msg",     NULL }, | ||||
| 	{ "query",   NULL }, | ||||
| 	{ "notice",  NULL }, | ||||
| @ -1483,17 +1537,93 @@ g_handlers[] = | ||||
| 	{ "motd",    NULL }, | ||||
| 	{ "away",    NULL }, | ||||
| 	{ "quote",   NULL }, | ||||
| 	{ "quit",    NULL }, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| static void | ||||
| process_internal_command (struct app_context *ctx, const char *command) | ||||
| handle_command_help (struct app_context *ctx, const char *arguments) | ||||
| { | ||||
| 	// TODO: resolve commands from a map
 | ||||
| 	// TODO: show a list of all user commands
 | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| command_handler_cmp_by_length (const void *a, const void *b) | ||||
| { | ||||
| 	const struct command_handler *first  = a; | ||||
| 	const struct command_handler *second = b; | ||||
| 	return strlen (first->name) - strlen (second->name); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| init_partial_matching_user_command_map (struct str_map *partial) | ||||
| { | ||||
| 	str_map_init (partial); | ||||
| 	partial->key_xfrm = tolower_ascii_strxfrm; | ||||
| 
 | ||||
| 	// We process them from the longest to the shortest one,
 | ||||
| 	// so that common prefixes favor shorter entries
 | ||||
| 	struct command_handler *by_length[N_ELEMENTS (g_command_handlers)]; | ||||
| 	for (size_t i = 0; i < N_ELEMENTS (by_length); i++) | ||||
| 		by_length[i] = &g_command_handlers[i]; | ||||
| 	qsort (by_length, N_ELEMENTS (by_length), sizeof *by_length, | ||||
| 		command_handler_cmp_by_length); | ||||
| 
 | ||||
| 	for (size_t i = N_ELEMENTS (by_length); i--; ) | ||||
| 	{ | ||||
| 		char *copy = xstrdup (by_length[i]->name); | ||||
| 		for (size_t part = strlen (copy); part; part--) | ||||
| 		{ | ||||
| 			copy[part] = '\0'; | ||||
| 			str_map_set (partial, copy, by_length[i]); | ||||
| 		} | ||||
| 		free (copy); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| process_user_command (struct app_context *ctx, char *command) | ||||
| { | ||||
| 	// Trivially create a partial matching map
 | ||||
| 	static bool initialized = false; | ||||
| 	struct str_map partial; | ||||
| 	if (!initialized) | ||||
| 	{ | ||||
| 		init_partial_matching_user_command_map (&partial); | ||||
| 		initialized = true; | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: cut a single word (strtok_r()?)
 | ||||
| 	// TODO: if it's a number, switch to the given buffer
 | ||||
| 
 | ||||
| 	if (!strcmp (command, "quit")) | ||||
| 		initiate_quit (ctx); | ||||
| 	struct command_handler *handler = str_map_find (&partial, command); | ||||
| 	if (handler) | ||||
| 		// FIXME: pass arguments correctly
 | ||||
| 		handler->handler (ctx, ""); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| send_message_to_current_buffer (struct app_context *ctx, char *message) | ||||
| { | ||||
| 	struct buffer *buffer = ctx->current_buffer; | ||||
| 	if (!buffer) | ||||
| 	{ | ||||
| 		// TODO: print an error message to the global buffer
 | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	switch (buffer->type) | ||||
| 	{ | ||||
| 	case BUFFER_GLOBAL: | ||||
| 	case BUFFER_SERVER: | ||||
| 		// TODO: print a message to the buffer that it's not a channel
 | ||||
| 		break; | ||||
| 	case BUFFER_CHANNEL: | ||||
| 		// TODO: send an IRC message to the channel
 | ||||
| 		break; | ||||
| 	case BUFFER_PM: | ||||
| 		// TODO: send an IRC message to the user
 | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -1503,19 +1633,12 @@ process_input (struct app_context *ctx, char *user_input) | ||||
| 	size_t len; | ||||
| 
 | ||||
| 	if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, &len))) | ||||
| 	{ | ||||
| 		print_error ("character conversion failed for `%s'", "user input"); | ||||
| 		goto fail; | ||||
| 	} | ||||
| 
 | ||||
| 	if (*input == '/') | ||||
| 		process_internal_command (ctx, input + 1); | ||||
| 	else if (input[0] == '/' && input[1] != '/') | ||||
| 		process_user_command (ctx, input + 1); | ||||
| 	else | ||||
| 	{ | ||||
| 		// TODO: send a message to the current buffer
 | ||||
| 	} | ||||
| 		send_message_to_current_buffer (ctx, input); | ||||
| 
 | ||||
| fail: | ||||
| 	free (input); | ||||
| } | ||||
| 
 | ||||
| @ -2048,7 +2171,10 @@ load_config (struct app_context *ctx, struct error **e) | ||||
| 	if (!success) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (!irc_get_boolean_from_config (ctx, "reconnect", &ctx->reconnect, e)) | ||||
| 	if (!irc_get_boolean_from_config (ctx, | ||||
| 		"reconnect", &ctx->reconnect, e) | ||||
| 	 || !irc_get_boolean_from_config (ctx, | ||||
| 		"isolate_buffers", &ctx->isolate_buffers, e)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	const char *delay_str = str_map_find (&ctx->config, "reconnect_delay"); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user